Skip to main content

docspec_json/
state.rs

1//! State machine for tracking JSON emission context.
2
3use docspec_core::{Error, Result};
4
5/// A single frame on the emission stack.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7#[non_exhaustive]
8pub enum Frame {
9    /// Inside an array.
10    Array,
11    /// Inside an object, awaiting either a key or a value.
12    Object(KeyState),
13    /// Root context (zero or one value allowed).
14    Root,
15}
16
17/// Sub-state within an [`Frame::Object`] frame.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19#[non_exhaustive]
20pub enum KeyState {
21    /// The object is expecting the next key.
22    ExpectingKey,
23    /// A key was just written; awaiting its value.
24    ExpectingValue,
25}
26
27/// Stack tracking the current JSON emission context.
28///
29/// Each push corresponds to opening an object or array; each pop corresponds
30/// to closing one. The root frame represents the outermost document level.
31pub struct StateStack {
32    stack: Vec<Frame>,
33}
34
35impl StateStack {
36    /// Return the current top frame, or `None` if the root value has been consumed.
37    #[inline]
38    #[must_use]
39    pub fn current(&self) -> Option<&Frame> {
40        self.stack.last()
41    }
42
43    /// Validate that a key is allowed. OK only when current frame is `Object(ExpectingKey)`.
44    ///
45    /// # Errors
46    ///
47    /// Returns `Err` if a key is not allowed at the current position.
48    #[inline]
49    pub fn expect_key_allowed(&self) -> Result<()> {
50        match self.stack.last() {
51            Some(Frame::Object(KeyState::ExpectingKey)) => Ok(()),
52            Some(Frame::Object(KeyState::ExpectingValue)) => Err(Self::json_err(
53                "key not allowed: previous key has no value yet",
54            )),
55            Some(Frame::Array) => Err(Self::json_err("key not allowed: inside an array")),
56            Some(Frame::Root) => Err(Self::json_err("key not allowed: not inside an object")),
57            None => Err(Self::json_err("key not allowed: no current frame")),
58        }
59    }
60
61    /// Validate that a value is allowed. OK when: `Object(ExpectingValue)`, `Array`, or `Root`.
62    ///
63    /// # Errors
64    ///
65    /// Returns `Err` if a value is not allowed at the current position.
66    #[inline]
67    pub fn expect_value_allowed(&self) -> Result<()> {
68        match self.stack.last() {
69            Some(Frame::Object(KeyState::ExpectingValue) | Frame::Array | Frame::Root) => Ok(()),
70            Some(Frame::Object(KeyState::ExpectingKey)) => Err(Self::json_err(
71                "value not allowed: object expects a key first",
72            )),
73            None => Err(Self::json_err("value not allowed: no current frame")),
74        }
75    }
76
77    /// Returns `true` when all containers have been closed and the root value consumed.
78    #[inline]
79    #[must_use]
80    pub fn is_finished(&self) -> bool {
81        self.stack.is_empty()
82    }
83
84    /// Builds an `Error::Json` with `position: None`. Wrap in `Err(...)` at
85    /// match-arm sites or pass `|| Self::json_err(...)` to `ok_or_else`.
86    /// Centralises the `position: None` and `.to_string()` boilerplate so
87    /// validation methods stay focused on the message that distinguishes each case.
88    #[inline]
89    fn json_err(msg: &str) -> Error {
90        Error::Json {
91            message: msg.to_string(),
92            position: None,
93        }
94    }
95
96    /// Mark a key as written — transition Object from `ExpectingKey` to `ExpectingValue`.
97    ///
98    /// # Errors
99    ///
100    /// Returns `Err` if the current frame is not `Object(ExpectingKey)`.
101    #[inline]
102    pub fn mark_key_written(&mut self) -> Result<()> {
103        match self.stack.last_mut() {
104            Some(Frame::Object(ks)) if *ks == KeyState::ExpectingKey => {
105                *ks = KeyState::ExpectingValue;
106                Ok(())
107            }
108            Some(Frame::Object(_)) => {
109                Err(Self::json_err("key already written; expected a value next"))
110            }
111            _ => Err(Self::json_err("cannot write key: not inside an object")),
112        }
113    }
114
115    /// Mark a value as written.
116    ///
117    /// Transitions: `Object(ExpectingValue)` → `ExpectingKey`; `Root` → pop; `Array` → no-op.
118    ///
119    /// # Errors
120    ///
121    /// Returns `Err` if called in `Object(ExpectingKey)` state (value without key).
122    #[inline]
123    pub fn mark_value_written(&mut self) -> Result<()> {
124        match self.stack.last_mut() {
125            Some(Frame::Object(ks)) if *ks == KeyState::ExpectingValue => {
126                *ks = KeyState::ExpectingKey;
127                Ok(())
128            }
129            Some(Frame::Object(_)) => {
130                Err(Self::json_err("value without key: object expects a key"))
131            }
132            Some(Frame::Array) => Ok(()),
133            Some(Frame::Root) => {
134                self.stack.pop();
135                Ok(())
136            }
137            None => Err(Self::json_err("no current frame for value")),
138        }
139    }
140
141    /// Create a new `StateStack` containing a single [`Frame::Root`].
142    #[inline]
143    #[must_use]
144    pub fn new() -> Self {
145        Self {
146            stack: vec![Frame::Root],
147        }
148    }
149
150    /// Validate that the top frame is an Array without popping.
151    ///
152    /// # Errors
153    ///
154    /// Returns `Err` if the current frame is not an Array.
155    #[inline]
156    pub fn peek_array(&self) -> Result<()> {
157        match self.stack.last() {
158            Some(Frame::Array) => Ok(()),
159            Some(Frame::Object(_)) => Err(Self::json_err(
160                "cannot close array: current frame is an object",
161            )),
162            _ => Err(Self::json_err("cannot close array: no open array")),
163        }
164    }
165
166    /// Validate that the top frame is an Object in `ExpectingKey` state, without popping.
167    ///
168    /// # Errors
169    ///
170    /// Returns `Err` if the current frame is not an Object, or if the Object is in
171    /// `ExpectingValue` state (a key was written but its value hasn't been emitted).
172    #[inline]
173    pub fn peek_object(&self) -> Result<()> {
174        match self.stack.last() {
175            Some(Frame::Object(KeyState::ExpectingKey)) => Ok(()),
176            Some(Frame::Object(KeyState::ExpectingValue)) => {
177                Err(Self::json_err("cannot close object: last key has no value"))
178            }
179            Some(Frame::Array) => Err(Self::json_err(
180                "cannot close object: current frame is an array",
181            )),
182            _ => Err(Self::json_err("cannot close object: no open object")),
183        }
184    }
185
186    /// Pop the top frame. Returns `Err` if the stack is empty.
187    ///
188    /// # Errors
189    ///
190    /// Returns `Err` if there is no open container to close.
191    #[inline]
192    pub fn pop(&mut self) -> Result<Frame> {
193        self.stack
194            .pop()
195            .ok_or_else(|| Self::json_err("cannot close: no open container"))
196    }
197
198    /// Pop the top frame, asserting it is an Array. Returns `Err` if it is not.
199    ///
200    /// # Errors
201    ///
202    /// Returns `Err` if the current frame is not an Array.
203    #[inline]
204    pub fn pop_array(&mut self) -> Result<()> {
205        self.peek_array()?;
206        let _ = self.stack.pop();
207        Ok(())
208    }
209
210    /// Pop the top frame, asserting it is an Object expecting a key. Returns `Err` if it is not.
211    ///
212    /// # Errors
213    ///
214    /// Returns `Err` if the current frame is not an Object expecting a key.
215    #[inline]
216    pub fn pop_object(&mut self) -> Result<()> {
217        self.peek_object()?;
218        let _ = self.stack.pop();
219        Ok(())
220    }
221
222    /// Push a new Array frame.
223    #[inline]
224    pub fn push_array(&mut self) {
225        self.stack.push(Frame::Array);
226    }
227
228    /// Push a new Object frame in `ExpectingKey` state.
229    #[inline]
230    pub fn push_object(&mut self) {
231        self.stack.push(Frame::Object(KeyState::ExpectingKey));
232    }
233}
234
235impl Default for StateStack {
236    #[inline]
237    fn default() -> Self {
238        Self::new()
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245
246    #[test]
247    fn new_returns_root_frame() {
248        let s = StateStack::new();
249        assert_eq!(s.current(), Some(&Frame::Root));
250    }
251
252    #[test]
253    fn push_pop_object_round_trip() {
254        let mut s = StateStack::new();
255        s.push_object();
256        assert!(matches!(
257            s.current(),
258            Some(Frame::Object(KeyState::ExpectingKey))
259        ));
260        assert!(s.pop_object().is_ok());
261    }
262
263    #[test]
264    fn push_pop_array_round_trip() {
265        let mut s = StateStack::new();
266        s.push_array();
267        assert_eq!(s.current(), Some(&Frame::Array));
268        assert!(s.pop_array().is_ok());
269    }
270
271    #[test]
272    fn nested_push_pop_lifo_order() {
273        let mut s = StateStack::new();
274        s.push_object();
275        s.push_array();
276        assert_eq!(s.current(), Some(&Frame::Array));
277        assert!(s.pop_array().is_ok());
278        assert!(matches!(s.current(), Some(Frame::Object(_))));
279    }
280
281    #[test]
282    fn pop_at_root_returns_error() {
283        let mut s = StateStack::new();
284        s.stack.clear();
285        assert!(s.pop().is_err());
286    }
287
288    #[test]
289    fn peek_array_at_root_errors() {
290        let s = StateStack::new();
291        assert!(s.peek_array().is_err());
292    }
293
294    #[test]
295    fn peek_array_when_top_is_array_succeeds() {
296        let mut s = StateStack::new();
297        s.push_array();
298        assert!(s.peek_array().is_ok());
299        assert_eq!(s.current(), Some(&Frame::Array));
300    }
301
302    #[test]
303    fn peek_array_when_top_is_object_errors() {
304        let mut s = StateStack::new();
305        s.push_object();
306        assert!(s.peek_array().is_err());
307    }
308
309    #[test]
310    fn peek_object_at_root_errors() {
311        let s = StateStack::new();
312        assert!(s.peek_object().is_err());
313    }
314
315    #[test]
316    fn peek_object_when_top_is_array_errors() {
317        let mut s = StateStack::new();
318        s.push_array();
319        assert!(s.peek_object().is_err());
320    }
321
322    #[test]
323    fn peek_object_when_top_is_object_expecting_key_succeeds() {
324        let mut s = StateStack::new();
325        s.push_object();
326        assert!(s.peek_object().is_ok());
327    }
328
329    #[test]
330    fn peek_object_when_top_is_object_expecting_value_errors() {
331        let mut s = StateStack::new();
332        s.push_object();
333        assert!(s.mark_key_written().is_ok());
334        assert!(s.peek_object().is_err());
335    }
336
337    #[test]
338    fn pop_object_when_top_is_object_succeeds() {
339        let mut s = StateStack::new();
340        s.push_object();
341        assert!(s.pop_object().is_ok());
342    }
343
344    #[test]
345    fn pop_object_when_top_is_array_errors() {
346        let mut s = StateStack::new();
347        s.push_array();
348        assert!(s.pop_object().is_err());
349    }
350
351    #[test]
352    fn pop_object_at_root_errors() {
353        let mut s = StateStack::new();
354        assert!(s.pop_object().is_err());
355    }
356
357    #[test]
358    fn pop_object_in_expecting_value_errors() {
359        let mut s = StateStack::new();
360        s.push_object();
361        assert!(s.mark_key_written().is_ok());
362        assert!(s.pop_object().is_err());
363    }
364
365    #[test]
366    fn pop_array_when_top_is_array_succeeds() {
367        let mut s = StateStack::new();
368        s.push_array();
369        assert!(s.pop_array().is_ok());
370    }
371
372    #[test]
373    fn pop_array_when_top_is_object_errors() {
374        let mut s = StateStack::new();
375        s.push_object();
376        assert!(s.pop_array().is_err());
377    }
378
379    #[test]
380    fn pop_array_at_root_errors() {
381        let mut s = StateStack::new();
382        assert!(s.pop_array().is_err());
383    }
384
385    #[test]
386    fn mark_key_written_in_object_expecting_key_transitions_to_expecting_value() {
387        let mut s = StateStack::new();
388        s.push_object();
389        assert!(s.mark_key_written().is_ok());
390        assert!(matches!(
391            s.current(),
392            Some(Frame::Object(KeyState::ExpectingValue))
393        ));
394    }
395
396    #[test]
397    fn mark_key_written_in_object_expecting_value_errors() {
398        let mut s = StateStack::new();
399        s.push_object();
400        assert!(s.mark_key_written().is_ok());
401        assert!(s.mark_key_written().is_err());
402    }
403
404    #[test]
405    fn mark_key_written_in_array_errors() {
406        let mut s = StateStack::new();
407        s.push_array();
408        assert!(s.mark_key_written().is_err());
409    }
410
411    #[test]
412    fn mark_key_written_at_root_errors() {
413        let mut s = StateStack::new();
414        assert!(s.mark_key_written().is_err());
415    }
416
417    #[test]
418    fn mark_value_written_in_object_expecting_value_transitions_back() {
419        let mut s = StateStack::new();
420        s.push_object();
421        assert!(s.mark_key_written().is_ok());
422        assert!(s.mark_value_written().is_ok());
423        assert!(matches!(
424            s.current(),
425            Some(Frame::Object(KeyState::ExpectingKey))
426        ));
427    }
428
429    #[test]
430    fn mark_value_written_in_object_expecting_key_errors() {
431        let mut s = StateStack::new();
432        s.push_object();
433        assert!(s.mark_value_written().is_err());
434    }
435
436    #[test]
437    fn mark_value_written_without_frame_errors() {
438        let mut s = StateStack::new();
439        s.stack.clear();
440        assert!(s.mark_value_written().is_err());
441    }
442
443    #[test]
444    fn mark_value_written_in_array_no_transition() {
445        let mut s = StateStack::new();
446        s.push_array();
447        assert!(s.mark_value_written().is_ok());
448        assert_eq!(s.current(), Some(&Frame::Array));
449    }
450
451    #[test]
452    fn mark_value_written_at_root_consumes_root() {
453        let mut s = StateStack::new();
454        assert!(s.mark_value_written().is_ok());
455        assert!(s.is_finished());
456    }
457
458    #[test]
459    fn expect_key_allowed_only_in_object_expecting_key() {
460        let mut s = StateStack::new();
461        assert!(s.expect_key_allowed().is_err());
462        s.push_array();
463        assert!(s.expect_key_allowed().is_err());
464        assert!(s.pop_array().is_ok());
465        s.push_object();
466        assert!(s.expect_key_allowed().is_ok());
467        assert!(s.mark_key_written().is_ok());
468        assert!(s.expect_key_allowed().is_err());
469    }
470
471    #[test]
472    fn expect_key_allowed_without_frame_errors() {
473        let mut s = StateStack::new();
474        s.stack.clear();
475        assert!(s.expect_key_allowed().is_err());
476    }
477
478    #[test]
479    fn expect_value_allowed_in_root_object_value_and_array_only() {
480        let mut s = StateStack::new();
481        assert!(s.expect_value_allowed().is_ok());
482        s.push_object();
483        assert!(s.expect_value_allowed().is_err());
484        assert!(s.mark_key_written().is_ok());
485        assert!(s.expect_value_allowed().is_ok());
486        assert!(s.mark_value_written().is_ok());
487        s.push_array();
488        assert!(s.expect_value_allowed().is_ok());
489    }
490
491    #[test]
492    fn expect_value_allowed_without_frame_errors() {
493        let mut s = StateStack::new();
494        s.stack.clear();
495        assert!(s.expect_value_allowed().is_err());
496    }
497
498    #[test]
499    fn is_finished_true_after_root_value_consumed() {
500        let mut s = StateStack::new();
501        assert!(s.mark_value_written().is_ok());
502        assert!(s.is_finished());
503    }
504
505    #[test]
506    fn is_finished_false_inside_open_container() {
507        let mut s = StateStack::new();
508        s.push_object();
509        assert!(!s.is_finished());
510    }
511
512    #[test]
513    fn state_stack_default_returns_root_frame() {
514        let s = StateStack::default();
515        assert_eq!(s.current(), Some(&Frame::Root));
516    }
517}