Skip to main content

endpoint_libs/model/
types.rs

1use serde::*;
2use std::collections::BTreeMap;
3use std::hash::{Hash, Hasher};
4
5/// Emitter-facing annotations attached to a [`Field`] or
6/// [`EndpointSchema`](crate::model::EndpointSchema).
7///
8/// Unused by endpoint-libs itself. It exists so that later releases can carry
9/// examples, JSON Schema constraints, tags, deprecation markers and
10/// protocol-binding hints into the OpenAPI/AsyncAPI emitters **without a breaking
11/// change to the schema model**. Unknown keys must round-trip untouched.
12///
13/// This is a newtype rather than a bare `BTreeMap` because `Field` derives `Hash`,
14/// `Ord` and `Eq`, which `serde_json::Value` does not implement. The manual impls
15/// below compare and hash values by their canonical JSON text.
16#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
17#[serde(transparent)]
18pub struct MetaMap(pub BTreeMap<String, serde_json::Value>);
19
20impl MetaMap {
21    pub fn is_empty(&self) -> bool {
22        self.0.is_empty()
23    }
24
25    pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
26        self.0.get(key)
27    }
28
29    pub fn insert(&mut self, key: impl Into<String>, value: serde_json::Value) {
30        self.0.insert(key.into(), value);
31    }
32}
33
34/// Deterministic because `BTreeMap` iterates in key order and `Value`'s `Display`
35/// is a canonical JSON rendering.
36impl Hash for MetaMap {
37    fn hash<H: Hasher>(&self, state: &mut H) {
38        for (key, value) in &self.0 {
39            key.hash(state);
40            value.to_string().hash(state);
41        }
42    }
43}
44
45impl Ord for MetaMap {
46    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
47        self.0
48            .iter()
49            .map(|(k, v)| (k, v.to_string()))
50            .cmp(other.0.iter().map(|(k, v)| (k, v.to_string())))
51    }
52}
53
54impl PartialOrd for MetaMap {
55    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
56        Some(self.cmp(other))
57    }
58}
59
60/// `Field` is a struct that represents the parameters and returns in an endpoint schema.
61#[derive(Clone, Debug, Hash, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
62#[non_exhaustive]
63pub struct Field {
64    /// The name of the field (e.g. `user_id`)
65    pub name: String,
66
67    /// The description of the field
68    #[serde(skip)]
69    pub description: String,
70
71    /// The type of the field (e.g. `Type::BigInt`)
72    pub ty: Type,
73
74    /// Emitter annotations — see [`MetaMap`]. Empty in 2.0.
75    #[serde(default, skip_serializing_if = "MetaMap::is_empty")]
76    pub meta: MetaMap,
77}
78
79impl Field {
80    /// Creates a new `Field` with the given name and type.
81    /// `description` is set to `""`.
82    pub fn new(name: impl Into<String>, ty: Type) -> Self {
83        Self {
84            name: name.into(),
85            description: "".into(),
86            ty,
87            meta: MetaMap::default(),
88        }
89    }
90
91    /// Creates a new `Field` with the given name, type and description.
92    pub fn new_with_description(
93        name: impl Into<String>,
94        description: impl Into<String>,
95        ty: Type,
96    ) -> Self {
97        Self {
98            name: name.into(),
99            description: description.into(),
100            ty,
101            meta: MetaMap::default(),
102        }
103    }
104
105    /// Attach emitter annotations. See [`MetaMap`].
106    #[must_use]
107    pub fn with_meta(mut self, meta: MetaMap) -> Self {
108        self.meta = meta;
109        self
110    }
111}
112
113/// `EnumVariant` is a struct that represents the variants of an enum.
114#[derive(Clone, Debug, Hash, Serialize, Deserialize, Ord, PartialOrd, Eq, PartialEq)]
115#[non_exhaustive]
116pub struct EnumVariant {
117    /// The name of the variant (e.g. `UniSwap`)
118    pub name: String,
119
120    /// A description added by `new_with_description` method
121    pub description: String,
122
123    /// The value of the variant (e.g. 1)
124    pub value: i64,
125}
126
127impl EnumVariant {
128    /// Creates a new `EnumVariant` with the given name and value.
129    /// `description` is set to `""`.
130    pub fn new(name: impl Into<String>, value: i64) -> Self {
131        Self {
132            name: name.into(),
133            description: "".into(),
134            value,
135        }
136    }
137
138    /// Creates a new `EnumVariant` with the given name, value and description.
139    pub fn new_with_description(
140        name: impl Into<String>,
141        description: impl Into<String>,
142        value: i64,
143    ) -> Self {
144        Self {
145            name: name.into(),
146            description: description.into(),
147            value,
148        }
149    }
150}
151
152/// `Type` is an enum that represents the types of the fields in an endpoint schema.
153#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, PartialOrd, Eq, Ord)]
154#[non_exhaustive]
155pub enum Type {
156    UInt32,
157    Int32,
158    Int64,
159    Float64,
160    Boolean,
161    String,
162    Bytea,
163    UUID,
164    NanoId {
165        len: usize,
166    },
167    IpAddr,
168    Struct {
169        name: String,
170        fields: Vec<Field>,
171    },
172    StructRef(String),
173    Object,
174    // DataTable {
175    //     name: String,
176    //     fields: Vec<Field>,
177    // },
178    StructTable {
179        struct_ref: String,
180    },
181    Vec(Box<Type>),
182    Unit,
183    Optional(Box<Type>),
184    Enum {
185        name: String,
186        variants: Vec<EnumVariant>,
187    },
188    EnumRef {
189        name: String,
190        #[serde(default, skip_serializing)]
191        prefixed_name: bool,
192    },
193    TimeStampMs,
194    BlockchainDecimal,
195    BlockchainAddress,
196    BlockchainTransactionHash,
197}
198
199impl Type {
200    /// Creates a new `Type::Struct` with the given name and fields.
201    pub fn struct_(name: impl Into<String>, fields: Vec<Field>) -> Self {
202        Self::Struct {
203            name: name.into(),
204            fields,
205        }
206    }
207
208    /// Creates a new `Type::StructRef` with the given name.
209    pub fn struct_ref(name: impl Into<String>) -> Self {
210        Self::StructRef(name.into())
211    }
212
213    // /// Creates a new `Type::DataTable` with the given name and fields.
214    // pub fn datatable(name: impl Into<String>, fields: Vec<Field>) -> Self {
215    //     Self::DataTable {
216    //         name: name.into(),
217    //         fields,
218    //     }
219    // }
220
221    /// Creates a new `Type::StructTable` with the given struct reference.
222    pub fn struct_table(struct_ref: impl Into<String>) -> Self {
223        Self::StructTable {
224            struct_ref: struct_ref.into(),
225        }
226    }
227
228    /// Creates a new `Type::Vec` with the given type.
229    pub fn vec(ty: Type) -> Self {
230        Self::Vec(Box::new(ty))
231    }
232
233    /// Creates a new `Type::Optional` with the given type.
234    pub fn optional(ty: Type) -> Self {
235        Self::Optional(Box::new(ty))
236    }
237
238    /// Creates a new `Type::EnumRef` with the given name.
239    pub fn enum_ref(name: impl Into<String>, prefixed_name: bool) -> Self {
240        Self::EnumRef {
241            name: name.into(),
242            prefixed_name,
243        }
244    }
245
246    /// Creates a new `Type::Enum` with the given name and fields/variants.
247    pub fn enum_(name: impl Into<String>, fields: Vec<EnumVariant>) -> Self {
248        Self::Enum {
249            name: name.into(),
250            variants: fields,
251        }
252    }
253    pub fn try_unwrap(self) -> Option<Self> {
254        match self {
255            Self::Vec(v) => Some(*v),
256            // Self::DataTable { .. } => None,
257            Self::StructTable { .. } => None,
258            _ => Some(self),
259        }
260    }
261
262    pub fn add_default_enum_derives(input: String) -> String {
263        format!(
264            r#"#[derive(Debug, Clone, Copy, Serialize, Deserialize, FromPrimitive, PartialEq, Eq, PartialOrd, Ord, EnumString, Display, Hash)]{input}"#
265        )
266    }
267
268    pub fn add_default_struct_derives(input: String) -> String {
269        format!(
270            r#" #[derive(Serialize, Deserialize, Debug, Clone)]
271                #[serde(rename_all = "camelCase")]
272                {input}
273            "#
274        )
275    }
276}