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    if field.is_primary_key {
21        return None;
22    }
23    let ty = field.ty.trim_start_matches("Option<").trim_end_matches('>');
24    let schema = match ty {
25        "DateTimeUtc" => json!({"type":"string","format":"date-time"}),
26        "bool" => json!({"type":"boolean"}),
27        "i64" | "f64" => json!({"type":"number"}),
28        "Json" => json!({"type":"json"}),
29        _ => json!({"type":"string"}),
30    };
31    let mut field = OpenApiField::new(
32        wire_name(field.name),
33        schema,
34        !field.ty.starts_with("Option<"),
35    );
36    if core && matches!(field.name.as_str(), "name" | "email") {
37        field.required = true;
38    }
39    if core && field.name == "emailVerified" {
40        field.input = false;
41        if let Some(object) = field.schema.as_object_mut() {
42            _ = object.insert("default".into(), json!(false));
43        }
44    }
45    Some(field)
46}
47/// Core wire projection guaranteed by the schema's mandatory entity accessors.
48#[must_use]
49pub fn core_models() -> Vec<OpenApiModel> {
50    [
51        ("User", EntityRole::User),
52        ("Session", EntityRole::Session),
53        ("Account", EntityRole::Account),
54        ("Verification", EntityRole::Verification),
55    ]
56    .into_iter()
57    .map(|(name, role)| {
58        OpenApiModel::new(
59            name,
60            core_fields(role)
61                .iter()
62                .filter_map(|definition| field(definition, true))
63                .collect(),
64        )
65    })
66    .collect()
67}
68/// Generic annotations for routes; built-in plugins supply their own declarations.
69#[must_use]
70pub fn route_metadata(routes: &[AuthRoute]) -> PluginOpenApiMetadata {
71    PluginOpenApiMetadata {
72        endpoints: routes
73            .iter()
74            .map(|route| {
75                (
76                    route.method.clone(),
77                    route.path.clone(),
78                    OpenApiEndpoint {
79                        operation_id: Some(route.operation_id.clone()),
80                        ..Default::default()
81                    },
82                )
83            })
84            .collect(),
85        ..Default::default()
86    }
87}