···6060 return Err("Reference cannot be empty".to_string());
6161 }
62626363- if ref_str.starts_with('#') {
6363+ if let Some(def_name) = ref_str.strip_prefix('#') {
6464 // Local reference
6565- let def_name = &ref_str[1..];
6665 if def_name.is_empty() {
6766 return Err("Local reference must have a definition name after #".to_string());
6867 }
···105104 // First validate syntax
106105 Self::validate_ref_syntax(ref_str)?;
107106108108- if ref_str.starts_with('#') {
107107+ if let Some(def_name) = ref_str.strip_prefix('#') {
109108 // Local reference
110110- let def_name = ref_str[1..].to_string();
109109+ let def_name = def_name.to_string();
111110 Ok(ParsedReference::Local(def_name))
112111 } else if ref_str.contains('#') {
113112 // Global reference with fragment
···358357 ))
359358 })?;
360359361361- // TODO: Validate that the reference can be resolved
362362- // This would require access to the reference resolution system
360360+ // Parse and validate that the reference can be resolved
361361+ let parsed_ref = Self::parse_reference(ref_str).map_err(|e| {
362362+ ValidationError::InvalidSchema(format!(
363363+ "Ref '{}' has invalid reference: {}",
364364+ def_name, e
365365+ ))
366366+ })?;
367367+368368+ // Attempt to resolve the reference to verify it exists
369369+ // Ensure we have a current lexicon context for resolving local references
370370+ ctx.current_lexicon_id()
371371+ .ok_or_else(|| {
372372+ ValidationError::InvalidSchema(format!(
373373+ "Cannot validate ref '{}': no current lexicon context",
374374+ def_name
375375+ ))
376376+ })?;
363377364364- Ok(())
378378+ match Self::resolve_reference(&parsed_ref, ctx) {
379379+ Ok(_) => Ok(()), // Reference resolved successfully
380380+ Err(ValidationError::DataValidation(msg)) if msg.contains("not found") => {
381381+ // Convert data validation error to schema validation error for definition validation
382382+ Err(ValidationError::InvalidSchema(format!(
383383+ "Ref '{}' references non-existent definition: {}",
384384+ def_name, ref_str
385385+ )))
386386+ }
387387+ Err(e) => Err(e), // Pass through other errors
388388+ }
365389 }
366390367391 /// Validates runtime data against a reference schema definition
···456480 .with_lexicons(vec![json!({
457481 "lexicon": 1,
458482 "id": "com.example.test",
459459- "defs": { "main": ref_def.clone() }
483483+ "defs": {
484484+ "main": ref_def.clone(),
485485+ "commonObject": {
486486+ "type": "object",
487487+ "properties": {}
488488+ }
489489+ }
460490 })])
461491 .unwrap()
462492 .build()
463463- .unwrap();
493493+ .unwrap()
494494+ .with_current_lexicon("com.example.test");
464495465496 let validator = RefValidator;
466497 assert!(validator.validate(&ref_def, &ctx).is_ok());
···474505 });
475506476507 let ctx = ValidationContext::builder()
477477- .with_lexicons(vec![json!({
478478- "lexicon": 1,
479479- "id": "com.example.test",
480480- "defs": { "main": ref_def.clone() }
481481- })])
508508+ .with_lexicons(vec![
509509+ json!({
510510+ "lexicon": 1,
511511+ "id": "com.example.test",
512512+ "defs": { "main": ref_def.clone() }
513513+ }),
514514+ json!({
515515+ "lexicon": 1,
516516+ "id": "com.example.defs",
517517+ "defs": {
518518+ "main": { "type": "object" },
519519+ "commonObject": {
520520+ "type": "object",
521521+ "properties": {}
522522+ }
523523+ }
524524+ })
525525+ ])
482526 .unwrap()
483527 .build()
484484- .unwrap();
528528+ .unwrap()
529529+ .with_current_lexicon("com.example.test");
485530486531 let validator = RefValidator;
487532 assert!(validator.validate(&ref_def, &ctx).is_ok());
···495540 });
496541497542 let ctx = ValidationContext::builder()
543543+ .with_lexicons(vec![
544544+ json!({
545545+ "lexicon": 1,
546546+ "id": "com.example.test",
547547+ "defs": { "main": ref_def.clone() }
548548+ }),
549549+ json!({
550550+ "lexicon": 1,
551551+ "id": "com.example.record",
552552+ "defs": {
553553+ "main": {
554554+ "type": "record",
555555+ "record": {
556556+ "type": "object",
557557+ "properties": {}
558558+ }
559559+ }
560560+ }
561561+ })
562562+ ])
563563+ .unwrap()
564564+ .build()
565565+ .unwrap()
566566+ .with_current_lexicon("com.example.test");
567567+568568+ let validator = RefValidator;
569569+ assert!(validator.validate(&ref_def, &ctx).is_ok());
570570+ }
571571+572572+ #[test]
573573+ fn test_missing_ref_field() {
574574+ let ref_def = json!({
575575+ "type": "ref"
576576+ });
577577+578578+ let ctx = ValidationContext::builder()
498579 .with_lexicons(vec![json!({
499580 "lexicon": 1,
500581 "id": "com.example.test",
···502583 })])
503584 .unwrap()
504585 .build()
505505- .unwrap();
586586+ .unwrap()
587587+ .with_current_lexicon("com.example.test");
506588507589 let validator = RefValidator;
508508- assert!(validator.validate(&ref_def, &ctx).is_ok());
590590+ assert!(validator.validate(&ref_def, &ctx).is_err());
509591 }
510592511593 #[test]
512512- fn test_missing_ref_field() {
594594+ fn test_ref_to_nonexistent_definition() {
513595 let ref_def = json!({
514514- "type": "ref"
596596+ "type": "ref",
597597+ "ref": "#nonExistentDef"
515598 });
516599517600 let ctx = ValidationContext::builder()
···522605 })])
523606 .unwrap()
524607 .build()
525525- .unwrap();
608608+ .unwrap()
609609+ .with_current_lexicon("com.example.test");
526610527611 let validator = RefValidator;
528528- assert!(validator.validate(&ref_def, &ctx).is_err());
612612+ let result = validator.validate(&ref_def, &ctx);
613613+ assert!(result.is_err());
614614+615615+ // Check that the error message mentions the non-existent definition
616616+ if let Err(ValidationError::InvalidSchema(msg)) = result {
617617+ assert!(msg.contains("non-existent definition"));
618618+ assert!(msg.contains("#nonExistentDef"));
619619+ } else {
620620+ panic!("Expected InvalidSchema error for non-existent reference");
621621+ }
529622 }
530623531624 #[test]
+21-8
packages/lexicon-rs/src/validation/field/union.rs
···4646 }
47474848 // Handle local reference patterns (#ref)
4949- if reference.starts_with('#') {
5050- let ref_name = &reference[1..];
4949+ if let Some(ref_name) = reference.strip_prefix('#') {
5150 // Match bare name against local ref
5251 if type_name == ref_name {
5352 return true;
···5958 }
60596160 // Handle implicit #main patterns
6262- if type_name.ends_with("#main") {
6363- let base_type = &type_name[..type_name.len() - 5]; // Remove "#main"
6161+ if let Some(base_type) = type_name.strip_suffix("#main") {
6262+ // Remove "#main"
6463 if reference == base_type {
6564 return true;
6665 }
···211210 }
212211 }
213212214214- // TODO: Validate that each reference can be resolved
215215- // This would require access to the reference resolution system
213213+ // Validate that each reference can be resolved
214214+ use crate::validation::field::reference::RefValidator;
215215+ let ref_validator = RefValidator;
216216+217217+ for (i, ref_item) in refs_array.iter().enumerate() {
218218+ if let Some(ref_str) = ref_item.as_str() {
219219+ // Create a temporary ref schema for validation
220220+ let temp_ref_schema = serde_json::json!({
221221+ "type": "ref",
222222+ "ref": ref_str
223223+ });
224224+225225+ let ref_ctx = ctx.with_path(&format!("{}.refs[{}]", def_name, i));
226226+ ref_validator.validate(&temp_ref_schema, &ref_ctx)?;
227227+ }
228228+ }
216229217230 Ok(())
218231 }
···922935923936 // String that should violate shortString maxLength constraint
924937 // Currently passes because ObjectValidator doesn't validate property constraints yet
925925- let invalid_short = json!({
938938+ let _invalid_short = json!({
926939 "$type": "shortString",
927940 "text": "This string is way too long for the short constraint"
928941 });
929942 // assert!(validator.validate_data(&invalid_short, &union_schema, &ctx).is_err()); // TODO: Enable when ObjectValidator supports constraints
930943931944 // String that should violate longString minLength constraint
932932- let invalid_long = json!({
945945+ let _invalid_long = json!({
933946 "$type": "longString",
934947 "text": "short"
935948 });
···4747 /// * `Ok(StringFormat)` if the format is valid
4848 /// * `Err(ValidationError)` if the format is unknown
4949 fn validate_format_constraint(def_name: &str, format_str: &str) -> Result<StringFormat, ValidationError> {
5050- StringFormat::from_str(format_str).ok_or_else(|| {
5050+ format_str.parse::<StringFormat>().map_err(|_| {
5151 ValidationError::InvalidSchema(format!(
5252 "String '{}' has unknown format '{}'. Valid formats: datetime, uri, at-uri, did, handle, at-identifier, nsid, cid, language, tid, record-key",
5353 def_name, format_str
···377377 // Validate format constraints
378378 if let Some(format_value) = schema.get("format") {
379379 if let Some(format_str) = format_value.as_str() {
380380- if let Some(format) = StringFormat::from_str(format_str) {
380380+ if let Ok(format) = format_str.parse::<StringFormat>() {
381381 self.validate_string_format(data_str, format, ctx)?;
382382 }
383383 }
+3-7
packages/lexicon-rs/src/validation/resolution.rs
···3535 ));
3636 }
37373838- let (lexicon_id, def_name) = if reference.starts_with('#') {
3838+ let (lexicon_id, def_name) = if let Some(def_name) = reference.strip_prefix('#') {
3939 // Local reference within current lexicon
4040- let def_name = &reference[1..];
4140 if def_name.is_empty() {
4241 return Err(ValidationError::InvalidSchema(
4342 "Local reference cannot be just '#'".to_string()
···245244 .map(|r| {
246245 if r.starts_with('#') {
247246 format!("{}{}", lexicon_id, r)
248248- } else if !r.contains('#') {
249249- r // Already a full reference to main
250247 } else {
251248 r // Already a full reference
252249 }
···262259 let mut rec_stack = HashSet::new();
263260264261 for def_id in dependency_graph.keys() {
265265- if !visited.contains(def_id) {
266266- if has_cycle_dfs(def_id, &dependency_graph, &mut visited, &mut rec_stack) {
262262+ if !visited.contains(def_id)
263263+ && has_cycle_dfs(def_id, &dependency_graph, &mut visited, &mut rec_stack) {
267264 return Err(ValidationError::InvalidSchema(format!(
268265 "Circular dependency detected involving definition: {}", def_id
269266 )));
270267 }
271271- }
272268 }
273269274270 Ok(())