domainstack_schema/
traits.rs

1//! Traits for schema generation.
2
3use crate::Schema;
4
5/// Types that can generate OpenAPI schemas.
6///
7/// This trait should be implemented for all domain types that need
8/// to appear in OpenAPI specifications.
9///
10/// # Example
11///
12/// ```rust
13/// use domainstack_schema::{ToSchema, Schema};
14///
15/// struct User {
16///     email: String,
17///     age: u8,
18/// }
19///
20/// impl ToSchema for User {
21///     fn schema_name() -> &'static str {
22///         "User"
23///     }
24///
25///     fn schema() -> Schema {
26///         Schema::object()
27///             .property("email", Schema::string().format("email"))
28///             .property("age", Schema::integer().minimum(0).maximum(150))
29///             .required(&["email", "age"])
30///     }
31/// }
32/// ```
33pub trait ToSchema {
34    /// The name of this schema in the OpenAPI spec.
35    fn schema_name() -> &'static str;
36
37    /// Generate the OpenAPI schema for this type.
38    fn schema() -> Schema;
39}
40
41// Implementations for primitive types
42impl ToSchema for String {
43    fn schema_name() -> &'static str {
44        "string"
45    }
46
47    fn schema() -> Schema {
48        Schema::string()
49    }
50}
51
52impl ToSchema for str {
53    fn schema_name() -> &'static str {
54        "string"
55    }
56
57    fn schema() -> Schema {
58        Schema::string()
59    }
60}
61
62impl ToSchema for u8 {
63    fn schema_name() -> &'static str {
64        "integer"
65    }
66
67    fn schema() -> Schema {
68        Schema::integer().minimum(0).maximum(255)
69    }
70}
71
72impl ToSchema for u16 {
73    fn schema_name() -> &'static str {
74        "integer"
75    }
76
77    fn schema() -> Schema {
78        Schema::integer().minimum(0).maximum(65535)
79    }
80}
81
82impl ToSchema for u32 {
83    fn schema_name() -> &'static str {
84        "integer"
85    }
86
87    fn schema() -> Schema {
88        Schema::integer().minimum(0)
89    }
90}
91
92impl ToSchema for i32 {
93    fn schema_name() -> &'static str {
94        "integer"
95    }
96
97    fn schema() -> Schema {
98        Schema::integer()
99    }
100}
101
102impl ToSchema for i64 {
103    fn schema_name() -> &'static str {
104        "integer"
105    }
106
107    fn schema() -> Schema {
108        Schema::integer()
109    }
110}
111
112impl ToSchema for f32 {
113    fn schema_name() -> &'static str {
114        "number"
115    }
116
117    fn schema() -> Schema {
118        Schema::number().format("float")
119    }
120}
121
122impl ToSchema for f64 {
123    fn schema_name() -> &'static str {
124        "number"
125    }
126
127    fn schema() -> Schema {
128        Schema::number().format("double")
129    }
130}
131
132impl ToSchema for bool {
133    fn schema_name() -> &'static str {
134        "boolean"
135    }
136
137    fn schema() -> Schema {
138        Schema::boolean()
139    }
140}
141
142impl<T: ToSchema> ToSchema for Vec<T> {
143    fn schema_name() -> &'static str {
144        "array"
145    }
146
147    fn schema() -> Schema {
148        Schema::array(T::schema())
149    }
150}
151
152impl<T: ToSchema> ToSchema for Option<T> {
153    fn schema_name() -> &'static str {
154        T::schema_name()
155    }
156
157    fn schema() -> Schema {
158        T::schema()
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_string_schema() {
168        assert_eq!(String::schema_name(), "string");
169        let schema = String::schema();
170        assert!(matches!(
171            schema.schema_type,
172            Some(crate::SchemaType::String)
173        ));
174    }
175
176    #[test]
177    fn test_str_schema() {
178        assert_eq!(<str>::schema_name(), "string");
179        let schema = <str>::schema();
180        assert!(matches!(
181            schema.schema_type,
182            Some(crate::SchemaType::String)
183        ));
184    }
185
186    #[test]
187    fn test_u8_schema() {
188        assert_eq!(u8::schema_name(), "integer");
189        let schema = u8::schema();
190        assert_eq!(schema.minimum, Some(0.0));
191        assert_eq!(schema.maximum, Some(255.0));
192    }
193
194    #[test]
195    fn test_u16_schema() {
196        assert_eq!(u16::schema_name(), "integer");
197        let schema = u16::schema();
198        assert_eq!(schema.minimum, Some(0.0));
199        assert_eq!(schema.maximum, Some(65535.0));
200    }
201
202    #[test]
203    fn test_u32_schema() {
204        assert_eq!(u32::schema_name(), "integer");
205        let schema = u32::schema();
206        assert_eq!(schema.minimum, Some(0.0));
207        assert_eq!(schema.maximum, None);
208    }
209
210    #[test]
211    fn test_i32_schema() {
212        assert_eq!(i32::schema_name(), "integer");
213        let schema = i32::schema();
214        assert!(matches!(
215            schema.schema_type,
216            Some(crate::SchemaType::Integer)
217        ));
218    }
219
220    #[test]
221    fn test_i64_schema() {
222        assert_eq!(i64::schema_name(), "integer");
223        let schema = i64::schema();
224        assert!(matches!(
225            schema.schema_type,
226            Some(crate::SchemaType::Integer)
227        ));
228    }
229
230    #[test]
231    fn test_f32_schema() {
232        assert_eq!(f32::schema_name(), "number");
233        let schema = f32::schema();
234        assert!(matches!(
235            schema.schema_type,
236            Some(crate::SchemaType::Number)
237        ));
238        assert_eq!(schema.format, Some("float".to_string()));
239    }
240
241    #[test]
242    fn test_f64_schema() {
243        assert_eq!(f64::schema_name(), "number");
244        let schema = f64::schema();
245        assert!(matches!(
246            schema.schema_type,
247            Some(crate::SchemaType::Number)
248        ));
249        assert_eq!(schema.format, Some("double".to_string()));
250    }
251
252    #[test]
253    fn test_bool_schema() {
254        assert_eq!(bool::schema_name(), "boolean");
255        let schema = bool::schema();
256        assert!(matches!(
257            schema.schema_type,
258            Some(crate::SchemaType::Boolean)
259        ));
260    }
261
262    #[test]
263    fn test_vec_schema() {
264        assert_eq!(<Vec<String>>::schema_name(), "array");
265        let schema = <Vec<String>>::schema();
266        assert!(matches!(schema.schema_type, Some(crate::SchemaType::Array)));
267        assert!(schema.items.is_some());
268    }
269
270    #[test]
271    fn test_option_schema() {
272        assert_eq!(<Option<u32>>::schema_name(), "integer");
273        let schema = <Option<u32>>::schema();
274        assert_eq!(schema.minimum, Some(0.0));
275    }
276}