scim_server/schema/
registry.rs1use super::{
7 embedded,
8 types::{AttributeDefinition, AttributeType, Schema},
9};
10
11use chrono::{DateTime, FixedOffset};
12use serde_json::Value;
13use std::collections::HashMap;
14use std::fs;
15use std::path::Path;
16
17#[derive(Debug, Clone)]
22pub struct SchemaRegistry {
23 core_user_schema: Schema,
24 core_group_schema: Schema,
25 schemas: HashMap<String, Schema>,
26}
27
28impl SchemaRegistry {
29 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
34 Self::with_embedded_schemas()
35 }
36
37 pub fn with_embedded_schemas() -> Result<Self, Box<dyn std::error::Error>> {
43 let core_user_schema = Self::load_schema_from_str(embedded::core_user_schema())?;
44 let core_group_schema = Self::load_schema_from_str(embedded::core_group_schema())?;
45
46 let mut schemas = HashMap::new();
47 schemas.insert(core_user_schema.id.clone(), core_user_schema.clone());
48 schemas.insert(core_group_schema.id.clone(), core_group_schema.clone());
49
50 Ok(Self {
51 core_user_schema,
52 core_group_schema,
53 schemas,
54 })
55 }
56
57 pub fn from_schema_dir<P: AsRef<Path>>(
59 schema_dir: P,
60 ) -> Result<Self, Box<dyn std::error::Error>> {
61 let user_schema_path = schema_dir.as_ref().join("User.json");
62 let core_user_schema = Self::load_schema_from_file(&user_schema_path)?;
63
64 let group_schema_path = schema_dir.as_ref().join("Group.json");
65 let core_group_schema = Self::load_schema_from_file(&group_schema_path)?;
66
67 let mut schemas = HashMap::new();
68 schemas.insert(core_user_schema.id.clone(), core_user_schema.clone());
69 schemas.insert(core_group_schema.id.clone(), core_group_schema.clone());
70
71 Ok(Self {
72 core_user_schema,
73 core_group_schema,
74 schemas,
75 })
76 }
77
78 fn load_schema_from_file<P: AsRef<Path>>(
80 path: P,
81 ) -> Result<Schema, Box<dyn std::error::Error>> {
82 let content = fs::read_to_string(&path)?;
83 Self::load_schema_from_str(&content)
84 }
85
86 fn load_schema_from_str(content: &str) -> Result<Schema, Box<dyn std::error::Error>> {
88 let mut schema: Schema = serde_json::from_str(content)?;
89
90 Self::convert_json_schema(&mut schema);
92
93 Ok(schema)
94 }
95
96 fn convert_json_schema(schema: &mut Schema) {
98 for attr in &mut schema.attributes {
99 Self::convert_attribute_definition(attr);
100 }
101 }
102
103 fn convert_attribute_definition(attr: &mut AttributeDefinition) {
105 for sub_attr in &mut attr.sub_attributes {
110 Self::convert_attribute_definition(sub_attr);
111 }
112 }
113
114 pub fn get_schemas(&self) -> Vec<&Schema> {
116 self.schemas.values().collect()
117 }
118
119 pub fn get_schema(&self, id: &str) -> Option<&Schema> {
121 self.schemas.get(id)
122 }
123
124 pub fn get_user_schema(&self) -> &Schema {
126 &self.core_user_schema
127 }
128
129 pub fn get_group_schema(&self) -> &Schema {
131 &self.core_group_schema
132 }
133
134 pub fn add_schema(&mut self, schema: Schema) -> Result<(), Box<dyn std::error::Error>> {
136 self.schemas.insert(schema.id.clone(), schema);
137 Ok(())
138 }
139
140 pub fn get_schema_by_id(&self, schema_id: &str) -> Option<&Schema> {
142 self.schemas.get(schema_id)
143 }
144
145 pub(super) fn is_valid_datetime_format(&self, value: &str) -> bool {
156 if value.is_empty() {
157 return false;
158 }
159
160 DateTime::<FixedOffset>::parse_from_rfc3339(value).is_ok()
162 }
163
164 pub(super) fn is_valid_base64(&self, value: &str) -> bool {
170 if value.is_empty() {
171 return false;
172 }
173
174 let base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
177 value.chars().all(|c| base64_chars.contains(c))
178 }
179
180 pub(super) fn is_valid_uri_format(&self, value: &str) -> bool {
185 if value.is_empty() {
186 return false;
187 }
188
189 value.contains("://") || value.starts_with("urn:")
192 }
193
194 pub(super) fn get_value_type(value: &Value) -> &'static str {
196 match value {
197 Value::Null => "null",
198 Value::Bool(_) => "boolean",
199 Value::Number(n) if n.is_i64() => "integer",
200 Value::Number(_) => "decimal",
201 Value::String(_) => "string",
202 Value::Array(_) => "array",
203 Value::Object(_) => "object",
204 }
205 }
206
207 pub(super) fn get_complex_attribute_definition(
209 &self,
210 attr_name: &str,
211 ) -> Option<&AttributeDefinition> {
212 self.core_user_schema
214 .attributes
215 .iter()
216 .find(|attr| attr.name == attr_name && matches!(attr.data_type, AttributeType::Complex))
217 }
218}
219
220impl Default for SchemaRegistry {
221 fn default() -> Self {
222 Self::new().expect("Failed to load default schemas")
223 }
224}