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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use serde_json::{json, Value};
use crate::genson_rs::node::{DataType, SchemaNode};
const DEFAULT_SCHEMA_URI: &str = "http://json-schema.org/schema#";
const NULL_SCHEMA_URI: &str = "NULL";
pub struct SchemaBuilder {
schema_uri: Option<String>,
root_node: SchemaNode,
}
impl SchemaBuilder {
/// Create a new SchemaBuilder object. The schema_uri parameter is optional, a value
/// of "AUTO" will automatically detect the schema URI based on the input schema, if no
/// schema URI was detected, a default URI of "<http://json-schema.org/schema#>" will be used.
/// A value of None will leave out the "$schema" keyword in the output schema.
pub fn new(schema_uri: Option<&str>) -> Self {
// TODO: the functionality to allow non-default node class with extended
// strategies is not supported yet
let root_node: SchemaNode = SchemaNode::new();
if let Some(uri) = schema_uri {
if uri == "AUTO" {
SchemaBuilder {
schema_uri: None,
root_node,
}
} else {
SchemaBuilder {
schema_uri: Some(uri.to_string()),
root_node,
}
}
} else {
SchemaBuilder {
schema_uri: Some(NULL_SCHEMA_URI.to_string()),
root_node,
}
}
}
/// Merge in raw JSON schema object
pub fn add_schema(&mut self, mut schema: Value) {
self.add_schema_mut(&mut schema);
}
/// Like [`add_schema`](Self::add_schema) but leaves `schema` with the caller, so it can be
/// dropped elsewhere (a large schema is costly to free). Removes `$schema` from it when
/// that is adopted as this builder's schema URI.
pub fn add_schema_mut(&mut self, schema: &mut Value) {
if let Value::Object(schema_obj) = schema {
if schema_obj.contains_key("$schema") && self.schema_uri.is_none() {
self.schema_uri = Some(schema_obj["$schema"].as_str().unwrap().to_string());
schema_obj.shift_remove("$schema");
}
self.root_node.add_schema(DataType::Schema(schema));
} else {
panic!("Invalid schema type - must be a valid JSON object")
}
}
/// Merge `schemas` in order, same result as calling [`add_schema_mut`](Self::add_schema_mut)
/// on each, with the merge parallelised across the root object's properties.
pub fn add_schemas_mut(&mut self, schemas: &mut [Value]) {
for schema in schemas.iter_mut() {
let Value::Object(schema_obj) = schema else {
panic!("Invalid schema type - must be a valid JSON object")
};
if schema_obj.contains_key("$schema") && self.schema_uri.is_none() {
self.schema_uri = Some(schema_obj["$schema"].as_str().unwrap().to_string());
schema_obj.shift_remove("$schema");
}
}
let refs: Vec<&Value> = schemas.iter().collect();
if !self.root_node.add_object_schemas_par(&refs) {
for schema in refs {
self.root_node.add_schema(DataType::Schema(schema));
}
}
}
/// Merge another builder into this one. When this builder is still empty and both
/// share the same `$schema` handling, takes over its nodes instead of round-tripping
/// through a schema value.
pub fn absorb(&mut self, other: SchemaBuilder) {
// `other` with the NULL uri emits no "$schema", so nothing else would be merged.
if self.root_node.is_empty() && other.schema_uri.as_deref() == Some(NULL_SCHEMA_URI) {
self.root_node = other.root_node;
} else {
self.add_schema(other.to_schema());
}
}
/// Merge multiple raw JSON schema objects at once
pub fn add_schemas(&mut self, schemas: &[Value]) {
self.root_node.add_schemas(schemas);
}
/// Merge in another SchemaNode object
pub fn add_schema_node(&mut self, node: SchemaNode) {
self.root_node.add_schema(DataType::SchemaNode(&node));
}
/// Modify the schema to accomodate the input object
pub fn add_object(&mut self, object: super::JsonValue) {
self.root_node.add_object(DataType::Object(object));
}
/// Export the currently constructed schema as a JSON object
pub fn to_schema(&self) -> Value {
let mut base_schema = self.get_base_schema();
let base_schema_map = base_schema.as_object_mut().unwrap();
let Value::Object(node_schema_map) = self.root_node.to_schema() else {
panic!("node schema must be an object")
};
for (key, value) in node_schema_map {
base_schema_map.insert(key, value);
}
base_schema
}
fn get_base_schema(&self) -> Value {
if let Some(uri) = &self.schema_uri {
if uri == NULL_SCHEMA_URI {
json!({})
} else {
json!({"$schema": uri})
}
} else {
json!({"$schema": DEFAULT_SCHEMA_URI})
}
}
/// Serialize the currently constructed schema to a JSON string
pub fn to_json(&self) -> String {
let schema = self.to_schema();
schema.to_string()
}
}