osc_types10/
lib.rs

1#![forbid(unsafe_code)]
2#![deny(missing_docs, unreachable_pub, rust_2018_idioms)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4#![cfg_attr(not(feature = "std"), no_std)]
5#![doc = r#"
6# osc-types10
7**⚠ Experimental / Not for production use**
8
9Defines message and bundle types for Open Sound Control 1.0.
10- Pre-release (`0.1.0-alpha`)
11- Breaking changes may occur frequently
12- `no_std` compatible (optional)
13
14## Example
15```rust
16use osc_types10::{Message, OscType};
17let msg = Message::new("/example", vec![OscType::String("abc")]);
18println!("{msg:?}");
19```
20"#]
21
22#[cfg(not(feature = "std"))]
23extern crate alloc;
24
25#[cfg(feature = "std")]
26use std::vec::Vec;
27
28#[cfg(not(feature = "std"))]
29use alloc::vec::Vec;
30
31/// OSC argument types as defined in OSC 1.0 specification
32#[derive(Debug, Clone, PartialEq)]
33pub enum OscType<'a> {
34    /// 32-bit integer (i)
35    Int(i32),
36    /// 32-bit IEEE 754 float (f)
37    Float(f32),
38    /// Null-terminated string (s)
39    String(&'a str),
40    /// Binary blob (b)
41    Blob(&'a [u8]),
42}
43
44/// OSC Message as defined in OSC 1.0 specification
45#[derive(Debug, Clone, PartialEq)]
46pub struct Message<'a> {
47    /// OSC address pattern
48    pub address: &'a str,
49    /// Arguments of the message
50    pub args: Vec<OscType<'a>>,
51}
52
53impl<'a> Message<'a> {
54    /// Create a new OSC message
55    pub fn new(address: &'a str, args: Vec<OscType<'a>>) -> Self {
56        Self { address, args }
57    }
58
59    /// Create a new OSC message with string arguments (convenience method)
60    pub fn with_strings(address: &'a str, string_args: Vec<&'a str>) -> Self {
61        let args = string_args.into_iter().map(OscType::String).collect();
62        Self::new(address, args)
63    }
64}
65
66/// Example placeholder type for OSC bundles
67#[derive(Debug, Clone, PartialEq)]
68pub struct Bundle<'a> {
69    /// OSC time tag
70    pub timetag: u64,
71    /// Messages contained in the bundle
72    pub messages: Vec<Message<'a>>,
73}
74
75impl<'a> Bundle<'a> {
76    /// Create a new OSC bundle
77    pub fn new(timetag: u64, messages: Vec<Message<'a>>) -> Self {
78        Self { timetag, messages }
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::{Bundle, Message, OscType};
85
86    #[cfg(not(feature = "std"))]
87    use alloc::vec;
88
89    #[test]
90    fn message_new_sets_address_and_args() {
91        let msg = Message::with_strings("/test", vec!["one", "two"]);
92
93        assert_eq!(msg.address, "/test");
94        assert_eq!(
95            msg.args,
96            vec![OscType::String("one"), OscType::String("two")]
97        );
98    }
99
100    #[test]
101    fn message_equality_compares_contents() {
102        let lhs = Message::with_strings("/foo", vec!["a", "b"]);
103        let rhs = Message::with_strings("/foo", vec!["a", "b"]);
104        let different_address = Message::with_strings("/bar", vec!["a", "b"]);
105        let different_args = Message::with_strings("/foo", vec!["a"]);
106
107        assert_eq!(lhs, rhs);
108        assert_ne!(lhs, different_address);
109        assert_ne!(lhs, different_args);
110    }
111
112    #[test]
113    fn message_supports_mixed_types() {
114        let msg = Message::new(
115            "/mixed",
116            vec![
117                OscType::Int(42),
118                OscType::Float(3.14),
119                OscType::String("hello"),
120                OscType::Blob(&[0x01, 0x02, 0x03]),
121            ],
122        );
123
124        assert_eq!(msg.address, "/mixed");
125        assert_eq!(msg.args.len(), 4);
126    }
127
128    #[test]
129    fn bundle_new_sets_timetag_and_messages() {
130        let messages = vec![
131            Message::with_strings("/bundle/one", vec!["1"]),
132            Message::with_strings("/bundle/two", vec!["2"]),
133        ];
134        let bundle = Bundle::new(42, messages.clone());
135
136        assert_eq!(bundle.timetag, 42);
137        assert_eq!(bundle.messages, messages);
138    }
139
140    #[test]
141    fn bundle_equality_compares_contents() {
142        let messages = vec![
143            Message::with_strings("/bundle", vec!["a"]),
144            Message::with_strings("/bundle", vec!["b"]),
145        ];
146        let lhs = Bundle::new(1, messages.clone());
147        let rhs = Bundle::new(1, messages);
148        let different_timetag = Bundle::new(2, vec![Message::with_strings("/bundle", vec!["a"])]);
149        let different_messages = Bundle::new(1, vec![Message::with_strings("/bundle", vec!["c"])]);
150
151        assert_eq!(lhs, rhs);
152        assert_ne!(lhs, different_timetag);
153        assert_ne!(lhs, different_messages);
154    }
155}