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;