pub use self::{
components::Components,
content::Content,
example::Example,
external_docs::ExternalDocs,
header::Header,
info::{Contact, Info, License},
operation::{Operation, Operations},
parameter::{Parameter, ParameterIn, ParameterStyle, Parameters},
path::{PathItem, PathItemType, Paths},
request_body::RequestBody,
response::{Response, Responses},
schema::{
Array, Discriminator, KnownFormat, Object, Ref, Schema, SchemaFormat, SchemaType, ToArray,
},
security::{SecurityRequirement, SecurityScheme},
server::{Server, ServerVariable, ServerVariables, Servers},
tag::Tag,
xml::Xml,
};
use hypers_core::{
async_trait,
prelude::{Request, Router},
Handler,
};
use serde::{de::Visitor, Deserialize, Serialize, Serializer};
use std::collections::BTreeSet;
pub mod components;
pub mod content;
pub mod encoding;
pub mod example;
pub mod external_docs;
pub mod header;
pub mod info;
pub mod operation;
pub mod parameter;
pub mod path;
pub mod request_body;
pub mod response;
pub mod schema;
pub mod security;
pub mod server;
pub mod tag;
pub mod xml;
#[non_exhaustive]
#[derive(Serialize, Deserialize, Default, Clone, PartialEq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[serde(rename_all = "camelCase")]
pub struct OpenApi {
pub openapi: OpenApiVersion,
pub info: Info,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub servers: BTreeSet<Server>,
pub paths: Paths, #[serde(skip_serializing_if = "Components::is_empty")]
pub components: Components,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub security: BTreeSet<SecurityRequirement>,
#[serde(skip_serializing_if = "BTreeSet::is_empty")]
pub tags: BTreeSet<Tag>,
#[serde(skip_serializing_if = "Option::is_none")]
pub external_docs: Option<ExternalDocs>,
#[serde(skip)]
pub server_url: String,
}
impl OpenApi {
pub fn new(title: impl Into<String>, version: impl Into<String>) -> Self {
Self {
info: Info::new(title, version),
..Default::default()
}
}
pub fn with_info(info: Info) -> Self {
Self {
info,
..Default::default()
}
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
pub fn to_pretty_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string_pretty(self)
}
#[cfg(feature = "yaml")]
pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
serde_yaml::to_string(self)
}
pub fn merge(&mut self, mut other: OpenApi) -> &mut Self {
self.servers.append(&mut other.servers);
self.paths.append(&mut other.paths);
self.components.append(&mut other.components);
self.security.append(&mut other.security);
self.tags.append(&mut other.tags);
self
}
pub fn info<I: Into<Info>>(mut self, info: I) -> Self {
self.info = info.into();
self
}
pub fn servers<S: IntoIterator<Item = Server>>(mut self, servers: S) -> Self {
self.servers = servers.into_iter().collect();
self
}
pub fn add_server(mut self, server: Server) -> Self {
let server_url = server.url.trim_end_matches('/');
match url::Url::parse(server_url) {
Ok(url) => {
self.server_url = url.path().to_owned();
}
Err(_) => self.server_url = server_url.to_owned(),
};
self.servers.insert(server);
self
}
pub fn paths<P: Into<Paths>>(mut self, paths: P) -> Self {
self.paths = paths.into();
self
}
#[inline]
pub fn metadata(mut self, mut meta: (String, PathItem, Components)) -> Self {
self.paths.insert(meta.0, meta.1);
self.components.append(&mut meta.2);
self
}
pub fn add_path<P, I>(mut self, path: P, item: I) -> Self
where
P: Into<String>,
I: Into<PathItem>,
{
self.paths.insert(path.into(), item.into());
self
}
pub fn components(mut self, components: impl Into<Components>) -> Self {
self.components = components.into();
self
}
pub fn security<S: IntoIterator<Item = SecurityRequirement>>(mut self, security: S) -> Self {
self.security = security.into_iter().collect();
self
}
pub fn add_security_scheme<N: Into<String>, S: Into<SecurityScheme>>(
mut self,
name: N,
security_scheme: S,
) -> Self {
self.components
.security_schemes
.insert(name.into(), security_scheme.into());
self
}
pub fn extend_security_schemes<
I: IntoIterator<Item = (N, S)>,
N: Into<String>,
S: Into<SecurityScheme>,
>(
mut self,
schemas: I,
) -> Self {
self.components.security_schemes.extend(
schemas
.into_iter()
.map(|(name, item)| (name.into(), item.into())),
);
self
}
pub fn add_schema<S: Into<String>, I: Into<RefOr<Schema>>>(
mut self,
name: S,
schema: I,
) -> Self {
self.components.schemas.insert(name.into(), schema.into());
self
}
pub fn extend_schemas<I, C, S>(mut self, schemas: I) -> Self
where
I: IntoIterator<Item = (S, C)>,
C: Into<RefOr<Schema>>,
S: Into<String>,
{
self.components.schemas.extend(
schemas
.into_iter()
.map(|(name, schema)| (name.into(), schema.into())),
);
self
}
pub fn response<S: Into<String>, R: Into<RefOr<Response>>>(
mut self,
name: S,
response: R,
) -> Self {
self.components
.responses
.insert(name.into(), response.into());
self
}
pub fn extend_responses<
I: IntoIterator<Item = (S, R)>,
S: Into<String>,
R: Into<RefOr<Response>>,
>(
mut self,
responses: I,
) -> Self {
self.components.responses.extend(
responses
.into_iter()
.map(|(name, response)| (name.into(), response.into())),
);
self
}
pub fn tags<I, T>(mut self, tags: I) -> Self
where
I: IntoIterator<Item = T>,
T: Into<Tag>,
{
self.tags = tags.into_iter().map(Into::into).collect();
self
}
pub fn external_docs(mut self, external_docs: ExternalDocs) -> Self {
self.external_docs = Some(external_docs);
self
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum OpenApiVersion {
#[serde(rename = "3.1.0")]
Version3,
}
impl Default for OpenApiVersion {
fn default() -> Self {
Self::Version3
}
}
#[derive(PartialEq, Eq, Clone)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum Deprecated {
True,
False,
}
impl From<bool> for Deprecated {
fn from(b: bool) -> Self {
if b {
Self::True
} else {
Self::False
}
}
}
impl Serialize for Deprecated {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bool(matches!(self, Self::True))
}
}
impl<'de> Deserialize<'de> for Deprecated {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct BoolVisitor;
impl<'de> Visitor<'de> for BoolVisitor {
type Value = Deprecated;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a bool true or false")
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match v {
true => Ok(Deprecated::True),
false => Ok(Deprecated::False),
}
}
}
deserializer.deserialize_bool(BoolVisitor)
}
}
impl Default for Deprecated {
fn default() -> Self {
Deprecated::False
}
}
#[derive(PartialEq, Eq, Clone, Default)]
#[cfg_attr(feature = "debug", derive(Debug))]
pub enum Required {
True,
False,
#[default]
Unset,
}
impl From<bool> for Required {
fn from(value: bool) -> Self {
if value {
Self::True
} else {
Self::False
}
}
}
impl Serialize for Required {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bool(matches!(self, Self::True))
}
}
impl<'de> Deserialize<'de> for Required {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
struct BoolVisitor;
impl<'de> Visitor<'de> for BoolVisitor {
type Value = Required;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a bool true or false")
}
fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
match v {
true => Ok(Required::True),
false => Ok(Required::False),
}
}
}
deserializer.deserialize_bool(BoolVisitor)
}
}
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "debug", derive(Debug))]
#[serde(untagged)]
pub enum RefOr<T> {
Ref(schema::Ref),
T(T),
}
#[async_trait]
impl Handler for OpenApi {
#[inline]
async fn handle(&self, _: Request) -> hypers_core::prelude::Response {
let mut res = hypers_core::prelude::Response::default();
res.json(&self);
res
}
}
#[cfg_attr(feature = "debug", derive(Debug))]
pub struct OpenApiService {
pub openapi: OpenApi,
pub router: Router,
}
impl OpenApiService {
#[inline]
pub fn new(openapi: OpenApi) -> Self {
let mut router = Router::default();
if !openapi.server_url.is_empty() {
router.prefix = openapi.server_url.clone();
}
Self { openapi, router }
}
#[inline]
pub fn push<T>(mut self, child: T) -> Self
where
T: Into<(Router, OpenApi)>,
{
let (router, openapi): (Router, OpenApi) = child.into();
self.router.push(router);
self.openapi.merge(openapi);
self
}
#[inline]
pub fn openapi(self, path: &str) -> Router {
let Self {
openapi,
mut router,
} = self;
router.route(
"/GET".to_owned() + path,
path.to_owned(),
"/GET".to_owned(),
std::sync::Arc::new(openapi),
);
router
}
}