1#![warn(missing_docs)]
2#![warn(clippy::std_instead_of_core)]
3#![warn(clippy::std_instead_of_alloc)]
4#![forbid(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 }
89 }
90 _ => {} }
92
93 match shape.def {
95 Def::Scalar(ref scalar_def) => serialize_scalar(scalar_def, writer)?,
96 Def::Map(_map_def) => todo!("Map"),
97 Def::List(list_def) => serialize_list(list_def, writer)?,
98 Def::Slice(slice_def) => serialize_slice(slice_def, writer)?,
99 Def::Array(array_def) => serialize_array(array_def, writer)?,
100 Def::Option(option_def) => serialize_option(option_def, writer)?,
101 Def::SmartPointer(SmartPointerDef {
102 pointee: Some(inner_shape),
103 ..
104 }) => serialize(inner_shape(), &[], writer)?,
105 Def::Undefined => {
106 match &shape.ty {
109 Type::Primitive(primitive) => {
110 use facet_core::{NumericType, PrimitiveType, TextualType};
111 match primitive {
112 PrimitiveType::Numeric(NumericType::Float) => {
113 write!(writer, "\"type\": \"number\", \"format\": \"double\"")?;
114 }
115 PrimitiveType::Boolean => {
116 write!(writer, "\"type\": \"boolean\"")?;
117 }
118 PrimitiveType::Textual(TextualType::Str) => {
119 write!(writer, "\"type\": \"string\"")?;
120 }
121 _ => {
122 write!(writer, "\"type\": \"unknown\"")?;
123 }
124 }
125 }
126 Type::Pointer(PointerType::Reference(pt) | PointerType::Raw(pt)) => {
127 serialize((pt.target)(), &[], writer)?
128 }
129 _ => {
130 write!(writer, "\"type\": \"unknown\"")?;
131 }
132 }
133 }
134 _ => {
135 write!(writer, "\"type\": \"unknown\"")?;
136 }
137 }
138
139 Ok(())
140}
141
142fn serialize_doc<W: Write>(doc: &[&str], writer: &mut W) -> Result<(), std::io::Error> {
143 if !doc.is_empty() {
144 let doc = doc.join("\n");
145 write!(writer, "\"description\": \"{}\",", doc.trim())?;
146 }
147 Ok(())
148}
149
150fn serialize_scalar<W: Write>(scalar_def: &ScalarDef, writer: &mut W) -> std::io::Result<()> {
152 match scalar_def.affinity {
153 facet_core::ScalarAffinity::Number(number_affinity) => {
154 match number_affinity.bits {
155 facet_core::NumberBits::Integer { size, sign } => {
156 write!(writer, "\"type\": \"integer\"")?;
157 let bits = match size {
158 facet_core::IntegerSize::Fixed(bits) => bits,
159 facet_core::IntegerSize::PointerSized => core::mem::size_of::<usize>() * 8,
160 };
161 match sign {
162 facet_core::Signedness::Unsigned => {
163 write!(writer, ", \"format\": \"uint{bits}\"")?;
164 write!(writer, ", \"minimum\": 0")?;
165 }
166 facet_core::Signedness::Signed => {
167 write!(writer, ", \"format\": \"int{bits}\"")?;
168 }
169 }
170 }
171 facet_core::NumberBits::Float { .. } => {
172 write!(writer, "\"type\": \"number\"")?;
173 write!(writer, ", \"format\": \"double\"")?;
174 }
175 _ => unimplemented!(),
176 }
177 Ok(())
178 }
179 facet_core::ScalarAffinity::String(_) => {
180 write!(writer, "\"type\": \"string\"")?;
181 Ok(())
182 }
183 facet_core::ScalarAffinity::Boolean(_) => {
184 write!(writer, "\"type\": \"boolean\"")?;
185 Ok(())
186 }
187 _ => Err(std::io::Error::other(format!(
188 "facet-jsonschema: nsupported scalar type: {scalar_def:#?}"
189 ))),
190 }
191}
192
193fn serialize_struct<W: Write>(
194 struct_type: &facet_core::StructType,
195 writer: &mut W,
196) -> std::io::Result<()> {
197 write!(writer, "\"type\": \"object\",")?;
198 let required = struct_type
199 .fields
200 .iter()
201 .map(|f| format!("\"{}\"", f.name))
202 .collect::<Vec<_>>()
203 .join(",");
204 write!(writer, "\"required\": [{required}],")?;
205 write!(writer, "\"properties\": {{")?;
206 let mut first = true;
207 for field in struct_type.fields {
208 if !first {
209 write!(writer, ",")?;
210 }
211 first = false;
212 write!(writer, "\"{}\": {{", field.name)?;
213 serialize(field.shape(), field.doc, writer)?;
214 write!(writer, "}}")?;
215 }
216 write!(writer, "}}")?;
217 Ok(())
218}
219
220fn serialize_list<W: Write>(list_def: facet_core::ListDef, writer: &mut W) -> std::io::Result<()> {
222 write!(writer, "\"type\": \"array\",")?;
223 write!(writer, "\"items\": {{")?;
224 serialize(list_def.t(), &[], writer)?;
225 write!(writer, "}}")?;
226 Ok(())
227}
228
229fn serialize_slice<W: Write>(
231 slice_def: facet_core::SliceDef,
232 writer: &mut W,
233) -> std::io::Result<()> {
234 write!(writer, "\"type\": \"array\",")?;
235 write!(writer, "\"items\": {{")?;
236 serialize(slice_def.t(), &[], writer)?;
237 write!(writer, "}}")?;
238 Ok(())
239}
240
241fn serialize_array<W: Write>(
243 array_def: facet_core::ArrayDef,
244 writer: &mut W,
245) -> std::io::Result<()> {
246 write!(writer, "\"type\": \"array\",")?;
247 write!(writer, "\"minItems\": {},", array_def.n)?;
248 write!(writer, "\"maxItems\": {},", array_def.n)?;
249 write!(writer, "\"items\": {{")?;
250 serialize(array_def.t(), &[], writer)?;
251 write!(writer, "}}")?;
252 Ok(())
253}
254
255fn serialize_option<W: Write>(
257 _option_def: facet_core::OptionDef,
258 writer: &mut W,
259) -> std::io::Result<()> {
260 write!(writer, "\"type\": \"[]\",")?;
261 unimplemented!("serialize_option");
262}
263
264#[cfg(test)]
265mod tests {
266 extern crate alloc;
267 use alloc::{rc::Rc, sync::Arc};
268
269 use super::*;
270 use facet_macros::Facet;
271 use insta::assert_snapshot;
272
273 #[test]
274 fn test_basic() {
275 #[derive(Facet)]
277 #[facet(id = "http://example.com/schema")]
278 struct TestStruct {
279 string_field: String,
281 int_field: u32,
283 vec_field: Vec<bool>,
284 slice_field: &'static [f64],
285 array_field: [f64; 3],
286 }
287
288 let schema = to_string::<TestStruct>();
289 assert_snapshot!(schema);
290 }
291
292 #[test]
293 fn test_pointers() {
294 #[derive(Facet)]
296 #[facet(id = "http://example.com/schema")]
297 struct TestStruct<'a> {
298 normal_pointer: &'a str,
299 box_pointer: Box<u32>,
300 arc: Arc<u32>,
301 rc: Rc<u32>,
302 #[allow(clippy::redundant_allocation)]
303 nested: Rc<&'a Arc<&'a *const u32>>,
304 }
305
306 let schema = to_string::<TestStruct>();
307 assert_snapshot!(schema);
308 }
309}