diff --git a/assets/openapi.json b/assets/openapi.json index f1ed8cb70..49e025f7f 100644 --- a/assets/openapi.json +++ b/assets/openapi.json @@ -41,45 +41,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "Represents a response object for all REST endpoints.", - "required": ["success"], - "properties": { - "data": { - "type": "object", - "description": "Response object for the `GET /` REST controller.", - "required": [ - "message", - "tagline", - "docs" - ], - "properties": { - "docs": { - "type": "string", - "description": "Documentation URL for this generic entrypoint response." - }, - "message": { - "type": "string", - "description": "The message, which will always be \"Hello, world!\"" - }, - "tagline": { - "type": "string", - "description": "You know, for Helm charts?" - } - } - }, - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Error" - }, - "description": "If the request failed, this is a list of errors as a \"stacktrace.\" `success` is always\n*false*; if not the case, blame it on Noel." - }, - "success": { - "type": "boolean", - "description": "Was the request a success or not?" - } - } + "$ref": "#/components/schemas/MainResponse" } } } @@ -231,37 +193,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "Represents a response object for all REST endpoints.", - "required": ["success"], - "properties": { - "data": { - "type": "object", - "description": "Generic entrypoint message for any API route like `/users`.", - "required": ["message", "docs"], - "properties": { - "docs": { - "type": "string", - "description": "URI to the documentation for this entrypoint." - }, - "message": { - "type": "string", - "description": "Humane message to greet you." - } - } - }, - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Error" - }, - "description": "If the request failed, this is a list of errors as a \"stacktrace.\" `success` is always\n*false*; if not the case, blame it on Noel." - }, - "success": { - "type": "boolean", - "description": "Was the request a success or not?" - } - } + "$ref": "#/components/schemas/EntrypointResponse" } } } @@ -285,79 +217,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "Represents a response object for all REST endpoints.", - "required": ["success"], - "properties": { - "data": { - "type": "object", - "required": [ - "created_at", - "updated_at", - "username", - "id" - ], - "properties": { - "admin": { - "type": "boolean", - "description": "Whether if this User is an Administrator of this instance", - "readOnly": true - }, - "avatar_hash": { - "type": ["string", "null"], - "description": "Unique hash that identifies the user's avatar that they uploaded via the REST API." - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "Date of when this user was created. This uses the host system's local time instead\nof UTC.", - "readOnly": true - }, - "description": { - "type": ["string", "null"], - "description": "Short description about this user." - }, - "gravatar_email": { - "type": ["string", "null"], - "description": "Email address that is the Gravatar email to which we should use the user's avatar." - }, - "id": { - "$ref": "#/components/schemas/Ulid", - "description": "Unique identifier to locate this user via the REST API." - }, - "name": { - "type": ["string", "null"], - "description": "Display name for this user, it should be displayed as '{name} (@{username})' or just '@{username}' if there is no display name" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "Date of when the server has last updated this user's metadata", - "readOnly": true - }, - "username": { - "$ref": "#/components/schemas/Name", - "description": "Name of this user that can be identified easier." - }, - "verified_publisher": { - "type": "boolean", - "description": "whether or not if this user is considered a verified publisher.", - "readOnly": true - } - } - }, - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Error" - }, - "description": "If the request failed, this is a list of errors as a \"stacktrace.\" `success` is always\n*false*; if not the case, blame it on Noel." - }, - "success": { - "type": "boolean", - "description": "Was the request a success or not?" - } - } + "$ref": "#/components/schemas/UserResponse" } } } @@ -406,45 +266,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "Represents a response object for all REST endpoints.", - "required": ["success"], - "properties": { - "data": { - "type": "object", - "description": "Response object for the `GET /` REST controller.", - "required": [ - "message", - "tagline", - "docs" - ], - "properties": { - "docs": { - "type": "string", - "description": "Documentation URL for this generic entrypoint response." - }, - "message": { - "type": "string", - "description": "The message, which will always be \"Hello, world!\"" - }, - "tagline": { - "type": "string", - "description": "You know, for Helm charts?" - } - } - }, - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Error" - }, - "description": "If the request failed, this is a list of errors as a \"stacktrace.\" `success` is always\n*false*; if not the case, blame it on Noel." - }, - "success": { - "type": "boolean", - "description": "Was the request a success or not?" - } - } + "$ref": "#/components/schemas/MainResponse" } } } @@ -596,37 +418,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "Represents a response object for all REST endpoints.", - "required": ["success"], - "properties": { - "data": { - "type": "object", - "description": "Generic entrypoint message for any API route like `/users`.", - "required": ["message", "docs"], - "properties": { - "docs": { - "type": "string", - "description": "URI to the documentation for this entrypoint." - }, - "message": { - "type": "string", - "description": "Humane message to greet you." - } - } - }, - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Error" - }, - "description": "If the request failed, this is a list of errors as a \"stacktrace.\" `success` is always\n*false*; if not the case, blame it on Noel." - }, - "success": { - "type": "boolean", - "description": "Was the request a success or not?" - } - } + "$ref": "#/components/schemas/EntrypointResponse" } } } @@ -650,79 +442,7 @@ "content": { "application/json": { "schema": { - "type": "object", - "description": "Represents a response object for all REST endpoints.", - "required": ["success"], - "properties": { - "data": { - "type": "object", - "required": [ - "created_at", - "updated_at", - "username", - "id" - ], - "properties": { - "admin": { - "type": "boolean", - "description": "Whether if this User is an Administrator of this instance", - "readOnly": true - }, - "avatar_hash": { - "type": ["string", "null"], - "description": "Unique hash that identifies the user's avatar that they uploaded via the REST API." - }, - "created_at": { - "type": "string", - "format": "date-time", - "description": "Date of when this user was created. This uses the host system's local time instead\nof UTC.", - "readOnly": true - }, - "description": { - "type": ["string", "null"], - "description": "Short description about this user." - }, - "gravatar_email": { - "type": ["string", "null"], - "description": "Email address that is the Gravatar email to which we should use the user's avatar." - }, - "id": { - "$ref": "#/components/schemas/Ulid", - "description": "Unique identifier to locate this user via the REST API." - }, - "name": { - "type": ["string", "null"], - "description": "Display name for this user, it should be displayed as '{name} (@{username})' or just '@{username}' if there is no display name" - }, - "updated_at": { - "type": "string", - "format": "date-time", - "description": "Date of when the server has last updated this user's metadata", - "readOnly": true - }, - "username": { - "$ref": "#/components/schemas/Name", - "description": "Name of this user that can be identified easier." - }, - "verified_publisher": { - "type": "boolean", - "description": "whether or not if this user is considered a verified publisher.", - "readOnly": true - } - } - }, - "errors": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Error" - }, - "description": "If the request failed, this is a list of errors as a \"stacktrace.\" `success` is always\n*false*; if not the case, blame it on Noel." - }, - "success": { - "type": "boolean", - "description": "Was the request a success or not?" - } - } + "$ref": "#/components/schemas/UserResponse" } } } @@ -1165,7 +885,7 @@ ], "description": "`Duration` is represented as a span of time, usually for system timeouts. `charted-server` supports passing in a unsigned 64-bot integer (represented in milliseconds) or with a string literal (i.e, `1s`) to represent time." }, - "EntrypointResponse": { + "Entrypoint": { "type": "object", "description": "Generic entrypoint message for any API route like `/users`.", "required": ["message", "docs"], @@ -1180,6 +900,23 @@ } } }, + "EntrypointResponse": { + "type": "object", + "description": "Response datatype for the `Entrypoint` type", + "required": ["success"], + "properties": { + "data": { "$ref": "#/components/schemas/Entrypoint" }, + "errors": { + "type": "array", + "items": { "$ref": "#/components/schemas/Error" }, + "description": "If the request failed, this is a list of errors as a \"stacktrace.\" `success` is always\n*false*; if not the case, blame it on Noel." + }, + "success": { + "type": "boolean", + "description": "Was the request a success or not?" + } + } + }, "Error": { "type": "object", "description": "Error that happened when going through a request.", @@ -1271,6 +1008,80 @@ } } }, + "Info": { + "type": "object", + "description": "Represents the response for the `GET /info` REST handler.", + "required": [ + "distribution", + "commit_sha", + "build_date", + "product", + "version", + "vendor" + ], + "properties": { + "build_date": { + "type": "string", + "description": "Build date in RFC3339 format" + }, + "commit_sha": { + "type": "string", + "description": "The commit hash from the Git repository." + }, + "distribution": { + "$ref": "#/components/schemas/Distribution", + "description": "The distribution the server is running off from" + }, + "product": { + "type": "string", + "description": "Product name. Will always be \"charted-server\"" + }, + "vendor": { + "type": "string", + "description": "Vendor of charted-server, will always be \"Noelware, LLC.\"" + }, + "version": { + "type": "string", + "description": "Valid SemVer 2 of the current version of this instance" + } + } + }, + "Main": { + "type": "object", + "description": "Response object for the `GET /` REST controller.", + "required": ["message", "tagline", "docs"], + "properties": { + "docs": { + "type": "string", + "description": "Documentation URL for this generic entrypoint response." + }, + "message": { + "type": "string", + "description": "The message, which will always be \"Hello, world!\"" + }, + "tagline": { + "type": "string", + "description": "You know, for Helm charts?" + } + } + }, + "MainResponse": { + "type": "object", + "description": "Response datatype for the `Main` type", + "required": ["success"], + "properties": { + "data": { "$ref": "#/components/schemas/Main" }, + "errors": { + "type": "array", + "items": { "$ref": "#/components/schemas/Error" }, + "description": "If the request failed, this is a list of errors as a \"stacktrace.\" `success` is always\n*false*; if not the case, blame it on Noel." + }, + "success": { + "type": "boolean", + "description": "Was the request a success or not?" + } + } + }, "Name": { "type": "string", "description": "Valid UTF-8 string that is used to identify a resource from the REST API in a humane fashion. This is meant to help identify a resource without trying to figure out how to calculate their ID.", @@ -1792,6 +1603,23 @@ } } }, + "UserResponse": { + "type": "object", + "description": "Response datatype for the `User` type", + "required": ["success"], + "properties": { + "data": { "$ref": "#/components/schemas/User" }, + "errors": { + "type": "array", + "items": { "$ref": "#/components/schemas/Error" }, + "description": "If the request failed, this is a list of errors as a \"stacktrace.\" `success` is always\n*false*; if not the case, blame it on Noel." + }, + "success": { + "type": "boolean", + "description": "Was the request a success or not?" + } + } + }, "Version": { "type": "string", "description": "Type that represents a semantic version (https://semver.org).", diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index 83034b27b..e5b3a2dc6 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![feature(never_type, decl_macro)] +#![feature(never_type, decl_macro, entry_insert)] mod state; pub use state::*; diff --git a/crates/server/src/openapi.rs b/crates/server/src/openapi.rs index f371d1d6d..4f6292cb0 100644 --- a/crates/server/src/openapi.rs +++ b/crates/server/src/openapi.rs @@ -31,7 +31,8 @@ use utoipa::{ modifiers( &UpdatePathsToIncludeDefaultVersion, &IncludeErrorProneDatatypes, - &SecuritySchemes + &SecuritySchemes, + &ResponseModifiers, ), info( title = "charted-server", @@ -94,7 +95,9 @@ use utoipa::{ charted_types::payloads::user::PatchUserPayload, // ==== Response Datatypes ==== - crate::routing::v1::EntrypointResponse, + crate::routing::v1::info::Info, + crate::routing::v1::main::Main, + crate::routing::v1::Entrypoint, // ==== Helm ==== charted_types::helm::StringOrImportValue, diff --git a/crates/server/src/openapi/modifiers.rs b/crates/server/src/openapi/modifiers.rs index a70af595f..f50e8a2aa 100644 --- a/crates/server/src/openapi/modifiers.rs +++ b/crates/server/src/openapi/modifiers.rs @@ -14,11 +14,11 @@ // limitations under the License. use charted_core::api::Version; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap, HashSet}; use utoipa::{ openapi::{ security::{ApiKey, ApiKeyValue, HttpAuthScheme, HttpBuilder, SecurityScheme}, - ComponentsBuilder, OpenApi, + ComponentsBuilder, OpenApi, Ref, RefOr, Schema, }, Modify, }; @@ -139,22 +139,131 @@ impl Modify for SecuritySchemes { } } +/// [Modifier][Modify] that replaces `Response_` as `Response`. +pub struct ResponseModifiers; + +// While the implementation can be cleaned and optimized, for now it works. +impl Modify for ResponseModifiers { + fn modify(&self, openapi: &mut OpenApi) { + let mut components = openapi.components.take().unwrap(); + + // 1. First, we need to update all `Response_<>` with `<>Response` and update the + // `data` field to be the type that we want + let mut scheduled_to_be_removed = HashSet::new(); + let mut scheduled_to_be_created = HashMap::new(); + let schemas = components.schemas.clone(); + + for (key, schema) in schemas + .iter() + .filter_map(|(key, schema)| key.starts_with("Response_").then_some((key, schema))) + { + // First, we need to schedule the deletion of `key` since we will + // no longer be using it + scheduled_to_be_removed.insert(key.as_str()); + + // Update the schema's description + // + // We only expect `"type": "object"` as response types are only objects + // and cannot be anything else + let RefOr::T(Schema::Object(mut object)) = schema.clone() else { + unreachable!(); + }; + + let (_, mut ty) = key.split_once('_').unwrap(); + if ty.ends_with("Response") { + ty = ty.trim_end_matches("Response"); + } + + object.description = Some(format!("Response datatype for the `{ty}` type")); + assert!(object.properties.remove("data").is_some()); + + object + .properties + .insert("data".into(), RefOr::Ref(Ref::from_schema_name(ty))); + + scheduled_to_be_created.insert(format!("{ty}Response"), RefOr::T(Schema::Object(object))); + } + + scheduled_to_be_removed + .iter() + .map(|x| components.schemas.remove(*x)) + .for_each(drop); + + scheduled_to_be_created + .iter() + .map(|(key, value)| components.schemas.insert(key.to_owned(), value.clone())) + .for_each(drop); + + // 2. Now, we need to go to every path and check if there is a ref + // with the `Response_<>` suffix + { + for item in openapi.paths.paths.values_mut() { + macro_rules! do_update { + ($kind:ident as $op:expr) => { + if let Some(ref mut op) = $op { + for resp in op + .responses + .responses + .values_mut() + .filter_map(|resp| match resp { + RefOr::T(resp) => Some(resp), + _ => None, + }) + { + for content in resp.content.values_mut() { + if let Some(RefOr::Ref(ref_)) = content.schema.as_ref() { + if ref_.ref_location.contains("Response_") { + let reference = ref_.ref_location.split("/").last().unwrap(); + + let (_, ty) = reference.split_once('_').unwrap(); + assert!(!ty.contains("_")); + + content.schema = + Some(RefOr::Ref(Ref::from_schema_name(format!("{ty}Response")))); + } + } + } + } + + item.$kind = ($op).clone(); + } + }; + } + + do_update!(get as item.get); + do_update!(put as item.put); + do_update!(head as item.head); + do_update!(post as item.post); + do_update!(patch as item.patch); + do_update!(trace as item.trace); + do_update!(delete as item.delete); + do_update!(delete as item.delete); + do_update!(options as item.options); + } + } + + openapi.components = Some(components); + } +} + #[cfg(test)] mod tests { use super::*; - use utoipa::OpenApi; - - /// a dummy route that exists - #[utoipa::path(get, path = "/v1/weow")] - #[allow(dead_code)] - fn dummy_route() {} - - #[utoipa::path(get, path = "/v1")] - #[allow(dead_code)] - fn other_dummy_route() {} + use charted_core::api; + use charted_types::User; + use utoipa::{openapi::HttpMethod, OpenApi}; #[test] fn update_paths_to_include_default_api_version() { + // a dummy route that exists + #[utoipa::path(get, path = "/v1/weow")] + #[allow(dead_code)] + fn dummy_route() {} + + #[utoipa::path(get, path = "/v1")] + #[allow(dead_code)] + fn other_dummy_route() {} + #[derive(OpenApi)] #[openapi(paths(dummy_route, other_dummy_route), modifiers(&UpdatePathsToIncludeDefaultVersion))] struct Document; @@ -164,4 +273,60 @@ mod tests { assert_eq!(&paths, &["/", "/v1", "/v1/weow", "/weow"]); } + + // This test combats using `Response_<>` as `<>Response`, where `<>` is + // the type that is registered as a schema. + #[test] + fn update_response_types() { + #[utoipa::path(get, path = "/", responses((status = 200, body = api::Response)))] + #[allow(unused)] + fn test_path() {} + + #[derive(OpenApi)] + #[openapi(paths(test_path), modifiers(&ResponseModifiers))] + struct Document; + + let openapi = Document::openapi(); + + // Check that `UserResponse` has the modified description and reference + { + let components = openapi.components.unwrap(); + let RefOr::T(Schema::Object(object)) = components.schemas.get("UserResponse").unwrap() else { + unreachable!(); + }; + + assert_eq!( + object.description, + Some("Response datatype for the `User` type".to_string()) + ); + + let Some(RefOr::Ref(ref_)) = object.properties.get("data") else { + unreachable!(); + }; + + assert_eq!(ref_.ref_location, "#/components/schemas/User"); + } + + // Check if `GET /` has the updated response type + { + let paths = openapi.paths.clone(); + let Some(path) = paths.get_path_operation("/", HttpMethod::Get) else { + unreachable!(); + }; + + let Some(RefOr::T(resp)) = path.responses.responses.get("200") else { + unreachable!(); + }; + + let Some(content) = resp.content.get("application/json") else { + unreachable!(); + }; + + let Some(RefOr::Ref(ref ref_)) = content.schema else { + unreachable!() + }; + + assert_eq!(ref_.ref_location, "#/components/responses/UserResponse"); + } + } } diff --git a/crates/server/src/routing/v1/info.rs b/crates/server/src/routing/v1/info.rs index d8e8f8a0c..afb508b81 100644 --- a/crates/server/src/routing/v1/info.rs +++ b/crates/server/src/routing/v1/info.rs @@ -20,7 +20,7 @@ use utoipa::ToSchema; /// Represents the response for the `GET /info` REST handler. #[derive(Serialize, ToSchema)] -pub struct InfoResponse { +pub struct Info { /// The distribution the server is running off from pub distribution: Distribution, @@ -40,9 +40,9 @@ pub struct InfoResponse { pub vendor: &'static str, } -impl Default for InfoResponse { - fn default() -> InfoResponse { - InfoResponse { +impl Default for Info { + fn default() -> Self { + Self { distribution: Distribution::detect(), commit_sha: COMMIT_HASH, build_date: BUILD_DATE, @@ -63,12 +63,12 @@ impl Default for InfoResponse { ( status = 200, description = "Successful response", - body = inline(api::Response), + body = api::Response, content_type = "application/json" ) ) )] #[cfg_attr(debug_assertions, axum::debug_handler)] -pub async fn info() -> api::Response { +pub async fn info() -> api::Response { api::from_default(StatusCode::OK) } diff --git a/crates/server/src/routing/v1/main.rs b/crates/server/src/routing/v1/main.rs index 53f8ebca3..c2763b74a 100644 --- a/crates/server/src/routing/v1/main.rs +++ b/crates/server/src/routing/v1/main.rs @@ -23,7 +23,7 @@ use utoipa::{ /// Response object for the `GET /` REST controller. #[derive(Serialize, ToSchema)] -pub struct MainResponse { +pub struct Main { /// The message, which will always be "Hello, world!" pub message: &'static str, @@ -34,9 +34,9 @@ pub struct MainResponse { pub docs: String, } -impl Default for MainResponse { +impl Default for Main { fn default() -> Self { - MainResponse { + Self { message: "Hello, world! 👋", tagline: "You know, for Helm charts?", docs: format!("https://charts.noelware.org/docs/server/{VERSION}"), @@ -44,10 +44,10 @@ impl Default for MainResponse { } } -impl<'r> ToResponse<'r> for MainResponse { +impl<'r> ToResponse<'r> for Main { fn response() -> (&'r str, RefOr) { ( - "MainResponse", + "Main", RefOr::T( ResponseBuilder::new() .description("Response for the `/` REST handler") @@ -73,12 +73,12 @@ impl<'r> ToResponse<'r> for MainResponse { ( status = 200, description = "Successful response", - body = inline(api::Response), + body = api::Response
, content_type = "application/json" ) ) )] #[cfg_attr(debug_assertions, axum::debug_handler)] -pub async fn main() -> api::Response { +pub async fn main() -> api::Response
{ api::from_default(StatusCode::OK) } diff --git a/crates/server/src/routing/v1/mod.rs b/crates/server/src/routing/v1/mod.rs index feb1cb28e..5240ab90f 100644 --- a/crates/server/src/routing/v1/mod.rs +++ b/crates/server/src/routing/v1/mod.rs @@ -30,7 +30,7 @@ use utoipa::ToSchema; /// Generic entrypoint message for any API route like `/users`. #[derive(Serialize, ToSchema)] -pub struct EntrypointResponse { +pub struct Entrypoint { /// Humane message to greet you. pub message: Cow<'static, str>, @@ -38,7 +38,7 @@ pub struct EntrypointResponse { pub docs: Cow<'static, str>, } -impl EntrypointResponse { +impl Entrypoint { pub fn new(entity: impl AsRef) -> Self { let entity = entity.as_ref(); Self { diff --git a/crates/server/src/routing/v1/user/mod.rs b/crates/server/src/routing/v1/user/mod.rs index e210c392d..30eff0b70 100644 --- a/crates/server/src/routing/v1/user/mod.rs +++ b/crates/server/src/routing/v1/user/mod.rs @@ -17,7 +17,7 @@ pub mod avatars; pub mod repositories; pub mod sessions; -use super::EntrypointResponse; +use super::Entrypoint; use crate::{ extract::{Json, Path}, hash_password, @@ -63,14 +63,14 @@ pub fn create_router() -> Router { ( status = 200, description = "Entrypoint response", - body = inline(api::Response), + body = api::Response, content_type = "application/json" ) ) )] #[cfg_attr(debug_assertions, axum::debug_handler)] -pub async fn main() -> api::Response { - api::ok(StatusCode::OK, EntrypointResponse::new("Users")) +pub async fn main() -> api::Response { + api::ok(StatusCode::OK, Entrypoint::new("Users")) } #[utoipa::path( @@ -87,7 +87,7 @@ pub async fn main() -> api::Response { ( status = 201, description = "User has been created", - body = inline(api::Response), + body = api::Response, content_type = "application/json" ), ( @@ -166,13 +166,12 @@ pub async fn create_user( // Check if we already have this `User` by their username { - let uname = &username; let exists = connection!(@raw conn { PostgreSQL(conn) => conn.build_transaction().read_only().run::<_, eyre::Report, _>(|txn| { - use postgresql::users::{dsl::*, table}; + use postgresql::users::{dsl, table}; use diesel::pg::Pg; - match table.select(>::as_select()).filter(username.eq(uname)).first(txn) { + match table.select(>::as_select()).filter(dsl::username.eq(&username)).first(txn) { Ok(_) => Ok(true), Err(diesel::result::Error::NotFound) => Ok(false), Err(e) => Err(eyre::Report::from(e)) @@ -180,10 +179,10 @@ pub async fn create_user( }); SQLite(conn) => conn.immediate_transaction(|txn| { - use sqlite::users::{dsl::*, table}; + use sqlite::users::{dsl, table}; use diesel::sqlite::Sqlite; - match table.select(>::as_select()).filter(username.eq(uname)).first(txn) { + match table.select(>::as_select()).filter(dsl::username.eq(&username)).first(txn) { Ok(_) => Ok(true), Err(diesel::result::Error::NotFound) => Ok(false), Err(e) => Err(eyre::Report::from(e)) @@ -200,7 +199,7 @@ pub async fn create_user( ( api::ErrorCode::EntityAlreadyExists, "a user with `username` already exists", - json!({"username":uname.as_str()}), + json!({"username":username.as_str()}), ), )); } @@ -208,13 +207,12 @@ pub async fn create_user( // Check if we already have this `User` by their email address { - let em = &email; let exists = connection!(@raw conn { PostgreSQL(conn) => conn.build_transaction().read_only().run::<_, eyre::Report, _>(|txn| { - use postgresql::users::{dsl::*, table}; + use postgresql::users::{dsl, table}; use diesel::pg::Pg; - match table.select(>::as_select()).filter(email.eq(em)).first(txn) { + match table.select(>::as_select()).filter(dsl::email.eq(&email)).first(txn) { Ok(_) => Ok(true), Err(diesel::result::Error::NotFound) => Ok(false), Err(e) => Err(eyre::Report::from(e)) @@ -222,10 +220,10 @@ pub async fn create_user( }); SQLite(conn) => conn.immediate_transaction(|txn| { - use sqlite::users::{dsl::*, table}; + use sqlite::users::{dsl, table}; use diesel::sqlite::Sqlite; - match table.select(>::as_select()).filter(email.eq(em)).first(txn) { + match table.select(>::as_select()).filter(dsl::email.eq(&email)).first(txn) { Ok(_) => Ok(true), Err(diesel::result::Error::NotFound) => Ok(false), Err(e) => Err(eyre::Report::from(e)) @@ -234,7 +232,7 @@ pub async fn create_user( }) .inspect_err(|e| { sentry_eyre::capture_report(e); - error!(user.email = em, error = %e, "failed to query user by email"); + error!(user.email = email, error = %e, "failed to query user by email"); }) .map_err(|_| api::internal_server_error())?; @@ -244,7 +242,7 @@ pub async fn create_user( ( api::ErrorCode::EntityAlreadyExists, "a user with the `email` given already exists", - json!({"email":em}), + json!({"email":email}), ), )); } @@ -343,7 +341,7 @@ pub async fn create_user( ( status = 200, description = "A single user found", - body = inline(api::Response), + body = api::Response, content_type = "application/json" ), (