1use alloc::string::String;
7
8pub const REST_PREFIX: &str = "/dds/rest1";
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub enum RestMethod {
14 Post,
16 Put,
18 Get,
20 Delete,
22 Head,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum RestRoute {
30 CreateApplication,
33 DeleteApplication {
35 app: String,
37 },
38 GetApplications,
40 CreateType,
42 DeleteType {
44 type_name: String,
46 },
47 GetTypes,
49 CreateQosLibrary,
51 UpdateQosLibrary {
53 qos_lib: String,
55 },
56 DeleteQosLibrary {
58 qos_lib: String,
60 },
61 GetQosLibraries,
63
64 CreateQosProfile {
68 qos_lib: String,
70 },
71 UpdateQosProfile {
74 qos_lib: String,
76 profile: String,
78 },
79 DeleteQosProfile {
82 qos_lib: String,
84 profile: String,
86 },
87 GetQosProfiles {
90 qos_lib: String,
92 },
93
94 CreateParticipant {
98 app: String,
100 },
101 UpdateParticipant {
104 app: String,
106 participant: String,
108 },
109 DeleteParticipant {
112 app: String,
114 participant: String,
116 },
117 GetParticipants {
120 app: String,
122 },
123 CreateWaitset {
126 app: String,
128 },
129 GetWaitset {
132 app: String,
134 waitset: String,
136 },
137
138 CreateTopic {
142 app: String,
144 participant: String,
146 },
147 CreatePublisher {
150 app: String,
152 participant: String,
154 },
155 CreateSubscriber {
158 app: String,
160 participant: String,
162 },
163
164 DataWriterWrite {
168 app: String,
170 participant: String,
172 publisher: String,
174 data_writer: String,
176 },
177 DataReaderRead {
180 app: String,
182 participant: String,
184 subscriber: String,
186 data_reader: String,
188 },
189
190 Unknown {
192 path: String,
194 },
195}
196
197pub fn parse_route(method: RestMethod, uri: &str) -> Result<RestRoute, RouteError> {
207 let path = uri
208 .strip_prefix(REST_PREFIX)
209 .ok_or(RouteError::MissingPrefix)?;
210 let segments: alloc::vec::Vec<&str> = path
211 .trim_start_matches('/')
212 .trim_end_matches('/')
213 .split('/')
214 .filter(|s| !s.is_empty())
215 .collect();
216 Ok(match (method, segments.as_slice()) {
217 (RestMethod::Post, ["applications"]) => RestRoute::CreateApplication,
219 (RestMethod::Get, ["applications"]) => RestRoute::GetApplications,
220 (RestMethod::Delete, ["applications", app]) => RestRoute::DeleteApplication {
221 app: String::from(*app),
222 },
223 (RestMethod::Post, ["types"]) => RestRoute::CreateType,
225 (RestMethod::Get, ["types"]) => RestRoute::GetTypes,
226 (RestMethod::Delete, ["types", t]) => RestRoute::DeleteType {
227 type_name: String::from(*t),
228 },
229 (RestMethod::Post, ["qos_libraries"]) => RestRoute::CreateQosLibrary,
231 (RestMethod::Get, ["qos_libraries"]) => RestRoute::GetQosLibraries,
232 (RestMethod::Put, ["qos_libraries", lib]) => RestRoute::UpdateQosLibrary {
233 qos_lib: String::from(*lib),
234 },
235 (RestMethod::Delete, ["qos_libraries", lib]) => RestRoute::DeleteQosLibrary {
236 qos_lib: String::from(*lib),
237 },
238 (RestMethod::Post, ["qos_libraries", lib, "qos_profiles"]) => RestRoute::CreateQosProfile {
240 qos_lib: String::from(*lib),
241 },
242 (RestMethod::Get, ["qos_libraries", lib, "qos_profiles"]) => RestRoute::GetQosProfiles {
243 qos_lib: String::from(*lib),
244 },
245 (RestMethod::Put, ["qos_libraries", lib, "qos_profiles", p]) => {
246 RestRoute::UpdateQosProfile {
247 qos_lib: String::from(*lib),
248 profile: String::from(*p),
249 }
250 }
251 (RestMethod::Delete, ["qos_libraries", lib, "qos_profiles", p]) => {
252 RestRoute::DeleteQosProfile {
253 qos_lib: String::from(*lib),
254 profile: String::from(*p),
255 }
256 }
257 (RestMethod::Post, ["applications", app, "domain_participants"]) => {
259 RestRoute::CreateParticipant {
260 app: String::from(*app),
261 }
262 }
263 (RestMethod::Get, ["applications", app, "domain_participants"]) => {
264 RestRoute::GetParticipants {
265 app: String::from(*app),
266 }
267 }
268 (RestMethod::Put, ["applications", app, "domain_participants", part]) => {
269 RestRoute::UpdateParticipant {
270 app: String::from(*app),
271 participant: String::from(*part),
272 }
273 }
274 (RestMethod::Delete, ["applications", app, "domain_participants", part]) => {
275 RestRoute::DeleteParticipant {
276 app: String::from(*app),
277 participant: String::from(*part),
278 }
279 }
280 (RestMethod::Post, ["applications", app, "waitsets"]) => RestRoute::CreateWaitset {
282 app: String::from(*app),
283 },
284 (RestMethod::Get, ["applications", app, "waitsets", ws]) => RestRoute::GetWaitset {
285 app: String::from(*app),
286 waitset: String::from(*ws),
287 },
288 (RestMethod::Post, ["applications", app, "domain_participants", part, "topics"]) => {
290 RestRoute::CreateTopic {
291 app: String::from(*app),
292 participant: String::from(*part),
293 }
294 }
295 (
296 RestMethod::Post,
297 [
298 "applications",
299 app,
300 "domain_participants",
301 part,
302 "publishers",
303 ],
304 ) => RestRoute::CreatePublisher {
305 app: String::from(*app),
306 participant: String::from(*part),
307 },
308 (
309 RestMethod::Post,
310 [
311 "applications",
312 app,
313 "domain_participants",
314 part,
315 "subscribers",
316 ],
317 ) => RestRoute::CreateSubscriber {
318 app: String::from(*app),
319 participant: String::from(*part),
320 },
321 (
323 RestMethod::Post,
324 [
325 "applications",
326 app,
327 "domain_participants",
328 part,
329 "publishers",
330 pubn,
331 "data_writers",
332 dw,
333 ],
334 ) => RestRoute::DataWriterWrite {
335 app: String::from(*app),
336 participant: String::from(*part),
337 publisher: String::from(*pubn),
338 data_writer: String::from(*dw),
339 },
340 (
342 RestMethod::Get,
343 [
344 "applications",
345 app,
346 "domain_participants",
347 part,
348 "subscribers",
349 sub,
350 "data_readers",
351 dr,
352 ],
353 ) => RestRoute::DataReaderRead {
354 app: String::from(*app),
355 participant: String::from(*part),
356 subscriber: String::from(*sub),
357 data_reader: String::from(*dr),
358 },
359 _ => RestRoute::Unknown {
360 path: String::from(path),
361 },
362 })
363}
364
365#[derive(Debug, Clone, Copy, PartialEq, Eq)]
367pub enum RouteError {
368 MissingPrefix,
370}
371
372impl core::fmt::Display for RouteError {
373 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
374 match self {
375 Self::MissingPrefix => f.write_str("URI must start with /dds/rest1"),
376 }
377 }
378}
379
380#[cfg(feature = "std")]
381impl std::error::Error for RouteError {}
382
383#[cfg(test)]
384#[allow(clippy::expect_used, clippy::panic)]
385mod tests {
386 use super::*;
387
388 #[test]
389 fn rest_prefix_constant_matches_spec() {
390 assert_eq!(REST_PREFIX, "/dds/rest1");
391 }
392
393 #[test]
394 fn missing_prefix_yields_error() {
395 assert_eq!(
396 parse_route(RestMethod::Get, "/foo/bar"),
397 Err(RouteError::MissingPrefix)
398 );
399 }
400
401 #[test]
402 fn create_application_route() {
403 let r = parse_route(RestMethod::Post, "/dds/rest1/applications").expect("ok");
405 assert_eq!(r, RestRoute::CreateApplication);
406 }
407
408 #[test]
409 fn get_applications_route() {
410 let r = parse_route(RestMethod::Get, "/dds/rest1/applications").expect("ok");
411 assert_eq!(r, RestRoute::GetApplications);
412 }
413
414 #[test]
415 fn delete_application_route_extracts_app_name() {
416 let r = parse_route(RestMethod::Delete, "/dds/rest1/applications/myapp").expect("ok");
417 assert_eq!(
418 r,
419 RestRoute::DeleteApplication {
420 app: String::from("myapp")
421 }
422 );
423 }
424
425 #[test]
426 fn create_participant_route_extracts_app_name() {
427 let r = parse_route(
428 RestMethod::Post,
429 "/dds/rest1/applications/myapp/domain_participants",
430 )
431 .expect("ok");
432 assert_eq!(
433 r,
434 RestRoute::CreateParticipant {
435 app: String::from("myapp")
436 }
437 );
438 }
439
440 #[test]
441 fn delete_participant_route_extracts_app_and_participant() {
442 let r = parse_route(
443 RestMethod::Delete,
444 "/dds/rest1/applications/myapp/domain_participants/p1",
445 )
446 .expect("ok");
447 assert_eq!(
448 r,
449 RestRoute::DeleteParticipant {
450 app: String::from("myapp"),
451 participant: String::from("p1")
452 }
453 );
454 }
455
456 #[test]
457 fn data_writer_write_route_extracts_4_segments() {
458 let r = parse_route(
459 RestMethod::Post,
460 "/dds/rest1/applications/app/domain_participants/p/publishers/pub/data_writers/dw",
461 )
462 .expect("ok");
463 assert_eq!(
464 r,
465 RestRoute::DataWriterWrite {
466 app: String::from("app"),
467 participant: String::from("p"),
468 publisher: String::from("pub"),
469 data_writer: String::from("dw"),
470 }
471 );
472 }
473
474 #[test]
475 fn data_reader_read_route_extracts_4_segments() {
476 let r = parse_route(
477 RestMethod::Get,
478 "/dds/rest1/applications/app/domain_participants/p/subscribers/sub/data_readers/dr",
479 )
480 .expect("ok");
481 assert_eq!(
482 r,
483 RestRoute::DataReaderRead {
484 app: String::from("app"),
485 participant: String::from("p"),
486 subscriber: String::from("sub"),
487 data_reader: String::from("dr"),
488 }
489 );
490 }
491
492 #[test]
493 fn qos_profile_routes_extract_lib_and_profile_name() {
494 let create = parse_route(
495 RestMethod::Post,
496 "/dds/rest1/qos_libraries/myLib/qos_profiles",
497 )
498 .expect("ok");
499 assert_eq!(
500 create,
501 RestRoute::CreateQosProfile {
502 qos_lib: String::from("myLib")
503 }
504 );
505 let put = parse_route(
506 RestMethod::Put,
507 "/dds/rest1/qos_libraries/myLib/qos_profiles/highQos",
508 )
509 .expect("ok");
510 assert_eq!(
511 put,
512 RestRoute::UpdateQosProfile {
513 qos_lib: String::from("myLib"),
514 profile: String::from("highQos")
515 }
516 );
517 }
518
519 #[test]
520 fn waitset_routes_extract_app_and_waitset() {
521 let create =
522 parse_route(RestMethod::Post, "/dds/rest1/applications/app/waitsets").expect("ok");
523 assert_eq!(
524 create,
525 RestRoute::CreateWaitset {
526 app: String::from("app")
527 }
528 );
529 let get =
530 parse_route(RestMethod::Get, "/dds/rest1/applications/app/waitsets/ws1").expect("ok");
531 assert_eq!(
532 get,
533 RestRoute::GetWaitset {
534 app: String::from("app"),
535 waitset: String::from("ws1"),
536 }
537 );
538 }
539
540 #[test]
541 fn topic_publisher_subscriber_create_routes() {
542 for (segment, expected_variant_test) in [
543 ("topics", "topic"),
544 ("publishers", "publisher"),
545 ("subscribers", "subscriber"),
546 ] {
547 let uri = alloc::format!("/dds/rest1/applications/app/domain_participants/p/{segment}");
548 let r = parse_route(RestMethod::Post, &uri).expect("ok");
549 assert!(
551 !matches!(r, RestRoute::Unknown { .. }),
552 "{expected_variant_test} -> got {r:?}"
553 );
554 }
555 }
556
557 #[test]
558 fn unknown_paths_yield_unknown_variant() {
559 let r = parse_route(RestMethod::Get, "/dds/rest1/foobar").expect("ok");
560 match r {
561 RestRoute::Unknown { path } => assert_eq!(path, "/foobar"),
562 _ => panic!("expected unknown"),
563 }
564 }
565
566 #[test]
567 fn types_routes_match_root_pattern() {
568 let create = parse_route(RestMethod::Post, "/dds/rest1/types").expect("ok");
570 assert_eq!(create, RestRoute::CreateType);
571 let delete = parse_route(RestMethod::Delete, "/dds/rest1/types/MyType").expect("ok");
572 assert_eq!(
573 delete,
574 RestRoute::DeleteType {
575 type_name: String::from("MyType")
576 }
577 );
578 }
579}