use indexmap::{IndexMap, IndexSet};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Deserialize, Serialize)]
pub(crate) struct OpenApiSpec {
#[serde(rename = "swagger", alias = "openapi", default)]
pub spec_version: Option<String>,
pub info: Info,
pub tags: Option<Vec<Tag>>,
pub paths: IndexMap<String, PathItem>,
pub servers: Option<Vec<Server>>,
pub components: Option<Components>,
pub security: Option<Vec<HashMap<String, Vec<String>>>>,
#[serde(flatten)]
pub extensions: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(untagged)]
pub enum AdditionalProperties {
Bool(bool),
Schema(Box<Schema>),
}
fn deserialize_optional_type<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum TypeField {
Single(String),
Multi(Vec<String>),
}
Ok(match Option::<TypeField>::deserialize(deserializer)? {
None => None,
Some(TypeField::Single(s)) => Some(s),
Some(TypeField::Multi(types)) => {
let non_null_types: Vec<String> = types.into_iter().filter(|t| t != "null").collect();
if non_null_types.is_empty() {
None
} else {
Some(non_null_types.join(" | "))
}
}
})
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct Info {
pub title: String,
pub version: String,
pub description: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct Tag {
pub name: String,
pub description: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct PathItem {
#[serde(rename = "get", skip_serializing_if = "Option::is_none")]
pub get: Option<Operation>,
#[serde(rename = "put", skip_serializing_if = "Option::is_none")]
pub put: Option<Operation>,
#[serde(rename = "post", skip_serializing_if = "Option::is_none")]
pub post: Option<Operation>,
#[serde(rename = "delete", skip_serializing_if = "Option::is_none")]
pub delete: Option<Operation>,
#[serde(rename = "options", skip_serializing_if = "Option::is_none")]
pub options: Option<Operation>,
#[serde(rename = "head", skip_serializing_if = "Option::is_none")]
pub head: Option<Operation>,
#[serde(rename = "patch", skip_serializing_if = "Option::is_none")]
pub patch: Option<Operation>,
#[serde(rename = "trace", skip_serializing_if = "Option::is_none")]
pub trace: Option<Operation>,
#[serde(rename = "parameters", skip_serializing_if = "Option::is_none")]
pub parameters: Option<Vec<Parameter>>,
#[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
}
impl PathItem {
pub fn operations(&self) -> [(&'static str, &Option<Operation>); 8] {
[
("get", &self.get),
("post", &self.post),
("put", &self.put),
("delete", &self.delete),
("options", &self.options),
("head", &self.head),
("patch", &self.patch),
("trace", &self.trace),
]
}
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct Operation {
pub tags: Option<Vec<String>>,
pub summary: Option<String>,
pub description: Option<String>,
#[serde(rename = "operationId")]
pub operation_id: Option<String>,
pub parameters: Option<Vec<Parameter>>,
#[serde(rename = "requestBody", skip_serializing_if = "Option::is_none")]
pub request_body: Option<RequestBody>,
#[serde(default)]
pub responses: IndexMap<String, Response>,
pub deprecated: Option<bool>,
#[serde(rename = "security", skip_serializing_if = "Option::is_none")]
pub security: Option<Vec<HashMap<String, Vec<String>>>>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Parameter {
#[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
#[serde(default)]
pub name: String,
pub description: Option<String>,
#[serde(rename = "in", default)]
pub parameter_in: String,
pub required: Option<bool>,
pub schema: Option<Schema>,
#[serde(skip_serializing_if = "Option::is_none")]
pub example: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub examples: Option<IndexMap<String, Example>>,
#[serde(flatten)]
pub extensions: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Default, Deserialize, Serialize, Clone)]
pub struct Schema {
#[serde(rename = "title", skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "description", skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(
rename = "type",
default,
deserialize_with = "deserialize_optional_type",
skip_serializing_if = "Option::is_none"
)]
pub schema_type: Option<String>,
#[serde(rename = "format", skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
#[serde(rename = "properties", skip_serializing_if = "Option::is_none")]
pub properties: Option<IndexMap<String, Schema>>,
#[serde(rename = "items", skip_serializing_if = "Option::is_none")]
pub items: Option<Box<Schema>>,
#[serde(rename = "required", skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
#[serde(rename = "allOf", skip_serializing_if = "Option::is_none")]
pub all_of: Option<Vec<Schema>>,
#[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")]
pub one_of: Option<Vec<Schema>>,
#[serde(rename = "anyOf", skip_serializing_if = "Option::is_none")]
pub any_of: Option<Vec<Schema>>,
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
pub enum_values: Option<Vec<serde_json::Value>>,
#[serde(rename = "nullable", skip_serializing_if = "Option::is_none")]
pub nullable: Option<bool>,
#[serde(
rename = "additionalProperties",
skip_serializing_if = "Option::is_none"
)]
pub additional_properties: Option<AdditionalProperties>,
#[serde(flatten)]
pub extensions: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Response {
#[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
pub description: Option<String>,
pub schema: Option<Schema>,
#[serde(rename = "content", skip_serializing_if = "Option::is_none")]
pub content: Option<IndexMap<String, MediaType>>,
#[serde(flatten)]
pub extensions: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct Server {
pub url: String,
pub description: Option<String>,
pub variables: Option<HashMap<String, ServerVariable>>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct ServerVariable {
#[serde(rename = "enum")]
pub enum_values: Option<Vec<String>>,
pub default: String,
pub description: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct Components {
pub schemas: Option<IndexMap<String, Schema>>,
pub responses: Option<HashMap<String, Response>>,
pub parameters: Option<HashMap<String, Parameter>>,
pub examples: Option<IndexMap<String, Example>>,
#[serde(rename = "requestBodies")]
pub request_bodies: Option<HashMap<String, RequestBody>>,
pub headers: Option<HashMap<String, Header>>,
#[serde(rename = "securitySchemes")]
pub security_schemes: Option<IndexMap<String, SecurityScheme>>,
pub links: Option<HashMap<String, Link>>,
pub callbacks: Option<HashMap<String, Callback>>,
#[serde(flatten)]
pub extensions: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Example {
#[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
pub summary: Option<String>,
pub description: Option<String>,
pub value: Option<serde_json::Value>,
#[serde(rename = "externalValue", skip_serializing_if = "Option::is_none")]
pub external_value: Option<String>,
#[serde(flatten)]
pub extensions: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct RequestBody {
#[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
pub reference: Option<String>,
pub description: Option<String>,
#[serde(default)]
pub content: IndexMap<String, MediaType>,
pub required: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct MediaType {
pub schema: Option<Schema>,
pub example: Option<serde_json::Value>,
pub examples: Option<IndexMap<String, Example>>,
pub encoding: Option<HashMap<String, Encoding>>,
#[serde(flatten)]
pub extensions: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Encoding {
#[serde(rename = "contentType")]
pub content_type: Option<String>,
pub headers: Option<HashMap<String, Header>>,
pub style: Option<String>,
pub explode: Option<bool>,
#[serde(rename = "allowReserved")]
pub allow_reserved: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct Header {
pub description: Option<String>,
pub schema: Option<Schema>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct SecurityScheme {
#[serde(rename = "type")]
pub security_type: String,
pub description: Option<String>,
pub name: Option<String>,
#[serde(rename = "in")]
pub location: Option<String>,
pub scheme: Option<String>,
#[serde(rename = "bearerFormat")]
pub bearer_format: Option<String>,
pub flows: Option<OAuthFlows>,
#[serde(rename = "openIdConnectUrl")]
pub open_id_connect_url: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct OAuthFlows {
pub implicit: Option<OAuthFlow>,
pub password: Option<OAuthFlow>,
#[serde(rename = "clientCredentials")]
pub client_credentials: Option<OAuthFlow>,
#[serde(rename = "authorizationCode")]
pub authorization_code: Option<OAuthFlow>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct OAuthFlow {
#[serde(rename = "authorizationUrl")]
pub authorization_url: Option<String>,
#[serde(rename = "tokenUrl")]
pub token_url: Option<String>,
#[serde(rename = "refreshUrl")]
pub refresh_url: Option<String>,
pub scopes: HashMap<String, String>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct Link {
#[serde(rename = "operationRef")]
pub operation_ref: Option<String>,
#[serde(rename = "operationId")]
pub operation_id: Option<String>,
pub parameters: Option<HashMap<String, serde_json::Value>>,
#[serde(rename = "requestBody")]
pub request_body: Option<serde_json::Value>,
pub description: Option<String>,
pub server: Option<Server>,
}
#[derive(Debug, Deserialize, Serialize, Clone)]
pub(crate) struct Callback {
#[serde(flatten)]
pub expression: HashMap<String, serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct Service {
pub name: String,
pub description: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Endpoint {
pub path: String,
pub method: String,
pub services: Vec<String>, pub summary: Option<String>,
pub description: Option<String>,
pub operation_id: Option<String>,
pub parameters: Vec<Parameter>,
pub responses: IndexMap<String, Response>,
pub deprecated: bool,
pub untagged: bool,
}
#[derive(Debug, Clone)]
pub struct DocConfig {
pub group_by: GroupBy,
pub service_filter: Option<Vec<String>>,
pub path_filter: Option<String>,
pub method_filter: Option<Vec<String>>,
pub operation_selector: Option<OperationSelector>,
pub exclude_deprecated: bool,
pub required_only: bool,
pub detail_level: DetailLevel,
pub include_schemas: bool,
pub inline_schemas: bool,
pub schema_depth: Option<usize>,
pub schema_names: Vec<String>,
pub schema_fields: Vec<String>,
pub source_path: Option<String>,
pub include_examples: bool,
pub include_auth: bool,
pub include_toc: bool,
pub sort_method: SortMethod,
pub max_tokens: Option<usize>,
pub include_report: bool,
}
impl DocConfig {
pub fn unfiltered() -> Self {
Self {
group_by: GroupBy::Service,
service_filter: None,
path_filter: None,
method_filter: None,
operation_selector: None,
exclude_deprecated: false,
required_only: false,
detail_level: DetailLevel::Full,
include_schemas: true,
inline_schemas: false,
schema_depth: None,
schema_names: Vec::new(),
schema_fields: Vec::new(),
source_path: None,
include_examples: false,
include_auth: false,
include_toc: true,
sort_method: SortMethod::Alphabetical,
max_tokens: None,
include_report: false,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct OperationRef {
pub method: String,
pub path: String,
}
impl OperationRef {
pub fn matches(&self, endpoint: &Endpoint) -> bool {
endpoint.method == self.method && endpoint.path == self.path
}
}
impl std::fmt::Display for OperationRef {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.method, self.path)
}
}
#[derive(Debug, Clone, Default)]
pub struct OperationSelector {
pub operations: IndexSet<OperationRef>,
pub operation_ids: IndexSet<String>,
}
impl OperationSelector {
pub fn matches(&self, endpoint: &Endpoint) -> bool {
self.operations.iter().any(|op| op.matches(endpoint))
|| endpoint
.operation_id
.as_ref()
.is_some_and(|id| self.operation_ids.contains(id))
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum GroupBy {
Service,
Method,
Path,
Flat,
}
#[derive(Debug, Clone, PartialEq)]
pub enum DetailLevel {
Summary,
Basic,
Standard,
Full,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SortMethod {
Alphabetical,
PathLength,
None,
}
#[derive(Debug)]
pub struct ApiDocumentation {
pub title: String,
pub version: String,
pub description: Option<String>,
pub services: Vec<Service>,
pub endpoints: Vec<Endpoint>,
pub servers: Vec<String>,
pub security_schemes: IndexMap<String, String>,
pub schemas: IndexMap<String, Schema>,
pub examples: IndexMap<String, Example>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(feature = "cli"), allow(dead_code))]
pub(crate) enum SplitMode {
Service,
Tag,
Endpoint,
}