Skip to main content

neco_json_ast/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use neco_ast::{
4    StructuredDocument as AstStructuredDocument, StructuredField, StructuredNumber, StructuredValue,
5};
6use neco_json::{parse as parse_value, JsonValue, ParseError};
7use std::borrow::Cow;
8
9#[derive(Debug, Clone, PartialEq)]
10pub struct JsonDocument {
11    value: JsonValue,
12}
13
14#[derive(Debug, Clone, Copy)]
15pub struct JsonNode<'a> {
16    key: Option<&'a str>,
17    value: &'a JsonValue,
18}
19
20pub fn parse(input: &[u8]) -> Result<JsonDocument, ParseError> {
21    parse_value(input).map(JsonDocument::from_value)
22}
23
24impl JsonDocument {
25    pub fn from_value(value: JsonValue) -> Self {
26        Self { value }
27    }
28
29    pub fn as_value(&self) -> &JsonValue {
30        &self.value
31    }
32}
33
34impl<'a> JsonNode<'a> {
35    pub fn from_value(value: &'a JsonValue) -> Self {
36        Self { key: None, value }
37    }
38
39    pub fn as_value(&self) -> &'a JsonValue {
40        self.value
41    }
42}
43
44impl<'a> AstStructuredDocument<'a> for JsonDocument {
45    type Node = JsonNode<'a>;
46
47    fn nodes(&'a self) -> Vec<Self::Node> {
48        match &self.value {
49            JsonValue::Object(fields) => fields
50                .iter()
51                .map(|(key, value)| JsonNode {
52                    key: Some(key.as_str()),
53                    value,
54                })
55                .collect(),
56            _ => vec![JsonNode::from_value(&self.value)],
57        }
58    }
59}
60
61impl<'a> neco_ast::StructuredNode<'a> for JsonNode<'a> {
62    fn kind(&self) -> Cow<'a, str> {
63        Cow::Borrowed(self.key.unwrap_or("root"))
64    }
65
66    fn identifier(&self) -> Option<Cow<'a, str>> {
67        self.key.map(Cow::Borrowed)
68    }
69
70    fn attribute(&self, key: &str) -> Option<StructuredValue<'a>> {
71        match self.value {
72            JsonValue::Object(fields) => fields
73                .iter()
74                .find_map(|(field, value)| (field == key).then(|| value_to_structured(value))),
75            _ => None,
76        }
77    }
78
79    fn children(&self) -> Vec<Self> {
80        match self.value {
81            JsonValue::Object(fields) => fields
82                .iter()
83                .map(|(key, value)| JsonNode {
84                    key: Some(key.as_str()),
85                    value,
86                })
87                .collect(),
88            JsonValue::Array(values) => values
89                .iter()
90                .map(|value| JsonNode {
91                    key: Some("item"),
92                    value,
93                })
94                .collect(),
95            _ => Vec::new(),
96        }
97    }
98
99    fn value(&self) -> StructuredValue<'a> {
100        value_to_structured(self.value)
101    }
102}
103
104fn value_to_structured<'a>(value: &'a JsonValue) -> StructuredValue<'a> {
105    match value {
106        JsonValue::Null => StructuredValue::Null,
107        JsonValue::Bool(value) => StructuredValue::Bool(*value),
108        JsonValue::Number(value) => StructuredValue::Number(StructuredNumber::from_f64(*value)),
109        JsonValue::String(value) => StructuredValue::String(Cow::Borrowed(value.as_str())),
110        JsonValue::Array(values) => {
111            StructuredValue::Sequence(values.iter().map(value_to_structured).collect())
112        }
113        JsonValue::Object(fields) => StructuredValue::Mapping(
114            fields
115                .iter()
116                .map(|(key, value)| StructuredField {
117                    key: Cow::Borrowed(key.as_str()),
118                    value: value_to_structured(value),
119                })
120                .collect(),
121        ),
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::{parse, value_to_structured, JsonDocument, JsonNode};
128    use neco_ast::{StructuredDocument, StructuredNode, StructuredValue};
129    use neco_json::JsonValue;
130
131    const SAMPLE: &[u8] = br#"{"name":"neco","enabled":true,"items":["one","two"]}"#;
132
133    #[test]
134    fn parse_document() {
135        assert!(parse(SAMPLE).is_ok());
136    }
137
138    #[test]
139    fn document_nodes_read_object_fields() {
140        let doc = parse(SAMPLE).expect("parse");
141        assert_eq!(doc.nodes().len(), 3);
142    }
143
144    #[test]
145    fn node_kind_uses_field_key() {
146        let doc = parse(SAMPLE).expect("parse");
147        assert_eq!(doc.nodes()[0].kind(), "name");
148    }
149
150    #[test]
151    fn node_identifier_uses_field_key() {
152        let doc = parse(SAMPLE).expect("parse");
153        assert_eq!(doc.nodes()[0].identifier().as_deref(), Some("name"));
154    }
155
156    #[test]
157    fn object_attribute_reads_string() {
158        let value = JsonValue::Object(vec![("name".into(), JsonValue::String("neco".into()))]);
159        let node = JsonNode::from_value(&value);
160        assert_eq!(node.attribute_str("name").as_deref(), Some("neco"));
161    }
162
163    #[test]
164    fn object_attribute_reads_bool() {
165        let value = JsonValue::Object(vec![("enabled".into(), JsonValue::Bool(true))]);
166        let node = JsonNode::from_value(&value);
167        assert_eq!(node.attribute_bool("enabled"), Some(true));
168    }
169
170    #[test]
171    fn missing_attribute_is_none() {
172        let value = JsonValue::Object(Vec::new());
173        let node = JsonNode::from_value(&value);
174        assert!(node.attribute("missing").is_none());
175    }
176
177    #[test]
178    fn array_children_are_items() {
179        let value = JsonValue::Array(vec![JsonValue::String("one".into())]);
180        let node = JsonNode::from_value(&value);
181        assert_eq!(node.children()[0].kind(), "item");
182    }
183
184    #[test]
185    fn value_mapping_preserves_key() {
186        let value = JsonValue::Object(vec![("name".into(), JsonValue::String("neco".into()))]);
187        let structured = value_to_structured(&value);
188        assert_eq!(structured.as_mapping().expect("mapping")[0].key, "name");
189    }
190
191    #[test]
192    fn scalar_value_preserves_string() {
193        let value = JsonValue::String("neco".into());
194        let structured = value_to_structured(&value);
195        assert_eq!(structured.as_str(), Some("neco"));
196    }
197
198    #[test]
199    fn document_from_value_keeps_root_scalar() {
200        let doc = JsonDocument::from_value(JsonValue::String("neco".into()));
201        assert_eq!(
202            doc.nodes()[0].value(),
203            StructuredValue::String("neco".into())
204        );
205    }
206}