Rust 读取示例
下面示例面向第三方工具开发者:快速把 `.gen` 或 `.gjson` 读成一个轻量的序列对象。生产环境可以在这些代码基础上补充错误类型、schema 迁移和字段校验。
Cargo
依赖配置
[dependencies]
anyhow = "1"
rusqlite = { version = "0.32", features = ["bundled"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
bundled 会编译内置 SQLite,无需目标机预装 SQLite,便于跨平台分发 CLI 工具。版本参考:rusqlite 0.32、serde 1、serde_json 1、anyhow 1。
SQLite
读取 .gen
use anyhow::Result;
use rusqlite::{Connection, OptionalExtension};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::path::Path;
#[derive(Debug, Serialize, Deserialize)]
struct FeatureSegment {
start: i64,
end: i64,
strand: Option<String>,
name: Option<String>,
color: Option<String>,
}
#[derive(Debug)]
struct Feature {
id: String,
name: String,
feature_type: String,
start: i64,
end: i64,
strand: String,
color: Option<String>,
label: Option<String>,
note: Option<String>,
frame: Option<i64>,
visible: bool,
segments: Vec<FeatureSegment>,
qualifiers: HashMap<String, Vec<String>>,
}
#[derive(Debug)]
struct GeneProject {
metadata: HashMap<String, String>,
sequence: String,
features: Vec<Feature>,
}
fn validate_sqlite_header(path: &Path) -> Result<()> {
let mut file = File::open(path)?;
let mut header = [0_u8; 16];
file.read_exact(&mut header)?;
anyhow::ensure!(&header == b"SQLite format 3\0", "not a .gen SQLite file");
Ok(())
}
fn read_metadata(conn: &Connection) -> Result<HashMap<String, String>> {
let mut stmt = conn.prepare("SELECT key, value FROM project_meta")?;
let rows = stmt.query_map([], |row| {
Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?))
})?;
let mut metadata = HashMap::new();
for row in rows {
let (key, value) = row?;
metadata.insert(key, value);
}
Ok(metadata)
}
fn read_sequence(conn: &Connection) -> Result<String> {
let mut stmt = conn.prepare(
"SELECT bases FROM sequence_blocks ORDER BY block_index"
)?;
let rows = stmt.query_map([], |row| row.get::<_, String>(0))?;
let mut sequence = String::new();
for row in rows {
sequence.push_str(&row?);
}
Ok(sequence)
}
fn parse_json_or_default<T>(raw: Option<String>) -> Result<T>
where
T: serde::de::DeserializeOwned + Default,
{
match raw {
Some(value) if !value.trim().is_empty() => Ok(serde_json::from_str(&value)?),
_ => Ok(T::default()),
}
}
fn read_features(conn: &Connection) -> Result<Vec<Feature>> {
let mut stmt = conn.prepare(
"SELECT id, name, type, start_pos, end_pos, strand, color, label, note,
frame, visible, segments, qualifiers
FROM features
ORDER BY start_pos"
)?;
let rows = stmt.query_map([], |row| {
let segments_raw: Option<String> = row.get(11)?;
let qualifiers_raw: Option<String> = row.get(12)?;
let segments = parse_json_or_default(segments_raw)
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(e.into()))?;
let qualifiers = parse_json_or_default(qualifiers_raw)
.map_err(|e| rusqlite::Error::ToSqlConversionFailure(e.into()))?;
Ok(Feature {
id: row.get(0)?,
name: row.get(1)?,
feature_type: row.get(2)?,
start: row.get(3)?,
end: row.get(4)?,
strand: row.get(5)?,
color: row.get(6)?,
label: row.get(7)?,
note: row.get(8)?,
frame: row.get(9)?,
visible: row.get::<_, i64>(10)? != 0,
segments,
qualifiers,
})
})?;
let mut features = Vec::new();
for row in rows {
features.push(row?);
}
Ok(features)
}
fn read_gen(path: impl AsRef<Path>) -> Result<GeneProject> {
let path = path.as_ref();
validate_sqlite_header(path)?;
let conn = Connection::open(path)?;
Ok(GeneProject {
metadata: read_metadata(&conn)?,
sequence: read_sequence(&conn)?,
features: read_features(&conn)?,
})
}
fn main() -> Result<()> {
let project = read_gen("example.gen")?;
println!("name: {:?}", project.metadata.get("name"));
println!("length: {}", project.sequence.len());
println!("features: {}", project.features.len());
Ok(())
}
如果需要读取 primers、base_color_ranges、alignment_entries,可按同样方式查询对应表。坐标已经是 1-based inclusive,无需转换。
预期输出(对一个含若干 feature 的 example.gen):
name: Some("pUC19")
length: 2686
features: 12
JSON
读取 .gjson
use anyhow::Result;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
#[derive(Debug, Deserialize, Default)]
struct GjsonFeatureSegment {
start: i64,
end: i64,
strand: Option<String>,
name: Option<String>,
color: Option<String>,
}
#[derive(Debug, Deserialize)]
struct GjsonFeature {
id: String,
name: String,
#[serde(rename = "type")]
feature_type: String,
start: i64,
end: i64,
strand: String,
color: Option<String>,
label: Option<String>,
note: Option<String>,
frame: Option<i64>,
#[serde(default = "default_true")]
visible: bool,
#[serde(default)]
segments: Vec<GjsonFeatureSegment>,
#[serde(default)]
qualifiers: HashMap<String, Vec<String>>,
}
#[derive(Debug, Deserialize)]
struct GjsonFile {
version: String,
sequence: String,
#[serde(default)]
custom_name: String,
#[serde(default)]
description: String,
#[serde(default)]
isCircular: bool,
isDoubleStranded: Option<bool>,
accession: Option<String>,
organism: Option<String>,
date: Option<String>,
moleculeType: Option<String>,
division: Option<String>,
gbVersion: Option<String>,
keywords: Option<String>,
source: Option<String>,
comments: Option<String>,
#[serde(default)]
features: Vec<GjsonFeature>,
}
fn default_true() -> bool {
true
}
fn read_gjson(path: &str) -> Result<GjsonFile> {
let text = fs::read_to_string(path)?;
let file: GjsonFile = serde_json::from_str(&text)?;
anyhow::ensure!(!file.version.is_empty(), "missing gjson version");
anyhow::ensure!(!file.sequence.is_empty(), "missing sequence");
Ok(file)
}
fn main() -> Result<()> {
let file = read_gjson("example.gjson")?;
println!("version: {}", file.version);
println!("length: {}", file.sequence.len());
println!("features: {}", file.features.len());
Ok(())
}
`.gjson` 中的 length 只是写出时的辅助值,读取程序应以 sequence.len() 为准。
预期输出(对一个有效 example.gjson):
version: 1.9
length: 8
features: 1