1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::HashMap;
4use thiserror::Error;
5
6use crate::entities::GtsEntity;
7use crate::gts::{GtsID, GtsWildcard, GTS_URI_PREFIX};
8use crate::schema_cast::GtsEntityCastResult;
9
10#[derive(Debug, Error)]
11pub enum StoreError {
12 #[error("JSON object with GTS ID '{0}' not found in store")]
13 ObjectNotFound(String),
14 #[error("JSON schema with GTS ID '{0}' not found in store")]
15 SchemaNotFound(String),
16 #[error("JSON entity with GTS ID '{0}' not found in store")]
17 EntityNotFound(String),
18 #[error("Can't determine JSON schema ID for instance with GTS ID '{0}'")]
19 SchemaForInstanceNotFound(String),
20 #[error(
21 "Cannot cast from schema ID '{0}'. The from_id must be an instance (not ending with '~')"
22 )]
23 CastFromSchemaNotAllowed(String),
24 #[error("Entity must have a valid gts_id")]
25 InvalidEntity,
26 #[error("Schema type_id must end with '~'")]
27 InvalidSchemaId,
28 #[error("{0}")]
29 ValidationError(String),
30 #[error("Invalid $ref: {0}")]
31 InvalidRef(String),
32}
33
34pub trait GtsReader: Send {
35 fn iter(&mut self) -> Box<dyn Iterator<Item = GtsEntity> + '_>;
36 fn read_by_id(&self, entity_id: &str) -> Option<GtsEntity>;
37 fn reset(&mut self);
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct GtsStoreQueryResult {
42 #[serde(skip_serializing_if = "String::is_empty")]
43 pub error: String,
44 pub count: usize,
45 pub limit: usize,
46 pub results: Vec<Value>,
47}
48
49pub struct GtsStore {
50 by_id: HashMap<String, GtsEntity>,
51 reader: Option<Box<dyn GtsReader>>,
52}
53
54impl GtsStore {
55 pub fn new(reader: Option<Box<dyn GtsReader>>) -> Self {
56 let mut store = GtsStore {
57 by_id: HashMap::new(),
58 reader,
59 };
60
61 if store.reader.is_some() {
62 store.populate_from_reader();
63 }
64
65 tracing::info!("Populated GtsStore with {} entities", store.by_id.len());
66 store
67 }
68
69 fn populate_from_reader(&mut self) {
70 if let Some(ref mut reader) = self.reader {
71 for entity in reader.iter() {
72 if let Some(id) = entity.effective_id() {
74 self.by_id.insert(id, entity);
75 }
76 }
77 }
78 }
79
80 pub fn register(&mut self, entity: GtsEntity) -> Result<(), StoreError> {
85 let id = entity.effective_id().ok_or(StoreError::InvalidEntity)?;
86 self.by_id.insert(id, entity);
87 Ok(())
88 }
89
90 pub fn register_schema(&mut self, type_id: &str, schema: &Value) -> Result<(), StoreError> {
95 if !type_id.ends_with('~') {
96 return Err(StoreError::InvalidSchemaId);
97 }
98
99 let gts_id = GtsID::new(type_id).map_err(|_| StoreError::InvalidSchemaId)?;
100 let entity = GtsEntity::new(
101 None,
102 None,
103 schema,
104 None,
105 Some(gts_id),
106 true,
107 String::new(),
108 None,
109 None,
110 );
111 self.by_id.insert(type_id.to_owned(), entity);
112 Ok(())
113 }
114
115 pub fn get(&mut self, entity_id: &str) -> Option<&GtsEntity> {
116 if self.by_id.contains_key(entity_id) {
118 return self.by_id.get(entity_id);
119 }
120
121 if let Some(ref reader) = self.reader {
123 if let Some(entity) = reader.read_by_id(entity_id) {
124 self.by_id.insert(entity_id.to_owned(), entity);
125 return self.by_id.get(entity_id);
126 }
127 }
128
129 None
130 }
131
132 pub fn get_schema_content(&mut self, type_id: &str) -> Result<Value, StoreError> {
137 if let Some(entity) = self.get(type_id) {
138 return Ok(entity.content.clone());
139 }
140 Err(StoreError::SchemaNotFound(type_id.to_owned()))
141 }
142
143 pub fn items(&self) -> impl Iterator<Item = (&String, &GtsEntity)> {
144 self.by_id.iter()
145 }
146
147 #[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
176 pub fn resolve_schema_refs(&self, schema: &Value) -> Value {
177 match schema {
179 Value::Object(map) => {
180 if let Some(Value::String(ref_uri)) = map.get("$ref") {
181 let canonical_ref = ref_uri.strip_prefix(GTS_URI_PREFIX).unwrap_or(ref_uri);
183
184 if let Some(entity) = self.by_id.get(canonical_ref) {
186 if entity.is_schema {
187 let mut resolved = self.resolve_schema_refs(&entity.content);
189
190 if let Value::Object(ref mut resolved_map) = resolved {
192 resolved_map.remove("$id");
193 resolved_map.remove("$schema");
194 }
195
196 if map.len() == 1 {
198 return resolved;
199 }
200
201 if let Value::Object(resolved_map) = resolved {
203 let mut merged = resolved_map;
204 for (k, v) in map {
205 if k != "$ref" {
206 merged.insert(k.clone(), self.resolve_schema_refs(v));
207 }
208 }
209 return Value::Object(merged);
210 }
211 }
212 }
213 let mut new_map = serde_json::Map::new();
216 for (k, v) in map {
217 if k != "$ref" {
218 new_map.insert(k.clone(), self.resolve_schema_refs(v));
219 }
220 }
221 if !new_map.is_empty() {
222 return Value::Object(new_map);
223 }
224 return schema.clone();
225 }
226
227 if let Some(Value::Array(all_of_array)) = map.get("allOf") {
229 let mut resolved_all_of = Vec::new();
230 let mut merged_properties = serde_json::Map::new();
231 let mut merged_required: Vec<String> = Vec::new();
232
233 for item in all_of_array {
234 let resolved_item = self.resolve_schema_refs(item);
235
236 match resolved_item {
237 Value::Object(ref item_map) => {
238 if item_map.contains_key("$ref") {
240 resolved_all_of.push(resolved_item);
242 } else {
243 if let Some(Value::Object(props_map)) =
244 item_map.get("properties")
245 {
246 for (k, v) in props_map {
247 merged_properties.insert(k.clone(), v.clone());
248 }
249 }
250 if let Some(Value::Array(req_array)) = item_map.get("required")
251 {
252 for v in req_array {
253 if let Value::String(s) = v {
254 if !merged_required.contains(s) {
255 merged_required.push(s.to_owned());
256 }
257 }
258 }
259 }
260 }
261 }
262 _ => resolved_all_of.push(resolved_item),
263 }
264 }
265
266 if !merged_properties.is_empty() {
268 let mut merged_schema = serde_json::Map::new();
269
270 for (k, v) in map {
272 if k != "allOf" {
273 merged_schema.insert(k.clone(), v.clone());
274 }
275 }
276
277 merged_schema
279 .insert("properties".to_owned(), Value::Object(merged_properties));
280 if !merged_required.is_empty() {
281 merged_schema.insert(
282 "required".to_owned(),
283 Value::Array(
284 merged_required.into_iter().map(Value::String).collect(),
285 ),
286 );
287 }
288
289 return Value::Object(merged_schema);
290 }
291 }
292
293 let mut new_map = serde_json::Map::new();
295 for (k, v) in map {
296 new_map.insert(k.clone(), self.resolve_schema_refs(v));
297 }
298 Value::Object(new_map)
299 }
300 Value::Array(arr) => {
301 Value::Array(arr.iter().map(|v| self.resolve_schema_refs(v)).collect())
302 }
303 _ => schema.clone(),
304 }
305 }
306
307 fn remove_x_gts_ref_fields(schema: &Value) -> Value {
308 match schema {
312 Value::Object(map) => {
313 let mut new_map = serde_json::Map::new();
314 for (key, value) in map {
315 if key == "x-gts-ref" {
316 continue; }
318 new_map.insert(key.clone(), Self::remove_x_gts_ref_fields(value));
319 }
320 Value::Object(new_map)
321 }
322 Value::Array(arr) => {
323 Value::Array(arr.iter().map(Self::remove_x_gts_ref_fields).collect())
324 }
325 _ => schema.clone(),
326 }
327 }
328
329 fn validate_schema_x_gts_refs(&mut self, gts_id: &str) -> Result<(), StoreError> {
330 if !gts_id.ends_with('~') {
331 return Err(StoreError::SchemaNotFound(format!(
332 "ID '{gts_id}' is not a schema (must end with '~')"
333 )));
334 }
335
336 let schema_entity = self
337 .get(gts_id)
338 .ok_or_else(|| StoreError::SchemaNotFound(gts_id.to_owned()))?;
339
340 if !schema_entity.is_schema {
341 return Err(StoreError::SchemaNotFound(format!(
342 "Entity '{gts_id}' is not a schema"
343 )));
344 }
345
346 tracing::info!("Validating schema x-gts-ref fields for {}", gts_id);
347
348 let validator = crate::x_gts_ref::XGtsRefValidator::new();
350 let x_gts_ref_errors = validator.validate_schema(&schema_entity.content, "", None);
351
352 if !x_gts_ref_errors.is_empty() {
353 let error_messages: Vec<String> = x_gts_ref_errors
354 .iter()
355 .map(|err| {
356 if err.field_path.is_empty() {
357 err.reason.clone()
358 } else {
359 format!("{}: {}", err.field_path, err.reason)
360 }
361 })
362 .collect();
363 let error_message =
364 format!("x-gts-ref validation failed: {}", error_messages.join("; "));
365 return Err(StoreError::ValidationError(error_message));
366 }
367
368 Ok(())
369 }
370
371 fn validate_schema_refs(schema: &Value, path: &str) -> Result<(), StoreError> {
381 match schema {
382 Value::Object(map) => {
383 if let Some(Value::String(ref_uri)) = map.get("$ref") {
385 let current_path = if path.is_empty() {
386 "$ref".to_owned()
387 } else {
388 format!("{path}.$ref")
389 };
390
391 if ref_uri.starts_with('#') {
393 }
395 else if let Some(gts_id) = ref_uri.strip_prefix(GTS_URI_PREFIX) {
397 if !GtsID::is_valid(gts_id) {
399 return Err(StoreError::InvalidRef(format!(
400 "at '{current_path}': '{ref_uri}' contains invalid GTS identifier '{gts_id}'"
401 )));
402 }
403 }
404 else {
406 return Err(StoreError::InvalidRef(format!(
407 "at '{current_path}': '{ref_uri}' must be a local ref (starting with '#') \
408 or a GTS URI (starting with 'gts://')"
409 )));
410 }
411 }
412
413 for (key, value) in map {
415 if key == "$ref" {
416 continue; }
418 let nested_path = if path.is_empty() {
419 key.clone()
420 } else {
421 format!("{path}.{key}")
422 };
423 Self::validate_schema_refs(value, &nested_path)?;
424 }
425 }
426 Value::Array(arr) => {
427 for (idx, item) in arr.iter().enumerate() {
428 let nested_path = format!("{path}[{idx}]");
429 Self::validate_schema_refs(item, &nested_path)?;
430 }
431 }
432 _ => {}
433 }
434 Ok(())
435 }
436
437 pub fn validate_schema(&mut self, gts_id: &str) -> Result<(), StoreError> {
442 if !gts_id.ends_with('~') {
443 return Err(StoreError::SchemaNotFound(format!(
444 "ID '{gts_id}' is not a schema (must end with '~')"
445 )));
446 }
447
448 let schema_entity = self
449 .get(gts_id)
450 .ok_or_else(|| StoreError::SchemaNotFound(gts_id.to_owned()))?;
451
452 if !schema_entity.is_schema {
453 return Err(StoreError::SchemaNotFound(format!(
454 "Entity '{gts_id}' is not a schema"
455 )));
456 }
457
458 let schema_content = schema_entity.content.clone();
459 if !schema_content.is_object() {
460 return Err(StoreError::SchemaNotFound(format!(
461 "Schema '{gts_id}' content must be a dictionary"
462 )));
463 }
464
465 tracing::info!("Validating schema {}", gts_id);
466
467 Self::validate_schema_refs(&schema_content, "")?;
469
470 self.validate_schema_x_gts_refs(gts_id)?;
474
475 let mut schema_for_validation = Self::remove_x_gts_ref_fields(&schema_content);
479
480 if let Value::Object(ref mut map) = schema_for_validation {
482 map.remove("$id");
483 map.remove("$schema");
484 }
485
486 jsonschema::JSONSchema::compile(&schema_for_validation).map_err(|e| {
488 StoreError::ValidationError(format!(
489 "JSON Schema validation failed for '{gts_id}': {e}"
490 ))
491 })?;
492
493 tracing::info!(
494 "Schema {} passed JSON Schema meta-schema validation",
495 gts_id
496 );
497
498 Ok(())
499 }
500
501 pub fn validate_instance(&mut self, gts_id: &str) -> Result<(), StoreError> {
506 let gid = GtsID::new(gts_id).map_err(|_| StoreError::ObjectNotFound(gts_id.to_owned()))?;
507
508 let obj = self
509 .get(&gid.id)
510 .ok_or_else(|| StoreError::ObjectNotFound(gts_id.to_owned()))?
511 .clone();
512
513 let schema_id = obj
514 .schema_id
515 .as_ref()
516 .ok_or_else(|| StoreError::SchemaForInstanceNotFound(gid.id.clone()))?
517 .clone();
518
519 let schema = self.get_schema_content(&schema_id)?;
520
521 tracing::info!(
522 "Validating instance {} against schema {}",
523 gts_id,
524 schema_id
525 );
526
527 let mut resolved_schema = self.resolve_schema_refs(&schema);
529
530 if let Value::Object(ref mut map) = resolved_schema {
532 map.remove("$id");
533 map.remove("$schema");
534 }
535
536 tracing::debug!(
537 "Resolved schema: {}",
538 serde_json::to_string_pretty(&resolved_schema).unwrap_or_default()
539 );
540
541 let compiled = jsonschema::JSONSchema::compile(&resolved_schema).map_err(|e| {
542 tracing::error!("Schema compilation error: {}", e);
543 StoreError::ValidationError(format!("Invalid schema: {e}"))
544 })?;
545
546 compiled.validate(&obj.content).map_err(|e| {
547 let errors: Vec<String> = e.map(|err| err.to_string()).collect();
548 StoreError::ValidationError(format!("Validation failed: {}", errors.join(", ")))
549 })?;
550
551 let validator = crate::x_gts_ref::XGtsRefValidator::new();
553 let x_gts_ref_errors = validator.validate_instance(&obj.content, &schema, "");
554
555 if !x_gts_ref_errors.is_empty() {
556 let error_messages: Vec<String> = x_gts_ref_errors
557 .iter()
558 .map(|err| {
559 if err.field_path.is_empty() {
560 err.reason.clone()
561 } else {
562 format!("{}: {}", err.field_path, err.reason)
563 }
564 })
565 .collect();
566 let error_message =
567 format!("x-gts-ref validation failed: {}", error_messages.join("; "));
568 return Err(StoreError::ValidationError(error_message));
569 }
570
571 Ok(())
572 }
573
574 pub fn cast(
579 &mut self,
580 from_id: &str,
581 target_schema_id: &str,
582 ) -> Result<GtsEntityCastResult, StoreError> {
583 let from_entity = self
584 .get(from_id)
585 .ok_or_else(|| StoreError::EntityNotFound(from_id.to_owned()))?
586 .clone();
587
588 if from_entity.is_schema {
589 return Err(StoreError::CastFromSchemaNotAllowed(from_id.to_owned()));
590 }
591
592 let to_schema = self
593 .get(target_schema_id)
594 .ok_or_else(|| StoreError::ObjectNotFound(target_schema_id.to_owned()))?
595 .clone();
596
597 let (from_schema, _from_schema_id) = if from_entity.is_schema {
599 let id = from_entity
600 .gts_id
601 .as_ref()
602 .ok_or(StoreError::InvalidEntity)?
603 .id
604 .clone();
605 (from_entity.clone(), id)
606 } else {
607 let schema_id = from_entity
608 .schema_id
609 .as_ref()
610 .ok_or_else(|| StoreError::SchemaForInstanceNotFound(from_id.to_owned()))?;
611 let schema = self
612 .get(schema_id)
613 .ok_or_else(|| StoreError::ObjectNotFound(schema_id.clone()))?
614 .clone();
615 (schema, schema_id.clone())
616 };
617
618 let resolver = None;
621
622 from_entity
623 .cast(&to_schema, &from_schema, resolver)
624 .map_err(|e| StoreError::SchemaNotFound(e.to_string()))
625 }
626
627 pub fn is_minor_compatible(
628 &mut self,
629 old_schema_id: &str,
630 new_schema_id: &str,
631 ) -> GtsEntityCastResult {
632 let old_entity = self.get(old_schema_id).cloned();
633 let new_entity = self.get(new_schema_id).cloned();
634
635 let (Some(old_ent), Some(new_ent)) = (old_entity, new_entity) else {
636 return GtsEntityCastResult {
637 from_id: old_schema_id.to_owned(),
638 to_id: new_schema_id.to_owned(),
639 old: old_schema_id.to_owned(),
640 new: new_schema_id.to_owned(),
641 direction: "unknown".to_owned(),
642 added_properties: Vec::new(),
643 removed_properties: Vec::new(),
644 changed_properties: Vec::new(),
645 is_fully_compatible: false,
646 is_backward_compatible: false,
647 is_forward_compatible: false,
648 incompatibility_reasons: vec!["Schema not found".to_owned()],
649 backward_errors: vec!["Schema not found".to_owned()],
650 forward_errors: vec!["Schema not found".to_owned()],
651 casted_entity: None,
652 error: None,
653 };
654 };
655
656 let old_schema = &old_ent.content;
657 let new_schema = &new_ent.content;
658
659 let (is_backward, backward_errors) =
661 GtsEntityCastResult::check_backward_compatibility(old_schema, new_schema);
662 let (is_forward, forward_errors) =
663 GtsEntityCastResult::check_forward_compatibility(old_schema, new_schema);
664
665 let direction = GtsEntityCastResult::infer_direction(old_schema_id, new_schema_id);
667
668 GtsEntityCastResult {
669 from_id: old_schema_id.to_owned(),
670 to_id: new_schema_id.to_owned(),
671 old: old_schema_id.to_owned(),
672 new: new_schema_id.to_owned(),
673 direction,
674 added_properties: Vec::new(),
675 removed_properties: Vec::new(),
676 changed_properties: Vec::new(),
677 is_fully_compatible: is_backward && is_forward,
678 is_backward_compatible: is_backward,
679 is_forward_compatible: is_forward,
680 incompatibility_reasons: Vec::new(),
681 backward_errors,
682 forward_errors,
683 casted_entity: None,
684 error: None,
685 }
686 }
687
688 pub fn build_schema_graph(&mut self, gts_id: &str) -> Value {
689 let mut seen_gts_ids = std::collections::HashSet::new();
690 self.gts2node(gts_id, &mut seen_gts_ids)
691 }
692
693 fn gts2node(
694 &mut self,
695 gts_id: &str,
696 seen_gts_ids: &mut std::collections::HashSet<String>,
697 ) -> Value {
698 let mut ret = serde_json::Map::new();
699 ret.insert("id".to_owned(), Value::String(gts_id.to_owned()));
700
701 if seen_gts_ids.contains(gts_id) {
702 return Value::Object(ret);
703 }
704
705 seen_gts_ids.insert(gts_id.to_owned());
706
707 let entity_clone = self.get(gts_id).cloned();
709
710 if let Some(entity) = entity_clone {
711 let mut refs = serde_json::Map::new();
712
713 let ref_ids: Vec<_> = entity
715 .gts_refs
716 .iter()
717 .filter(|r| {
718 r.id != gts_id
719 && !r.id.starts_with("http://json-schema.org")
720 && !r.id.starts_with("https://json-schema.org")
721 })
722 .map(|r| (r.source_path.clone(), r.id.clone()))
723 .collect();
724
725 for (source_path, ref_id) in ref_ids {
726 refs.insert(source_path, self.gts2node(&ref_id, seen_gts_ids));
727 }
728
729 if !refs.is_empty() {
730 ret.insert("refs".to_owned(), Value::Object(refs));
731 }
732
733 if let Some(ref schema_id) = entity.schema_id {
734 if !schema_id.starts_with("http://json-schema.org")
735 && !schema_id.starts_with("https://json-schema.org")
736 {
737 let schema_id_clone = schema_id.clone();
738 ret.insert(
739 "schema_id".to_owned(),
740 self.gts2node(&schema_id_clone, seen_gts_ids),
741 );
742 }
743 } else {
744 let mut errors = ret
745 .get("errors")
746 .and_then(|e| e.as_array())
747 .cloned()
748 .unwrap_or_default();
749 errors.push(Value::String("Schema not recognized".to_owned()));
750 ret.insert("errors".to_owned(), Value::Array(errors));
751 }
752 } else {
753 let mut errors = ret
754 .get("errors")
755 .and_then(|e| e.as_array())
756 .cloned()
757 .unwrap_or_default();
758 errors.push(Value::String("Entity not found".to_owned()));
759 ret.insert("errors".to_owned(), Value::Array(errors));
760 }
761
762 Value::Object(ret)
763 }
764
765 #[must_use]
766 pub fn query(&self, expr: &str, limit: usize) -> GtsStoreQueryResult {
767 let mut result = GtsStoreQueryResult {
768 error: String::new(),
769 count: 0,
770 limit,
771 results: Vec::new(),
772 };
773
774 let (base, _, filt) = expr.partition('[');
776 let base_pattern = base.trim();
777 let is_wildcard = base_pattern.contains('*');
778
779 let filter_str = if filt.is_empty() {
781 ""
782 } else {
783 filt.rsplit_once(']').map_or("", |x| x.0)
784 };
785 let filters = Self::parse_query_filters(filter_str);
786
787 let (wildcard_pattern, exact_gts_id, error) =
789 Self::validate_query_pattern(base_pattern, is_wildcard);
790 if !error.is_empty() {
791 result.error = error;
792 return result;
793 }
794
795 for entity in self.by_id.values() {
797 if result.results.len() >= limit {
798 break;
799 }
800
801 if !entity.content.is_object() {
802 continue;
803 }
804
805 let Some(ref gts_id) = entity.gts_id else {
806 continue;
807 };
808
809 if !Self::matches_id_pattern(
811 gts_id,
812 base_pattern,
813 is_wildcard,
814 wildcard_pattern.as_ref(),
815 exact_gts_id.as_ref(),
816 ) {
817 continue;
818 }
819
820 if !Self::matches_filters(&entity.content, &filters) {
822 continue;
823 }
824
825 result.results.push(entity.content.clone());
826 }
827
828 result.count = result.results.len();
829 result
830 }
831
832 fn parse_query_filters(filter_str: &str) -> HashMap<String, String> {
833 let mut filters = HashMap::new();
834 if filter_str.is_empty() {
835 return filters;
836 }
837
838 let parts: Vec<&str> = filter_str.split(',').map(str::trim).collect();
839 for part in parts {
840 if let Some((k, v)) = part.split_once('=') {
841 let v = v.trim().trim_matches('"').trim_matches('\'');
842 filters.insert(k.trim().to_owned(), v.to_owned());
843 }
844 }
845
846 filters
847 }
848
849 fn validate_query_pattern(
850 base_pattern: &str,
851 is_wildcard: bool,
852 ) -> (Option<GtsWildcard>, Option<GtsID>, String) {
853 if is_wildcard {
854 if !base_pattern.ends_with(".*") && !base_pattern.ends_with("~*") {
855 return (
856 None,
857 None,
858 "Invalid query: wildcard patterns must end with .* or ~*".to_owned(),
859 );
860 }
861 match GtsWildcard::new(base_pattern) {
862 Ok(pattern) => (Some(pattern), None, String::new()),
863 Err(e) => (None, None, format!("Invalid query: {e}")),
864 }
865 } else {
866 match GtsID::new(base_pattern) {
867 Ok(gts_id) => {
868 if gts_id.gts_id_segments.is_empty() {
869 (
870 None,
871 None,
872 "Invalid query: GTS ID has no valid segments".to_owned(),
873 )
874 } else {
875 (None, Some(gts_id), String::new())
876 }
877 }
878 Err(e) => (None, None, format!("Invalid query: {e}")),
879 }
880 }
881 }
882
883 fn matches_id_pattern(
884 entity_id: &GtsID,
885 base_pattern: &str,
886 is_wildcard: bool,
887 wildcard_pattern: Option<&GtsWildcard>,
888 exact_gts_id: Option<&GtsID>,
889 ) -> bool {
890 if is_wildcard {
891 if let Some(pattern) = wildcard_pattern {
892 return entity_id.wildcard_match(pattern);
893 }
894 }
895
896 if let Some(_exact) = exact_gts_id {
898 match GtsWildcard::new(base_pattern) {
899 Ok(pattern_as_wildcard) => entity_id.wildcard_match(&pattern_as_wildcard),
900 Err(_) => entity_id.id == base_pattern,
901 }
902 } else {
903 entity_id.id == base_pattern
904 }
905 }
906
907 fn matches_filters(entity_content: &Value, filters: &HashMap<String, String>) -> bool {
908 if filters.is_empty() {
909 return true;
910 }
911
912 if let Some(obj) = entity_content.as_object() {
913 for (key, value) in filters {
914 let entity_value = obj.get(key).map_or_else(String::new, ToString::to_string);
915
916 if value == "*" {
918 if entity_value.is_empty() || entity_value == "null" {
919 return false;
920 }
921 } else if entity_value != format!("\"{value}\"") && entity_value != *value {
922 return false;
923 }
924 }
925 true
926 } else {
927 false
928 }
929 }
930}
931
932trait StringPartition {
934 fn partition(&self, delimiter: char) -> (&str, &str, &str);
935}
936
937impl StringPartition for str {
938 fn partition(&self, delimiter: char) -> (&str, &str, &str) {
939 if let Some(pos) = self.find(delimiter) {
940 let (before, after_with_delim) = self.split_at(pos);
941 let after = &after_with_delim[delimiter.len_utf8()..];
942 (before, &after_with_delim[..delimiter.len_utf8()], after)
943 } else {
944 (self, "", "")
945 }
946 }
947}
948#[cfg(test)]
949#[allow(clippy::unwrap_used, clippy::expect_used)]
950mod tests {
951 use super::*;
952 use crate::entities::{GtsConfig, GtsEntity};
953 use serde_json::json;
954
955 #[test]
956 fn test_gts_store_query_result_default() {
957 let result = GtsStoreQueryResult {
958 error: String::new(),
959 count: 0,
960 limit: 100,
961 results: vec![],
962 };
963
964 assert_eq!(result.count, 0);
965 assert_eq!(result.limit, 100);
966 assert!(result.error.is_empty());
967 assert!(result.results.is_empty());
968 }
969
970 #[test]
971 fn test_gts_store_query_result_serialization() {
972 let result = GtsStoreQueryResult {
973 error: String::new(),
974 count: 2,
975 limit: 10,
976 results: vec![json!({"id": "test1"}), json!({"id": "test2"})],
977 };
978
979 let json_value = serde_json::to_value(&result).expect("test");
980 let json = json_value.as_object().expect("test");
981 assert_eq!(json.get("count").expect("test").as_u64().expect("test"), 2);
982 assert_eq!(json.get("limit").expect("test").as_u64().expect("test"), 10);
983 assert!(json.get("results").expect("test").is_array());
984 }
985
986 #[test]
987 fn test_gts_store_new_without_reader() {
988 let store: GtsStore = GtsStore::new(None);
989 assert_eq!(store.items().count(), 0);
990 }
991
992 #[test]
993 fn test_gts_store_register_entity() {
994 let mut store = GtsStore::new(None);
995 let cfg = GtsConfig::default();
996
997 let content = json!({
998 "id": "gts.vendor.package.namespace.type.v1.0",
999 "name": "test"
1000 });
1001
1002 let entity = GtsEntity::new(
1003 None,
1004 None,
1005 &content,
1006 Some(&cfg),
1007 None,
1008 false,
1009 String::new(),
1010 None,
1011 None,
1012 );
1013
1014 let result = store.register(entity);
1015 assert!(result.is_ok());
1016 assert_eq!(store.items().count(), 1);
1017 }
1018
1019 #[test]
1020 fn test_gts_store_register_schema() {
1021 let mut store = GtsStore::new(None);
1022
1023 let schema_content = json!({
1024 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1025 "$schema": "http://json-schema.org/draft-07/schema#",
1026 "type": "object",
1027 "properties": {
1028 "name": {"type": "string"}
1029 }
1030 });
1031
1032 let result =
1033 store.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_content);
1034
1035 assert!(result.is_ok());
1036
1037 let entity = store.get("gts.vendor.package.namespace.type.v1.0~");
1038 assert!(entity.is_some());
1039 assert!(entity.expect("test").is_schema);
1040 }
1041
1042 #[test]
1043 fn test_gts_store_register_schema_invalid_id() {
1044 let mut store = GtsStore::new(None);
1045
1046 let schema_content = json!({
1047 "type": "object"
1048 });
1049
1050 let result = store.register_schema(
1051 "gts.vendor.package.namespace.type.v1.0", &schema_content,
1053 );
1054
1055 assert!(result.is_err());
1056 match result {
1057 Err(StoreError::InvalidSchemaId) => {}
1058 _ => panic!("Expected InvalidSchemaId error"),
1059 }
1060 }
1061
1062 #[test]
1063 fn test_gts_store_get_schema_content() {
1064 let mut store = GtsStore::new(None);
1065
1066 let schema_content = json!({
1067 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1068 "$schema": "http://json-schema.org/draft-07/schema#",
1069 "type": "object"
1070 });
1071
1072 store
1073 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_content)
1074 .expect("test");
1075
1076 let result = store.get_schema_content("gts.vendor.package.namespace.type.v1.0~");
1077 assert!(result.is_ok());
1078 assert_eq!(result.expect("test"), schema_content);
1079 }
1080
1081 #[test]
1082 fn test_gts_store_get_schema_content_not_found() {
1083 let mut store = GtsStore::new(None);
1084 let result = store.get_schema_content("nonexistent~");
1085 assert!(result.is_err());
1086
1087 match result {
1088 Err(StoreError::SchemaNotFound(id)) => {
1089 assert_eq!(id, "nonexistent~");
1090 }
1091 _ => panic!("Expected SchemaNotFound error"),
1092 }
1093 }
1094
1095 #[test]
1096 fn test_gts_store_items_iterator() {
1097 let mut store = GtsStore::new(None);
1098
1099 for i in 0..3 {
1101 let schema_content = json!({
1102 "$id": format!("gts.vendor.package.namespace.type.v{i}.0~"),
1103 "$schema": "http://json-schema.org/draft-07/schema#",
1104 "type": "object"
1105 });
1106
1107 store
1108 .register_schema(
1109 &format!("gts.vendor.package.namespace.type.v{i}.0~"),
1110 &schema_content,
1111 )
1112 .expect("test");
1113 }
1114
1115 assert_eq!(store.items().count(), 3);
1116
1117 assert_eq!(store.items().count(), 3);
1119 }
1120
1121 #[test]
1122 fn test_gts_store_validate_instance_missing_schema() {
1123 let mut store = GtsStore::new(None);
1124 let cfg = GtsConfig::default();
1125
1126 let content = json!({
1128 "id": "gts.vendor.package.namespace.type.v1.0",
1129 "name": "test"
1130 });
1131
1132 let entity = GtsEntity::new(
1133 None,
1134 None,
1135 &content,
1136 Some(&cfg),
1137 None,
1138 false,
1139 String::new(),
1140 None,
1141 None,
1142 );
1143
1144 store.register(entity).expect("test");
1145
1146 let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
1148 assert!(result.is_err());
1149 }
1150
1151 #[test]
1152 fn test_gts_store_build_schema_graph() {
1153 let mut store = GtsStore::new(None);
1154
1155 let schema_content = json!({
1156 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1157 "$schema": "http://json-schema.org/draft-07/schema#",
1158 "type": "object"
1159 });
1160
1161 store
1162 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_content)
1163 .expect("test");
1164
1165 let graph = store.build_schema_graph("gts.vendor.package.namespace.type.v1.0~");
1166 assert!(graph.is_object());
1167 }
1168
1169 #[test]
1172 fn test_gts_store_query_wildcard() {
1173 let mut store = GtsStore::new(None);
1174
1175 for i in 0..3 {
1177 let schema_content = json!({
1178 "$id": format!("gts.vendor.package.namespace.type.v{i}.0~"),
1179 "$schema": "http://json-schema.org/draft-07/schema#",
1180 "type": "object"
1181 });
1182
1183 let schema_id = format!("gts.vendor.package.namespace.type.v{i}.0~");
1184
1185 store
1186 .register_schema(&schema_id, &schema_content)
1187 .expect("test");
1188 }
1189
1190 let result = store.query("gts.vendor.*", 10);
1192 assert_eq!(result.count, 3);
1193 assert_eq!(result.results.len(), 3);
1194 }
1195
1196 #[test]
1197 fn test_gts_store_query_with_limit() {
1198 let mut store = GtsStore::new(None);
1199
1200 for i in 0..5 {
1202 let schema_content = json!({
1203 "$id": format!("gts.vendor.package.namespace.type.v{i}.0~"),
1204 "$schema": "http://json-schema.org/draft-07/schema#",
1205 "type": "object"
1206 });
1207
1208 store
1209 .register_schema(
1210 &format!("gts.vendor.package.namespace.type.v{i}.0~"),
1211 &schema_content,
1212 )
1213 .expect("test");
1214 }
1215
1216 let result = store.query("gts.vendor.*", 2);
1218 assert_eq!(result.results.len(), 2);
1219 assert!(result.count >= 2);
1221 }
1222
1223 #[test]
1224 fn test_store_error_display() {
1225 let error = StoreError::ObjectNotFound("test_id".to_owned());
1226 assert!(error.to_string().contains("test_id"));
1227
1228 let error = StoreError::SchemaNotFound("schema_id".to_owned());
1229 assert!(error.to_string().contains("schema_id"));
1230
1231 let error = StoreError::EntityNotFound("entity_id".to_owned());
1232 assert!(error.to_string().contains("entity_id"));
1233
1234 let error = StoreError::SchemaForInstanceNotFound("instance_id".to_owned());
1235 assert!(error.to_string().contains("instance_id"));
1236 }
1237
1238 #[test]
1241 fn test_gts_store_cast() {
1242 let mut store = GtsStore::new(None);
1243
1244 let schema_v1 = json!({
1246 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1247 "$schema": "http://json-schema.org/draft-07/schema#",
1248 "type": "object",
1249 "properties": {
1250 "name": {"type": "string"}
1251 }
1252 });
1253
1254 let schema_v2 = json!({
1255 "$id": "gts://gts.vendor.package.namespace.type.v1.1~",
1256 "$schema": "http://json-schema.org/draft-07/schema#",
1257 "type": "object",
1258 "properties": {
1259 "name": {"type": "string"},
1260 "email": {"type": "string", "default": "test@example.com"}
1261 }
1262 });
1263
1264 store
1265 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
1266 .expect("test");
1267 store
1268 .register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
1269 .expect("test");
1270
1271 let cfg = GtsConfig::default();
1273 let content = json!({
1274 "id": "gts.vendor.package.namespace.type.v1.0",
1275 "type": "gts.vendor.package.namespace.type.v1.0~",
1276 "name": "John"
1277 });
1278
1279 let entity = GtsEntity::new(
1280 None,
1281 None,
1282 &content,
1283 Some(&cfg),
1284 None,
1285 false,
1286 String::new(),
1287 None,
1288 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
1289 );
1290
1291 store.register(entity).expect("test");
1292
1293 let result = store.cast(
1295 "gts.vendor.package.namespace.type.v1.0",
1296 "gts.vendor.package.namespace.type.v1.1~",
1297 );
1298
1299 assert!(result.is_ok() || result.is_err());
1301 }
1302
1303 #[test]
1304 fn test_gts_store_cast_missing_entity() {
1305 let mut store = GtsStore::new(None);
1306
1307 let result = store.cast("nonexistent", "gts.vendor.package.namespace.type.v1.0~");
1308 assert!(result.is_err());
1309 }
1310
1311 #[test]
1312 fn test_gts_store_cast_missing_schema() {
1313 let mut store = GtsStore::new(None);
1314 let cfg = GtsConfig::default();
1315
1316 let content = json!({
1317 "id": "gts.vendor.package.namespace.type.v1.0",
1318 "name": "test"
1319 });
1320
1321 let entity = GtsEntity::new(
1322 None,
1323 None,
1324 &content,
1325 Some(&cfg),
1326 None,
1327 false,
1328 String::new(),
1329 None,
1330 None,
1331 );
1332
1333 store.register(entity).expect("test");
1334
1335 let result = store.cast("gts.vendor.package.namespace.type.v1.0", "nonexistent~");
1336 assert!(result.is_err());
1337 }
1338
1339 #[test]
1340 fn test_gts_store_is_minor_compatible() {
1341 let mut store = GtsStore::new(None);
1342
1343 let schema_v1 = json!({
1344 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1345 "$schema": "http://json-schema.org/draft-07/schema#",
1346 "type": "object",
1347 "properties": {
1348 "name": {"type": "string"}
1349 }
1350 });
1351
1352 let schema_v2 = json!({
1353 "$id": "gts://gts.vendor.package.namespace.type.v1.1~",
1354 "$schema": "http://json-schema.org/draft-07/schema#",
1355 "type": "object",
1356 "properties": {
1357 "name": {"type": "string"},
1358 "email": {"type": "string"}
1359 }
1360 });
1361
1362 store
1363 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
1364 .expect("test");
1365 store
1366 .register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
1367 .expect("test");
1368
1369 let result = store.is_minor_compatible(
1370 "gts.vendor.package.namespace.type.v1.0~",
1371 "gts.vendor.package.namespace.type.v1.1~",
1372 );
1373
1374 assert!(result.is_backward_compatible);
1376 }
1377
1378 #[test]
1379 fn test_gts_store_get() {
1380 let mut store = GtsStore::new(None);
1381 let cfg = GtsConfig::default();
1382
1383 let content = json!({
1384 "id": "gts.vendor.package.namespace.type.v1.0",
1385 "name": "test"
1386 });
1387
1388 let entity = GtsEntity::new(
1389 None,
1390 None,
1391 &content,
1392 Some(&cfg),
1393 None,
1394 false,
1395 String::new(),
1396 None,
1397 None,
1398 );
1399
1400 store.register(entity).expect("test");
1401
1402 let result = store.get("gts.vendor.package.namespace.type.v1.0");
1403 assert!(result.is_some());
1404 }
1405
1406 #[test]
1407 fn test_gts_store_get_nonexistent() {
1408 let mut store = GtsStore::new(None);
1409 let result = store.get("nonexistent");
1410 assert!(result.is_none());
1411 }
1412
1413 #[test]
1414 fn test_gts_store_query_exact_match() {
1415 let mut store = GtsStore::new(None);
1416
1417 let schema = json!({
1418 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1419 "$schema": "http://json-schema.org/draft-07/schema#",
1420 "type": "object"
1421 });
1422
1423 store
1424 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
1425 .expect("test");
1426
1427 let result = store.query("gts.vendor.package.namespace.type.v1.0~", 10);
1428 assert_eq!(result.count, 1);
1429 }
1430
1431 #[test]
1432 fn test_gts_store_register_duplicate() {
1433 let mut store = GtsStore::new(None);
1434 let cfg = GtsConfig::default();
1435
1436 let content = json!({
1437 "id": "gts.vendor.package.namespace.type.v1.0",
1438 "name": "test"
1439 });
1440
1441 let entity1 = GtsEntity::new(
1442 None,
1443 None,
1444 &content,
1445 Some(&cfg),
1446 None,
1447 false,
1448 String::new(),
1449 None,
1450 None,
1451 );
1452
1453 let entity2 = GtsEntity::new(
1454 None,
1455 None,
1456 &content,
1457 Some(&cfg),
1458 None,
1459 false,
1460 String::new(),
1461 None,
1462 None,
1463 );
1464
1465 store.register(entity1).expect("test");
1466 let result = store.register(entity2);
1467
1468 assert!(result.is_ok());
1470 }
1471
1472 #[test]
1473 fn test_gts_store_validate_instance_success() {
1474 let mut store = GtsStore::new(None);
1475
1476 let schema = json!({
1477 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1478 "$schema": "http://json-schema.org/draft-07/schema#",
1479 "type": "object",
1480 "properties": {
1481 "name": {"type": "string"}
1482 },
1483 "required": ["name"]
1484 });
1485
1486 store
1487 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
1488 .expect("test");
1489
1490 let cfg = GtsConfig::default();
1491 let content = json!({
1492 "id": "gts.vendor.package.namespace.type.v1.0~a.b.c.d.v1",
1493 "type": "gts.vendor.package.namespace.type.v1.2~",
1494 "name": "test"
1495 });
1496
1497 let entity = GtsEntity::new(
1498 None,
1499 None,
1500 &content,
1501 Some(&cfg),
1502 None,
1503 false,
1504 String::new(),
1505 None,
1506 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
1507 );
1508
1509 store.register(entity).expect("test");
1510
1511 let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0~a.b.c.d.v1");
1512 assert!(result.is_ok());
1513 }
1514
1515 #[test]
1516 fn test_gts_store_validate_instance_missing_entity() {
1517 let mut store = GtsStore::new(None);
1518 let result = store.validate_instance("nonexistent");
1519 assert!(result.is_err());
1520 }
1521
1522 #[test]
1523 fn test_gts_store_validate_instance_no_schema() {
1524 let mut store = GtsStore::new(None);
1525 let cfg = GtsConfig::default();
1526
1527 let content = json!({
1528 "id": "gts.vendor.package.namespace.type.v1.0",
1529 "name": "test"
1530 });
1531
1532 let entity = GtsEntity::new(
1533 None,
1534 None,
1535 &content,
1536 Some(&cfg),
1537 None,
1538 false,
1539 String::new(),
1540 None,
1541 None,
1542 );
1543
1544 store.register(entity).expect("test");
1545
1546 let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
1547 assert!(result.is_err());
1548 }
1549
1550 #[test]
1551 fn test_gts_store_register_schema_with_invalid_id() {
1552 let mut store = GtsStore::new(None);
1553
1554 let schema = json!({
1555 "$id": "invalid",
1556 "$schema": "http://json-schema.org/draft-07/schema#",
1557 "type": "object"
1558 });
1559
1560 let result = store.register_schema("invalid", &schema);
1561 assert!(result.is_err());
1562 }
1563
1564 #[test]
1565 fn test_gts_store_get_schema_content_missing() {
1566 let mut store = GtsStore::new(None);
1567 let result = store.get_schema_content("nonexistent~");
1568 assert!(result.is_err());
1569 }
1570
1571 #[test]
1572 fn test_gts_store_query_empty() {
1573 let store = GtsStore::new(None);
1574 let result = store.query("gts.vendor.*", 10);
1575 assert_eq!(result.count, 0);
1576 assert_eq!(result.results.len(), 0);
1577 }
1578
1579 #[test]
1580 fn test_gts_store_items_empty() {
1581 let store = GtsStore::new(None);
1582 assert_eq!(store.items().count(), 0);
1583 }
1584
1585 #[test]
1586 fn test_gts_store_register_entity_without_id() {
1587 let mut store = GtsStore::new(None);
1588
1589 let content = json!({
1590 "name": "test"
1591 });
1592
1593 let entity = GtsEntity::new(
1594 None,
1595 None,
1596 &content,
1597 None,
1598 None,
1599 false,
1600 String::new(),
1601 None,
1602 None,
1603 );
1604
1605 let result = store.register(entity);
1606 assert!(result.is_err());
1607 }
1608
1609 #[test]
1610 fn test_gts_store_build_schema_graph_missing() {
1611 let mut store = GtsStore::new(None);
1612 let graph = store.build_schema_graph("nonexistent~");
1613 assert!(graph.is_object());
1614 }
1615
1616 #[test]
1617 fn test_gts_store_new_empty() {
1618 let store = GtsStore::new(None);
1619 assert_eq!(store.items().count(), 0);
1620 }
1621
1622 #[test]
1623 fn test_gts_store_cast_entity_without_schema() {
1624 let mut store = GtsStore::new(None);
1625 let cfg = GtsConfig::default();
1626
1627 let content = json!({
1628 "id": "gts.vendor.package.namespace.type.v1.0",
1629 "name": "test"
1630 });
1631
1632 let entity = GtsEntity::new(
1633 None,
1634 None,
1635 &content,
1636 Some(&cfg),
1637 None,
1638 false,
1639 String::new(),
1640 None,
1641 None,
1642 );
1643
1644 store.register(entity).expect("test");
1645
1646 let result = store.cast(
1647 "gts.vendor.package.namespace.type.v1.0",
1648 "gts.vendor.package.namespace.type.v1.1~",
1649 );
1650 assert!(result.is_err());
1651 }
1652
1653 #[test]
1654 fn test_gts_store_is_minor_compatible_missing_schemas() {
1655 let mut store = GtsStore::new(None);
1656 let result = store.is_minor_compatible("nonexistent1~", "nonexistent2~");
1657 assert!(!result.is_backward_compatible);
1658 }
1659
1660 #[test]
1661 fn test_gts_store_validate_instance_with_refs() {
1662 let mut store = GtsStore::new(None);
1663
1664 let base_schema = json!({
1666 "$id": "gts://gts.vendor.package.namespace.base.v1.0~",
1667 "$schema": "http://json-schema.org/draft-07/schema#",
1668 "type": "object",
1669 "properties": {
1670 "id": {"type": "string"}
1671 }
1672 });
1673
1674 let schema = json!({
1676 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1677 "$schema": "http://json-schema.org/draft-07/schema#",
1678 "allOf": [
1679 {"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"},
1680 {
1681 "type": "object",
1682 "properties": {
1683 "name": {"type": "string"}
1684 }
1685 }
1686 ]
1687 });
1688
1689 store
1690 .register_schema("gts.vendor.package.namespace.base.v1.0~", &base_schema)
1691 .expect("test");
1692 store
1693 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
1694 .expect("test");
1695
1696 let cfg = GtsConfig::default();
1697 let content = json!({
1698 "id": "gts.vendor.package.namespace.type.v1.0",
1699 "type": "gts.vendor.package.namespace.type.v1.0~",
1700 "name": "test"
1701 });
1702
1703 let entity = GtsEntity::new(
1704 None,
1705 None,
1706 &content,
1707 Some(&cfg),
1708 None,
1709 false,
1710 String::new(),
1711 None,
1712 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
1713 );
1714
1715 store.register(entity).expect("test");
1716
1717 let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
1718 assert!(result.is_ok() || result.is_err());
1720 }
1721
1722 #[test]
1723 fn test_gts_store_validate_instance_validation_failure() {
1724 let mut store = GtsStore::new(None);
1725
1726 let schema = json!({
1727 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1728 "$schema": "http://json-schema.org/draft-07/schema#",
1729 "type": "object",
1730 "properties": {
1731 "age": {"type": "number"}
1732 },
1733 "required": ["age"]
1734 });
1735
1736 store
1737 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
1738 .expect("test");
1739
1740 let cfg = GtsConfig::default();
1741 let content = json!({
1742 "id": "gts.vendor.package.namespace.type.v1.0",
1743 "type": "gts.vendor.package.namespace.type.v1.0~",
1744 "age": "not a number"
1745 });
1746
1747 let entity = GtsEntity::new(
1748 None,
1749 None,
1750 &content,
1751 Some(&cfg),
1752 None,
1753 false,
1754 String::new(),
1755 None,
1756 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
1757 );
1758
1759 store.register(entity).expect("test");
1760
1761 let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
1762 assert!(result.is_err());
1763 }
1764
1765 #[test]
1766 fn test_gts_store_query_with_filters() {
1767 let mut store = GtsStore::new(None);
1768
1769 for i in 0..5 {
1770 let schema = json!({
1771 "$id": format!("gts.vendor.package.namespace.type{i}.v1.0~"),
1772 "$schema": "http://json-schema.org/draft-07/schema#",
1773 "type": "object"
1774 });
1775
1776 store
1777 .register_schema(
1778 &format!("gts.vendor.package.namespace.type{i}.v1.0~"),
1779 &schema,
1780 )
1781 .expect("test");
1782 }
1783
1784 let result = store.query("gts.vendor.package.namespace.type0.*", 10);
1785 assert_eq!(result.count, 1);
1786 }
1787
1788 #[test]
1789 fn test_gts_store_register_multiple_schemas() {
1790 let mut store = GtsStore::new(None);
1791
1792 for i in 0..10 {
1793 let schema = json!({
1794 "$id": format!("gts.vendor.package.namespace.type.v1.{i}~"),
1795 "$schema": "http://json-schema.org/draft-07/schema#",
1796 "type": "object"
1797 });
1798
1799 let result = store.register_schema(
1800 &format!("gts.vendor.package.namespace.type.v1.{i}~"),
1801 &schema,
1802 );
1803 assert!(result.is_ok());
1804 }
1805
1806 assert_eq!(store.items().count(), 10);
1807 }
1808
1809 #[test]
1810 fn test_gts_store_cast_with_validation() {
1811 let mut store = GtsStore::new(None);
1812
1813 let schema_v1 = json!({
1814 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1815 "$schema": "http://json-schema.org/draft-07/schema#",
1816 "type": "object",
1817 "properties": {
1818 "name": {"type": "string"}
1819 },
1820 "required": ["name"]
1821 });
1822
1823 let schema_v2 = json!({
1824 "$id": "gts://gts.vendor.package.namespace.type.v1.1~",
1825 "$schema": "http://json-schema.org/draft-07/schema#",
1826 "type": "object",
1827 "properties": {
1828 "name": {"type": "string"},
1829 "email": {"type": "string", "default": "test@example.com"}
1830 },
1831 "required": ["name"]
1832 });
1833
1834 store
1835 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
1836 .expect("test");
1837 store
1838 .register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
1839 .expect("test");
1840
1841 let cfg = GtsConfig::default();
1842 let content = json!({
1843 "id": "gts.vendor.package.namespace.type.v1.0",
1844 "type": "gts.vendor.package.namespace.type.v1.0~",
1845 "name": "John"
1846 });
1847
1848 let entity = GtsEntity::new(
1849 None,
1850 None,
1851 &content,
1852 Some(&cfg),
1853 None,
1854 false,
1855 String::new(),
1856 None,
1857 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
1858 );
1859
1860 store.register(entity).expect("test");
1861
1862 let result = store.cast(
1863 "gts.vendor.package.namespace.type.v1.0",
1864 "gts.vendor.package.namespace.type.v1.1~",
1865 );
1866
1867 assert!(result.is_ok() || result.is_err());
1868 }
1869
1870 #[test]
1871 fn test_gts_store_build_schema_graph_with_refs() {
1872 let mut store = GtsStore::new(None);
1873
1874 let base_schema = json!({
1875 "$id": "gts://gts.vendor.package.namespace.base.v1.0~",
1876 "$schema": "http://json-schema.org/draft-07/schema#",
1877 "type": "object",
1878 "properties": {
1879 "id": {"type": "string"}
1880 }
1881 });
1882
1883 let schema = json!({
1884 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1885 "$schema": "http://json-schema.org/draft-07/schema#",
1886 "allOf": [
1887 {"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"}
1888 ]
1889 });
1890
1891 store
1892 .register_schema("gts.vendor.package.namespace.base.v1.0~", &base_schema)
1893 .expect("test");
1894 store
1895 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
1896 .expect("test");
1897
1898 let graph = store.build_schema_graph("gts.vendor.package.namespace.type.v1.0~");
1899 assert!(graph.is_object());
1900 }
1901
1902 #[test]
1903 fn test_gts_store_get_schema_content_success() {
1904 let mut store = GtsStore::new(None);
1905
1906 let schema = json!({
1907 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1908 "$schema": "http://json-schema.org/draft-07/schema#",
1909 "type": "object",
1910 "properties": {
1911 "name": {"type": "string"}
1912 }
1913 });
1914
1915 store
1916 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
1917 .expect("test");
1918
1919 let result = store.get_schema_content("gts.vendor.package.namespace.type.v1.0~");
1920 assert!(result.is_ok());
1921 assert_eq!(
1922 result
1923 .expect("test")
1924 .get("type")
1925 .expect("test")
1926 .as_str()
1927 .expect("test"),
1928 "object"
1929 );
1930 }
1931
1932 #[test]
1933 fn test_gts_store_register_entity_with_schema() {
1934 let mut store = GtsStore::new(None);
1935 let cfg = GtsConfig::default();
1936
1937 let schema = json!({
1938 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1939 "$schema": "http://json-schema.org/draft-07/schema#",
1940 "type": "object"
1941 });
1942
1943 store
1944 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
1945 .expect("test");
1946
1947 let content = json!({
1948 "id": "gts.vendor.package.namespace.type.v1.0",
1949 "type": "gts.vendor.package.namespace.type.v1.0~",
1950 "name": "test"
1951 });
1952
1953 let entity = GtsEntity::new(
1954 None,
1955 None,
1956 &content,
1957 Some(&cfg),
1958 None,
1959 false,
1960 String::new(),
1961 None,
1962 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
1963 );
1964
1965 let result = store.register(entity);
1966 assert!(result.is_ok());
1967 }
1968
1969 #[test]
1970 fn test_gts_store_query_result_structure() {
1971 let result = GtsStoreQueryResult {
1972 error: String::new(),
1973 count: 0,
1974 limit: 100,
1975 results: vec![],
1976 };
1977
1978 assert_eq!(result.count, 0);
1979 assert_eq!(result.limit, 100);
1980 assert!(result.results.is_empty());
1981 }
1982
1983 #[test]
1984 fn test_gts_store_error_variants() {
1985 let err1 = StoreError::InvalidEntity;
1986 assert!(!err1.to_string().is_empty());
1987
1988 let err2 = StoreError::InvalidSchemaId;
1989 assert!(!err2.to_string().is_empty());
1990 }
1991
1992 #[test]
1993 fn test_gts_store_register_schema_overwrite() {
1994 let mut store = GtsStore::new(None);
1995
1996 let schema1 = json!({
1997 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
1998 "$schema": "http://json-schema.org/draft-07/schema#",
1999 "type": "object",
2000 "properties": {
2001 "name": {"type": "string"}
2002 }
2003 });
2004
2005 let schema2 = json!({
2006 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2007 "$schema": "http://json-schema.org/draft-07/schema#",
2008 "type": "object",
2009 "properties": {
2010 "name": {"type": "string"},
2011 "email": {"type": "string"}
2012 }
2013 });
2014
2015 store
2016 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema1)
2017 .expect("test");
2018 store
2019 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema2)
2020 .expect("test");
2021
2022 let result = store.get_schema_content("gts.vendor.package.namespace.type.v1.0~");
2023 assert!(result.is_ok());
2024 let schema = result.expect("test");
2025 assert!(schema
2026 .get("properties")
2027 .expect("test")
2028 .get("email")
2029 .is_some());
2030 }
2031
2032 #[test]
2033 fn test_gts_store_cast_missing_source_schema() {
2034 let mut store = GtsStore::new(None);
2035 let cfg = GtsConfig::default();
2036
2037 let schema = json!({
2038 "$id": "gts://gts.vendor.package.namespace.type.v1.1~",
2039 "$schema": "http://json-schema.org/draft-07/schema#",
2040 "type": "object"
2041 });
2042
2043 store
2044 .register_schema("gts.vendor.package.namespace.type.v1.1~", &schema)
2045 .expect("test");
2046
2047 let content = json!({
2048 "id": "gts.vendor.package.namespace.type.v1.0",
2049 "name": "test"
2050 });
2051
2052 let entity = GtsEntity::new(
2053 None,
2054 None,
2055 &content,
2056 Some(&cfg),
2057 None,
2058 false,
2059 String::new(),
2060 None,
2061 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
2062 );
2063
2064 store.register(entity).expect("test");
2065
2066 let result = store.cast(
2067 "gts.vendor.package.namespace.type.v1.0",
2068 "gts.vendor.package.namespace.type.v1.1~",
2069 );
2070 assert!(result.is_err());
2071 }
2072
2073 #[test]
2074 fn test_gts_store_query_multiple_patterns() {
2075 let mut store = GtsStore::new(None);
2076
2077 let schema1 = json!({
2078 "$id": "gts://gts.vendor1.package.namespace.type.v1.0~",
2079 "$schema": "http://json-schema.org/draft-07/schema#",
2080 "type": "object"
2081 });
2082
2083 let schema2 = json!({
2084 "$id": "gts://gts.vendor2.package.namespace.type.v1.0~",
2085 "$schema": "http://json-schema.org/draft-07/schema#",
2086 "type": "object"
2087 });
2088
2089 store
2090 .register_schema("gts.vendor1.package.namespace.type.v1.0~", &schema1)
2091 .expect("test");
2092 store
2093 .register_schema("gts.vendor2.package.namespace.type.v1.0~", &schema2)
2094 .expect("test");
2095
2096 let result1 = store.query("gts.vendor1.*", 10);
2097 assert_eq!(result1.count, 1);
2098
2099 let result2 = store.query("gts.vendor2.*", 10);
2100 assert_eq!(result2.count, 1);
2101
2102 let result3 = store.query("gts.*", 10);
2103 assert_eq!(result3.count, 2);
2104 }
2105
2106 #[test]
2107 fn test_gts_store_validate_with_nested_refs() {
2108 let mut store = GtsStore::new(None);
2109
2110 let base = json!({
2111 "$id": "gts://gts.vendor.package.namespace.base.v1.0~",
2112 "$schema": "http://json-schema.org/draft-07/schema#",
2113 "type": "object",
2114 "properties": {
2115 "id": {"type": "string"}
2116 }
2117 });
2118
2119 let middle = json!({
2120 "$id": "gts://gts.vendor.package.namespace.middle.v1.0~",
2121 "$schema": "http://json-schema.org/draft-07/schema#",
2122 "allOf": [
2123 {"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"},
2124 {
2125 "type": "object",
2126 "properties": {
2127 "name": {"type": "string"}
2128 }
2129 }
2130 ]
2131 });
2132
2133 let top = json!({
2134 "$id": "gts://gts.vendor.package.namespace.top.v1.0~",
2135 "$schema": "http://json-schema.org/draft-07/schema#",
2136 "allOf": [
2137 {"$ref": "gts://gts.vendor.package.namespace.middle.v1.0~"},
2138 {
2139 "type": "object",
2140 "properties": {
2141 "email": {"type": "string"}
2142 }
2143 }
2144 ]
2145 });
2146
2147 store
2148 .register_schema("gts.vendor.package.namespace.base.v1.0~", &base)
2149 .expect("test");
2150 store
2151 .register_schema("gts.vendor.package.namespace.middle.v1.0~", &middle)
2152 .expect("test");
2153 store
2154 .register_schema("gts.vendor.package.namespace.top.v1.0~", &top)
2155 .expect("test");
2156
2157 let cfg = GtsConfig::default();
2158 let content = json!({
2159 "id": "gts.vendor.package.namespace.top.v1.0",
2160 "name": "test",
2161 "email": "test@example.com"
2162 });
2163
2164 let entity = GtsEntity::new(
2165 None,
2166 None,
2167 &content,
2168 Some(&cfg),
2169 None,
2170 false,
2171 String::new(),
2172 None,
2173 Some("gts.vendor.package.namespace.top.v1.0~".to_owned()),
2174 );
2175
2176 store.register(entity).expect("test");
2177
2178 let result = store.validate_instance("gts.vendor.package.namespace.top.v1.0");
2179 assert!(result.is_ok() || result.is_err());
2180 }
2181
2182 #[test]
2183 fn test_gts_store_query_with_version_wildcard() {
2184 let mut store = GtsStore::new(None);
2185
2186 for i in 0..3 {
2187 let schema = json!({
2188 "$id": format!("gts://gts.vendor.package.namespace.type.v{i}.0~"),
2189 "$schema": "http://json-schema.org/draft-07/schema#",
2190 "type": "object"
2191 });
2192
2193 store
2194 .register_schema(
2195 &format!("gts.vendor.package.namespace.type.v{i}.0~"),
2196 &schema,
2197 )
2198 .expect("test");
2199 }
2200
2201 let result = store.query("gts.vendor.package.namespace.type.*", 10);
2202 assert_eq!(result.count, 3);
2203 }
2204
2205 #[test]
2206 fn test_gts_store_cast_backward_incompatible() {
2207 let mut store = GtsStore::new(None);
2208
2209 let schema_v1 = json!({
2210 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2211 "$schema": "http://json-schema.org/draft-07/schema#",
2212 "type": "object",
2213 "properties": {
2214 "name": {"type": "string"}
2215 }
2216 });
2217
2218 let schema_v2 = json!({
2219 "$id": "gts://gts.vendor.package.namespace.type.v2.0~",
2220 "$schema": "http://json-schema.org/draft-07/schema#",
2221 "type": "object",
2222 "properties": {
2223 "name": {"type": "string"},
2224 "age": {"type": "number"}
2225 },
2226 "required": ["name", "age"]
2227 });
2228
2229 store
2230 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
2231 .expect("test");
2232 store
2233 .register_schema("gts.vendor.package.namespace.type.v2.0~", &schema_v2)
2234 .expect("test");
2235
2236 let cfg = GtsConfig::default();
2237 let content = json!({
2238 "id": "gts.vendor.package.namespace.type.v1.0",
2239 "name": "John"
2240 });
2241
2242 let entity = GtsEntity::new(
2243 None,
2244 None,
2245 &content,
2246 Some(&cfg),
2247 None,
2248 false,
2249 String::new(),
2250 None,
2251 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
2252 );
2253
2254 store.register(entity).expect("test");
2255
2256 let result = store.cast(
2257 "gts.vendor.package.namespace.type.v1.0",
2258 "gts.vendor.package.namespace.type.v2.0~",
2259 );
2260
2261 assert!(result.is_ok() || result.is_err());
2262 }
2263
2264 #[test]
2265 fn test_gts_store_items_iterator_multiple() {
2266 let mut store = GtsStore::new(None);
2267
2268 for i in 0..5 {
2269 let schema = json!({
2270 "$id": format!("gts.vendor.package.namespace.type{i}.v1.0~"),
2271 "$schema": "http://json-schema.org/draft-07/schema#",
2272 "type": "object"
2273 });
2274
2275 store
2276 .register_schema(
2277 &format!("gts.vendor.package.namespace.type{i}.v1.0~"),
2278 &schema,
2279 )
2280 .expect("test");
2281 }
2282
2283 let count = store.items().count();
2284 assert_eq!(count, 5);
2285 }
2286
2287 #[test]
2288 fn test_gts_store_compatibility_fully_compatible() {
2289 let mut store = GtsStore::new(None);
2290
2291 let schema_v1 = json!({
2292 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2293 "$schema": "http://json-schema.org/draft-07/schema#",
2294 "type": "object",
2295 "properties": {
2296 "name": {"type": "string"}
2297 }
2298 });
2299
2300 let schema_v2 = json!({
2301 "$id": "gts://gts.vendor.package.namespace.type.v1.1~",
2302 "$schema": "http://json-schema.org/draft-07/schema#",
2303 "type": "object",
2304 "properties": {
2305 "name": {"type": "string"},
2306 "email": {"type": "string"}
2307 }
2308 });
2309
2310 store
2311 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
2312 .expect("test");
2313 store
2314 .register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
2315 .expect("test");
2316
2317 let result = store.is_minor_compatible(
2318 "gts.vendor.package.namespace.type.v1.0~",
2319 "gts.vendor.package.namespace.type.v1.1~",
2320 );
2321
2322 assert!(result.is_backward_compatible);
2324 }
2325
2326 #[test]
2327 fn test_gts_store_build_schema_graph_complex() {
2328 let mut store = GtsStore::new(None);
2329
2330 let base1 = json!({
2331 "$id": "gts://gts.vendor.package.namespace.base1.v1.0~",
2332 "$schema": "http://json-schema.org/draft-07/schema#",
2333 "type": "object",
2334 "properties": {
2335 "id": {"type": "string"}
2336 }
2337 });
2338
2339 let base2 = json!({
2340 "$id": "gts://gts.vendor.package.namespace.base2.v1.0~",
2341 "$schema": "http://json-schema.org/draft-07/schema#",
2342 "type": "object",
2343 "properties": {
2344 "name": {"type": "string"}
2345 }
2346 });
2347
2348 let combined = json!({
2349 "$id": "gts://gts.vendor.package.namespace.combined.v1.0~",
2350 "$schema": "http://json-schema.org/draft-07/schema#",
2351 "allOf": [
2352 {"$ref": "gts://gts.vendor.package.namespace.base1.v1.0~"},
2353 {"$ref": "gts://gts.vendor.package.namespace.base2.v1.0~"}
2354 ]
2355 });
2356
2357 store
2358 .register_schema("gts.vendor.package.namespace.base1.v1.0~", &base1)
2359 .expect("test");
2360 store
2361 .register_schema("gts.vendor.package.namespace.base2.v1.0~", &base2)
2362 .expect("test");
2363 store
2364 .register_schema("gts.vendor.package.namespace.combined.v1.0~", &combined)
2365 .expect("test");
2366
2367 let graph = store.build_schema_graph("gts.vendor.package.namespace.combined.v1.0~");
2368 assert!(graph.is_object());
2369 }
2370
2371 #[test]
2372 fn test_gts_store_register_invalid_json_entity() {
2373 let mut store = GtsStore::new(None);
2374 let content = json!({"name": "test"});
2375
2376 let entity = GtsEntity::new(
2377 None,
2378 None,
2379 &content,
2380 None,
2381 None,
2382 false,
2383 String::new(),
2384 None,
2385 None,
2386 );
2387
2388 let result = store.register(entity);
2389 assert!(result.is_err());
2390 }
2391
2392 #[test]
2393 fn test_gts_store_validate_with_complex_schema() {
2394 let mut store = GtsStore::new(None);
2395
2396 let schema = json!({
2397 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2398 "$schema": "http://json-schema.org/draft-07/schema#",
2399 "type": "object",
2400 "properties": {
2401 "name": {"type": "string", "minLength": 1, "maxLength": 100},
2402 "age": {"type": "integer", "minimum": 0, "maximum": 150},
2403 "email": {"type": "string", "format": "email"},
2404 "tags": {
2405 "type": "array",
2406 "items": {"type": "string"},
2407 "minItems": 1
2408 }
2409 },
2410 "required": ["name", "age"]
2411 });
2412
2413 store
2414 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2415 .expect("test");
2416
2417 let cfg = GtsConfig::default();
2418 let content = json!({
2419 "id": "gts.vendor.package.namespace.type.v1.0",
2420 "name": "John Doe",
2421 "age": 30,
2422 "email": "john@example.com",
2423 "tags": ["developer", "rust"]
2424 });
2425
2426 let entity = GtsEntity::new(
2427 None,
2428 None,
2429 &content,
2430 Some(&cfg),
2431 None,
2432 false,
2433 String::new(),
2434 None,
2435 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
2436 );
2437
2438 store.register(entity).expect("test");
2439
2440 let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
2441 assert!(result.is_ok() || result.is_err());
2443 }
2444
2445 #[test]
2446 fn test_gts_store_validate_missing_required_field() {
2447 let mut store = GtsStore::new(None);
2448
2449 let schema = json!({
2450 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2451 "$schema": "http://json-schema.org/draft-07/schema#",
2452 "type": "object",
2453 "properties": {
2454 "name": {"type": "string"}
2455 },
2456 "required": ["name"]
2457 });
2458
2459 store
2460 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2461 .expect("test");
2462
2463 let cfg = GtsConfig::default();
2464 let content = json!({
2465 "id": "gts.vendor.package.namespace.type.v1.0"
2466 });
2467
2468 let entity = GtsEntity::new(
2469 None,
2470 None,
2471 &content,
2472 Some(&cfg),
2473 None,
2474 false,
2475 String::new(),
2476 None,
2477 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
2478 );
2479
2480 store.register(entity).expect("test");
2481
2482 let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
2483 assert!(result.is_err());
2484 }
2485
2486 #[test]
2487 fn test_gts_store_schema_with_properties_only() {
2488 let mut store = GtsStore::new(None);
2489
2490 let schema = json!({
2491 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2492 "$schema": "http://json-schema.org/draft-07/schema#",
2493 "properties": {
2494 "name": {"type": "string"}
2495 }
2496 });
2497
2498 let result = store.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema);
2499 assert!(result.is_ok());
2500 }
2501
2502 #[test]
2503 fn test_gts_store_query_no_results() {
2504 let store = GtsStore::new(None);
2505 let result = store.query("gts.nonexistent.*", 10);
2506 assert_eq!(result.count, 0);
2507 assert!(result.results.is_empty());
2508 }
2509
2510 #[test]
2511 fn test_gts_store_query_with_zero_limit() {
2512 let mut store = GtsStore::new(None);
2513
2514 let schema = json!({
2515 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2516 "$schema": "http://json-schema.org/draft-07/schema#",
2517 "type": "object"
2518 });
2519
2520 store
2521 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2522 .expect("test");
2523
2524 let result = store.query("gts.vendor.*", 0);
2525 assert_eq!(result.results.len(), 0);
2526 }
2527
2528 #[test]
2529 fn test_gts_store_cast_same_version() {
2530 let mut store = GtsStore::new(None);
2531
2532 let schema = json!({
2533 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2534 "$schema": "http://json-schema.org/draft-07/schema#",
2535 "type": "object",
2536 "properties": {
2537 "name": {"type": "string"}
2538 }
2539 });
2540
2541 store
2542 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2543 .expect("test");
2544
2545 let cfg = GtsConfig::default();
2546 let content = json!({
2547 "id": "gts.vendor.package.namespace.type.v1.0",
2548 "name": "test"
2549 });
2550
2551 let entity = GtsEntity::new(
2552 None,
2553 None,
2554 &content,
2555 Some(&cfg),
2556 None,
2557 false,
2558 String::new(),
2559 None,
2560 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
2561 );
2562
2563 store.register(entity).expect("test");
2564
2565 let result = store.cast(
2566 "gts.vendor.package.namespace.type.v1.0",
2567 "gts.vendor.package.namespace.type.v1.0~",
2568 );
2569 assert!(result.is_ok() || result.is_err());
2570 }
2571
2572 #[test]
2573 fn test_gts_store_multiple_entities_same_schema() {
2574 let mut store = GtsStore::new(None);
2575
2576 let schema = json!({
2577 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2578 "$schema": "http://json-schema.org/draft-07/schema#",
2579 "type": "object",
2580 "properties": {
2581 "name": {"type": "string"}
2582 }
2583 });
2584
2585 store
2586 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2587 .expect("test");
2588
2589 let cfg = GtsConfig::default();
2590
2591 for i in 0..5 {
2592 let content = json!({
2593 "id": format!("gts.vendor.package.namespace.instance{i}.v1.0"),
2594 "name": format!("test{i}")
2595 });
2596
2597 let entity = GtsEntity::new(
2598 None,
2599 None,
2600 &content,
2601 Some(&cfg),
2602 None,
2603 false,
2604 String::new(),
2605 None,
2606 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
2607 );
2608
2609 store.register(entity).expect("test");
2610 }
2611
2612 let count = store.items().count();
2613 assert!(count >= 5); }
2615
2616 #[test]
2617 fn test_gts_store_get_schema_content_for_entity() {
2618 let mut store = GtsStore::new(None);
2619
2620 let schema = json!({
2621 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2622 "$schema": "http://json-schema.org/draft-07/schema#",
2623 "type": "object",
2624 "properties": {
2625 "name": {"type": "string"}
2626 }
2627 });
2628
2629 store
2630 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2631 .expect("test");
2632
2633 let result = store.get_schema_content("gts.vendor.package.namespace.type.v1.0~");
2634 assert!(result.is_ok());
2635
2636 let retrieved = result.expect("test");
2637 assert_eq!(
2638 retrieved.get("type").expect("test").as_str().expect("test"),
2639 "object"
2640 );
2641 }
2642
2643 #[test]
2644 fn test_gts_store_compatibility_with_removed_properties() {
2645 let mut store = GtsStore::new(None);
2646
2647 let schema_v1 = json!({
2648 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2649 "$schema": "http://json-schema.org/draft-07/schema#",
2650 "type": "object",
2651 "properties": {
2652 "name": {"type": "string"},
2653 "age": {"type": "number"},
2654 "email": {"type": "string"}
2655 }
2656 });
2657
2658 let schema_v2 = json!({
2659 "$id": "gts://gts.vendor.package.namespace.type.v1.1~",
2660 "$schema": "http://json-schema.org/draft-07/schema#",
2661 "type": "object",
2662 "properties": {
2663 "name": {"type": "string"},
2664 "age": {"type": "number"}
2665 }
2666 });
2667
2668 store
2669 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
2670 .expect("test");
2671 store
2672 .register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
2673 .expect("test");
2674
2675 let result = store.is_minor_compatible(
2676 "gts.vendor.package.namespace.type.v1.0~",
2677 "gts.vendor.package.namespace.type.v1.1~",
2678 );
2679
2680 assert!(result.is_forward_compatible);
2682 }
2683
2684 #[test]
2685 fn test_gts_store_build_schema_graph_single_schema() {
2686 let mut store = GtsStore::new(None);
2687
2688 let schema = json!({
2689 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2690 "$schema": "http://json-schema.org/draft-07/schema#",
2691 "type": "object",
2692 "properties": {
2693 "name": {"type": "string"}
2694 }
2695 });
2696
2697 store
2698 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2699 .expect("test");
2700
2701 let graph = store.build_schema_graph("gts.vendor.package.namespace.type.v1.0~");
2702 assert!(graph.is_object());
2703 }
2704
2705 #[test]
2706 fn test_gts_store_register_schema_without_id() {
2707 let mut store = GtsStore::new(None);
2708
2709 let schema = json!({
2710 "$schema": "http://json-schema.org/draft-07/schema#",
2711 "type": "object"
2712 });
2713
2714 let result = store.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema);
2715 assert!(result.is_ok());
2716 }
2717
2718 #[test]
2719 fn test_gts_store_validate_with_unresolvable_ref() {
2720 let mut store = GtsStore::new(None);
2721
2722 let schema = json!({
2723 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2724 "$schema": "http://json-schema.org/draft-07/schema#",
2725 "allOf": [
2726 {"$ref": "gts://gts.vendor.package.namespace.nonexistent.v1.0~"}
2727 ]
2728 });
2729
2730 store
2731 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2732 .expect("test");
2733
2734 let cfg = GtsConfig::default();
2735 let content = json!({
2736 "id": "gts.vendor.package.namespace.type.v1.0",
2737 "name": "test"
2738 });
2739
2740 let entity = GtsEntity::new(
2741 None,
2742 None,
2743 &content,
2744 Some(&cfg),
2745 None,
2746 false,
2747 String::new(),
2748 None,
2749 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
2750 );
2751
2752 store.register(entity).expect("test");
2753
2754 let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
2755 assert!(result.is_ok() || result.is_err());
2757 }
2758
2759 #[test]
2760 fn test_gts_store_query_result_serialization_with_error() {
2761 let result = GtsStoreQueryResult {
2762 error: "Test error message".to_owned(),
2763 count: 0,
2764 limit: 10,
2765 results: vec![],
2766 };
2767
2768 let json_value = serde_json::to_value(&result).expect("test");
2769 let json = json_value.as_object().expect("test");
2770 assert_eq!(
2771 json.get("error").expect("test").as_str().expect("test"),
2772 "Test error message"
2773 );
2774 assert_eq!(json.get("count").expect("test").as_u64().expect("test"), 0);
2775 }
2776
2777 #[test]
2778 fn test_gts_store_resolve_schema_refs_with_merge() {
2779 let mut store = GtsStore::new(None);
2780
2781 let base_schema = json!({
2783 "$id": "gts://gts.vendor.package.namespace.base.v1.0~",
2784 "$schema": "http://json-schema.org/draft-07/schema#",
2785 "type": "object",
2786 "properties": {
2787 "id": {"type": "string"}
2788 }
2789 });
2790
2791 let schema = json!({
2793 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2794 "$schema": "http://json-schema.org/draft-07/schema#",
2795 "allOf": [
2796 {
2797 "$ref": "gts://gts.vendor.package.namespace.base.v1.0~",
2798 "properties": {
2799 "name": {"type": "string"}
2800 }
2801 }
2802 ]
2803 });
2804
2805 store
2806 .register_schema("gts.vendor.package.namespace.base.v1.0~", &base_schema)
2807 .expect("test");
2808 store
2809 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2810 .expect("test");
2811
2812 let cfg = GtsConfig::default();
2813 let content = json!({
2814 "id": "gts.vendor.package.namespace.type.v1.0",
2815 "name": "test"
2816 });
2817
2818 let entity = GtsEntity::new(
2819 None,
2820 None,
2821 &content,
2822 Some(&cfg),
2823 None,
2824 false,
2825 String::new(),
2826 None,
2827 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
2828 );
2829
2830 store.register(entity).expect("test");
2831
2832 let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
2833 assert!(result.is_ok() || result.is_err());
2834 }
2835
2836 #[test]
2837 fn test_gts_store_resolve_schema_refs_with_unresolvable_and_properties() {
2838 let mut store = GtsStore::new(None);
2839
2840 let schema = json!({
2842 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2843 "$schema": "http://json-schema.org/draft-07/schema#",
2844 "properties": {
2845 "data": {
2846 "$ref": "gts://gts.vendor.package.namespace.nonexistent.v1.0~",
2847 "type": "object"
2848 }
2849 }
2850 });
2851
2852 store
2853 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2854 .expect("test");
2855
2856 let cfg = GtsConfig::default();
2857 let content = json!({
2858 "id": "gts.vendor.package.namespace.type.v1.0",
2859 "data": {}
2860 });
2861
2862 let entity = GtsEntity::new(
2863 None,
2864 None,
2865 &content,
2866 Some(&cfg),
2867 None,
2868 false,
2869 String::new(),
2870 None,
2871 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
2872 );
2873
2874 store.register(entity).expect("test");
2875
2876 let result = store.validate_instance("gts.vendor.package.namespace.type.v1.0");
2877 assert!(result.is_ok() || result.is_err());
2878 }
2879
2880 #[test]
2881 fn test_gts_store_cast_from_schema_entity() {
2882 let mut store = GtsStore::new(None);
2883
2884 let schema_v1 = json!({
2886 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2887 "$schema": "http://json-schema.org/draft-07/schema#",
2888 "type": "object",
2889 "properties": {
2890 "name": {"type": "string"}
2891 }
2892 });
2893
2894 let schema_v2 = json!({
2895 "$id": "gts://gts.vendor.package.namespace.type.v1.1~",
2896 "$schema": "http://json-schema.org/draft-07/schema#",
2897 "type": "object",
2898 "properties": {
2899 "name": {"type": "string"},
2900 "email": {"type": "string"}
2901 }
2902 });
2903
2904 store
2905 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema_v1)
2906 .expect("test");
2907 store
2908 .register_schema("gts.vendor.package.namespace.type.v1.1~", &schema_v2)
2909 .expect("test");
2910
2911 let result = store.cast(
2913 "gts.vendor.package.namespace.type.v1.0~",
2914 "gts.vendor.package.namespace.type.v1.1~",
2915 );
2916
2917 assert!(result.is_ok() || result.is_err());
2918 }
2919
2920 #[test]
2921 fn test_gts_store_build_schema_graph_with_schema_id() {
2922 let mut store = GtsStore::new(None);
2923
2924 let schema = json!({
2926 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
2927 "$schema": "http://json-schema.org/draft-07/schema#",
2928 "type": "object",
2929 "properties": {
2930 "name": {"type": "string"}
2931 }
2932 });
2933
2934 store
2935 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
2936 .expect("test");
2937
2938 let cfg = GtsConfig::default();
2940 let content = json!({
2941 "id": "gts.vendor.package.namespace.instance.v1.0",
2942 "name": "test"
2943 });
2944
2945 let entity = GtsEntity::new(
2946 None,
2947 None,
2948 &content,
2949 Some(&cfg),
2950 None,
2951 false,
2952 String::new(),
2953 None,
2954 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
2955 );
2956
2957 store.register(entity).expect("test");
2958
2959 let graph = store.build_schema_graph("gts.vendor.package.namespace.instance.v1.0");
2960 assert!(graph.is_object());
2961
2962 let graph_obj = graph.as_object().expect("test");
2964 assert!(graph_obj.contains_key("schema_id") || graph_obj.contains_key("errors"));
2965 }
2966
2967 #[test]
2968 fn test_gts_store_query_with_filter_brackets() {
2969 let mut store = GtsStore::new(None);
2970
2971 let cfg = GtsConfig::default();
2973 for i in 0..3 {
2974 let content = json!({
2975 "id": format!("gts.vendor.package.namespace.item{i}.v1.0~abc.app.custom.item{i}.v1.0"),
2976 "name": format!("item{i}"),
2977 "status": if i % 2 == 0 { "active" } else { "inactive" }
2978 });
2979
2980 let entity = GtsEntity::new(
2981 None,
2982 None,
2983 &content,
2984 Some(&cfg),
2985 None,
2986 false,
2987 String::new(),
2988 None,
2989 None,
2990 );
2991
2992 store.register(entity).expect("test");
2993 }
2994
2995 let result = store.query("gts.vendor.*[status=active]", 10);
2997 assert!(result.count >= 1);
2998 }
2999
3000 #[test]
3001 fn test_gts_store_query_with_wildcard_filter() {
3002 let mut store = GtsStore::new(None);
3003
3004 let cfg = GtsConfig::default();
3005 for i in 0..3 {
3006 let content = if i == 0 {
3007 json!({
3008 "id": format!("gts.vendor.package.namespace.items.v1.0~a.b._.{i}.v1"),
3009 "name": format!("item{i}"),
3010 "category": null
3011 })
3012 } else {
3013 json!({
3014 "id": format!("gts.vendor.package.namespace.items.v1.0~c.d.e.{i}.v1"),
3015 "name": format!("item{i}"),
3016 "category": format!("cat{i}")
3017 })
3018 };
3019
3020 let entity = GtsEntity::new(
3021 None,
3022 None,
3023 &content,
3024 Some(&cfg),
3025 None,
3026 false,
3027 String::new(),
3028 None,
3029 None,
3030 );
3031
3032 store.register(entity).expect("test");
3033 }
3034
3035 let mut all_entities = Vec::new();
3037 for i in 0..3 {
3038 let id1 = format!("gts.vendor.package.namespace.items.v1.0~a.b._.{i}.v1");
3039 let id2 = format!("gts.vendor.package.namespace.items.v1.0~c.d.e.{i}.v1");
3040 if let Some(entity) = store.get(&id1) {
3041 all_entities.push((id1, entity.content.get("category").cloned()));
3042 }
3043 if i > 0 {
3044 if let Some(entity) = store.get(&id2) {
3045 all_entities.push((id2, entity.content.get("category").cloned()));
3046 }
3047 }
3048 }
3049
3050 let non_null_count = all_entities
3055 .iter()
3056 .filter(|(_, cat)| cat.is_some() && cat.as_ref().unwrap() != &serde_json::Value::Null)
3057 .count();
3058
3059 assert_eq!(non_null_count, 2);
3062 }
3064
3065 #[test]
3066 fn test_gts_store_query_invalid_wildcard_pattern() {
3067 let store = GtsStore::new(None);
3068
3069 let result = store.query("gts.vendor*", 10);
3071 assert!(!result.error.is_empty());
3072 assert!(result.error.contains("wildcard"));
3073 }
3074
3075 #[test]
3076 fn test_gts_store_query_invalid_gts_id() {
3077 let store = GtsStore::new(None);
3078
3079 let result = store.query("invalid-id", 10);
3081 assert!(!result.error.is_empty());
3082 }
3083
3084 #[test]
3085 fn test_gts_store_query_gts_id_no_segments() {
3086 let store = GtsStore::new(None);
3087
3088 let result = store.query("gts", 10);
3090 assert!(!result.error.is_empty());
3091 }
3092
3093 #[test]
3094 fn test_gts_store_validate_instance_invalid_gts_id() {
3095 let mut store = GtsStore::new(None);
3096
3097 let result = store.validate_instance("invalid-id");
3099 assert!(result.is_err());
3100 }
3101
3102 #[test]
3103 fn test_gts_store_validate_instance_invalid_schema() {
3104 let mut store = GtsStore::new(None);
3105
3106 let schema = json!({
3108 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
3109 "$schema": "http://json-schema.org/draft-07/schema#",
3110 "type": "invalid_type"
3111 });
3112
3113 store
3114 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
3115 .expect("test");
3116
3117 let cfg = GtsConfig::default();
3118 let content = json!({
3119 "id": "gts.vendor.package.namespace.instance.v1.0",
3120 "name": "test"
3121 });
3122
3123 let entity = GtsEntity::new(
3124 None,
3125 None,
3126 &content,
3127 Some(&cfg),
3128 None,
3129 false,
3130 String::new(),
3131 None,
3132 Some("gts.vendor.package.namespace.type.v1.0~".to_owned()),
3133 );
3134
3135 store.register(entity).expect("test");
3136
3137 let result = store.validate_instance("gts.vendor.package.namespace.instance.v1.0");
3138 assert!(result.is_err());
3139 }
3140
3141 struct MockGtsReader {
3143 entities: Vec<GtsEntity>,
3144 index: usize,
3145 }
3146
3147 impl MockGtsReader {
3148 fn new(entities: Vec<GtsEntity>) -> Self {
3149 MockGtsReader { entities, index: 0 }
3150 }
3151 }
3152
3153 impl GtsReader for MockGtsReader {
3154 fn iter(&mut self) -> Box<dyn Iterator<Item = GtsEntity> + '_> {
3155 Box::new(self.entities.clone().into_iter())
3156 }
3157
3158 fn read_by_id(&self, entity_id: &str) -> Option<GtsEntity> {
3159 self.entities
3160 .iter()
3161 .find(|e| e.gts_id.as_ref().map(|id| id.id.as_str()) == Some(entity_id))
3162 .cloned()
3163 }
3164
3165 fn reset(&mut self) {
3166 self.index = 0;
3167 }
3168 }
3169
3170 #[test]
3171 fn test_gts_store_with_reader() {
3172 let cfg = GtsConfig::default();
3173
3174 let mut entities = Vec::new();
3176 for i in 0..3 {
3177 let content = json!({
3178 "id": format!("gts.vendor.package.namespace.item{i}.v1.0"),
3179 "name": format!("item{i}")
3180 });
3181
3182 let entity = GtsEntity::new(
3183 None,
3184 None,
3185 &content,
3186 Some(&cfg),
3187 None,
3188 false,
3189 String::new(),
3190 None,
3191 None,
3192 );
3193
3194 entities.push(entity);
3195 }
3196
3197 let reader = MockGtsReader::new(entities);
3198 let store = GtsStore::new(Some(Box::new(reader)));
3199
3200 assert_eq!(store.items().count(), 3);
3202 }
3203
3204 #[test]
3205 fn test_gts_store_get_from_reader() {
3206 let cfg = GtsConfig::default();
3207
3208 let content = json!({
3210 "id": "gts.vendor.package.namespace.item.v1.0",
3211 "name": "test"
3212 });
3213
3214 let entity = GtsEntity::new(
3215 None,
3216 None,
3217 &content,
3218 Some(&cfg),
3219 None,
3220 false,
3221 String::new(),
3222 None,
3223 None,
3224 );
3225
3226 let reader = MockGtsReader::new(vec![entity]);
3227 let mut store = GtsStore::new(Some(Box::new(reader)));
3228
3229 let result = store.get("gts.vendor.package.namespace.item.v1.0");
3231 assert!(result.is_some());
3232 }
3233
3234 #[test]
3235 fn test_gts_store_reader_without_gts_id() {
3236 let content = json!({
3238 "name": "test"
3239 });
3240
3241 let entity = GtsEntity::new(
3242 None,
3243 None,
3244 &content,
3245 None,
3246 None,
3247 false,
3248 String::new(),
3249 None,
3250 None,
3251 );
3252
3253 let reader = MockGtsReader::new(vec![entity]);
3254 let store = GtsStore::new(Some(Box::new(reader)));
3255
3256 assert_eq!(store.items().count(), 0);
3258 }
3259
3260 #[test]
3261 fn test_validate_schema_refs_valid_gts_uri() {
3262 let schema = json!({
3264 "$ref": "gts://gts.vendor.package.namespace.type.v1.0~"
3265 });
3266 let result = GtsStore::validate_schema_refs(&schema, "");
3267 assert!(result.is_ok());
3268 }
3269
3270 #[test]
3271 fn test_validate_schema_refs_valid_local_ref() {
3272 let schema = json!({
3274 "$ref": "#/definitions/MyType"
3275 });
3276 let result = GtsStore::validate_schema_refs(&schema, "");
3277 assert!(result.is_ok());
3278 }
3279
3280 #[test]
3281 fn test_validate_schema_refs_invalid_bare_gts_id() {
3282 let schema = json!({
3284 "$ref": "gts.vendor.package.namespace.type.v1.0~"
3285 });
3286 let result = GtsStore::validate_schema_refs(&schema, "");
3287 assert!(result.is_err());
3288 let err = result.unwrap_err().to_string();
3289 assert!(err.contains("must be a local ref"));
3290 assert!(err.contains("gts://"));
3291 }
3292
3293 #[test]
3294 fn test_validate_schema_refs_invalid_http_uri() {
3295 let schema = json!({
3297 "$ref": "https://example.com/schema.json"
3298 });
3299 let result = GtsStore::validate_schema_refs(&schema, "");
3300 assert!(result.is_err());
3301 let err = result.unwrap_err().to_string();
3302 assert!(err.contains("must be a local ref"));
3303 }
3304
3305 #[test]
3306 fn test_validate_schema_refs_invalid_gts_id_in_uri() {
3307 let schema = json!({
3309 "$ref": "gts://invalid-gts-id"
3310 });
3311 let result = GtsStore::validate_schema_refs(&schema, "");
3312 assert!(result.is_err());
3313 let err = result.unwrap_err().to_string();
3314 assert!(err.contains("invalid GTS identifier"));
3315 }
3316
3317 #[test]
3318 fn test_validate_schema_refs_nested() {
3319 let schema = json!({
3321 "properties": {
3322 "user": {
3323 "$ref": "gts://gts.vendor.package.namespace.user.v1.0~"
3324 },
3325 "order": {
3326 "$ref": "invalid-ref"
3327 }
3328 }
3329 });
3330 let result = GtsStore::validate_schema_refs(&schema, "");
3331 assert!(result.is_err());
3332 let err = result.unwrap_err().to_string();
3333 assert!(err.contains("properties.order.$ref"));
3334 }
3335
3336 #[test]
3337 fn test_validate_schema_refs_in_array() {
3338 let schema = json!({
3340 "allOf": [
3341 {"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"},
3342 {"$ref": "not-valid-ref"}
3343 ]
3344 });
3345 let result = GtsStore::validate_schema_refs(&schema, "");
3346 assert!(result.is_err());
3347 let err = result.unwrap_err().to_string();
3348 assert!(err.contains("allOf[1].$ref"));
3349 }
3350
3351 #[test]
3352 fn test_validate_schema_integration() {
3353 let mut store = GtsStore::new(None);
3354
3355 let schema = json!({
3357 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
3358 "$schema": "http://json-schema.org/draft-07/schema#",
3359 "allOf": [
3360 {"$ref": "gts.vendor.package.namespace.base.v1.0~"}
3361 ]
3362 });
3363
3364 let result = store.register_schema("gts.vendor.package.namespace.type.v1.0~", &schema);
3365 assert!(result.is_ok()); let validation_result = store.validate_schema("gts.vendor.package.namespace.type.v1.0~");
3369 assert!(validation_result.is_err());
3370 let err = validation_result.unwrap_err().to_string();
3371 assert!(err.contains("must be a local ref") || err.contains("gts://"));
3372 }
3373
3374 #[test]
3375 fn test_resolve_schema_refs_with_gts_uri_prefix() {
3376 let mut store = GtsStore::new(None);
3377
3378 let base_schema = json!({
3380 "$id": "gts://gts.vendor.package.namespace.base.v1.0~",
3381 "$schema": "http://json-schema.org/draft-07/schema#",
3382 "type": "object",
3383 "properties": {
3384 "id": {"type": "string"}
3385 }
3386 });
3387
3388 let schema = json!({
3390 "$id": "gts://gts.vendor.package.namespace.type.v1.0~",
3391 "$schema": "http://json-schema.org/draft-07/schema#",
3392 "allOf": [
3393 {"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"}
3394 ]
3395 });
3396
3397 store
3398 .register_schema("gts.vendor.package.namespace.base.v1.0~", &base_schema)
3399 .expect("test");
3400 store
3401 .register_schema("gts.vendor.package.namespace.type.v1.0~", &schema)
3402 .expect("test");
3403
3404 let cfg = GtsConfig::default();
3406 let content = json!({
3407 "id": "gts.vendor.package.namespace.type.v1.0~instance.v1.0",
3408 "type": "gts.vendor.package.namespace.type.v1.0~"
3409 });
3410
3411 let entity = GtsEntity::new(
3412 None,
3413 None,
3414 &content,
3415 Some(&cfg),
3416 None,
3417 false,
3418 String::new(),
3419 None,
3420 None,
3421 );
3422
3423 store.register(entity).expect("test");
3424
3425 let result =
3427 store.validate_instance("gts.vendor.package.namespace.type.v1.0~instance.v1.0");
3428 let _ = result;
3431 }
3432
3433 #[test]
3438 fn test_validate_schema_refs_rejects_external_ref_without_gts_prefix() {
3439 let schema = json!({
3441 "$ref": "http://example.com/schema.json"
3442 });
3443 let result = GtsStore::validate_schema_refs(&schema, "");
3444 assert!(result.is_err());
3445 let err = result.unwrap_err().to_string();
3446 assert!(
3447 err.contains("must be a local ref") || err.contains("GTS URI"),
3448 "Error should mention local ref or GTS URI requirement"
3449 );
3450 }
3451
3452 #[test]
3453 fn test_validate_schema_refs_rejects_malformed_gts_id_in_ref() {
3454 let schema = json!({
3456 "$ref": "gts://invalid-gts-id"
3457 });
3458 let result = GtsStore::validate_schema_refs(&schema, "");
3459 assert!(result.is_err());
3460 let err = result.unwrap_err().to_string();
3461 assert!(
3462 err.contains("invalid GTS identifier") || err.contains("contains invalid"),
3463 "Error should mention invalid GTS identifier"
3464 );
3465 }
3466
3467 #[test]
3468 fn test_validate_schema_refs_accepts_valid_gts_ref() {
3469 let schema = json!({
3471 "$ref": "gts://gts.vendor.package.namespace.type.v1.0~"
3472 });
3473 let result = GtsStore::validate_schema_refs(&schema, "");
3474 assert!(result.is_ok(), "Valid gts:// ref should be accepted");
3475 }
3476
3477 #[test]
3478 fn test_validate_schema_refs_accepts_local_json_pointer() {
3479 let schema = json!({
3481 "$ref": "#/definitions/Base"
3482 });
3483 let result = GtsStore::validate_schema_refs(&schema, "");
3484 assert!(result.is_ok(), "Local JSON Pointer ref should be accepted");
3485 }
3486
3487 #[test]
3488 fn test_validate_schema_refs_accepts_root_json_pointer() {
3489 let schema = json!({
3491 "$ref": "#"
3492 });
3493 let result = GtsStore::validate_schema_refs(&schema, "");
3494 assert!(result.is_ok(), "Root JSON Pointer ref should be accepted");
3495 }
3496
3497 #[test]
3498 fn test_validate_schema_refs_rejects_gts_colon_without_slashes() {
3499 let schema = json!({
3501 "$ref": "gts:gts.vendor.package.namespace.type.v1.0~"
3502 });
3503 let result = GtsStore::validate_schema_refs(&schema, "");
3504 assert!(result.is_err());
3505 let err = result.unwrap_err().to_string();
3506 assert!(
3507 err.contains("must be a local ref") || err.contains("GTS URI"),
3508 "Error should mention local ref or GTS URI requirement"
3509 );
3510 }
3511
3512 #[test]
3513 fn test_validate_schema_refs_deeply_nested_invalid_ref() {
3514 let schema = json!({
3516 "properties": {
3517 "level1": {
3518 "properties": {
3519 "level2": {
3520 "properties": {
3521 "level3": {
3522 "$ref": "invalid-external-ref"
3523 }
3524 }
3525 }
3526 }
3527 }
3528 }
3529 });
3530 let result = GtsStore::validate_schema_refs(&schema, "");
3531 assert!(result.is_err());
3532 let err = result.unwrap_err().to_string();
3533 assert!(
3534 err.contains("properties.level1.properties.level2.properties.level3.$ref"),
3535 "Error should report the correct nested path"
3536 );
3537 }
3538
3539 #[test]
3540 fn test_validate_schema_refs_mixed_valid_and_invalid() {
3541 let schema = json!({
3543 "allOf": [
3544 {"$ref": "gts://gts.vendor.package.namespace.base.v1.0~"},
3545 {"$ref": "#/definitions/Local"},
3546 {"$ref": "invalid-ref"}
3547 ]
3548 });
3549 let result = GtsStore::validate_schema_refs(&schema, "");
3550 assert!(result.is_err(), "Should fail when any ref is invalid");
3551 let err = result.unwrap_err().to_string();
3552 assert!(
3553 err.contains("allOf[2].$ref"),
3554 "Should report the invalid ref path"
3555 );
3556 }
3557
3558 #[test]
3559 fn test_validate_schema_refs_empty_string() {
3560 let schema = json!({
3562 "$ref": ""
3563 });
3564 let result = GtsStore::validate_schema_refs(&schema, "");
3565 assert!(result.is_err());
3566 let err = result.unwrap_err().to_string();
3567 assert!(
3568 err.contains("must be a local ref") || err.contains("GTS URI"),
3569 "Error should mention local ref or GTS URI requirement"
3570 );
3571 }
3572
3573 #[test]
3574 fn test_validate_schema_refs_gts_prefix_but_empty_id() {
3575 let schema = json!({
3577 "$ref": "gts://"
3578 });
3579 let result = GtsStore::validate_schema_refs(&schema, "");
3580 assert!(result.is_err());
3581 let err = result.unwrap_err().to_string();
3582 assert!(
3583 err.contains("invalid GTS identifier") || err.contains("contains invalid"),
3584 "Error should mention invalid GTS identifier"
3585 );
3586 }
3587}