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