Add Schema methods insert, get and remove

These are just convenience methods that delegate to the inner object. `insert` will also convert bool schemas to object schemas.
This commit is contained in:
Graham Esau 2024-08-30 11:40:29 +01:00
parent d6c8b6b022
commit 0672c862c8
7 changed files with 109 additions and 76 deletions

View file

@ -129,9 +129,7 @@ pub struct MyTransform;
impl Transform for MyTransform { impl Transform for MyTransform {
fn transform(&mut self, schema: &mut Schema) { fn transform(&mut self, schema: &mut Schema) {
// First, make our change to this schema // First, make our change to this schema
if let Some(obj) = schema.as_object_mut() { schema.insert("my_property".to_string(), serde_json::json!("hello world"));
obj.insert("my_property".to_string(), serde_json::json!("hello world"));
}
// Then apply the transform to any subschemas // Then apply the transform to any subschemas
transform_subschemas(self, schema); transform_subschemas(self, schema);
@ -147,9 +145,7 @@ Also, since `Transform` is now implemented for functions that take a single `&mu
```rust ```rust
fn my_transform(schema: &mut Schema) { fn my_transform(schema: &mut Schema) {
// First, make our change to this schema // First, make our change to this schema
if let Some(obj) = schema.as_object_mut() { schema.insert("my_property".to_string(), serde_json::json!("hello world"));
obj.insert("my_property".to_string(), serde_json::json!("hello world"));
}
// Then apply the transform to any subschemas // Then apply the transform to any subschemas
transform_subschemas(&mut my_transform, schema); transform_subschemas(&mut my_transform, schema);
@ -165,9 +161,7 @@ Finally, you can also use the `RecursiveTransform` newtype to convert a non-recu
```rust ```rust
fn my_transform2(schema: &mut Schema) { fn my_transform2(schema: &mut Schema) {
if let Some(obj) = schema.as_object_mut() { schema.insert("my_property".to_string(), serde_json::json!("hello world"));
obj.insert("my_property".to_string(), serde_json::json!("hello world"));
}
} }
let mut schema = schemars::schema_for!(str); let mut schema = schemars::schema_for!(str);

View file

@ -25,9 +25,7 @@ pub enum MyEnum {
} }
fn remove_format(schema: &mut Schema) { fn remove_format(schema: &mut Schema) {
if let Some(obj) = schema.as_object_mut() { schema.remove("format");
obj.remove("format");
}
} }
fn main() { fn main() {

View file

@ -25,9 +25,7 @@ pub enum MyEnum {
} }
fn remove_format(schema: &mut Schema) { fn remove_format(schema: &mut Schema) {
if let Some(obj) = schema.as_object_mut() { schema.remove("format");
obj.remove("format");
}
} }
fn main() { fn main() {

View file

@ -20,9 +20,7 @@ pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
let mut schema = T::_schemars_private_non_optional_json_schema(generator); let mut schema = T::_schemars_private_non_optional_json_schema(generator);
if T::_schemars_private_is_option() && !required { if T::_schemars_private_is_option() && !required {
if let Some(object) = schema.as_object_mut() { schema.remove("required");
object.remove("required");
}
} }
// Always allow aditional/unevaluated properties, because the outer struct determines // Always allow aditional/unevaluated properties, because the outer struct determines
@ -33,17 +31,15 @@ pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
} }
fn allow_unknown_properties(schema: &mut Schema) { fn allow_unknown_properties(schema: &mut Schema) {
if let Some(obj) = schema.as_object_mut() { if schema.get("additionalProperties").and_then(Value::as_bool) == Some(false) {
if obj.get("additionalProperties").and_then(Value::as_bool) == Some(false) { schema.remove("additionalProperties");
obj.remove("additionalProperties");
} }
if obj.get("unevaluatedProperties").and_then(Value::as_bool) == Some(false) { if schema.get("unevaluatedProperties").and_then(Value::as_bool) == Some(false) {
obj.remove("unevaluatedProperties"); schema.remove("unevaluatedProperties");
} }
transform_immediate_subschemas(&mut allow_unknown_properties, schema); transform_immediate_subschemas(&mut allow_unknown_properties, schema);
} }
}
/// Hack to simulate specialization: /// Hack to simulate specialization:
/// `MaybeSerializeWrapper(x).maybe_to_value()` will resolve to either /// `MaybeSerializeWrapper(x).maybe_to_value()` will resolve to either
@ -211,7 +207,7 @@ pub fn apply_inner_validation(schema: &mut Schema, f: fn(&mut Schema) -> ()) {
if let Some(inner_schema) = schema if let Some(inner_schema) = schema
.as_object_mut() .as_object_mut()
.and_then(|o| o.get_mut("items")) .and_then(|o| o.get_mut("items"))
.and_then(|i| <&mut Schema>::try_from(i).ok()) .and_then(|i| i.try_into().ok())
{ {
f(inner_schema); f(inner_schema);
} }

View file

@ -138,10 +138,80 @@ impl Schema {
self.0 = Value::Object(map); self.0 = Value::Object(map);
} }
self.as_object_mut() self.0
.as_object_mut()
.expect("Schema value should be of type Object.") .expect("Schema value should be of type Object.")
} }
/// Inserts a property into the schema, replacing any previous value.
///
/// If the schema wraps a bool value, it will first be converted into an equivalent object schema.
///
/// If the schema did not have this key present, `None` is returned.
///
/// If the schema did have this key present, the value is updated, and the old value is returned.
///
/// # Example
/// ```
/// use schemars::json_schema;
/// use serde_json::json;
///
/// let mut schema = json_schema!(true);
/// assert_eq!(schema.insert("type".to_owned(), "array".into()), None);
/// assert_eq!(schema.insert("type".to_owned(), "object".into()), Some(json!("array")));
///
/// assert_eq!(schema, json_schema!({"type": "object"}));
/// ```
pub fn insert(&mut self, k: String, v: Value) -> Option<Value> {
self.ensure_object().insert(k, v)
}
/// If the `Schema`'s underlying JSON value is an object, gets a reference to that object's value for the given key.
///
/// This always returns `None` for bool schemas.
///
/// # Example
/// ```
/// use schemars::json_schema;
/// use serde_json::json;
///
/// let obj_schema = json_schema!({"type": "array"});
/// assert_eq!(obj_schema.get("type"), Some(&json!("array")));
/// assert_eq!(obj_schema.get("format"), None);
///
/// let bool_schema = json_schema!(true);
/// assert_eq!(bool_schema.get("type"), None);
/// ```
pub fn get<Q>(&self, key: &Q) -> Option<&Value>
where
String: core::borrow::Borrow<Q>,
Q: ?Sized + Ord + Eq + core::hash::Hash,
{
self.0.as_object().and_then(|o| o.get(key))
}
/// If the `Schema`'s underlying JSON value is an object, removes and returns its value for the given key.
///
/// This always returns `None` for bool schemas, without modifying them.
///
/// # Example
/// ```
/// use schemars::json_schema;
/// use serde_json::json;
///
/// let mut schema = json_schema!({"type": "array"});
/// assert_eq!(schema.remove("type"), Some(json!("array")));
/// assert_eq!(schema, json_schema!({}));
///
/// ```
pub fn remove<Q>(&mut self, key: &Q) -> Option<Value>
where
String: core::borrow::Borrow<Q>,
Q: ?Sized + Ord + Eq + core::hash::Hash,
{
self.0.as_object_mut().and_then(|o| o.remove(key))
}
pub(crate) fn has_type(&self, ty: &str) -> bool { pub(crate) fn has_type(&self, ty: &str) -> bool {
match self.0.get("type") { match self.0.get("type") {
Some(Value::Array(values)) => values.iter().any(|v| v.as_str() == Some(ty)), Some(Value::Array(values)) => values.iter().any(|v| v.as_str() == Some(ty)),

View file

@ -21,9 +21,7 @@ pub struct MyTransform;
impl Transform for MyTransform { impl Transform for MyTransform {
fn transform(&mut self, schema: &mut Schema) { fn transform(&mut self, schema: &mut Schema) {
// First, make our change to this schema // First, make our change to this schema
if let Some(obj) = schema.as_object_mut() { schema.insert("my_property".to_string(), "hello world".into());
obj.insert("my_property".to_string(), serde_json::json!("hello world"));
}
// Then apply the transform to any subschemas // Then apply the transform to any subschemas
transform_subschemas(self, schema); transform_subschemas(self, schema);
@ -55,9 +53,7 @@ The same example with a `fn` transform:
use schemars::transform::transform_subschemas; use schemars::transform::transform_subschemas;
fn add_property(schema: &mut Schema) { fn add_property(schema: &mut Schema) {
if let Some(obj) = schema.as_object_mut() { schema.insert("my_property".to_string(), "hello world".into());
obj.insert("my_property".to_string(), serde_json::json!("hello world"));
}
transform_subschemas(&mut add_property, schema) transform_subschemas(&mut add_property, schema)
} }
@ -87,9 +83,7 @@ And the same example using a closure wrapped in a `RecursiveTransform`:
use schemars::transform::{Transform, RecursiveTransform}; use schemars::transform::{Transform, RecursiveTransform};
let mut transform = RecursiveTransform(|schema: &mut Schema| { let mut transform = RecursiveTransform(|schema: &mut Schema| {
if let Some(obj) = schema.as_object_mut() { schema.insert("my_property".to_string(), "hello world".into());
obj.insert("my_property".to_string(), serde_json::json!("hello world"));
}
}); });
let mut schema = json_schema!({ let mut schema = json_schema!({
@ -236,9 +230,7 @@ pub(crate) fn transform_immediate_subschemas<T: Transform + ?Sized>(
/// use schemars::transform::{Transform, RecursiveTransform}; /// use schemars::transform::{Transform, RecursiveTransform};
/// ///
/// let mut transform = RecursiveTransform(|schema: &mut Schema| { /// let mut transform = RecursiveTransform(|schema: &mut Schema| {
/// if let Some(obj) = schema.as_object_mut() { /// schema.insert("my_property".to_string(), "hello world".into());
/// obj.insert("my_property".to_string(), serde_json::json!("hello world"));
/// }
/// }); /// });
/// ///
/// let mut schema = json_schema!({ /// let mut schema = json_schema!({
@ -289,9 +281,7 @@ impl Transform for ReplaceBoolSchemas {
if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") { if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") {
transform_subschemas(self, schema); transform_subschemas(self, schema);
if let Some(obj) = schema.as_object_mut() { schema.insert(ap_key, ap_value);
obj.insert(ap_key, ap_value);
}
return; return;
} }
@ -339,11 +329,9 @@ impl Transform for SetSingleExample {
fn transform(&mut self, schema: &mut Schema) { fn transform(&mut self, schema: &mut Schema) {
transform_subschemas(self, schema); transform_subschemas(self, schema);
if let Some(obj) = schema.as_object_mut() { if let Some(Value::Array(examples)) = schema.remove("examples") {
if let Some(Value::Array(examples)) = obj.remove("examples") {
if let Some(first_example) = examples.into_iter().next() { if let Some(first_example) = examples.into_iter().next() {
obj.insert("example".into(), first_example); schema.insert("example".into(), first_example);
}
} }
} }
} }
@ -360,10 +348,8 @@ impl Transform for ReplaceConstValue {
fn transform(&mut self, schema: &mut Schema) { fn transform(&mut self, schema: &mut Schema) {
transform_subschemas(self, schema); transform_subschemas(self, schema);
if let Some(obj) = schema.as_object_mut() { if let Some(value) = schema.remove("const") {
if let Some(value) = obj.remove("const") { schema.insert("enum".into(), Value::Array(vec![value]));
obj.insert("enum".into(), Value::Array(vec![value]));
}
} }
} }
} }
@ -381,13 +367,11 @@ impl Transform for ReplacePrefixItems {
fn transform(&mut self, schema: &mut Schema) { fn transform(&mut self, schema: &mut Schema) {
transform_subschemas(self, schema); transform_subschemas(self, schema);
if let Some(obj) = schema.as_object_mut() { if let Some(prefix_items) = schema.remove("prefixItems") {
if let Some(prefix_items) = obj.remove("prefixItems") { let previous_items = schema.insert("items".to_owned(), prefix_items);
let previous_items = obj.insert("items".to_owned(), prefix_items);
if let Some(previous_items) = previous_items { if let Some(previous_items) = previous_items {
obj.insert("additionalItems".to_owned(), previous_items); schema.insert("additionalItems".to_owned(), previous_items);
}
} }
} }
} }
@ -400,14 +384,11 @@ impl Transform for ReplaceUnevaluatedProperties {
fn transform(&mut self, schema: &mut Schema) { fn transform(&mut self, schema: &mut Schema) {
transform_subschemas(self, schema); transform_subschemas(self, schema);
let Some(obj) = schema.as_object_mut() else { let Some(up) = schema.remove("unevaluatedProperties") else {
return;
};
let Some(up) = obj.remove("unevaluatedProperties") else {
return; return;
}; };
obj.insert("additionalProperties".to_owned(), up); schema.insert("additionalProperties".to_owned(), up);
let mut gather_property_names = GatherPropertyNames::default(); let mut gather_property_names = GatherPropertyNames::default();
gather_property_names.transform(schema); gather_property_names.transform(schema);

View file

@ -1,24 +1,20 @@
mod util; mod util;
use schemars::{transform::RecursiveTransform, JsonSchema, Schema}; use schemars::{transform::RecursiveTransform, JsonSchema, Schema};
use serde_json::Value; use serde_json::{Map, Value};
use util::*; use util::*;
fn capitalize_type(schema: &mut Schema) { fn capitalize_type(schema: &mut Schema) {
if let Some(obj) = schema.as_object_mut() { if let Some(Value::String(ty)) = schema.get("type") {
if let Some(Value::String(ty)) = obj.get("type") { schema.insert("upperType".to_owned(), ty.to_uppercase().into());
obj.insert("upperType".to_owned(), ty.to_uppercase().into());
}
} }
} }
fn insert_property_count(schema: &mut Schema) { fn insert_property_count(schema: &mut Schema) {
if let Some(obj) = schema.as_object_mut() { let count = schema
let count = obj
.get("properties") .get("properties")
.and_then(|p| p.as_object()) .and_then(Value::as_object)
.map_or(0, |p| p.len()); .map_or(0, Map::len);
obj.insert("propertyCount".to_owned(), count.into()); schema.insert("propertyCount".to_owned(), count.into());
}
} }
#[allow(dead_code)] #[allow(dead_code)]