1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Path compiler
mod builder;
use anyhow::{anyhow, Result};
pub use builder::{CompilerBuilder, CompilerOptions};
use regex::Regex;
use crate::{internal::DataValue, try_into_with::TryIntoWith, Key, ParserOptions, Token};
/// Path compiler
pub struct Compiler {
pub(crate) tokens: Vec<Token>,
pub(crate) matches: Vec<Option<Regex>>,
pub(crate) options: CompilerOptions,
}
impl Compiler {
/// Create a [`Compiler`](struct.Compiler.html) with the options
#[inline]
pub fn new<I>(path: I) -> Result<Compiler>
where
I: TryIntoWith<Vec<Token>, ParserOptions>,
{
CompilerBuilder::new(path).build()
}
/// Create a [`Compiler`](struct.Compiler.html) with the options
#[inline]
pub fn new_with_options<I>(path: I, options: CompilerOptions) -> Result<Compiler>
where
I: TryIntoWith<Vec<Token>, ParserOptions>,
{
CompilerBuilder::new_with_options(path, options).build()
}
/// render parameters into a path
pub fn render(&self, data: &DataValue) -> Result<String> {
let mut path = String::new();
let CompilerOptions {
validate, encode, ..
} = self.options;
let array_type_name = "an array containing only strings or numbers";
let item_type_name = "a string or a number";
for (i, token) in self.tokens.iter().enumerate() {
match token {
Token::Static(token) => {
path += token;
continue;
}
Token::Key(token) => {
let Key {
name,
prefix,
suffix,
pattern,
modifier,
} = token;
let value = data.get(name);
let modifier = modifier.as_str();
let optional = matches!(modifier, "?" | "*");
let repeat = matches!(modifier, "+" | "*");
let mut resolve_string = |value: &String| {
let segment = encode(value, token);
if validate
&& self.matches[i]
.as_ref()
.map(|m| m.is_match(segment.as_str()))
.is_none()
{
return Err(anyhow!("Expected all \"{name}\" to match \"{pattern}\", but got \"{segment}\""));
}
path = format!("{path}{prefix}{segment}{suffix}");
Ok(())
};
if let Some(value) = value {
match value {
DataValue::Array(value) => {
if !repeat {
return Err(anyhow!(
"Expected \"{name}\" to not repeat, but got an array",
));
}
if value.is_empty() {
if optional {
continue;
}
return Err(anyhow!("Expected \"{name}\" to not be empty",));
}
for value in value.iter() {
match value {
DataValue::Number(value) => {
resolve_string(&value.to_string())?;
}
DataValue::String(value) => {
resolve_string(value)?;
}
_ => {
return Err(anyhow!(
"Expected \"{name}\" to be {array_type_name}"
))
}
}
}
continue;
}
DataValue::Number(value) => {
resolve_string(&value.to_string())?;
continue;
}
DataValue::String(value) => {
resolve_string(value)?;
continue;
}
_ => (),
}
}
if optional {
continue;
}
let type_of_message = if repeat {
array_type_name
} else {
item_type_name
};
return Err(anyhow!("Expected \"{name}\" to be {type_of_message}"));
}
}
}
Ok(path)
}
}