Skip to main content

phoxal_model/
lib.rs

1//! Canonical immutable runtime robot model and strict `robot.json` encoding.
2
3pub mod asset;
4pub mod component;
5pub mod robot;
6pub mod simulation;
7pub mod structure;
8
9pub use asset::AssetId;
10pub use robot::{ROBOT_SCHEMA, Robot, RobotIdentity};
11
12/// Compiler linkage that is intentionally absent from the runtime model API.
13#[doc(hidden)]
14pub mod __private {
15    /// Construct a validated structure from the manifest compiler's normalized
16    /// JSON value. Runtime consumers decode a complete [`crate::Robot`] instead.
17    pub fn structure_from_compiler_value(
18        document: serde_json::Value,
19    ) -> Result<crate::structure::Structure, crate::ModelError> {
20        crate::structure::Structure::from_compiler_value(document)
21    }
22}
23
24/// Invalid canonical robot semantics.
25#[derive(Debug, thiserror::Error)]
26pub enum ModelError {
27    #[error("{0}")]
28    Invalid(String),
29}
30
31/// Canonical encoding failure.
32#[derive(Debug, thiserror::Error)]
33#[error("failed to encode canonical robot: {0}")]
34pub struct EncodeError(#[from] serde_json::Error);
35
36/// Canonical decoding failure.
37#[derive(Debug, thiserror::Error)]
38pub enum DecodeError {
39    #[error("invalid canonical robot JSON: {0}")]
40    Json(#[from] serde_json::Error),
41    #[error("canonical robot is missing string field 'schema'")]
42    MissingSchema,
43    #[error("unsupported robot schema '{0}'")]
44    UnsupportedSchema(String),
45    #[error("invalid canonical robot: {0}")]
46    Model(#[from] ModelError),
47}
48
49mod strict_json {
50    use std::collections::BTreeMap;
51
52    use serde::de::{Deserialize, Deserializer, MapAccess, SeqAccess, Visitor};
53    use serde_json::{Number, Value};
54
55    use crate::DecodeError;
56
57    pub(crate) fn parse(bytes: &[u8]) -> Result<Value, DecodeError> {
58        let mut deserializer = serde_json::Deserializer::from_slice(bytes);
59        let value = StrictValue::deserialize(&mut deserializer)?.0;
60        deserializer.end()?;
61        Ok(value)
62    }
63
64    struct StrictValue(Value);
65
66    impl<'de> Deserialize<'de> for StrictValue {
67        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
68            deserializer.deserialize_any(StrictVisitor)
69        }
70    }
71
72    struct StrictVisitor;
73
74    impl<'de> Visitor<'de> for StrictVisitor {
75        type Value = StrictValue;
76
77        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78            formatter.write_str("a JSON value without duplicate object fields")
79        }
80
81        fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E> {
82            Ok(StrictValue(Value::Bool(value)))
83        }
84
85        fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E> {
86            Ok(StrictValue(Value::Number(Number::from(value))))
87        }
88
89        fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E> {
90            Ok(StrictValue(Value::Number(Number::from(value))))
91        }
92
93        fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<Self::Value, E> {
94            Number::from_f64(value)
95                .map(Value::Number)
96                .map(StrictValue)
97                .ok_or_else(|| E::custom("non-finite JSON number"))
98        }
99
100        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
101            Ok(StrictValue(Value::String(value.to_string())))
102        }
103
104        fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
105            Ok(StrictValue(Value::String(value)))
106        }
107
108        fn visit_none<E>(self) -> Result<Self::Value, E> {
109            Ok(StrictValue(Value::Null))
110        }
111
112        fn visit_unit<E>(self) -> Result<Self::Value, E> {
113            Ok(StrictValue(Value::Null))
114        }
115
116        fn visit_seq<A: SeqAccess<'de>>(self, mut sequence: A) -> Result<Self::Value, A::Error> {
117            let mut values = Vec::new();
118            while let Some(value) = sequence.next_element::<StrictValue>()? {
119                values.push(value.0);
120            }
121            Ok(StrictValue(Value::Array(values)))
122        }
123
124        fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
125            let mut values = BTreeMap::new();
126            while let Some((key, value)) = map.next_entry::<String, StrictValue>()? {
127                if values.insert(key.clone(), value.0).is_some() {
128                    return Err(serde::de::Error::custom(format!(
129                        "duplicate JSON object field '{key}'"
130                    )));
131                }
132            }
133            Ok(StrictValue(Value::Object(values.into_iter().collect())))
134        }
135    }
136}