From d951fdb6799311206c0817b7163202e80f98c590 Mon Sep 17 00:00:00 2001 From: Yahel Nachum Date: Mon, 16 Mar 2026 14:30:04 -0400 Subject: [PATCH] feat: Validate with JSON pointer --- crates/jsonschema/src/compiler.rs | 77 ++++- crates/jsonschema/src/options.rs | 11 + crates/jsonschema/src/validator.rs | 440 ++++++++++++++++++++++++++ crates/jsonschema/tests/petstore.json | 379 ++++++++++++++++++++++ 4 files changed, 904 insertions(+), 3 deletions(-) create mode 100644 crates/jsonschema/tests/petstore.json diff --git a/crates/jsonschema/src/compiler.rs b/crates/jsonschema/src/compiler.rs index 95a42ee0..9a9ee9e4 100644 --- a/crates/jsonschema/src/compiler.rs +++ b/crates/jsonschema/src/compiler.rs @@ -12,7 +12,7 @@ use crate::{ }, node::{PendingSchemaNode, SchemaNode}, options::{PatternEngineOptions, ValidationOptions}, - paths::{Location, LocationSegment}, + paths::{write_escaped_str, Location, LocationSegment}, types::{JsonType, JsonTypeSet}, validator::Validate, ValidationError, Validator, @@ -729,7 +729,22 @@ pub(crate) fn build_validator( // Finally, compile the validator let root = compile(&ctx, resource_ref).map_err(ValidationError::to_owned)?; let draft = config.draft(); - Ok(Validator { root, draft }) + + // Collect all subschemas with their JSON pointers + if config.collect_pointer_subschemas { + let subschemas = Some(collect_subschemas(&ctx, schema)); + return Ok(Validator { + root, + draft, + subschemas, + }); + } + + Ok(Validator { + root, + draft, + subschemas: None, + }) } #[cfg(feature = "resolve-async")] @@ -801,7 +816,22 @@ pub(crate) async fn build_validator_async( let root = compile(&ctx, resource_ref).map_err(ValidationError::to_owned)?; let draft = config.draft(); - Ok(Validator { root, draft }) + + // Collect all subschemas with their JSON pointers + if config.collect_pointer_subschemas { + let subschemas = Some(collect_subschemas(&ctx, schema)); + return Ok(Validator { + root, + draft, + subschemas, + }); + } + + Ok(Validator { + root, + draft, + subschemas: None, + }) } fn annotations_to_value(annotations: AHashMap) -> Arc { @@ -824,6 +854,47 @@ fn collect_resource_pairs( ) } +/// Iteratively collect all subschemas from a schema document with their JSON pointers +fn collect_subschemas(ctx: &Context, schema: &Value) -> AHashMap { + let mut subschemas = AHashMap::new(); + + // Stack to hold (schema, pointer) pairs to process + let mut stack = vec![(schema, "#".to_string())]; + + while let Some((current_schema, pointer)) = stack.pop() { + // Compile and store the current schema if it's not the root (empty pointer) + let resource_ref = ctx.as_resource_ref(current_schema); + let ctx_at_location = ctx.new_at_location(pointer.as_str()); + if let Ok(node) = compile(&ctx_at_location, resource_ref) { + subschemas.insert(pointer.clone(), node); + } else { + // Skip schemas that fail to compile (e.g., invalid references) + // This allows the validator to still work with valid subschemas + } + + // Process subschemas based on schema structure + match current_schema { + Value::Object(obj) => { + stack.extend(obj.iter().map(|(key, value)| { + let mut buffer = String::new(); + write_escaped_str(&mut buffer, key); + (value, format!("{pointer}/{buffer}")) + })); + } + Value::Array(arr) => { + stack.extend( + arr.iter() + .enumerate() + .map(|(idx, item)| (item, format!("{pointer}/{idx}"))), + ); + } + _ => {} + } + } + + subschemas +} + fn validate_schema(draft: Draft, schema: &Value) -> Result<(), ValidationError<'static>> { // Boolean schemas are always valid per the spec, skip validation if schema.is_boolean() { diff --git a/crates/jsonschema/src/options.rs b/crates/jsonschema/src/options.rs index 4975df79..74fd146d 100644 --- a/crates/jsonschema/src/options.rs +++ b/crates/jsonschema/src/options.rs @@ -32,6 +32,7 @@ pub struct ValidationOptions> { formats: AHashMap>, validate_formats: Option, pub(crate) validate_schema: bool, + pub(crate) collect_pointer_subschemas: bool, ignore_unknown_formats: bool, keywords: AHashMap>, pattern_options: PatternEngineOptions, @@ -51,6 +52,7 @@ impl Default for ValidationOptions> { formats: AHashMap::default(), validate_formats: None, validate_schema: true, + collect_pointer_subschemas: false, ignore_unknown_formats: true, keywords: AHashMap::default(), pattern_options: PatternEngineOptions::default(), @@ -73,6 +75,7 @@ impl Default for ValidationOptions> { formats: AHashMap::default(), validate_formats: None, validate_schema: true, + collect_pointer_subschemas: false, ignore_unknown_formats: true, keywords: AHashMap::default(), pattern_options: PatternEngineOptions::default(), @@ -413,6 +416,12 @@ impl ValidationOptions { self.validate_schema = false; self } + /// Collect JSON pointer subschemas during compilation. + #[must_use] + pub fn collect_pointer_subschemas(mut self) -> Self { + self.collect_pointer_subschemas = true; + self + } /// Set whether to validate formats. /// /// Default behavior depends on the draft version. This method overrides @@ -748,6 +757,7 @@ impl ValidationOptions> { formats: self.formats, validate_formats: self.validate_formats, validate_schema: self.validate_schema, + collect_pointer_subschemas: self.collect_pointer_subschemas, ignore_unknown_formats: self.ignore_unknown_formats, keywords: self.keywords, pattern_options: self.pattern_options, @@ -783,6 +793,7 @@ impl ValidationOptions> { formats: self.formats, validate_formats: self.validate_formats, validate_schema: self.validate_schema, + collect_pointer_subschemas: self.collect_pointer_subschemas, ignore_unknown_formats: self.ignore_unknown_formats, keywords: self.keywords, pattern_options: self.pattern_options, diff --git a/crates/jsonschema/src/validator.rs b/crates/jsonschema/src/validator.rs index 7db3a940..d1811258 100644 --- a/crates/jsonschema/src/validator.rs +++ b/crates/jsonschema/src/validator.rs @@ -272,6 +272,8 @@ impl From for EvaluationResult { pub struct Validator { pub(crate) root: SchemaNode, pub(crate) draft: Draft, + /// Map of JSON pointers to compiled schema nodes for all subschemas + pub(crate) subschemas: Option>, } impl Validator { @@ -349,6 +351,66 @@ impl Validator { self.root .validate(instance, &LazyLocation::new(), None, &mut ctx) } + + /// Validate `instance` against a specific subschema identified by a JSON pointer. + /// + /// # Arguments + /// + /// * `instance` - The JSON value to validate + /// * `pointer` - A JSON pointer (e.g., "#/properties/name", "#/$defs/Person") identifying the subschema + /// + /// # Errors + /// + /// Returns an error if: + /// - The JSON pointer doesn't exist in the schema + /// - The instance doesn't satisfy the subschema + /// + /// # Example + /// + /// ```rust + /// # use serde_json::json; + /// # fn main() -> Result<(), Box> { + /// let schema = json!({ + /// "type": "object", + /// "properties": { + /// "name": {"type": "string"}, + /// "age": {"type": "integer"} + /// }, + /// "$defs": { + /// "Person": { + /// "type": "object", + /// "required": ["name", "age"] + /// } + /// } + /// }); + /// + /// let validator = jsonschema::options().collect_pointer_subschemas().build(&schema)?; + /// + /// // Validate against the "name" property subschema + /// assert!(validator.validate_with_pointer(&json!("John"), "#/properties/name").is_ok()); + /// assert!(validator.validate_with_pointer(&json!(42), "#/properties/name").is_err()); + /// + /// // Validate against a definition + /// let person = json!({"name": "John", "age": 30}); + /// assert!(validator.validate_with_pointer(&person, "#/$defs/Person").is_ok()); + /// # Ok(()) + /// # } + /// ``` + pub fn validate_with_pointer<'i>( + &self, + instance: &'i Value, + pointer: &str, + ) -> Result<(), ValidationError<'i>> { + if let Some(subschemas) = &self.subschemas { + let node = subschemas.get(pointer).ok_or_else(|| { + ValidationError::custom(format!("JSON pointer '{pointer}' not found in schema")) + })?; + let mut ctx = ValidationContext::new(); + node.validate(instance, &LazyLocation::new(), None, &mut ctx) + } else { + Err(ValidationError::custom("Invalid function call since validator did not collect all subschemas. Please recompile validator with collect_subschema option enabled.")) + } + } /// Run validation against `instance` and return an iterator over [`ValidationError`] in the error case. #[inline] #[must_use] @@ -357,6 +419,41 @@ impl Validator { self.root .iter_errors(instance, &LazyLocation::new(), None, &mut ctx) } + + /// Run validation against `instance` using a specific subschema and return an iterator over errors. + /// + /// # Arguments + /// + /// * `instance` - The JSON value to validate + /// * `pointer` - A JSON pointer identifying the subschema + /// + /// # Returns + /// + /// An iterator over validation errors. If the pointer doesn't exist, returns an iterator with a single error. + #[must_use] + pub fn iter_errors_with_pointer<'i>( + &'i self, + instance: &'i Value, + pointer: &str, + ) -> ErrorIterator<'i> { + match &self.subschemas { + Some(subschemas) => { + if let Some(node) = subschemas.get(pointer) { + let mut ctx = ValidationContext::new(); + node.iter_errors(instance, &LazyLocation::new(), None, &mut ctx) + } else { + let err = ValidationError::custom(format!( + "JSON pointer '{pointer}' not found in schema" + )); + crate::error::error(err) + } + }, + None => { + crate::error::error(ValidationError::custom("Invalid function call since validator did not collect all subschemas. Please recompile validator with collect_subschema option enabled.")) + } + } + } + /// Run validation against `instance` but return a boolean result instead of an iterator. /// It is useful for cases, where it is important to only know the fact if the data is valid or not. /// This approach is much faster, than [`Validator::validate`]. @@ -366,6 +463,24 @@ impl Validator { let mut ctx = ValidationContext::new(); self.root.is_valid(instance, &mut ctx) } + + /// Check if `instance` is valid against a specific subschema identified by a JSON pointer. + /// + /// # Arguments + /// + /// * `instance` - The JSON value to validate + /// * `pointer` - A JSON pointer identifying the subschema + /// + /// # Returns + /// + /// `false` if the pointer doesn't exist or the instance is invalid, `true` otherwise. + #[must_use] + pub fn is_valid_with_pointer(&self, instance: &Value, pointer: &str) -> bool { + self.subschemas + .as_ref() + .and_then(|subschemas| subschemas.get(pointer)) + .is_some_and(|node| node.is_valid(instance, &mut ValidationContext::new())) + } /// Evaluate the schema and expose structured output formats. #[must_use] #[inline] @@ -772,4 +887,329 @@ mod tests { assert_eq!(error.evaluation_path().as_str(), "/$ref/required"); } } + + #[test] + fn test_validate_with_pointer_basic_properties() { + let schema = json!({ + "type": "object", + "properties": { + "name": {"type": "string", "minLength": 3}, + "age": {"type": "integer", "minimum": 0}, + } + }); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + // Test #/properties/name + assert!(validator.is_valid_with_pointer(&json!("John"), "#/properties/name")); + assert!(!validator.is_valid_with_pointer(&json!(123), "#/properties/name")); + assert!(!validator.is_valid_with_pointer(&json!("Jo"), "#/properties/name")); // Too short + + // Test #/properties/age + assert!(validator.is_valid_with_pointer(&json!(30), "#/properties/age")); + assert!(!validator.is_valid_with_pointer(&json!("30"), "#/properties/age")); + assert!(!validator.is_valid_with_pointer(&json!(-5), "#/properties/age")); + // Negative + } + + #[test] + fn test_validate_with_pointer_definitions() { + let schema = json!({ + "type": "object", + "$defs": { + "Person": { + "type": "object", + "required": ["name", "age"], + "properties": { + "name": {"type": "string"}, + "age": {"type": "integer"} + } + }, + "Address": { + "type": "object", + "properties": { + "street": {"type": "string"}, + "city": {"type": "string"} + } + } + } + }); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + // Test #/$defs/Person + let person = json!({"name": "Jane", "age": 25}); + assert!(validator.is_valid_with_pointer(&person, "#/$defs/Person")); + + let invalid_person = json!({"name": "Jane"}); // Missing required 'age' + assert!(!validator.is_valid_with_pointer(&invalid_person, "#/$defs/Person")); + + // Test #/$defs/Address + let address = json!({"street": "123 Main St", "city": "Springfield"}); + assert!(validator.is_valid_with_pointer(&address, "#/$defs/Address")); + } + + #[test] + fn test_validate_with_pointer_nested_properties() { + let schema = json!({ + "type": "object", + "properties": { + "user": { + "type": "object", + "properties": { + "profile": { + "type": "object", + "properties": { + "bio": {"type": "string"} + } + } + } + } + } + }); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + // Test nested property + assert!(validator.is_valid_with_pointer( + &json!("Hello world"), + "#/properties/user/properties/profile/properties/bio" + )); + assert!(!validator.is_valid_with_pointer( + &json!(123), + "#/properties/user/properties/profile/properties/bio" + )); + } + + #[test] + fn test_validate_with_pointer_array_items() { + let schema = json!({ + "type": "array", + "items": { + "type": "object", + "properties": { + "id": {"type": "integer"} + } + } + }); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + assert!(validator.is_valid_with_pointer(&json!({"id": 1}), "#/items")); + assert!(!validator.is_valid_with_pointer(&json!({"id": "1"}), "#/items")); + } + + #[test] + fn test_validate_with_pointer_returns_error() { + let schema = json!({ + "properties": { + "name": {"type": "string", "minLength": 3} + } + }); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + // Should return error for invalid data + let instance = json!("Jo"); + let error = validator + .validate_with_pointer(&instance, "#/properties/name") + .expect_err("Should fail"); + assert_eq!(error.to_string(), "\"Jo\" is shorter than 3 characters"); + } + + #[test] + fn test_validate_with_pointer_nonexistent() { + let schema = json!({ + "properties": { + "name": {"type": "string"} + } + }); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + // Non-existent pointer should return false + let instance = json!("test"); + assert!(!validator.is_valid_with_pointer(&instance, "#/properties/nonexistent")); + + // validate_with_pointer should return error for non-existent pointer + let error = validator + .validate_with_pointer(&instance, "#/properties/nonexistent") + .expect_err("Should fail"); + assert_eq!( + error.to_string(), + "JSON pointer '#/properties/nonexistent' not found in schema" + ); + } + + #[test] + fn test_iter_errors_with_pointer() { + let schema = json!({ + "properties": { + "name": {"type": "string"} + } + }); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + // Should get errors for invalid data + let invalid_instance = json!(123); + let errors: Vec<_> = validator + .iter_errors_with_pointer(&invalid_instance, "#/properties/name") + .collect(); + assert!(!errors.is_empty()); + assert_eq!(errors.len(), 1); + let error = &errors[0]; + assert_eq!(error.to_string(), "123 is not of type \"string\""); + + // Should get no errors for valid data + let valid_instance = json!("John"); + let errors: Vec<_> = validator + .iter_errors_with_pointer(&valid_instance, "#/properties/name") + .collect(); + assert!(errors.is_empty()); + } + + #[test] + fn test_validate_with_pointer_allof() { + let schema = json!({ + "allOf": [ + {"type": "object"}, + {"properties": {"name": {"type": "string"}}} + ] + }); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + // Test first allOf item + assert!(validator.is_valid_with_pointer(&json!({}), "#/allOf/0")); + assert!(!validator.is_valid_with_pointer(&json!("not an object"), "#/allOf/0")); + + // Test second allOf item + assert!(validator.is_valid_with_pointer(&json!({"name": "John"}), "#/allOf/1")); + assert!(!validator.is_valid_with_pointer(&json!({"name": 1}), "#/allOf/1")); + assert!(validator.is_valid_with_pointer(&json!("John"), "#/allOf/1/properties/name")); + assert!(!validator.is_valid_with_pointer(&json!(1), "#/allOf/1/properties/name")); + assert!(validator.is_valid_with_pointer(&json!([]), "#/allOf/1")); + } + + #[test] + fn test_validate_with_pointer_anyof() { + let schema = json!({ + "anyOf": [ + {"type": "string"}, + {"type": "number"} + ] + }); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + // Test first anyOf item + assert!(validator.is_valid_with_pointer(&json!("hello"), "#/anyOf/0")); + assert!(!validator.is_valid_with_pointer(&json!(123), "#/anyOf/0")); + + // Test second anyOf item + assert!(validator.is_valid_with_pointer(&json!(123), "#/anyOf/1")); + assert!(!validator.is_valid_with_pointer(&json!("hello"), "#/anyOf/1")); + } + + #[test] + fn test_validate_with_pointer_arbitrary() { + let schema = json!({"a": {"type": "string"}}); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + // Test first anyOf item + let instance = json!("hello"); + assert!(validator.is_valid_with_pointer(&instance, "#/a")); + assert!(!validator.is_valid_with_pointer(&json!(123), "#/a")); + + let error = validator + .validate_with_pointer(&instance, "#/b") + .expect_err("Should fail"); + assert_eq!(error.to_string(), "JSON pointer '#/b' not found in schema"); + } + + #[test] + fn test_validate_with_pointer_escape() { + let schema = json!({ + "~a": {"type": "string"}, + "/a": {"type": "string"}, + "~/a": {"type": "string"}, + "a": {"~b": {"~c": {"type": "string"}}} + }); + + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + // Test first anyOf item + let instance = json!("hello"); + assert!(validator.is_valid_with_pointer(&instance, "#/~0a")); + assert!(!validator.is_valid_with_pointer(&json!(123), "#/~0a")); + + assert!(validator.is_valid_with_pointer(&instance, "#/~1a")); + assert!(!validator.is_valid_with_pointer(&json!(123), "#/~1a")); + + assert!(validator.is_valid_with_pointer(&instance, "#/~0~1a")); + assert!(!validator.is_valid_with_pointer(&json!(123), "#/~0~1a")); + + assert!(validator.is_valid_with_pointer(&instance, "#/a/~0b/~0c")); + assert!(!validator.is_valid_with_pointer(&json!(123), "#/a/~0b/~0c")); + } + + #[test] + fn test_validate_with_pointer_petstore() { + let schema = load("tests/petstore.json", 0); + let validator = crate::options() + .collect_pointer_subschemas() + .build(&schema) + .expect("Good compilation"); + + let root_value_ok = json!({"name":"Bob","photoUrls":[]}); + let root_value_err = json!({"name": 1 ,"photoUrls":[]}); + + let sub_value_ok = json!("Bob"); + let sub_value_err = json!(1); + + assert!(validator.validate(&root_value_ok).is_ok()); + assert!(validator.validate(&root_value_err).is_err()); + + assert!(validator + .validate_with_pointer(&sub_value_ok, "#/components/schemas/Pet/properties/name") + .is_ok()); + assert!(validator + .validate_with_pointer(&sub_value_err, "#/components/schemas/Pet/properties/name") + .is_err()); + } } diff --git a/crates/jsonschema/tests/petstore.json b/crates/jsonschema/tests/petstore.json new file mode 100644 index 00000000..9262beb3 --- /dev/null +++ b/crates/jsonschema/tests/petstore.json @@ -0,0 +1,379 @@ +[{"schema":{ + "$ref": "#/components/schemas/Pet", + "openapi" : "3.1.0", + "info" : { + "title" : "Swagger Petstore - OpenAPI 3.1", + "description" : "This is a sample Pet Store Server based on the OpenAPI 3.1 specification.\nYou can find out more about\nSwagger at [https://swagger.io](https://swagger.io).", + "termsOfService" : "https://swagger.io/terms/", + "contact" : { + "email" : "apiteam@swagger.io" + }, + "license" : { + "name" : "Apache 2.0", + "url" : "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version" : "1.0.10", + "summary" : "Pet Store 3.1", + "x-namespace" : "swagger" + }, + "externalDocs" : { + "description" : "Find out more about Swagger", + "url" : "https://swagger.io" + }, + "servers" : [ { + "url" : "/api/v31" + } ], + "tags" : [ { + "name" : "pet", + "description" : "Everything about your Pets", + "externalDocs" : { + "description" : "Find out more", + "url" : "https://swagger.io" + } + } ], + "paths" : { + "/pet" : { + "put" : { + "tags" : [ "pet" ], + "summary" : "Update an existing pet.", + "description" : "Update an existing pet by Id.", + "operationId" : "updatePet", + "requestBody" : { + "description" : "Pet object that needs to be updated in the store", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "A Pet in JSON Format", + "required" : [ "id" ], + "writeOnly" : true + } + }, + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "A Pet in XML Format", + "required" : [ "id" ], + "writeOnly" : true + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "Successful operation", + "content" : { + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "A Pet in XML Format", + "readOnly" : true + } + }, + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "A Pet in JSON Format", + "readOnly" : true + } + } + } + }, + "400" : { + "description" : "Invalid ID supplied" + }, + "404" : { + "description" : "Pet not found" + }, + "405" : { + "description" : "Validation exception" + }, + "default" : { + "description" : "Unexpected error" + } + }, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + } ] + }, + "post" : { + "tags" : [ "pet" ], + "summary" : "Add a new pet to the store.", + "description" : "Add a new pet to the store.", + "operationId" : "addPet", + "requestBody" : { + "description" : "Create a new pet in the store", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "A Pet in JSON Format", + "required" : [ "id" ], + "writeOnly" : true + } + }, + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "A Pet in XML Format", + "required" : [ "id" ], + "writeOnly" : true + } + } + }, + "required" : true + }, + "responses" : { + "200" : { + "description" : "Successful operation", + "content" : { + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "A Pet in XML Format", + "readOnly" : true + } + }, + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "A Pet in JSON format", + "readOnly" : true + } + } + } + }, + "405" : { + "description" : "Invalid input" + }, + "default" : { + "description" : "Unexpected error" + } + }, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + } ] + } + }, + "/pet/{petId}" : { + "get" : { + "tags" : [ "pet" ], + "summary" : "Find pet by it's identifier.", + "description" : "Returns a pet when 0 < ID <= 10. ID > 10 or non-integers will simulate API error conditions.", + "operationId" : "getPetById", + "parameters" : [ { + "name" : "petId", + "in" : "path", + "description" : "ID of pet that needs to be fetched", + "required" : true, + "schema" : { + "type" : "integer", + "format" : "int64", + "description" : "param ID of pet that needs to be fetched", + "exclusiveMaximum" : 10, + "exclusiveMinimum" : 1 + } + } ], + "responses" : { + "200" : { + "description" : "The pet", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "A Pet in JSON format" + } + }, + "application/xml" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "A Pet in XML format" + } + } + } + }, + "400" : { + "description" : "Invalid ID supplied" + }, + "404" : { + "description" : "Pet not found" + }, + "default" : { + "description" : "Unexpected error" + } + }, + "security" : [ { + "petstore_auth" : [ "write:pets", "read:pets" ] + }, { + "api_key" : [ ] + } ] + } + } + }, + "components" : { + "schemas" : { + "Category" : { + "$id" : "/api/v31/components/schemas/category", + "description" : "Category", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "name" : { + "type" : "string" + } + }, + "xml" : { + "name" : "Category" + } + }, + "Pet" : { + "$schema" : "https://json-schema.org/draft/2020-12/schema", + "description" : "Pet", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "category" : { + "$ref" : "#/components/schemas/Category", + "description" : "Pet Category" + }, + "name" : { + "type" : "string", + "examples" : [ "doggie" ] + }, + "photoUrls" : { + "type" : "array", + "items" : { + "type" : "string", + "xml" : { + "name" : "photoUrl" + } + }, + "xml" : { + "wrapped" : true + } + }, + "tags" : { + "type" : "array", + "items" : { + "$ref" : "#/components/schemas/Tag" + }, + "xml" : { + "wrapped" : true + } + }, + "status" : { + "type" : "string", + "description" : "pet status in the store", + "enum" : [ "available", "pending", "sold" ] + }, + "availableInstances" : { + "type" : "integer", + "format" : "int32", + "examples" : [ "7" ], + "exclusiveMaximum" : 10, + "exclusiveMinimum" : 1, + "swagger-extension" : true + }, + "petDetailsId" : { + "type" : "integer", + "format" : "int64" + }, + "petDetails" : { + "type" : "string" + } + }, + "required" : [ "name", "photoUrls" ], + "xml" : { + "name" : "Pet" + } + }, + "PetDetails" : { + "$id" : "/api/v31/components/schemas/petdetails", + "$schema" : "https://json-schema.org/draft/2020-12/schema", + "$vocabulary" : "https://spec.openapis.org/oas/3.1/schema-base", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64", + "$anchor" : "pet_details_id", + "examples" : [ "10" ] + }, + "category" : { + "$ref" : "/api/v31/components/schemas/category", + "description" : "PetDetails Category" + }, + "tag" : { + "$ref" : "/api/v31/components/schemas/tag" + } + }, + "xml" : { + "name" : "PetDetails" + } + }, + "Tag" : { + "$id" : "/api/v31/components/schemas/tag", + "properties" : { + "id" : { + "type" : "integer", + "format" : "int64" + }, + "name" : { + "type" : "string" + } + }, + "xml" : { + "name" : "Tag" + } + } + }, + "securitySchemes" : { + "petstore_auth" : { + "type" : "oauth2", + "flows" : { + "implicit" : { + "authorizationUrl" : "https://petstore31.swagger.io/oauth/authorize", + "scopes" : { + "write:pets" : "modify pets in your account", + "read:pets" : "read your pets" + } + } + } + }, + "mutual_tls" : { + "type" : "mutualTLS" + }, + "api_key" : { + "type" : "apiKey", + "name" : "api_key", + "in" : "header" + } + } + }, + "webhooks" : { + "newPet" : { + "post" : { + "requestBody" : { + "description" : "Information about a new pet in the system", + "content" : { + "application/json" : { + "schema" : { + "$ref" : "#/components/schemas/Pet", + "description" : "Webhook Pet" + } + } + } + }, + "responses" : { + "200" : { + "description" : "Return a 200 status to indicate that the data was received successfully" + } + } + } + } + } +}}]