sim_lib_audio_graph_core/
port.rs1use std::{fmt, str::FromStr};
2
3use sim_kernel::{Error, Result};
4use sim_lib_stream_core::RateContract;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
8pub enum PortMedia {
9 Audio,
11 Control,
13 Event,
15}
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub enum PortDir {
20 In,
22 Out,
24}
25
26#[derive(Clone, Debug, PartialEq, Eq)]
29pub struct PortDecl {
30 pub name: String,
32 pub media: PortMedia,
34 pub dir: PortDir,
36 pub channels: u16,
38 pub rate_contract: RateContract,
40}
41
42impl PortDecl {
43 pub fn new(name: impl Into<String>, media: PortMedia, dir: PortDir, channels: u16) -> Self {
45 Self {
46 name: name.into(),
47 media,
48 dir,
49 channels,
50 rate_contract: media.default_rate_contract(),
51 }
52 }
53
54 pub fn with_rate_contract(mut self, rate_contract: RateContract) -> Self {
56 self.rate_contract = rate_contract;
57 self
58 }
59}
60
61impl PortMedia {
62 pub fn default_rate_contract(self) -> RateContract {
65 match self {
66 Self::Audio => RateContract::sample_exact(None),
67 Self::Control | Self::Event => RateContract::control(),
68 }
69 }
70}
71
72#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
78pub struct PortUri {
79 pub scheme: String,
81 pub authority: String,
83 pub path: Vec<String>,
85 pub index: u32,
87}
88
89impl PortUri {
90 pub fn new(
92 scheme: impl Into<String>,
93 authority: impl Into<String>,
94 path: Vec<String>,
95 index: u32,
96 ) -> Result<Self> {
97 let uri = Self {
98 scheme: scheme.into(),
99 authority: authority.into(),
100 path,
101 index,
102 };
103 uri.validate()?;
104 Ok(uri)
105 }
106
107 pub fn node(
109 graph_id: impl Into<String>,
110 node_id: impl Into<String>,
111 port_name: impl Into<String>,
112 index: u32,
113 ) -> Result<Self> {
114 Self::new(
115 "sim-node",
116 "graph",
117 vec![graph_id.into(), node_id.into(), port_name.into()],
118 index,
119 )
120 }
121
122 pub fn node_id(&self) -> Option<&str> {
124 (self.scheme == "sim-node" && self.authority == "graph" && self.path.len() >= 3)
125 .then_some(self.path[1].as_str())
126 }
127
128 pub fn node_port_name(&self) -> Option<&str> {
130 (self.scheme == "sim-node" && self.authority == "graph" && self.path.len() >= 3)
131 .then_some(self.path[2].as_str())
132 }
133
134 fn validate(&self) -> Result<()> {
135 if self.scheme.is_empty() {
136 return Err(Error::Eval("port URI scheme cannot be empty".to_owned()));
137 }
138 if self.scheme.contains(':') || self.scheme.contains('/') {
139 return Err(Error::Eval(format!(
140 "port URI scheme contains an invalid separator: {}",
141 self.scheme
142 )));
143 }
144 if self.authority.is_empty() {
145 return Err(Error::Eval("port URI authority cannot be empty".to_owned()));
146 }
147 if self.authority.contains('/') {
148 return Err(Error::Eval(format!(
149 "port URI authority contains an invalid separator: {}",
150 self.authority
151 )));
152 }
153 if let Some(segment) = self.path.iter().find(|segment| segment.is_empty()) {
154 return Err(Error::Eval(format!(
155 "port URI path segment cannot be empty: {segment}"
156 )));
157 }
158 if let Some(segment) = self.path.iter().find(|segment| segment.contains('/')) {
159 return Err(Error::Eval(format!(
160 "port URI path segment contains an invalid separator: {segment}"
161 )));
162 }
163 Ok(())
164 }
165}
166
167impl fmt::Display for PortUri {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 write!(f, "{}://{}", self.scheme, self.authority)?;
170 for segment in &self.path {
171 write!(f, "/{segment}")?;
172 }
173 write!(f, ":{}", self.index)
174 }
175}
176
177impl FromStr for PortUri {
178 type Err = Error;
179
180 fn from_str(text: &str) -> Result<Self> {
181 let (scheme, rest) = text
182 .split_once("://")
183 .ok_or_else(|| Error::Eval(format!("port URI is missing scheme: {text}")))?;
184 let (address, index_text) = rest
185 .rsplit_once(':')
186 .ok_or_else(|| Error::Eval(format!("port URI is missing port index: {text}")))?;
187 let index = index_text
188 .parse::<u32>()
189 .map_err(|_| Error::Eval(format!("port URI has invalid port index: {text}")))?;
190 let mut segments = address.split('/');
191 let authority = segments
192 .next()
193 .ok_or_else(|| Error::Eval(format!("port URI is missing authority: {text}")))?;
194 let path = segments.map(str::to_owned).collect::<Vec<_>>();
195 Self::new(scheme, authority, path, index)
196 }
197}