diff --git a/crates/oxidetalis_core/src/types/cipher.rs b/crates/oxidetalis_core/src/types/cipher.rs index 8aa8aa5..b2f50d8 100644 --- a/crates/oxidetalis_core/src/types/cipher.rs +++ b/crates/oxidetalis_core/src/types/cipher.rs @@ -24,15 +24,6 @@ use std::{fmt, str::FromStr}; use base58::{FromBase58, ToBase58}; -#[cfg(feature = "openapi")] -use salvo_oapi::{ - schema::{ - Schema as OapiSchema, - SchemaFormat as OapiSchemaFormat, - SchemaType as OapiSchemaType, - }, - ToSchema, -}; use crate::cipher::{hmac_sha256, CipherError}; @@ -206,23 +197,3 @@ impl From<[u8; 56]> for Signature { } } } - -#[cfg(feature = "openapi")] -impl ToSchema for PublicKey { - fn to_schema(_components: &mut salvo_oapi::Components) -> salvo_oapi::RefOr { - salvo_oapi::Object::new() - .schema_type(OapiSchemaType::String) - .format(OapiSchemaFormat::Custom("base58".to_owned())) - .into() - } -} - -#[cfg(feature = "openapi")] -impl ToSchema for Signature { - fn to_schema(_components: &mut salvo_oapi::Components) -> salvo_oapi::RefOr { - salvo_oapi::Object::new() - .schema_type(OapiSchemaType::String) - .format(OapiSchemaFormat::Custom("hex".to_owned())) - .into() - } -} diff --git a/crates/oxidetalis_core/src/types/impl_openapi.rs b/crates/oxidetalis_core/src/types/impl_openapi.rs new file mode 100644 index 0000000..b22fbfe --- /dev/null +++ b/crates/oxidetalis_core/src/types/impl_openapi.rs @@ -0,0 +1,162 @@ +// OxideTalis Messaging Protocol homeserver core implementation +// Copyright (C) 2024 Awiteb , OxideTalis Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! OpenAPI schema for some core types. + +use std::str::FromStr; + +use salvo_core::{extract::Metadata as ExtractMetadata, http::StatusError, Extractible, Request}; +use salvo_oapi::{ + schema::{ + Schema as OapiSchema, + SchemaFormat as OapiSchemaFormat, + SchemaType as OapiSchemaType, + }, + Components as OapiComponents, + EndpointArgRegister, + Parameter, + ParameterIn, + Parameters, + ToParameters, + ToSchema, +}; + +use super::{PublicKey as CorePublicKey, Signature}; + +impl ToSchema for CorePublicKey { + fn to_schema(_components: &mut salvo_oapi::Components) -> salvo_oapi::RefOr { + salvo_oapi::Object::new() + .name(crate::PUBLIC_KEY_HEADER) + .description("User's public key") + .schema_type(OapiSchemaType::String) + .format(OapiSchemaFormat::Custom("base58".to_owned())) + .required(crate::PUBLIC_KEY_HEADER) + // A 33-byte base58 string can be either 44 or 45 characters long + .example("rW8FMG5D75NVNJV3Wd498dEh65BgUuhwY1Yk5zYJPpRe".into()) + .max_length(45) + .min_length(44) + .into() + } +} + +impl ToSchema for Signature { + fn to_schema(_components: &mut salvo_oapi::Components) -> salvo_oapi::RefOr { + salvo_oapi::Object::new() + .name(crate::SIGNATURE_HEADER) + .description("Signature of the request") + .schema_type(OapiSchemaType::String) + .format(OapiSchemaFormat::Custom("hex".to_owned())) + .required(crate::SIGNATURE_HEADER) + // 56 bytes in hex (valid signature) + .example("0".repeat(112).into()) + .max_length(112) + .min_length(112) + .into() + } +} + +impl<'ex> Extractible<'ex> for CorePublicKey { + fn metadata() -> &'ex ExtractMetadata { + static METADATA: ExtractMetadata = ExtractMetadata::new(""); + &METADATA + } + + #[allow(refining_impl_trait)] + async fn extract(req: &'ex mut Request) -> Result { + extract_header(req, crate::PUBLIC_KEY_HEADER).and_then(|public_key| { + CorePublicKey::from_str(public_key).map_err(|err| { + StatusError::bad_request() + .brief("Invalid public key") + .cause(err.to_string()) + }) + }) + } + + #[allow(refining_impl_trait)] + async fn extract_with_arg(req: &'ex mut Request, _: &str) -> Result { + Self::extract(req).await + } +} + +impl EndpointArgRegister for CorePublicKey { + fn register(components: &mut OapiComponents, operation: &mut salvo_oapi::Operation, _: &str) { + operation.parameters.insert( + Parameter::new(crate::PUBLIC_KEY_HEADER) + .parameter_in(ParameterIn::Header) + .required(true) + .description("User's public key") + .example("2BiUSWkJUy5bcdJB8qszq9K6a5EXVHvK41vQWZVkUBUM8".into()) + .schema(CorePublicKey::to_schema(components)), + ) + } +} + +impl<'ex> Extractible<'ex> for Signature { + fn metadata() -> &'ex ExtractMetadata { + unreachable!( + " + `Extractible` is required to implement `ToParameters` for `Signature`, but \ + Salvo does not need it actually, see https://github.com/salvo-rs/salvo/issues/838" + ) + } + + #[allow(refining_impl_trait)] + async fn extract(_: &'ex mut Request) -> Result { + unreachable!( + " + `Extractible` is required to implement `ToParameters` for `Signature`, but \ + Salvo does not need it actually, see https://github.com/salvo-rs/salvo/issues/838" + ) + } +} + +impl ToParameters<'_> for Signature { + fn to_parameters(components: &mut OapiComponents) -> Parameters { + Parameters::new().parameter( + Parameter::new(crate::SIGNATURE_HEADER) + .parameter_in(ParameterIn::Header) + .required(true) + .description("Signature of the request") + .example("0".repeat(112).into()) + .schema(Self::to_schema(components)), + ) + } +} + +fn extract_header<'req>(req: &'req Request, name: &str) -> Result<&'req str, StatusError> { + req.headers() + .get(name) + .map(|v| { + v.to_str().map_err(|_| { + StatusError::bad_request() + .brief("Invalid header value") + .cause("Header value must be a valid ascii string") + }) + }) + .transpose()? + .ok_or_else(|| { + StatusError::bad_request() + .brief(format!("Could not find {name} in headers")) + .cause(format!( + "{name} is required to authenication and authorization" + )) + }) +} diff --git a/crates/oxidetalis_core/src/types/mod.rs b/crates/oxidetalis_core/src/types/mod.rs index 7635eb3..a2257f1 100644 --- a/crates/oxidetalis_core/src/types/mod.rs +++ b/crates/oxidetalis_core/src/types/mod.rs @@ -22,6 +22,8 @@ //! Oxidetalis server types mod cipher; +#[cfg(feature = "openapi")] +mod impl_openapi; #[cfg(feature = "sea-orm")] mod impl_sea_orm; #[cfg(feature = "serde")]