Skip to main content

jsonld_syntax/context/definition/
version.rs

1use std::{hash::Hash, str::FromStr};
2
3#[derive(Debug, thiserror::Error)]
4#[error("unknown JSON-LD version `{0}`")]
5/// Error raised when `@version` holds anything but `1.1`.
6pub struct UnknownVersion(pub String);
7
8/// Value of the `@version` entry, selecting the processing mode.
9///
10/// The only value JSON-LD allows is the number `1.1`, so this enum has a single
11/// variant and all its values are equal.
12#[derive(Clone, Copy, PartialOrd, Ord, Debug)]
13pub enum Version {
14    /// JSON-LD 1.1 processing mode.
15    V1_1,
16}
17
18impl Version {
19    /// Returns the decimal spelling of this version as bytes: `b"1.1"`.
20    #[must_use]
21    pub fn into_bytes(self) -> &'static [u8] {
22        match self {
23            Self::V1_1 => b"1.1",
24        }
25    }
26
27    /// Returns the decimal spelling of this version: `"1.1"`.
28    #[must_use]
29    pub fn into_str(self) -> &'static str {
30        match self {
31            Self::V1_1 => "1.1",
32        }
33    }
34
35    /// Returns this version as a borrowed JSON number.
36    #[must_use]
37    pub fn into_json_number(self) -> &'static jstrict::Number {
38        // SAFETY: `into_bytes` only returns `b"1.1"`, a valid JSON number.
39        unsafe { jstrict::Number::new_unchecked(self.into_bytes()) }
40    }
41
42    /// Returns this version as an owned JSON number.
43    #[must_use]
44    pub fn into_json_number_buf(self) -> jstrict::NumberBuf {
45        // SAFETY: `into_bytes` only returns `b"1.1"`, a valid JSON number.
46        unsafe { jstrict::NumberBuf::new_unchecked(self.into_bytes().into()) }
47    }
48}
49
50impl PartialEq for Version {
51    fn eq(&self, _other: &Self) -> bool {
52        true
53    }
54}
55
56impl Eq for Version {}
57
58impl Hash for Version {
59    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
60        self.into_str().hash(state);
61    }
62}
63
64impl From<Version> for &jstrict::Number {
65    fn from(v: Version) -> Self {
66        v.into_json_number()
67    }
68}
69
70impl From<Version> for jstrict::NumberBuf {
71    fn from(v: Version) -> Self {
72        v.into_json_number_buf()
73    }
74}
75
76impl TryFrom<jstrict::NumberBuf> for Version {
77    type Error = UnknownVersion;
78
79    fn try_from(value: jstrict::NumberBuf) -> Result<Self, Self::Error> {
80        if value.trimmed().as_str() == "1.1" {
81            Ok(Self::V1_1)
82        } else {
83            Err(UnknownVersion(value.to_string()))
84        }
85    }
86}
87
88impl FromStr for Version {
89    type Err = UnknownVersion;
90
91    fn from_str(s: &str) -> Result<Self, Self::Err> {
92        if s == "1.1" { Ok(Version::V1_1) } else { Err(UnknownVersion(s.to_owned())) }
93    }
94}
95
96impl TryFrom<f32> for Version {
97    type Error = UnknownVersion;
98
99    // Exact comparison is the intent: `@version` is the literal `1.1` and
100    // nothing else, and a JSON `1.1` parses to exactly this float. An epsilon
101    // window would accept values the specification does not define.
102    #[allow(clippy::float_cmp)]
103    fn try_from(value: f32) -> Result<Self, Self::Error> {
104        if value == 1.1 {
105            Ok(Version::V1_1)
106        } else {
107            Err(UnknownVersion(value.to_string()))
108        }
109    }
110}
111
112impl TryFrom<f64> for Version {
113    type Error = UnknownVersion;
114
115    #[allow(clippy::float_cmp)]
116    fn try_from(value: f64) -> Result<Self, Self::Error> {
117        if value == 1.1 {
118            Ok(Version::V1_1)
119        } else {
120            Err(UnknownVersion(value.to_string()))
121        }
122    }
123}
124
125#[cfg(feature = "serde")]
126impl serde::Serialize for Version {
127    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
128    where
129        S: serde::Serializer,
130    {
131        self.into_json_number().serialize(serializer)
132    }
133}
134
135#[cfg(feature = "serde")]
136impl<'de> serde::Deserialize<'de> for Version {
137    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
138    where
139        D: serde::Deserializer<'de>,
140    {
141        jstrict::NumberBuf::deserialize(deserializer)?.try_into().map_err(serde::de::Error::custom)
142    }
143}