roas 0.14.0

Rust OpenAPI Specification — parser, validator, and loader for OpenAPI v2.0 / v3.0.x / v3.1.x / v3.2.x
Documentation
//! Example object.

use crate::common::helpers::validate_optional_uri;
use crate::v3_1::spec::Spec;
use crate::validation::{Context, PushError, ValidateWithContext};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;

/// Example object.
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Example {
    /// Short description for the example.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary: Option<String>,

    /// Long description for the example.
    /// [CommonMark](https://spec.commonmark.org) syntax MAY be used for rich text representation.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,

    /// Embedded literal example.
    /// The `value` field and `externalValue` field are mutually exclusive.
    /// To represent examples of media types that cannot naturally represented in JSON or YAML,
    /// use a string value to contain the example, escaping where necessary.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub value: Option<serde_json::Value>,

    /// A URI reference (RFC 3986) that points to the literal example.
    /// Per the OAS 3.1 JSON Schema this is `format: uri-reference`, so
    /// relative refs and non-HTTP schemes are allowed.
    /// The `value` field and `externalValue` field are mutually exclusive.
    #[serde(rename = "externalValue")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub external_value: Option<String>,

    /// This object MAY be extended with Specification Extensions.
    /// The field name MUST begin with `x-`, for example, `x-internal-id`.
    /// The value can be null, a primitive, an array or an object.
    #[serde(flatten)]
    #[serde(with = "crate::common::extensions")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub extensions: Option<BTreeMap<String, serde_json::Value>>,
}

impl ValidateWithContext<Spec> for Example {
    fn validate_with_context(&self, ctx: &mut Context<Spec>, path: String) {
        if self.value.is_some() && self.external_value.is_some() {
            ctx.error(
                path.clone(),
                "value and externalValue are mutually exclusive",
            );
        }
        validate_optional_uri(&self.external_value, ctx, format!("{path}.externalValue"));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::validation::Context;
    use crate::validation::Options;
    use crate::validation::ValidationErrorsExt;
    use serde_json::json;

    #[test]
    fn xor_value_and_external_errors() {
        let spec = Spec::default();
        let mut ctx = Context::new(&spec, Options::new());
        Example {
            value: Some(json!(1)),
            external_value: Some("https://example.com/x.json".into()),
            ..Default::default()
        }
        .validate_with_context(&mut ctx, "ex".into());
        assert!(
            ctx.errors.mentions("mutually exclusive"),
            "errors: {:?}",
            ctx.errors
        );
    }

    #[test]
    fn external_value_uri_reference_validated() {
        // Per the OAS 3.1 JSON Schema, `externalValue` is `format:
        // uri-reference`: relative paths and non-HTTP schemes pass while
        // whitespace / control-char garbage fails.
        let spec = Spec::default();
        for ok in ["./fixtures/example.json", "urn:example:my-example"] {
            let mut ctx = Context::new(&spec, Options::new());
            Example {
                external_value: Some(ok.to_owned()),
                ..Default::default()
            }
            .validate_with_context(&mut ctx, "ex".into());
            assert!(
                ctx.errors.is_empty(),
                "uri-reference `{ok}` should pass: {:?}",
                ctx.errors
            );
        }
        let mut ctx = Context::new(&spec, Options::new());
        Example {
            external_value: Some("not a uri".into()),
            ..Default::default()
        }
        .validate_with_context(&mut ctx, "ex".into());
        assert!(
            ctx.errors.mentions("must be a valid URI"),
            "errors: {:?}",
            ctx.errors
        );
    }
}