WIP: aide OpenAPI docs

This commit is contained in:
Oystein Kristoffer Tveit 2024-04-16 18:21:26 +02:00
parent 9934b11766
commit 361c683b5f
Signed by: oysteikt
GPG Key ID: 9F2F7D8250F35146
4 changed files with 1057 additions and 36 deletions

947
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,18 +9,31 @@ readme = "README.md"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
aide = { version = "0.13.4", features = [
"axum",
"axum-extra",
"axum-extra-query",
"axum-extra-form",
"axum-ws",
"macros",
"scalar",
] }
anyhow = "1.0.82" anyhow = "1.0.82"
axum = { version = "0.6.20", features = ["macros"] } axum = { version = "0.6.20", features = ["macros", "ws"] }
axum-jsonschema = { version = "0.8.0", features = ["aide"] }
axum-macros = "0.4.1"
clap = { version = "4.4.1", features = ["derive"] } clap = { version = "4.4.1", features = ["derive"] }
clap-verbosity-flag = "2.2.2" clap-verbosity-flag = "2.2.2"
env_logger = "0.10.0" env_logger = "0.10.0"
log = "0.4.20" log = "0.4.20"
mpvipc-async = { git = "https://git.pvv.ntnu.no/oysteikt/mpvipc-async.git", rev = "v0.1.0" } mpvipc-async = { git = "https://git.pvv.ntnu.no/oysteikt/mpvipc-async.git", rev = "v0.1.0" }
sd-notify = "0.4.3" sd-notify = "0.4.3"
schemars = "0.8.16"
serde = { version = "1.0.188", features = ["derive"] } serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105" serde_json = "1.0.105"
systemd-journal-logger = "2.2.0" systemd-journal-logger = "2.2.0"
tempfile = "3.11.0" tempfile = "3.11.0"
serde_urlencoded = "0.7.1"
tokio = { version = "1.32.0", features = ["full"] } tokio = { version = "1.32.0", features = ["full"] }
tower = { version = "0.4.13", features = ["full"] } tower = { version = "0.4.13", features = ["full"] }
tower-http = { version = "0.4.3", features = ["full"] } tower-http = { version = "0.4.3", features = ["full"] }

View File

