1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
use serde::{Deserialize, Serialize};
use std::error::Error;
use std::fmt;

#[derive(Debug, Serialize, Deserialize)]
pub struct Entity {
    /// Describes the nature of an entity's content based on the current
    /// representation. Possible values are implementation-dependent and should
    /// be documented. MUST be an array of strings. Optional.
    #[serde(default)]
    pub class: Vec<String>,
    /// A set of key-value pairs that describe the state of an entity. In JSON
    /// Siren, this is an object such as { "name": "Kevin", "age": 30 }.
    /// Optional.
    #[serde(default)]
    pub properties: serde_json::Value,
    /// A collection of related sub-entities. If a sub-entity contains an href
    /// value, it should be treated as an embedded link. Clients may choose to
    /// optimistically load embedded links. If no href value exists, the
    /// sub-entity is an embedded entity representation that contains all the
    /// characteristics of a typical entity. One difference is that a sub-entity
    /// MUST contain a rel attribute to describe its relationship to the parent
    /// entity.
    // In JSON Siren, this is represented as an array. Optional.
    #[serde(default)]
    pub entities: Vec<SubEntity>,
    /// A collection of items that describe navigational links, distinct from
    /// entity relationships. Link items should contain a rel attribute to
    /// describe the relationship and an href attribute to point to the target
    /// URI. Entities should include a link rel to self. In JSON Siren, this is
    /// represented as "links": `[{ "rel": ["self"], "href": "http://api.x.io/orders/1234" }]`
    /// Optional.
    #[serde(default)]
    pub links: Vec<NavigationalLink>,
    /// A collection of action objects, represented in JSON Siren as an array
    /// such as { "actions": [{ ... }] }. See Actions. Optional
    #[serde(default)]
    pub actions: Vec<Action>,
    /// Descriptive text about the entity. Optional.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
}

impl Default for Entity {
    fn default() -> Self {
        Self {
            class: Vec::default(),
            properties: serde_json::Value::Object(serde_json::Map::default()),
            entities: Vec::default(),
            links: Vec::default(),
            actions: Vec::default(),
            title: None,
        }
    }
}

#[derive(Debug)]
pub enum EntityBuilderError {
    /// Whatever you passed, it doesn't serialize to a JSON object
    NotAnObject,
    Serde(serde_json::Error),
}

impl fmt::Display for EntityBuilderError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let desc = match self {
            EntityBuilderError::NotAnObject => "does not serialize to an object",
            EntityBuilderError::Serde(_) => "serialization failure",
        };

        write!(f, "{}", desc)
    }
}

impl std::error::Error for EntityBuilderError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            EntityBuilderError::NotAnObject => None,
            EntityBuilderError::Serde(inner) => Some(inner),
        }
    }
}

impl From<serde_json::Error> for EntityBuilderError {
    fn from(serde_error: serde_json::Error) -> Self {
        EntityBuilderError::Serde(serde_error)
    }
}

impl Entity {
    pub fn with_properties<S: Serialize>(
        self,
        serializable: S,
    ) -> Result<Self, EntityBuilderError> {
        let value = serde_json::to_value(serializable)?;

        match &value {
            serde_json::Value::Object(_) => Ok(Entity {
                properties: value,
                ..self
            }),
            _ => Err(EntityBuilderError::NotAnObject),
        }
    }

    pub fn with_class_member(mut self, class_member: impl Into<String>) -> Self {
        self.class.push(class_member.into());

        self
    }

    pub fn with_link(mut self, link: NavigationalLink) -> Self {
        self.links.push(link);

        self
    }

    pub fn with_action(mut self, action: Action) -> Self {
        self.actions.push(action);

        self
    }

