1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
/*!
Integration with [json_typegen](https://github.com/evestera/json_typegen)
You can:
```rust
# use schema_analysis::Schema;
# use schema_analysis::targets::json_typegen::{Shape, OutputMode, Options};
#
# let schema: Schema = Schema::Boolean(Default::default());
#
// Convert to a json_typegen Shape.
let shape: Shape = schema.to_json_typegen_shape();
// Convert to a specific json_typegen output with default options.
let output: String = schema.process_with_json_typegen(OutputMode::Rust).unwrap();
// Convert a json_typegen Shape with custom options.
let output: String = json_typegen_shared::codegen_from_shape("Root", &Shape::Bool, Options::default()).unwrap();
```
*/
pub use json_typegen_shared::{codegen_from_shape, ErrorKind, JTError, Options, OutputMode, Shape};
use crate::{Field, Schema};
impl Schema {
/// Convert a [Schema] to a json_typegen [Shape].
pub fn to_json_typegen_shape(&self) -> Shape {
schema_to_shape(self)
}
/// Convert a [Schema] to a supported json_typegen output
pub fn process_with_json_typegen(&self, mode: OutputMode) -> Result<String, JTError> {
let mut options = Options::default();
options.output_mode = mode;
self.process_with_json_typegen_options("Root", &options)
}
/// Convert a [Schema] to a supported json_typegen output using custom settings.
pub fn process_with_json_typegen_options(
&self,
name: &str,
options: &Options,
) -> Result<String, JTError> {
let shape = self.to_json_typegen_shape();
codegen_from_shape(name, &shape, options.clone())
}
}
impl From<Schema> for Shape {
fn from(schema: Schema) -> Self {
schema_to_shape(&schema)
}
}
fn schema_to_shape(schema: &Schema) -> Shape {
match schema {
Schema::Null(_) => Shape::Null,
Schema::Boolean(_) => Shape::Bool,
Schema::Integer(_) => Shape::Integer,
Schema::Float(_) => Shape::Floating,
Schema::String(_) => Shape::StringT,
Schema::Bytes(_) => Shape::Any,
Schema::Sequence { field, .. } => Shape::VecT {
elem_type: Box::new(convert_field(field.as_ref(), field.status.may_be_null)),
},
Schema::Struct { fields, .. } => Shape::Struct {
fields: fields
.iter()
.map(|(name, field)| (name.clone(), convert_field(field, field.status.is_option())))
.collect(),
},
// From Shape docs:
// `Any` represents conflicting inference information that can not be represented by any
// single shape
Schema::Union { .. } => Shape::Any,
}
}
/// This function also takes `is_option` because fields in structs are considered 'optional' also
/// if they are missing, while sequences whose fields may be missing are merely empty.
///
/// In both cases the field is optional if it may have a value of null/none.
fn convert_field(field: &Field, is_option: bool) -> Shape {
// From Shape docs:
// `Bottom` represents the absence of any inference information
// `Optional(T)` represents that a value is nullable, or not always present
// `Null` represents optionality with no further information. [Equivalent to `Optional(Bottom)`]
// So:
// `Bottom` would be equivalent to a field with a `None` schema.
// `Optional(T)` would be equivalent to a field marked as possibly missing or possibly null.
// `Null` would be equivalent to a field that is both missing/null and has no schema.
match &field.schema {
Some(s) if is_option => Shape::Optional(Box::new(schema_to_shape(s))),
Some(s) => schema_to_shape(s),
None if is_option => Shape::Null,
None => Shape::Bottom,
}
}