json.rsannotatedjson.rssource78 lines · 2.3 KB · raw
1use std::fmt;
2
3use serde::Serialize;
4use serde_json::value::RawValue;
5
6use crate::ProtocolError;
7
8/// A JSON value kept as the exact bytes that will be sent. The API
9/// accepts a string, an object or an array wherever it takes free-form
10/// content (`state`, `instructions`, descriptions).
11#[derive(Clone)]
12pub struct Json(Box<RawValue>);
13
14impl Json {
15    /// A JSON string holding `text`.
16    pub fn text(text: &str) -> Self {
17        Json(RawValue::from_string(serde_json::to_string(text).expect("a str serializes")).expect("valid JSON"))
18    }
19
20    /// `json` as written, after checking it is JSON.
21    pub fn verbatim(json: &str) -> Result<Self, ProtocolError> {
22        RawValue::from_string(json.to_owned())
23            .map(Json)
24            .map_err(|e| ProtocolError::Invalid(format!("not JSON: {e}")))
25    }
26
27    /// `json` in RFC 8785 canonical form: sorted keys, no whitespace,
28    /// ECMAScript number formatting, and numbers a double cannot hold
29    /// exactly kept as strings. Equal values give equal bytes.
30    pub fn canonical(json: &str) -> Result<Self, ProtocolError> {
31        let canonical = crate::jcs::canonicalize(json).map_err(|e| ProtocolError::Invalid(format!("not JSON: {e}")))?;
32        Json::verbatim(&canonical)
33    }
34
35    pub fn as_str(&self) -> &str {
36        self.0.get()
37    }
38}
39
40impl fmt::Debug for Json {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.write_str(self.as_str())
43    }
44}
45
46impl PartialEq for Json {
47    fn eq(&self, other: &Self) -> bool {
48        self.as_str() == other.as_str()
49    }
50}
51
52impl Serialize for Json {
53    fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
54        self.0.serialize(s)
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn canonical_sorts_keys_and_formats_numbers() {
64        let j = Json::canonical(r#"{ "id": 1, "body": "hi", "a": 1.0e0, "big": 1e21, "bigint": 9007199254740993 }"#).unwrap();
65        assert_eq!(j.as_str(), r#"{"a":1,"big":1e+21,"bigint":"9007199254740993","body":"hi","id":1}"#);
66    }
67
68    #[test]
69    fn text_is_a_json_string() {
70        assert_eq!(Json::text("say \"hi\"").as_str(), r#""say \"hi\"""#);
71    }
72
73    #[test]
74    fn verbatim_keeps_bytes() {
75        assert_eq!(Json::verbatim(r#"{"b":1, "a":2}"#).unwrap().as_str(), r#"{"b":1, "a":2}"#);
76        assert!(Json::verbatim("{").is_err());
77    }
78}