From 0672c862c8d648366ee79d3b1991914168bfc4e8 Mon Sep 17 00:00:00 2001 From: Graham Esau Date: Fri, 30 Aug 2024 11:40:29 +0100 Subject: [PATCH] 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. --- docs/0-migrating.md | 12 +--- docs/_includes/examples/schemars_attrs.rs | 4 +- schemars/examples/schemars_attrs.rs | 4 +- schemars/src/_private/mod.rs | 22 +++---- schemars/src/schema.rs | 72 ++++++++++++++++++++++- schemars/src/transform.rs | 51 +++++----------- schemars/tests/transform.rs | 20 +++---- 7 files changed, 109 insertions(+), 76 deletions(-) diff --git a/docs/0-migrating.md b/docs/0-migrating.md index 499f32ca..80fccb38 100644 --- a/docs/0-migrating.md +++ b/docs/0-migrating.md @@ -129,9 +129,7 @@ pub struct MyTransform; impl Transform for MyTransform { fn transform(&mut self, schema: &mut Schema) { // First, make our change to this schema - if let Some(obj) = schema.as_object_mut() { - obj.insert("my_property".to_string(), serde_json::json!("hello world")); - } + schema.insert("my_property".to_string(), serde_json::json!("hello world")); // Then apply the transform to any subschemas transform_subschemas(self, schema); @@ -147,9 +145,7 @@ Also, since `Transform` is now implemented for functions that take a single `&mu ```rust fn my_transform(schema: &mut Schema) { // First, make our change to this schema - if let Some(obj) = schema.as_object_mut() { - obj.insert("my_property".to_string(), serde_json::json!("hello world")); - } + schema.insert("my_property".to_string(), serde_json::json!("hello world")); // Then apply the transform to any subschemas transform_subschemas(&mut my_transform, schema); @@ -165,9 +161,7 @@ Finally, you can also use the `RecursiveTransform` newtype to convert a non-recu ```rust fn my_transform2(schema: &mut Schema) { - if let Some(obj) = schema.as_object_mut() { - obj.insert("my_property".to_string(), serde_json::json!("hello world")); - } + schema.insert("my_property".to_string(), serde_json::json!("hello world")); } let mut schema = schemars::schema_for!(str); diff --git a/docs/_includes/examples/schemars_attrs.rs b/docs/_includes/examples/schemars_attrs.rs index 30418b46..21c3bdcf 100644 --- a/docs/_includes/examples/schemars_attrs.rs +++ b/docs/_includes/examples/schemars_attrs.rs @@ -25,9 +25,7 @@ pub enum MyEnum { } fn remove_format(schema: &mut Schema) { - if let Some(obj) = schema.as_object_mut() { - obj.remove("format"); - } + schema.remove("format"); } fn main() { diff --git a/schemars/examples/schemars_attrs.rs b/schemars/examples/schemars_attrs.rs index 30418b46..21c3bdcf 100644 --- a/schemars/examples/schemars_attrs.rs +++ b/schemars/examples/schemars_attrs.rs @@ -25,9 +25,7 @@ pub enum MyEnum { } fn remove_format(schema: &mut Schema) { - if let Some(obj) = schema.as_object_mut() { - obj.remove("format"); - } + schema.remove("format"); } fn main() { diff --git a/schemars/src/_private/mod.rs b/schemars/src/_private/mod.rs index 613c9dc8..8fb532d1 100644 --- a/schemars/src/_private/mod.rs +++ b/schemars/src/_private/mod.rs @@ -20,9 +20,7 @@ pub fn json_schema_for_flatten( let mut schema = T::_schemars_private_non_optional_json_schema(generator); if T::_schemars_private_is_option() && !required { - if let Some(object) = schema.as_object_mut() { - object.remove("required"); - } + schema.remove("required"); } // Always allow aditional/unevaluated properties, because the outer struct determines @@ -33,16 +31,14 @@ pub fn json_schema_for_flatten( } fn allow_unknown_properties(schema: &mut Schema) { - if let Some(obj) = schema.as_object_mut() { - if obj.get("additionalProperties").and_then(Value::as_bool) == Some(false) { - obj.remove("additionalProperties"); - } - if obj.get("unevaluatedProperties").and_then(Value::as_bool) == Some(false) { - obj.remove("unevaluatedProperties"); - } - - transform_immediate_subschemas(&mut allow_unknown_properties, schema); + if schema.get("additionalProperties").and_then(Value::as_bool) == Some(false) { + schema.remove("additionalProperties"); + } + if schema.get("unevaluatedProperties").and_then(Value::as_bool) == Some(false) { + schema.remove("unevaluatedProperties"); } + + transform_immediate_subschemas(&mut allow_unknown_properties, schema); } /// Hack to simulate specialization: @@ -211,7 +207,7 @@ pub fn apply_inner_validation(schema: &mut Schema, f: fn(&mut Schema) -> ()) { if let Some(inner_schema) = schema .as_object_mut() .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); } diff --git a/schemars/src/schema.rs b/schemars/src/schema.rs index 96be6624..9a5262e4 100644 --- a/schemars/src/schema.rs +++ b/schemars/src/schema.rs @@ -138,10 +138,80 @@ impl Schema { self.0 = Value::Object(map); } - self.as_object_mut() + self.0 + .as_object_mut() .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 { + 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(&self, key: &Q) -> Option<&Value> + where + String: core::borrow::Borrow, + 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(&mut self, key: &Q) -> Option + where + String: core::borrow::Borrow, + 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 { match self.0.get("type") { Some(Value::Array(values)) => values.iter().any(|v| v.as_str() == Some(ty)), diff --git a/schemars/src/transform.rs b/schemars/src/transform.rs index 902655ec..53dd4b52 100644 --- a/schemars/src/transform.rs +++ b/schemars/src/transform.rs @@ -21,9 +21,7 @@ pub struct MyTransform; impl Transform for MyTransform { fn transform(&mut self, schema: &mut Schema) { // First, make our change to this schema - if let Some(obj) = schema.as_object_mut() { - obj.insert("my_property".to_string(), serde_json::json!("hello world")); - } + schema.insert("my_property".to_string(), "hello world".into()); // Then apply the transform to any subschemas transform_subschemas(self, schema); @@ -55,9 +53,7 @@ The same example with a `fn` transform: use schemars::transform::transform_subschemas; fn add_property(schema: &mut Schema) { - if let Some(obj) = schema.as_object_mut() { - obj.insert("my_property".to_string(), serde_json::json!("hello world")); - } + schema.insert("my_property".to_string(), "hello world".into()); 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}; let mut transform = RecursiveTransform(|schema: &mut Schema| { - if let Some(obj) = schema.as_object_mut() { - obj.insert("my_property".to_string(), serde_json::json!("hello world")); - } + schema.insert("my_property".to_string(), "hello world".into()); }); let mut schema = json_schema!({ @@ -236,9 +230,7 @@ pub(crate) fn transform_immediate_subschemas( /// use schemars::transform::{Transform, RecursiveTransform}; /// /// let mut transform = RecursiveTransform(|schema: &mut Schema| { -/// if let Some(obj) = schema.as_object_mut() { -/// obj.insert("my_property".to_string(), serde_json::json!("hello world")); -/// } +/// schema.insert("my_property".to_string(), "hello world".into()); /// }); /// /// let mut schema = json_schema!({ @@ -289,9 +281,7 @@ impl Transform for ReplaceBoolSchemas { if let Some((ap_key, ap_value)) = obj.remove_entry("additionalProperties") { transform_subschemas(self, schema); - if let Some(obj) = schema.as_object_mut() { - obj.insert(ap_key, ap_value); - } + schema.insert(ap_key, ap_value); return; } @@ -339,11 +329,9 @@ impl Transform for SetSingleExample { fn transform(&mut self, schema: &mut Schema) { transform_subschemas(self, schema); - if let Some(obj) = schema.as_object_mut() { - if let Some(Value::Array(examples)) = obj.remove("examples") { - if let Some(first_example) = examples.into_iter().next() { - obj.insert("example".into(), first_example); - } + if let Some(Value::Array(examples)) = schema.remove("examples") { + if let Some(first_example) = examples.into_iter().next() { + schema.insert("example".into(), first_example); } } } @@ -360,10 +348,8 @@ impl Transform for ReplaceConstValue { fn transform(&mut self, schema: &mut Schema) { transform_subschemas(self, schema); - if let Some(obj) = schema.as_object_mut() { - if let Some(value) = obj.remove("const") { - obj.insert("enum".into(), Value::Array(vec![value])); - } + if let Some(value) = schema.remove("const") { + schema.insert("enum".into(), Value::Array(vec![value])); } } } @@ -381,13 +367,11 @@ impl Transform for ReplacePrefixItems { fn transform(&mut self, schema: &mut Schema) { transform_subschemas(self, schema); - if let Some(obj) = schema.as_object_mut() { - if let Some(prefix_items) = obj.remove("prefixItems") { - let previous_items = obj.insert("items".to_owned(), prefix_items); + if let Some(prefix_items) = schema.remove("prefixItems") { + let previous_items = schema.insert("items".to_owned(), prefix_items); - if let Some(previous_items) = previous_items { - obj.insert("additionalItems".to_owned(), previous_items); - } + if let Some(previous_items) = previous_items { + schema.insert("additionalItems".to_owned(), previous_items); } } } @@ -400,14 +384,11 @@ impl Transform for ReplaceUnevaluatedProperties { fn transform(&mut self, schema: &mut Schema) { transform_subschemas(self, schema); - let Some(obj) = schema.as_object_mut() else { - return; - }; - let Some(up) = obj.remove("unevaluatedProperties") else { + let Some(up) = schema.remove("unevaluatedProperties") else { return; }; - obj.insert("additionalProperties".to_owned(), up); + schema.insert("additionalProperties".to_owned(), up); let mut gather_property_names = GatherPropertyNames::default(); gather_property_names.transform(schema); diff --git a/schemars/tests/transform.rs b/schemars/tests/transform.rs index 13ac30b1..23d5b891 100644 --- a/schemars/tests/transform.rs +++ b/schemars/tests/transform.rs @@ -1,24 +1,20 @@ mod util; use schemars::{transform::RecursiveTransform, JsonSchema, Schema}; -use serde_json::Value; +use serde_json::{Map, Value}; use util::*; fn capitalize_type(schema: &mut Schema) { - if let Some(obj) = schema.as_object_mut() { - if let Some(Value::String(ty)) = obj.get("type") { - obj.insert("upperType".to_owned(), ty.to_uppercase().into()); - } + if let Some(Value::String(ty)) = schema.get("type") { + schema.insert("upperType".to_owned(), ty.to_uppercase().into()); } } fn insert_property_count(schema: &mut Schema) { - if let Some(obj) = schema.as_object_mut() { - let count = obj - .get("properties") - .and_then(|p| p.as_object()) - .map_or(0, |p| p.len()); - obj.insert("propertyCount".to_owned(), count.into()); - } + let count = schema + .get("properties") + .and_then(Value::as_object) + .map_or(0, Map::len); + schema.insert("propertyCount".to_owned(), count.into()); } #[allow(dead_code)]