@ -1,11 +1,14 @@
use std::ops::Deref;
use aide::{axum::IntoApiResponse, operation::OperationIo, OperationOutput};
use axum_jsonschema::JsonSchemaRejection;
use axum::{ use axum::{
extract::{Query, State}, async_trait, extract::{rejection::{FailedToDeserializeQueryString, QueryRejection}, FromRequest, FromRequestParts, State}, http::{request::Parts, StatusCode}, response::{IntoResponse, Response}, routing::{delete, get, post}, Json, Router
http::StatusCode,
response::{IntoResponse, Response},
routing::{delete, get, post},
Json, Router,
}; };
use mpvipc_async::Mpv; use mpvipc_async::Mpv;
use schemars::JsonSchema;
use serde::{de::DeserializeOwned, Serialize};
use serde_json::{json, Value}; use serde_json::{json, Value};
use super::base; use super::base;
@ -31,6 +34,13 @@ pub fn rest_api_routes(mpv: Mpv) -> Router {
.with_state(mpv) .with_state(mpv)
} }
// #[derive(FromRequest, OperationIo)]
// #[from_request(via(axum_jsonschema::Json), rejection(RestResponse))]
// #[aide(
// input_with = "axum_jsonschema::Json<T>",
// output_with = "axum_jsonschema::Json<T>",
// json_schema
// )]
pub struct RestResponse(anyhow::Result<Value>); pub struct RestResponse(anyhow::Result<Value>);
impl From<anyhow::Result<Value>> for RestResponse { impl From<anyhow::Result<Value>> for RestResponse {
@ -58,13 +68,103 @@ impl IntoResponse for RestResponse {
} }
} }
impl aide::OperationOutput for RestResponse {
type Inner = anyhow::Result<Value>;
}
/// -------
// impl<T> aide::OperationInput for Query<T> {}
// #[derive(FromRequest, OperationIo)]
// #[from_request(via(axum_jsonschema::Json), rejection(RestResponse))]
// #[aide(
// input_with = "axum_jsonschema::Json<T>",
// output_with = "axum_jsonschema::Json<T>",
// json_schema
// )]
// pub struct Json<T>(pub T);
// impl<T> IntoResponse for Json<T>
// where
// T: Serialize,
// {
// fn into_response(self) -> axum::response::Response {
// axum::Json(self.0).into_response()
// }
// }
#[derive(OperationIo)]
#[aide(json_schema)]
pub struct Query<T>(pub T);
#[async_trait]
impl <T, S> FromRequestParts<S> for Query<T>
where
T: JsonSchema + DeserializeOwned,
S: Send + Sync,
{
type Rejection = QueryRejection;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let axum::extract::Query(query) = axum::extract::Query::try_from_uri(&parts.uri)?;
Ok(Query(query))
}
}
impl<T> Deref for Query<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub fn rest_api_route_docs(mpv: Mpv) -> Router {
use aide::axum::ApiRouter;
use aide::axum::routing::{delete, get, post};
let mut api = aide::openapi::OpenApi::default();
let x = ApiRouter::new()
// .api_route("/load", get(loadfile))
.api_route("/play", get(play_get))
.finish_api(&mut api);
// .with_state(mpv);
todo!()
}
// ----------
pub fn rest_api_routes(mpv: Mpv) -> Router {
Router::new()
.route("/load", post(loadfile))
.route("/play", get(play_get))
.route("/play", post(play_set))
.route("/volume", get(volume_get))
.route("/volume", post(volume_set))
.route("/time", get(time_get))
.route("/time", post(time_set))
.route("/playlist", get(playlist_get))
.route("/playlist/next", post(playlist_next))
.route("/playlist/previous", post(playlist_previous))
.route("/playlist/goto", post(playlist_goto))
.route("/playlist", delete(playlist_remove_or_clear))
.route("/playlist/move", post(playlist_move))
.route("/playlist/shuffle", post(shuffle))
.route("/playlist/loop", get(playlist_get_looping))
.route("/playlist/loop", post(playlist_set_looping))
.with_state(mpv)
}
// -------------------// // -------------------//
// Boilerplate galore // // Boilerplate galore //
// -------------------// // -------------------//
// TODO: These could possibly be generated with a proc macro // TODO: These could possibly be generated with a proc macro
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, JsonSchema)]
struct LoadFileArgs { struct LoadFileArgs {
path: String, path: String,
} }
@ -73,11 +173,11 @@ async fn loadfile(State(mpv): State<Mpv>, Query(query): Query<LoadFileArgs>) ->
base::loadfile(mpv, &query.path).await.into() base::loadfile(mpv, &query.path).await.into()
} }
async fn play_get(State(mpv): State<Mpv>) -> RestResponse { async fn play_get(State(mpv): State<Mpv>) -> impl IntoApiResponse {
base::play_get(mpv).await.into() RestResponse::from(base::play_get(mpv).await)
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, JsonSchema)]
struct PlaySetArgs { struct PlaySetArgs {
play: String, play: String,
} }
@ -91,7 +191,7 @@ async fn volume_get(State(mpv): State<Mpv>) -> RestResponse {
base::volume_get(mpv).await.into() base::volume_get(mpv).await.into()
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, JsonSchema)]
struct VolumeSetArgs { struct VolumeSetArgs {
volume: f64, volume: f64,
} }
@ -104,7 +204,7 @@ async fn time_get(State(mpv): State<Mpv>) -> RestResponse {
base::time_get(mpv).await.into() base::time_get(mpv).await.into()
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, JsonSchema)]
struct TimeSetArgs { struct TimeSetArgs {
pos: Option<f64>, pos: Option<f64>,
percent: Option<f64>, percent: Option<f64>,
@ -126,7 +226,7 @@ async fn playlist_previous(State(mpv): State<Mpv>) -> RestResponse {
base::playlist_previous(mpv).await.into() base::playlist_previous(mpv).await.into()
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, JsonSchema)]
struct PlaylistGotoArgs { struct PlaylistGotoArgs {
index: usize, index: usize,
} }
@ -138,7 +238,7 @@ async fn playlist_goto(
base::playlist_goto(mpv, query.index).await.into() base::playlist_goto(mpv, query.index).await.into()
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, JsonSchema)]
struct PlaylistRemoveOrClearArgs { struct PlaylistRemoveOrClearArgs {
index: Option<usize>, index: Option<usize>,
} }
@ -153,7 +253,7 @@ async fn playlist_remove_or_clear(
} }
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, JsonSchema)]
struct PlaylistMoveArgs { struct PlaylistMoveArgs {
index1: usize, index1: usize,
index2: usize, index2: usize,
@ -176,7 +276,7 @@ async fn playlist_get_looping(State(mpv): State<Mpv>) -> RestResponse {
base::playlist_get_looping(mpv).await.into() base::playlist_get_looping(mpv).await.into()
} }
#[derive(serde::Deserialize)] #[derive(serde::Deserialize, JsonSchema)]
struct PlaylistSetLoopingArgs { struct PlaylistSetLoopingArgs {
r#loop: bool, r#loop: bool,
} }

View File

@ -10,6 +10,7 @@ use tempfile::NamedTempFile;
mod api; mod api;
mod mpv_setup; mod mpv_setup;
// mod mpv_broker;
#[derive(Parser)] #[derive(Parser)]
struct Args { struct Args {