use crate::utils::fixed::{Fixed128, Fixed256};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use utoipa::{
openapi::{SchemaFormat, Type},
PartialSchema, ToSchema,
};
#[cfg(feature = "utoipa-axum")]
pub use utoipa_axum_feat::*;
use super::tokens::Brc4Ticker;
#[cfg(feature = "utoipa-axum")]
mod utoipa_axum_feat {
use std::convert::Infallible;
use axum::{handler::Handler, routing::MethodRouter};
use utoipa::openapi::{
path::{Operation, OperationBuilder},
PathItem, RefOr, Schema,
};
use utoipa_axum::router::{OpenApiRouter, UtoipaMethodRouter};
pub enum Method<S> {
Post(MethodRouter<S, Infallible>),
Get(MethodRouter<S, Infallible>),
Put(MethodRouter<S, Infallible>),
Delete(MethodRouter<S, Infallible>),
}
pub enum MethodType {
Post,
Get,
Put,
Delete,
}
impl<S> Method<S> {
fn extract(self) -> (MethodType, MethodRouter<S>) {
match self {
Method::Post(method_router) => (MethodType::Post, method_router),
Method::Get(method_router) => (MethodType::Get, method_router),
Method::Put(method_router) => (MethodType::Put, method_router),
Method::Delete(method_router) => (MethodType::Delete, method_router),
}
}
}
pub trait OperationMod {
fn and_mutate<F>(&mut self, func: F) -> Operation
where
F: FnMut(&mut Operation);
fn and_modify<F>(&self, func: F) -> Operation
where
F: FnMut(&mut Operation);
fn then_modify<F>(self, func: F) -> Operation
where
F: FnMut(&mut Operation);
fn desc(&mut self, desc: impl Into<String>);
}
impl OperationMod for Operation {
fn and_modify<F>(&self, mut func: F) -> Operation
where
F: FnMut(&mut Operation),
{
let mut s = self.clone();
func(&mut s);
s
}
fn and_mutate<F>(&mut self, mut func: F) -> Operation
where
F: FnMut(&mut Operation),
{
func(self);
self.clone()
}
fn desc(&mut self, desc: impl Into<String>) {
self.description = Some(desc.into())
}
fn then_modify<F>(mut self, mut func: F) -> Operation
where
F: FnMut(&mut Operation),
{
func(&mut self);
self
}
}
pub trait UtoipaAxumExtras<S> {
fn route_desc_full(self, path: &str, method_router: Method<S>, op: Operation) -> Self;
fn route_desc(self, path: &str, method_router: Method<S>) -> Self;
fn schemas(self, schemas: Vec<(String, RefOr<Schema>)>) -> Self;
fn routes2_lol(self, a: UtoipaMethodRouter<S>, new_path: &str) -> Self;
fn routes2_lol_with_op_merge(self, a: UtoipaMethodRouter<S>, new_path: &str, method_type: MethodType, op: Operation) -> Self;
}
impl<S> UtoipaAxumExtras<S> for OpenApiRouter<S>
where
S: Send + Sync + Clone + 'static,
{
fn route_desc_full(self, path: &str, method_router: Method<S>, op: Operation) -> Self {
let (typ, method_router) = method_router.extract();
let (a, mut b) = self.route(path, method_router).split_for_parts();
let mut item: PathItem = PathItem::default();
match typ {
MethodType::Post => item.post = Some(op),
MethodType::Get => item.get = Some(op),
MethodType::Put => item.put = Some(op),
MethodType::Delete => item.delete = Some(op),
}
if let Some(it) = b.paths.paths.get_mut(path) {
it.merge_operations(item);
} else {
b.paths.paths.insert(path.to_string(), item);
}
OpenApiRouter::with_openapi(b).merge(a.into())
}
fn route_desc(self, path: &str, method_router: Method<S>) -> Self {
let op_builder = OperationBuilder::new().build();
self.route_desc_full(path, method_router, op_builder)
}
fn schemas(self, schemas: Vec<(String, RefOr<Schema>)>) -> Self {
let (a, mut b) = self.split_for_parts();
let components = b.components.get_or_insert(utoipa::openapi::Components::new());
components.schemas.extend(schemas);
OpenApiRouter::with_openapi(b).merge(a.into())
}
fn routes2_lol(self, (schemas, mut paths, method_router): UtoipaMethodRouter<S>, new_path: &str) -> Self {
let (a, mut b) = self.split_for_parts();
let a = if paths.paths.len() == 1 {
let first_entry = &paths.paths.first_entry();
let path = first_entry.as_ref().map(|path| path.key());
let Some(path) = path else {
unreachable!("Whoopsie, I thought there was one Path entry");
};
let path = if path.is_empty() { "/" } else { new_path };
a.route(path, method_router)
} else {
panic!("Nope, just one route here, and go fuck yourself otherwise");
};
for (_, item) in paths.paths {
if let Some(it) = b.paths.paths.get_mut(new_path) {
it.merge_operations(item);
} else {
b.paths.paths.insert(new_path.to_string(), item);
}
}
let components = b.components.get_or_insert(utoipa::openapi::Components::new());
components.schemas.extend(schemas);
OpenApiRouter::with_openapi(b).merge(a.into())
}
fn routes2_lol_with_op_merge(self, (schemas, mut paths, method_router): UtoipaMethodRouter<S>, new_path: &str, method_type: MethodType, op: Operation) -> Self {
let (a, mut b) = self.split_for_parts();
let a = if paths.paths.len() == 1 {
let first_entry = &paths.paths.first_entry();
let path = first_entry.as_ref().map(|path| path.key());
let Some(path) = path else {
unreachable!("Whoopsie, I thought there was one Path entry");
};
let path = if path.is_empty() { "/" } else { new_path };
a.route(path, method_router)
} else {
panic!("Nope, just one route here, and go fuck yourself otherwise");
};
for (_, mut item) in paths.paths {
let typ;
let method = match method_type {
MethodType::Post => {
typ = "post";
&mut item.post
}
MethodType::Get => {
typ = "get";
&mut item.get
}
MethodType::Put => {
typ = "put";
&mut item.put
}
MethodType::Delete => {
typ = "delete";
&mut item.delete
}
};
if let Some(method) = method {
method.operation_id = Some((new_path.trim_start_matches('/').replace('/', "-").replace(['}', '{'], "") + typ).to_string());
if let Some(ref tags) = op.tags {
method.tags = Some(tags.clone());
}
if let Some(ref summary) = op.summary {
method.summary = Some(summary.clone());
}
if let Some(ref description) = op.description {
method.description = Some(description.clone());
}
if let Some(ref operation_id) = op.operation_id {
method.operation_id = Some(operation_id.clone());
}
if let Some(ref external_docs) = op.external_docs {
method.external_docs = Some(external_docs.clone());
}
if let Some(ref parameters) = op.parameters {
method.parameters = Some(parameters.clone());
}
if let Some(ref request_body) = op.request_body {
method.request_body = Some(request_body.clone());
}
if let Some(ref callbacks) = op.callbacks {
method.callbacks = Some(callbacks.clone());
}
if let Some(ref deprecated) = op.deprecated {
method.deprecated = Some(deprecated.clone());
}
if let Some(ref security) = op.security {
method.security = Some(security.clone());
}
if let Some(ref servers) = op.servers {
method.servers = Some(servers.clone());
}
if let Some(ref extensions) = op.extensions {
method.extensions = Some(extensions.clone());
}
} else {
panic!("metod not found");
}
if let Some(it) = b.paths.paths.get_mut(new_path) {
it.merge_operations(item);
} else {
b.paths.paths.insert(new_path.to_string(), item);
}
}
let components = b.components.get_or_insert(utoipa::openapi::Components::new());
components.schemas.extend(schemas);
OpenApiRouter::with_openapi(b).merge(a.into())
}
}
pub fn post<H, T, S>(handler: H) -> Method<S>
where
H: Handler<T, S>,
T: 'static,
S: Clone + Send + Sync + 'static,
{
Method::Post(axum::routing::post(handler))
}
pub fn put<H, T, S>(handler: H) -> Method<S>
where
H: Handler<T, S>,
T: 'static,
S: Clone + Send + Sync + 'static,
{
Method::Put(axum::routing::put(handler))
}
pub fn delete<H, T, S>(handler: H) -> Method<S>
where
H: Handler<T, S>,
T: 'static,
S: Clone + Send + Sync + 'static,
{
Method::Delete(axum::routing::delete(handler))
}
pub fn get<H, T, S>(handler: H) -> Method<S>
where
H: Handler<T, S>,
T: 'static,
S: Clone + Send + Sync + 'static,
{
Method::Get(axum::routing::get(handler))
}
}
#[derive(Serialize, Deserialize, Clone, Copy)]
pub struct SchemedDecimal(pub Decimal);
impl<const PRECISION: u8> From<Fixed128<PRECISION>> for SchemedDecimal {
fn from(value: Fixed128<PRECISION>) -> Self {
SchemedDecimal(value.into_decimal())
}
}
impl From<Decimal> for SchemedDecimal {
fn from(value: Decimal) -> Self {
SchemedDecimal(value)
}
}
impl ToSchema for Brc4Ticker {
fn name() -> std::borrow::Cow<'static, str> {
let full_type_name = std::any::type_name::<Self>();
let type_name_without_generic = full_type_name.split_once("<").map(|(s1, _)| s1).unwrap_or(full_type_name);
let type_name = type_name_without_generic.rsplit_once("::").map(|(_, tn)| tn).unwrap_or(type_name_without_generic);
std::borrow::Cow::Borrowed(type_name)
}
}
impl PartialSchema for Brc4Ticker {
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(
utoipa::openapi::ObjectBuilder::new()
.title(Some("tick".to_string()))
.schema_type(Type::String)
.format(Some(SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Float)))
.description(Some("brc-20 token tick"))
.default(Some(serde_json::to_value("0").unwrap()))
.minimum(Some(0))
.examples(vec![
Some(serde_json::to_value("nook").unwrap()),
Some(serde_json::to_value("1234").unwrap()),
Some(serde_json::to_value("æœ").unwrap()),
Some(serde_json::to_value("汉 ").unwrap()),
Some(serde_json::to_value("ҐҐ").unwrap()),
])
.build(),
))
}
}
impl ToSchema for SchemedDecimal {
fn name() -> std::borrow::Cow<'static, str> {
let full_type_name = std::any::type_name::<Self>();
let type_name_without_generic = full_type_name.split_once("<").map(|(s1, _)| s1).unwrap_or(full_type_name);
let type_name = type_name_without_generic.rsplit_once("::").map(|(_, tn)| tn).unwrap_or(type_name_without_generic);
std::borrow::Cow::Borrowed(type_name)
}
}
impl PartialSchema for SchemedDecimal {
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(
utoipa::openapi::ObjectBuilder::new()
.title(Some("decimal".to_string()))
.schema_type(Type::String)
.format(Some(SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Float)))
.description(Some("`Decimal` represents a 128 bit representation of a fixed-precision decimal number."))
.default(Some(serde_json::to_value("0").unwrap()))
.minimum(Some(0))
.examples(vec![
Some(serde_json::to_value("0").unwrap()),
Some(serde_json::to_value("1.0").unwrap()),
Some(serde_json::to_value("3.5").unwrap()),
Some(serde_json::to_value("13.31").unwrap()),
])
.build(),
))
}
}
impl<const PRECISION: u8> ToSchema for Fixed128<PRECISION> {
fn name() -> std::borrow::Cow<'static, str> {
let full_type_name = std::any::type_name::<Self>();
let type_name_without_generic = full_type_name.split_once("<").map(|(s1, _)| s1).unwrap_or(full_type_name);
let type_name = type_name_without_generic.rsplit_once("::").map(|(_, tn)| tn).unwrap_or(type_name_without_generic);
std::borrow::Cow::Borrowed(type_name)
}
}
impl<const PRECISION: u8> PartialSchema for Fixed128<PRECISION> {
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(
utoipa::openapi::ObjectBuilder::new()
.title(Some("fixed".to_string()))
.schema_type(Type::String)
.format(Some(SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Float)))
.description(Some("128-bit unsigned fixed point number with const generic precision."))
.default(Some(serde_json::to_value("0").unwrap()))
.minimum(Some(0))
.examples(vec![
Some(serde_json::to_value("0").unwrap()),
Some(serde_json::to_value("1.0").unwrap()),
Some(serde_json::to_value("3.5").unwrap()),
Some(serde_json::to_value("13.31").unwrap()),
])
.build(),
))
}
}
impl<const PRECISION: u8> ToSchema for Fixed256<PRECISION> {
fn name() -> std::borrow::Cow<'static, str> {
let full_type_name = std::any::type_name::<Self>();
let type_name_without_generic = full_type_name.split_once("<").map(|(s1, _)| s1).unwrap_or(full_type_name);
let type_name = type_name_without_generic.rsplit_once("::").map(|(_, tn)| tn).unwrap_or(type_name_without_generic);
std::borrow::Cow::Borrowed(type_name)
}
}
impl<const PRECISION: u8> PartialSchema for Fixed256<PRECISION> {
fn schema() -> utoipa::openapi::RefOr<utoipa::openapi::schema::Schema> {
utoipa::openapi::RefOr::T(utoipa::openapi::Schema::Object(
utoipa::openapi::ObjectBuilder::new()
.title(Some("fixed".to_string()))
.schema_type(Type::String)
.format(Some(SchemaFormat::KnownFormat(utoipa::openapi::KnownFormat::Float)))
.description(Some("256-bit unsigned fixed point number with const generic precision."))
.default(Some(serde_json::to_value("0").unwrap()))
.minimum(Some(0))
.examples(vec![
Some(serde_json::to_value("0").unwrap()),
Some(serde_json::to_value("1.0").unwrap()),
Some(serde_json::to_value("3.5").unwrap()),
Some(serde_json::to_value("13.31").unwrap()),
])
.build(),
))
}
}