asyncapi_rust_models/
lib.rs

1//! Runtime data structures for AsyncAPI 3.0 specifications
2//!
3//! This crate provides Rust types that represent [AsyncAPI 3.0](https://www.asyncapi.com/docs/reference/specification/v3.0.0)
4//! specification objects. These types are used by the proc macros to generate
5//! specifications at compile time and can also be constructed manually.
6//!
7//! ## Overview
8//!
9//! The main types mirror the AsyncAPI 3.0 specification structure:
10//!
11//! - [`AsyncApiSpec`] - Root specification object
12//! - [`Info`] - General API information
13//! - [`Server`] - Server connection details
14//! - [`Channel`] - Communication channels
15//! - [`Operation`] - Send/receive operations
16//! - [`Message`] - Message definitions
17//! - [`Schema`] - JSON Schema definitions
18//! - [`Components`] - Reusable components
19//!
20//! ## Serialization
21//!
22//! All types implement [`serde::Serialize`] and [`serde::Deserialize`] for JSON
23//! serialization, following the AsyncAPI 3.0 specification's JSON Schema.
24//!
25//! ## Example
26//!
27//! ```rust
28//! use asyncapi_rust_models::*;
29//! use std::collections::HashMap;
30//!
31//! // Create a simple AsyncAPI specification
32//! let spec = AsyncApiSpec {
33//!     asyncapi: "3.0.0".to_string(),
34//!     info: Info {
35//!         title: "My API".to_string(),
36//!         version: "1.0.0".to_string(),
37//!         description: Some("A simple API".to_string()),
38//!     },
39//!     servers: None,
40//!     channels: None,
41//!     operations: None,
42//!     components: None,
43//! };
44//!
45//! // Serialize to JSON
46//! let json = serde_json::to_string_pretty(&spec).unwrap();
47//! ```
48
49#![deny(missing_docs)]
50#![warn(clippy::all)]
51
52use serde::{Deserialize, Serialize};
53use std::collections::HashMap;
54
55/// AsyncAPI 3.0 Specification
56///
57/// Root document object representing a complete AsyncAPI specification.
58///
59/// This is the top-level object that contains all information about an API,
60/// including servers, channels, operations, and reusable components.
61///
62/// # Example
63///
64/// ```rust
65/// use asyncapi_rust_models::*;
66///
67/// let spec = AsyncApiSpec {
68///     asyncapi: "3.0.0".to_string(),
69///     info: Info {
70///         title: "My WebSocket API".to_string(),
71///         version: "1.0.0".to_string(),
72///         description: Some("Real-time messaging API".to_string()),
73///     },
74///     servers: None,
75///     channels: None,
76///     operations: None,
77///     components: None,
78/// };
79/// ```
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct AsyncApiSpec {
82    /// AsyncAPI version (e.g., "3.0.0")
83    pub asyncapi: String,
84
85    /// General information about the API
86    pub info: Info,
87
88    /// Server connection details
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub servers: Option<HashMap<String, Server>>,
91
92    /// Available channels (communication paths)
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub channels: Option<HashMap<String, Channel>>,
95
96    /// Operations (send/receive)
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub operations: Option<HashMap<String, Operation>>,
99
100    /// Reusable components (messages, schemas, etc.)
101    #[serde(skip_serializing_if = "Option::is_none")]
102    pub components: Option<Components>,
103}
104
105/// API information object
106///
107/// Contains general metadata about the API such as title, version, and description.
108/// This information is displayed in documentation tools and helps users understand
109/// the purpose and version of the API.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct Info {
112    /// API title
113    ///
114    /// A human-readable name for the API (e.g., "Chat WebSocket API")
115    pub title: String,
116
117    /// API version
118    ///
119    /// The version of the API (e.g., "1.0.0"). Should follow semantic versioning.
120    pub version: String,
121
122    /// API description
123    ///
124    /// A longer description of the API's purpose and functionality (optional).
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub description: Option<String>,
127}
128
129/// Server connection information
130///
131/// Defines connection details for a server that hosts the API. Multiple servers
132/// can be defined to support different environments (production, staging, development).
133///
134/// # Example
135///
136/// ```rust
137/// use asyncapi_rust_models::Server;
138///
139/// let server = Server {
140///     host: "chat.example.com:443".to_string(),
141///     protocol: "wss".to_string(),
142///     description: Some("Production WebSocket server".to_string()),
143/// };
144/// ```
145#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct Server {
147    /// Server URL or host
148    ///
149    /// The hostname or URL where the server is hosted. May include port number.
150    /// Examples: "localhost:8080", "api.example.com", "ws.example.com:443"
151    pub host: String,
152
153    /// Protocol (e.g., "wss", "ws", "grpc")
154    ///
155    /// The protocol used to communicate with the server.
156    /// Common values: "ws" (WebSocket), "wss" (WebSocket Secure), "grpc", "mqtt"
157    pub protocol: String,
158
159    /// Server description
160    ///
161    /// An optional human-readable description of the server's purpose or environment
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub description: Option<String>,
164}
165
166/// Communication channel
167///
168/// Represents a communication path through which messages are exchanged.
169/// Channels define where messages are sent and received (e.g., WebSocket endpoints,
170/// message queue topics, gRPC methods).
171///
172/// # Example
173///
174/// ```rust
175/// use asyncapi_rust_models::Channel;
176///
177/// let channel = Channel {
178///     address: Some("/ws/chat".to_string()),
179///     messages: None, // Messages are typically added via operations
180/// };
181/// ```
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct Channel {
184    /// Channel address/path
185    ///
186    /// The location where this channel is available. For WebSocket, this is typically
187    /// the WebSocket path (e.g., "/ws/chat"). For other protocols, this could be a
188    /// topic name, queue name, or method path.
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub address: Option<String>,
191
192    /// Messages available on this channel
193    ///
194    /// A map of message identifiers to message definitions or references.
195    /// Messages define the structure of data that flows through this channel.
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub messages: Option<HashMap<String, MessageRef>>,
198}
199
200/// Reference to a message definition
201///
202/// Messages can be defined either inline or as references to reusable components.
203/// This enum supports both patterns, following the AsyncAPI 3.0 specification.
204///
205/// # Example
206///
207/// ```rust
208/// use asyncapi_rust_models::{MessageRef, Message};
209///
210/// // Reference to a component message
211/// let ref_msg = MessageRef::Reference {
212///     reference: "#/components/messages/ChatMessage".to_string(),
213/// };
214///
215/// // Inline message definition
216/// let inline_msg = MessageRef::Inline(Box::new(Message {
217///     name: Some("ChatMessage".to_string()),
218///     title: Some("Chat Message".to_string()),
219///     summary: Some("A chat message".to_string()),
220///     description: None,
221///     content_type: Some("application/json".to_string()),
222///     payload: None,
223/// }));
224/// ```
225#[derive(Debug, Clone, Serialize, Deserialize)]
226#[serde(untagged)]
227pub enum MessageRef {
228    /// Reference to component message
229    ///
230    /// Points to a reusable message definition in the components section.
231    /// Format: "#/components/messages/{messageName}"
232    Reference {
233        /// $ref path
234        #[serde(rename = "$ref")]
235        reference: String,
236    },
237    /// Inline message definition
238    ///
239    /// Embeds the message definition directly rather than referencing a component
240    Inline(Box<Message>),
241}
242
243/// Message definition
244///
245/// Represents a message that can be sent or received through a channel.
246/// Messages describe the structure, content type, and documentation for data
247/// exchanged in asynchronous communication.
248///
249/// # Example
250///
251/// ```rust
252/// use asyncapi_rust_models::{Message, Schema, SchemaObject};
253/// use std::collections::HashMap;
254///
255/// let message = Message {
256///     name: Some("ChatMessage".to_string()),
257///     title: Some("Chat Message".to_string()),
258///     summary: Some("A message in a chat room".to_string()),
259///     description: Some("Sent when a user posts a message".to_string()),
260///     content_type: Some("application/json".to_string()),
261///     payload: Some(Schema::Object(Box::new(SchemaObject {
262///         schema_type: Some("object".to_string()),
263///         properties: None,
264///         required: None,
265///         description: Some("Chat message payload".to_string()),
266///         title: None,
267///         enum_values: None,
268///         const_value: None,
269///         items: None,
270///         additional_properties: None,
271///         one_of: None,
272///         any_of: None,
273///         all_of: None,
274///         additional: HashMap::new(),
275///     }))),
276/// };
277/// ```
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct Message {
280    /// Message name
281    ///
282    /// A machine-readable identifier for the message (e.g., "ChatMessage", "user.join")
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub name: Option<String>,
285
286    /// Message title
287    ///
288    /// A human-readable title for the message
289    #[serde(skip_serializing_if = "Option::is_none")]
290    pub title: Option<String>,
291
292    /// Message summary
293    ///
294    /// A short summary of what the message is for
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub summary: Option<String>,
297
298    /// Message description
299    ///
300    /// A detailed description of the message's purpose and usage
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub description: Option<String>,
303
304    /// Content type (e.g., "application/json")
305    ///
306    /// The MIME type of the message payload. Common values:
307    /// - "application/json" (default for text messages)
308    /// - "application/octet-stream" (binary data)
309    /// - "application/x-protobuf" (Protocol Buffers)
310    /// - "application/x-msgpack" (MessagePack)
311    #[serde(rename = "contentType", skip_serializing_if = "Option::is_none")]
312    pub content_type: Option<String>,
313
314    /// Message payload schema
315    ///
316    /// JSON Schema defining the structure of the message payload
317    #[serde(skip_serializing_if = "Option::is_none")]
318    pub payload: Option<Schema>,
319}
320
321/// Operation (send or receive)
322///
323/// Defines an action that can be performed on a channel. Operations describe
324/// whether an application sends or receives messages through a specific channel.
325///
326/// # Example
327///
328/// ```rust
329/// use asyncapi_rust_models::{Operation, OperationAction, ChannelRef};
330///
331/// let operation = Operation {
332///     action: OperationAction::Send,
333///     channel: ChannelRef {
334///         reference: "#/channels/chat".to_string(),
335///     },
336///     messages: None,
337/// };
338/// ```
339#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct Operation {
341    /// Operation action (send or receive)
342    ///
343    /// Specifies whether the application sends or receives messages
344    pub action: OperationAction,
345
346    /// Channel reference
347    ///
348    /// Points to the channel where this operation takes place
349    pub channel: ChannelRef,
350
351    /// Messages for this operation
352    ///
353    /// Optional list of messages that can be used with this operation
354    #[serde(skip_serializing_if = "Option::is_none")]
355    pub messages: Option<Vec<MessageRef>>,
356}
357
358/// Operation action type
359#[derive(Debug, Clone, Serialize, Deserialize)]
360#[serde(rename_all = "lowercase")]
361pub enum OperationAction {
362    /// Send message
363    Send,
364    /// Receive message
365    Receive,
366}
367
368/// Reference to a channel
369#[derive(Debug, Clone, Serialize, Deserialize)]
370pub struct ChannelRef {
371    /// $ref path
372    #[serde(rename = "$ref")]
373    pub reference: String,
374}
375
376/// Reusable components
377#[derive(Debug, Clone, Serialize, Deserialize)]
378pub struct Components {
379    /// Message definitions
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub messages: Option<HashMap<String, Message>>,
382
383    /// Schema definitions
384    #[serde(skip_serializing_if = "Option::is_none")]
385    pub schemas: Option<HashMap<String, Schema>>,
386}
387
388/// JSON Schema object
389///
390/// Flexible representation that can hold any valid JSON Schema. This type supports
391/// both schema references (using `$ref`) and complete inline schema definitions.
392///
393/// Schemas define the structure and validation rules for message payloads,
394/// following the JSON Schema specification.
395///
396/// # Example
397///
398/// ## Reference Schema
399///
400/// ```rust
401/// use asyncapi_rust_models::Schema;
402///
403/// let schema = Schema::Reference {
404///     reference: "#/components/schemas/ChatMessage".to_string(),
405/// };
406/// ```
407///
408/// ## Object Schema
409///
410/// ```rust
411/// use asyncapi_rust_models::{Schema, SchemaObject};
412/// use std::collections::HashMap;
413///
414/// let schema = Schema::Object(Box::new(SchemaObject {
415///     schema_type: Some("object".to_string()),
416///     properties: None,
417///     required: Some(vec!["username".to_string(), "room".to_string()]),
418///     description: Some("A chat message".to_string()),
419///     title: Some("ChatMessage".to_string()),
420///     enum_values: None,
421///     const_value: None,
422///     items: None,
423///     additional_properties: None,
424///     one_of: None,
425///     any_of: None,
426///     all_of: None,
427///     additional: HashMap::new(),
428/// }));
429/// ```
430#[derive(Debug, Clone, Serialize, Deserialize)]
431#[serde(untagged)]
432pub enum Schema {
433    /// Reference to another schema ($ref)
434    ///
435    /// Points to a reusable schema definition in the components section.
436    /// Format: "#/components/schemas/{schemaName}"
437    Reference {
438        /// $ref path
439        #[serde(rename = "$ref")]
440        reference: String,
441    },
442    /// Full schema object (boxed to reduce enum size)
443    ///
444    /// Contains a complete JSON Schema definition with all properties inline
445    Object(Box<SchemaObject>),
446}
447
448/// Schema object with all JSON Schema properties
449///
450/// Complete representation of a JSON Schema with support for all standard properties.
451/// This struct provides fine-grained control over schema definitions for message payloads.
452///
453/// # Example
454///
455/// ```rust
456/// use asyncapi_rust_models::{Schema, SchemaObject};
457/// use std::collections::HashMap;
458///
459/// // String property schema
460/// let username_schema = Schema::Object(Box::new(SchemaObject {
461///     schema_type: Some("string".to_string()),
462///     properties: None,
463///     required: None,
464///     description: Some("User's display name".to_string()),
465///     title: None,
466///     enum_values: None,
467///     const_value: None,
468///     items: None,
469///     additional_properties: None,
470///     one_of: None,
471///     any_of: None,
472///     all_of: None,
473///     additional: HashMap::new(),
474/// }));
475///
476/// // Object schema with properties
477/// let mut properties = HashMap::new();
478/// properties.insert("username".to_string(), Box::new(username_schema));
479///
480/// let message_schema = SchemaObject {
481///     schema_type: Some("object".to_string()),
482///     properties: Some(properties),
483///     required: Some(vec!["username".to_string()]),
484///     description: Some("A chat message".to_string()),
485///     title: Some("ChatMessage".to_string()),
486///     enum_values: None,
487///     const_value: None,
488///     items: None,
489///     additional_properties: None,
490///     one_of: None,
491///     any_of: None,
492///     all_of: None,
493///     additional: HashMap::new(),
494/// };
495/// ```
496#[derive(Debug, Clone, Serialize, Deserialize)]
497pub struct SchemaObject {
498    /// Schema type
499    ///
500    /// The JSON Schema type: "object", "array", "string", "number", "integer", "boolean", "null"
501    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
502    pub schema_type: Option<String>,
503
504    /// Properties (for object type)
505    ///
506    /// Map of property names to their schemas when schema_type is "object"
507    #[serde(skip_serializing_if = "Option::is_none")]
508    pub properties: Option<HashMap<String, Box<Schema>>>,
509
510    /// Required properties
511    ///
512    /// List of property names that must be present (for object types)
513    #[serde(skip_serializing_if = "Option::is_none")]
514    pub required: Option<Vec<String>>,
515
516    /// Description
517    ///
518    /// Human-readable description of what this schema represents
519    #[serde(skip_serializing_if = "Option::is_none")]
520    pub description: Option<String>,
521
522    /// Title
523    ///
524    /// A short title for the schema
525    #[serde(skip_serializing_if = "Option::is_none")]
526    pub title: Option<String>,
527
528    /// Enum values
529    ///
530    /// List of allowed values (for enum types)
531    #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
532    pub enum_values: Option<Vec<serde_json::Value>>,
533
534    /// Const value
535    ///
536    /// A single constant value that this schema must match
537    #[serde(rename = "const", skip_serializing_if = "Option::is_none")]
538    pub const_value: Option<serde_json::Value>,
539
540    /// Items schema (for array type)
541    ///
542    /// Schema for array elements when schema_type is "array"
543    #[serde(skip_serializing_if = "Option::is_none")]
544    pub items: Option<Box<Schema>>,
545
546    /// Additional properties
547    ///
548    /// Schema for additional properties not explicitly defined (for object types)
549    #[serde(
550        rename = "additionalProperties",
551        skip_serializing_if = "Option::is_none"
552    )]
553    pub additional_properties: Option<Box<Schema>>,
554
555    /// OneOf schemas
556    ///
557    /// Value must match exactly one of these schemas (XOR logic)
558    #[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")]
559    pub one_of: Option<Vec<Schema>>,
560
561    /// AnyOf schemas
562    ///
563    /// Value must match at least one of these schemas (OR logic)
564    #[serde(rename = "anyOf", skip_serializing_if = "Option::is_none")]
565    pub any_of: Option<Vec<Schema>>,
566
567    /// AllOf schemas
568    ///
569    /// Value must match all of these schemas (AND logic)
570    #[serde(rename = "allOf", skip_serializing_if = "Option::is_none")]
571    pub all_of: Option<Vec<Schema>>,
572
573    /// Additional fields that may be present in the schema
574    ///
575    /// Captures any additional JSON Schema properties not explicitly defined above
576    #[serde(flatten)]
577    pub additional: HashMap<String, serde_json::Value>,
578}
579
580impl Default for AsyncApiSpec {
581    fn default() -> Self {
582        Self {
583            asyncapi: "3.0.0".to_string(),
584            info: Info {
585                title: "API".to_string(),
586                version: "1.0.0".to_string(),
587                description: None,
588            },
589            servers: None,
590            channels: None,
591            operations: None,
592            components: None,
593        }
594    }
595}
596
597#[cfg(test)]
598mod tests {
599    use super::*;
600
601    #[test]
602    fn test_spec_serialization() {
603        let spec = AsyncApiSpec::default();
604        let json = serde_json::to_string(&spec).unwrap();
605        assert!(json.contains("asyncapi"));
606        assert!(json.contains("3.0.0"));
607    }
608
609    #[test]
610    fn test_spec_deserialization() {
611        let json = r#"{
612            "asyncapi": "3.0.0",
613            "info": {
614                "title": "Test API",
615                "version": "1.0.0"
616            }
617        }"#;
618        let spec: AsyncApiSpec = serde_json::from_str(json).unwrap();
619        assert_eq!(spec.asyncapi, "3.0.0");
620        assert_eq!(spec.info.title, "Test API");
621    }
622}