#![cfg_attr(not(feature = "std"), no_std)]
#![warn(missing_docs, missing_debug_implementations)]
extern crate alloc;
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct OpenRpc {
#[cfg_attr(feature = "relaxed", serde(default = "serde_fns::openrpc_version"))]
pub openrpc: String,
#[cfg_attr(feature = "relaxed", serde(default = "serde_fns::info"))]
pub info: Info,
#[serde(default = "serde_fns::servers")]
pub servers: Vec<Server>,
pub methods: Vec<RefOr<Method>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub components: Option<Components>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub external_docs: Option<ExternalDocumentation>,
}
impl OpenRpc {
pub fn get_schema(&self, reference: &str) -> Option<&Schema> {
let mut components = reference.split('/');
if !matches!(components.next(), Some("#")) {
return None;
}
if !matches!(components.next(), Some("components")) {
return None;
}
if !matches!(components.next(), Some("schemas")) {
return None;
}
let name = components.next()?;
self.components.as_ref()?.schemas.get(name)
}
pub fn get_error(&self, reference: &str) -> Option<&Error> {
let mut components = reference.split('/');
if !matches!(components.next(), Some("#")) {
return None;
}
if !matches!(components.next(), Some("components")) {
return None;
}
if !matches!(components.next(), Some("errors")) {
return None;
}
let name = components.next()?;
self.components.as_ref()?.errors.get(name)
}
pub fn get_content_descriptor(&self, reference: &str) -> Option<&ContentDescriptor> {
let mut components = reference.split('/');
if !matches!(components.next(), Some("#")) {
return None;
}
if !matches!(components.next(), Some("components")) {
return None;
}
if !matches!(components.next(), Some("contentDescriptors")) {
return None;
}
let name = components.next()?;
self.components.as_ref()?.content_descriptors.get(name)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Info {
#[cfg_attr(feature = "relaxed", serde(default))]
pub title: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub terms_of_service: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub contact: Option<Contact>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub license: Option<License>,
#[cfg_attr(feature = "relaxed", serde(default))]
pub version: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Contact {
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub name: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub url: Option<String>,
#[cfg_attr(
feature = "serde",
serde(default, skip_serializing_if = "Option::is_none")
)]
pub email: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct License {
#[cfg_attr(feature = "relaxed", serde(default))]
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Server {
#[cfg_attr(feature = "relaxed", serde(default))]
pub name: String,
#[cfg_attr(feature = "relaxed", serde(default = "serde_fns::runtime_expr"))]
pub url: RuntimeExpression,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub variables: BTreeMap<String, ServerVariable>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ServerVariable {
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub enum_: Vec<String>,
#[cfg_attr(feature = "relaxed", serde(default))]
pub default: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Method {
#[cfg_attr(feature = "relaxed", serde(default))]
pub name: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<RefOr<Tag>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub external_docs: Option<ExternalDocumentation>,
#[cfg_attr(feature = "relaxed", serde(default))]
pub params: Vec<RefOr<ContentDescriptor>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub result: Option<RefOr<ContentDescriptor>>,
#[serde(default, skip_serializing_if = "serde_fns::is_false")]
pub deprecated: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub servers: Option<Vec<Server>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub errors: Vec<RefOr<Error>>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub links: Vec<RefOr<Link>>,
#[serde(default, skip_serializing_if = "serde_fns::is_default")]
pub param_structure: ParamStructure,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub examples: Vec<RefOr<ExamplePairing>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
#[serde(rename_all = "kebab-case")]
pub enum ParamStructure {
ByName,
ByPosition,
#[default]
Either,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ContentDescriptor {
#[cfg_attr(feature = "relaxed", serde(default))]
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "serde_fns::is_false")]
pub required: bool,
#[cfg_attr(feature = "relaxed", serde(default))]
pub schema: Schema,
#[serde(default, skip_serializing_if = "serde_fns::is_false")]
pub deprecated: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct Schema {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(flatten)]
pub contents: SchemaContents,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum SchemaContents {
Reference {
#[serde(rename = "$ref")]
reference: String,
},
Literal(Literal),
AllOf {
#[serde(rename = "allOf")]
all_of: Vec<Schema>,
},
AnyOf {
#[serde(rename = "anyOf")]
any_of: Vec<Schema>,
},
OneOf {
#[serde(rename = "oneOf")]
one_of: Vec<Schema>,
},
}
impl Default for SchemaContents {
#[inline]
fn default() -> Self {
Self::Literal(Literal::Null)
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum Literal {
Boolean,
Integer(IntegerLiteral),
Number(NumberLiteral),
Array(ArrayLiteral),
String(StringLiteral),
Object(ObjectLiteral),
Null,
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Debug, Clone)]
pub struct IntegerLiteral {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub multiple_of: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub minimum: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub maximum: Option<i64>,
#[serde(default, skip_serializing_if = "serde_fns::is_false")]
pub exclusive_minimum: bool,
#[serde(default, skip_serializing_if = "serde_fns::is_false")]
pub exclusive_maximum: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct NumberLiteral {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub multiple_of: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub minimum: Option<f64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub maximum: Option<f64>,
#[serde(default, skip_serializing_if = "serde_fns::is_false")]
pub exclusive_minimum: bool,
#[serde(default, skip_serializing_if = "serde_fns::is_false")]
pub exclusive_maximum: bool,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ArrayLiteral {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub items: Option<Box<Schema>>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct StringLiteral {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub min_length: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub max_length: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub pattern: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub format: Option<StringFormat>,
#[serde(default, skip_serializing_if = "Option::is_none", rename = "enum")]
pub enumeration: Option<Vec<String>>,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[serde(rename_all = "kebab-case")]
pub enum StringFormat {
DateTime,
Time,
Date,
Duration,
Email,
IdnEmail,
Hostname,
IdnHostname,
#[serde(rename = "ipv4")]
IpV4,
#[serde(rename = "ipv6")]
IpV6,
Uuid,
Uri,
UriReference,
Iri,
IriReference,
UriTemplate,
JsonPointer,
RelativeJsonPointer,
Regex,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ObjectLiteral {
pub properties: BTreeMap<String, Schema>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub required: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ExamplePairing {
#[cfg_attr(feature = "relaxed", serde(default))]
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[cfg_attr(feature = "relaxed", serde(default))]
pub params: Vec<RefOr<ExampleObject>>,
#[cfg_attr(feature = "relaxed", serde(default))]
pub result: RefOr<ExampleObject>,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ExampleObject {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<ExampleValue>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ExampleValue {
#[cfg(feature = "serde_json")]
#[serde(rename = "value")]
Value(serde_json::Value),
#[serde(rename = "externalValue")]
External(String),
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[derive(Debug, Clone)]
pub struct Link {
#[cfg_attr(feature = "relaxed", serde(default))]
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub method: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub params: Option<LinkParams>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub server: Option<Server>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum LinkParams {
Dynamic(RuntimeExpression),
#[cfg(feature = "serde_json")]
Constant(BTreeMap<String, serde_json::Value>),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(transparent)]
pub struct RuntimeExpression(pub String);
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Error {
#[cfg_attr(feature = "relaxed", serde(default))]
pub code: i64,
#[cfg_attr(feature = "relaxed", serde(default))]
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[cfg(feature = "serde_json")]
pub data: Option<serde_json::Value>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Components {
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub content_descriptors: BTreeMap<String, ContentDescriptor>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub schemas: BTreeMap<String, Schema>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub examples: BTreeMap<String, ExampleObject>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub links: BTreeMap<String, Link>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub errors: BTreeMap<String, Error>,
#[serde(
default,
skip_serializing_if = "BTreeMap::is_empty",
rename = "examplePairingObjects"
)]
pub example_pairings: BTreeMap<String, ExamplePairing>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub tags: BTreeMap<String, Tag>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Tag {
#[cfg_attr(feature = "relaxed", serde(default))]
pub name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub external_docs: Option<ExternalDocumentation>,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ExternalDocumentation {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[cfg_attr(feature = "relaxed", serde(default))]
pub url: String,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum RefOr<T> {
Reference {
#[serde(rename = "$ref")]
reference: String,
},
Inline(T),
}
impl<T: Default> Default for RefOr<T> {
#[inline]
fn default() -> Self {
RefOr::Inline(T::default())
}
}
mod serde_fns {
use alloc::collections::BTreeMap;
use crate::{Info, RuntimeExpression, Server};
pub fn servers() -> Vec<Server> {
alloc::vec![Server {
name: "default".into(),
url: RuntimeExpression("localhost".into()),
summary: None,
description: None,
variables: BTreeMap::new(),
}]
}
#[inline]
pub fn is_false(b: &bool) -> bool {
!*b
}
pub fn is_default<T: Default + PartialEq>(t: &T) -> bool {
*t == T::default()
}
#[cfg(feature = "relaxed")]
pub fn runtime_expr() -> RuntimeExpression {
RuntimeExpression(String::new())
}
#[cfg(feature = "relaxed")]
pub fn openrpc_version() -> String {
"1.0.0-rc1".into()
}
#[cfg(feature = "relaxed")]
pub fn info() -> Info {
Info {
title: "OpenRPC API".into(),
description: None,
terms_of_service: None,
contact: None,
license: None,
version: "1.0.0-rc1".into(),
}
}
}