use super::{
embedded,
types::{AttributeDefinition, AttributeType, Schema},
};
use chrono::{DateTime, FixedOffset};
use serde_json::Value;
use std::collections::HashMap;
use std::fs;
use std::path::Path;
#[derive(Debug, Clone)]
pub struct SchemaRegistry {
core_user_schema: Schema,
core_group_schema: Schema,
schemas: HashMap<String, Schema>,
}
impl SchemaRegistry {
pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
Self::with_embedded_schemas()
}
pub fn with_embedded_schemas() -> Result<Self, Box<dyn std::error::Error>> {
let core_user_schema = Self::load_schema_from_str(embedded::core_user_schema())?;
let core_group_schema = Self::load_schema_from_str(embedded::core_group_schema())?;
let mut schemas = HashMap::new();
schemas.insert(core_user_schema.id.clone(), core_user_schema.clone());
schemas.insert(core_group_schema.id.clone(), core_group_schema.clone());
Ok(Self {
core_user_schema,
core_group_schema,
schemas,
})
}
pub fn from_schema_dir<P: AsRef<Path>>(
schema_dir: P,
) -> Result<Self, Box<dyn std::error::Error>> {
let user_schema_path = schema_dir.as_ref().join("User.json");
let core_user_schema = Self::load_schema_from_file(&user_schema_path)?;
let group_schema_path = schema_dir.as_ref().join("Group.json");
let core_group_schema = Self::load_schema_from_file(&group_schema_path)?;
let mut schemas = HashMap::new();
schemas.insert(core_user_schema.id.clone(), core_user_schema.clone());
schemas.insert(core_group_schema.id.clone(), core_group_schema.clone());
Ok(Self {
core_user_schema,
core_group_schema,
schemas,
})
}
fn load_schema_from_file<P: AsRef<Path>>(
path: P,
) -> Result<Schema, Box<dyn std::error::Error>> {
let content = fs::read_to_string(&path)?;
Self::load_schema_from_str(&content)
}
fn load_schema_from_str(content: &str) -> Result<Schema, Box<dyn std::error::Error>> {
let mut schema: Schema = serde_json::from_str(content)?;
Self::convert_json_schema(&mut schema);
Ok(schema)
}
fn convert_json_schema(schema: &mut Schema) {
for attr in &mut schema.attributes {
Self::convert_attribute_definition(attr);
}
}
fn convert_attribute_definition(attr: &mut AttributeDefinition) {
for sub_attr in &mut attr.sub_attributes {
Self::convert_attribute_definition(sub_attr);
}
}
pub fn get_schemas(&self) -> Vec<&Schema> {
self.schemas.values().collect()
}
pub fn get_schema(&self, id: &str) -> Option<&Schema> {
self.schemas.get(id)
}
pub fn get_user_schema(&self) -> &Schema {
&self.core_user_schema
}
pub fn get_group_schema(&self) -> &Schema {
&self.core_group_schema
}
pub fn add_schema(&mut self, schema: Schema) -> Result<(), Box<dyn std::error::Error>> {
self.schemas.insert(schema.id.clone(), schema);
Ok(())
}
pub fn get_schema_by_id(&self, schema_id: &str) -> Option<&Schema> {
self.schemas.get(schema_id)
}
pub(super) fn is_valid_datetime_format(&self, value: &str) -> bool {
if value.is_empty() {
return false;
}
DateTime::<FixedOffset>::parse_from_rfc3339(value).is_ok()
}
pub(super) fn is_valid_base64(&self, value: &str) -> bool {
if value.is_empty() {
return false;
}
let base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
value.chars().all(|c| base64_chars.contains(c))
}
pub(super) fn is_valid_uri_format(&self, value: &str) -> bool {
if value.is_empty() {
return false;
}
value.contains("://") || value.starts_with("urn:")
}
pub(super) fn get_value_type(value: &Value) -> &'static str {
match value {
Value::Null => "null",
Value::Bool(_) => "boolean",
Value::Number(n) if n.is_i64() => "integer",
Value::Number(_) => "decimal",
Value::String(_) => "string",
Value::Array(_) => "array",
Value::Object(_) => "object",
}
}
pub(super) fn get_complex_attribute_definition(
&self,
attr_name: &str,
) -> Option<&AttributeDefinition> {
self.core_user_schema
.attributes
.iter()
.find(|attr| attr.name == attr_name && matches!(attr.data_type, AttributeType::Complex))
}
}
impl Default for SchemaRegistry {
fn default() -> Self {
Self::new().expect("Failed to load default schemas")
}
}