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::config::{InfoOverrides, ServerEntry};
27use crate::discover::ProtoMetadata;
28use crate::error;
29
30#[derive(Debug)]
45pub struct PatchConfig<'a> {
46 metadata: &'a ProtoMetadata,
48
49 unimplemented_method_names: Vec<String>,
51
52 public_method_names: Vec<String>,
54
55 deprecated_method_names: Vec<String>,
57
58 error_schema_ref: String,
60
61 plain_text_endpoints: Vec<PlainTextEndpoint>,
63
64 metrics_path: Option<String>,
66
67 readiness_path: Option<String>,
69
70 transforms: crate::config::TransformConfig,
72
73 bearer_description: Option<String>,
77
78 servers: Vec<ServerEntry>,
80
81 info: InfoOverrides,
83
84 write_only_fields: Vec<String>,
86
87 read_only_fields: Vec<String>,
89}
90
91impl<'a> PatchConfig<'a> {
92 #[must_use]
94 pub fn new(metadata: &'a ProtoMetadata) -> Self {
95 Self {
96 metadata,
97 unimplemented_method_names: Vec::new(),
98 public_method_names: Vec::new(),
99 deprecated_method_names: Vec::new(),
100 error_schema_ref: crate::DEFAULT_ERROR_SCHEMA_REF.to_string(),
101 plain_text_endpoints: Vec::new(),
102 metrics_path: None,
103 readiness_path: None,
104 transforms: crate::config::TransformConfig::default(),
105 bearer_description: None,
106 servers: Vec::new(),
107 info: InfoOverrides::default(),
108 write_only_fields: Vec::new(),
109 read_only_fields: Vec::new(),
110 }
111 }
112
113 #[must_use]
126 pub fn with_project_config(mut self, project: &crate::ProjectConfig) -> Self {
127 self.error_schema_ref.clone_from(&project.error_schema_ref);
128 self.plain_text_endpoints
129 .clone_from(&project.plain_text_endpoints);
130 self.metrics_path.clone_from(&project.metrics_path);
131 self.readiness_path.clone_from(&project.readiness_path);
132 self.servers.clone_from(&project.servers);
133 self.info = project.info.clone();
134 self.write_only_fields
135 .clone_from(&project.write_only_fields);
136 self.read_only_fields.clone_from(&project.read_only_fields);
137 self.transforms = project.transforms;
138
139 if !project.unimplemented_methods.is_empty() {
140 self.unimplemented_method_names
141 .clone_from(&project.unimplemented_methods);
142 }
143 if !project.public_methods.is_empty() {
144 self.public_method_names.clone_from(&project.public_methods);
145 }
146 if !project.deprecated_methods.is_empty() {
147 self.deprecated_method_names
148 .clone_from(&project.deprecated_methods);
149 }
150
151 self
152 }
153
154 #[must_use]
159 pub fn unimplemented_methods(mut self, methods: &[&str]) -> Self {
160 self.unimplemented_method_names = methods.iter().map(ToString::to_string).collect();
161 self
162 }
163
164 #[must_use]
169 pub fn public_methods(mut self, methods: &[&str]) -> Self {
170 self.public_method_names = methods.iter().map(ToString::to_string).collect();
171 self
172 }
173
174 #[must_use]
179 pub fn deprecated_methods(mut self, methods: &[&str]) -> Self {
180 self.deprecated_method_names = methods.iter().map(ToString::to_string).collect();
181 self
182 }
183
184 #[must_use]
186 pub fn error_schema_ref(mut self, ref_path: &str) -> Self {
187 self.error_schema_ref = ref_path.to_string();
188 self
189 }
190
191 #[must_use]
193 pub fn upgrade_to_3_1(mut self, enabled: bool) -> Self {
194 self.transforms.upgrade_to_3_1 = enabled;
195 self
196 }
197
198 #[must_use]
200 pub fn annotate_sse(mut self, enabled: bool) -> Self {
201 self.transforms.annotate_sse = enabled;
202 self
203 }
204
205 #[must_use]
207 pub fn inject_validation(mut self, enabled: bool) -> Self {
208 self.transforms.inject_validation = enabled;
209 self
210 }
211
212 #[must_use]
214 pub fn add_security(mut self, enabled: bool) -> Self {
215 self.transforms.add_security = enabled;
216 self
217 }
218
219 #[must_use]
221 pub fn inline_request_bodies(mut self, enabled: bool) -> Self {
222 self.transforms.inline_request_bodies = enabled;
223 self
224 }
225
226 #[must_use]
228 pub fn flatten_uuid_refs(mut self, enabled: bool) -> Self {
229 self.transforms.flatten_uuid_refs = enabled;
230 self
231 }
232
233 #[must_use]
235 pub fn normalize_line_endings(mut self, enabled: bool) -> Self {
236 self.transforms.normalize_line_endings = enabled;
237 self
238 }
239
240 #[must_use]
242 pub fn inject_servers(mut self, enabled: bool) -> Self {
243 self.transforms.inject_servers = enabled;
244 self
245 }
246
247 #[must_use]
249 pub fn rewrite_create_responses(mut self, enabled: bool) -> Self {
250 self.transforms.rewrite_create_responses = enabled;
251 self
252 }
253
254 #[must_use]
256 pub fn annotate_field_access(mut self, enabled: bool) -> Self {
257 self.transforms.annotate_field_access = enabled;
258 self
259 }
260
261 #[must_use]
263 pub fn skip_upgrade(self) -> Self {
264 self.upgrade_to_3_1(false)
265 }
266
267 #[must_use]
269 pub fn skip_sse(self) -> Self {
270 self.annotate_sse(false)
271 }
272
273 #[must_use]
275 pub fn skip_validation(self) -> Self {
276 self.inject_validation(false)
277 }
278
279 #[must_use]
281 pub fn skip_security(self) -> Self {
282 self.add_security(false)
283 }
284
285 #[must_use]
287 pub fn skip_inline_request_bodies(self) -> Self {
288 self.inline_request_bodies(false)
289 }
290
291 #[must_use]
293 pub fn skip_uuid_flattening(self) -> Self {
294 self.flatten_uuid_refs(false)
295 }
296
297 #[must_use]
299 pub fn skip_line_ending_normalization(self) -> Self {
300 self.normalize_line_endings(false)
301 }
302
303 #[must_use]
305 pub fn skip_servers(self) -> Self {
306 self.inject_servers(false)
307 }
308
309 #[must_use]
311 pub fn skip_create_response_rewrite(self) -> Self {
312 self.rewrite_create_responses(false)
313 }
314
315 #[must_use]
317 pub fn skip_field_access_annotation(self) -> Self {
318 self.annotate_field_access(false)
319 }
320
321 #[must_use]
325 pub fn bearer_description(mut self, description: &str) -> Self {
326 self.bearer_description = Some(description.to_string());
327 self
328 }
329
330 #[must_use]
332 pub fn servers(mut self, servers: &[ServerEntry]) -> Self {
333 self.servers = servers.to_vec();
334 self
335 }
336
337 #[must_use]
339 pub fn info(mut self, info: InfoOverrides) -> Self {
340 self.info = info;
341 self
342 }
343
344 #[must_use]
346 pub fn write_only_fields(mut self, fields: &[&str]) -> Self {
347 self.write_only_fields = fields.iter().map(ToString::to_string).collect();
348 self
349 }
350
351 #[must_use]
353 pub fn read_only_fields(mut self, fields: &[&str]) -> Self {
354 self.read_only_fields = fields.iter().map(ToString::to_string).collect();
355 self
356 }
357
358 #[must_use]
360 pub fn plain_text_endpoints(mut self, endpoints: &[PlainTextEndpoint]) -> Self {
361 self.plain_text_endpoints = endpoints.to_vec();
362 self
363 }
364
365 #[must_use]
367 pub fn metrics_path(mut self, path: &str) -> Self {
368 self.metrics_path = Some(path.to_string());
369 self
370 }
371
372 #[must_use]
374 pub fn readiness_path(mut self, path: &str) -> Self {
375 self.readiness_path = Some(path.to_string());
376 self
377 }
378
379 fn resolved_ops(&self) -> error::Result<(Vec<String>, Vec<String>, Vec<String>)> {
381 let unimplemented = self.resolve_method_list(&self.unimplemented_method_names)?;
382 let public = self.resolve_method_list(&self.public_method_names)?;
383 let deprecated = self.resolve_method_list(&self.deprecated_method_names)?;
384 Ok((unimplemented, public, deprecated))
385 }
386
387 fn resolve_method_list(&self, names: &[String]) -> error::Result<Vec<String>> {
389 if names.is_empty() {
390 return Ok(Vec::new());
391 }
392 let refs: Vec<&str> = names.iter().map(String::as_str).collect();
393 crate::discover::resolve_operation_ids(self.metadata, &refs)
394 }
395}
396
397pub fn patch(input_yaml: &str, config: &PatchConfig<'_>) -> error::Result<String> {
431 let mut doc: Value = serde_yaml_ng::from_str(input_yaml)?;
432
433 let (unimplemented_ops, public_ops, deprecated_ops) = config.resolved_ops()?;
435
436 if config.transforms.upgrade_to_3_1 {
438 oas31::upgrade_version(&mut doc);
439 oas31::convert_nullable(&mut doc);
440 }
441 if config.transforms.inject_servers {
442 oas31::inject_servers_and_info(&mut doc, &config.servers, &config.info);
443 }
444
445 if config.transforms.annotate_sse {
447 streaming::annotate_sse(&mut doc, &config.metadata.streaming_ops);
448 }
449
450 responses::patch_empty_responses(&mut doc);
452 responses::remove_redundant_query_params(&mut doc);
453 responses::patch_plain_text_endpoints(&mut doc, &config.plain_text_endpoints);
454 responses::patch_metrics_response_headers(&mut doc, config.metrics_path.as_deref());
455 responses::patch_readiness_probe_responses(&mut doc, config.readiness_path.as_deref());
456 responses::patch_redirect_endpoints(&mut doc, &config.metadata.redirect_paths);
457 responses::ensure_rest_error_schema(&mut doc, &config.error_schema_ref);
458 responses::rewrite_default_error_responses(&mut doc, &config.error_schema_ref);
459 if config.transforms.rewrite_create_responses {
460 responses::rewrite_create_responses(&mut doc);
461 }
462
463 cleanup::rewrite_enum_values(&mut doc, config.metadata);
469 cleanup::strip_unspecified_from_query_enums(&mut doc);
470
471 if !unimplemented_ops.is_empty() {
473 cleanup::mark_unimplemented_operations(
474 &mut doc,
475 &unimplemented_ops,
476 &config.error_schema_ref,
477 );
478 }
479
480 if !deprecated_ops.is_empty() {
481 cleanup::mark_deprecated_operations(&mut doc, &deprecated_ops);
482 }
483
484 if config.transforms.add_security {
486 security::add_security_schemes(&mut doc, &public_ops, config.bearer_description.as_deref());
487 }
488
489 cleanup::clean_tag_descriptions(&mut doc);
491 cleanup::populate_operation_summaries(&mut doc);
492 cleanup::remove_empty_request_bodies(&mut doc);
493 cleanup::remove_unused_empty_schemas(&mut doc);
494 cleanup::remove_format_enum(&mut doc);
495
496 validation::flatten_uuid_path_templates(&mut doc);
498 if config.transforms.flatten_uuid_refs {
499 validation::flatten_uuid_refs(&mut doc, config.metadata.uuid_schema.as_deref());
500 }
501 validation::simplify_uuid_query_params(&mut doc);
502
503 if config.transforms.inject_validation {
505 validation::inject_validation_constraints(&mut doc, &config.metadata.field_constraints);
506 }
507 if config.transforms.annotate_field_access {
508 validation::annotate_field_access(
509 &mut doc,
510 &config.write_only_fields,
511 &config.read_only_fields,
512 );
513 }
514 validation::annotate_duration_fields(&mut doc);
515
516 validation::strip_path_fields_from_body(&mut doc);
518 validation::enrich_path_params(&mut doc, &config.metadata.path_param_constraints);
519
520 if config.transforms.inline_request_bodies {
522 cleanup::inline_request_bodies(&mut doc);
523 cleanup::remove_empty_inlined_request_bodies(&mut doc);
524 }
525
526 if config.transforms.normalize_line_endings {
528 oas31::normalize_line_endings(&mut doc);
529 }
530
531 serde_yaml_ng::to_string(&doc).map_err(error::Error::from)
532}