scim_server/schema/
registry.rs1use super::{embedded, types::{AttributeDefinition, AttributeType, Schema}};
7
8use chrono::{DateTime, FixedOffset};
9use serde_json::Value;
10use std::collections::HashMap;
11use std::fs;
12use std::path::Path;
13
14#[derive(Debug, Clone)]
19pub struct SchemaRegistry {
20 core_user_schema: Schema,
21 core_group_schema: Schema,
22 schemas: HashMap<String, Schema>,
23}
24
25impl SchemaRegistry {
26 pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
31 Self::with_embedded_schemas()
32 }
33
34 pub fn with_embedded_schemas() -> Result<Self, Box<dyn std::error::Error>> {
40 let core_user_schema = Self::load_schema_from_str(embedded::core_user_schema())?;
41 let core_group_schema = Self::load_schema_from_str(embedded::core_group_schema())?;
42
43 let mut schemas = HashMap::new();
44 schemas.insert(core_user_schema.id.clone(), core_user_schema.clone());
45 schemas.insert(core_group_schema.id.clone(), core_group_schema.clone());
46
47 Ok(Self {
48 core_user_schema,
49 core_group_schema,
50 schemas,
51 })
52 }
53
54 pub fn from_schema_dir<P: AsRef<Path>>(
56 schema_dir: P,
57 ) -> Result<Self, Box<dyn std::error::Error>> {
58 let user_schema_path = schema_dir.as_ref().join("User.json");
59 let core_user_schema = Self::load_schema_from_file(&user_schema_path)?;
60
61 let group_schema_path = schema_dir.as_ref().join("Group.json");
62 let core_group_schema = Self::load_schema_from_file(&group_schema_path)?;
63
64 let mut schemas = HashMap::new();
65 schemas.insert(core_user_schema.id.clone(), core_user_schema.clone());
66 schemas.insert(core_group_schema.id.clone(), core_group_schema.clone());
67
68 Ok(Self {
69 core_user_schema,
70 core_group_schema,
71 schemas,
72 })
73 }
74
75 fn load_schema_from_file<P: AsRef<Path>>(
77 path: P,
78 ) -> Result<Schema, Box<dyn std::error::Error>> {
79 let content = fs::read_to_string(&path)?;
80 Self::load_schema_from_str(&content)
81 }
82
83 fn load_schema_from_str(content: &str) -> Result<Schema, Box<dyn std::error::Error>> {
85 let mut schema: Schema = serde_json::from_str(content)?;
86
87 Self::convert_json_schema(&mut schema);
89
90 Ok(schema)
91 }
92
93 fn convert_json_schema(schema: &mut Schema) {
95 for attr in &mut schema.attributes {
96 Self::convert_attribute_definition(attr);
97 }
98 }
99
100 fn convert_attribute_definition(attr: &mut AttributeDefinition) {
102 for sub_attr in &mut attr.sub_attributes {
107 Self::convert_attribute_definition(sub_attr);
108 }
109 }
110
111 pub fn get_schemas(&self) -> Vec<&Schema> {
113 self.schemas.values().collect()
114 }
115
116 pub fn get_schema(&self, id: &str) -> Option<&Schema> {
118 self.schemas.get(id)
119 }
120
121 pub fn get_user_schema(&self) -> &Schema {
123 &self.core_user_schema
124 }
125
126 pub fn get_group_schema(&self) -> &Schema {
128 &self.core_group_schema
129 }
130
131 pub fn add_schema(&mut self, schema: Schema) -> Result<(), Box<dyn std::error::Error>> {
133 self.schemas.insert(schema.id.clone(), schema);
134 Ok(())
135 }
136
137 pub fn get_schema_by_id(&self, schema_id: &str) -> Option<&Schema> {
139 self.schemas.get(schema_id)
140 }
141
142 pub(super) fn is_valid_datetime_format(&self, value: &str) -> bool {
153 if value.is_empty() {
154 return false;
155 }
156
157 DateTime::<FixedOffset>::parse_from_rfc3339(value).is_ok()
159 }
160
161 pub(super) fn is_valid_base64(&self, value: &str) -> bool {
167 if value.is_empty() {
168 return false;
169 }
170
171 let base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
174 value.chars().all(|c| base64_chars.contains(c))
175 }
176
177 pub(super) fn is_valid_uri_format(&self, value: &str) -> bool {
182 if value.is_empty() {
183 return false;
184 }
185
186 value.contains("://") || value.starts_with("urn:")
189 }
190
191 pub(super) fn get_value_type(value: &Value) -> &'static str {
193 match value {
194 Value::Null => "null",
195 Value::Bool(_) => "boolean",
196 Value::Number(n) if n.is_i64() => "integer",
197 Value::Number(_) => "decimal",
198 Value::String(_) => "string",
199 Value::Array(_) => "array",
200 Value::Object(_) => "object",
201 }
202 }
203
204 pub(super) fn get_complex_attribute_definition(
206 &self,
207 attr_name: &str,
208 ) -> Option<&AttributeDefinition> {
209 self.core_user_schema
211 .attributes
212 .iter()
213 .find(|attr| attr.name == attr_name && matches!(attr.data_type, AttributeType::Complex))
214 }
215}
216
217impl Default for SchemaRegistry {
218 fn default() -> Self {
219 Self::new().expect("Failed to load default schemas")
220 }
221}