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#[derive(Debug, Clone, PartialEq)]
33pub enum OscPacket<'a> {
34 Message(Message<'a>),
36 Bundle(Bundle<'a>),
38}
39
40#[derive(Debug, Clone, PartialEq)]
42pub enum OscType<'a> {
43 Int(i32),
45 Float(f32),
47 String(&'a str),
49 Blob(&'a [u8]),
51}
52
53#[derive(Debug, Clone, PartialEq)]
55pub struct Message<'a> {
56 pub address: &'a str,
58 pub args: Vec<OscType<'a>>,
60}
61
62impl<'a> Message<'a> {
63 pub fn new(address: &'a str, args: Vec<OscType<'a>>) -> Self {
65 Self { address, args }
66 }
67
68 pub fn with_strings(address: &'a str, string_args: Vec<&'a str>) -> Self {
70 let args = string_args.into_iter().map(OscType::String).collect();
71 Self::new(address, args)
72 }
73}
74
75#[derive(Debug, Clone, PartialEq)]
80pub struct Bundle<'a> {
81 pub timetag: u64,
83 pub packets: Vec<OscPacket<'a>>,
85}
86
87impl<'a> Bundle<'a> {
88 pub fn new(timetag: u64, packets: Vec<OscPacket<'a>>) -> Self {
90 Self { timetag, packets }
91 }
92
93 pub fn with_messages(timetag: u64, messages: Vec<Message<'a>>) -> Self {
95 let packets = messages.into_iter().map(OscPacket::Message).collect();
96 Self::new(timetag, packets)
97 }
98
99 pub fn empty(timetag: u64) -> Self {
101 Self::new(timetag, Vec::new())
102 }
103
104 pub fn add_message(&mut self, message: Message<'a>) {
106 self.packets.push(OscPacket::Message(message));
107 }
108
109 pub fn add_bundle(&mut self, bundle: Bundle<'a>) {
111 self.packets.push(OscPacket::Bundle(bundle));
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::{Bundle, Message, OscPacket, OscType};
118
119 #[cfg(not(feature = "std"))]
120 use alloc::vec;
121
122 #[test]
123 fn message_new_sets_address_and_args() {
124 let msg = Message::with_strings("/test", vec!["one", "two"]);
125
126 assert_eq!(msg.address, "/test");
127 assert_eq!(
128 msg.args,
129 vec![OscType::String("one"), OscType::String("two")]
130 );
131 }
132
133 #[test]
134 fn message_equality_compares_contents() {
135 let lhs = Message::with_strings("/foo", vec!["a", "b"]);
136 let rhs = Message::with_strings("/foo", vec!["a", "b"]);
137 let different_address = Message::with_strings("/bar", vec!["a", "b"]);
138 let different_args = Message::with_strings("/foo", vec!["a"]);
139
140 assert_eq!(lhs, rhs);
141 assert_ne!(lhs, different_address);
142 assert_ne!(lhs, different_args);
143 }
144
145 #[test]
146 fn message_supports_mixed_types() {
147 let msg = Message::new(
148 "/mixed",
149 vec![
150 OscType::Int(42),
151 OscType::Float(3.14),
152 OscType::String("hello"),
153 OscType::Blob(&[0x01, 0x02, 0x03]),
154 ],
155 );
156
157 assert_eq!(msg.address, "/mixed");
158 assert_eq!(msg.args.len(), 4);
159 }
160
161 #[test]
162 fn bundle_new_sets_timetag_and_packets() {
163 let messages = vec![
164 Message::with_strings("/bundle/one", vec!["1"]),
165 Message::with_strings("/bundle/two", vec!["2"]),
166 ];
167 let bundle = Bundle::with_messages(42, messages.clone());
168
169 assert_eq!(bundle.timetag, 42);
170 assert_eq!(bundle.packets.len(), 2);
172 if let OscPacket::Message(ref msg) = bundle.packets[0] {
173 assert_eq!(msg, &messages[0]);
174 } else {
175 panic!("Expected message in bundle");
176 }
177 }
178
179 #[test]
180 fn bundle_equality_compares_contents() {
181 let messages = vec![
182 Message::with_strings("/bundle", vec!["a"]),
183 Message::with_strings("/bundle", vec!["b"]),
184 ];
185 let lhs = Bundle::with_messages(1, messages.clone());
186 let rhs = Bundle::with_messages(1, messages);
187 let different_timetag =
188 Bundle::with_messages(2, vec![Message::with_strings("/bundle", vec!["a"])]);
189 let different_messages =
190 Bundle::with_messages(1, vec![Message::with_strings("/bundle", vec!["c"])]);
191
192 assert_eq!(lhs, rhs);
193 assert_ne!(lhs, different_timetag);
194 assert_ne!(lhs, different_messages);
195 }
196
197 #[test]
198 fn bundle_supports_nested_bundles() {
199 let inner_bundle = Bundle::with_messages(
200 100,
201 vec![Message::with_strings("/inner/msg", vec!["inner"])],
202 );
203 let mut outer_bundle = Bundle::empty(200);
204 outer_bundle.add_message(Message::with_strings("/outer/msg", vec!["outer"]));
205 outer_bundle.add_bundle(inner_bundle.clone());
206
207 assert_eq!(outer_bundle.timetag, 200);
208 assert_eq!(outer_bundle.packets.len(), 2);
209
210 if let OscPacket::Message(ref msg) = outer_bundle.packets[0] {
212 assert_eq!(msg.address, "/outer/msg");
213 } else {
214 panic!("Expected message at index 0");
215 }
216
217 if let OscPacket::Bundle(ref bundle) = outer_bundle.packets[1] {
219 assert_eq!(bundle, &inner_bundle);
220 } else {
221 panic!("Expected bundle at index 1");
222 }
223 }
224
225 #[test]
226 fn bundle_add_methods_work_correctly() {
227 let mut bundle = Bundle::empty(42);
228
229 let msg1 = Message::with_strings("/test1", vec!["a"]);
230 let msg2 = Message::with_strings("/test2", vec!["b"]);
231 let nested_bundle = Bundle::with_messages(100, vec![msg2.clone()]);
232
233 bundle.add_message(msg1.clone());
234 bundle.add_bundle(nested_bundle.clone());
235
236 assert_eq!(bundle.packets.len(), 2);
237
238 if let OscPacket::Message(ref msg) = bundle.packets[0] {
240 assert_eq!(msg, &msg1);
241 } else {
242 panic!("Expected message at index 0");
243 }
244
245 if let OscPacket::Bundle(ref b) = bundle.packets[1] {
247 assert_eq!(b, &nested_bundle);
248 } else {
249 panic!("Expected bundle at index 1");
250 }
251 }
252
253 #[test]
254 fn bundle_with_messages_convenience_method() {
255 let messages = vec![
256 Message::with_strings("/msg1", vec!["hello"]),
257 Message::with_strings("/msg2", vec!["world"]),
258 ];
259
260 let bundle = Bundle::with_messages(42, messages.clone());
261
262 assert_eq!(bundle.timetag, 42);
263 assert_eq!(bundle.packets.len(), 2);
264
265 for (i, expected_msg) in messages.iter().enumerate() {
266 if let OscPacket::Message(ref msg) = bundle.packets[i] {
267 assert_eq!(msg, expected_msg);
268 } else {
269 panic!("Expected message at index {}", i);
270 }
271 }
272 }
273}