Skip to main content

endpoint_gen/
error_codes.rs

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}