use alloc::string::String;
use alloc::vec::Vec;
use crate::access_control::Decision;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BackendResult {
Created(String),
Deleted,
List(Vec<String>),
Body {
content_type: String,
body: Vec<u8>,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BackendError {
NotFound(String),
Conflict(String),
BadRequest(String),
Forbidden,
Internal(String),
}
pub trait DdsBackend {
fn create_application(&mut self, app_name: &str) -> Result<BackendResult, BackendError>;
fn delete_application(&mut self, app_name: &str) -> Result<BackendResult, BackendError>;
fn list_applications(&self, pattern: &str) -> Result<BackendResult, BackendError>;
fn create_participant(
&mut self,
app_name: &str,
domain_id: u32,
) -> Result<BackendResult, BackendError>;
fn delete_participant(
&mut self,
app_name: &str,
participant: &str,
) -> Result<BackendResult, BackendError>;
fn create_topic(
&mut self,
app_name: &str,
participant: &str,
topic_name: &str,
type_name: &str,
) -> Result<BackendResult, BackendError>;
fn create_data_writer(
&mut self,
app_name: &str,
participant: &str,
publisher: &str,
topic: &str,
) -> Result<BackendResult, BackendError>;
fn create_data_reader(
&mut self,
app_name: &str,
participant: &str,
subscriber: &str,
topic: &str,
) -> Result<BackendResult, BackendError>;
fn write_sample(
&mut self,
app_name: &str,
participant: &str,
publisher: &str,
writer: &str,
body: &[u8],
) -> Result<BackendResult, BackendError>;
fn read_samples(
&mut self,
app_name: &str,
participant: &str,
subscriber: &str,
reader: &str,
selector: Option<&str>,
) -> Result<BackendResult, BackendError>;
}
pub fn enforce(decision: Decision) -> Result<(), BackendError> {
match decision {
Decision::Permit => Ok(()),
Decision::Deny => Err(BackendError::Forbidden),
}
}
#[cfg(test)]
#[allow(clippy::expect_used)]
mod tests {
use super::*;
use crate::access_control::{Decision, Operation, Permissions, Rule};
use alloc::collections::BTreeMap;
#[derive(Default)]
struct InMemoryBackend {
apps: BTreeMap<String, ()>,
}
impl DdsBackend for InMemoryBackend {
fn create_application(&mut self, n: &str) -> Result<BackendResult, BackendError> {
if self.apps.contains_key(n) {
return Err(BackendError::Conflict(n.to_string()));
}
self.apps.insert(n.to_string(), ());
Ok(BackendResult::Created(alloc::format!("/applications/{n}")))
}
fn delete_application(&mut self, n: &str) -> Result<BackendResult, BackendError> {
if self.apps.remove(n).is_none() {
return Err(BackendError::NotFound(n.to_string()));
}
Ok(BackendResult::Deleted)
}
fn list_applications(&self, _p: &str) -> Result<BackendResult, BackendError> {
Ok(BackendResult::List(self.apps.keys().cloned().collect()))
}
fn create_participant(&mut self, _: &str, _: u32) -> Result<BackendResult, BackendError> {
Ok(BackendResult::Created("p".to_string()))
}
fn delete_participant(&mut self, _: &str, _: &str) -> Result<BackendResult, BackendError> {
Ok(BackendResult::Deleted)
}
fn create_topic(
&mut self,
_: &str,
_: &str,
_: &str,
_: &str,
) -> Result<BackendResult, BackendError> {
Ok(BackendResult::Created("t".to_string()))
}
fn create_data_writer(
&mut self,
_: &str,
_: &str,
_: &str,
_: &str,
) -> Result<BackendResult, BackendError> {
Ok(BackendResult::Created("dw".to_string()))
}
fn create_data_reader(
&mut self,
_: &str,
_: &str,
_: &str,
_: &str,
) -> Result<BackendResult, BackendError> {
Ok(BackendResult::Created("dr".to_string()))
}
fn write_sample(
&mut self,
_: &str,
_: &str,
_: &str,
_: &str,
_: &[u8],
) -> Result<BackendResult, BackendError> {
Ok(BackendResult::Deleted)
}
fn read_samples(
&mut self,
_: &str,
_: &str,
_: &str,
_: &str,
_: Option<&str>,
) -> Result<BackendResult, BackendError> {
Ok(BackendResult::List(Vec::new()))
}
}
#[test]
fn enforce_permit_allows_call() {
assert!(enforce(Decision::Permit).is_ok());
}
#[test]
fn enforce_deny_returns_forbidden() {
let r = enforce(Decision::Deny);
assert!(matches!(r, Err(BackendError::Forbidden)));
}
#[test]
fn permission_check_then_backend_call_chains_correctly() {
let perms = Permissions {
subject_name: "alice".to_string(),
default: Decision::Deny,
rules: alloc::vec![Rule::allow("*", alloc::vec![Operation::Admin])],
};
let mut backend = InMemoryBackend::default();
let dec = perms.evaluate(Operation::Admin, "App1");
assert!(enforce(dec).is_ok());
let r = backend.create_application("App1").expect("ok");
assert!(matches!(r, BackendResult::Created(_)));
}
#[test]
fn create_then_delete_application_round_trip() {
let mut b = InMemoryBackend::default();
b.create_application("X").expect("ok");
let r = b.delete_application("X").expect("ok");
assert_eq!(r, BackendResult::Deleted);
}
#[test]
fn create_application_twice_yields_conflict() {
let mut b = InMemoryBackend::default();
b.create_application("X").expect("ok");
let err = b.create_application("X").expect_err("conflict");
assert!(matches!(err, BackendError::Conflict(_)));
}
}