wasmer_deploy_schema/schema/entity/
mod.rs1mod 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#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
19pub struct EntityMeta {
20 pub name: String,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
29 pub description: Option<String>,
30
31 #[serde(default)]
34 #[serde(skip_serializing_if = "HashMap::is_empty")]
35 pub labels: HashMap<String, String>,
36
37 #[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#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
74pub struct Entity<D, C = serde_json::Value> {
75 pub meta: EntityMeta,
77 pub spec: D,
79 #[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 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 pub fn to_json_map(&self) -> Result<serde_json::Value, serde_json::Error> {
130 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 pub fn to_yaml_map(&self) -> Result<serde_yaml::Mapping, serde_yaml::Error> {
148 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 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 pub fn to_generic(&self) -> Result<GenericEntity, serde_json::Error> {
167 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#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
181pub struct GenericEntity {
182 pub kind: String,
183
184 pub meta: EntityMeta,
186 pub spec: serde_json::Value,
188 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#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
204pub struct FullEntity<D, S = (), C = serde_json::Value> {
205 pub meta: EntityMeta,
207 pub spec: D,
209 pub children: Option<Vec<C>>,
211 pub state: EntityState<S>,
212}
213
214#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
222pub struct EntityState<S = ()> {
223 pub uid: Uuid,
225
226 pub entity_version: u64,
229
230 pub parent_uid: Option<Uuid>,
233
234 #[serde(deserialize_with = "time::serde::timestamp::deserialize")]
236 #[schemars(with = "u64")]
237 pub created_at: OffsetDateTime,
238 #[serde(serialize_with = "time::serde::timestamp::serialize")]
241 #[schemars(with = "u64")]
242 pub updated_at: OffsetDateTime,
243
244 pub main: Option<EntityStateComponent<S>>,
246
247 pub components: HashMap<String, EntityStateComponent>,
249}
250
251#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
253pub struct EntityStateComponent<T = serde_json::Value> {
254 pub state_version: u64,
257 #[serde(with = "time::serde::timestamp")]
259 #[schemars(with = "u64")]
260 pub updated_at: OffsetDateTime,
261 pub data: T,
263}
264
265pub trait EntityDescriptorConst {
269 const NAMESPACE: &'static str;
270 const NAME: &'static str;
271 const VERSION: &'static str;
272 const KIND: &'static str;
273
274 type Spec: Serialize + DeserializeOwned + JsonSchema + Clone + PartialEq + Eq + std::fmt::Debug;
276 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 fn type_name() -> String {
293 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
305pub 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}