use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
pub enum TenancyModel {
#[default]
TenantScoped,
Shared,
Configurable,
}
pub trait ResourceTenancy: Send + Sync {
fn tenancy_model(&self, resource_type: &str) -> TenancyModel;
fn is_shared(&self, resource_type: &str) -> bool {
self.tenancy_model(resource_type) == TenancyModel::Shared
}
fn is_tenant_scoped(&self, resource_type: &str) -> bool {
self.tenancy_model(resource_type) == TenancyModel::TenantScoped
}
}
#[derive(Debug, Clone, Default)]
pub struct DefaultResourceTenancy;
impl ResourceTenancy for DefaultResourceTenancy {
fn tenancy_model(&self, resource_type: &str) -> TenancyModel {
match resource_type {
"CodeSystem" | "ValueSet" | "ConceptMap" | "NamingSystem" => TenancyModel::Shared,
"StructureDefinition"
| "CapabilityStatement"
| "SearchParameter"
| "OperationDefinition"
| "CompartmentDefinition"
| "ImplementationGuide"
| "MessageDefinition"
| "StructureMap"
| "GraphDefinition"
| "ExampleScenario" => TenancyModel::Shared,
"Library" | "Measure" | "PlanDefinition" | "ActivityDefinition" | "Questionnaire" => {
TenancyModel::Shared
}
"Organization" | "Location" | "HealthcareService" | "Endpoint" => {
TenancyModel::Configurable
}
_ => TenancyModel::TenantScoped,
}
}
}
#[derive(Debug, Clone)]
pub struct CustomResourceTenancy<F: ResourceTenancy> {
overrides: std::collections::HashMap<String, TenancyModel>,
fallback: F,
}
impl<F: ResourceTenancy> CustomResourceTenancy<F> {
pub fn new(fallback: F) -> Self {
Self {
overrides: std::collections::HashMap::new(),
fallback,
}
}
pub fn with_override(mut self, resource_type: &str, model: TenancyModel) -> Self {
self.overrides.insert(resource_type.to_string(), model);
self
}
}
impl<F: ResourceTenancy> ResourceTenancy for CustomResourceTenancy<F> {
fn tenancy_model(&self, resource_type: &str) -> TenancyModel {
self.overrides
.get(resource_type)
.copied()
.unwrap_or_else(|| self.fallback.tenancy_model(resource_type))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_tenancy_clinical() {
let tenancy = DefaultResourceTenancy;
assert_eq!(tenancy.tenancy_model("Patient"), TenancyModel::TenantScoped);
assert_eq!(
tenancy.tenancy_model("Observation"),
TenancyModel::TenantScoped
);
assert_eq!(
tenancy.tenancy_model("Encounter"),
TenancyModel::TenantScoped
);
}
#[test]
fn test_default_tenancy_terminology() {
let tenancy = DefaultResourceTenancy;
assert_eq!(tenancy.tenancy_model("CodeSystem"), TenancyModel::Shared);
assert_eq!(tenancy.tenancy_model("ValueSet"), TenancyModel::Shared);
assert_eq!(tenancy.tenancy_model("ConceptMap"), TenancyModel::Shared);
}
#[test]
fn test_default_tenancy_conformance() {
let tenancy = DefaultResourceTenancy;
assert_eq!(
tenancy.tenancy_model("StructureDefinition"),
TenancyModel::Shared
);
assert_eq!(
tenancy.tenancy_model("CapabilityStatement"),
TenancyModel::Shared
);
}
#[test]
fn test_default_tenancy_configurable() {
let tenancy = DefaultResourceTenancy;
assert_eq!(
tenancy.tenancy_model("Organization"),
TenancyModel::Configurable
);
assert_eq!(
tenancy.tenancy_model("Location"),
TenancyModel::Configurable
);
}
#[test]
fn test_is_shared() {
let tenancy = DefaultResourceTenancy;
assert!(tenancy.is_shared("CodeSystem"));
assert!(!tenancy.is_shared("Patient"));
}
#[test]
fn test_is_tenant_scoped() {
let tenancy = DefaultResourceTenancy;
assert!(tenancy.is_tenant_scoped("Patient"));
assert!(!tenancy.is_tenant_scoped("CodeSystem"));
}
#[test]
fn test_custom_tenancy() {
let tenancy = CustomResourceTenancy::new(DefaultResourceTenancy)
.with_override("Organization", TenancyModel::TenantScoped);
assert_eq!(
tenancy.tenancy_model("Organization"),
TenancyModel::TenantScoped
);
assert_eq!(tenancy.tenancy_model("Patient"), TenancyModel::TenantScoped);
assert_eq!(tenancy.tenancy_model("CodeSystem"), TenancyModel::Shared);
}
}