wasmer_deploy_schema/schema/entity/
mod.rs

1mod entity_type;
2mod kind;
3mod uri;
4
5pub use self::{entity_type::*, kind::*, uri::*};
6
7use std::collections::HashMap;
8
9use anyhow::Context;
10use schemars::JsonSchema;
11use serde::{de::DeserializeOwned, Deserialize, Serialize};
12use time::OffsetDateTime;
13use uuid::Uuid;
14
15/// Common entity metadata.
16///
17/// This data is not generic, and is the same for all entity kinds.
18#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
19pub struct EntityMeta {
20    /// Name of the entity.
21    ///
22    /// This is only unique within the scope of the entity.
23    pub name: String,
24
25    /// Long description.
26    ///
27    /// Should be either plain text or markdown.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub description: Option<String>,
30
31    /// Labels are used to organize entities.
32    /// They are a set of simple key/value pairs.
33    #[serde(default)]
34    #[serde(skip_serializing_if = "HashMap::is_empty")]
35    pub labels: HashMap<String, String>,
36
37    /// Annotations are used to attach arbitrary metadata to entities.
38    /// They can contain arbitrary (json-encodable) data.
39    #[serde(default)]
40    #[serde(skip_serializing_if = "HashMap::is_empty")]
41    pub annotations: HashMap<String, serde_json::Value>,
42
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub parent: Option<EntityUri>,
45}
46
47impl EntityMeta {
48    pub fn new(name: impl Into<String>) -> Self {
49        Self {
50            name: name.into(),
51            description: None,
52            labels: Default::default(),
53            annotations: Default::default(),
54            parent: None,
55        }
56    }
57
58    pub fn with_annotations<I, K, V>(mut self, annotations: I) -> Self
59    where
60        I: IntoIterator<Item = (K, V)>,
61        K: Into<String>,
62        V: Into<serde_json::Value>,
63    {
64        self.annotations = annotations
65            .into_iter()
66            .map(|(k, v)| (k.into(), v.into()))
67            .collect();
68        self
69    }
70}
71
72/// An entity with associated data.
73#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
74pub struct Entity<D, C = serde_json::Value> {
75    /// Common entity metadata.
76    pub meta: EntityMeta,
77    /// Specification of the entity.
78    pub spec: D,
79    /// Inline child entity specs.
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub children: Option<Vec<C>>,
82}
83
84impl<D> Entity<D> {
85    pub fn new_with_name(name: impl Into<String>, spec: D) -> Self {
86        Self {
87            meta: EntityMeta::new(name),
88            spec,
89            children: None,
90        }
91    }
92
93    pub fn try_map_spec<F, O, E>(self, f: F) -> Result<Entity<O>, E>
94    where
95        F: FnOnce(D) -> Result<O, E>,
96    {
97        Ok(Entity {
98            meta: self.meta,
99            spec: f(self.spec)?,
100            children: self.children,
101        })
102    }
103}
104
105pub type JsonEntity = Entity<serde_json::Value, serde_json::Value>;
106
107impl<D, C> Entity<D, C>
108where
109    D: EntityDescriptorConst,
110{
111    pub fn uri(&self) -> String {
112        format!("{}:{}", D::KIND, self.meta.name)
113    }
114
115    pub fn build_uri(&self) -> EntityUri {
116        // NOTE: using unwrap here because an invalid kind in Self::KIND is a
117        // user error.
118        EntityUri::parse(self.uri()).unwrap()
119    }
120}
121
122impl<D, C> Entity<D, C>
123where
124    D: EntityDescriptorConst + serde::Serialize,
125    C: serde::Serialize,
126{
127    /// Convert this type to yaml, injecting the kind into the output.
128    // TODO: make this redundant with a custom Serialize impl!
129    pub fn to_json_map(&self) -> Result<serde_json::Value, serde_json::Error> {
130        // Constructing a custom object to properly order the fields.
131        // (kind, then meta, then spec)
132        let mut map = serde_json::Value::Object(Default::default());
133        map["kind"] = D::KIND.into();
134        map["meta"] = serde_json::to_value(&self.meta)?;
135        map["spec"] = serde_json::to_value(&self.spec)?;
136
137        Ok(map)
138    }
139
140    pub fn to_json(&self) -> Result<String, serde_json::Error> {
141        let map = self.to_json_map()?;
142        serde_json::to_string_pretty(&map)
143    }
144
145    /// Convert this type to yaml, injecting the kind into the output.
146    // TODO: make this redundant with a custom Serialize impl!
147    pub fn to_yaml_map(&self) -> Result<serde_yaml::Mapping, serde_yaml::Error> {
148        // Constructing a custom object to properly order the fields.
149        // (kind, then meta, then spec)
150        let mut map = serde_yaml::Mapping::new();
151        map.insert("kind".into(), D::KIND.into());
152        map.insert("meta".into(), serde_yaml::to_value(&self.meta)?);
153        map.insert("spec".into(), serde_yaml::to_value(&self.spec)?);
154
155        Ok(map)
156    }
157
158    /// Convert this type to yaml, injecting the kind into the output.
159    // TODO: make this redundant with a custom Serialize impl!
160    pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
161        let map = self.to_yaml_map()?;
162        serde_yaml::to_string(&map)
163    }
164
165    /// Converts this type into a generic entity
166    pub fn to_generic(&self) -> Result<GenericEntity, serde_json::Error> {
167        // TODO: @Christoph - the children parser needs to be implemented
168        assert!(self.children.is_none());
169
170        Ok(GenericEntity {
171            kind: D::KIND.to_string(),
172            meta: self.meta.clone(),
173            spec: serde_json::to_value(&self.spec)?,
174            children: None,
175        })
176    }
177}
178
179/// Generic, untyped entity.
180#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
181pub struct GenericEntity {
182    pub kind: String,
183
184    /// Common entity metadata.
185    pub meta: EntityMeta,
186    /// Specification of the entity.
187    pub spec: serde_json::Value,
188    /// Inline child entity specs.
189    pub children: Option<Vec<GenericEntity>>,
190}
191
192impl GenericEntity {
193    pub fn build_uri_str(&self) -> String {
194        format!("{}:{}", self.kind, self.meta.name)
195    }
196
197    pub fn build_uri(&self) -> Result<EntityUri, EntityUriParseError> {
198        EntityUri::new_kind_name(&self.kind, &self.meta.name)
199    }
200}
201
202/// An entity with associated data, including state.
203#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
204pub struct FullEntity<D, S = (), C = serde_json::Value> {
205    /// Common entity metadata.
206    pub meta: EntityMeta,
207    /// Specification of the entity.
208    pub spec: D,
209    /// Inline child entity specs.
210    pub children: Option<Vec<C>>,
211    pub state: EntityState<S>,
212}
213
214/// State of an entity.
215///
216/// Contains a `main` state, which will be managed by the owning service that
217/// manages the entity.
218///
219/// Additional services may inject their own state, which will be found in
220/// [`Self::components`].
221#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
222pub struct EntityState<S = ()> {
223    /// Globally unique UUID.
224    pub uid: Uuid,
225
226    /// Version of the entity.
227    /// All modifications to metadata or spec will increment this version.
228    pub entity_version: u64,
229
230    /// UUID of the parent entity.
231    /// This is only set if the entity is a child of another entity.
232    pub parent_uid: Option<Uuid>,
233
234    /// Creation timestamp.
235    #[serde(deserialize_with = "time::serde::timestamp::deserialize")]
236    #[schemars(with = "u64")]
237    pub created_at: OffsetDateTime,
238    /// Last update timestamp.
239    /// Will be set on each metadata or spec change, but not on state changes.
240    #[serde(serialize_with = "time::serde::timestamp::serialize")]
241    #[schemars(with = "u64")]
242    pub updated_at: OffsetDateTime,
243
244    /// The primary state of the entity, managed by the owning service.
245    pub main: Option<EntityStateComponent<S>>,
246
247    /// Additional entity states, managed by services other than the entity owners.
248    pub components: HashMap<String, EntityStateComponent>,
249}
250
251/// Single component of an entities state.
252#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
253pub struct EntityStateComponent<T = serde_json::Value> {
254    /// Version of this state.
255    /// Will be incremented on each change.
256    pub state_version: u64,
257    /// Update timestamp.
258    #[serde(with = "time::serde::timestamp")]
259    #[schemars(with = "u64")]
260    pub updated_at: OffsetDateTime,
261    /// The actual state data.
262    pub data: T,
263}
264
265/// A marker trait for entity types.
266///
267/// Should be implementes on the struct representing the entities spec.
268pub trait EntityDescriptorConst {
269    const NAMESPACE: &'static str;
270    const NAME: &'static str;
271    const VERSION: &'static str;
272    const KIND: &'static str;
273
274    /// Entity specification.
275    type Spec: Serialize + DeserializeOwned + JsonSchema + Clone + PartialEq + Eq + std::fmt::Debug;
276    /// The main entity state.
277    type State: Serialize + DeserializeOwned + JsonSchema + Clone + PartialEq + Eq + std::fmt::Debug;
278
279    fn json_schema() -> schemars::schema::RootSchema {
280        schemars::schema_for!(Entity<Self::Spec>)
281    }
282
283    fn build_uri_str(name: &str) -> String {
284        format!("{}:{}", Self::KIND, name)
285    }
286
287    fn build_uri(name: &str) -> Result<EntityUri, EntityUriParseError> {
288        EntityUri::new_kind_name(Self::KIND, name)
289    }
290
291    /// Build the name that is used for the EntityTypeSpec representing this type.
292    fn type_name() -> String {
293        // TODO: this should be an additional const...
294        format!("{}-{}-v{}", Self::NAMESPACE, Self::NAME, Self::VERSION)
295    }
296
297    fn build_type_descriptor() -> Entity<EntityTypeSpec>
298    where
299        Self: JsonSchema + Sized,
300    {
301        EntityTypeSpec::build_for_type::<Self>()
302    }
303}
304
305/// Deserialize a typed entity from YAML.
306pub fn deserialize_entity_yaml_typed<T>(input: &str) -> Result<Entity<T>, anyhow::Error>
307where
308    T: EntityDescriptorConst + DeserializeOwned,
309{
310    let raw: serde_yaml::Value = serde_yaml::from_str(input).context("invalid YAML")?;
311    let kind = raw
312        .get("kind")
313        .context("missing 'kind' field in yaml")?
314        .as_str()
315        .context("'kind' field is not a string")?;
316
317    if kind != T::KIND {
318        anyhow::bail!("expected kind '{}' but got '{}'", T::KIND, kind);
319    }
320
321    let out = serde_yaml::from_value(raw).context("could not deserialize to entity data")?;
322    Ok(out)
323}