use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Extension {
pub namespace: String,
pub name: String,
pub description: Option<String>,
pub version: Option<String>,
pub authors: Vec<String>,
pub samm_version: String,
pub elements: Vec<ExtensionElement>,
pub custom_properties: HashMap<String, PropertyDefinition>,
pub validation_rules: Vec<ValidationRule>,
}
impl Extension {
pub fn new(namespace: impl Into<String>, name: impl Into<String>) -> Self {
Self {
namespace: namespace.into(),
name: name.into(),
description: None,
version: Some("1.0.0".to_string()),
authors: Vec::new(),
samm_version: "2.1.0".to_string(),
elements: Vec::new(),
custom_properties: HashMap::new(),
validation_rules: Vec::new(),
}
}
pub fn set_description(&mut self, description: impl Into<String>) {
self.description = Some(description.into());
}
pub fn set_version(&mut self, version: impl Into<String>) {
self.version = Some(version.into());
}
pub fn add_author(&mut self, author: impl Into<String>) {
self.authors.push(author.into());
}
pub fn set_samm_version(&mut self, version: impl Into<String>) {
self.samm_version = version.into();
}
pub fn add_element(&mut self, element: ExtensionElement) {
self.elements.push(element);
}
pub fn add_custom_property(&mut self, name: String, property: PropertyDefinition) {
self.custom_properties.insert(name, property);
}
pub fn add_validation_rule(&mut self, rule: ValidationRule) {
self.validation_rules.push(rule);
}
pub fn get_element(&self, name: &str) -> Option<&ExtensionElement> {
self.elements.iter().find(|e| e.name == name)
}
pub fn has_element(&self, name: &str) -> bool {
self.elements.iter().any(|e| e.name == name)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExtensionElement {
pub name: String,
pub description: String,
pub element_type: String,
pub extends: Option<String>,
pub required_properties: Vec<String>,
pub optional_properties: Vec<String>,
pub attributes: HashMap<String, String>,
}
impl ExtensionElement {
pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
Self {
name: name.into(),
description: description.into(),
element_type: "Custom".to_string(),
extends: None,
required_properties: Vec::new(),
optional_properties: Vec::new(),
attributes: HashMap::new(),
}
}
pub fn with_type(mut self, element_type: impl Into<String>) -> Self {
self.element_type = element_type.into();
self
}
pub fn extends(mut self, parent: impl Into<String>) -> Self {
self.extends = Some(parent.into());
self
}
pub fn require(mut self, property: impl Into<String>) -> Self {
self.required_properties.push(property.into());
self
}
pub fn optional(mut self, property: impl Into<String>) -> Self {
self.optional_properties.push(property.into());
self
}
pub fn attribute(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.attributes.insert(key.into(), value.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PropertyDefinition {
pub name: String,
pub description: String,
pub data_type: String,
pub required: bool,
pub default_value: Option<String>,
pub allowed_values: Vec<String>,
}
impl PropertyDefinition {
pub fn new(name: impl Into<String>, data_type: impl Into<String>) -> Self {
Self {
name: name.into(),
description: String::new(),
data_type: data_type.into(),
required: false,
default_value: None,
allowed_values: Vec::new(),
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = description.into();
self
}
pub fn required(mut self) -> Self {
self.required = true;
self
}
pub fn with_default(mut self, value: impl Into<String>) -> Self {
self.default_value = Some(value.into());
self
}
pub fn allow(mut self, value: impl Into<String>) -> Self {
self.allowed_values.push(value.into());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationRule {
pub name: String,
pub description: String,
pub applies_to: String,
pub severity: ValidationSeverity,
pub expression: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ValidationSeverity {
Error,
Warning,
Info,
}
pub struct ExtensionRegistry {
extensions: Arc<RwLock<HashMap<String, Extension>>>,
}
impl ExtensionRegistry {
pub fn new() -> Self {
Self {
extensions: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn register(&self, extension: Extension) {
let namespace = extension.namespace.clone();
let mut extensions = self
.extensions
.write()
.expect("write lock should not be poisoned");
extensions.insert(namespace, extension);
}
pub fn get(&self, namespace: &str) -> Option<Extension> {
let extensions = self
.extensions
.read()
.expect("read lock should not be poisoned");
extensions.get(namespace).cloned()
}
pub fn has(&self, namespace: &str) -> bool {
let extensions = self
.extensions
.read()
.expect("read lock should not be poisoned");
extensions.contains_key(namespace)
}
pub fn remove(&self, namespace: &str) -> bool {
let mut extensions = self
.extensions
.write()
.expect("write lock should not be poisoned");
extensions.remove(namespace).is_some()
}
pub fn list(&self) -> Vec<String> {
let extensions = self
.extensions
.read()
.expect("read lock should not be poisoned");
extensions.keys().cloned().collect()
}
pub fn count(&self) -> usize {
let extensions = self
.extensions
.read()
.expect("read lock should not be poisoned");
extensions.len()
}
pub fn clear(&self) {
let mut extensions = self
.extensions
.write()
.expect("write lock should not be poisoned");
extensions.clear();
}
pub fn all(&self) -> Vec<Extension> {
let extensions = self
.extensions
.read()
.expect("read lock should not be poisoned");
extensions.values().cloned().collect()
}
pub fn find_by_samm_version(&self, version: &str) -> Vec<Extension> {
let extensions = self
.extensions
.read()
.expect("read lock should not be poisoned");
extensions
.values()
.filter(|ext| ext.samm_version == version)
.cloned()
.collect()
}
}
impl Default for ExtensionRegistry {
fn default() -> Self {
Self::new()
}
}
impl Clone for ExtensionRegistry {
fn clone(&self) -> Self {
Self {
extensions: Arc::clone(&self.extensions),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extension_creation() {
let ext = Extension::new("urn:ext:test", "Test Extension");
assert_eq!(ext.namespace, "urn:ext:test");
assert_eq!(ext.name, "Test Extension");
assert_eq!(ext.samm_version, "2.1.0");
}
#[test]
fn test_extension_builder() {
let mut ext = Extension::new("urn:ext:test", "Test");
ext.set_description("A test extension");
ext.set_version("2.0.0");
ext.add_author("Test Author");
ext.set_samm_version("2.1.0");
assert_eq!(ext.description, Some("A test extension".to_string()));
assert_eq!(ext.version, Some("2.0.0".to_string()));
assert_eq!(ext.authors.len(), 1);
}
#[test]
fn test_extension_element() {
let element = ExtensionElement::new("CustomChar", "Custom characteristic")
.with_type("Characteristic")
.extends("samm:Characteristic")
.require("customProperty")
.optional("optionalProperty")
.attribute("category", "validation");
assert_eq!(element.name, "CustomChar");
assert_eq!(element.element_type, "Characteristic");
assert_eq!(element.extends, Some("samm:Characteristic".to_string()));
assert_eq!(element.required_properties.len(), 1);
assert_eq!(element.optional_properties.len(), 1);
assert_eq!(
element.attributes.get("category"),
Some(&"validation".to_string())
);
}
#[test]
fn test_property_definition() {
let prop = PropertyDefinition::new("industryCode", "string")
.with_description("Industry classification code")
.required()
.with_default("UNKNOWN")
.allow("AUTOMOTIVE")
.allow("AEROSPACE")
.allow("MANUFACTURING");
assert_eq!(prop.name, "industryCode");
assert_eq!(prop.data_type, "string");
assert!(prop.required);
assert_eq!(prop.default_value, Some("UNKNOWN".to_string()));
assert_eq!(prop.allowed_values.len(), 3);
}
#[test]
fn test_registry_basic() {
let registry = ExtensionRegistry::new();
assert_eq!(registry.count(), 0);
let ext = Extension::new("urn:ext:test1", "Test 1");
registry.register(ext);
assert_eq!(registry.count(), 1);
assert!(registry.has("urn:ext:test1"));
assert!(!registry.has("urn:ext:nonexistent"));
}
#[test]
fn test_registry_get() {
let registry = ExtensionRegistry::new();
let ext = Extension::new("urn:ext:test", "Test");
registry.register(ext);
let retrieved = registry.get("urn:ext:test");
assert!(retrieved.is_some());
assert_eq!(retrieved.expect("operation should succeed").name, "Test");
}
#[test]
fn test_registry_remove() {
let registry = ExtensionRegistry::new();
let ext = Extension::new("urn:ext:test", "Test");
registry.register(ext);
assert_eq!(registry.count(), 1);
assert!(registry.remove("urn:ext:test"));
assert_eq!(registry.count(), 0);
assert!(!registry.remove("urn:ext:test"));
}
#[test]
fn test_registry_list() {
let registry = ExtensionRegistry::new();
registry.register(Extension::new("urn:ext:a", "A"));
registry.register(Extension::new("urn:ext:b", "B"));
registry.register(Extension::new("urn:ext:c", "C"));
let list = registry.list();
assert_eq!(list.len(), 3);
assert!(list.contains(&"urn:ext:a".to_string()));
assert!(list.contains(&"urn:ext:b".to_string()));
assert!(list.contains(&"urn:ext:c".to_string()));
}
#[test]
fn test_registry_clear() {
let registry = ExtensionRegistry::new();
registry.register(Extension::new("urn:ext:a", "A"));
registry.register(Extension::new("urn:ext:b", "B"));
assert_eq!(registry.count(), 2);
registry.clear();
assert_eq!(registry.count(), 0);
}
#[test]
fn test_find_by_samm_version() {
let registry = ExtensionRegistry::new();
let mut ext1 = Extension::new("urn:ext:a", "A");
ext1.set_samm_version("2.1.0");
registry.register(ext1);
let mut ext2 = Extension::new("urn:ext:b", "B");
ext2.set_samm_version("2.2.0");
registry.register(ext2);
let mut ext3 = Extension::new("urn:ext:c", "C");
ext3.set_samm_version("2.1.0");
registry.register(ext3);
let v21_exts = registry.find_by_samm_version("2.1.0");
assert_eq!(v21_exts.len(), 2);
let v22_exts = registry.find_by_samm_version("2.2.0");
assert_eq!(v22_exts.len(), 1);
}
#[test]
fn test_extension_add_element() {
let mut ext = Extension::new("urn:ext:test", "Test");
let element = ExtensionElement::new("Custom", "Custom element");
ext.add_element(element);
assert_eq!(ext.elements.len(), 1);
assert!(ext.has_element("Custom"));
assert!(!ext.has_element("NonExistent"));
}
#[test]
fn test_extension_custom_property() {
let mut ext = Extension::new("urn:ext:test", "Test");
let prop = PropertyDefinition::new("custom", "string");
ext.add_custom_property("customProp".to_string(), prop);
assert_eq!(ext.custom_properties.len(), 1);
assert!(ext.custom_properties.contains_key("customProp"));
}
}