Skip to main content

apollo_errors/
catalog.rs

1//! Error catalog with expanded transparent variants
2//!
3//! This module provides functions to build an error catalog where transparent variants
4//! are expanded to show the actual variants they forward to.
5
6use crate::metadata::{
7    CodeMetadata, ErrorMetadata, FieldMetadata, RegularVariantMetadata, VariantMetadata,
8};
9use crate::registry::ERROR_REGISTRY;
10use serde::{Serialize, Serializer};
11
12/// Serialize StatusCode as its u16 value for JSON output
13fn serialize_status_code<S: Serializer>(
14    status: &http::StatusCode,
15    serializer: S,
16) -> Result<S::Ok, S::Error> {
17    serializer.serialize_u16(status.as_u16())
18}
19
20/// An error type entry in the expanded catalog
21#[derive(Debug, Clone, Serialize)]
22#[serde(rename_all = "camelCase")]
23pub struct CatalogErrorEntry {
24    /// The Rust type name
25    pub type_name: &'static str,
26
27    /// All variants (with transparent variants expanded)
28    pub variants: Vec<CatalogVariantEntry>,
29}
30
31/// A variant entry in the expanded catalog
32///
33/// This is always a "regular" variant - transparent variants are expanded
34/// to include the variants from the type they forward to.
35#[derive(Debug, Clone, Serialize)]
36#[serde(rename_all = "camelCase")]
37pub struct CatalogVariantEntry {
38    /// The variant name
39    pub name: &'static str,
40
41    /// Error message template
42    pub message: &'static str,
43
44    /// Error code variants
45    pub code: CodeMetadata,
46
47    /// HTTP status code
48    #[serde(serialize_with = "serialize_status_code")]
49    pub http_status: http::StatusCode,
50
51    /// Optional help text
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub help: Option<&'static str>,
54
55    /// Optional documentation URL
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub url: Option<&'static str>,
58
59    /// Optional severity
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub severity: Option<&'static str>,
62
63    /// Fields in this variant
64    pub fields: &'static [FieldMetadata],
65}
66
67impl From<&RegularVariantMetadata> for CatalogVariantEntry {
68    fn from(v: &RegularVariantMetadata) -> Self {
69        Self {
70            name: v.name,
71            message: v.message,
72            code: CodeMetadata {
73                default: v.code,
74                screaming_snake: v.code_screaming_snake,
75                camel: v.code_camel,
76                pascal: v.code_pascal,
77                kebab: v.code_kebab,
78            },
79            http_status: crate::private::http_status_from_u16(v.http_status),
80            help: v.help,
81            url: v.url,
82            severity: v.severity,
83            fields: v.fields,
84        }
85    }
86}
87
88/// Find error metadata by type name
89fn find_error_by_type_name(type_name: &str) -> Option<&'static ErrorMetadata> {
90    ERROR_REGISTRY
91        .iter()
92        .find(|entry| entry.metadata.type_name == type_name)
93        .map(|entry| entry.metadata)
94}
95
96/// Expand variants, resolving transparent variants to their inner type's variants
97fn expand_variants(variants: &[VariantMetadata]) -> Vec<CatalogVariantEntry> {
98    let mut result = Vec::new();
99
100    for variant in variants {
101        match variant {
102            VariantMetadata::Regular(regular) => {
103                result.push(CatalogVariantEntry::from(regular));
104            }
105            VariantMetadata::Transparent(transparent) => {
106                // Look up the forwarded type and include its variants
107                if let Some(inner_metadata) = find_error_by_type_name(transparent.forward_to) {
108                    // Recursively expand in case the inner type also has transparent variants
109                    result.extend(expand_variants(inner_metadata.variants));
110                }
111                // If the type isn't found in the registry, we skip it
112                // (it might not be registered, e.g., a generic type)
113            }
114        }
115    }
116
117    result
118}
119
120/// Build an expanded error catalog
121///
122/// Returns a list of all registered error types with their variants.
123/// Transparent variants are expanded to show the actual variants from
124/// the type they forward to.
125///
126/// # Example
127///
128/// ```rust,ignore
129/// use apollo_errors::error_catalog;
130///
131/// let catalog = error_catalog();
132/// let json = serde_json::to_string_pretty(&catalog).unwrap();
133/// println!("{}", json);
134/// ```
135pub fn error_catalog() -> Vec<CatalogErrorEntry> {
136    ERROR_REGISTRY
137        .iter()
138        .map(|entry| {
139            let metadata = entry.metadata;
140            CatalogErrorEntry {
141                type_name: metadata.type_name,
142                variants: expand_variants(metadata.variants),
143            }
144        })
145        .collect()
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn test_catalog_returns_entries() {
154        let catalog = error_catalog();
155        // We can't test specific entries without having error types registered,
156        // but we can verify the function works
157        assert!(catalog.is_empty() || !catalog.is_empty());
158    }
159}