use std::collections::BTreeSet;
use std::fmt;
use indexmap::IndexMap;
use serde::de::{Error as _, MapAccess, SeqAccess, Visitor};
use crate::v1_1::bmm_persistence::error::PBmmReadError;
use crate::v1_1::bmm_persistence::p_bmm_schema::PBmmSchema;
use crate::v1_1::bmm_persistence::reader::{join_path, read_schema_tree};
use crate::v1_1::odin::{OdinInterval, OdinKey, OdinValue};
pub const KEYED_COLLECTIONS: &[&str] = &[
"includes",
"packages",
"primitive_types",
"class_definitions",
"constants",
"properties",
"functions",
"invariants",
"generic_parameter_defs",
"ancestor_defs",
"aliases",
"parameters",
"pre_conditions",
"post_conditions",
];
const TYPE_MEMBER: &str = "_type";
const CARDINALITY: &str = "cardinality";
pub fn read_schema_json(src: &str) -> Result<PBmmSchema, PBmmReadError> {
let root: JsonNode = serde_json::from_str(src).map_err(|error| json_syntax(&error))?;
let tree = to_odin("", &root, false)?;
read_schema_tree(&tree)
}
fn json_syntax(error: &serde_json::Error) -> PBmmReadError {
PBmmReadError::Json {
line: error.line(),
column: error.column(),
message: error.to_string(),
}
}
enum JsonNode {
Null,
Bool(bool),
Number(serde_json::Number),
String(String),
Array(Vec<JsonNode>),
Object(Vec<(String, JsonNode)>),
}
impl<'de> serde::Deserialize<'de> for JsonNode {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_any(NodeVisitor)
}
}
struct NodeVisitor;
impl<'de> Visitor<'de> for NodeVisitor {
type Value = JsonNode;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a JSON value")
}
fn visit_unit<E: serde::de::Error>(self) -> Result<JsonNode, E> {
Ok(JsonNode::Null)
}
fn visit_bool<E: serde::de::Error>(self, value: bool) -> Result<JsonNode, E> {
Ok(JsonNode::Bool(value))
}
fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<JsonNode, E> {
Ok(JsonNode::Number(value.into()))
}
fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<JsonNode, E> {
Ok(JsonNode::Number(value.into()))
}
fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<JsonNode, E> {
serde_json::Number::from_f64(value)
.map(JsonNode::Number)
.ok_or_else(|| E::custom("a non-finite number"))
}
fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<JsonNode, E> {
Ok(JsonNode::String(value.to_owned()))
}
fn visit_string<E: serde::de::Error>(self, value: String) -> Result<JsonNode, E> {
Ok(JsonNode::String(value))
}
fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<JsonNode, A::Error> {
let mut items = Vec::new();
while let Some(item) = seq.next_element()? {
items.push(item);
}
Ok(JsonNode::Array(items))
}
fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<JsonNode, A::Error> {
let mut seen = BTreeSet::new();
let mut members = Vec::new();
while let Some(name) = map.next_key::<String>()? {
if !seen.insert(name.clone()) {
return Err(A::Error::custom(format_args!("duplicate member `{name}`")));
}
let value = map.next_value()?;
members.push((name, value));
}
Ok(JsonNode::Object(members))
}
}
fn kind_name(node: &JsonNode) -> &'static str {
match node {
JsonNode::Null => "null",
JsonNode::Bool(_) => "a boolean",
JsonNode::Number(_) => "a number",
JsonNode::String(_) => "a string",
JsonNode::Array(_) => "an array",
JsonNode::Object(_) => "an object",
}
}
fn to_odin(path: &str, node: &JsonNode, keyed: bool) -> Result<OdinValue, PBmmReadError> {
match node {
JsonNode::Null => Err(PBmmReadError::WrongValueShape {
path: path.to_owned(),
expected: "a value",
found: kind_name(node),
}),
JsonNode::Bool(flag) => Ok(OdinValue::Boolean(*flag)),
JsonNode::Number(number) => number_to_odin(path, number),
JsonNode::String(text) => Ok(OdinValue::String(text.clone())),
JsonNode::Array(items) => array_to_odin(path, items),
JsonNode::Object(members) if keyed => members
.iter()
.map(|(key, value)| {
let entry = to_odin(&join_path(path, key), value, false)?;
Ok((OdinKey::String(key.clone()), entry))
})
.collect::<Result<_, PBmmReadError>>()
.map(OdinValue::KeyedList),
JsonNode::Object(members) => object_to_odin(path, members),
}
}
fn number_to_odin(path: &str, number: &serde_json::Number) -> Result<OdinValue, PBmmReadError> {
if let Some(integer) = number.as_i64() {
return Ok(OdinValue::Integer(integer));
}
match number.as_f64() {
Some(real) if !number.is_u64() => Ok(OdinValue::Real(real)),
_ => Err(PBmmReadError::WrongValueShape {
path: path.to_owned(),
expected: "an integer within the 64-bit signed range or a real",
found: "an out-of-range integer",
}),
}
}
fn array_to_odin(path: &str, items: &[JsonNode]) -> Result<OdinValue, PBmmReadError> {
if items
.iter()
.any(|item| matches!(item, JsonNode::Object(_) | JsonNode::Array(_)))
{
return (1_i64..)
.zip(items)
.map(|(key, item)| {
let entry = to_odin(&join_path(path, &key.to_string()), item, false)?;
Ok((OdinKey::Integer(key), entry))
})
.collect::<Result<_, PBmmReadError>>()
.map(OdinValue::KeyedList);
}
items
.iter()
.map(|item| to_odin(path, item, false))
.collect::<Result<_, PBmmReadError>>()
.map(OdinValue::List)
}
fn object_to_odin(path: &str, members: &[(String, JsonNode)]) -> Result<OdinValue, PBmmReadError> {
let mut attributes = IndexMap::new();
let mut marker = None;
for (name, value) in members {
if name == TYPE_MEMBER {
let JsonNode::String(type_name) = value else {
return Err(PBmmReadError::WrongValueShape {
path: join_path(path, name),
expected: "a type name",
found: kind_name(value),
});
};
marker = Some(type_name.clone());
continue;
}
let member_path = join_path(path, name);
let mapped = match value {
JsonNode::Object(limits) if name == CARDINALITY => cardinality(&member_path, limits)?,
other => to_odin(
&member_path,
other,
KEYED_COLLECTIONS.contains(&name.as_str()),
)?,
};
attributes.insert(name.clone(), mapped);
}
let object = OdinValue::Object(attributes);
Ok(match marker {
Some(rm_type) => OdinValue::Typed {
rm_type,
value: Box::new(object),
},
None => object,
})
}
fn cardinality(path: &str, members: &[(String, JsonNode)]) -> Result<OdinValue, PBmmReadError> {
let mut lower = None;
let mut upper = None;
let mut lower_unbounded = None;
let mut upper_unbounded = None;
let mut lower_included = None;
let mut upper_included = None;
for (name, value) in members {
match name.as_str() {
"lower" => lower = Some(interval_limit(path, value)?),
"upper" => upper = Some(interval_limit(path, value)?),
"lower_unbounded" => lower_unbounded = Some(interval_flag(path, value)?),
"upper_unbounded" => upper_unbounded = Some(interval_flag(path, value)?),
"lower_included" => lower_included = Some(interval_flag(path, value)?),
"upper_included" => upper_included = Some(interval_flag(path, value)?),
_ => {
return Err(PBmmReadError::UnknownAttribute {
path: path.to_owned(),
attribute: name.clone(),
});
}
}
}
let lower_unbounded = lower_unbounded.unwrap_or(lower.is_none());
let upper_unbounded = upper_unbounded.unwrap_or(upper.is_none());
let lower_included = lower_included.unwrap_or(!lower_unbounded);
let upper_included = upper_included.unwrap_or(!upper_unbounded);
let consistent = lower_unbounded != lower.is_some()
&& upper_unbounded != upper.is_some()
&& !(lower_unbounded && lower_included)
&& !(upper_unbounded && upper_included);
if !consistent {
return Err(PBmmReadError::UnsupportedCardinality {
path: path.to_owned(),
});
}
Ok(OdinValue::Interval(OdinInterval::Range {
lower: lower.map(|limit| Box::new(OdinValue::Integer(limit))),
lower_included,
upper: upper.map(|limit| Box::new(OdinValue::Integer(limit))),
upper_included,
}))
}
fn interval_limit(path: &str, value: &JsonNode) -> Result<i64, PBmmReadError> {
match value {
JsonNode::Number(number) => {
number
.as_i64()
.ok_or_else(|| PBmmReadError::UnsupportedCardinality {
path: path.to_owned(),
})
}
_ => Err(PBmmReadError::UnsupportedCardinality {
path: path.to_owned(),
}),
}
}
fn interval_flag(path: &str, value: &JsonNode) -> Result<bool, PBmmReadError> {
match value {
JsonNode::Bool(flag) => Ok(*flag),
_ => Err(PBmmReadError::UnsupportedCardinality {
path: path.to_owned(),
}),
}
}