oapi_codegen/lower/
prune.rs1use std::collections::BTreeSet;
17use std::collections::HashMap;
18
19use crate::ir::EnumKind;
20use crate::ir::Item;
21use crate::ir::Module;
22use crate::ir::RequestPayload;
23use crate::ir::ResponseBody;
24use crate::ir::RustType;
25use crate::ir::Service;
26use crate::ir::Struct;
27use crate::naming::Case;
28use crate::naming::to_ident;
29
30pub fn prune_unused_models(module: &mut Module, service: &Service) {
32 let mut reachable: BTreeSet<String> = BTreeSet::new();
33 let mut worklist: Vec<String> = Vec::new();
34
35 for name in service_refs(service) {
36 if reachable.insert(name.clone()) {
37 worklist.push(name);
38 }
39 }
40
41 let index: HashMap<&str, &Item> = module.items.iter().map(|item| return (item.name(), item)).collect();
42
43 while let Some(name) = worklist.pop() {
44 let Some(item) = index.get(name.as_str()) else {
45 continue;
46 };
47 for referenced in item_refs(item) {
48 if reachable.insert(referenced.clone()) {
49 worklist.push(referenced);
50 }
51 }
52 }
53
54 module.items.retain(|item| return reachable.contains(item.name()));
55}
56
57fn canonical(name: &str) -> String {
60 return to_ident(name, Case::Pascal).logical().to_owned();
61}
62
63fn named_ref(ty: &RustType) -> Option<String> {
69 return match ty {
70 RustType::Named(name) => Some(canonical(name)),
71 RustType::Vec(inner)
72 | RustType::Map(inner)
73 | RustType::Option(inner)
74 | RustType::Nullable(inner)
75 | RustType::Boxed(inner) => named_ref(inner),
76 _ => None,
77 };
78}
79
80fn item_refs(item: &Item) -> Vec<String> {
82 return match item {
83 Item::Struct(s) => struct_refs(s),
84 Item::Enum(enumeration) => match &enumeration.kind {
85 EnumKind::Strings(_) | EnumKind::Integers { .. } => Vec::new(),
86 EnumKind::Union(variants) | EnumKind::AnyOf(variants) => variants
87 .iter()
88 .filter_map(|variant| return named_ref(&variant.ty))
89 .collect(),
90 },
91 Item::Alias(alias) => named_ref(&alias.ty).into_iter().collect(),
92 };
93}
94
95fn struct_refs(s: &Struct) -> Vec<String> {
97 return s
98 .fields
99 .iter()
100 .filter_map(|field| return named_ref(&field.ty))
101 .chain(s.additional_properties.as_ref().and_then(named_ref))
102 .collect();
103}
104
105fn service_refs(service: &Service) -> Vec<String> {
107 let mut refs = Vec::new();
108 for operation in &service.operations {
109 for param in &operation.path_params {
110 refs.extend(named_ref(¶m.ty));
111 }
112 if let Some(query) = &operation.query {
113 refs.extend(struct_refs(query));
114 }
115 if let Some(headers) = &operation.headers {
116 for param in &headers.params {
117 refs.extend(named_ref(¶m.ty));
118 }
119 }
120 if let Some(cookies) = &operation.cookies {
121 for param in &cookies.params {
122 refs.extend(named_ref(¶m.ty));
123 }
124 }
125 if let Some(request) = &operation.request {
126 match request {
127 RequestPayload::Single(body) => refs.extend(named_ref(&body.ty)),
128 RequestPayload::Multipart(multipart) => {
129 for field in &multipart.fields {
130 refs.extend(named_ref(&field.ty));
131 }
132 }
133 RequestPayload::Negotiated(negotiated) => {
134 for variant in &negotiated.variants {
135 refs.extend(named_ref(&variant.body.ty));
136 }
137 }
138 }
139 }
140 for response in &operation.responses {
141 match &response.body {
142 Some(ResponseBody::Single(body)) => refs.extend(named_ref(&body.ty)),
143 Some(ResponseBody::Negotiated(negotiated)) => {
144 for variant in &negotiated.variants {
145 refs.extend(named_ref(&variant.body.ty));
146 }
147 }
148 None => {}
149 }
150 for header in &response.headers {
151 refs.extend(named_ref(&header.ty));
152 }
153 }
154 }
155 return refs;
156}