Skip to main content

kynos_openapi/model/
reference.rs

1//! The Reference Object, and the "either a reference or the thing" wrapper.
2
3use serde::{Deserialize, Serialize};
4
5/// A reference to another part of this or another description.
6///
7/// # This is not a JSON Schema `$ref`
8///
9/// The specification draws a sharp line that is easy to miss. A *Reference
10/// Object* — this type — has exactly three fields, and any other property
11/// present alongside them **shall be ignored**. A *Schema Object* `$ref` is
12/// plain JSON Schema 2020-12, where sibling keywords are fully applied.
13///
14/// [`Schema`](crate::Schema) therefore models `$ref` as an ordinary keyword,
15/// and does not use this type.
16///
17/// Accordingly this object carries no [`Extensions`](crate::Extensions): it
18/// cannot be extended.
19#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
20pub struct Ref {
21    /// The URI of the referenced component.
22    #[serde(rename = "$ref")]
23    pub location: String,
24
25    /// A short summary, overriding that of the referenced component.
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub summary: Option<String>,
28
29    /// A description, overriding that of the referenced component.
30    ///
31    /// [CommonMark] syntax may be used.
32    ///
33    /// [CommonMark]: https://spec.commonmark.org/
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub description: Option<String>,
36}
37
38impl Ref {
39    /// References an arbitrary URI.
40    pub fn new(location: impl Into<String>) -> Self {
41        Self {
42            location: location.into(),
43            summary: None,
44            description: None,
45        }
46    }
47
48    /// References a named entry under `#/components/schemas`.
49    #[must_use]
50    pub fn schema(name: &str) -> Self {
51        Self::new(format!("#/components/schemas/{name}"))
52    }
53
54    /// References a named entry under `#/components/responses`.
55    #[must_use]
56    pub fn response(name: &str) -> Self {
57        Self::new(format!("#/components/responses/{name}"))
58    }
59
60    /// References a named entry under `#/components/parameters`.
61    #[must_use]
62    pub fn parameter(name: &str) -> Self {
63        Self::new(format!("#/components/parameters/{name}"))
64    }
65
66    /// References a named entry under `#/components/requestBodies`.
67    #[must_use]
68    pub fn request_body(name: &str) -> Self {
69        Self::new(format!("#/components/requestBodies/{name}"))
70    }
71
72    /// References a named entry under `#/components/securitySchemes`.
73    #[must_use]
74    pub fn security_scheme(name: &str) -> Self {
75        Self::new(format!("#/components/securitySchemes/{name}"))
76    }
77
78    /// Sets the overriding summary.
79    #[must_use]
80    pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
81        self.summary = Some(summary.into());
82        self
83    }
84
85    /// Sets the overriding description.
86    #[must_use]
87    pub fn with_description(mut self, description: impl Into<String>) -> Self {
88        self.description = Some(description.into());
89        self
90    }
91}
92
93/// Either an inline `T` or a [`Ref`] standing in for one.
94///
95/// Deserialization prefers the reference: an object carrying `$ref` is always
96/// read as a [`Ref`], matching the specification's rule that the remaining
97/// properties of a Reference Object are ignored.
98#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(untagged)]
100pub enum RefOr<T> {
101    /// A reference to a component defined elsewhere.
102    Ref(Ref),
103    /// The object itself, inline.
104    Item(T),
105}
106
107impl<T> RefOr<T> {
108    /// Returns the inline item, or `None` when this is a reference.
109    pub fn as_item(&self) -> Option<&T> {
110        match self {
111            Self::Item(item) => Some(item),
112            Self::Ref(_) => None,
113        }
114    }
115
116    /// Returns the reference, or `None` when this is an inline item.
117    pub fn as_ref_object(&self) -> Option<&Ref> {
118        match self {
119            Self::Ref(reference) => Some(reference),
120            Self::Item(_) => None,
121        }
122    }
123
124    /// Returns `true` when this is a reference rather than an inline item.
125    pub fn is_ref(&self) -> bool {
126        matches!(self, Self::Ref(_))
127    }
128}
129
130impl<T> From<Ref> for RefOr<T> {
131    fn from(reference: Ref) -> Self {
132        Self::Ref(reference)
133    }
134}
135
136#[cfg(test)]
137mod tests;