pub mod error;
pub mod normalization;
pub mod types;
pub mod validation;
pub use error::A3Error;
pub use types::{
A3, A3_SCHEMA_URI, A3_VERSION, A3Index, Annotations, FlexEntry, Metadata, RegionEntry,
SiteEntry, VariantRecord,
};
pub use validation::validate;
use serde::Serialize as _;
pub fn a3_from_json(text: &str) -> Result<A3, A3Error> {
let raw: A3 = serde_json::from_str(text)?;
validate(raw)
}
pub fn a3_to_json(a3: &A3, indent: Option<usize>) -> Result<String, A3Error> {
match indent {
None => Ok(serde_json::to_string(a3).map_err(A3Error::Serialize)?),
Some(n) => {
let indent_str = " ".repeat(n);
let formatter = serde_json::ser::PrettyFormatter::with_indent(indent_str.as_bytes());
let mut buf = Vec::new();
let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
a3.serialize(&mut ser).map_err(A3Error::Serialize)?;
Ok(String::from_utf8(buf).expect("serde_json always produces valid UTF-8"))
}
}
}
pub fn residue_at(a3: &A3, position: u32) -> Option<char> {
if position == 0 || position > a3.sequence.len() as u32 {
return None;
}
a3.sequence
.as_bytes()
.get((position - 1) as usize)
.map(|&b| b as char)
}
pub fn variants_at(a3: &A3, position: u32) -> Vec<&VariantRecord> {
a3.annotations
.variant
.iter()
.filter(|v| v.position == position)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
const MINIMAL_JSON: &str = r#"{
"$schema": "https://schema.rtemis.org/a3/v1/schema.json",
"a3_version": "1.0.0",
"sequence": "MAEPRQ",
"annotations": {
"site": {},
"region": {},
"ptm": {},
"processing": {},
"variant": []
},
"metadata": {
"uniprot_id": "",
"description": "",
"reference": "",
"organism": ""
}
}"#;
#[test]
fn round_trip() {
let a3 = a3_from_json(MINIMAL_JSON).unwrap();
let json = a3_to_json(&a3, None).unwrap();
let a3_again = a3_from_json(&json).unwrap();
assert_eq!(
a3_to_json(&a3, None).unwrap(),
a3_to_json(&a3_again, None).unwrap()
);
}
#[test]
fn residue_at_valid_position() {
let a3 = a3_from_json(MINIMAL_JSON).unwrap();
assert_eq!(residue_at(&a3, 1), Some('M'));
assert_eq!(residue_at(&a3, 6), Some('Q'));
}
#[test]
fn residue_at_out_of_bounds() {
let a3 = a3_from_json(MINIMAL_JSON).unwrap();
assert_eq!(residue_at(&a3, 0), None);
assert_eq!(residue_at(&a3, 99), None);
}
#[test]
fn rejects_missing_schema() {
let json = r#"{"a3_version":"1.0.0","sequence":"MAEPRQ","annotations":{"site":{},"region":{},"ptm":{},"processing":{},"variant":[]},"metadata":{}}"#;
assert!(a3_from_json(json).is_err());
}
#[test]
fn rejects_wrong_schema_uri() {
let json = r#"{"$schema":"https://example.com/wrong","a3_version":"1.0.0","sequence":"MAEPRQ","annotations":{"site":{},"region":{},"ptm":{},"processing":{},"variant":[]},"metadata":{}}"#;
assert!(a3_from_json(json).is_err());
}
#[test]
fn rejects_missing_version() {
let json = r#"{"$schema":"https://schema.rtemis.org/a3/v1/schema.json","sequence":"MAEPRQ","annotations":{"site":{},"region":{},"ptm":{},"processing":{},"variant":[]},"metadata":{}}"#;
assert!(a3_from_json(json).is_err());
}
#[test]
fn rejects_unknown_top_level_key() {
let json = r#"{"$schema":"https://schema.rtemis.org/a3/v1/schema.json","a3_version":"1.0.0","sequence":"MAEPRQ","foo":"bar"}"#;
assert!(a3_from_json(json).is_err());
}
#[test]
fn rejects_unknown_metadata_key() {
let json = r#"{"$schema":"https://schema.rtemis.org/a3/v1/schema.json","a3_version":"1.0.0","sequence":"MAEPRQ","metadata":{"gene":"MAPT"}}"#;
assert!(a3_from_json(json).is_err());
}
#[test]
fn pretty_print_contains_newlines() {
let a3 = a3_from_json(MINIMAL_JSON).unwrap();
let pretty = a3_to_json(&a3, Some(2)).unwrap();
assert!(pretty.contains('\n'));
}
}