From 78e988b7cc410069605661696b13a63a9a8b9b49 Mon Sep 17 00:00:00 2001 From: Quentin Gliech Date: Tue, 30 Jul 2024 11:34:57 +0200 Subject: [PATCH] Tweak the schema generation and use a common definition for ULIDs --- crates/handlers/src/admin/mod.rs | 5 + crates/handlers/src/admin/params.rs | 12 +-- crates/handlers/src/admin/response.rs | 2 +- crates/handlers/src/admin/schema.rs | 56 +++++++++++ docs/api/spec.json | 133 ++++++++++---------------- 5 files changed, 116 insertions(+), 92 deletions(-) create mode 100644 crates/handlers/src/admin/schema.rs diff --git a/crates/handlers/src/admin/mod.rs b/crates/handlers/src/admin/mod.rs index 3019977f..da086fe4 100644 --- a/crates/handlers/src/admin/mod.rs +++ b/crates/handlers/src/admin/mod.rs @@ -37,6 +37,7 @@ mod call_context; mod model; mod params; mod response; +mod schema; mod v1; use self::call_context::CallContext; @@ -48,6 +49,10 @@ where Templates: FromRef, UrlBuilder: FromRef, { + aide::gen::in_context(|ctx| { + ctx.schema = schemars::gen::SchemaGenerator::new(schemars::gen::SchemaSettings::openapi3()); + }); + let mut api = OpenApi::default(); let router = ApiRouter::::new() .nest("/api/admin/v1", self::v1::router()) diff --git a/crates/handlers/src/admin/params.rs b/crates/handlers/src/admin/params.rs index 36f41171..ff4e595d 100644 --- a/crates/handlers/src/admin/params.rs +++ b/crates/handlers/src/admin/params.rs @@ -52,12 +52,8 @@ impl IntoResponse for UlidPathParamRejection { #[derive(JsonSchema, Debug, Clone, Copy, Deserialize)] struct UlidInPath { - #[schemars( - with = "String", - title = "ULID", - description = "A ULID as per https://github.com/ulid/spec", - regex(pattern = r"^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$") - )] + /// # The ID of the resource + #[schemars(with = "super::schema::Ulid")] id: Ulid, } @@ -81,12 +77,12 @@ const DEFAULT_PAGE_SIZE: usize = 10; struct PaginationParams { /// Retrieve the items before the given ID #[serde(rename = "page[before]")] - #[schemars(with = "Option")] + #[schemars(with = "Option")] before: Option, /// Retrieve the items after the given ID #[serde(rename = "page[after]")] - #[schemars(with = "Option")] + #[schemars(with = "Option")] after: Option, /// Retrieve the first N items diff --git a/crates/handlers/src/admin/response.rs b/crates/handlers/src/admin/response.rs index dde7b739..d05dea6b 100644 --- a/crates/handlers/src/admin/response.rs +++ b/crates/handlers/src/admin/response.rs @@ -143,7 +143,7 @@ struct SingleResource { type_: &'static str, /// The ID of the resource - #[schemars(with = "String")] + #[schemars(with = "super::schema::Ulid")] id: Ulid, /// The attributes of the resource diff --git a/crates/handlers/src/admin/schema.rs b/crates/handlers/src/admin/schema.rs new file mode 100644 index 00000000..0605f202 --- /dev/null +++ b/crates/handlers/src/admin/schema.rs @@ -0,0 +1,56 @@ +// Copyright 2024 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Common schema definitions + +use schemars::{ + gen::SchemaGenerator, + schema::{InstanceType, Metadata, Schema, SchemaObject, StringValidation}, + JsonSchema, +}; + +/// A type to use for schema definitions of ULIDs +/// +/// Use with `#[schemars(with = "crate::admin::schema::Ulid")]` +pub struct Ulid; + +impl JsonSchema for Ulid { + fn schema_name() -> String { + "ULID".to_owned() + } + + fn json_schema(_gen: &mut SchemaGenerator) -> Schema { + SchemaObject { + instance_type: Some(InstanceType::String.into()), + + metadata: Some(Box::new(Metadata { + title: Some("ULID".into()), + description: Some("A ULID as per https://github.com/ulid/spec".into()), + examples: vec![ + "01ARZ3NDEKTSV4RRFFQ69G5FAV".into(), + "01J41912SC8VGAQDD50F6APK91".into(), + ], + ..Metadata::default() + })), + + string: Some(Box::new(StringValidation { + pattern: Some(r"^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$".into()), + ..StringValidation::default() + })), + + ..SchemaObject::default() + } + .into() + } +} diff --git a/docs/api/spec.json b/docs/api/spec.json index c94599e3..23f10d96 100644 --- a/docs/api/spec.json +++ b/docs/api/spec.json @@ -29,10 +29,8 @@ "description": "Retrieve the items before the given ID", "schema": { "description": "Retrieve the items before the given ID", - "type": [ - "string", - "null" - ] + "$ref": "#/components/schemas/ULID", + "nullable": true }, "style": "form" }, @@ -42,10 +40,8 @@ "description": "Retrieve the items after the given ID", "schema": { "description": "Retrieve the items after the given ID", - "type": [ - "string", - "null" - ] + "$ref": "#/components/schemas/ULID", + "nullable": true }, "style": "form" }, @@ -55,12 +51,10 @@ "description": "Retrieve the first N items", "schema": { "description": "Retrieve the first N items", - "type": [ - "integer", - "null" - ], + "type": "integer", "format": "uint", - "minimum": 1.0 + "minimum": 1.0, + "nullable": true }, "style": "form" }, @@ -70,12 +64,10 @@ "description": "Retrieve the last N items", "schema": { "description": "Retrieve the last N items", - "type": [ - "integer", - "null" - ], + "type": "integer", "format": "uint", - "minimum": 1.0 + "minimum": 1.0, + "nullable": true }, "style": "form" }, @@ -83,10 +75,8 @@ "in": "query", "name": "filter[can_request_admin]", "schema": { - "type": [ - "boolean", - "null" - ] + "type": "boolean", + "nullable": true }, "style": "form" }, @@ -94,14 +84,8 @@ "in": "query", "name": "filter[status]", "schema": { - "anyOf": [ - { - "$ref": "#/components/schemas/UserStatus" - }, - { - "type": "null" - } - ] + "$ref": "#/components/schemas/UserStatus", + "nullable": true }, "style": "form" } @@ -182,13 +166,10 @@ { "in": "path", "name": "id", - "description": "A ULID as per https://github.com/ulid/spec", "required": true, "schema": { - "title": "ULID", - "description": "A ULID as per https://github.com/ulid/spec", - "type": "string", - "pattern": "^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$" + "title": "The ID of the resource", + "$ref": "#/components/schemas/ULID" }, "style": "simple" } @@ -340,56 +321,50 @@ "properties": { "page[before]": { "description": "Retrieve the items before the given ID", - "type": [ - "string", - "null" - ] + "$ref": "#/components/schemas/ULID", + "nullable": true }, "page[after]": { "description": "Retrieve the items after the given ID", - "type": [ - "string", - "null" - ] + "$ref": "#/components/schemas/ULID", + "nullable": true }, "page[first]": { "description": "Retrieve the first N items", - "type": [ - "integer", - "null" - ], + "type": "integer", "format": "uint", - "minimum": 1.0 + "minimum": 1.0, + "nullable": true }, "page[last]": { "description": "Retrieve the last N items", - "type": [ - "integer", - "null" - ], + "type": "integer", "format": "uint", - "minimum": 1.0 + "minimum": 1.0, + "nullable": true } } }, + "ULID": { + "title": "ULID", + "description": "A ULID as per https://github.com/ulid/spec", + "examples": [ + "01ARZ3NDEKTSV4RRFFQ69G5FAV", + "01J41912SC8VGAQDD50F6APK91" + ], + "type": "string", + "pattern": "^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$" + }, "FilterParams": { "type": "object", "properties": { "filter[can_request_admin]": { - "type": [ - "boolean", - "null" - ] + "type": "boolean", + "nullable": true }, "filter[status]": { - "anyOf": [ - { - "$ref": "#/components/schemas/UserStatus" - }, - { - "type": "null" - } - ] + "$ref": "#/components/schemas/UserStatus", + "nullable": true } } }, @@ -467,7 +442,7 @@ }, "id": { "description": "The ID of the resource", - "type": "string" + "$ref": "#/components/schemas/ULID" }, "attributes": { "description": "The attributes of the resource", @@ -499,11 +474,9 @@ }, "locked_at": { "description": "When the user was locked. If null, the user is not locked.", - "type": [ - "string", - "null" - ], - "format": "date-time" + "type": "string", + "format": "date-time", + "nullable": true }, "can_request_admin": { "description": "Whether the user can request admin privileges.", @@ -547,17 +520,13 @@ }, "next": { "description": "The link to the next page of results\n\nOnly present if there is a next page", - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true }, "prev": { "description": "The link to the previous page of results\n\nOnly present if there is a previous page", - "type": [ - "string", - "null" - ] + "type": "string", + "nullable": true } } }, @@ -597,10 +566,8 @@ ], "properties": { "id": { - "title": "ULID", - "description": "A ULID as per https://github.com/ulid/spec", - "type": "string", - "pattern": "^[0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26}$" + "title": "The ID of the resource", + "$ref": "#/components/schemas/ULID" } } },