Skip to main content

pipa/value/
parse.rs

1// SPDX-FileCopyrightText: Copyright 2026 olav@occy.org
2// SPDX-License-Identifier: MPL-2.0
3
4//! Parse values from JSON and YAML.
5
6use crate::value::Value;
7use crate::value::error::Error;
8use crate::value::keys::Keys;
9use crate::value::list::List;
10use crate::value::map::Map;
11use crate::value::span::Span;
12use crate::value::tracer::Segment;
13use crate::value::tracer::Tracer;
14use saphyr::AnnotatedMapping;
15use saphyr::AnnotatedSequence;
16use saphyr::LoadableYamlNode;
17use saphyr::MarkedYaml;
18use saphyr::Scalar;
19use saphyr::YamlData;
20
21pub(crate) fn usize(value: &[u8]) -> Result<usize, Error> {
22    str(value)?
23        .parse::<usize>()
24        .map_err(|e| Error::Parse(e.to_string()))
25}
26
27pub(crate) fn str(value: &[u8]) -> Result<&str, Error> {
28    core::str::from_utf8(value)
29        .map_err(|_| Error::Parse(String::from_utf8_lossy(value).into_owned()))
30}
31
32pub fn json_map(data: &[u8]) -> Result<Map, Error> {
33    let data = parse_bytes(data)?;
34
35    let Ok(json) = serde_json::from_str(data) else {
36        return Err(Error::Parse(display_bytes(data)));
37    };
38
39    match json {
40        serde_json::Value::Object(v) => Ok(json_object(v)),
41        _ => Err(Error::Parse(display_bytes(data))),
42    }
43}
44
45fn json_value(json: serde_json::Value) -> Value {
46    match json {
47        serde_json::Value::Null => Value::default(),
48        serde_json::Value::Bool(v) => v.into(),
49        serde_json::Value::Number(v) => v.to_string().into(),
50        serde_json::Value::String(v) => v.into(),
51        serde_json::Value::Array(v) => json_array(v).into(),
52        serde_json::Value::Object(v) => json_object(v).into(),
53    }
54}
55
56fn json_object(json: serde_json::Map<String, serde_json::Value>) -> Map {
57    json.into_iter().map(|(k, v)| (k, json_value(v))).collect()
58}
59
60fn json_array(json: Vec<serde_json::Value>) -> List {
61    json.into_iter().map(json_value).collect()
62}
63
64pub fn yaml_map(source: String, span: Span, segment: Segment, data: &[u8]) -> Result<Map, Error> {
65    let data = parse_bytes(data)?;
66    let keys = Keys::default();
67
68    if data.trim().is_empty() {
69        return Ok(Map::default());
70    }
71
72    let yaml = MarkedYaml::load_from_str(data)?;
73
74    if yaml.len() != 1 {
75        return Err(Error::Parse(display_bytes(data)));
76    }
77
78    let Some(yaml) = yaml.into_iter().next() else {
79        return Err(Error::Parse(display_bytes(data)));
80    };
81
82    let Ok(Value::Map(mut map)) = yaml_value(&source, span, segment, data, &keys, yaml) else {
83        return Err(Error::Parse(display_bytes(data)));
84    };
85
86    map.tracer = Tracer::default()
87        .with_source(source)
88        .with_span(span)
89        .with_segment(segment);
90
91    Ok(map)
92}
93
94fn yaml_value(
95    source: &str,
96    span: Span,
97    segment: Segment,
98    data: &str,
99    keys: &Keys,
100    yaml: MarkedYaml<'_>,
101) -> Result<Value, ()> {
102    let i0 = yaml.span.start.index();
103    let i1 = yaml.span.end.index();
104
105    let yaml_span = match &yaml.data {
106        YamlData::Sequence(_) if data.chars().nth(i0) == Some('[') => {
107            span.zoom(Span::new(i0.saturating_add(1), i1))
108        }
109        _ => span.zoom(Span::new(i0, i1)),
110    };
111
112    let value = match yaml.data {
113        YamlData::Sequence(d) => yaml_sequence(source, span, segment, data, keys, d)?,
114        YamlData::Mapping(d) => yaml_mapping(source, span, segment, data, keys, d)?,
115        YamlData::Value(Scalar::Null) => Value::default(),
116        YamlData::Value(Scalar::Boolean(d)) => d.into(),
117        YamlData::Value(Scalar::FloatingPoint(d)) => Value::from(d.to_string()),
118        YamlData::Value(Scalar::Integer(d)) => Value::from(d.to_string()),
119        YamlData::Value(Scalar::String(d)) => Value::from(d.into_owned()),
120        YamlData::Representation(..) => return Err(()),
121        YamlData::Tagged(..) => return Err(()),
122        YamlData::Alias(_) => return Err(()),
123        YamlData::BadValue => return Err(()),
124    };
125
126    let tracer = Tracer::default()
127        .with_source(source.to_string())
128        .with_span(yaml_span)
129        .with_segment(segment)
130        .with_keys(keys.clone());
131
132    Ok(value.with_tracer(tracer))
133}
134
135fn yaml_sequence(
136    source: &str,
137    span: Span,
138    segment: Segment,
139    data: &str,
140    keys: &Keys,
141    yaml: AnnotatedSequence<MarkedYaml<'_>>,
142) -> Result<Value, ()> {
143    Ok(Value::List(
144        yaml.into_iter()
145            .enumerate()
146            .map(|(i, v)| yaml_value(source, span, segment, data, &keys.clone().fold(i), v))
147            .collect::<Result<List, _>>()?,
148    ))
149}
150
151fn yaml_mapping(
152    source: &str,
153    span: Span,
154    segment: Segment,
155    data: &str,
156    keys: &Keys,
157    yaml: AnnotatedMapping<'_, MarkedYaml<'_>>,
158) -> Result<Value, ()> {
159    Ok(Value::Map(
160        yaml.into_iter()
161            .map(|(k, v)| yaml_entry(source, span, segment, data, keys, k, v))
162            .collect::<Result<Map, _>>()?,
163    ))
164}
165
166fn yaml_entry(
167    source: &str,
168    span: Span,
169    segment: Segment,
170    data: &str,
171    keys: &Keys,
172    k: MarkedYaml<'_>,
173    v: MarkedYaml<'_>,
174) -> Result<(String, Value), ()> {
175    yaml_value(source, span, segment, data, keys, k)
176        .and_then(|k| String::from_utf8(k.join()).map_err(|_| ()))
177        .map(|k| (k.clone(), keys.clone().fold(k)))
178        .and_then(|(k, keys)| yaml_value(source, span, segment, data, &keys, v).map(|v| (k, v)))
179}
180
181#[cfg(test)]
182mod test {
183    use crate::value::Value;
184    use crate::value::equivalent::Equivalent;
185    use crate::value::keys::Keys;
186    use crate::value::list::List;
187    use crate::value::map::Map;
188    use crate::value::parse::json_map;
189    use crate::value::parse::yaml_map;
190    use crate::value::span::Span;
191    use crate::value::tracer::Segment;
192    use crate::value::tracer::Tracer;
193
194    #[test]
195    fn test_json_map() {
196        let parsed = json_map(br#"{ "a": [1,2,"b"] }"#).unwrap();
197        let list = List::default().with("1").with("2").with("b");
198        let map = Map::default().with("a", list);
199
200        assert!(parsed.equivalent(&map), "{parsed}");
201    }
202
203    #[test]
204    fn test_yaml_map() {
205        let value = Value::from(String::from("value")).with_tracer(
206            Tracer::default()
207                .with_source(String::from("index.html"))
208                .with_span(Span::new(1016, 1021))
209                .with_keys(Keys::parse(b"outer.inner")),
210        );
211
212        let inner = Value::Map(Map::default().with("inner", value)).with_tracer(
213            Tracer::default()
214                .with_source(String::from("index.html"))
215                .with_span(Span::new(1000, 1023))
216                .with_keys(Keys::parse(b"outer")),
217        );
218
219        let outer = Value::Map(Map::default().with("outer", inner)).with_tracer(
220            Tracer::default()
221                .with_source("index.html".to_string())
222                .with_span(Span::new(1000, 1023)),
223        );
224
225        let parsed = yaml_map(
226            String::from("index.html"),
227            Span::new(1000, 1023),
228            Segment::Field,
229            b"outer: { inner: value }",
230        );
231
232        assert!(parsed.unwrap().equivalent(&outer.try_into().unwrap()));
233    }
234}
235
236fn parse_bytes(data: &[u8]) -> Result<&str, Error> {
237    str::from_utf8(data).map_err(|_| Error::Parse(display_bytes(data)))
238}
239
240fn display_bytes<T: AsRef<[u8]>>(t: T) -> String {
241    String::from_utf8_lossy(t.as_ref()).into_owned()
242}