1use std::{fmt, str::FromStr};
2
3#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub enum OpenApiPlane {
6 Runtime,
8 Server,
10 Control,
12}
13
14impl fmt::Display for OpenApiPlane {
15 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
16 formatter.write_str(match self {
17 Self::Runtime => "runtime",
18 Self::Server => "server",
19 Self::Control => "control",
20 })
21 }
22}
23
24impl FromStr for OpenApiPlane {
25 type Err = ParseOpenApiPlaneError;
26
27 fn from_str(value: &str) -> Result<Self, Self::Err> {
28 match value {
29 "runtime" => Ok(Self::Runtime),
30 "server" => Ok(Self::Server),
31 "control" => Ok(Self::Control),
32 _ => Err(ParseOpenApiPlaneError),
33 }
34 }
35}
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39pub struct ParseOpenApiPlaneError;
40
41impl fmt::Display for ParseOpenApiPlaneError {
42 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43 formatter.write_str("plane must be `runtime`, `server`, or `control`")
44 }
45}
46
47impl std::error::Error for ParseOpenApiPlaneError {}
48
49pub fn to_pretty_json(plane: OpenApiPlane) -> Result<String, serde_json::Error> {
55 match plane {
56 OpenApiPlane::Runtime => {
57 let mut document = serde_json::to_value(crate::runtime::openapi())?;
58 require_contract_headers(&mut document);
59 serde_json::to_string_pretty(&document)
60 }
61 OpenApiPlane::Server => {
62 let mut document = serde_json::to_value(crate::server::openapi())?;
63 require_contract_headers(&mut document);
64 require_server_literal_discriminator(&mut document);
65 serde_json::to_string_pretty(&document)
66 }
67 OpenApiPlane::Control => crate::control::openapi().to_pretty_json(),
68 }
69}
70
71fn require_contract_headers(document: &mut serde_json::Value) {
72 let Some(paths) = document
76 .get_mut("paths")
77 .and_then(serde_json::Value::as_object_mut)
78 else {
79 return;
80 };
81 for path in paths.values_mut() {
82 let Some(operations) = path.as_object_mut() else {
83 continue;
84 };
85 for operation in operations.values_mut() {
86 for (status, header) in [("401", "WWW-Authenticate")] {
87 let Some(value) = operation
88 .get_mut("responses")
89 .and_then(|responses| responses.get_mut(status))
90 .and_then(|response| response.get_mut("headers"))
91 .and_then(|headers| headers.get_mut(header))
92 .and_then(serde_json::Value::as_object_mut)
93 else {
94 continue;
95 };
96 value.insert("required".to_owned(), serde_json::Value::Bool(true));
97 }
98 }
99 }
100}
101
102fn require_server_literal_discriminator(document: &mut serde_json::Value) {
103 for (schema, expected) in [
104 ("InactiveProjectToken", false),
105 ("ActiveProjectToken", true),
106 ] {
107 let Some(active) = document
108 .get_mut("components")
109 .and_then(|components| components.get_mut("schemas"))
110 .and_then(|schemas| schemas.get_mut(schema))
111 .and_then(|schema| schema.get_mut("properties"))
112 .and_then(|properties| properties.get_mut("active"))
113 .and_then(serde_json::Value::as_object_mut)
114 else {
115 continue;
116 };
117 active.insert("const".to_owned(), serde_json::Value::Bool(expected));
118 }
119}