1#![warn(missing_docs)]
2#![warn(clippy::std_instead_of_core)]
3#![warn(clippy::std_instead_of_alloc)]
4#![deny(unsafe_code)]
5#![doc = include_str!("../README.md")]
6
7extern crate facet_core as facet;
8use facet::{PointerType, SmartPointerDef};
9use facet_core::{Def, Facet, ScalarDef, Shape, Type, UserType};
10
11use std::io::Write;
12
13pub fn to_string<'a, T: Facet<'a>>() -> String {
15 let mut buffer = Vec::new();
16 write!(buffer, "{{").unwrap();
17 write!(
18 buffer,
19 "\"$schema\": \"https://json-schema.org/draft/2020-12/schema\","
20 )
21 .unwrap();
22
23 let mut id = T::SHAPE.attributes.iter().filter_map(|attr| match attr {
25 facet_core::ShapeAttribute::Arbitrary(attr_str) => {
26 if attr_str.starts_with("id") {
27 let id = attr_str
28 .split('=')
29 .nth(1)
30 .unwrap_or_default()
31 .trim()
32 .trim_matches('"');
33 Some(id)
34 } else {
35 None
36 }
37 }
38 _ => None,
39 });
40 match (id.next(), id.next()) {
41 (Some(_), Some(_)) => panic!("More than one id attribute found"),
42 (Some(id), None) => {
43 write!(buffer, "\"$id\": \"{id}\",").unwrap();
44 }
45 _ => {
46 }
48 }
49
50 serialize(T::SHAPE, &[], &mut buffer).unwrap();
51 write!(buffer, "}}").unwrap();
52 String::from_utf8(buffer).unwrap()
53}
54
55fn serialize<'shape, W: Write>(
56 shape: &'shape Shape<'shape>,
57 doc: &[&str],
58 writer: &mut W,
59) -> std::io::Result<()> {
60 serialize_doc(&[shape.doc, doc].concat(), writer)?;
61
62 match &shape.ty {
64 Type::User(UserType::Struct(struct_def)) => {
65 serialize_struct(struct_def, writer)?;
66 return Ok(());
67 }
68 Type::User(UserType::Enum(_enum_def)) => {
69 todo!("Enum");
70 }
71 Type::Sequence(sequence_type) => {
72 use facet_core::SequenceType;
73 match sequence_type {
74 SequenceType::Slice(_slice_type) => {
75 if let Def::Slice(slice_def) = shape.def {
77 serialize_slice(slice_def, writer)?;
78 return Ok(());
79 }
80 }
81 SequenceType::Array(_array_type) => {
82 if let Def::Array(array_def) = shape.def {
84 serialize_array(array_def, writer)?;
85 return Ok(());
86 }
87 }
88 _ => {} }
90 }
91 _ => {} }
93
94 match shape.def {
96 Def::Scalar(ref scalar_def) => serialize_scalar(scalar_def, writer)?,
97 Def::Map(_map_def) => todo!("Map"),
98 Def::List(list_def) => serialize_list(list_def, writer)?,
99 Def::Slice(slice_def) => serialize_slice(slice_def, writer)?,
100 Def::Array(array_def) => serialize_array(array_def, writer)?,
101 Def::Option(option_def) => serialize_option(option_def, writer)?,
102 Def::SmartPointer(SmartPointerDef {
103 pointee: Some(inner_shape),
104 ..
105 }) => serialize(inner_shape(), &[], writer)?,
106 Def::Undefined => {
107 match &shape.ty {
110 Type::Primitive(primitive) => {
111 use facet_core::{NumericType, PrimitiveType, TextualType};
112 match primitive {
113 PrimitiveType::Numeric(NumericType::Float) => {
114 write!(writer, "\"type\": \"number\", \"format\": \"double\"")?;
115 }
116 PrimitiveType::Boolean => {
117 write!(writer, "\"type\": \"boolean\"")?;
118 }
119 PrimitiveType::Textual(TextualType::Str) => {
120 write!(writer, "\"type\": \"string\"")?;
121 }
122 _ => {
123 write!(writer, "\"type\": \"unknown\"")?;
124 }
125 }
126 }
127 Type::Pointer(PointerType::Reference(pt) | PointerType::Raw(pt)) => {
128 serialize((pt.target)(), &[], writer)?
129 }
130 _ => {
131 write!(writer, "\"type\": \"unknown\"")?;
132 }
133 }
134 }
135 _ => {
136 write!(writer, "\"type\": \"unknown\"")?;
137 }
138 }
139
140 Ok(())
141}
142
143fn serialize_doc<W: Write>(doc: &[&str], writer: &mut W) -> Result<(), std::io::Error> {
144 if !doc.is_empty() {
145 let doc = doc.join("\n");
146 write!(writer, "\"description\": \"{}\",", doc.trim())?;
147 }
148 Ok(())
149}
150
151fn serialize_scalar<W: Write>(scalar_def: &ScalarDef, writer: &mut W) -> std::io::Result<()> {
153 match scalar_def.affinity {
154 facet_core::ScalarAffinity::Number(number_affinity) => {
155 match number_affinity.bits {
156 facet_core::NumberBits::Integer { bits, sign } => {
157 write!(writer, "\"type\": \"integer\"")?;
158 match sign {
159 facet_core::Signedness::Unsigned => {
160 write!(writer, ", \"format\": \"uint{bits}\"")?;
161 write!(writer, ", \"minimum\": 0")?;
162 }
163 facet_core::Signedness::Signed => {
164 write!(writer, ", \"format\": \"int{bits}\"")?;
165 }
166 }
167 }
168 facet_core::NumberBits::Float { .. } => {
169 write!(writer, "\"type\": \"number\"")?;
170 write!(writer, ", \"format\": \"double\"")?;
171 }
172 _ => unimplemented!(),
173 }
174 Ok(())
175 }
176 facet_core::ScalarAffinity::String(_) => {
177 write!(writer, "\"type\": \"string\"")?;
178 Ok(())
179 }
180 facet_core::ScalarAffinity::Boolean(_) => {
181 write!(writer, "\"type\": \"boolean\"")?;
182 Ok(())
183 }
184 _ => Err(std::io::Error::other(format!(
185 "facet-jsonschema: nsupported scalar type: {scalar_def:#?}"
186 ))),
187 }
188}
189
190fn serialize_struct<W: Write>(
191 struct_type: &facet_core::StructType,
192 writer: &mut W,
193) -> std::io::Result<()> {
194 write!(writer, "\"type\": \"object\",")?;
195 let required = struct_type
196 .fields
197 .iter()
198 .map(|f| format!("\"{}\"", f.name))
199 .collect::<Vec<_>>()
200 .join(",");
201 write!(writer, "\"required\": [{required}],")?;
202 write!(writer, "\"properties\": {{")?;
203 let mut first = true;
204 for field in struct_type.fields {
205 if !first {
206 write!(writer, ",")?;
207 }
208 first = false;
209 write!(writer, "\"{}\": {{", field.name)?;
210 serialize(field.shape(), field.doc, writer)?;
211 write!(writer, "}}")?;
212 }
213 write!(writer, "}}")?;
214 Ok(())
215}
216
217fn serialize_list<W: Write>(list_def: facet_core::ListDef, writer: &mut W) -> std::io::Result<()> {
219 write!(writer, "\"type\": \"array\",")?;
220 write!(writer, "\"items\": {{")?;
221 serialize(list_def.t(), &[], writer)?;
222 write!(writer, "}}")?;
223 Ok(())
224}
225
226fn serialize_slice<W: Write>(
228 slice_def: facet_core::SliceDef,
229 writer: &mut W,
230) -> std::io::Result<()> {
231 write!(writer, "\"type\": \"array\",")?;
232 write!(writer, "\"items\": {{")?;
233 serialize(slice_def.t(), &[], writer)?;
234 write!(writer, "}}")?;
235 Ok(())
236}
237
238fn serialize_array<W: Write>(
240 array_def: facet_core::ArrayDef,
241 writer: &mut W,
242) -> std::io::Result<()> {
243 write!(writer, "\"type\": \"array\",")?;
244 write!(writer, "\"minItems\": {},", array_def.n)?;
245 write!(writer, "\"maxItems\": {},", array_def.n)?;
246 write!(writer, "\"items\": {{")?;
247 serialize(array_def.t(), &[], writer)?;
248 write!(writer, "}}")?;
249 Ok(())
250}
251
252fn serialize_option<W: Write>(
254 _option_def: facet_core::OptionDef,
255 writer: &mut W,
256) -> std::io::Result<()> {
257 write!(writer, "\"type\": \"[]\",")?;
258 unimplemented!("serialize_option");
259}
260
261#[cfg(test)]
262mod tests {
263 extern crate alloc;
264 use alloc::{rc::Rc, sync::Arc};
265
266 use super::*;
267 use facet_derive::Facet;
268 use insta::assert_snapshot;
269
270 #[test]
271 fn test_basic() {
272 #[derive(Facet)]
274 #[facet(id = "http://example.com/schema")]
275 struct TestStruct {
276 string_field: String,
278 int_field: u32,
280 vec_field: Vec<bool>,
281 slice_field: &'static [f64],
282 array_field: [f64; 3],
283 }
284
285 let schema = to_string::<TestStruct>();
286 assert_snapshot!(schema);
287 }
288
289 #[test]
290 fn test_pointers() {
291 #[derive(Facet)]
293 #[facet(id = "http://example.com/schema")]
294 struct TestStruct<'a> {
295 normal_pointer: &'a str,
296 box_pointer: Box<u32>,
297 arc: Arc<u32>,
298 rc: Rc<u32>,
299 #[allow(clippy::redundant_allocation)]
300 nested: Rc<&'a Arc<&'a *const u32>>,
301 }
302
303 let schema = to_string::<TestStruct>();
304 assert_snapshot!(schema);
305 }
306}