Skip to main content

valico/json_schema/
helpers.rs

1use serde_json::Value;
2use serde_json::Value::Number;
3use url::Url;
4use uuid::Uuid;
5
6use super::schema;
7
8pub fn generate_id() -> Url {
9    let uuid = Uuid::new_v4();
10    Url::parse(&format!("json-schema://{uuid}")).unwrap()
11}
12
13/// <http://tools.ietf.org/html/draft-ietf-appsawg-json-pointer-07>
14pub fn encode(string: &str) -> String {
15    const QUERY_SET: percent_encoding::AsciiSet = percent_encoding::CONTROLS
16        .add(b' ')
17        .add(b'"')
18        .add(b'#')
19        .add(b'<')
20        .add(b'>')
21        .add(b'%');
22    percent_encoding::percent_encode(
23        string.replace('~', "~0").replace('/', "~1").as_bytes(),
24        &QUERY_SET,
25    )
26    .to_string()
27}
28
29/// Encode and connect
30pub fn connect(strings: &[&str]) -> String {
31    strings
32        .iter()
33        .map(|s| encode(s))
34        .collect::<Vec<String>>()
35        .join("/")
36}
37
38pub fn parse_url_key(key: &str, obj: &Value) -> Result<Option<Url>, schema::SchemaError> {
39    match obj.get(key) {
40        Some(value) => match value.as_str() {
41            Some(string) => Url::parse(string)
42                .map(Some)
43                .map_err(schema::SchemaError::UrlParseError),
44            None => Ok(None),
45        },
46        None => Ok(None),
47    }
48}
49
50pub fn parse_url_key_with_base(
51    key: &str,
52    obj: &Value,
53    base: &Url,
54) -> Result<Option<Url>, schema::SchemaError> {
55    match obj.get(key) {
56        Some(value) => match value.as_str() {
57            Some(string) => Url::options()
58                .base_url(Some(base))
59                .parse(string)
60                .map(Some)
61                .map_err(schema::SchemaError::UrlParseError),
62            None => Ok(None),
63        },
64        None => Ok(None),
65    }
66}
67
68pub fn alter_fragment_path(mut url: Url, new_fragment: String) -> Url {
69    let normalized_fragment = if let Some(prefix) = new_fragment.strip_prefix('/') {
70        prefix
71    } else {
72        new_fragment.as_ref()
73    };
74
75    let result_fragment = match url.fragment() {
76        Some(fragment) if !fragment.is_empty() => {
77            if !fragment.starts_with('/') {
78                let mut result_fragment = "".to_string();
79                let mut fragment_parts = fragment.split('/').map(|s| s.to_string());
80                result_fragment.push('#');
81                result_fragment.push_str(fragment_parts.next().unwrap().as_ref());
82                result_fragment.push('/');
83                result_fragment.push_str(normalized_fragment.as_ref());
84                result_fragment
85            } else {
86                "/".to_string() + normalized_fragment
87            }
88        }
89        _ => "/".to_string() + normalized_fragment,
90    };
91
92    url.set_fragment(Some(&result_fragment));
93    url
94}
95
96pub fn serialize_schema_path(url: &Url) -> (String, Option<String>) {
97    let mut url_without_fragment = url.clone();
98    url_without_fragment.set_fragment(None);
99    let mut url_str: String = url_without_fragment.into();
100
101    match url.fragment().as_ref() {
102        Some(fragment) if !fragment.is_empty() => {
103            if !fragment.starts_with('/') {
104                let fragment_parts = fragment
105                    .split('/')
106                    .map(|s| s.to_string())
107                    .collect::<Vec<String>>();
108                url_str.push('#');
109                url_str.push_str(fragment_parts[0].as_ref());
110                let fragment = if fragment_parts.len() > 1 {
111                    Some("/".to_string() + fragment_parts[1..].join("/").as_ref())
112                } else {
113                    None
114                };
115                (url_str, fragment)
116            } else {
117                (url_str, Some((*fragment).to_string()))
118            }
119        }
120        _ => (url_str, None),
121    }
122}
123
124pub fn convert_boolean_schema(val: Value) -> Value {
125    match val.as_bool() {
126        Some(b) => {
127            if b {
128                json!({})
129            } else {
130                json!({"not": {}})
131            }
132        }
133        None => val,
134    }
135}
136
137pub fn is_matching(va: &Value, vb: &Value) -> bool {
138    match va {
139        Number(a) => match vb {
140            Number(b) => a.as_f64().unwrap() == b.as_f64().unwrap(),
141            _ => false,
142        },
143        _ => *va == *vb,
144    }
145}