use crate::{
v3_0::components::{Components, ObjectOrReference},
Str,
};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json;
use url::Url;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Spec {
pub openapi: Str,
pub info: Info,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub servers: Vec<Server>,
pub paths: IndexMap<Str, PathItem>,
#[serde(skip_serializing_if = "Option::is_none")]
pub components: Option<Components>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub security: Vec<SecurityRequirement>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<Tag>,
#[serde(skip_serializing_if = "Option::is_none", rename = "externalDocs")]
pub external_docs: Option<ExternalDoc>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Info {
pub title: Str,
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
#[serde(rename = "termsOfService", skip_serializing_if = "Option::is_none")]
pub terms_of_service: Option<Url>,
pub version: Str,
#[serde(skip_serializing_if = "Option::is_none")]
pub contact: Option<Contact>,
#[serde(skip_serializing_if = "Option::is_none")]
pub license: Option<License>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Contact {
#[serde(skip_serializing_if = "str::is_empty")]
pub name: Str,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<Url>,
#[serde(skip_serializing_if = "str::is_empty")]
pub email: Str,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct License {
pub name: Str,
#[serde(skip_serializing_if = "Option::is_none")]
pub url: Option<Url>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Server {
pub url: Str,
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
pub variables: IndexMap<Str, ServerVariable>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct ServerVariable {
pub default: Str,
#[serde(rename = "enum", skip_serializing_if = "Vec::is_empty")]
pub substitutions_enum: Vec<Str>,
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct PathItem {
#[serde(skip_serializing_if = "str::is_empty", rename = "$ref")]
pub reference: Str,
#[serde(skip_serializing_if = "str::is_empty")]
pub summary: Str,
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
#[serde(skip_serializing_if = "Option::is_none")]
pub get: Option<Operation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub put: Option<Operation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub post: Option<Operation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub delete: Option<Operation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub options: Option<Operation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub head: Option<Operation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub patch: Option<Operation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub trace: Option<Operation>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub servers: Vec<Server>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub parameters: Vec<ObjectOrReference<Parameter>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Operation {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<Str>,
#[serde(skip_serializing_if = "str::is_empty")]
pub summary: Str,
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
#[serde(skip_serializing_if = "Option::is_none", rename = "externalDocs")]
pub external_docs: Option<ExternalDoc>,
#[serde(skip_serializing_if = "str::is_empty", rename = "operationId")]
pub operation_id: Str,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub parameters: Vec<ObjectOrReference<Parameter>>,
#[serde(skip_serializing_if = "Option::is_none", rename = "requestBody")]
pub request_body: Option<ObjectOrReference<RequestBody>>,
pub responses: IndexMap<Str, Response>,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
pub callbacks: IndexMap<Str, Callback>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<bool>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub security: Vec<SecurityRequirement>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub servers: Vec<Server>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct Parameter {
pub name: Str,
#[serde(rename = "in")]
pub location: Location,
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deprecated: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub style: Option<ParameterStyle>,
#[serde(skip_serializing_if = "Option::is_none")]
pub explode: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub allow_reserved: Option<bool>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub representation: Option<ParameterRepresentation>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub example: Option<ParameterExamples>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum ParameterRepresentation {
Simple {
schema: ComponentOrInlineSchema,
},
Content {
content: IndexMap<Str, MediaType>,
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum ParameterExamples {
One {
example: Option<serde_json::Value>,
},
Multiple {
examples: IndexMap<Str, ObjectOrReference<Example>>,
},
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum Type {
#[serde(rename = "string")]
String,
#[serde(rename = "number")]
Number,
#[serde(rename = "integer")]
Integer,
#[serde(rename = "boolean")]
Boolean,
#[serde(rename = "array")]
Array,
#[serde(rename = "object")]
Object,
#[serde(rename = "file")]
File,
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]
pub enum Location {
#[serde(rename = "query")]
Query,
#[serde(rename = "header")]
Header,
#[serde(rename = "path")]
Path,
#[serde(rename = "formData")]
FormData,
}
impl Default for Location {
fn default() -> Self {
Location::Query
}
}
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum ParameterStyle {
Matrix,
Label,
Form,
Simple,
SpaceDelimited,
PipeDelimited,
DeepObject,
}
mod component_ser_as_ref {
use super::*;
use serde::*;
const PATH_REF_PREFIX: &str = "#/components/schemas/";
pub fn serialize<S: Serializer>(component: &Str, ser: S) -> Result<S::Ok, S::Error> {
ser.serialize_str(&(PATH_REF_PREFIX.to_string() + component))
}
pub fn deserialize<'de, D: Deserializer<'de>>(deser: D) -> Result<Str, D::Error> {
let s = String::deserialize(deser)?;
if let Some(s) = s.strip_prefix(PATH_REF_PREFIX) {
Ok(Str::Owned(s.to_string()))
} else {
Err(de::Error::custom("not a component schema reference path"))
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum ComponentOrInlineSchema {
Component {
#[serde(rename = "$ref", serialize_with = "component_ser_as_ref::serialize", deserialize_with = "component_ser_as_ref::deserialize")]
name: Str
},
Inline(Schema),
}
impl ComponentOrInlineSchema {
pub fn unwrap(&self) -> Option<&Schema> {
match self {
Self::Inline(s) => Some(s),
Self::Component{..} => None,
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct Schema {
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
#[serde(skip_serializing_if = "str::is_empty")]
pub format: Str,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Box<ComponentOrInlineSchema>>,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
pub properties: IndexMap<Str, ComponentOrInlineSchema>,
#[serde(skip_serializing_if = "Option::is_none")]
pub read_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub write_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nullable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub additional_properties: Option<Box<ComponentOrInlineSchema>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub example: Option<serde_json::value::Value>,
#[serde(skip_serializing_if = "str::is_empty")]
pub title: Str,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub all_of: Vec<ComponentOrInlineSchema>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub one_of: Vec<ComponentOrInlineSchema>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub any_of: Vec<ComponentOrInlineSchema>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub schema_type: Option<Type>,
#[serde(rename = "enum", skip_serializing_if = "Vec::is_empty")]
pub enum_values: Vec<Str>,
#[serde(rename = "const", skip_serializing_if = "Option::is_none")]
pub const_value: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub multiple_of: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minimum: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclusive_minimum: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maximum: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclusive_maximum: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_length: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_length: Option<usize>,
#[serde(skip_serializing_if = "str::is_empty")]
pub pattern: Str,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_items: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_items: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub unique_items: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_properties: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_properties: Option<usize>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub required: Vec<Str>,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
pub dependent_required: IndexMap<Str, Vec<Str>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Response {
pub description: Str,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
pub headers: IndexMap<Str, ObjectOrReference<Header>>,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
pub content: IndexMap<Str, MediaType>,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
pub links: IndexMap<Str, ObjectOrReference<Link>>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Header {
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schema: Option<ComponentOrInlineSchema>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "uniqueItems")]
pub unique_items: Option<bool>,
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub param_type: Option<Type>,
#[serde(skip_serializing_if = "str::is_empty")]
pub format: Str,
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct RequestBody {
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
pub content: IndexMap<Str, MediaType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct Link {
#[serde(flatten)]
operation: LinkOperation,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
parameters: IndexMap<Str, RuntimeExpressionOrValue>,
#[serde(rename = "requestBody", skip_serializing_if = "Option::is_none")]
request_body: Option<RuntimeExpressionOrValue>,
#[serde(skip_serializing_if = "str::is_empty")]
description: Str,
#[serde(skip_serializing_if = "Option::is_none")]
server: Option<Server>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum RuntimeExpressionOrValue {
RuntimeExpression(Str),
LiteralValue(serde_json::Value),
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum LinkOperation {
Id {
#[serde(rename = "operationId")]
operation_id: Str,
},
Ref {
#[serde(rename = "operationRef")]
operation_ref: Str,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct MediaType {
#[serde(skip_serializing_if = "Option::is_none")]
pub schema: Option<ComponentOrInlineSchema>,
#[serde(flatten, skip_serializing_if = "Option::is_none")]
pub examples: Option<MediaTypeExample>,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
pub encoding: IndexMap<Str, Encoding>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum MediaTypeExample {
Example { example: serde_json::Value },
Examples {
examples: IndexMap<Str, ObjectOrReference<Example>>,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Encoding {
#[serde(skip_serializing_if = "str::is_empty", rename = "contentType")]
pub content_type: Str,
#[serde(skip_serializing_if = "IndexMap::is_empty")]
pub headers: IndexMap<Str, ObjectOrReference<Header>>,
#[serde(skip_serializing_if = "str::is_empty")]
pub style: Str,
#[serde(skip_serializing_if = "Option::is_none")]
pub explode: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", rename = "allowReserved")]
pub allow_reserved: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Example {
#[serde(skip_serializing_if = "str::is_empty")]
pub summary: Str,
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
#[serde(skip_serializing_if = "Option::is_none", flatten)]
pub value: Option<ExampleValue>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum ExampleValue {
Embedded {
value: serde_json::Value
},
External {
#[serde(rename = "externalValue")]
external_value: Str
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(tag = "type")]
pub enum SecurityScheme {
#[serde(rename = "apiKey")]
ApiKey {
name: Str,
#[serde(rename = "in")]
location: Str,
},
#[serde(rename = "http")]
Http {
scheme: Str,
#[serde(rename = "bearerFormat")]
bearer_format: Str,
},
#[serde(rename = "oauth2")]
OAuth2 { flows: Flows },
#[serde(rename = "openIdConnect")]
OpenIdConnect {
#[serde(rename = "openIdConnectUrl")]
open_id_connect_url: Str,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Flows {
#[serde(skip_serializing_if = "Option::is_none")]
pub implicit: Option<ImplicitFlow>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<PasswordFlow>,
#[serde(skip_serializing_if = "Option::is_none")]
pub client_credentials: Option<ClientCredentialsFlow>,
#[serde(skip_serializing_if = "Option::is_none")]
pub authorization_code: Option<AuthorizationCodeFlow>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ImplicitFlow {
pub authorization_url: Url,
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_url: Option<Url>,
pub scopes: IndexMap<Str, Str>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct PasswordFlow {
token_url: Url,
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_url: Option<Url>,
pub scopes: IndexMap<Str, Str>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ClientCredentialsFlow {
token_url: Url,
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_url: Option<Url>,
pub scopes: IndexMap<Str, Str>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct AuthorizationCodeFlow {
pub authorization_url: Url,
token_url: Url,
#[serde(skip_serializing_if = "Option::is_none")]
pub refresh_url: Option<Url>,
pub scopes: IndexMap<Str, Str>,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Callback(
serde_json::Value, );
pub type SecurityRequirement = IndexMap<Str, Vec<Str>>;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Default)]
pub struct Tag {
pub name: Str,
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
pub struct ExternalDoc {
pub url: Url,
#[serde(skip_serializing_if = "str::is_empty")]
pub description: Str,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_security_scheme_oauth_deser() {
const IMPLICIT_OAUTH2_SAMPLE: &str = r#"{
"type": "oauth2",
"flows": {
"implicit": {
"authorizationUrl": "https://example.com/api/oauth/dialog",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
},
"authorizationCode": {
"authorizationUrl": "https://example.com/api/oauth/dialog",
"tokenUrl": "https://example.com/api/oauth/token",
"scopes": {
"write:pets": "modify pets in your account",
"read:pets": "read your pets"
}
}
}
}"#;
let obj: SecurityScheme = serde_json::from_str(&IMPLICIT_OAUTH2_SAMPLE).unwrap();
match obj {
SecurityScheme::OAuth2 { flows } => {
assert!(flows.implicit.is_some());
let implicit = flows.implicit.unwrap();
assert_eq!(
implicit.authorization_url,
Url::parse("https://example.com/api/oauth/dialog").unwrap()
);
assert!(implicit.scopes.contains_key("write:pets"));
assert!(implicit.scopes.contains_key("read:pets"));
assert!(flows.authorization_code.is_some());
let auth_code = flows.authorization_code.unwrap();
assert_eq!(
auth_code.authorization_url,
Url::parse("https://example.com/api/oauth/dialog").unwrap()
);
assert_eq!(
auth_code.token_url,
Url::parse("https://example.com/api/oauth/token").unwrap()
);
assert!(implicit.scopes.contains_key("write:pets"));
assert!(implicit.scopes.contains_key("read:pets"));
}
_ => assert!(false, "wrong security scheme type"),
}
}
}