1use crate::definitions::{EnumElement, ErrorCodeSchema, GenService};
2use crate::rust;
3use convert_case::{Case, Casing};
4use endpoint_libs::libs::error_code::ErrorCode;
5use endpoint_libs::model::Type;
6use eyre::bail;
7use std::collections::{HashMap, HashSet};
8
9pub fn build_error_code_catalog(custom_error_codes: Vec<ErrorCodeSchema>) -> eyre::Result<Vec<ErrorCodeSchema>> {
10 let mut codes = builtin_error_codes();
11 codes.extend(custom_error_codes);
12
13 let mut names: HashMap<String, ErrorCodeSchema> = HashMap::new();
14 let mut numbers: HashMap<i64, ErrorCodeSchema> = HashMap::new();
15
16 for code in &codes {
17 let variant = validate_error_code_variant(&code.name)?;
18 if let Some(existing) = names.insert(variant.clone(), code.clone()) {
19 bail!(
20 "Duplicate error code name '{}': conflicts with '{}'",
21 code.name,
22 existing.name
23 );
24 }
25
26 if let Some(existing) = numbers.insert(code.code, code.clone()) {
27 bail!(
28 "Duplicate error code value {} for '{}' and '{}'",
29 code.code,
30 existing.name,
31 code.name
32 );
33 }
34 }
35
36 codes.sort_by_key(|code| code.code);
37 Ok(codes)
38}
39
40pub fn validate_reserved_enum_names(enums: &[EnumElement]) -> eyre::Result<()> {
41 for enum_element in enums {
42 if let Type::Enum { name, .. } = &enum_element.inner {
43 if name.to_case(Case::Pascal) == "ErrorCode" {
44 bail!("Enum name 'ErrorCode' is reserved for generated endpoint error codes");
45 }
46 }
47 }
48
49 Ok(())
50}
51
52pub fn validate_endpoint_error_codes(services: &[GenService], error_codes: &[ErrorCodeSchema]) -> eyre::Result<()> {
53 let allowed_variants = error_codes
54 .iter()
55 .map(|code| validate_error_code_variant(&code.name))
56 .collect::<eyre::Result<HashSet<_>>>()?;
57
58 for service in services {
59 for endpoint in &service.endpoints {
60 for error in &endpoint.schema.errors {
61 let variant = rust::error_code_variant_name(error.code.variant());
62 if !allowed_variants.contains(&variant) {
63 bail!(
64 "Unknown error code '{}' in endpoint '{}' error '{}'",
65 error.code,
66 endpoint.schema.name,
67 error.name
68 );
69 }
70 }
71 }
72 }
73
74 Ok(())
75}
76
77fn validate_error_code_variant(name: &str) -> eyre::Result<String> {
78 let variant = rust::error_code_variant_name(name);
79 let is_valid = variant.chars().next().is_some_and(|c| c.is_ascii_uppercase())
80 && variant.chars().all(|c| c.is_ascii_alphanumeric());
81
82 if !is_valid {
83 bail!("Invalid error code name '{name}': expected a Rust enum variant name");
84 }
85
86 Ok(variant)
87}
88
89fn builtin_error_codes() -> Vec<ErrorCodeSchema> {
90 vec![
91 ErrorCodeSchema::new("BadRequest", ErrorCode::BAD_REQUEST.code() as i64, "Bad request"),
92 ErrorCodeSchema::new(
93 "Unauthorized",
94 ErrorCode::UNAUTHORIZED.code() as i64,
95 "Authentication is required",
96 ),
97 ErrorCodeSchema::new(
98 "PaymentRequired",
99 ErrorCode::PAYMENT_REQUIRED.code() as i64,
100 "Payment is required",
101 ),
102 ErrorCodeSchema::new("Forbidden", ErrorCode::FORBIDDEN.code() as i64, "Access is forbidden"),
103 ErrorCodeSchema::new("NotFound", ErrorCode::NOT_FOUND.code() as i64, "Resource was not found"),
104 ErrorCodeSchema::new(
105 "MethodNotAllowed",
106 ErrorCode::METHOD_NOT_ALLOWED.code() as i64,
107 "Method is not allowed",
108 ),
109 ErrorCodeSchema::new(
110 "NotAcceptable",
111 ErrorCode::NOT_ACCEPTABLE.code() as i64,
112 "Response format is not acceptable",
113 ),
114 ErrorCodeSchema::new(
115 "ProxyAuthenticationRequired",
116 ErrorCode::PROXY_AUTHENTICATION_REQUIRED.code() as i64,
117 "Proxy authentication is required",
118 ),
119 ErrorCodeSchema::new(
120 "RequestTimeout",
121 ErrorCode::REQUEST_TIMEOUT.code() as i64,
122 "Request timed out",
123 ),
124 ErrorCodeSchema::new(
125 "Conflict",
126 ErrorCode::CONFLICT.code() as i64,
127 "Request conflicts with current state",
128 ),
129 ErrorCodeSchema::new("Gone", ErrorCode::GONE.code() as i64, "Resource is gone"),
130 ErrorCodeSchema::new(
131 "LengthRequired",
132 ErrorCode::LENGTH_REQUIRED.code() as i64,
133 "Content length is required",
134 ),
135 ErrorCodeSchema::new(
136 "PreconditionFailed",
137 ErrorCode::PRECONDITION_FAILED.code() as i64,
138 "Precondition failed",
139 ),
140 ErrorCodeSchema::new(
141 "PayloadTooLarge",
142 ErrorCode::PAYLOAD_TOO_LARGE.code() as i64,
143 "Payload is too large",
144 ),
145 ErrorCodeSchema::new("UriTooLong", ErrorCode::URI_TOO_LONG.code() as i64, "URI is too long"),
146 ErrorCodeSchema::new(
147 "UnsupportedMediaType",
148 ErrorCode::UNSUPPORTED_MEDIA_TYPE.code() as i64,
149 "Media type is unsupported",
150 ),
151 ErrorCodeSchema::new(
152 "RangeNotSatisfiable",
153 ErrorCode::RANGE_NOT_SATISFIABLE.code() as i64,
154 "Requested range cannot be satisfied",
155 ),
156 ErrorCodeSchema::new(
157 "ExpectationFailed",
158 ErrorCode::EXPECTATION_FAILED.code() as i64,
159 "Expectation failed",
160 ),
161 ErrorCodeSchema::new("ImATeapot", ErrorCode::IM_A_TEAPOT.code() as i64, "I'm a teapot"),
162 ErrorCodeSchema::new(
163 "MisdirectedRequest",
164 ErrorCode::MISDIRECTED_REQUEST.code() as i64,
165 "Request was misdirected",
166 ),
167 ErrorCodeSchema::new(
168 "UnprocessableEntity",
169 ErrorCode::UNPROCESSABLE_ENTITY.code() as i64,
170 "Entity could not be processed",
171 ),
172 ErrorCodeSchema::new("Locked", ErrorCode::LOCKED.code() as i64, "Resource is locked"),
173 ErrorCodeSchema::new(
174 "FailedDependency",
175 ErrorCode::FAILED_DEPENDENCY.code() as i64,
176 "Dependency failed",
177 ),
178 ErrorCodeSchema::new(
179 "UpgradeRequired",
180 ErrorCode::UPGRADE_REQUIRED.code() as i64,
181 "Request must be upgraded",
182 ),
183 ErrorCodeSchema::new(
184 "PreconditionRequired",
185 ErrorCode::PRECONDITION_REQUIRED.code() as i64,
186 "Precondition is required",
187 ),
188 ErrorCodeSchema::new(
189 "TooManyRequests",
190 ErrorCode::TOO_MANY_REQUESTS.code() as i64,
191 "Too many requests",
192 ),
193 ErrorCodeSchema::new(
194 "RequestHeaderFieldsTooLarge",
195 ErrorCode::REQUEST_HEADER_FIELDS_TOO_LARGE.code() as i64,
196 "Request header fields are too large",
197 ),
198 ErrorCodeSchema::new(
199 "UnavailableForLegalReasons",
200 ErrorCode::UNAVAILABLE_FOR_LEGAL_REASONS.code() as i64,
201 "Unavailable for legal reasons",
202 ),
203 ErrorCodeSchema::new(
204 "InternalError",
205 ErrorCode::INTERNAL_ERROR.code() as i64,
206 "Internal server error",
207 ),
208 ErrorCodeSchema::new(
209 "NotImplemented",
210 ErrorCode::NOT_IMPLEMENTED.code() as i64,
211 "Endpoint is not implemented",
212 ),
213 ErrorCodeSchema::new("BadGateway", ErrorCode::BAD_GATEWAY.code() as i64, "Bad gateway"),
214 ErrorCodeSchema::new(
215 "ServiceUnavailable",
216 ErrorCode::SERVICE_UNAVAILABLE.code() as i64,
217 "Service is unavailable",
218 ),
219 ErrorCodeSchema::new(
220 "GatewayTimeout",
221 ErrorCode::GATEWAY_TIMEOUT.code() as i64,
222 "Gateway timed out",
223 ),
224 ErrorCodeSchema::new(
225 "HttpVersionNotSupported",
226 ErrorCode::HTTP_VERSION_NOT_SUPPORTED.code() as i64,
227 "HTTP version is not supported",
228 ),
229 ErrorCodeSchema::new(
230 "VariantAlsoNegotiates",
231 ErrorCode::VARIANT_ALSO_NEGOTIATES.code() as i64,
232 "Content negotiation variant problem",
233 ),
234 ErrorCodeSchema::new(
235 "InsufficientStorage",
236 ErrorCode::INSUFFICIENT_STORAGE.code() as i64,
237 "Insufficient storage",
238 ),
239 ErrorCodeSchema::new(
240 "LoopDetected",
241 ErrorCode::LOOP_DETECTED.code() as i64,
242 "Loop was detected",
243 ),
244 ErrorCodeSchema::new(
245 "NotExtended",
246 ErrorCode::NOT_EXTENDED.code() as i64,
247 "Request must be extended",
248 ),
249 ErrorCodeSchema::new(
250 "NetworkAuthenticationRequired",
251 ErrorCode::NETWORK_AUTHENTICATION_REQUIRED.code() as i64,
252 "Network authentication is required",
253 ),
254 ]
255}