1pub fn coerce_value(raw: &str) -> serde_json::Value {
18 if raw.is_empty() {
19 return serde_json::Value::Null;
20 }
21
22 if raw.eq_ignore_ascii_case("true") {
23 return serde_json::Value::Bool(true);
24 }
25 if raw.eq_ignore_ascii_case("false") {
26 return serde_json::Value::Bool(false);
27 }
28 if raw.eq_ignore_ascii_case("null") {
29 return serde_json::Value::Null;
30 }
31
32 if let Ok(n) = raw.parse::<i64>() {
33 return serde_json::Value::Number(n.into());
34 }
35
36 if let Ok(f) = raw.parse::<f64>() {
37 if f.is_finite() {
38 if let Some(n) = serde_json::Number::from_f64(f) {
39 return serde_json::Value::Number(n);
40 }
41 }
42 }
43
44 serde_json::Value::String(raw.to_string())
45}
46
47pub fn parse_query_string(query: &str) -> serde_json::Map<String, serde_json::Value> {
52 let mut map = serde_json::Map::new();
53
54 if query.is_empty() {
55 return map;
56 }
57
58 for pair in query.split('&') {
59 if pair.is_empty() {
60 continue;
61 }
62 let (key, raw_value) = match pair.split_once('=') {
63 Some((k, v)) => (k, v),
64 None => (pair, ""),
65 };
66
67 let key = url_decode(key);
68 let raw_value = url_decode(raw_value);
69
70 map.insert(key, coerce_value(&raw_value));
71 }
72
73 map
74}
75
76pub fn query_string_to_json(query: &str) -> serde_json::Value {
80 serde_json::Value::Object(parse_query_string(query))
81}
82
83pub fn cookies_to_json(cookie_header: &str) -> serde_json::Value {
87 let mut map = serde_json::Map::new();
88
89 if cookie_header.is_empty() {
90 return serde_json::Value::Object(map);
91 }
92
93 for cookie in cookie_header.split(';') {
94 let cookie = cookie.trim();
95 if cookie.is_empty() {
96 continue;
97 }
98 let (name, value) = match cookie.split_once('=') {
99 Some((n, v)) => (n.trim(), v.trim()),
100 None => (cookie.trim(), ""),
101 };
102 map.insert(name.to_string(), coerce_value(value));
103 }
104
105 serde_json::Value::Object(map)
106}
107
108pub fn format_issues(err: &vld::error::VldError) -> Vec<serde_json::Value> {
112 err.issues
113 .iter()
114 .map(|i| {
115 let path: String = i
116 .path
117 .iter()
118 .map(|p| p.to_string())
119 .collect::<Vec<_>>()
120 .join(".");
121 serde_json::json!({
122 "path": path,
123 "message": i.message,
124 })
125 })
126 .collect()
127}
128
129pub fn format_vld_error(err: &vld::error::VldError) -> serde_json::Value {
132 serde_json::json!({
133 "error": "Validation failed",
134 "issues": format_issues(err),
135 })
136}
137
138pub fn format_issues_with_code(err: &vld::error::VldError) -> Vec<serde_json::Value> {
143 err.issues
144 .iter()
145 .map(|issue| {
146 let path: String = issue
147 .path
148 .iter()
149 .map(|p| p.to_string())
150 .collect::<Vec<_>>()
151 .join("");
152 serde_json::json!({
153 "path": path,
154 "message": issue.message,
155 "code": issue.code.key(),
156 })
157 })
158 .collect()
159}
160
161pub fn url_decode(input: &str) -> String {
165 let s = input.replace('+', " ");
166 let mut result = String::with_capacity(s.len());
167 let mut chars = s.chars();
168 while let Some(c) = chars.next() {
169 if c == '%' {
170 let hex: String = chars.by_ref().take(2).collect();
171 if let Ok(byte) = u8::from_str_radix(&hex, 16) {
172 result.push(byte as char);
173 } else {
174 result.push('%');
175 result.push_str(&hex);
176 }
177 } else {
178 result.push(c);
179 }
180 }
181 result
182}
183
184pub fn extract_path_param_names(pattern: &str) -> Vec<String> {
186 let mut names = Vec::new();
187 let mut chars = pattern.chars().peekable();
188 while let Some(c) = chars.next() {
189 if c == '{' {
190 let name: String = chars.by_ref().take_while(|&c| c != '}').collect();
191 if !name.is_empty() {
192 names.push(name);
193 }
194 }
195 }
196 names
197}