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