Skip to main content

alibi_core/openapi/
annotations.rs

1//! Generic route annotations and the mandatory entity wire projections.
2use super::{OpenApiEndpoint, OpenApiField, OpenApiModel, PluginOpenApiMetadata};
3use crate::AuthRoute;
4use alibi_schema_registry::{EntityRole, FieldDef, core_fields};
5use serde_json::json;
6
7fn wire_name(name: &str) -> String {
8    let mut words = name.split('_');
9    let mut output = words.next().unwrap_or_default().to_owned();
10    for word in words {
11        let mut letters = word.chars();
12        if let Some(first) = letters.next() {
13            output.extend(first.to_uppercase());
14            output.extend(letters);
15        }
16    }
17    output
18}
19fn field(field: &FieldDef, core: bool) -> Option<OpenApiField> {
20    // `active` is an internal Rust revocation flag, not a public session field.
21    if field.is_primary_key || (core && field.name == "active") {
22        return None;
23    }
24    let ty = field.ty.trim_start_matches("Option<").trim_end_matches('>');
25    let schema = match ty {
26        "DateTimeUtc" => json!({"type":"string","format":"date-time"}),
27        "bool" => json!({"type":"boolean"}),
28        "i64" | "f64" => json!({"type":"number"}),
29        "Json" => json!({"type":"json"}),
30        _ => json!({"type":"string"}),
31    };
32    let mut field = OpenApiField::new(
33        wire_name(field.name),
34        schema,
35        !field.ty.starts_with("Option<"),
36    );
37    if core && matches!(field.name.as_str(), "name" | "email") {
38        field.required = true;
39    }
40    if core && field.name == "emailVerified" {
41        field.input = false;
42        if let Some(object) = field.schema.as_object_mut() {
43            _ = object.insert("default".into(), json!(false));
44        }
45    }
46    Some(field)
47}
48/// Core wire projection guaranteed by the schema's mandatory entity accessors.
49#[must_use]
50pub fn core_models() -> Vec<OpenApiModel> {
51    [
52        ("User", EntityRole::User),
53        ("Session", EntityRole::Session),
54        ("Account", EntityRole::Account),
55        ("Verification", EntityRole::Verification),
56    ]
57    .into_iter()
58    .map(|(name, role)| {
59        OpenApiModel::new(
60            name,
61            core_fields(role)
62                .iter()
63                .filter_map(|definition| field(definition, true))
64                .collect(),
65        )
66    })
67    .collect()
68}
69/// Generic annotations for routes; built-in plugins supply their own declarations.
70#[must_use]
71pub fn route_metadata(routes: &[AuthRoute]) -> PluginOpenApiMetadata {
72    PluginOpenApiMetadata {
73        endpoints: routes
74            .iter()
75            .map(|route| {
76                (
77                    route.method.clone(),
78                    route.path.clone(),
79                    OpenApiEndpoint {
80                        operation_id: Some(route.operation_id.clone()),
81                        ..Default::default()
82                    },
83                )
84            })
85            .collect(),
86        ..Default::default()
87    }
88}