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) | RustType::Map(inner) | RustType::Option(inner) | RustType::Boxed(inner) => {
72 named_ref(inner)
73 }
74 _ => None,
75 };
76}
77
78fn item_refs(item: &Item) -> Vec<String> {
80 return match item {
81 Item::Struct(s) => struct_refs(s),
82 Item::Enum(enumeration) => match &enumeration.kind {
83 EnumKind::Strings(_) | EnumKind::Integers { .. } => Vec::new(),
84 EnumKind::Union(variants) => variants
85 .iter()
86 .filter_map(|variant| return named_ref(&variant.ty))
87 .collect(),
88 },
89 Item::Alias(alias) => named_ref(&alias.ty).into_iter().collect(),
90 };
91}
92
93fn struct_refs(s: &Struct) -> Vec<String> {
95 return s
96 .fields
97 .iter()
98 .filter_map(|field| return named_ref(&field.ty))
99 .chain(s.additional_properties.as_ref().and_then(named_ref))
100 .collect();
101}
102
103fn service_refs(service: &Service) -> Vec<String> {
105 let mut refs = Vec::new();
106 for operation in &service.operations {
107 for param in &operation.path_params {
108 refs.extend(named_ref(¶m.ty));
109 }
110 if let Some(query) = &operation.query {
111 refs.extend(struct_refs(query));
112 }
113 if let Some(headers) = &operation.headers {
114 for param in &headers.params {
115 refs.extend(named_ref(¶m.ty));
116 }
117 }
118 if let Some(cookies) = &operation.cookies {
119 for param in &cookies.params {
120 refs.extend(named_ref(¶m.ty));
121 }
122 }
123 if let Some(request) = &operation.request {
124 match request {
125 RequestPayload::Single(body) => refs.extend(named_ref(&body.ty)),
126 RequestPayload::Multipart(multipart) => {
127 for field in &multipart.fields {
128 refs.extend(named_ref(&field.ty));
129 }
130 }
131 RequestPayload::Negotiated(negotiated) => {
132 for variant in &negotiated.variants {
133 refs.extend(named_ref(&variant.body.ty));
134 }
135 }
136 }
137 }
138 for response in &operation.responses {
139 match &response.body {
140 Some(ResponseBody::Single(body)) => refs.extend(named_ref(&body.ty)),
141 Some(ResponseBody::Negotiated(negotiated)) => {
142 for variant in &negotiated.variants {
143 refs.extend(named_ref(&variant.body.ty));
144 }
145 }
146 None => {}
147 }
148 for header in &response.headers {
149 refs.extend(named_ref(&header.ty));
150 }
151 }
152 }
153 return refs;
154}