    pub fn push_sub_entity(&mut self, sub_entity: SubEntity) {
        self.entities.push(sub_entity);
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SubEntity {
    Link {
        #[serde(flatten)]
        inner: EntityLink,
    },
    Embedded {
        #[serde(flatten)]
        inner: Entity,
        /// Defines the relationship of the sub-entity to its parent, per Web
        /// Linking (RFC5988) and Link Relations. MUST be a non-empty array of
        /// strings. Required.
        #[serde(default)]
        rel: Vec<String>,
    },
}

impl SubEntity {
    pub fn from_link(entity_link: EntityLink) -> Self {
        SubEntity::Link { inner: entity_link }
    }

    pub fn from_entity(entity: Entity, rels: &[impl Into<String> + Clone]) -> Self {
        SubEntity::Embedded {
            inner: entity,
            rel: rels.iter().map(|rel| rel.clone().into()).collect(),
        }
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct EntityLink {
    /// Describes the nature of an entity's content based on the current
    /// representation. Possible values are implementation-dependent and
    /// should be documented. MUST be an array of strings. Optional.
    #[serde(default)]
    pub class: Vec<String>,
    /// Descriptive text about the entity. Optional.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    /// Defines the relationship of the sub-entity to its parent, per Web
    /// Linking (RFC5988) and Link Relations. MUST be a non-empty array of
    /// strings. Required.
    #[serde(default)]
    pub rel: Vec<String>,
    /// The URI of the linked sub-entity. Required.
    pub href: String,
    /// Defines media type of the linked sub-entity, per Web Linking
    /// (RFC5988). Optional.
    #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
    pub _type: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct NavigationalLink {
    /// Defines the relationship of the link to its entity, per Web Linking
    /// (RFC5988) and Link Relations. MUST be an array of strings. Required.
    pub rel: Vec<String>,
    /// Describes aspects of the link based on the current representation.
    /// Possible values are implementation-dependent and should be documented.
    /// MUST be an array of strings. Optional.
    #[serde(default)]
    pub class: Vec<String>,
    /// The URI of the linked resource. Required.
    pub href: String,
    /// Text describing the nature of a link. Optional.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    /// Defines media type of the linked resource, per Web Linking (RFC5988).
    /// Optional.
    #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
    pub _type: Option<String>,
}

impl NavigationalLink {
    pub fn new(rels: &[impl Into<String> + Clone], href: impl Into<String>) -> Self {
        Self {
            href: href.into(),
            rel: rels.iter().map(|rel| rel.clone().into()).collect(),
            class: Vec::new(),
            title: None,
            _type: None,
        }
    }

    pub fn with_class_member(mut self, class_member: impl Into<String>) -> Self {
        self.class.push(class_member.into());

        self
    }

    pub fn with_type(mut self, _type: impl Into<String>) -> Self {
        self._type = Some(_type.into());

        self
    }

    pub fn with_title(mut self, title: impl Into<String>) -> Self {
        self.title = Some(title.into());

        self
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Action {
    /// A string that identifies the action to be performed. Action names MUST
    /// be unique within the set of actions for an entity. The behaviour of
    /// clients when parsing a Siren document that violates this constraint is
    /// undefined. Required.
    pub name: String,
    /// Describes the nature of an action based on the current representation.
    /// Possible values are implementation-dependent and should be documented.
    /// MUST be an array of strings. Optional.
    #[serde(default)]
    pub class: Vec<String>,
    /// An enumerated attribute mapping to a protocol method. For HTTP, these
    /// values may be GET, PUT, POST, DELETE, or PATCH. As new methods are
    /// introduced, this list can be extended. If this attribute is omitted, GET
    /// should be assumed. Optional.
    #[serde(with = "crate::http_serde::option_method")]
    pub method: Option<http::Method>,
    /// The URI of the action. Required.
    pub href: String,
    /// Descriptive text about the action. Optional.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
    /// The encoding type for the request. When omitted and the fields attribute
    /// exists, the default value is application/x-www-form-urlencoded.
    /// Optional.
    #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
    pub _type: Option<String>,
    /// A collection of fields, expressed as an array of objects in JSON Siren
    /// such as { "fields" : [{ ... }] }. See Fields. Optional.
    #[serde(default)]
    pub fields: Vec<Field>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Field {
    /// A name describing the control. Field names MUST be unique within the set
    /// of fields for an action. The behaviour of clients when parsing a Siren
    /// document that violates this constraint is undefined. Required.
    pub name: String,
    /// Describes aspects of the field based on the current representation.
    /// Possible values are implementation-dependent and should be documented.
    /// MUST be an array of strings. Optional.
    #[serde(default)]
    pub class: Vec<String>,
    /// The input type of the field. This may include any of the following input
    /// types specified in HTML5:
    /// hidden, text, search, tel, url, email, password, datetime, date, month,
    /// week, time, datetime-local, number, range, color, checkbox, radio,
    /// file
    //
    /// When missing, the default value is text. Serialization of these fields
    /// will depend on the value of the action's type attribute. See type
    /// under Actions, above. Optional.
    #[serde(default, rename = "type", skip_serializing_if = "Option::is_none")]
    pub _type: Option<String>,
    /// A value assigned to the field. Optional.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub value: Option<String>,
    /// Textual annotation of a field. Clients may use this as a label.
    /// Optional.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub title: Option<String>,
}