use alloc::string::String;
pub const REST_PREFIX: &str = "/dds/rest1";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RestMethod {
Post,
Put,
Get,
Delete,
Head,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RestRoute {
CreateApplication,
DeleteApplication {
app: String,
},
GetApplications,
CreateType,
DeleteType {
type_name: String,
},
GetTypes,
CreateQosLibrary,
UpdateQosLibrary {
qos_lib: String,
},
DeleteQosLibrary {
qos_lib: String,
},
GetQosLibraries,
CreateQosProfile {
qos_lib: String,
},
UpdateQosProfile {
qos_lib: String,
profile: String,
},
DeleteQosProfile {
qos_lib: String,
profile: String,
},
GetQosProfiles {
qos_lib: String,
},
CreateParticipant {
app: String,
},
UpdateParticipant {
app: String,
participant: String,
},
DeleteParticipant {
app: String,
participant: String,
},
GetParticipants {
app: String,
},
CreateWaitset {
app: String,
},
GetWaitset {
app: String,
waitset: String,
},
CreateTopic {
app: String,
participant: String,
},
CreatePublisher {
app: String,
participant: String,
},
CreateSubscriber {
app: String,
participant: String,
},
DataWriterWrite {
app: String,
participant: String,
publisher: String,
data_writer: String,
},
DataReaderRead {
app: String,
participant: String,
subscriber: String,
data_reader: String,
},
Unknown {
path: String,
},
}
pub fn parse_route(method: RestMethod, uri: &str) -> Result<RestRoute, RouteError> {
let path = uri
.strip_prefix(REST_PREFIX)
.ok_or(RouteError::MissingPrefix)?;
let segments: alloc::vec::Vec<&str> = path
.trim_start_matches('/')
.trim_end_matches('/')
.split('/')
.filter(|s| !s.is_empty())
.collect();
Ok(match (method, segments.as_slice()) {
(RestMethod::Post, ["applications"]) => RestRoute::CreateApplication,
(RestMethod::Get, ["applications"]) => RestRoute::GetApplications,
(RestMethod::Delete, ["applications", app]) => RestRoute::DeleteApplication {
app: String::from(*app),
},
(RestMethod::Post, ["types"]) => RestRoute::CreateType,
(RestMethod::Get, ["types"]) => RestRoute::GetTypes,
(RestMethod::Delete, ["types", t]) => RestRoute::DeleteType {
type_name: String::from(*t),
},
(RestMethod::Post, ["qos_libraries"]) => RestRoute::CreateQosLibrary,
(RestMethod::Get, ["qos_libraries"]) => RestRoute::GetQosLibraries,
(RestMethod::Put, ["qos_libraries", lib]) => RestRoute::UpdateQosLibrary {
qos_lib: String::from(*lib),
},
(RestMethod::Delete, ["qos_libraries", lib]) => RestRoute::DeleteQosLibrary {
qos_lib: String::from(*lib),
},
(RestMethod::Post, ["qos_libraries", lib, "qos_profiles"]) => RestRoute::CreateQosProfile {
qos_lib: String::from(*lib),
},
(RestMethod::Get, ["qos_libraries", lib, "qos_profiles"]) => RestRoute::GetQosProfiles {
qos_lib: String::from(*lib),
},
(RestMethod::Put, ["qos_libraries", lib, "qos_profiles", p]) => {
RestRoute::UpdateQosProfile {
qos_lib: String::from(*lib),
profile: String::from(*p),
}
}
(RestMethod::Delete, ["qos_libraries", lib, "qos_profiles", p]) => {
RestRoute::DeleteQosProfile {
qos_lib: String::from(*lib),
profile: String::from(*p),
}
}
(RestMethod::Post, ["applications", app, "domain_participants"]) => {
RestRoute::CreateParticipant {
app: String::from(*app),
}
}
(RestMethod::Get, ["applications", app, "domain_participants"]) => {
RestRoute::GetParticipants {
app: String::from(*app),
}
}
(RestMethod::Put, ["applications", app, "domain_participants", part]) => {
RestRoute::UpdateParticipant {
app: String::from(*app),
participant: String::from(*part),
}
}
(RestMethod::Delete, ["applications", app, "domain_participants", part]) => {
RestRoute::DeleteParticipant {
app: String::from(*app),
participant: String::from(*part),
}
}
(RestMethod::Post, ["applications", app, "waitsets"]) => RestRoute::CreateWaitset {
app: String::from(*app),
},
(RestMethod::Get, ["applications", app, "waitsets", ws]) => RestRoute::GetWaitset {
app: String::from(*app),
waitset: String::from(*ws),
},
(RestMethod::Post, ["applications", app, "domain_participants", part, "topics"]) => {
RestRoute::CreateTopic {
app: String::from(*app),
participant: String::from(*part),
}
}
(
RestMethod::Post,
[
"applications",
app,
"domain_participants",
part,
"publishers",
],
) => RestRoute::CreatePublisher {
app: String::from(*app),
participant: String::from(*part),
},
(
RestMethod::Post,
[
"applications",
app,
"domain_participants",
part,
"subscribers",
],
) => RestRoute::CreateSubscriber {
app: String::from(*app),
participant: String::from(*part),
},
(
RestMethod::Post,
[
"applications",
app,
"domain_participants",
part,
"publishers",
pubn,
"data_writers",
dw,
],
) => RestRoute::DataWriterWrite {
app: String::from(*app),
participant: String::from(*part),
publisher: String::from(*pubn),
data_writer: String::from(*dw),
},
(
RestMethod::Get,
[
"applications",
app,
"domain_participants",
part,
"subscribers",
sub,
"data_readers",
dr,
],
) => RestRoute::DataReaderRead {
app: String::from(*app),
participant: String::from(*part),
subscriber: String::from(*sub),
data_reader: String::from(*dr),
},
_ => RestRoute::Unknown {
path: String::from(path),
},
})
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RouteError {
MissingPrefix,
}
impl core::fmt::Display for RouteError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::MissingPrefix => f.write_str("URI must start with /dds/rest1"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for RouteError {}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::panic)]
mod tests {
use super::*;
#[test]
fn rest_prefix_constant_matches_spec() {
assert_eq!(REST_PREFIX, "/dds/rest1");
}
#[test]
fn missing_prefix_yields_error() {
assert_eq!(
parse_route(RestMethod::Get, "/foo/bar"),
Err(RouteError::MissingPrefix)
);
}
#[test]
fn create_application_route() {
let r = parse_route(RestMethod::Post, "/dds/rest1/applications").expect("ok");
assert_eq!(r, RestRoute::CreateApplication);
}
#[test]
fn get_applications_route() {
let r = parse_route(RestMethod::Get, "/dds/rest1/applications").expect("ok");
assert_eq!(r, RestRoute::GetApplications);
}
#[test]
fn delete_application_route_extracts_app_name() {
let r = parse_route(RestMethod::Delete, "/dds/rest1/applications/myapp").expect("ok");
assert_eq!(
r,
RestRoute::DeleteApplication {
app: String::from("myapp")
}
);
}
#[test]
fn create_participant_route_extracts_app_name() {
let r = parse_route(
RestMethod::Post,
"/dds/rest1/applications/myapp/domain_participants",
)
.expect("ok");
assert_eq!(
r,
RestRoute::CreateParticipant {
app: String::from("myapp")
}
);
}
#[test]
fn delete_participant_route_extracts_app_and_participant() {
let r = parse_route(
RestMethod::Delete,
"/dds/rest1/applications/myapp/domain_participants/p1",
)
.expect("ok");
assert_eq!(
r,
RestRoute::DeleteParticipant {
app: String::from("myapp"),
participant: String::from("p1")
}
);
}
#[test]
fn data_writer_write_route_extracts_4_segments() {
let r = parse_route(
RestMethod::Post,
"/dds/rest1/applications/app/domain_participants/p/publishers/pub/data_writers/dw",
)
.expect("ok");
assert_eq!(
r,
RestRoute::DataWriterWrite {
app: String::from("app"),
participant: String::from("p"),
publisher: String::from("pub"),
data_writer: String::from("dw"),
}
);
}
#[test]
fn data_reader_read_route_extracts_4_segments() {
let r = parse_route(
RestMethod::Get,
"/dds/rest1/applications/app/domain_participants/p/subscribers/sub/data_readers/dr",
)
.expect("ok");
assert_eq!(
r,
RestRoute::DataReaderRead {
app: String::from("app"),
participant: String::from("p"),
subscriber: String::from("sub"),
data_reader: String::from("dr"),
}
);
}
#[test]
fn qos_profile_routes_extract_lib_and_profile_name() {
let create = parse_route(
RestMethod::Post,
"/dds/rest1/qos_libraries/myLib/qos_profiles",
)
.expect("ok");
assert_eq!(
create,
RestRoute::CreateQosProfile {
qos_lib: String::from("myLib")
}
);
let put = parse_route(
RestMethod::Put,
"/dds/rest1/qos_libraries/myLib/qos_profiles/highQos",
)
.expect("ok");
assert_eq!(
put,
RestRoute::UpdateQosProfile {
qos_lib: String::from("myLib"),
profile: String::from("highQos")
}
);
}
#[test]
fn waitset_routes_extract_app_and_waitset() {
let create =
parse_route(RestMethod::Post, "/dds/rest1/applications/app/waitsets").expect("ok");
assert_eq!(
create,
RestRoute::CreateWaitset {
app: String::from("app")
}
);
let get =
parse_route(RestMethod::Get, "/dds/rest1/applications/app/waitsets/ws1").expect("ok");
assert_eq!(
get,
RestRoute::GetWaitset {
app: String::from("app"),
waitset: String::from("ws1"),
}
);
}
#[test]
fn topic_publisher_subscriber_create_routes() {
for (segment, expected_variant_test) in [
("topics", "topic"),
("publishers", "publisher"),
("subscribers", "subscriber"),
] {
let uri = alloc::format!("/dds/rest1/applications/app/domain_participants/p/{segment}");
let r = parse_route(RestMethod::Post, &uri).expect("ok");
assert!(
!matches!(r, RestRoute::Unknown { .. }),
"{expected_variant_test} -> got {r:?}"
);
}
}
#[test]
fn unknown_paths_yield_unknown_variant() {
let r = parse_route(RestMethod::Get, "/dds/rest1/foobar").expect("ok");
match r {
RestRoute::Unknown { path } => assert_eq!(path, "/foobar"),
_ => panic!("expected unknown"),
}
}
#[test]
fn types_routes_match_root_pattern() {
let create = parse_route(RestMethod::Post, "/dds/rest1/types").expect("ok");
assert_eq!(create, RestRoute::CreateType);
let delete = parse_route(RestMethod::Delete, "/dds/rest1/types/MyType").expect("ok");
assert_eq!(
delete,
RestRoute::DeleteType {
type_name: String::from("MyType")
}
);
}
}