1use std::collections::HashMap;
2
3use serde_json::Value;
4
5use crate::context::Context;
6use crate::error::ValidationError;
7use crate::schemas::{self, Draft};
8const DOCUMENT_PROTOCOL: &str = "document:///";
11
12fn id_of(draft: Draft, schema: &Value) -> Option<&str> {
13 if let Value::Object(object) = schema {
14 if draft == Draft::Draft4 {
15 object.get("$id").or_else(|| object.get("id"))
16 } else {
17 object.get("$id")
18 }
19 .and_then(Value::as_str)
20 } else {
21 None
22 }
23}
24
25pub struct Resolver<'a> {
26 base_url: String,
27 id_mapping: HashMap<String, &'a Value>,
28}
29
30fn find_ids<'a, F>(
33 draft: Draft,
34 schema: &'a Value,
35 base_url: &url::Url,
36 visitor: &mut F,
37) -> Result<Option<&'a Value>, ValidationError>
38where
39 F: FnMut(String, &'a Value) -> Option<&'a Value>,
40{
41 match schema {
42 Value::Object(object) => {
43 if let Some(url) = id_of(draft, schema) {
44 let new_url = base_url.join(url)?;
45 if let Some(x) = visitor(new_url.to_string(), schema) {
46 return Ok(Some(x));
47 }
48 for (_k, v) in object {
49 let result = find_ids(draft, v, &new_url, visitor)?;
50 if result.is_some() {
51 return Ok(result);
52 }
53 }
54 } else {
55 for (_k, v) in object {
56 let result = find_ids(draft, v, base_url, visitor)?;
57 if result.is_some() {
58 return Ok(result);
59 }
60 }
61 }
62 }
63 Value::Array(array) => {
64 for v in array {
65 let result = find_ids(draft, v, base_url, visitor)?;
66 if result.is_some() {
67 return Ok(result);
68 }
69 }
70 }
71 _ => {}
72 }
73 Ok(None)
74}
75
76impl<'a> Resolver<'a> {
77 pub fn from_schema(draft: Draft, schema: &'a Value) -> Result<Resolver<'a>, ValidationError> {
78 let base_url = match id_of(draft, schema) {
79 Some(url) => url.to_string(),
80 None => DOCUMENT_PROTOCOL.to_string(),
81 };
82
83 let mut id_mapping: HashMap<String, &'a Value> = HashMap::new();
84
85 find_ids(draft, schema, &url::Url::parse(&base_url)?, &mut |id, x| {
86 id_mapping.insert(id, x);
87 None
88 })?;
89
90 Ok(Resolver {
91 base_url,
92 id_mapping,
93 })
94 }
95
96 pub fn join_url(
97 &self,
98 draft: Draft,
99 url_ref: &str,
100 ctx: &Context,
101 ) -> Result<url::Url, ValidationError> {
102 let mut urls: Vec<&str> = vec![url_ref];
103 let mut frame = ctx;
104 loop {
105 if let Some(id) = id_of(draft, frame.x) {
106 urls.push(id);
107 }
108 match frame.parent {
109 Some(x) => frame = x,
110 None => break,
111 }
112 }
113 let base_url = url::Url::parse(&self.base_url)?;
114 let url = urls.iter().rev().try_fold(base_url, |x, y| x.join(y));
115 Ok(url?)
116 }
117
118 pub fn resolve_url(
119 &self,
120 url: &url::Url,
121 instance: &'a Value,
122 ) -> Result<&'a Value, ValidationError> {
123 let url_str = url.as_str();
124 match url_str {
125 DOCUMENT_PROTOCOL => Ok(instance),
126 _ => match schemas::draft_from_url(url_str) {
127 Some(value) => Ok(value.get_schema()),
128 _ => match self.id_mapping.get(url_str) {
129 Some(value) => Ok(value),
130 None => Err(ValidationError::new(
131 &format!("Can't resolve url {}", url_str),
132 None,
133 None,
134 )),
135 },
136 },
137 }
138 }
139
140 pub fn resolve_fragment(
141 &self,
142 draft: Draft,
143 url: &str,
144 ctx: &Context,
145 instance: &'a Value,
146 ) -> Result<(url::Url, &'a Value), ValidationError> {
147 let url = self.join_url(draft, url, ctx)?;
148 let mut resource = url.clone();
149 resource.set_fragment(None);
150 let fragment = percent_encoding::percent_decode(url.fragment().unwrap_or("").as_bytes())
151 .decode_utf8()
152 .unwrap();
153
154 if let Some(x) = find_ids(
155 draft,
156 instance,
157 &url::Url::parse(DOCUMENT_PROTOCOL)?,
158 &mut |id, x| {
159 if id == url.as_str() {
160 Some(x)
161 } else {
162 None
163 }
164 },
165 )? {
166 return Ok((resource, x));
167 }
168
169 let document = self.resolve_url(&resource, instance)?;
170
171 match document.pointer(&fragment) {
173 Some(x) => Ok((resource, x)),
174 None => Err(ValidationError::new(
175 &format!("Couldn't resolve JSON pointer {}", url),
176 None,
177 None,
178 )),
179 }
180 }
181}