Skip to content

Commit

Permalink
Add Schema methods insert, get and remove
Browse files Browse the repository at this point in the history
These are just convenience methods that delegate to the inner object. `insert` will also convert bool schemas to object schemas.
  • Loading branch information
GREsau committed Aug 30, 2024
1 parent d6c8b6b commit 0672c86
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 76 deletions.
12 changes: 3 additions & 9 deletions docs/0-migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand Down
4 changes: 1 addition & 3 deletions docs/_includes/examples/schemars_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
4 changes: 1 addition & 3 deletions schemars/examples/schemars_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
22 changes: 9 additions & 13 deletions schemars/src/_private/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
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
Expand All @@ -33,16 +31,14 @@ pub fn json_schema_for_flatten<T: ?Sized + JsonSchema>(
}

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:
Expand Down Expand Up @@ -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);
}
Expand Down
72 changes: 71 additions & 1 deletion schemars/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 {
match self.0.get("type") {
Some(Value::Array(values)) => values.iter().any(|v| v.as_str() == Some(ty)),
Expand Down
51 changes: 16 additions & 35 deletions schemars/src/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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!({
Expand Down Expand Up @@ -236,9 +230,7 @@ pub(crate) fn transform_immediate_subschemas<T: Transform + ?Sized>(
/// 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!({
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
}
}
Expand All @@ -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]));
}
}
}
Expand All @@ -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);
}
}
}
Expand All @@ -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);
Expand Down
20 changes: 8 additions & 12 deletions schemars/tests/transform.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down

0 comments on commit 0672c86

Please sign in to comment.