#![deny(missing_docs)]
#![warn(clippy::all)]
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AsyncApiSpec {
pub asyncapi: String,
pub info: Info,
#[serde(skip_serializing_if = "Option::is_none")]
pub servers: Option<IndexMap<String, Server>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub channels: Option<IndexMap<String, Channel>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub operations: Option<IndexMap<String, Operation>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub components: Option<Components>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Info {
pub title: String,
pub version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Server {
pub host: String,
pub protocol: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub pathname: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub variables: Option<IndexMap<String, ServerVariable>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub bindings: Option<ServerBindings>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerBindings {
#[serde(skip_serializing_if = "Option::is_none")]
pub mqtt: Option<MqttServerBindings>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MqttLastWill {
pub topic: String,
pub qos: u8,
pub message: String,
pub retain: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MqttBindingNumValue {
Value(u32),
Schema(Schema),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MqttServerBindings {
#[serde(skip_serializing_if = "Option::is_none", rename = "clientId")]
pub client_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "cleanSession")]
pub clean_session: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none", rename = "lastWill")]
pub last_will: Option<MqttLastWill>,
#[serde(skip_serializing_if = "Option::is_none", rename = "keepAlive")]
pub keep_alive: Option<u32>,
#[serde(
skip_serializing_if = "Option::is_none",
rename = "sessionExpiryInterval"
)]
pub session_expiry_interval: Option<MqttBindingNumValue>,
#[serde(skip_serializing_if = "Option::is_none", rename = "maximumPacketSize")]
pub max_packet_size: Option<MqttBindingNumValue>,
#[serde(skip_serializing_if = "Option::is_none", rename = "bindingVersion")]
pub binding_version: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerVariable {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<String>,
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
pub enum_values: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub examples: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Channel {
#[serde(skip_serializing_if = "Option::is_none")]
pub address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub messages: Option<IndexMap<String, MessageRef>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub parameters: Option<IndexMap<String, Parameter>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Parameter {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub default: Option<String>,
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
pub enum_values: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub examples: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub location: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum MessageRef {
Reference {
#[serde(rename = "$ref")]
reference: String,
},
Inline(Box<Message>),
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Message {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub summary: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "contentType", skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub payload: Option<Schema>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub bindings: Option<MessageBindings>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MessageBindings {
#[serde(skip_serializing_if = "Option::is_none")]
pub mqtt: Option<MqttMessageBindings>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum MqttResponseTopic {
Uri(String),
Schema(Schema),
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MqttMessageBindings {
#[serde(
skip_serializing_if = "Option::is_none",
rename = "payloadFormatIndicator"
)]
pub payload_format_indicator: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none", rename = "correlationData")]
pub correlation_data: Option<Schema>,
#[serde(skip_serializing_if = "Option::is_none", rename = "contentType")]
pub content_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "responseTopic")]
pub response_topic: Option<MqttResponseTopic>,
#[serde(skip_serializing_if = "Option::is_none", rename = "bindingVersion")]
pub binding_version: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct Operation {
pub action: OperationAction,
pub channel: ChannelRef,
#[serde(skip_serializing_if = "Option::is_none")]
pub messages: Option<Vec<MessageRef>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub bindings: Option<OperationBindings>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct OperationBindings {
#[serde(skip_serializing_if = "Option::is_none")]
pub mqtt: Option<MqttOperationBindings>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MqttOperationBindings {
#[serde(skip_serializing_if = "Option::is_none")]
pub qos: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub retain: Option<bool>,
#[serde(
skip_serializing_if = "Option::is_none",
rename = "messageExpiryInterval"
)]
pub message_expiry_interval: Option<MqttBindingNumValue>,
#[serde(skip_serializing_if = "Option::is_none", rename = "bindingVersion")]
pub binding_version: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum OperationAction {
#[default]
Send,
Receive,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ChannelRef {
#[serde(rename = "$ref")]
pub reference: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Components {
#[serde(skip_serializing_if = "Option::is_none")]
pub messages: Option<IndexMap<String, Message>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub schemas: Option<IndexMap<String, Schema>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Schema {
Reference {
#[serde(rename = "$ref")]
reference: String,
},
Object(Box<SchemaObject>),
Any(serde_json::Value),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SchemaObject {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub schema_type: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<IndexMap<String, Box<Schema>>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
pub enum_values: Option<Vec<serde_json::Value>>,
#[serde(rename = "const", skip_serializing_if = "Option::is_none")]
pub const_value: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub items: Option<Box<Schema>>,
#[serde(
rename = "additionalProperties",
skip_serializing_if = "Option::is_none"
)]
pub additional_properties: Option<Box<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 = "allOf", skip_serializing_if = "Option::is_none")]
pub all_of: Option<Vec<Schema>>,
#[serde(flatten)]
pub additional: IndexMap<String, serde_json::Value>,
}
impl Default for AsyncApiSpec {
fn default() -> Self {
Self {
asyncapi: "3.0.0".to_string(),
info: Info {
title: "API".to_string(),
version: "1.0.0".to_string(),
description: None,
},
servers: None,
channels: None,
operations: None,
components: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spec_serialization() {
let spec = AsyncApiSpec::default();
let json = serde_json::to_string(&spec).unwrap();
assert!(json.contains("asyncapi"));
assert!(json.contains("3.0.0"));
}
#[test]
fn test_spec_deserialization() {
let json = r#"{
"asyncapi": "3.0.0",
"info": {
"title": "Test API",
"version": "1.0.0"
}
}"#;
let spec: AsyncApiSpec = serde_json::from_str(json).unwrap();
assert_eq!(spec.asyncapi, "3.0.0");
assert_eq!(spec.info.title, "Test API");
}
}