tonic_rest_openapi/patch/
mod.rs1mod cleanup;
16mod helpers;
17mod oas31;
18mod responses;
19mod security;
20mod streaming;
21mod validation;
22
23use serde_yaml_ng::Value;
24
25use crate::config::PlainTextEndpoint;
26use crate::discover::ProtoMetadata;
27use crate::error;
28
29#[derive(Debug)]
44pub struct PatchConfig<'a> {
45 metadata: &'a ProtoMetadata,
47
48 unimplemented_method_names: Vec<String>,
50
51 public_method_names: Vec<String>,
53
54 error_schema_ref: String,
56
57 plain_text_endpoints: Vec<PlainTextEndpoint>,
59
60 metrics_path: Option<String>,
62
63 readiness_path: Option<String>,
65
66 transforms: crate::config::TransformConfig,
68
69 bearer_description: Option<String>,
73}
74
75impl<'a> PatchConfig<'a> {
76 #[must_use]
78 pub fn new(metadata: &'a ProtoMetadata) -> Self {
79 Self {
80 metadata,
81 unimplemented_method_names: Vec::new(),
82 public_method_names: Vec::new(),
83 error_schema_ref: crate::DEFAULT_ERROR_SCHEMA_REF.to_string(),
84 plain_text_endpoints: Vec::new(),
85 metrics_path: None,
86 readiness_path: None,
87 transforms: crate::config::TransformConfig::default(),
88 bearer_description: None,
89 }
90 }
91
92 #[must_use]
105 pub fn with_project_config(mut self, project: &crate::ProjectConfig) -> Self {
106 self.error_schema_ref.clone_from(&project.error_schema_ref);
107 self.plain_text_endpoints
108 .clone_from(&project.plain_text_endpoints);
109 self.metrics_path.clone_from(&project.metrics_path);
110 self.readiness_path.clone_from(&project.readiness_path);
111 self.transforms = crate::config::TransformConfig {
112 upgrade_to_3_1: project.transforms.upgrade_to_3_1,
113 annotate_sse: project.transforms.annotate_sse,
114 inject_validation: project.transforms.inject_validation,
115 add_security: project.transforms.add_security,
116 inline_request_bodies: project.transforms.inline_request_bodies,
117 flatten_uuid_refs: project.transforms.flatten_uuid_refs,
118 normalize_line_endings: project.transforms.normalize_line_endings,
119 };
120
121 if !project.unimplemented_methods.is_empty() {
122 self.unimplemented_method_names
123 .clone_from(&project.unimplemented_methods);
124 }
125 if !project.public_methods.is_empty() {
126 self.public_method_names.clone_from(&project.public_methods);
127 }
128
129 self
130 }
131
132 #[must_use]
137 pub fn unimplemented_methods(mut self, methods: &[&str]) -> Self {
138 self.unimplemented_method_names = methods.iter().map(ToString::to_string).collect();
139 self
140 }
141
142 #[must_use]
147 pub fn public_methods(mut self, methods: &[&str]) -> Self {
148 self.public_method_names = methods.iter().map(ToString::to_string).collect();
149 self
150 }
151
152 #[must_use]
154 pub fn error_schema_ref(mut self, ref_path: &str) -> Self {
155 self.error_schema_ref = ref_path.to_string();
156 self
157 }
158
159 #[must_use]
161 pub fn upgrade_to_3_1(mut self, enabled: bool) -> Self {
162 self.transforms.upgrade_to_3_1 = enabled;
163 self
164 }
165
166 #[must_use]
168 pub fn annotate_sse(mut self, enabled: bool) -> Self {
169 self.transforms.annotate_sse = enabled;
170 self
171 }
172
173 #[must_use]
175 pub fn inject_validation(mut self, enabled: bool) -> Self {
176 self.transforms.inject_validation = enabled;
177 self
178 }
179
180 #[must_use]
182 pub fn add_security(mut self, enabled: bool) -> Self {
183 self.transforms.add_security = enabled;
184 self
185 }
186
187 #[must_use]
189 pub fn inline_request_bodies(mut self, enabled: bool) -> Self {
190 self.transforms.inline_request_bodies = enabled;
191 self
192 }
193
194 #[must_use]
196 pub fn flatten_uuid_refs(mut self, enabled: bool) -> Self {
197 self.transforms.flatten_uuid_refs = enabled;
198 self
199 }
200
201 #[must_use]
203 pub fn normalize_line_endings(mut self, enabled: bool) -> Self {
204 self.transforms.normalize_line_endings = enabled;
205 self
206 }
207
208 #[must_use]
210 pub fn skip_upgrade(self) -> Self {
211 self.upgrade_to_3_1(false)
212 }
213
214 #[must_use]
216 pub fn skip_sse(self) -> Self {
217 self.annotate_sse(false)
218 }
219
220 #[must_use]
222 pub fn skip_validation(self) -> Self {
223 self.inject_validation(false)
224 }
225
226 #[must_use]
228 pub fn skip_security(self) -> Self {
229 self.add_security(false)
230 }
231
232 #[must_use]
234 pub fn skip_inline_request_bodies(self) -> Self {
235 self.inline_request_bodies(false)
236 }
237
238 #[must_use]
240 pub fn skip_uuid_flattening(self) -> Self {
241 self.flatten_uuid_refs(false)
242 }
243
244 #[must_use]
246 pub fn skip_line_ending_normalization(self) -> Self {
247 self.normalize_line_endings(false)
248 }
249
250 #[must_use]
254 pub fn bearer_description(mut self, description: &str) -> Self {
255 self.bearer_description = Some(description.to_string());
256 self
257 }
258
259 #[must_use]
261 pub fn plain_text_endpoints(mut self, endpoints: &[PlainTextEndpoint]) -> Self {
262 self.plain_text_endpoints = endpoints.to_vec();
263 self
264 }
265
266 #[must_use]
268 pub fn metrics_path(mut self, path: &str) -> Self {
269 self.metrics_path = Some(path.to_string());
270 self
271 }
272
273 #[must_use]
275 pub fn readiness_path(mut self, path: &str) -> Self {
276 self.readiness_path = Some(path.to_string());
277 self
278 }
279
280 fn resolved_ops(&self) -> error::Result<(Vec<String>, Vec<String>)> {
282 let unimplemented = self.resolve_method_list(&self.unimplemented_method_names)?;
283 let public = self.resolve_method_list(&self.public_method_names)?;
284 Ok((unimplemented, public))
285 }
286
287 fn resolve_method_list(&self, names: &[String]) -> error::Result<Vec<String>> {
289 if names.is_empty() {
290 return Ok(Vec::new());
291 }
292 let refs: Vec<&str> = names.iter().map(String::as_str).collect();
293 crate::discover::resolve_operation_ids(self.metadata, &refs)
294 }
295}
296
297pub fn patch(input_yaml: &str, config: &PatchConfig<'_>) -> error::Result<String> {
330 let mut doc: Value = serde_yaml_ng::from_str(input_yaml)?;
331
332 let (unimplemented_ops, public_ops) = config.resolved_ops()?;
334
335 if config.transforms.upgrade_to_3_1 {
337 oas31::upgrade_version(&mut doc);
338 oas31::convert_nullable(&mut doc);
339 }
340
341 if config.transforms.annotate_sse {
343 streaming::annotate_sse(&mut doc, &config.metadata.streaming_ops);
344 }
345
346 responses::patch_empty_responses(&mut doc);
348 responses::remove_redundant_query_params(&mut doc);
349 responses::patch_plain_text_endpoints(&mut doc, &config.plain_text_endpoints);
350 responses::patch_metrics_response_headers(&mut doc, config.metrics_path.as_deref());
351 responses::patch_readiness_probe_responses(&mut doc, config.readiness_path.as_deref());
352 responses::patch_redirect_endpoints(&mut doc, &config.metadata.redirect_paths);
353 responses::ensure_rest_error_schema(&mut doc, &config.error_schema_ref);
354 responses::rewrite_default_error_responses(&mut doc, &config.error_schema_ref);
355
356 cleanup::strip_unspecified_from_query_enums(&mut doc);
358 cleanup::rewrite_enum_values(&mut doc, config.metadata);
359
360 if !unimplemented_ops.is_empty() {
362 cleanup::mark_unimplemented_operations(
363 &mut doc,
364 &unimplemented_ops,
365 &config.error_schema_ref,
366 );
367 }
368
369 if config.transforms.add_security {
371 security::add_security_schemes(&mut doc, &public_ops, config.bearer_description.as_deref());
372 }
373
374 cleanup::clean_tag_descriptions(&mut doc);
376 cleanup::remove_empty_request_bodies(&mut doc);
377 cleanup::remove_unused_empty_schemas(&mut doc);
378 cleanup::remove_format_enum(&mut doc);
379
380 if config.transforms.flatten_uuid_refs {
382 validation::flatten_uuid_refs(&mut doc, config.metadata.uuid_schema.as_deref());
383 }
384 validation::simplify_uuid_query_params(&mut doc);
385
386 if config.transforms.inject_validation {
388 validation::inject_validation_constraints(&mut doc, &config.metadata.field_constraints);
389 }
390
391 validation::strip_path_fields_from_body(&mut doc);
393 validation::enrich_path_params(&mut doc, &config.metadata.path_param_constraints);
394
395 if config.transforms.inline_request_bodies {
397 cleanup::inline_request_bodies(&mut doc);
398 cleanup::remove_empty_inlined_request_bodies(&mut doc);
399 }
400
401 if config.transforms.normalize_line_endings {
403 oas31::normalize_line_endings(&mut doc);
404 }
405
406 serde_yaml_ng::to_string(&doc).map_err(error::Error::from)
407}