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, ServerVariable};
138/// use std::collections::HashMap;
139///
140/// let mut variables = HashMap::new();
141/// variables.insert("userId".to_string(), ServerVariable {
142/// description: Some("User ID for connection".to_string()),
143/// default: None,
144/// enum_values: None,
145/// examples: Some(vec!["12".to_string(), "13".to_string()]),
146/// });
147///
148/// let server = Server {
149/// host: "chat.example.com:443".to_string(),
150/// protocol: "wss".to_string(),
151/// pathname: Some("/api/ws/{userId}".to_string()),
152/// description: Some("Production WebSocket server".to_string()),
153/// variables: Some(variables),
154/// };
155/// ```
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct Server {
158 /// Server URL or host
159 ///
160 /// The hostname or URL where the server is hosted. May include port number.
161 /// Examples: "localhost:8080", "api.example.com", "ws.example.com:443"
162 pub host: String,
163
164 /// Protocol (e.g., "wss", "ws", "grpc")
165 ///
166 /// The protocol used to communicate with the server.
167 /// Common values: "ws" (WebSocket), "wss" (WebSocket Secure), "grpc", "mqtt"
168 pub protocol: String,
169
170 /// Optional pathname for the server URL
171 ///
172 /// The pathname to append to the host. Can contain variables in curly braces (e.g., "/api/ws/{userId}")
173 #[serde(skip_serializing_if = "Option::is_none")]
174 pub pathname: Option<String>,
175
176 /// Server description
177 ///
178 /// An optional human-readable description of the server's purpose or environment
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub description: Option<String>,
181
182 /// Server variables
183 ///
184 /// A map of variable name to ServerVariable definition for variables used in the pathname
185 #[serde(skip_serializing_if = "Option::is_none")]
186 pub variables: Option<HashMap<String, ServerVariable>>,
187}
188
189/// Server variable definition
190///
191/// Defines a variable that can be used in the server pathname. Variables are
192/// substituted at runtime with actual values.
193///
194/// # Example
195///
196/// ```rust
197/// use asyncapi_rust_models::ServerVariable;
198///
199/// let user_id_var = ServerVariable {
200/// description: Some("Authenticated user ID".to_string()),
201/// default: None,
202/// enum_values: None,
203/// examples: Some(vec!["12".to_string(), "13".to_string()]),
204/// };
205/// ```
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct ServerVariable {
208 /// Variable description
209 ///
210 /// Human-readable description of what this variable represents
211 #[serde(skip_serializing_if = "Option::is_none")]
212 pub description: Option<String>,
213
214 /// Default value
215 ///
216 /// The default value to use if no value is provided
217 #[serde(skip_serializing_if = "Option::is_none")]
218 pub default: Option<String>,
219
220 /// Enumeration of allowed values
221 ///
222 /// If specified, only these values are valid for this variable
223 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
224 pub enum_values: Option<Vec<String>>,
225
226 /// Example values
227 ///
228 /// A list of example values for documentation purposes
229 #[serde(skip_serializing_if = "Option::is_none")]
230 pub examples: Option<Vec<String>>,
231}
232
233/// Communication channel
234///
235/// Represents a communication path through which messages are exchanged.
236/// Channels define where messages are sent and received (e.g., WebSocket endpoints,
237/// message queue topics, gRPC methods).
238///
239/// # Example
240///
241/// ```rust
242/// use asyncapi_rust_models::{Channel, Parameter, Schema, SchemaObject};
243/// use std::collections::HashMap;
244///
245/// let mut parameters = HashMap::new();
246/// parameters.insert("userId".to_string(), Parameter {
247/// description: Some("User ID for this WebSocket connection".to_string()),
248/// schema: Some(Schema::Object(Box::new(SchemaObject {
249/// schema_type: Some(serde_json::json!("integer")),
250/// properties: None,
251/// required: None,
252/// description: None,
253/// title: None,
254/// enum_values: None,
255/// const_value: None,
256/// items: None,
257/// additional_properties: None,
258/// one_of: None,
259/// any_of: None,
260/// all_of: None,
261/// additional: HashMap::new(),
262/// }))),
263/// });
264///
265/// let channel = Channel {
266/// address: Some("/ws/chat/{userId}".to_string()),
267/// messages: None,
268/// parameters: Some(parameters),
269/// };
270/// ```
271#[derive(Debug, Clone, Serialize, Deserialize)]
272pub struct Channel {
273 /// Channel address/path
274 ///
275 /// The location where this channel is available. For WebSocket, this is typically
276 /// the WebSocket path (e.g., "/ws/chat"). For other protocols, this could be a
277 /// topic name, queue name, or method path.
278 #[serde(skip_serializing_if = "Option::is_none")]
279 pub address: Option<String>,
280
281 /// Messages available on this channel
282 ///
283 /// A map of message identifiers to message definitions or references.
284 /// Messages define the structure of data that flows through this channel.
285 #[serde(skip_serializing_if = "Option::is_none")]
286 pub messages: Option<HashMap<String, MessageRef>>,
287
288 /// Channel parameters
289 ///
290 /// A map of parameter names to their schema definitions for variables used in the address
291 #[serde(skip_serializing_if = "Option::is_none")]
292 pub parameters: Option<HashMap<String, Parameter>>,
293}
294
295/// Channel parameter definition
296///
297/// Defines a parameter that can be used in the channel address. Parameters are
298/// substituted at runtime with actual values and have associated schema definitions.
299///
300/// # Example
301///
302/// ```rust
303/// use asyncapi_rust_models::{Parameter, Schema, SchemaObject};
304/// use std::collections::HashMap;
305///
306/// let user_id_param = Parameter {
307/// description: Some("User ID for this WebSocket connection".to_string()),
308/// schema: Some(Schema::Object(Box::new(SchemaObject {
309/// schema_type: Some(serde_json::json!("integer")),
310/// properties: None,
311/// required: None,
312/// description: None,
313/// title: None,
314/// enum_values: None,
315/// const_value: None,
316/// items: None,
317/// additional_properties: None,
318/// one_of: None,
319/// any_of: None,
320/// all_of: None,
321/// additional: HashMap::new(),
322/// }))),
323/// };
324/// ```
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct Parameter {
327 /// Parameter description
328 ///
329 /// Human-readable description of what this parameter represents
330 #[serde(skip_serializing_if = "Option::is_none")]
331 pub description: Option<String>,
332
333 /// Parameter schema
334 ///
335 /// The JSON Schema definition for this parameter's type and validation rules
336 #[serde(skip_serializing_if = "Option::is_none")]
337 pub schema: Option<Schema>,
338}
339
340/// Reference to a message definition
341///
342/// Messages can be defined either inline or as references to reusable components.
343/// This enum supports both patterns, following the AsyncAPI 3.0 specification.
344///
345/// # Example
346///
347/// ```rust
348/// use asyncapi_rust_models::{MessageRef, Message};
349///
350/// // Reference to a component message
351/// let ref_msg = MessageRef::Reference {
352/// reference: "#/components/messages/ChatMessage".to_string(),
353/// };
354///
355/// // Inline message definition
356/// let inline_msg = MessageRef::Inline(Box::new(Message {
357/// name: Some("ChatMessage".to_string()),
358/// title: Some("Chat Message".to_string()),
359/// summary: Some("A chat message".to_string()),
360/// description: None,
361/// content_type: Some("application/json".to_string()),
362/// payload: None,
363/// }));
364/// ```
365#[derive(Debug, Clone, Serialize, Deserialize)]
366#[serde(untagged)]
367pub enum MessageRef {
368 /// Reference to component message
369 ///
370 /// Points to a reusable message definition in the components section.
371 /// Format: "#/components/messages/{messageName}"
372 Reference {
373 /// $ref path
374 #[serde(rename = "$ref")]
375 reference: String,
376 },
377 /// Inline message definition
378 ///
379 /// Embeds the message definition directly rather than referencing a component
380 Inline(Box<Message>),
381}
382
383/// Message definition
384///
385/// Represents a message that can be sent or received through a channel.
386/// Messages describe the structure, content type, and documentation for data
387/// exchanged in asynchronous communication.
388///
389/// # Example
390///
391/// ```rust
392/// use asyncapi_rust_models::{Message, Schema, SchemaObject};
393/// use std::collections::HashMap;
394///
395/// let message = Message {
396/// name: Some("ChatMessage".to_string()),
397/// title: Some("Chat Message".to_string()),
398/// summary: Some("A message in a chat room".to_string()),
399/// description: Some("Sent when a user posts a message".to_string()),
400/// content_type: Some("application/json".to_string()),
401/// payload: Some(Schema::Object(Box::new(SchemaObject {
402/// schema_type: Some(serde_json::json!("object")),
403/// properties: None,
404/// required: None,
405/// description: Some("Chat message payload".to_string()),
406/// title: None,
407/// enum_values: None,
408/// const_value: None,
409/// items: None,
410/// additional_properties: None,
411/// one_of: None,
412/// any_of: None,
413/// all_of: None,
414/// additional: HashMap::new(),
415/// }))),
416/// };
417/// ```
418#[derive(Debug, Clone, Serialize, Deserialize)]
419pub struct Message {
420 /// Message name
421 ///
422 /// A machine-readable identifier for the message (e.g., "ChatMessage", "user.join")
423 #[serde(skip_serializing_if = "Option::is_none")]
424 pub name: Option<String>,
425
426 /// Message title
427 ///
428 /// A human-readable title for the message
429 #[serde(skip_serializing_if = "Option::is_none")]
430 pub title: Option<String>,
431
432 /// Message summary
433 ///
434 /// A short summary of what the message is for
435 #[serde(skip_serializing_if = "Option::is_none")]
436 pub summary: Option<String>,
437
438 /// Message description
439 ///
440 /// A detailed description of the message's purpose and usage
441 #[serde(skip_serializing_if = "Option::is_none")]
442 pub description: Option<String>,
443
444 /// Content type (e.g., "application/json")
445 ///
446 /// The MIME type of the message payload. Common values:
447 /// - "application/json" (default for text messages)
448 /// - "application/octet-stream" (binary data)
449 /// - "application/x-protobuf" (Protocol Buffers)
450 /// - "application/x-msgpack" (MessagePack)
451 #[serde(rename = "contentType", skip_serializing_if = "Option::is_none")]
452 pub content_type: Option<String>,
453
454 /// Message payload schema
455 ///
456 /// JSON Schema defining the structure of the message payload
457 #[serde(skip_serializing_if = "Option::is_none")]
458 pub payload: Option<Schema>,
459}
460
461/// Operation (send or receive)
462///
463/// Defines an action that can be performed on a channel. Operations describe
464/// whether an application sends or receives messages through a specific channel.
465///
466/// # Example
467///
468/// ```rust
469/// use asyncapi_rust_models::{Operation, OperationAction, ChannelRef};
470///
471/// let operation = Operation {
472/// action: OperationAction::Send,
473/// channel: ChannelRef {
474/// reference: "#/channels/chat".to_string(),
475/// },
476/// messages: None,
477/// };
478/// ```
479#[derive(Debug, Clone, Serialize, Deserialize)]
480pub struct Operation {
481 /// Operation action (send or receive)
482 ///
483 /// Specifies whether the application sends or receives messages
484 pub action: OperationAction,
485
486 /// Channel reference
487 ///
488 /// Points to the channel where this operation takes place
489 pub channel: ChannelRef,
490
491 /// Messages for this operation
492 ///
493 /// Optional list of messages that can be used with this operation
494 #[serde(skip_serializing_if = "Option::is_none")]
495 pub messages: Option<Vec<MessageRef>>,
496}
497
498/// Operation action type
499#[derive(Debug, Clone, Serialize, Deserialize)]
500#[serde(rename_all = "lowercase")]
501pub enum OperationAction {
502 /// Send message
503 Send,
504 /// Receive message
505 Receive,
506}
507
508/// Reference to a channel
509#[derive(Debug, Clone, Serialize, Deserialize)]
510pub struct ChannelRef {
511 /// $ref path
512 #[serde(rename = "$ref")]
513 pub reference: String,
514}
515
516/// Reusable components
517#[derive(Debug, Clone, Serialize, Deserialize)]
518pub struct Components {
519 /// Message definitions
520 #[serde(skip_serializing_if = "Option::is_none")]
521 pub messages: Option<HashMap<String, Message>>,
522
523 /// Schema definitions
524 #[serde(skip_serializing_if = "Option::is_none")]
525 pub schemas: Option<HashMap<String, Schema>>,
526}
527
528/// JSON Schema object
529///
530/// Flexible representation that can hold any valid JSON Schema. This type supports
531/// both schema references (using `$ref`) and complete inline schema definitions.
532///
533/// Schemas define the structure and validation rules for message payloads,
534/// following the JSON Schema specification.
535///
536/// # Example
537///
538/// ## Reference Schema
539///
540/// ```rust
541/// use asyncapi_rust_models::Schema;
542///
543/// let schema = Schema::Reference {
544/// reference: "#/components/schemas/ChatMessage".to_string(),
545/// };
546/// ```
547///
548/// ## Object Schema
549///
550/// ```rust
551/// use asyncapi_rust_models::{Schema, SchemaObject};
552/// use std::collections::HashMap;
553///
554/// let schema = Schema::Object(Box::new(SchemaObject {
555/// schema_type: Some(serde_json::json!("object")),
556/// properties: None,
557/// required: Some(vec!["username".to_string(), "room".to_string()]),
558/// description: Some("A chat message".to_string()),
559/// title: Some("ChatMessage".to_string()),
560/// enum_values: None,
561/// const_value: None,
562/// items: None,
563/// additional_properties: None,
564/// one_of: None,
565/// any_of: None,
566/// all_of: None,
567/// additional: HashMap::new(),
568/// }));
569/// ```
570#[derive(Debug, Clone, Serialize, Deserialize)]
571#[serde(untagged)]
572pub enum Schema {
573 /// Reference to another schema ($ref)
574 ///
575 /// Points to a reusable schema definition in the components section.
576 /// Format: "#/components/schemas/{schemaName}"
577 Reference {
578 /// $ref path
579 #[serde(rename = "$ref")]
580 reference: String,
581 },
582 /// Full schema object (boxed to reduce enum size)
583 ///
584 /// Contains a complete JSON Schema definition with all properties inline
585 Object(Box<SchemaObject>),
586}
587
588/// Schema object with all JSON Schema properties
589///
590/// Complete representation of a JSON Schema with support for all standard properties.
591/// This struct provides fine-grained control over schema definitions for message payloads.
592///
593/// # Example
594///
595/// ```rust
596/// use asyncapi_rust_models::{Schema, SchemaObject};
597/// use std::collections::HashMap;
598///
599/// // String property schema
600/// let username_schema = Schema::Object(Box::new(SchemaObject {
601/// schema_type: Some(serde_json::json!("string")),
602/// properties: None,
603/// required: None,
604/// description: Some("User's display name".to_string()),
605/// title: None,
606/// enum_values: None,
607/// const_value: None,
608/// items: None,
609/// additional_properties: None,
610/// one_of: None,
611/// any_of: None,
612/// all_of: None,
613/// additional: HashMap::new(),
614/// }));
615///
616/// // Object schema with properties
617/// let mut properties = HashMap::new();
618/// properties.insert("username".to_string(), Box::new(username_schema));
619///
620/// let message_schema = SchemaObject {
621/// schema_type: Some(serde_json::json!("object")),
622/// properties: Some(properties),
623/// required: Some(vec!["username".to_string()]),
624/// description: Some("A chat message".to_string()),
625/// title: Some("ChatMessage".to_string()),
626/// enum_values: None,
627/// const_value: None,
628/// items: None,
629/// additional_properties: None,
630/// one_of: None,
631/// any_of: None,
632/// all_of: None,
633/// additional: HashMap::new(),
634/// };
635/// ```
636#[derive(Debug, Clone, Serialize, Deserialize)]
637pub struct SchemaObject {
638 /// Schema type
639 ///
640 /// The JSON Schema type: "object", "array", "string", "number", "integer", "boolean", "null"
641 /// Can also be an array of types for schemas that allow multiple types (e.g., ["string", "null"])
642 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
643 pub schema_type: Option<serde_json::Value>,
644
645 /// Properties (for object type)
646 ///
647 /// Map of property names to their schemas when schema_type is "object"
648 #[serde(skip_serializing_if = "Option::is_none")]
649 pub properties: Option<HashMap<String, Box<Schema>>>,
650
651 /// Required properties
652 ///
653 /// List of property names that must be present (for object types)
654 #[serde(skip_serializing_if = "Option::is_none")]
655 pub required: Option<Vec<String>>,
656
657 /// Description
658 ///
659 /// Human-readable description of what this schema represents
660 #[serde(skip_serializing_if = "Option::is_none")]
661 pub description: Option<String>,
662
663 /// Title
664 ///
665 /// A short title for the schema
666 #[serde(skip_serializing_if = "Option::is_none")]
667 pub title: Option<String>,
668
669 /// Enum values
670 ///
671 /// List of allowed values (for enum types)
672 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
673 pub enum_values: Option<Vec<serde_json::Value>>,
674
675 /// Const value
676 ///
677 /// A single constant value that this schema must match
678 #[serde(rename = "const", skip_serializing_if = "Option::is_none")]
679 pub const_value: Option<serde_json::Value>,
680
681 /// Items schema (for array type)
682 ///
683 /// Schema for array elements when schema_type is "array"
684 #[serde(skip_serializing_if = "Option::is_none")]
685 pub items: Option<Box<Schema>>,
686
687 /// Additional properties
688 ///
689 /// Schema for additional properties not explicitly defined (for object types)
690 #[serde(
691 rename = "additionalProperties",
692 skip_serializing_if = "Option::is_none"
693 )]
694 pub additional_properties: Option<Box<Schema>>,
695
696 /// OneOf schemas
697 ///
698 /// Value must match exactly one of these schemas (XOR logic)
699 #[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")]
700 pub one_of: Option<Vec<Schema>>,
701
702 /// AnyOf schemas
703 ///
704 /// Value must match at least one of these schemas (OR logic)
705 #[serde(rename = "anyOf", skip_serializing_if = "Option::is_none")]
706 pub any_of: Option<Vec<Schema>>,
707
708 /// AllOf schemas
709 ///
710 /// Value must match all of these schemas (AND logic)
711 #[serde(rename = "allOf", skip_serializing_if = "Option::is_none")]
712 pub all_of: Option<Vec<Schema>>,
713
714 /// Additional fields that may be present in the schema
715 ///
716 /// Captures any additional JSON Schema properties not explicitly defined above
717 #[serde(flatten)]
718 pub additional: HashMap<String, serde_json::Value>,
719}
720
721impl Default for AsyncApiSpec {
722 fn default() -> Self {
723 Self {
724 asyncapi: "3.0.0".to_string(),
725 info: Info {
726 title: "API".to_string(),
727 version: "1.0.0".to_string(),
728 description: None,
729 },
730 servers: None,
731 channels: None,
732 operations: None,
733 components: None,
734 }
735 }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741
742 #[test]
743 fn test_spec_serialization() {
744 let spec = AsyncApiSpec::default();
745 let json = serde_json::to_string(&spec).unwrap();
746 assert!(json.contains("asyncapi"));
747 assert!(json.contains("3.0.0"));
748 }
749
750 #[test]
751 fn test_spec_deserialization() {
752 let json = r#"{
753 "asyncapi": "3.0.0",
754 "info": {
755 "title": "Test API",
756 "version": "1.0.0"
757 }
758 }"#;
759 let spec: AsyncApiSpec = serde_json::from_str(json).unwrap();
760 assert_eq!(spec.asyncapi, "3.0.0");
761 assert_eq!(spec.info.title, "Test API");
762 }
763}