airbyte_protocol/
schema.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6pub struct JsonSchema {
7    #[serde(skip_serializing_if = "Option::is_none")]
8    pub title: Option<String>,
9    #[serde(skip_serializing_if = "Option::is_none")]
10    pub description: Option<String>,
11    #[serde(flatten)]
12    pub r#type: Type,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
16#[serde(untagged)]
17pub enum Type {
18    PrimitiveFormat {
19        r#type: PrimitiveFormat,
20        format: Format,
21    },
22    SingleFormat {
23        r#type: [PrimitiveFormat; 1],
24        format: Format,
25    },
26    VariantFormat {
27        r#type: [PrimitiveFormat; 2],
28        format: Format,
29    },
30    Primitive {
31        r#type: Primitive,
32    },
33    Single {
34        r#type: [Primitive; 1],
35    },
36    Variant {
37        r#type: [Primitive; 2],
38    },
39    Compound(Compound),
40    Empty(Empty),
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
44#[serde(rename_all = "lowercase")]
45pub enum Primitive {
46    Null,
47    Boolean,
48    Integer,
49    Number,
50    String,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54#[serde(rename_all = "lowercase")]
55pub enum PrimitiveFormat {
56    Null,
57    String,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
61#[serde(rename_all = "kebab-case")]
62pub enum Format {
63    Date,
64    Time,
65    DateTime,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
69#[serde(tag = "type", rename_all = "lowercase")]
70pub enum Compound {
71    Array(Array),
72    Object(Object),
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
76#[serde(rename_all = "camelCase")]
77pub struct Array {
78    pub items: Box<Type>,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
82#[serde(rename_all = "camelCase")]
83pub struct Object {
84    pub properties: HashMap<String, Type>,
85    #[serde(skip_serializing_if = "Option::is_none")]
86    pub required: Option<Vec<String>>,
87    #[serde(skip_serializing_if = "Option::is_none")]
88    pub additional_properties: Option<bool>,
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
92pub struct Empty {}
93
94#[cfg(test)]
95pub mod tests {
96    use std::collections::HashMap;
97
98    use crate::schema::{
99        Array, Compound, Empty, Format, JsonSchema, Object, Primitive, PrimitiveFormat, Type,
100    };
101
102    #[test]
103    fn test_null() {
104        let input = r#"{"type": "null"}"#;
105
106        let value: JsonSchema = serde_json::from_str(input).unwrap();
107
108        let expected = JsonSchema {
109            title: None,
110            description: None,
111            r#type: Type::Primitive {
112                r#type: Primitive::Null,
113            },
114        };
115        assert_eq!(value, expected);
116    }
117
118    #[test]
119    fn test_boolean() {
120        let input = r#"{"type": "boolean"}"#;
121
122        let value: JsonSchema = serde_json::from_str(input).unwrap();
123
124        let expected = JsonSchema {
125            title: None,
126            description: None,
127            r#type: Type::Primitive {
128                r#type: Primitive::Boolean,
129            },
130        };
131        assert_eq!(value, expected);
132    }
133
134    #[test]
135    fn test_integer() {
136        let input = r#"{"type": "integer"}"#;
137
138        let value: JsonSchema = serde_json::from_str(input).unwrap();
139
140        let expected = JsonSchema {
141            title: None,
142            description: None,
143            r#type: Type::Primitive {
144                r#type: Primitive::Integer,
145            },
146        };
147        assert_eq!(value, expected);
148    }
149
150    #[test]
151    fn test_string() {
152        let input = r#"{"type": "string"}"#;
153
154        let value: JsonSchema = serde_json::from_str(input).unwrap();
155
156        let expected = JsonSchema {
157            title: None,
158            description: None,
159            r#type: Type::Primitive {
160                r#type: Primitive::String,
161            },
162        };
163        assert_eq!(value, expected);
164    }
165
166    #[test]
167    fn test_single() {
168        let input = r#"{"type": ["string"]}"#;
169
170        let value: JsonSchema = serde_json::from_str(input).unwrap();
171
172        let expected = JsonSchema {
173            title: None,
174            description: None,
175            r#type: Type::Single {
176                r#type: [Primitive::String; 1],
177            },
178        };
179        assert_eq!(value, expected);
180    }
181
182    #[test]
183    fn test_array() {
184        let input = r#"{"type": "array", "items": {"type": "integer"}}"#;
185
186        let value: JsonSchema = serde_json::from_str(input).unwrap();
187
188        let expected = JsonSchema {
189            title: None,
190            description: None,
191            r#type: Type::Compound(Compound::Array(Array {
192                items: Box::new(Type::Primitive {
193                    r#type: Primitive::Integer,
194                }),
195            })),
196        };
197        assert_eq!(value, expected);
198    }
199
200    #[test]
201    fn test_object() {
202        let input =
203            r#"{"required": ["id"], "type": "object", "properties": {"id": {"type": "integer"}}}"#;
204
205        let schema: JsonSchema = serde_json::from_str(input).unwrap();
206
207        let expected = JsonSchema {
208            title: None,
209            description: None,
210            r#type: Type::Compound(Compound::Object(Object {
211                properties: HashMap::from_iter(vec![(
212                    "id".to_string(),
213                    Type::Primitive {
214                        r#type: Primitive::Integer,
215                    },
216                )]),
217                required: Some(vec!["id".to_string()]),
218                additional_properties: None,
219            })),
220        };
221        assert_eq!(schema, expected);
222    }
223
224    #[test]
225    fn test_variant() {
226        let input = r#"{"required": ["id"], "type": "object", "properties": {"id": {"type": ["integer", "null"]}}}"#;
227
228        let schema: JsonSchema = serde_json::from_str(input).unwrap();
229
230        let expected = JsonSchema {
231            title: None,
232            description: None,
233            r#type: Type::Compound(Compound::Object(Object {
234                properties: HashMap::from_iter(vec![(
235                    "id".to_string(),
236                    Type::Variant {
237                        r#type: [Primitive::Integer, Primitive::Null],
238                    },
239                )]),
240                required: Some(vec!["id".to_string()]),
241                additional_properties: None,
242            })),
243        };
244        assert_eq!(schema, expected);
245    }
246
247    #[test]
248    fn test_date() {
249        let input = r#"
250        {
251        "type": "object",
252        "properties": {
253          "order_date": {
254            "type": [
255              "null",
256              "string"
257            ],
258            "format": "date-time"
259          }
260        }}
261       "#;
262
263        let schema: JsonSchema = serde_json::from_str(input).unwrap();
264
265        let expected = JsonSchema {
266            title: None,
267            description: None,
268            r#type: Type::Compound(Compound::Object(Object {
269                properties: HashMap::from_iter(vec![(
270                    "order_date".to_string(),
271                    Type::VariantFormat {
272                        r#type: [PrimitiveFormat::Null, PrimitiveFormat::String],
273                        format: Format::DateTime,
274                    },
275                )]),
276                required: None,
277                additional_properties: None,
278            })),
279        };
280        assert_eq!(schema, expected);
281    }
282
283    #[test]
284    fn test_empty() {
285        let input = r#"{"required": ["id"], "type": "object", "properties": {"id": {}}}"#;
286
287        let schema: JsonSchema = serde_json::from_str(input).unwrap();
288
289        let expected = JsonSchema {
290            title: None,
291            description: None,
292            r#type: Type::Compound(Compound::Object(Object {
293                properties: HashMap::from_iter(vec![("id".to_string(), Type::Empty(Empty {}))]),
294                required: Some(vec!["id".to_string()]),
295                additional_properties: None,
296            })),
297        };
298        assert_eq!(schema, expected);
299    }
300}