oas3 0.21.0

Structures and tools to parse, navigate, and validate OpenAPI v3.1.xß specifications
Documentation
use std::{collections::BTreeMap, error::Error as StdError};

use serde::{Deserialize, Serialize};

use crate::spec::PathItem;

/// Map of possible out-of band callbacks related to the parent operation.
///
/// Each value in the map is a [Path Item Object] that describes a set of requests that may be
/// initiated by the API provider and the expected responses.
///
/// See <https://spec.openapis.org/oas/v3.1.1#callback-object>.
///
/// NB: this structure is flattened when serializing and unflattened when deserializing in order to
/// support spec extensions. I.e., `paths` is a synthetic property within the data tree that
/// comprises an OpenAPI document.
///
/// [Path Item Object]: https://spec.openapis.org/oas/v3.1.1#path-item-object
#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)]
#[serde(try_from = "CallbackSerde", into = "CallbackSerde")]
pub struct Callback {
    /// Map of [Path Item Object]s that describe a set of requests that may be initiated by the API
    /// provider and the expected responses.
    ///
    /// The key value used to identify the [Path Item Object] is an expression, evaluated at
    /// runtime, that identifies a URL to use for the callback operation.
    ///
    /// [Path Item Object]: https://spec.openapis.org/oas/v3.1.1#path-item-object
    pub paths: BTreeMap<String, PathItem>,

    /// Specification extensions.
    ///
    /// Only "x-" prefixed keys are collected, and the prefix is stripped.
    ///
    /// See <https://spec.openapis.org/oas/v3.1.1#specification-extensions>.
    pub extensions: BTreeMap<String, serde_json::Value>,
}

#[derive(Debug, Deserialize, Serialize)]
#[serde(transparent)]
struct CallbackSerde(serde_json::Map<String, serde_json::Value>);

impl TryFrom<CallbackSerde> for Callback {
    type Error = Box<dyn StdError>;

    fn try_from(CallbackSerde(map): CallbackSerde) -> Result<Self, Self::Error> {
        let (extensions, paths) = bisect_map(map, |key| key.starts_with("x-"));

        let paths = paths
            .into_iter()
            .map(|(key, value)| serde_json::from_value(value).map(|v| (key, v)))
            .collect::<Result<_, _>>()?;

        Ok(Self {
            paths,
            extensions: extensions.into_iter().collect(),
        })
    }
}

fn bisect_map(
    map: serde_json::Map<String, serde_json::Value>,
    predicate: fn(&String) -> bool,
) -> (
    serde_json::Map<String, serde_json::Value>,
    serde_json::Map<String, serde_json::Value>,
) {
    let mut first = map;
    let mut second = first.clone();

    first.retain(|key, _| predicate(key));
    second.retain(|key, _| !predicate(key));

    (first, second)
}

impl From<Callback> for CallbackSerde {
    fn from(val: Callback) -> Self {
        let Callback { paths, extensions } = val;

        CallbackSerde(
            paths
                .into_iter()
                .map(|(key, val)| {
                    (
                        key,
                        serde_json::to_value(val).expect("path item serialization should not fail"),
                    )
                })
                .chain(extensions)
                .collect(),
        )
    }
}