Skip to main content

amiss_wire/
de.rs

1use crate::json::{self, Value};
2
3#[derive(Clone, Debug, PartialEq, Eq)]
4pub struct Error {
5    pub path: String,
6    pub kind: ErrorKind,
7}
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum ErrorKind {
11    Json(json::Error),
12    MissingField,
13    UnknownField,
14    WrongType,
15    InvalidValue,
16    UnsortedSet,
17    DuplicateMember,
18    LimitExceeded,
19    DigestMismatch,
20    Inconsistent,
21}
22
23impl Error {
24    #[must_use]
25    pub fn new(path: &str, kind: ErrorKind) -> Self {
26        Self {
27            path: path.to_owned(),
28            kind,
29        }
30    }
31}
32
33/// # Errors
34///
35/// Always fails with the given kind at the given path.
36pub fn fail<T>(path: &str, kind: ErrorKind) -> Result<T, Error> {
37    Err(Error::new(path, kind))
38}
39
40pub struct Obj {
41    path: String,
42    members: Vec<(String, Value)>,
43}
44
45impl Obj {
46    /// # Errors
47    ///
48    /// Fails with `WrongType` when the value is not a JSON object.
49    pub fn new(path: &str, value: Value) -> Result<Self, Error> {
50        match value {
51            Value::Object(members) => Ok(Self {
52                path: path.to_owned(),
53                members,
54            }),
55            Value::Null
56            | Value::Bool(_)
57            | Value::Integer(_)
58            | Value::String(_)
59            | Value::Array(_) => fail(path, ErrorKind::WrongType),
60        }
61    }
62
63    #[must_use]
64    pub fn field(&self, name: &str) -> String {
65        format!("{}.{name}", self.path)
66    }
67
68    /// # Errors
69    ///
70    /// Fails with `MissingField` when the member is absent.
71    pub fn take(&mut self, name: &str) -> Result<Value, Error> {
72        match self.members.iter().position(|(key, _)| key == name) {
73            Some(index) => Ok(self.members.remove(index).1),
74            None => fail(&self.field(name), ErrorKind::MissingField),
75        }
76    }
77
78    /// # Errors
79    ///
80    /// Fails with `UnknownField` at the first leftover member.
81    pub fn finish(self) -> Result<(), Error> {
82        match self.members.into_iter().next() {
83            None => Ok(()),
84            Some((name, _)) => Err(Error {
85                kind: ErrorKind::UnknownField,
86                path: format!("{}.{name}", self.path),
87            }),
88        }
89    }
90}
91
92/// # Errors
93///
94/// Fails with `WrongType` when the value is not a string.
95pub fn string(path: &str, value: Value) -> Result<String, Error> {
96    match value {
97        Value::String(s) => Ok(s),
98        Value::Null | Value::Bool(_) | Value::Integer(_) | Value::Array(_) | Value::Object(_) => {
99            fail(path, ErrorKind::WrongType)
100        }
101    }
102}
103
104/// # Errors
105///
106/// Fails with `WrongType` when the value is not an integer.
107#[expect(
108    clippy::needless_pass_by_value,
109    reason = "uniform consuming decoder signature"
110)]
111pub fn integer(path: &str, value: Value) -> Result<i64, Error> {
112    match value {
113        Value::Integer(n) => Ok(n),
114        Value::Null | Value::Bool(_) | Value::String(_) | Value::Array(_) | Value::Object(_) => {
115            fail(path, ErrorKind::WrongType)
116        }
117    }
118}
119
120/// # Errors
121///
122/// Fails with `WrongType` when the value is not an array.
123pub fn array(path: &str, value: Value) -> Result<Vec<Value>, Error> {
124    match value {
125        Value::Array(items) => Ok(items),
126        Value::Null | Value::Bool(_) | Value::Integer(_) | Value::String(_) | Value::Object(_) => {
127            fail(path, ErrorKind::WrongType)
128        }
129    }
130}
131
132/// # Errors
133///
134/// Fails with `InvalidValue` when the string differs from `expected`.
135pub fn const_str(path: &str, value: Value, expected: &str) -> Result<(), Error> {
136    if string(path, value)? == expected {
137        Ok(())
138    } else {
139        fail(path, ErrorKind::InvalidValue)
140    }
141}
142
143#[must_use]
144pub fn nullable(value: Value) -> Option<Value> {
145    match value {
146        Value::Null => None,
147        other @ (Value::Bool(_)
148        | Value::Integer(_)
149        | Value::String(_)
150        | Value::Array(_)
151        | Value::Object(_)) => Some(other),
152    }
153}