1use saphyr::MarkedYaml;
2use saphyr::YamlData;
3
4use crate::utils::format_marker;
5
6#[derive(Clone, Debug, Default, PartialEq)]
9pub struct Reference {
10 pub ref_name: String,
11}
12
13impl std::fmt::Display for Reference {
14 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
15 write!(f, "$ref: {}", self.ref_name)
16 }
17}
18
19impl Reference {
20 pub fn new<S>(ref_name: S) -> Reference
21 where
22 S: Into<String>,
23 {
24 Reference {
25 ref_name: ref_name.into(),
26 }
27 }
28}
29
30impl TryFrom<&MarkedYaml<'_>> for Reference {
31 type Error = crate::Error;
32
33 fn try_from(value: &MarkedYaml<'_>) -> std::result::Result<Self, Self::Error> {
34 if let YamlData::Mapping(mapping) = &value.data {
35 let ref_key = MarkedYaml::value_from_str("$ref");
36 if !mapping.contains_key(&ref_key) {
37 return Err(generic_error!(
38 "{} Expected a $ref key, but got: {:#?}",
39 format_marker(&value.span.start),
40 mapping
41 ));
42 }
43
44 let ref_value = mapping.get(&ref_key).unwrap();
45 match &ref_value.data {
46 YamlData::Value(saphyr::Scalar::String(s)) => {
47 if !s.starts_with("#/$defs/") && !s.starts_with("#/definitions/") {
48 return Err(generic_error!(
49 "Only local references, starting with #/$defs/ or #/definitions/ are supported for now. Found: {}",
50 s
51 ));
52 }
53 let ref_name = match s.strip_prefix("#/$defs/") {
54 Some(ref_name) => ref_name,
55 _ => s.strip_prefix("#/definitions/").unwrap(),
56 };
57
58 Ok(Reference::new(ref_name))
59 }
60 _ => Err(generic_error!(
61 "Expected a string value for $ref, but got: {:#?}",
62 ref_value
63 )),
64 }
65 } else {
66 Err(generic_error!(
67 "{} value is not a mapping: {:?}",
68 format_marker(&value.span.start),
69 value
70 ))
71 }
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use crate::RootSchema;
78 use crate::Schema;
79 use saphyr::LoadableYamlNode;
80
81 #[test]
82 fn test_reference() {
83 let schema = r##"
84 $defs:
85 name:
86 type: string
87 type: object
88 properties:
89 name:
90 $ref: "#/$defs/name"
91 "##;
92 let root_schema = RootSchema::load_from_str(schema).unwrap();
93 let yaml_schema = root_schema.schema.as_ref();
94 println!("yaml_schema: {yaml_schema:#?}");
95 let schema = yaml_schema.schema.as_ref().unwrap();
96 println!("schema: {schema:#?}");
97 if let Schema::Typed(typed_schema) = schema {
98 let first_type = typed_schema.r#type.first().unwrap();
99 if let crate::schemas::TypedSchemaType::Object(object_schema) = first_type {
100 if let Some(properties) = &object_schema.properties {
101 if let Some(name_property) = properties.get("name") {
102 let name_ref = name_property.r#ref.as_ref().unwrap();
103 assert_eq!(name_ref.ref_name, "name");
104 }
105 }
106 }
107 } else {
108 panic!("Expected Schema::Typed, but got: {schema:?}");
109 }
110 let context = crate::Context::with_root_schema(&root_schema, true);
111 let value = r##"
112 name: "John Doe"
113 "##;
114 let docs = saphyr::MarkedYaml::load_from_str(value).unwrap();
115 let value = docs.first().unwrap();
116 let result = root_schema.validate(&context, value);
117 assert!(result.is_ok());
118 assert!(!context.has_errors());
119 }
120}