openauth-plugins 0.0.4

Official OpenAuth plugin modules.
Documentation
use http::Method;
use openauth_core::api::OpenApiOperation;
use serde_json::{json, Value};

pub struct EndpointDoc {
    pub path: &'static str,
    pub method: Method,
    pub operation_id: &'static str,
    pub description: &'static str,
    pub request_schema: Option<Value>,
    pub parameters: Vec<Value>,
    pub response_200: Value,
}

impl EndpointDoc {
    pub fn operation(&self) -> OpenApiOperation {
        let mut operation = OpenApiOperation::new(self.operation_id)
            .description(self.description)
            .tag("Admin")
            .response("200", self.response_200.clone());
        for parameter in &self.parameters {
            operation = operation.parameter(parameter.clone());
        }
        if let Some(schema) = &self.request_schema {
            operation = operation.request_body(json!({
                "required": true,
                "content": {
                    "application/json": {
                        "schema": schema
                    }
                }
            }));
        }
        operation
    }
}

pub fn user_id_body() -> Value {
    schema(&[("userId", "string", true, "The user id.")])
}

pub fn create_user_schema() -> Value {
    json!({
        "type": "object",
        "properties": {
            "email": {
                "type": "string",
                "format": "email",
                "description": "The user's email address.",
            },
            "name": {
                "type": "string",
                "description": "The user's display name.",
            },
            "password": {
                "type": "string",
                "minLength": 8,
                "description": "Optional credential password. Must satisfy the configured core password policy.",
            },
            "role": role_schema("Optional role or role list for the new user."),
            "data": {
                "type": "object",
                "description": "Optional custom additional user fields. Core/admin reserved fields are rejected.",
                "additionalProperties": true,
            },
        },
        "required": ["email", "name"],
    })
}

pub fn set_role_schema() -> Value {
    json!({
        "type": "object",
        "properties": {
            "userId": {
                "type": "string",
                "description": "The user id to update.",
            },
            "role": role_schema("The role or roles to assign."),
        },
        "required": ["userId", "role"],
    })
}

pub fn list_user_parameters() -> Vec<Value> {
    vec![
        query_parameter("searchValue", "string", false, "Search value."),
        query_parameter(
            "searchField",
            "string",
            false,
            "Search field, usually `email` or `name`.",
        ),
        query_parameter(
            "searchOperator",
            "string",
            false,
            "Search operator: contains, starts_with, or ends_with.",
        ),
        query_parameter("limit", "number", false, "Maximum number of users to return."),
        query_parameter("offset", "number", false, "Offset for pagination."),
        query_parameter("sortBy", "string", false, "Field to sort by."),
        query_parameter("sortDirection", "string", false, "Sort direction: asc or desc."),
        query_parameter("filterField", "string", false, "Field to filter by."),
        query_parameter(
            "filterValue",
            "string",
            false,
            "Filter value. Booleans, integers, and JSON arrays are parsed when possible.",
        ),
        query_parameter(
            "filterOperator",
            "string",
            false,
            "Filter operator, including eq, ne, in, not_in, lt, lte, gt, gte, contains, starts_with, or ends_with.",
        ),
    ]
}

pub fn schema(fields: &[(&str, &str, bool, &str)]) -> Value {
    let mut properties = serde_json::Map::new();
    let mut required = Vec::new();
    for (name, schema_type, is_required, description) in fields {
        properties.insert(
            (*name).to_owned(),
            json!({
                "type": schema_type,
                "description": description,
            }),
        );
        if *is_required {
            required.push(Value::String((*name).to_owned()));
        }
    }
    json!({
        "type": "object",
        "properties": properties,
        "required": required,
    })
}

pub fn query_parameter(name: &str, schema_type: &str, required: bool, description: &str) -> Value {
    json!({
        "name": name,
        "in": "query",
        "required": required,
        "description": description,
        "schema": { "type": schema_type },
    })
}

pub fn ref_schema(name: &str) -> Value {
    json!({ "$ref": format!("#/components/schemas/{name}") })
}

pub fn ref_response(description: &str, schema_name: &str) -> Value {
    json_response(description, ref_schema(schema_name))
}

pub fn success_response(description: &str) -> Value {
    object_response(description, &[("success", json!({ "type": "boolean" }))])
}

pub fn object_response(description: &str, fields: &[(&str, Value)]) -> Value {
    let mut properties = serde_json::Map::new();
    for (name, schema) in fields {
        properties.insert((*name).to_owned(), schema.clone());
    }
    json_response(
        description,
        json!({
            "type": "object",
            "properties": properties,
            "required": fields.iter().map(|(name, _)| *name).collect::<Vec<_>>(),
        }),
    )
}

fn role_schema(description: &str) -> Value {
    json!({
        "description": description,
        "oneOf": [
            { "type": "string" },
            { "type": "array", "items": { "type": "string" } }
        ],
    })
}

fn json_response(description: &str, schema: Value) -> Value {
    json!({
        "description": description,
        "content": {
            "application/json": {
                "schema": schema,
            },
        },
    })
}