Skip to main content

json_ld/syntax/
container.rs

1use std::str::FromStr;
2
3use crate::context::JsonLdContainer;
4use crate::syntax::context::JsonLdContextTypeContainer;
5
6use super::JsonLdKeyword;
7
8#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10pub enum JsonLdContainerItem {
11	#[cfg_attr(feature = "serde", serde(rename = "@graph"))]
12	Graph,
13
14	#[cfg_attr(feature = "serde", serde(rename = "@id"))]
15	Id,
16
17	#[cfg_attr(feature = "serde", serde(rename = "@index"))]
18	Index,
19
20	#[cfg_attr(feature = "serde", serde(rename = "@language"))]
21	Language,
22
23	#[cfg_attr(feature = "serde", serde(rename = "@list"))]
24	List,
25
26	#[cfg_attr(feature = "serde", serde(rename = "@set"))]
27	Set,
28
29	#[cfg_attr(feature = "serde", serde(rename = "@type"))]
30	Type,
31}
32
33impl JsonLdContainerItem {
34	pub fn into_keyword(self) -> JsonLdKeyword {
35		self.into()
36	}
37
38	pub fn keyword(&self) -> JsonLdKeyword {
39		self.into_keyword()
40	}
41
42	pub fn as_str(&self) -> &'static str {
43		self.into_keyword().into_str()
44	}
45}
46
47impl std::fmt::Display for JsonLdContainerItem {
48	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49		f.write_str(self.as_str())
50	}
51}
52
53#[derive(Debug, Clone, Copy, thiserror::Error)]
54#[error("Invalid `@container` item: `{0}`")]
55pub struct InvalidJsonLdContainerItem<T = String>(pub T);
56
57impl<T: ?Sized + ToOwned> InvalidJsonLdContainerItem<&T> {
58	pub fn into_owned(self) -> InvalidJsonLdContainerItem<T::Owned> {
59		InvalidJsonLdContainerItem(self.0.to_owned())
60	}
61}
62
63impl FromStr for JsonLdContainerItem {
64	type Err = InvalidJsonLdContainerItem;
65
66	fn from_str(s: &str) -> Result<Self, Self::Err> {
67		s.try_into().map_err(InvalidJsonLdContainerItem::into_owned)
68	}
69}
70
71impl<'a> TryFrom<&'a str> for JsonLdContainerItem {
72	type Error = InvalidJsonLdContainerItem<&'a str>;
73
74	fn try_from(str: &'a str) -> Result<JsonLdContainerItem, InvalidJsonLdContainerItem<&'a str>> {
75		use JsonLdContainerItem::*;
76		match str {
77			"@graph" => Ok(Graph),
78			"@id" => Ok(Id),
79			"@index" => Ok(Index),
80			"@language" => Ok(Language),
81			"@list" => Ok(List),
82			"@set" => Ok(Set),
83			"@type" => Ok(Type),
84			_ => Err(InvalidJsonLdContainerItem(str)),
85		}
86	}
87}
88
89impl TryFrom<JsonLdKeyword> for JsonLdContainerItem {
90	type Error = JsonLdKeyword;
91
92	fn try_from(k: JsonLdKeyword) -> Result<JsonLdContainerItem, JsonLdKeyword> {
93		use JsonLdContainerItem::*;
94		match k {
95			JsonLdKeyword::Graph => Ok(Graph),
96			JsonLdKeyword::Id => Ok(Id),
97			JsonLdKeyword::Index => Ok(Index),
98			JsonLdKeyword::Language => Ok(Language),
99			JsonLdKeyword::List => Ok(List),
100			JsonLdKeyword::Set => Ok(Set),
101			JsonLdKeyword::Type => Ok(Type),
102			k => Err(k),
103		}
104	}
105}
106
107impl From<JsonLdContainerItem> for JsonLdKeyword {
108	fn from(c: JsonLdContainerItem) -> JsonLdKeyword {
109		use JsonLdContainerItem::*;
110		match c {
111			Graph => JsonLdKeyword::Graph,
112			Id => JsonLdKeyword::Id,
113			Index => JsonLdKeyword::Index,
114			Language => JsonLdKeyword::Language,
115			List => JsonLdKeyword::List,
116			Set => JsonLdKeyword::Set,
117			Type => JsonLdKeyword::Type,
118		}
119	}
120}
121
122/// Syntax-level representation of a `@container` value.
123///
124/// Preserves whether the value was null, a single item, or an array,
125/// which matters for JSON-LD 1.0 validation.
126#[derive(Clone, PartialEq, Eq, Hash, Debug)]
127#[cfg_attr(
128	feature = "serde",
129	derive(serde::Serialize, serde::Deserialize),
130	serde(untagged)
131)]
132pub enum JsonLdContainerValue {
133	/// Null container value.
134	Null,
135
136	/// A single container keyword, e.g. `"@set"`.
137	Item(JsonLdContainerItem),
138
139	/// An array of container keywords, e.g. `["@set"]` or `["@graph", "@id"]`.
140	Array(Vec<JsonLdContainerItem>),
141}
142
143impl JsonLdContainerValue {
144	/// Whether this value was specified as an array in the source JSON.
145	pub fn is_array(&self) -> bool {
146		matches!(self, Self::Array(_))
147	}
148
149	/// Process this syntax value into a [`JsonLdContainer`].
150	pub fn to_container(&self) -> Result<JsonLdContainer, UnexpectedJsonLdContainerItem> {
151		match self {
152			Self::Null => Ok(JsonLdContainer::Null),
153			Self::Item(item) => Ok((*item).into()),
154			Self::Array(items) => {
155				JsonLdContainer::from(items.iter()).map_err(UnexpectedJsonLdContainerItem)
156			}
157		}
158	}
159}
160
161/// Error returned when a container array contains an unexpected item
162/// combination.
163#[derive(Debug, Clone, Copy, thiserror::Error)]
164#[error("unexpected `@container` item: `{0}`")]
165pub struct UnexpectedJsonLdContainerItem(pub JsonLdContainerItem);
166
167impl From<JsonLdContextTypeContainer> for JsonLdContainerValue {
168	fn from(c: JsonLdContextTypeContainer) -> Self {
169		match c {
170			JsonLdContextTypeContainer::Set => JsonLdContainerValue::Item(JsonLdContainerItem::Set),
171		}
172	}
173}