Skip to content

Commit

Permalink
feat: Adding support to parse discriminator field in ObjectSchema (#138)
Browse files Browse the repository at this point in the history
Co-authored-by: Rob Ede <[email protected]>
  • Loading branch information
vgerber and robjtede authored Dec 29, 2024
1 parent 09127ce commit 7d9a0d5
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 2 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"refpath",
"reqwest",
"rustfmt",
"rustup",
"semver",
"serde",
"spdx",
Expand Down
2 changes: 2 additions & 0 deletions crates/oas3/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
- Add `spec::ObjectSchema::deprecated` field.
- Add `spec::ObjectSchema::examples` field.
- Add `spec::Contact::validate_email()` method.
- Add `spec::Discriminator` type.
- Add `spec::ObjectSchema::discriminator` field.
- Expose the `spec::ClientCredentialsFlow::token_url` field.
- The type of the `spec::ObjectSchema::enum` field is now `Vec<serde_json::Value>`.
- The type of the `spec::ObjectSchema::const` field is now `Option<serde_json::Value>`.
Expand Down
60 changes: 60 additions & 0 deletions crates/oas3/src/spec/discriminator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Schema specification for [OpenAPI 3.1](https://github.com/OAI/OpenAPI-Specification/blob/HEAD/versions/3.1.0.md)
use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

/// A discriminator object can be used to aid in serialization, deserialization, and validation when
/// payloads may be one of a number of different schemas.
///
/// The discriminator is a specific object in a schema which is used to inform the consumer of the
/// document of an alternative schema based on the value associated with it.
///
/// See <https://github.com/OAI/OpenAPI-Specification/blob/HEAD/versions/3.1.0.md#discriminator-object>.
#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Discriminator {
/// Name of the property in the payload that will hold the discriminator value.
pub property_name: String,

/// Object to hold mappings between payload values and schema names or references.
///
/// When using the discriminator, inline schemas will not be considered.
#[serde(skip_serializing_if = "Option::is_none")]
pub mapping: Option<BTreeMap<String, String>>,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn discriminator_property_name_parsed_correctly() {
let spec = "propertyName: testName";
let discriminator = serde_yml::from_str::<Discriminator>(spec).unwrap();
assert_eq!("testName", discriminator.property_name);
assert!(discriminator.mapping.is_none());
}

#[test]
fn discriminator_mapping_parsed_correctly() {
let spec = indoc::indoc! {"
propertyName: petType
mapping:
dog: '#/components/schemas/Dog'
cat: '#/components/schemas/Cat'
monster: 'https://gigantic-server.com/schemas/Monster/schema.json'
"};
let discriminator = serde_yml::from_str::<Discriminator>(spec).unwrap();

assert_eq!("petType", discriminator.property_name);
let mapping = discriminator.mapping.unwrap();

assert_eq!("#/components/schemas/Dog", mapping.get("dog").unwrap());
assert_eq!("#/components/schemas/Cat", mapping.get("cat").unwrap());
assert_eq!(
"https://gigantic-server.com/schemas/Monster/schema.json",
mapping.get("monster").unwrap()
);
}
}
2 changes: 2 additions & 0 deletions crates/oas3/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod components;
mod contact;
mod encoding;

mod discriminator;
mod error;
mod example;
mod external_doc;
Expand All @@ -38,6 +39,7 @@ mod tag;
pub use self::{
components::*,
contact::*,
discriminator::*,
encoding::*,
error::Error,
example::*,
Expand Down
31 changes: 30 additions & 1 deletion crates/oas3/src/spec/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use std::{collections::BTreeMap, fmt};
use derive_more::derive::{Display, Error};
use serde::{Deserialize, Deserializer, Serialize};

use super::{spec_extensions, FromRef, ObjectOrReference, Ref, RefError, RefType, Spec};
use super::{
discriminator::Discriminator, spec_extensions, FromRef, ObjectOrReference, Ref, RefError,
RefType, Spec,
};

/// Schema errors.
#[derive(Debug, Clone, PartialEq, Display, Error)]
Expand Down Expand Up @@ -498,6 +501,12 @@ pub struct ObjectSchema {
// #########################################################################

//
/// Discriminator for object selection based on propertyName
///
/// See <https://github.com/OAI/OpenAPI-Specification/blob/HEAD/versions/3.1.0.md#discriminator-object>
#[serde(default, skip_serializing_if = "Option::is_none")]
pub discriminator: Option<Discriminator>,

/// A free-form property to include an example of an instance for this schema.
///
/// To represent examples that cannot be naturally represented in JSON or YAML, a string value
Expand Down Expand Up @@ -628,4 +637,24 @@ mod tests {
let schema = serde_yml::from_str::<ObjectSchema>(spec).unwrap();
assert_eq!(schema.example, Some(serde_json::Value::Null));
}

#[test]
fn discriminator_example_is_parsed_correctly() {
let spec = indoc::indoc! {"
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
- $ref: '#/components/schemas/Lizard'
- $ref: 'https://gigantic-server.com/schemas/Monster/schema.json'
discriminator:
propertyName: petType
mapping:
dog: '#/components/schemas/Dog'
monster: 'https://gigantic-server.com/schemas/Monster/schema.json'
"};
let schema = serde_yml::from_str::<ObjectSchema>(spec).unwrap();

assert!(schema.discriminator.is_some());
assert_eq!(2, schema.discriminator.unwrap().mapping.unwrap().len());
}
}
2 changes: 1 addition & 1 deletion tests/samples.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ fn validate_passing_samples() {
#[test]
fn validate_failing_samples() {
// TODO: implement validation for one-of: [paths, components, webhooks]
// see https://spec.openapis.org/oas/v3.1.0#openapi-document
// see https://github.com/OAI/OpenAPI-Specification/blob/HEAD/versions/3.1.0.md#openapi-document
// oas3::from_str(include_str!("samples/fail/no_containers.yaml")).unwrap_err();

// TODO: implement validation for non-empty server enum
Expand Down

0 comments on commit 7d9a0d5

Please sign in to comment.