use std::collections::BTreeSet;
use std::mem;
use http::{Method, Uri};
use utoipa::openapi::{Components, Info, OpenApi, Paths, Server, Tag};
mod builder;
use crate::client::openapi::channel::{CollectorHandle, CollectorMessage};
use crate::client::openapi::schema::Schemas;
pub use self::builder::ApiClientBuilder;
mod call;
pub use self::call::ApiCall;
mod parameters;
pub use self::parameters::{
CallBody, CallCookies, CallHeaders, CallPath, CallQuery, ParamStyle, ParamValue, ParameterValue,
};
mod response;
pub use self::response::ExpectedStatusCodes;
#[cfg(feature = "redaction")]
pub use self::response::{
RedactOptions, RedactedResult, RedactionBuilder, Redactor, RequestBodyRedactionBuilder,
ValueRedactionBuilder, redact_value,
};
mod auth;
pub use self::auth::{Authentication, AuthenticationError, SecureString};
#[cfg(feature = "oauth2")]
pub mod oauth2;
#[cfg(feature = "oauth2")]
pub use self::oauth2::{OAuth2Config, OAuth2ConfigBuilder, OAuth2Error, OAuth2Token};
mod security;
pub use self::security::{
ApiKeyLocation, OAuth2Flow, OAuth2Flows, OAuth2ImplicitFlow, SecurityRequirement,
SecurityScheme,
};
mod call_parameters;
mod openapi;
pub use self::openapi::{CallResult, RawBody, RawResult};
mod error;
pub use self::error::ApiClientError;
#[cfg(test)]
mod integration_tests;
#[cfg(test)]
mod mock_server_tests;
use indexmap::IndexMap;
#[derive(Debug, Clone)]
pub struct ApiClient {
client: reqwest::Client,
base_uri: Uri,
base_path: String,
info: Option<Info>,
servers: Vec<Server>,
collector_handle: CollectorHandle,
authentication: Option<Authentication>,
security_schemes: IndexMap<String, SecurityScheme>,
default_security: Vec<SecurityRequirement>,
}
impl ApiClient {
pub fn builder() -> ApiClientBuilder {
ApiClientBuilder::default()
}
}
impl ApiClient {
pub async fn collected_paths(&mut self) -> Paths {
let mut builder = Paths::builder();
let mut collectors = self.collector_handle.get_collectors().await;
for (path, item) in collectors.as_map(&self.base_path) {
builder = builder.path(path, item);
}
mem::drop(collectors);
builder.build()
}
pub async fn collected_openapi(&mut self) -> OpenApi {
let mut builder = OpenApi::builder();
if let Some(ref info) = self.info {
builder = builder.info(info.clone());
}
if !self.servers.is_empty() {
builder = builder.servers(Some(self.servers.clone()));
}
builder = builder.paths(self.collected_paths().await);
let collectors = self.collector_handle.get_collectors().await;
let mut components_builder = Components::builder().schemas_from_iter(collectors.schemas());
for (name, scheme) in &self.security_schemes {
components_builder = components_builder.security_scheme(name, scheme.to_utoipa());
}
let components = components_builder.build();
let tags = self.compute_tags(&collectors).await;
mem::drop(collectors);
let builder = builder.components(Some(components));
let builder = if tags.is_empty() {
builder
} else {
builder.tags(Some(tags))
};
let builder = if self.default_security.is_empty() {
builder
} else {
let security: Vec<_> = self
.default_security
.iter()
.map(SecurityRequirement::to_utoipa)
.collect();
builder.security(Some(security))
};
builder.build()
}
async fn compute_tags(&self, collectors: &openapi::Collectors) -> Vec<Tag> {
let mut tag_names = BTreeSet::new();
for operation in collectors.operations() {
if let Some(tags) = operation.tags() {
for tag in tags {
tag_names.insert(tag.clone());
}
}
}
tag_names.into_iter().map(Tag::new).collect()
}
pub async fn register_schema<T>(&mut self)
where
T: utoipa::ToSchema + 'static,
{
let mut schemas = Schemas::default();
schemas.add::<T>();
self.collector_handle
.sender()
.send(CollectorMessage::AddSchemas(schemas))
.await;
}
}
impl ApiClient {
pub fn call(&self, method: Method, path: CallPath) -> Result<ApiCall, ApiClientError> {
let default_security = if self.default_security.is_empty() {
None
} else {
Some(self.default_security.clone())
};
ApiCall::build(
self.client.clone(),
self.base_uri.clone(),
self.collector_handle.sender(),
method,
path,
self.authentication.clone(),
default_security,
)
}
pub fn get(&self, path: impl Into<CallPath>) -> Result<ApiCall, ApiClientError> {
self.call(Method::GET, path.into())
}
pub fn post(&self, path: impl Into<CallPath>) -> Result<ApiCall, ApiClientError> {
self.call(Method::POST, path.into())
}
pub fn put(&self, path: impl Into<CallPath>) -> Result<ApiCall, ApiClientError> {
self.call(Method::PUT, path.into())
}
pub fn delete(&self, path: impl Into<CallPath>) -> Result<ApiCall, ApiClientError> {
self.call(Method::DELETE, path.into())
}
pub fn patch(&self, path: impl Into<CallPath>) -> Result<ApiCall, ApiClientError> {
self.call(Method::PATCH, path.into())
}
}