Skip to content

Commit 657f0fb

Browse files
(GH-538) Add get_keyword_as_subschema extension methods
Prior to this change, the `dsc-lib-jsonschema` crate defined extension methods for schemas to retrieve keywords as various underlying data types when the caller knows the data type they need. However, the extension methods didn't include a way to retrieve values as _schemas_, so any use of those values requires the caller to reimplement the extension methods logic or to manually convert the value to a schema. This change adds two helper methods to retrieve a keyword as a `schemars::Schema` instance (borrowed and mutably borrowed) to make working with subschemas more ergonomic.
1 parent ff9a762 commit 657f0fb

File tree

2 files changed

+112
-1
lines changed

2 files changed

+112
-1
lines changed

lib/dsc-lib-jsonschema/src/schema_utility_extensions.rs

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
//!
1111
//! The rest of the utility methods work with specific keywords, like `$id` and `$defs`.
1212
13-
use core::{clone::Clone, iter::Iterator, option::Option::None};
13+
use core::{clone::Clone, convert::TryInto, iter::Iterator, option::Option::None};
1414
use std::string::String;
1515

1616
use schemars::Schema;
@@ -537,6 +537,103 @@ pub trait SchemaUtilityExtensions {
537537
/// )
538538
/// ```
539539
fn get_keyword_as_string(&self, key: &str) -> Option<String>;
540+
/// Checks a JSON Schema for a given keyword and returns the value of that keyword, if it
541+
/// exists, as a [`Schema`].
542+
///
543+
/// If the keyword doesn't exist or isn't a subchema, this function returns [`None`].
544+
///
545+
/// # Examples
546+
///
547+
/// When the given keyword exists and is a subschema, the function returns the subschema.
548+
///
549+
/// ```rust
550+
/// use schemars::json_schema;
551+
/// use dsc_lib_jsonschema::schema_utility_extensions::SchemaUtilityExtensions;
552+
///
553+
/// let ref schema = json_schema!({
554+
/// "type": "array",
555+
/// "items": {
556+
/// "type": "string"
557+
/// }
558+
/// });
559+
/// assert_eq!(
560+
/// schema.get_keyword_as_subschema("items"),
561+
/// Some(&json_schema!({"type": "string"}))
562+
/// );
563+
/// ```
564+
///
565+
/// When the given keyword doesn't exist or has the wrong data type, the function returns
566+
/// [`None`].
567+
///
568+
/// ```rust
569+
/// use schemars::json_schema;
570+
/// use dsc_lib_jsonschema::schema_utility_extensions::SchemaUtilityExtensions;
571+
///
572+
/// let ref schema = json_schema!({
573+
/// "items": "invalid"
574+
/// });
575+
///
576+
/// assert_eq!(
577+
/// schema.get_keyword_as_subschema("not_exist"),
578+
/// None
579+
/// );
580+
///
581+
/// assert_eq!(
582+
/// schema.get_keyword_as_subschema("items"),
583+
/// None
584+
/// )
585+
/// ```
586+
fn get_keyword_as_subschema(&self, key: &str) -> Option<&Schema>;
587+
/// Checks a JSON Schema for a given keyword and mutably borrows the value of that keyword,
588+
/// if it exists, as a [`Schema`].
589+
///
590+
/// If the keyword doesn't exist or isn't a subschema, this function returns [`None`].
591+
///
592+
/// # Examples
593+
///
594+
/// When the given keyword exists and is a subschema, the function returns the subschema.
595+
///
596+
/// ```rust
597+
/// use schemars::json_schema;
598+
/// use serde_json::json;
599+
/// use dsc_lib_jsonschema::schema_utility_extensions::SchemaUtilityExtensions;
600+
///
601+
/// let ref mut subschema = json_schema!({
602+
/// "type": "string"
603+
/// });
604+
/// let ref mut schema = json_schema!({
605+
/// "type": "array",
606+
/// "items": subschema
607+
/// });
608+
/// assert_eq!(
609+
/// schema.get_keyword_as_subschema_mut("items"),
610+
/// Some(subschema)
611+
/// );
612+
/// ```
613+
///
614+
/// When the given keyword doesn't exist or has the wrong data type, the function returns
615+
/// [`None`].
616+
///
617+
/// ```rust
618+
/// use schemars::json_schema;
619+
/// use serde_json::json;
620+
/// use dsc_lib_jsonschema::schema_utility_extensions::SchemaUtilityExtensions;
621+
///
622+
/// let ref mut schema = json_schema!({
623+
/// "items": "invalid"
624+
/// });
625+
///
626+
/// assert_eq!(
627+
/// schema.get_keyword_as_object_mut("not_exist"),
628+
/// None
629+
/// );
630+
///
631+
/// assert_eq!(
632+
/// schema.get_keyword_as_object_mut("items"),
633+
/// None
634+
/// )
635+
/// ```
636+
fn get_keyword_as_subschema_mut(&mut self, key: &str) -> Option<&mut Schema>;
540637
/// Checks a JSON schema for a given keyword and returns the value of that keyword, if it
541638
/// exists, as a [`u64`].
542639
///
@@ -1222,6 +1319,14 @@ impl SchemaUtilityExtensions for Schema {
12221319
.and_then(Value::as_str)
12231320
.map(std::string::ToString::to_string)
12241321
}
1322+
fn get_keyword_as_subschema(&self, key: &str) -> Option<&Schema> {
1323+
self.get(key)
1324+
.and_then(|v| <&Value as TryInto<&Schema>>::try_into(v).ok())
1325+
}
1326+
fn get_keyword_as_subschema_mut(&mut self, key: &str) -> Option<&mut Schema> {
1327+
self.get_mut(key)
1328+
.and_then(|v| <&mut Value as TryInto<&mut Schema>>::try_into(v).ok())
1329+
}
12251330
fn get_keyword_as_u64(&self, key: &str) -> Option<u64> {
12261331
self.get(key)
12271332
.and_then(Value::as_u64)

lib/dsc-lib-jsonschema/src/tests/schema_utility_extensions/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ static OBJECT_VALUE: LazyLock<Map<String, Value>> = LazyLock::new(|| json!({
2626
}).as_object().unwrap().clone());
2727
static NULL_VALUE: () = ();
2828
static STRING_VALUE: &str = "value";
29+
static SUBSCHEMA_VALUE: LazyLock<Schema> = LazyLock::new(|| json_schema!({
30+
"$id": "https://schema.contoso.com/test/get_keyword_as/subschema.json"
31+
}));
2932
static TEST_SCHEMA: LazyLock<Schema> = LazyLock::new(|| json_schema!({
3033
"$id": "https://schema.contoso.com/test/get_keyword_as.json",
3134
"array": *ARRAY_VALUE,
@@ -35,6 +38,7 @@ static TEST_SCHEMA: LazyLock<Schema> = LazyLock::new(|| json_schema!({
3538
"object": *OBJECT_VALUE,
3639
"null": null,
3740
"string": *STRING_VALUE,
41+
"subschema": *SUBSCHEMA_VALUE,
3842
}));
3943

4044
/// Defines test cases for a given `get_keyword_as` function (non-mutable).
@@ -135,12 +139,14 @@ test_cases_for_get_keyword_as!(
135139
get_keyword_as_number: "array", "integer", Some(&(INTEGER_VALUE.into())),
136140
get_keyword_as_str: "array", "string", Some(STRING_VALUE),
137141
get_keyword_as_string: "array", "string", Some(STRING_VALUE.to_string()),
142+
get_keyword_as_subschema: "array", "subschema", Some(&*SUBSCHEMA_VALUE),
138143
);
139144

140145

141146
test_cases_for_get_keyword_as_mut!(
142147
get_keyword_as_array_mut: "boolean", "array", Some(&mut (*ARRAY_VALUE).clone()),
143148
get_keyword_as_object_mut: "array", "object", Some(&mut (*OBJECT_VALUE).clone()),
149+
get_keyword_as_subschema_mut: "array", "subschema", Some(&mut (*SUBSCHEMA_VALUE).clone()),
144150
);
145151

146152
#[cfg(test)] mod get_id {

0 commit comments

Comments
 (0)