Skip to content

Commit 7d9a0d5

Browse files
vgerberrobjtede
andauthored
feat: Adding support to parse discriminator field in ObjectSchema (x52dev#138)
Co-authored-by: Rob Ede <[email protected]>
1 parent 09127ce commit 7d9a0d5

File tree

6 files changed

+96
-2
lines changed

6 files changed

+96
-2
lines changed

.vscode/settings.json

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"refpath",
1919
"reqwest",
2020
"rustfmt",
21+
"rustup",
2122
"semver",
2223
"serde",
2324
"spdx",

crates/oas3/CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
- Add `spec::ObjectSchema::deprecated` field.
66
- Add `spec::ObjectSchema::examples` field.
77
- Add `spec::Contact::validate_email()` method.
8+
- Add `spec::Discriminator` type.
9+
- Add `spec::ObjectSchema::discriminator` field.
810
- Expose the `spec::ClientCredentialsFlow::token_url` field.
911
- The type of the `spec::ObjectSchema::enum` field is now `Vec<serde_json::Value>`.
1012
- The type of the `spec::ObjectSchema::const` field is now `Option<serde_json::Value>`.

crates/oas3/src/spec/discriminator.rs

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//! Schema specification for [OpenAPI 3.1](https://github.com/OAI/OpenAPI-Specification/blob/HEAD/versions/3.1.0.md)
2+
3+
use std::collections::BTreeMap;
4+
5+
use serde::{Deserialize, Serialize};
6+
7+
/// A discriminator object can be used to aid in serialization, deserialization, and validation when
8+
/// payloads may be one of a number of different schemas.
9+
///
10+
/// The discriminator is a specific object in a schema which is used to inform the consumer of the
11+
/// document of an alternative schema based on the value associated with it.
12+
///
13+
/// See <https://github.com/OAI/OpenAPI-Specification/blob/HEAD/versions/3.1.0.md#discriminator-object>.
14+
#[derive(Debug, Clone, PartialEq, Default, Deserialize, Serialize)]
15+
#[serde(rename_all = "camelCase")]
16+
pub struct Discriminator {
17+
/// Name of the property in the payload that will hold the discriminator value.
18+
pub property_name: String,
19+
20+
/// Object to hold mappings between payload values and schema names or references.
21+
///
22+
/// When using the discriminator, inline schemas will not be considered.
23+
#[serde(skip_serializing_if = "Option::is_none")]
24+
pub mapping: Option<BTreeMap<String, String>>,
25+
}
26+
27+
#[cfg(test)]
28+
mod tests {
29+
use super::*;
30+
31+
#[test]
32+
fn discriminator_property_name_parsed_correctly() {
33+
let spec = "propertyName: testName";
34+
let discriminator = serde_yml::from_str::<Discriminator>(spec).unwrap();
35+
assert_eq!("testName", discriminator.property_name);
36+
assert!(discriminator.mapping.is_none());
37+
}
38+
39+
#[test]
40+
fn discriminator_mapping_parsed_correctly() {
41+
let spec = indoc::indoc! {"
42+
propertyName: petType
43+
mapping:
44+
dog: '#/components/schemas/Dog'
45+
cat: '#/components/schemas/Cat'
46+
monster: 'https://gigantic-server.com/schemas/Monster/schema.json'
47+
"};
48+
let discriminator = serde_yml::from_str::<Discriminator>(spec).unwrap();
49+
50+
assert_eq!("petType", discriminator.property_name);
51+
let mapping = discriminator.mapping.unwrap();
52+
53+
assert_eq!("#/components/schemas/Dog", mapping.get("dog").unwrap());
54+
assert_eq!("#/components/schemas/Cat", mapping.get("cat").unwrap());
55+
assert_eq!(
56+
"https://gigantic-server.com/schemas/Monster/schema.json",
57+
mapping.get("monster").unwrap()
58+
);
59+
}
60+
}

crates/oas3/src/spec/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod components;
1313
mod contact;
1414
mod encoding;
1515

16+
mod discriminator;
1617
mod error;
1718
mod example;
1819
mod external_doc;
@@ -38,6 +39,7 @@ mod tag;
3839
pub use self::{
3940
components::*,
4041
contact::*,
42+
discriminator::*,
4143
encoding::*,
4244
error::Error,
4345
example::*,

crates/oas3/src/spec/schema.rs

+30-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use std::{collections::BTreeMap, fmt};
55
use derive_more::derive::{Display, Error};
66
use serde::{Deserialize, Deserializer, Serialize};
77

8-
use super::{spec_extensions, FromRef, ObjectOrReference, Ref, RefError, RefType, Spec};
8+
use super::{
9+
discriminator::Discriminator, spec_extensions, FromRef, ObjectOrReference, Ref, RefError,
10+
RefType, Spec,
11+
};
912

1013
/// Schema errors.
1114
#[derive(Debug, Clone, PartialEq, Display, Error)]
@@ -498,6 +501,12 @@ pub struct ObjectSchema {
498501
// #########################################################################
499502

500503
//
504+
/// Discriminator for object selection based on propertyName
505+
///
506+
/// See <https://github.com/OAI/OpenAPI-Specification/blob/HEAD/versions/3.1.0.md#discriminator-object>
507+
#[serde(default, skip_serializing_if = "Option::is_none")]
508+
pub discriminator: Option<Discriminator>,
509+
501510
/// A free-form property to include an example of an instance for this schema.
502511
///
503512
/// To represent examples that cannot be naturally represented in JSON or YAML, a string value
@@ -628,4 +637,24 @@ mod tests {
628637
let schema = serde_yml::from_str::<ObjectSchema>(spec).unwrap();
629638
assert_eq!(schema.example, Some(serde_json::Value::Null));
630639
}
640+
641+
#[test]
642+
fn discriminator_example_is_parsed_correctly() {
643+
let spec = indoc::indoc! {"
644+
oneOf:
645+
- $ref: '#/components/schemas/Cat'
646+
- $ref: '#/components/schemas/Dog'
647+
- $ref: '#/components/schemas/Lizard'
648+
- $ref: 'https://gigantic-server.com/schemas/Monster/schema.json'
649+
discriminator:
650+
propertyName: petType
651+
mapping:
652+
dog: '#/components/schemas/Dog'
653+
monster: 'https://gigantic-server.com/schemas/Monster/schema.json'
654+
"};
655+
let schema = serde_yml::from_str::<ObjectSchema>(spec).unwrap();
656+
657+
assert!(schema.discriminator.is_some());
658+
assert_eq!(2, schema.discriminator.unwrap().mapping.unwrap().len());
659+
}
631660
}

tests/samples.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn validate_passing_samples() {
1515
#[test]
1616
fn validate_failing_samples() {
1717
// TODO: implement validation for one-of: [paths, components, webhooks]
18-
// see https://spec.openapis.org/oas/v3.1.0#openapi-document
18+
// see https://github.com/OAI/OpenAPI-Specification/blob/HEAD/versions/3.1.0.md#openapi-document
1919
// oas3::from_str(include_str!("samples/fail/no_containers.yaml")).unwrap_err();
2020

2121
// TODO: implement validation for non-empty server enum

0 commit comments

Comments
 (0)