Skip to main content

kynos_openapi/emit/
mod.rs

1//! Producing an artifact from a [`Document`], at a chosen specification
2//! version.
3//!
4//! The split from [`crate::model`] is the one the architecture asks for: the
5//! model is version-agnostic data, and everything that turns it into bytes at a
6//! particular version lives here. The serde derives stay on the model types
7//! themselves — they are part of how those types are represented, not part of
8//! choosing a version to represent them at.
9
10pub mod downgrade;
11
12use crate::{
13    model::document::{Document, SpecVersion},
14    validate::violation::SpecError,
15};
16
17impl Document {
18    /// Serializes to pretty-printed JSON.
19    ///
20    /// # Errors
21    ///
22    /// Returns an error only if a specification extension holds a value that
23    /// cannot be represented in JSON.
24    pub fn to_json(&self) -> Result<String, serde_json::Error> {
25        serde_json::to_string_pretty(self)
26    }
27
28    /// Serializes to YAML.
29    ///
30    /// # Errors
31    ///
32    /// Returns an error only if a specification extension holds a value that
33    /// cannot be represented in YAML.
34    #[cfg(feature = "yaml")]
35    pub fn to_yaml(&self) -> Result<String, serde_yaml_ng::Error> {
36        serde_yaml_ng::to_string(self)
37    }
38
39    /// Produces this document as `version`, refusing a lossy downgrade.
40    ///
41    /// Cargo unifies features across a dependency graph, so a program can find
42    /// itself built with `openapi32` enabled even when it needs to publish a
43    /// 3.1 description. This is the safe way to ask for one: rather than
44    /// dropping 3.2-only constructs and emitting something that misdescribes
45    /// the API, it fails and names what stands in the way.
46    ///
47    /// # Errors
48    ///
49    /// Returns [`SpecError::RequiresV3_2`] when the document uses a construct
50    /// that `version` cannot express.
51    pub fn emit(&self, version: SpecVersion) -> Result<Self, SpecError> {
52        let blockers = downgrade::three_two_only_constructs(self);
53        if !version.supports_3_2() && !blockers.is_empty() {
54            return Err(SpecError::RequiresV3_2 { blockers });
55        }
56
57        let mut emitted = self.clone();
58        version.as_str().clone_into(&mut emitted.openapi);
59        Ok(emitted)
60    }
61}
62
63#[cfg(test)]
64mod tests;