siren_types/types.rs
1use serde::{Deserialize, Serialize};
2use std::error::Error;
3use std::fmt;
4
5#[derive(Debug, Serialize, Deserialize)]
6pub struct Entity {
7 /// Describes the nature of an entity's content based on the current
8 /// representation. Possible values are implementation-dependent and should
9 /// be documented. MUST be an array of strings. Optional.
10 #[serde(default)]
11 pub class: Vec<String>,
12 /// A set of key-value pairs that describe the state of an entity. In JSON
13 /// Siren, this is an object such as { "name": "Kevin", "age": 30 }.
14 /// Optional.
15 #[serde(default)]
16 pub properties: serde_json::Value,
17 /// A collection of related sub-entities. If a sub-entity contains an href
18 /// value, it should be treated as an embedded link. Clients may choose to
19 /// optimistically load embedded links. If no href value exists, the
20 /// sub-entity is an embedded entity representation that contains all the
21 /// characteristics of a typical entity. One difference is that a sub-entity
22 /// MUST contain a rel attribute to describe its relationship to the parent
23 /// entity.
24 // In JSON Siren, this is represented as an array. Optional.
25 #[serde(default)]
26 pub entities: Vec<SubEntity>,
27 /// A collection of items that describe navigational links, distinct from
28 /// entity relationships. Link items should contain a rel attribute to
29 /// describe the relationship and an href attribute to point to the target
30 /// URI. Entities should include a link rel to self. In JSON Siren, this is
31 /// represented as "links": `[{ "rel": ["self"], "href": "http://api.x.io/orders/1234" }]`
32 /// Optional.
33 #[serde(default)]
34 pub links: Vec<NavigationalLink>,
35 /// A collection of action objects, represented in JSON Siren as an array
36 /// such as { "actions": [{ ... }] }. See Actions. Optional
37 #[serde(default)]
38 pub actions: Vec<Action>,
39 /// Descriptive text about the entity. Optional.
40 #[serde(default, skip_serializing_if = "Option::is_none")]
41 pub title: Option<String>,
42}
43
44impl Default for Entity {
45 fn default() -> Self {
46 Self {
47 class: Vec::default(),
48 properties: serde_json::Value::Object(serde_json::Map::default()),
49 entities: Vec::default(),
50 links: Vec::default(),
51 actions: Vec::default(),
52 title: None,
53 }
54 }
55}
56
57#[derive(Debug)]
58pub enum EntityBuilderError {
59 /// Whatever you passed, it doesn't serialize to a JSON object
60 NotAnObject,
61 Serde(serde_json::Error),
62}
63
64impl fmt::Display for EntityBuilderError {
65 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66 let desc = match self {
67 EntityBuilderError::NotAnObject => "does not serialize to an object",
68 EntityBuilderError::Serde(_) => "serialization failure",
69 };
70
71 write!(f, "{}", desc)
72 }
73}
74
75impl std::error::Error for EntityBuilderError {
76 fn source(&self) -> Option<&(dyn Error + 'static)> {
77 match self {
78 EntityBuilderError::NotAnObject => None,
79 EntityBuilderError::Serde(inner) => Some(inner),
80 }
81 }
82}
83
84impl From<serde_json::Error> for EntityBuilderError {
85 fn from(serde_error: serde_json::Error) -> Self {
86 EntityBuilderError::Serde(serde_error)
87 }
88}
89
90impl Entity {
91 pub fn with_properties<S: Serialize>(
92 self,
93 serializable: S,
94 ) -> Result<Self, EntityBuilderError> {
95 let value = serde_json::to_value(serializable)?;
96
97 match &value {
98 serde_json::Value::Object(_) => Ok(Entity {
99 properties: value,
100 ..self
101 }),
102 _ => Err(EntityBuilderError::NotAnObject),
103 }
104 }
105
106 pub fn with_class_member(mut self, class_member: impl Into<String>) -> Self {
107 self.class.push(class_member.into());
108
109 self
110 }
111
112 pub fn with_link(mut self, link: NavigationalLink) -> Self {
113 self.links.push(link);
114
115 self
116 }
117
118 pub fn with_action(mut self, action: Action) -> Self {
119 self.actions.push(action);
120
121 self
122 }
123
124 pub fn push_sub_entity(&mut self, sub_entity: SubEntity) {
125 self.entities.push(sub_entity);
126 }
127}
128
129#[derive(Debug, Serialize, Deserialize)]
130#[serde(untagged)]
131pub enum SubEntity {
132 Link {
133 #[serde(flatten)]
134 inner: EntityLink,
135 },
136 Embedded {
137 #[serde(flatten)]
138 inner: Entity,
139 /// Defines the relationship of the sub-entity to its parent, per Web
140 /// Linking (RFC5988) and Link Relations. MUST be a non-empty array of
141 /// strings. Required.
142 #[serde(default)]
143 rel: Vec<String>,
144 },
145}
146
147impl SubEntity {
148 pub fn from_link(entity_link: EntityLink) -> Self {
149 SubEntity::Link { inner: entity_link }
150 }
151
152 pub fn from_entity(entity: Entity, rels: &[impl Into<String> + Clone]) -> Self {
153 SubEntity::Embedded {
154 inner: entity,
155 rel: rels.iter().map(|rel| rel.clone().into()).collect(),
156 }
157 }
158}
159
160#[derive(Debug, Serialize, Deserialize)]
161pub struct EntityLink {
162 /// Describes the nature of an entity's content based on the current
163 /// representation. Possible values are implementation-dependent and
164 /// should be documented. MUST be an array of strings. Optional.
165 #[serde(default)]
166 pub class: Vec<String>,
167 /// Descriptive text about the entity. Optional.
168 #[serde(default, skip_serializing_if = "Option::is_none")]
169 pub title: Option<String>,
170 /// Defines the relationship of the sub-entity to its parent, per Web
171 /// Linking (RFC5988) and Link Relations. MUST be a non-empty array of
172 /// strings. Required.
173 #[serde(default)]
174 pub rel: Vec<String>,
175 /// The URI of the linked sub-entity. Required.
176 pub href: String,
177 /// Defines media type of the linked sub-entity, per Web Linking
178 /// (RFC5988). Optional.
179 #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
180 pub _type: Option<String>,
181}
182
183#[derive(Debug, Serialize, Deserialize)]
184pub struct NavigationalLink {
185 /// Defines the relationship of the link to its entity, per Web Linking
186 /// (RFC5988) and Link Relations. MUST be an array of strings. Required.
187 pub rel: Vec<String>,
188 /// Describes aspects of the link based on the current representation.
189 /// Possible values are implementation-dependent and should be documented.
190 /// MUST be an array of strings. Optional.
191 #[serde(default)]
192 pub class: Vec<String>,
193 /// The URI of the linked resource. Required.
194 pub href: String,
195 /// Text describing the nature of a link. Optional.
196 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub title: Option<String>,
198 /// Defines media type of the linked resource, per Web Linking (RFC5988).
199 /// Optional.
200 #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
201 pub _type: Option<String>,
202}
203
204impl NavigationalLink {
205 pub fn new(rels: &[impl Into<String> + Clone], href: impl Into<String>) -> Self {
206 Self {
207 href: href.into(),
208 rel: rels.iter().map(|rel| rel.clone().into()).collect(),
209 class: Vec::new(),
210 title: None,
211 _type: None,
212 }
213 }
214
215 pub fn with_class_member(mut self, class_member: impl Into<String>) -> Self {
216 self.class.push(class_member.into());
217
218 self
219 }
220
221 pub fn with_type(mut self, _type: impl Into<String>) -> Self {
222 self._type = Some(_type.into());
223
224 self
225 }
226
227 pub fn with_title(mut self, title: impl Into<String>) -> Self {
228 self.title = Some(title.into());
229
230 self
231 }
232}
233
234#[derive(Debug, Serialize, Deserialize)]
235pub struct Action {
236 /// A string that identifies the action to be performed. Action names MUST
237 /// be unique within the set of actions for an entity. The behaviour of
238 /// clients when parsing a Siren document that violates this constraint is
239 /// undefined. Required.
240 pub name: String,
241 /// Describes the nature of an action based on the current representation.
242 /// Possible values are implementation-dependent and should be documented.
243 /// MUST be an array of strings. Optional.
244 #[serde(default)]
245 pub class: Vec<String>,
246 /// An enumerated attribute mapping to a protocol method. For HTTP, these
247 /// values may be GET, PUT, POST, DELETE, or PATCH. As new methods are
248 /// introduced, this list can be extended. If this attribute is omitted, GET
249 /// should be assumed. Optional.
250 #[serde(with = "crate::http_serde::option_method")]
251 pub method: Option<http::Method>,
252 /// The URI of the action. Required.
253 pub href: String,
254 /// Descriptive text about the action. Optional.
255 #[serde(default, skip_serializing_if = "Option::is_none")]
256 pub title: Option<String>,
257 /// The encoding type for the request. When omitted and the fields attribute
258 /// exists, the default value is application/x-www-form-urlencoded.
259 /// Optional.
260 #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
261 pub _type: Option<String>,
262 /// A collection of fields, expressed as an array of objects in JSON Siren
263 /// such as { "fields" : [{ ... }] }. See Fields. Optional.
264 #[serde(default)]
265 pub fields: Vec<Field>,
266}
267
268#[derive(Debug, Serialize, Deserialize)]
269pub struct Field {
270 /// A name describing the control. Field names MUST be unique within the set
271 /// of fields for an action. The behaviour of clients when parsing a Siren
272 /// document that violates this constraint is undefined. Required.
273 pub name: String,
274 /// Describes aspects of the field based on the current representation.
275 /// Possible values are implementation-dependent and should be documented.
276 /// MUST be an array of strings. Optional.
277 #[serde(default)]
278 pub class: Vec<String>,
279 /// The input type of the field. This may include any of the following input
280 /// types specified in HTML5:
281 /// hidden, text, search, tel, url, email, password, datetime, date, month,
282 /// week, time, datetime-local, number, range, color, checkbox, radio,
283 /// file
284 //
285 /// When missing, the default value is text. Serialization of these fields
286 /// will depend on the value of the action's type attribute. See type
287 /// under Actions, above. Optional.
288 #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
289 pub _type: Option<String>,
290 /// A value assigned to the field. Optional.
291 #[serde(default, skip_serializing_if = "Option::is_none")]
292 pub value: Option<String>,
293 /// Textual annotation of a field. Clients may use this as a label.
294 /// Optional.
295 #[serde(default, skip_serializing_if = "Option::is_none")]
296 pub title: Option<String>,
297}