feat: Chat request implementation #14

Manually merged
awiteb merged 55 commits from awiteb/chat-request-and-response into master 2024-07-18 14:21:39 +02:00 AGit
3 changed files with 143 additions and 0 deletions
Showing only changes of commit d065d7ea95 - Show all commits

View file

@ -30,6 +30,7 @@ mod extensions;
mod macros; mod macros;
mod middlewares; mod middlewares;
mod nonce; mod nonce;
mod parameters;
mod routes; mod routes;
mod schemas; mod schemas;
mod utils; mod utils;

View file

@ -0,0 +1,21 @@
// OxideTalis Messaging Protocol homeserver implementation
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
//! Set of route parameters for the API
mod pagination;
pub use pagination::*;

View file

@ -0,0 +1,121 @@
// OxideTalis Messaging Protocol homeserver implementation
// Copyright (C) 2024 OxideTalis Developers <otmp@4rs.nl>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://gnu.org/licenses/agpl-3.0>.
//! Pagination parameters for the API
use std::{
fmt,
num::{NonZeroU32, NonZeroU8},
str::FromStr,
};
use salvo::{
extract::Metadata as ExtractMetadata,
oapi::{
Components as OapiComponents,
Object,
Parameter,
ParameterIn,
Parameters,
SchemaType,
ToParameters,
},
Extractible,
Request,
};
use serde_json::json;
use crate::routes::{ApiError, ApiResult};
#[derive(Debug)]
pub struct Pagination {
awiteb marked this conversation as resolved
Review

We can add EndpointArgRegister implementation and thus we can use it in the arg directly

async fn user_whitelist(
    req: &mut Request,
    depot: &mut Depot,
    pagination: Pagination,
) -> ApiResult<Json<Vec<WhiteListedUser>>>
impl EndpointArgRegister for Pagination {
    fn register(components: &mut OapiComponents, operation: &mut Operation, _arg: &str) {
        for parameter in Self::to_parameters(components) {
            operation.parameters.insert(parameter);
        }
    }
}

If we go with this, we can also use ToParameters macro to generate the implementation.
The issue with it is that it doesn't use ApiError, but if we are using it as a paremeter, salvo will handle the error reporting

We can add `EndpointArgRegister` implementation and thus we can use it in the arg directly ```rust async fn user_whitelist( req: &mut Request, depot: &mut Depot, pagination: Pagination, ) -> ApiResult<Json<Vec<WhiteListedUser>>> ``` ```rust impl EndpointArgRegister for Pagination { fn register(components: &mut OapiComponents, operation: &mut Operation, _arg: &str) { for parameter in Self::to_parameters(components) { operation.parameters.insert(parameter); } } } ``` If we go with this, we can also use `ToParameters` macro to generate the implementation. The issue with it is that it doesn't use `ApiError`, but if we are using it as a paremeter, `salvo` will handle the error reporting
Review

I'll see it, the reason why I don't put it as an argument, is because
Salvo ask me to implement ToSchema to it.

As I see, Salvo want it as one parameter, but the pagination
is two parameters. I'll see

I'll see it, the reason why I don't put it as an argument, is because Salvo ask me to implement `ToSchema` to it. As I see, Salvo want it as one parameter, but the pagination is two parameters. I'll see
Review

Ok, I get it. It ask me to implement ToSchema because of this

impl<T, const R: bool> EndpointArgRegister for QueryParam<T, R>
where
    T: ToSchema, // <----
{
    fn register(components: &mut Components, operation: &mut Operation, arg: &str) {
        let parameter = Parameter::new(arg)
            .parameter_in(ParameterIn::Query)
            .description(format!("Get parameter `{arg}` from request url query."))
            .schema(T::to_schema(components))
            .required(R);
        operation.parameters.insert(parameter);
    }
}

So I can just implement the EndpointArgRegister trait and not using QueryParam.

Thank you @Amjad50

Ok, I get it. It ask me to implement `ToSchema` because of this ```rust impl<T, const R: bool> EndpointArgRegister for QueryParam<T, R> where T: ToSchema, // <---- { fn register(components: &mut Components, operation: &mut Operation, arg: &str) { let parameter = Parameter::new(arg) .parameter_in(ParameterIn::Query) .description(format!("Get parameter `{arg}` from request url query.")) .schema(T::to_schema(components)) .required(R); operation.parameters.insert(parameter); } } ``` So I can just implement the `EndpointArgRegister` trait and not using `QueryParam`. Thank you @Amjad50
/// The page number of the result
pub page: NonZeroU32,
/// The page size
pub page_size: NonZeroU8,
}
impl<'ex> Extractible<'ex> for Pagination {
fn metadata() -> &'ex ExtractMetadata {
static METADATA: ExtractMetadata = ExtractMetadata::new("");
&METADATA
}
#[allow(refining_impl_trait)]
async fn extract(req: &'ex mut Request) -> ApiResult<Self> {
let page = extract_query(req, "page", NonZeroU32::new(1).expect("is non-zero"))?;
let page_size = extract_query(req, "page_size", NonZeroU8::new(10).expect("is non-zero"))?;
Ok(Self { page, page_size })
}
#[allow(refining_impl_trait)]
async fn extract_with_arg(req: &'ex mut Request, _arg: &str) -> ApiResult<Self> {
Self::extract(req).await
}
}
impl ToParameters<'_> for Pagination {
fn to_parameters(_components: &mut OapiComponents) -> Parameters {
Parameters::new()
.parameter(create_parameter(
"page",
"Page number, starting from 1",
1,
f64::from(u32::MAX),
))
.parameter(create_parameter(
"page_size",
"How many items per page, starting from 1",
10,
f64::from(u8::MAX),
))
}
}
/// Extract a query parameter from the request
fn extract_query<T: FromStr>(req: &Request, name: &str, default_value: T) -> ApiResult<T>
where
<T as FromStr>::Err: fmt::Display,
{
Ok(req
.queries()
.get(name)
.map(|p| p.parse::<T>())
.transpose()
.map_err(|err| ApiError::Querys(format!("Invalid value for `{name}` query ({err})")))?
.unwrap_or(default_value))
}
/// Create a parameter for the pagination
fn create_parameter(name: &str, description: &str, default: usize, max: f64) -> Parameter {
Parameter::new(name)
.parameter_in(ParameterIn::Query)
.required(false)
.description(description)
.example(json!(default))
.schema(
Object::new()
.name(name)
.description(description)
.schema_type(SchemaType::Integer)
.default_value(json!(default))
.example(json!(default))
.maximum(max)
.minimum(1.0)
.read_only(true),
)
}