1mod validate;
13
14use facet::Facet;
15use facet_value::{DestructuredRef, VObject, Value};
16
17use crate::entries::Entries;
18use crate::error::{AnnotationError, DocumentError, MarkerField, TextError, ValueError};
19use crate::header::GENERATED;
20use crate::path::Segment;
21use crate::scalar::Scalar;
22use crate::text::Comment;
23
24const MARKER_KEY: &str = "_ephemeris";
25const MARKER_VALUE: &str = "annotated";
26
27#[derive(Debug, Clone, Copy, Facet, PartialEq, Eq)]
29#[repr(u8)]
30pub(crate) enum StringStyle {
31 #[facet(rename = "plain")]
33 Plain,
34 #[facet(rename = "literal")]
37 Literal,
38}
39
40#[derive(Debug, Facet)]
41#[repr(u8)]
42enum MarkerTag {
43 #[facet(rename = "annotated")]
44 Annotated,
45}
46
47#[derive(Debug, Facet)]
49#[facet(deny_unknown_fields)]
50struct Marker {
51 _ephemeris: MarkerTag,
52 #[facet(default)]
53 order: Option<i64>,
54 #[facet(default)]
55 string: Option<StringStyle>,
56 #[facet(default)]
57 comment: Option<String>,
58 #[facet(default)]
59 dotted: Option<bool>,
60}
61
62#[derive(Debug, Clone)]
64pub(crate) struct Annotated {
65 pub(crate) comment: Option<Comment>,
67 pub(crate) value: AnnotatedValue,
69}
70
71impl Annotated {
72 pub(crate) fn mark_generated(&mut self) -> Result<(), TextError> {
74 let text = self.comment.as_ref().map_or_else(
75 || GENERATED.to_owned(),
76 |existing| format!("{GENERATED}\n{}", &**existing),
77 );
78 self.comment = Some(Comment::new(text)?);
79 Ok(())
80 }
81
82 pub(crate) fn carries_comment(&self) -> bool {
84 self.comment.is_some()
85 || match &self.value {
86 AnnotatedValue::Scalar(_) => false,
87 AnnotatedValue::Array(items) => items.iter().any(Self::carries_comment),
88 AnnotatedValue::Object(entries) => entries.carries_comment(),
89 }
90 }
91}
92
93#[derive(Debug, Clone)]
95pub(crate) enum AnnotatedValue {
96 Scalar(Scalar),
98 Array(Vec<Annotated>),
100 Object(Entries),
102}
103
104impl TryFrom<&Value> for Annotated {
105 type Error = DocumentError;
106
107 fn try_from(value: &Value) -> Result<Self, Self::Error> {
114 Decoded::decode(value)?.into_ranked()?.into_unordered()
115 }
116}
117
118struct Ranked {
120 order: Option<i64>,
121 annotated: Annotated,
122}
123
124impl Ranked {
125 fn into_unordered(self) -> Result<Annotated, DocumentError> {
126 match self.order {
127 Some(_) => Err(DocumentError::annotation(
128 AnnotationError::OrderOutsideObject,
129 )),
130 None => Ok(self.annotated),
131 }
132 }
133}
134
135struct Decoded {
138 comment: Option<Comment>,
139 order: Option<i64>,
140 style: Option<StringStyle>,
141 dotted: Option<bool>,
142 value: AnnotatedValue,
143}
144
145impl Decoded {
146 const fn plain(value: AnnotatedValue) -> Self {
147 Self {
148 comment: None,
149 order: None,
150 style: None,
151 dotted: None,
152 value,
153 }
154 }
155
156 fn decode(value: &Value) -> Result<Self, DocumentError> {
157 #[expect(
158 clippy::wildcard_enum_match_arm,
159 reason = "DestructuredRef is a foreign, growable enum from facet-value"
160 )]
161 let node = match value.destructure_ref() {
162 DestructuredRef::Object(object) if is_marker(object) => return Self::marker(object),
163 DestructuredRef::Object(object) => {
164 let entries = object
165 .iter()
166 .map(|(key, entry)| {
167 let ranked = Self::decode(entry)
168 .and_then(Self::into_ranked)
169 .map_err(|error| error.under(Segment::Key(key.as_str().to_owned())))?;
170 Ok((ranked.order, key.as_str().to_owned(), ranked.annotated))
171 })
172 .collect::<Result<_, DocumentError>>()?;
173 AnnotatedValue::Object(Entries::sorted(entries))
174 }
175 DestructuredRef::Array(values) => {
176 let items = values
177 .iter()
178 .enumerate()
179 .map(|(index, item)| {
180 Self::decode(item)
181 .and_then(Self::into_ranked)
182 .and_then(Ranked::into_unordered)
183 .map_err(|error| error.under(Segment::Index(index)))
184 })
185 .collect::<Result<_, DocumentError>>()?;
186 AnnotatedValue::Array(items)
187 }
188 DestructuredRef::Null => AnnotatedValue::Scalar(Scalar::Null),
189 DestructuredRef::Bool(value) => AnnotatedValue::Scalar(Scalar::Bool(value)),
190 DestructuredRef::Number(number) => {
191 AnnotatedValue::Scalar(Scalar::number(number).map_err(DocumentError::value)?)
192 }
193 DestructuredRef::String(text) => AnnotatedValue::Scalar(Scalar::String {
194 text: text.as_str().to_owned(),
195 style: StringStyle::Plain,
196 }),
197 _ => return Err(DocumentError::value(ValueError::UnsupportedKind)),
198 };
199 Ok(Self::plain(node))
200 }
201
202 fn marker(object: &VObject) -> Result<Self, DocumentError> {
203 let mut fields = VObject::new();
204 for (key, field) in object {
205 if key.as_str() != "value" {
206 fields.insert(key.as_str(), field.clone());
207 }
208 }
209 let marker = facet_value::from_value::<Marker>(Value::from(fields)).map_err(|source| {
210 DocumentError::annotation(AnnotationError::Marker {
211 source: Box::new(source),
212 })
213 })?;
214 let inner = object
215 .get("value")
216 .ok_or_else(|| DocumentError::annotation(AnnotationError::MissingValue))?;
217 let mut node = Self::decode(inner)?;
218 MarkerField::Order
219 .set_once(&mut node.order, marker.order)
220 .map_err(DocumentError::annotation)?;
221 MarkerField::String
222 .set_once(&mut node.style, marker.string)
223 .map_err(DocumentError::annotation)?;
224 MarkerField::Dotted
225 .set_once(&mut node.dotted, marker.dotted)
226 .map_err(DocumentError::annotation)?;
227 MarkerField::Comment
228 .set_once(
229 &mut node.comment,
230 marker.comment.map(Comment::new).transpose()?,
231 )
232 .map_err(DocumentError::annotation)?;
233 Ok(node)
234 }
235
236 fn into_ranked(self) -> Result<Ranked, DocumentError> {
237 let value = match self.dotted {
238 Some(dotted) => Self::dotted(dotted, self.value)?,
239 None => self.value,
240 };
241 let value = match (self.style, value) {
242 (Some(style), AnnotatedValue::Scalar(Scalar::String { text, style: _ })) => {
243 AnnotatedValue::Scalar(Scalar::String { text, style })
244 }
245 (
246 Some(_),
247 AnnotatedValue::Scalar(_) | AnnotatedValue::Array(_) | AnnotatedValue::Object(_),
248 ) => {
249 return Err(DocumentError::annotation(AnnotationError::StyleOnNonString));
250 }
251 (None, value) => value,
252 };
253 Ok(Ranked {
254 order: self.order,
255 annotated: Annotated {
256 comment: self.comment,
257 value,
258 },
259 })
260 }
261}
262
263impl Decoded {
264 fn dotted(dotted: bool, value: AnnotatedValue) -> Result<AnnotatedValue, DocumentError> {
265 match value {
266 AnnotatedValue::Object(mut entries) => {
267 entries.set_dotted(dotted);
268 Ok(AnnotatedValue::Object(entries))
269 }
270 AnnotatedValue::Scalar(_) | AnnotatedValue::Array(_) => Err(DocumentError::annotation(
271 AnnotationError::DottedOnNonObject,
272 )),
273 }
274 }
275}
276
277impl MarkerField {
278 fn set_once<T>(self, slot: &mut Option<T>, value: Option<T>) -> Result<(), AnnotationError> {
279 match (slot.is_some(), value) {
280 (true, Some(_)) => Err(AnnotationError::FieldSetTwice { field: self }),
281 (false, Some(value)) => {
282 *slot = Some(value);
283 Ok(())
284 }
285 (_, None) => Ok(()),
286 }
287 }
288}
289
290fn is_marker(object: &VObject) -> bool {
291 matches!(
292 object.get(MARKER_KEY).map(Value::destructure_ref),
293 Some(DestructuredRef::String(marker)) if marker.as_str() == MARKER_VALUE
294 )
295}
296
297#[cfg(test)]
298mod tests;