astarte_interfaces/mapping/
endpoint.rs1use std::hash::Hash;
22use std::{fmt::Display, slice::Iter as SliceIter, str::FromStr};
23
24use tracing::trace;
25
26use super::path::MappingPath;
27
28pub const ENDPOINT_MAX_LEN: usize = 64;
30
31#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
48pub struct Endpoint<T = String> {
49 levels: Vec<Level<T>>,
50}
51
52impl<T> Endpoint<T> {
53 pub fn iter(&self) -> SliceIter<'_, Level<T>> {
55 self.levels.iter()
56 }
57
58 #[must_use]
60 pub fn eq_mapping<'a>(&self, mapping: &MappingPath<'a>) -> bool
61 where
62 T: PartialEq<&'a str> + Eq,
63 {
64 if self.len() != mapping.len() {
65 return false;
66 }
67
68 self.iter()
69 .zip(mapping.levels.iter())
70 .all(|(endpoint_level, path_level)| endpoint_level == path_level)
71 }
72
73 pub(crate) fn last(&self) -> Option<&Level<T>> {
78 self.levels.last()
79 }
80
81 pub(crate) fn len(&self) -> usize {
83 self.levels.len()
84 }
85}
86
87impl<'a> TryFrom<&'a str> for Endpoint<&'a str> {
88 type Error = EndpointError;
89
90 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
91 parse_endpoint(value)
92 }
93}
94
95impl TryFrom<&str> for Endpoint<String> {
96 type Error = EndpointError;
97
98 fn try_from(value: &str) -> Result<Self, Self::Error> {
99 Endpoint::<&str>::try_from(value).map(Endpoint::into)
100 }
101}
102
103impl FromStr for Endpoint<String> {
104 type Err = EndpointError;
105
106 fn from_str(s: &str) -> Result<Self, Self::Err> {
107 Self::try_from(s)
108 }
109}
110
111impl<T> Display for Endpoint<T>
112where
113 T: Display,
114{
115 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116 for level in &self.levels {
117 write!(f, "/{level}")?;
118 }
119
120 Ok(())
121 }
122}
123
124impl From<Endpoint<&str>> for Endpoint<String> {
125 fn from(value: Endpoint<&str>) -> Self {
126 Self {
127 levels: value.levels.into_iter().map(Level::into).collect(),
128 }
129 }
130}
131
132impl<'a, T> PartialEq<MappingPath<'a>> for Endpoint<T>
133where
134 T: for<'b> PartialEq<&'b str> + Eq,
135{
136 fn eq(&self, other: &MappingPath<'a>) -> bool {
137 self.eq_mapping(other)
138 }
139}
140
141impl<'a, T> IntoIterator for &'a Endpoint<T> {
142 type Item = &'a Level<T>;
143 type IntoIter = std::slice::Iter<'a, Level<T>>;
144 fn into_iter(self) -> Self::IntoIter {
145 self.iter()
146 }
147}
148
149#[derive(Debug, Eq, PartialOrd, Ord, Clone, Copy)]
164pub enum Level<T> {
165 Simple(T),
167 Parameter(T),
169}
170
171impl<T> Level<T> {
172 fn eq_str<'a>(&self, other: &'a str) -> bool
174 where
175 T: PartialEq<&'a str> + Eq,
176 {
177 match self {
178 Level::Simple(level) => *level == other,
179 Level::Parameter(_) => true,
180 }
181 }
182}
183
184impl<T> PartialEq for Level<T>
185where
186 T: PartialEq,
187{
188 fn eq(&self, other: &Self) -> bool {
189 match (self, other) {
190 (Self::Simple(l0), Self::Simple(r0)) => l0 == r0,
191 (Self::Parameter(_), Self::Parameter(_)) => true,
192 _ => false,
193 }
194 }
195}
196
197impl<T: Hash> Hash for Level<T> {
198 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
199 core::mem::discriminant(self).hash(state);
200
201 match self {
202 Level::Simple(level) => level.hash(state),
203 Level::Parameter(_) => {}
204 }
205 }
206}
207
208impl<T> Display for Level<T>
209where
210 T: Display,
211{
212 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
213 match self {
214 Self::Simple(level) => write!(f, "{level}"),
215 Self::Parameter(level) => write!(f, "%{{{level}}}"),
217 }
218 }
219}
220
221impl From<Level<&str>> for Level<String> {
222 fn from(value: Level<&str>) -> Self {
223 match value {
224 Level::Simple(simple) => Level::Simple(simple.into()),
225 Level::Parameter(param) => Level::Parameter(param.into()),
226 }
227 }
228}
229
230impl<'a, T> PartialEq<&'a str> for Level<T>
231where
232 T: PartialEq<&'a str> + Eq,
233{
234 fn eq(&self, other: &&'a str) -> bool {
235 self.eq_str(other)
236 }
237}
238
239impl<'a, T> PartialEq<Level<T>> for &'a str
240where
241 T: PartialEq<&'a str> + Eq,
242{
243 fn eq(&self, other: &Level<T>) -> bool {
244 other.eq_str(self)
245 }
246}
247
248#[non_exhaustive]
250#[derive(thiserror::Error, Debug, Clone)]
251pub enum EndpointError {
252 #[error("endpoint must start with a slash, got instead: {0}")]
254 Prefix(String),
255 #[error("endpoint must contain at least a level: {0}")]
259 Empty(String),
260 #[error("endpoint must contain at most 64 levels, but it has {0}")]
262 MaxLen(usize),
263 #[error("endpoint contains invalid level: {input}")]
265 Level {
266 input: String,
268 #[source]
270 error: LevelError,
271 },
272}
273
274#[non_exhaustive]
276#[derive(thiserror::Error, Debug, Clone)]
277pub enum LevelError {
278 #[error("levels must not be empty")]
280 Empty,
281 #[error("levels contain an invalid character {0}")]
283 InvalidCharacter(char),
284 #[error("the parameter should incapsulate the whole level")]
288 Parameter,
289}
290
291fn parse_endpoint(input: &str) -> Result<Endpoint<&str>, EndpointError> {
320 trace!("parsing endpoint: {}", input);
321
322 let endpoint = input
323 .strip_prefix('/')
324 .ok_or_else(|| EndpointError::Prefix(input.to_string()))?;
325
326 let levels = endpoint
327 .split('/')
328 .map(parse_level)
329 .collect::<Result<Vec<_>, LevelError>>()
330 .map_err(|error| EndpointError::Level {
331 input: input.to_string(),
332 error,
333 })?;
334
335 if levels.is_empty() {
336 return Err(EndpointError::Empty(input.to_string()));
337 }
338
339 if levels.len() > ENDPOINT_MAX_LEN {
340 return Err(EndpointError::MaxLen(levels.len()));
341 }
342
343 trace!("levels: {:?}", levels);
344
345 Ok(Endpoint { levels })
346}
347
348fn parse_level(input: &str) -> Result<Level<&str>, LevelError> {
349 trace!("parsing level: {}", input);
350
351 let level = if let Some(param) = parse_parameter(input)? {
352 trace!("level is a parameter: {}", param);
353
354 Level::Parameter(param)
355 } else {
356 let level = parse_simple(input)?;
357
358 trace!("level is simple: {}", level);
359
360 Level::Simple(level)
361 };
362
363 Ok(level)
364}
365
366fn parse_simple(input: &str) -> Result<&str, LevelError> {
367 let mut chars = input.chars().peekable();
368
369 match chars.next() {
370 Some('a'..='z' | 'A'..='Z') => {}
371 Some(c) => return Err(LevelError::InvalidCharacter(c)),
372 None => return Err(LevelError::Empty),
373 }
374
375 while let Some(chr) = chars.next() {
376 match chr {
377 '%' if Some('{') == chars.peek().copied() => {
378 return Err(LevelError::Parameter);
379 }
380 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => {}
381 c => return Err(LevelError::InvalidCharacter(c)),
382 }
383 }
384
385 Ok(input)
386}
387
388fn parse_parameter(input: &str) -> Result<Option<&str>, LevelError> {
389 let parameter = input
390 .strip_prefix("%{")
391 .and_then(|input| input.strip_suffix('}'));
392
393 let name = match parameter {
394 Some(param) => {
395 let name = parse_simple(param)?;
396
397 Some(name)
398 }
399 None => None,
400 };
401
402 Ok(name)
403}
404
405#[cfg(test)]
406mod tests {
407 use pretty_assertions::assert_eq;
408
409 use super::*;
410
411 #[test]
412 fn endpoint_equals_to_mapping() {
413 let endpoint = Endpoint {
414 levels: vec![
415 Level::Parameter("sensor_id".to_string()),
416 Level::Simple("boolean_endpoint".to_string()),
417 ],
418 };
419
420 let path = MappingPath::try_from("/1/boolean_endpoint").unwrap();
421
422 assert!(endpoint.eq_mapping(&path));
423 }
424
425 #[test]
426 fn test_parse_parameter() {
427 let res = parse_parameter("%{test}");
428
429 assert!(
430 res.is_ok(),
431 "failed to parse parameter: {}",
432 res.unwrap_err()
433 );
434
435 let parameter = res.unwrap();
436
437 assert_eq!(parameter, Some("test"));
438 }
439
440 #[test]
441 fn test_parse_level_parameter() {
442 let level = parse_level("%{test}").unwrap();
443
444 assert_eq!(level, Level::Parameter("test"));
445 }
446
447 #[test]
448 fn test_parse_endpoint() {
449 let res = parse_endpoint("/a/%{b}/c");
450
451 assert!(
452 res.is_ok(),
453 "failed to parse endpoint: {}",
454 res.unwrap_err()
455 );
456
457 let endpoint = res.unwrap();
458
459 let expected = Endpoint {
460 levels: vec![
461 Level::Simple("a"),
462 Level::Parameter("b"),
463 Level::Simple("c"),
464 ],
465 };
466
467 assert_eq!(endpoint, expected);
468 }
469
470 #[test]
471 fn test_parse_endpoint_first() {
472 let res = parse_endpoint("/%{a}/b/c");
473
474 assert!(
475 res.is_ok(),
476 "failed to parse endpoint: {}",
477 res.unwrap_err()
478 );
479
480 let endpoint = res.unwrap();
481
482 let expected = Endpoint {
483 levels: vec![
484 Level::Parameter("a"),
485 Level::Simple("b"),
486 Level::Simple("c"),
487 ],
488 };
489
490 assert_eq!(endpoint, expected);
491 }
492
493 #[test]
494 fn test_parse_endpoint_multi() {
495 let res = parse_endpoint("/a/%{b}/c/%{d}/e");
496
497 assert!(
498 res.is_ok(),
499 "failed to parse endpoint: {}",
500 res.unwrap_err()
501 );
502
503 let endpoint = res.unwrap();
504
505 let expected = Endpoint {
506 levels: vec![
507 Level::Simple("a"),
508 Level::Parameter("b"),
509 Level::Simple("c"),
510 Level::Parameter("d"),
511 Level::Simple("e"),
512 ],
513 };
514
515 assert_eq!(endpoint, expected);
516 }
517
518 #[test]
519 fn test_parse_endpoint_parameters() {
520 let cases = [
521 (
522 "/%{sensor_id}/boolean_endpoint",
523 Endpoint {
524 levels: vec![
525 Level::Parameter("sensor_id"),
526 Level::Simple("boolean_endpoint"),
527 ],
528 },
529 ),
530 (
531 "/%{sensor_id}/enable",
532 Endpoint {
533 levels: vec![Level::Parameter("sensor_id"), Level::Simple("enable")],
534 },
535 ),
536 ];
537
538 for (endpoint, expected) in cases {
539 let res = parse_endpoint(endpoint);
540
541 assert!(
542 res.is_ok(),
543 "failed to parse endpoint: {}",
544 res.unwrap_err()
545 );
546
547 let endpoint = res.unwrap();
548
549 assert_eq!(endpoint, expected);
550 }
551 }
552
553 #[test]
554 fn level_eq_str() {
555 let param = Level::Parameter("sensor_id".to_string());
556
557 assert_eq!(param, "some");
558 assert_eq!(param, "foo");
559
560 let simple = Level::Simple("boolean_endpoint".to_string());
561
562 assert_eq!(simple, "boolean_endpoint");
563 assert_ne!(simple, "foo");
564 }
565
566 #[test]
567 fn level_param_eq_ignore_param() {
568 let a = Level::Parameter("foo");
569 let b = Level::Parameter("bar");
570
571 assert_eq!(a, b);
572 }
573}