1use crate::error::{ValidationError, ValidationResult};
8use crate::resource::value_objects::{
9 Address, EmailAddress, ExternalId, GroupMembers, Meta, MultiValuedAddresses, MultiValuedEmails,
10 MultiValuedPhoneNumbers, Name, PhoneNumber, ResourceId, SchemaUri, UserName,
11};
12
13use serde_json::{Map, Value};
14
15#[derive(Debug, Clone)]
21pub struct Resource {
22 pub resource_type: String,
24 pub id: Option<ResourceId>,
26 pub schemas: Vec<SchemaUri>,
28 pub external_id: Option<ExternalId>,
30 pub user_name: Option<UserName>,
32 pub meta: Option<Meta>,
34 pub name: Option<Name>,
36 pub addresses: Option<MultiValuedAddresses>,
38 pub phone_numbers: Option<MultiValuedPhoneNumbers>,
40 pub emails: Option<MultiValuedEmails>,
42 pub members: Option<GroupMembers>,
44 pub attributes: Map<String, Value>,
46}
47
48impl Resource {
49 pub fn from_json(resource_type: String, data: Value) -> ValidationResult<Self> {
76 let obj = data
77 .as_object()
78 .ok_or_else(|| ValidationError::custom("Resource must be a JSON object"))?;
79
80 let id = Self::extract_resource_id(obj)?;
82 let schemas = Self::extract_schemas(obj, &resource_type)?;
83 let external_id = Self::extract_external_id(obj)?;
84 let user_name = Self::extract_user_name(obj)?;
85 let meta = Self::extract_meta(&data)?;
86 let name = Self::extract_name(obj)?;
87 let addresses = Self::extract_addresses(obj)?;
88 let phone_numbers = Self::extract_phone_numbers(obj)?;
89 let emails = Self::extract_emails(obj)?;
90 let members = Self::extract_members(obj)?;
91
92 let mut attributes = obj.clone();
94 attributes.remove("id");
95 attributes.remove("schemas");
96 attributes.remove("externalId");
97 attributes.remove("userName");
98 attributes.remove("meta");
99 attributes.remove("name");
100 attributes.remove("addresses");
101 attributes.remove("phoneNumbers");
102 attributes.remove("emails");
103 attributes.remove("members");
104
105 Ok(Self {
106 resource_type,
107 id,
108 schemas,
109 external_id,
110 user_name,
111 meta,
112 name,
113 addresses,
114 phone_numbers,
115 emails,
116 members,
117 attributes,
118 })
119 }
120
121 pub fn new(
126 resource_type: String,
127 id: Option<ResourceId>,
128 schemas: Vec<SchemaUri>,
129 external_id: Option<ExternalId>,
130 user_name: Option<UserName>,
131 attributes: Map<String, Value>,
132 ) -> Self {
133 Self {
134 resource_type,
135 id,
136 schemas,
137 external_id,
138 user_name,
139 meta: None,
140 name: None,
141 addresses: None,
142 phone_numbers: None,
143 emails: None,
144 members: None,
145 attributes,
146 }
147 }
148
149 pub fn new_with_meta(
153 resource_type: String,
154 id: Option<ResourceId>,
155 schemas: Vec<SchemaUri>,
156 external_id: Option<ExternalId>,
157 user_name: Option<UserName>,
158 meta: Option<Meta>,
159 attributes: Map<String, Value>,
160 ) -> Self {
161 Self {
162 resource_type,
163 id,
164 schemas,
165 external_id,
166 user_name,
167 meta,
168 name: None,
169 addresses: None,
170 phone_numbers: None,
171 emails: None,
172 members: None,
173 attributes,
174 }
175 }
176
177 fn extract_resource_id(obj: &Map<String, Value>) -> ValidationResult<Option<ResourceId>> {
179 if let Some(id_value) = obj.get("id") {
180 if let Some(id_str) = id_value.as_str() {
181 return Ok(Some(ResourceId::new(id_str.to_string())?));
182 } else {
183 return Err(ValidationError::InvalidIdFormat {
184 id: id_value.to_string(),
185 });
186 }
187 }
188 Ok(None)
189 }
190
191 fn extract_schemas(
193 obj: &Map<String, Value>,
194 resource_type: &str,
195 ) -> ValidationResult<Vec<SchemaUri>> {
196 if let Some(schemas_value) = obj.get("schemas") {
197 if let Some(schemas_array) = schemas_value.as_array() {
198 if schemas_array.is_empty() {
199 return Err(ValidationError::EmptySchemas);
200 }
201
202 let mut schemas = Vec::new();
203 for schema_value in schemas_array {
204 if let Some(uri_str) = schema_value.as_str() {
205 schemas.push(SchemaUri::new(uri_str.to_string())?);
206 }
207 }
208 if !schemas.is_empty() {
209 return Ok(schemas);
210 }
211 }
212 }
213
214 let default_uri = match resource_type {
216 "User" => "urn:ietf:params:scim:schemas:core:2.0:User",
217 "Group" => "urn:ietf:params:scim:schemas:core:2.0:Group",
218 _ => return Err(ValidationError::custom("Unknown resource type")),
219 };
220
221 Ok(vec![SchemaUri::new(default_uri.to_string())?])
222 }
223
224 fn extract_external_id(obj: &Map<String, Value>) -> ValidationResult<Option<ExternalId>> {
226 if let Some(ext_id_value) = obj.get("externalId") {
227 if let Some(ext_id_str) = ext_id_value.as_str() {
228 return Ok(Some(ExternalId::new(ext_id_str.to_string())?));
229 } else {
230 return Err(ValidationError::InvalidExternalId);
231 }
232 }
233 Ok(None)
234 }
235
236 fn extract_user_name(obj: &Map<String, Value>) -> ValidationResult<Option<UserName>> {
238 if let Some(username_value) = obj.get("userName") {
239 if let Some(username_str) = username_value.as_str() {
240 return Ok(Some(UserName::new(username_str.to_string())?));
241 } else {
242 return Err(ValidationError::custom(
243 "userName must be a string".to_string(),
244 ));
245 }
246 }
247 Ok(None)
248 }
249
250 fn extract_name(obj: &Map<String, Value>) -> ValidationResult<Option<Name>> {
252 if let Some(name_value) = obj.get("name") {
253 if let Some(_) = name_value.as_object() {
254 let name: Name = serde_json::from_value(name_value.clone())
256 .map_err(|e| ValidationError::custom(format!("Invalid name format: {}", e)))?;
257 return Ok(Some(name));
258 } else {
259 return Err(ValidationError::custom(
260 "name must be an object".to_string(),
261 ));
262 }
263 }
264 Ok(None)
265 }
266
267 fn extract_addresses(
269 obj: &Map<String, Value>,
270 ) -> ValidationResult<Option<MultiValuedAddresses>> {
271 if let Some(addresses_value) = obj.get("addresses") {
272 if let Some(_) = addresses_value.as_array() {
273 let addresses: Vec<Address> = serde_json::from_value(addresses_value.clone())
275 .map_err(|e| {
276 ValidationError::custom(format!("Invalid addresses format: {}", e))
277 })?;
278 if !addresses.is_empty() {
279 let multi_addresses = MultiValuedAddresses::new(addresses)?;
280 return Ok(Some(multi_addresses));
281 }
282 } else {
283 return Err(ValidationError::custom(
284 "addresses must be an array".to_string(),
285 ));
286 }
287 }
288 Ok(None)
289 }
290
291 fn extract_phone_numbers(
293 obj: &Map<String, Value>,
294 ) -> ValidationResult<Option<MultiValuedPhoneNumbers>> {
295 if let Some(phones_value) = obj.get("phoneNumbers") {
296 if let Some(_) = phones_value.as_array() {
297 let phone_numbers: Vec<PhoneNumber> = serde_json::from_value(phones_value.clone())
299 .map_err(|e| {
300 ValidationError::custom(format!("Invalid phoneNumbers format: {}", e))
301 })?;
302 if !phone_numbers.is_empty() {
303 let multi_phones = MultiValuedPhoneNumbers::new(phone_numbers)?;
304 return Ok(Some(multi_phones));
305 }
306 } else {
307 return Err(ValidationError::custom(
308 "phoneNumbers must be an array".to_string(),
309 ));
310 }
311 }
312 Ok(None)
313 }
314
315 fn extract_emails(obj: &Map<String, Value>) -> ValidationResult<Option<MultiValuedEmails>> {
317 if let Some(emails_value) = obj.get("emails") {
318 if let Some(_) = emails_value.as_array() {
319 let emails: Vec<EmailAddress> = serde_json::from_value(emails_value.clone())
321 .map_err(|e| {
322 ValidationError::custom(format!("Invalid emails format: {}", e))
323 })?;
324 if !emails.is_empty() {
325 let multi_emails = MultiValuedEmails::new(emails)?;
326 return Ok(Some(multi_emails));
327 }
328 } else {
329 return Err(ValidationError::custom(
330 "emails must be an array".to_string(),
331 ));
332 }
333 }
334 Ok(None)
335 }
336
337 fn extract_members(obj: &Map<String, Value>) -> ValidationResult<Option<GroupMembers>> {
339 if let Some(members_value) = obj.get("members") {
340 if let Some(_) = members_value.as_array() {
341 let members_data: Vec<serde_json::Value> =
343 serde_json::from_value(members_value.clone()).map_err(|e| {
344 ValidationError::custom(format!("Invalid members format: {}", e))
345 })?;
346
347 let mut members = Vec::new();
348 for member_data in members_data {
349 if let Some(obj) = member_data.as_object() {
350 if let Some(value_str) = obj.get("value").and_then(|v| v.as_str()) {
351 let resource_id = ResourceId::new(value_str.to_string())?;
352 let display = obj
353 .get("display")
354 .and_then(|v| v.as_str())
355 .map(|s| s.to_string());
356 let member_type = obj
357 .get("type")
358 .and_then(|v| v.as_str())
359 .map(|s| s.to_string());
360
361 let member = crate::resource::value_objects::GroupMember::new(
362 resource_id,
363 display,
364 member_type,
365 )?;
366 members.push(member);
367 }
368 }
369 }
370
371 if !members.is_empty() {
372 let group_members = GroupMembers::new(members)?;
373 return Ok(Some(group_members));
374 }
375 } else {
376 return Err(ValidationError::custom(
377 "members must be an array".to_string(),
378 ));
379 }
380 }
381 Ok(None)
382 }
383
384 pub fn get_id(&self) -> Option<&str> {
386 self.id.as_ref().map(|id| id.as_str())
387 }
388
389 pub fn set_id(&mut self, id: &str) -> ValidationResult<()> {
391 self.id = Some(ResourceId::new(id.to_string())?);
392 Ok(())
393 }
394
395 pub fn get(&self, key: &str) -> Option<&Value> {
397 self.attributes.get(key)
398 }
399
400 pub fn get_username(&self) -> Option<&str> {
402 self.user_name.as_ref().map(|name| name.as_str())
403 }
404
405 pub fn get_name(&self) -> Option<&Name> {
407 self.name.as_ref()
408 }
409
410 pub fn get_addresses(&self) -> Option<&MultiValuedAddresses> {
412 self.addresses.as_ref()
413 }
414
415 pub fn get_phone_numbers(&self) -> Option<&MultiValuedPhoneNumbers> {
417 self.phone_numbers.as_ref()
418 }
419
420 pub fn get_emails(&self) -> Option<&MultiValuedEmails> {
422 self.emails.as_ref()
423 }
424
425 pub fn get_members(&self) -> Option<&GroupMembers> {
427 self.members.as_ref()
428 }
429
430 pub fn set_name(&mut self, name: Name) {
432 self.name = Some(name);
433 }
434
435 pub fn set_addresses(&mut self, addresses: MultiValuedAddresses) {
437 self.addresses = Some(addresses);
438 }
439
440 pub fn set_phone_numbers(&mut self, phone_numbers: MultiValuedPhoneNumbers) {
442 self.phone_numbers = Some(phone_numbers);
443 }
444
445 pub fn set_emails(&mut self, emails: MultiValuedEmails) {
447 self.emails = Some(emails);
448 }
449
450 pub fn set_members(&mut self, members: GroupMembers) {
452 self.members = Some(members);
453 }
454
455 pub fn add_address(&mut self, address: Address) -> ValidationResult<()> {
457 match &self.addresses {
458 Some(existing) => {
459 let new_addresses = existing.clone().with_value(address);
460 self.addresses = Some(new_addresses);
461 }
462 None => {
463 let new_addresses = MultiValuedAddresses::single(address);
464 self.addresses = Some(new_addresses);
465 }
466 }
467 Ok(())
468 }
469
470 pub fn add_phone_number(&mut self, phone_number: PhoneNumber) -> ValidationResult<()> {
472 match &self.phone_numbers {
473 Some(existing) => {
474 let new_phones = existing.clone().with_value(phone_number);
475 self.phone_numbers = Some(new_phones);
476 }
477 None => {
478 let new_phones = MultiValuedPhoneNumbers::single(phone_number);
479 self.phone_numbers = Some(new_phones);
480 }
481 }
482 Ok(())
483 }
484
485 pub fn add_email(&mut self, email: EmailAddress) -> ValidationResult<()> {
487 match &self.emails {
488 Some(existing) => {
489 let new_emails = existing.clone().with_value(email);
490 self.emails = Some(new_emails);
491 }
492 None => {
493 let new_emails = MultiValuedEmails::single(email);
494 self.emails = Some(new_emails);
495 }
496 }
497 Ok(())
498 }
499
500 pub fn get_attribute(&self, attribute_name: &str) -> Option<&Value> {
505 self.attributes.get(attribute_name)
506 }
507
508 pub fn set_attribute(&mut self, attribute_name: String, value: Value) {
514 self.attributes.insert(attribute_name, value);
515 }
516
517 pub fn get_schemas(&self) -> Vec<String> {
519 self.schemas
520 .iter()
521 .map(|s| s.as_str().to_string())
522 .collect()
523 }
524
525 pub fn get_schema_uris(&self) -> &[SchemaUri] {
527 &self.schemas
528 }
529
530 pub fn add_metadata(&mut self, base_url: &str, created: &str, last_modified: &str) {
538 let created_dt = chrono::DateTime::parse_from_rfc3339(created)
540 .map(|dt| dt.with_timezone(&chrono::Utc))
541 .unwrap_or_else(|_| chrono::Utc::now());
542
543 let last_modified_dt = chrono::DateTime::parse_from_rfc3339(last_modified)
544 .map(|dt| dt.with_timezone(&chrono::Utc))
545 .unwrap_or_else(|_| chrono::Utc::now());
546
547 let location = if let Some(id) = &self.id {
548 Some(Meta::generate_location(
549 base_url,
550 &self.resource_type,
551 id.as_str(),
552 ))
553 } else {
554 None
555 };
556
557 let version = if let Some(id) = &self.id {
558 Some(Meta::generate_version(id.as_str(), last_modified_dt))
559 } else {
560 None
561 };
562
563 if let Ok(meta) = Meta::new(
565 self.resource_type.clone(),
566 created_dt,
567 last_modified_dt,
568 location,
569 version,
570 ) {
571 self.set_meta(meta);
572 }
573 }
574
575 pub fn is_active(&self) -> bool {
579 self.attributes
580 .get("active")
581 .and_then(|v| v.as_bool())
582 .unwrap_or(true)
583 }
584
585 pub fn to_json(&self) -> ValidationResult<Value> {
590 let mut result = self.attributes.clone();
591
592 if let Some(ref id) = self.id {
594 result.insert("id".to_string(), Value::String(id.as_str().to_string()));
595 }
596
597 let schemas: Vec<Value> = self
598 .schemas
599 .iter()
600 .map(|s| Value::String(s.as_str().to_string()))
601 .collect();
602 result.insert("schemas".to_string(), Value::Array(schemas));
603
604 if let Some(ref external_id) = self.external_id {
605 result.insert(
606 "externalId".to_string(),
607 Value::String(external_id.as_str().to_string()),
608 );
609 }
610
611 if let Some(ref user_name) = self.user_name {
612 result.insert(
613 "userName".to_string(),
614 Value::String(user_name.as_str().to_string()),
615 );
616 }
617
618 if let Some(ref meta) = self.meta {
620 if let Ok(meta_json) = serde_json::to_value(meta) {
621 result.insert("meta".to_string(), meta_json);
622 }
623 }
624
625 if let Some(ref name) = self.name {
627 if let Ok(name_json) = serde_json::to_value(name) {
628 result.insert("name".to_string(), name_json);
629 }
630 }
631
632 if let Some(ref addresses) = self.addresses {
634 let addresses_json = serde_json::to_value(addresses.values())
635 .map_err(|e| ValidationError::custom(format!("Serialization error: {}", e)))?;
636 result.insert("addresses".to_string(), addresses_json);
637 }
638
639 if let Some(ref phone_numbers) = self.phone_numbers {
640 let phones_json = serde_json::to_value(phone_numbers.values())
641 .map_err(|e| ValidationError::custom(format!("Serialization error: {}", e)))?;
642 result.insert("phoneNumbers".to_string(), phones_json);
643 }
644
645 if let Some(ref emails) = self.emails {
646 let emails_json = serde_json::to_value(emails.values())
647 .map_err(|e| ValidationError::custom(format!("Serialization error: {}", e)))?;
648 result.insert("emails".to_string(), emails_json);
649 }
650
651 if let Some(ref members) = self.members {
652 let members_json = serde_json::to_value(members.values())
653 .map_err(|e| ValidationError::custom(format!("Serialization error: {}", e)))?;
654 result.insert("members".to_string(), members_json);
655 }
656
657 Ok(Value::Object(result))
658 }
659
660 pub fn get_external_id(&self) -> Option<&str> {
662 self.external_id.as_ref().map(|id| id.as_str())
663 }
664
665 fn extract_meta(data: &Value) -> ValidationResult<Option<Meta>> {
667 if let Some(meta_value) = data.get("meta") {
668 if let Some(meta_obj) = meta_value.as_object() {
669 let resource_type = if let Some(rt_value) = meta_obj.get("resourceType") {
671 if let Some(rt_str) = rt_value.as_str() {
672 Some(rt_str.to_string())
673 } else {
674 return Err(ValidationError::InvalidMetaStructure);
676 }
677 } else {
678 None
679 };
680
681 let created = meta_obj.get("created").and_then(|v| v.as_str());
682 let last_modified = meta_obj.get("lastModified").and_then(|v| v.as_str());
683
684 if meta_obj.get("created").is_some() && created.is_none() {
686 return Err(ValidationError::InvalidCreatedDateTime);
687 }
688 if meta_obj.get("lastModified").is_some() && last_modified.is_none() {
689 return Err(ValidationError::InvalidModifiedDateTime);
690 }
691
692 if let Some(location_value) = meta_obj.get("location") {
694 if !location_value.is_string() {
695 return Err(ValidationError::InvalidLocationUri);
696 }
697 }
698
699 if let Some(version_value) = meta_obj.get("version") {
701 if !version_value.is_string() {
702 return Err(ValidationError::InvalidVersionFormat);
703 }
704 }
705
706 if let (Some(resource_type), Some(created), Some(last_modified)) =
708 (resource_type, created, last_modified)
709 {
710 let location = meta_obj
711 .get("location")
712 .and_then(|v| v.as_str())
713 .map(|s| s.to_string());
714
715 let version = meta_obj
716 .get("version")
717 .and_then(|v| v.as_str())
718 .map(|s| s.to_string());
719
720 let created_dt = chrono::DateTime::parse_from_rfc3339(created)
722 .map_err(|_| ValidationError::InvalidCreatedDateTime)?
723 .with_timezone(&chrono::Utc);
724
725 let last_modified_dt = chrono::DateTime::parse_from_rfc3339(last_modified)
726 .map_err(|_| ValidationError::InvalidModifiedDateTime)?
727 .with_timezone(&chrono::Utc);
728
729 let meta = Meta::new(
730 resource_type,
731 created_dt,
732 last_modified_dt,
733 location,
734 version,
735 )?;
736 Ok(Some(meta))
737 } else {
738 Ok(None)
740 }
741 } else {
742 Err(ValidationError::InvalidMetaStructure)
743 }
744 } else {
745 Ok(None)
746 }
747 }
748
749 pub fn get_meta(&self) -> Option<&Meta> {
751 self.meta.as_ref()
752 }
753
754 pub fn set_meta(&mut self, meta: Meta) {
756 let meta_json = serde_json::to_value(&meta).unwrap_or(Value::Null);
758 self.set_attribute("meta".to_string(), meta_json);
759 self.meta = Some(meta);
760 }
761
762 pub fn create_meta(&mut self, base_url: &str) -> ValidationResult<()> {
764 let meta = Meta::new_for_creation(self.resource_type.clone())?;
765 let meta_with_location = if let Some(id) = &self.id {
766 let location = Meta::generate_location(base_url, &self.resource_type, id.as_str());
767 meta.with_location(location)?
768 } else {
769 meta
770 };
771 self.set_meta(meta_with_location);
772 Ok(())
773 }
774
775 pub fn update_meta(&mut self) {
777 if let Some(meta) = &self.meta {
778 let updated_meta = meta.with_updated_timestamp();
779 self.set_meta(updated_meta);
780 }
781 }
782}