1use crate::builders::{buildize, fault_node};
8use crate::node::{Kind, Node};
9use crate::normalize::normalize;
10use crate::spec::Spec;
11use crate::value::Value;
12use indexmap::IndexMap;
13
14#[derive(Clone, Debug, Default, PartialEq, Eq)]
16pub struct Names(pub Vec<String>);
17
18impl From<&str> for Names {
19 fn from(s: &str) -> Self {
20 Names(vec![s.to_string()])
21 }
22}
23impl From<String> for Names {
24 fn from(s: String) -> Self {
25 Names(vec![s])
26 }
27}
28impl From<Vec<String>> for Names {
29 fn from(v: Vec<String>) -> Self {
30 Names(v)
31 }
32}
33impl From<Vec<&str>> for Names {
34 fn from(v: Vec<&str>) -> Self {
35 Names(v.into_iter().map(String::from).collect())
36 }
37}
38impl<const N: usize> From<[&str; N]> for Names {
39 fn from(v: [&str; N]) -> Self {
40 Names(v.iter().map(|s| s.to_string()).collect())
41 }
42}
43
44impl Names {
45 pub fn from_value(v: &Value, builder: &str) -> Result<Names, String> {
47 match v {
48 Value::Str(s) => Ok(Names(vec![s.clone()])),
49 Value::Arr(items) => {
50 let mut out = Vec::with_capacity(items.len());
51 for item in items {
52 match item {
53 Value::Str(s) => out.push(s.clone()),
54 _ => return Err(format!("{} needs a list of property names", builder)),
55 }
56 }
57 Ok(Names(out))
58 }
59 _ => Err(format!("{} needs a list of property names", builder)),
60 }
61 }
62}
63
64fn object_base(spec: Spec, builder: &str) -> Result<Node, String> {
66 let n = buildize(spec);
67 if n.kind != Kind::Object {
68 return Err(format!("{} needs an object shape", builder));
69 }
70 Ok(n)
71}
72
73fn object_node(mut base: Node, children: IndexMap<String, Node>) -> Node {
76 if let Value::Obj(dm) = &base.default {
77 let mut nd = crate::value::Map::with_capacity(children.len());
78 for k in children.keys() {
79 if let Some(dv) = dm.get(k) {
80 nd.insert(k.clone(), dv.clone());
81 }
82 }
83 base.default = Value::Obj(nd);
84 }
85 base.obj_children = children;
86 base.consumed.clear();
87 base
88}
89
90pub(crate) fn pick_node(names: Names, spec: Spec) -> Result<Node, String> {
91 let base = object_base(spec, "Pick")?;
92 for k in &names.0 {
93 if !base.obj_children.contains_key(k) {
94 return Err(format!("Pick: unknown property \"{}\"", k));
95 }
96 }
97 let children: IndexMap<String, Node> = base
98 .obj_children
99 .iter()
100 .filter(|(k, _)| names.0.contains(k))
101 .map(|(k, v)| (k.clone(), v.clone()))
102 .collect();
103 Ok(object_node(base, children))
104}
105
106pub(crate) fn omit_node(names: Names, spec: Spec) -> Result<Node, String> {
107 let base = object_base(spec, "Omit")?;
108 let children: IndexMap<String, Node> = base
109 .obj_children
110 .iter()
111 .filter(|(k, _)| !names.0.contains(k))
112 .map(|(k, v)| (k.clone(), v.clone()))
113 .collect();
114 Ok(object_node(base, children))
115}
116
117pub(crate) fn partial_node(spec: Spec) -> Result<Node, String> {
118 let base = object_base(spec, "Partial")?;
119 let children: IndexMap<String, Node> = base
120 .obj_children
121 .iter()
122 .map(|(k, v)| {
123 let mut cc = v.clone();
124 cc.required = false;
125 cc.required_set = true;
126 (k.clone(), cc)
127 })
128 .collect();
129 Ok(object_node(base, children))
130}
131
132pub(crate) fn extend_node(extra: Spec, spec: Spec) -> Result<Node, String> {
133 let base = object_base(spec, "Extend")?;
134 let ext = normalize(extra);
135 if ext.kind != Kind::Object {
136 return Err("Extend needs an object to extend with".to_string());
137 }
138 let mut children = base.obj_children.clone();
139 for (k, cn) in ext.obj_children {
140 children.insert(k, cn);
141 }
142 Ok(object_node(base, children))
143}
144
145fn or_fault(r: Result<Node, String>) -> Node {
146 r.unwrap_or_else(fault_node)
147}
148
149pub fn pick(names: impl Into<Names>, spec: impl Into<Spec>) -> Node {
152 or_fault(pick_node(names.into(), spec.into()))
153}
154
155pub fn omit(names: impl Into<Names>, spec: impl Into<Spec>) -> Node {
158 or_fault(omit_node(names.into(), spec.into()))
159}
160
161pub fn partial(spec: impl Into<Spec>) -> Node {
165 or_fault(partial_node(spec.into()))
166}
167
168pub fn extend(extra: impl Into<Spec>, spec: impl Into<Spec>) -> Node {
171 or_fault(extend_node(extra.into(), spec.into()))
172}
173
174impl Node {
175 pub fn pick(self, names: impl Into<Names>) -> Node {
176 pick(names, self)
177 }
178 pub fn omit(self, names: impl Into<Names>) -> Node {
179 omit(names, self)
180 }
181 pub fn partial(self) -> Node {
182 partial(self)
183 }
184 pub fn extend(self, extra: impl Into<Spec>) -> Node {
185 extend(extra, self)
186 }
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192 use crate::builders::{any, open};
193 use crate::node::Token;
194 use crate::spec::obj;
195 use crate::Schema;
196
197 fn j(s: &str) -> Value {
198 Value::from(serde_json::from_str::<serde_json::Value>(s).unwrap())
199 }
200
201 fn run(s: &Schema, input: &str) -> String {
202 match s.validate(j(input)) {
203 Ok(v) => serde_json::Value::from(v).to_string(),
204 Err(e) => format!("ERR {}", e),
205 }
206 }
207
208 fn base() -> Spec {
209 obj([
210 ("a", Spec::from(1)),
211 ("b", Spec::from(Token::String)),
212 ("c", Spec::from(true)),
213 ])
214 }
215
216 #[test]
217 fn reshapes_objects() {
218 assert_eq!(run(&Schema::new(pick(["a"], base())), "{}"), r#"{"a":1}"#);
219 assert_eq!(run(&Schema::new(pick("a", base())), "{}"), r#"{"a":1}"#);
220 assert_eq!(
221 run(&Schema::new(pick(vec!["a", "c"], base())), r#"{"c":false}"#),
222 r#"{"c":false,"a":1}"#
223 );
224 assert_eq!(
225 run(&Schema::new(pick(vec!["b".to_string()], base())), "{}"),
226 "ERR Validation failed for property \"b\" because the property is missing."
227 );
228 assert_eq!(
229 run(&Schema::new(pick("z", base())), "{}"),
230 "ERR Pick: unknown property \"z\""
231 );
232 assert_eq!(
233 run(&Schema::new(pick("a", Token::Number)), "1"),
234 "ERR Pick needs an object shape"
235 );
236 assert_eq!(
237 run(&Schema::new(pick("a", open(base()))), r#"{"a":2,"z":1}"#),
238 r#"{"a":2,"z":1}"#
239 );
240 assert_eq!(
241 run(&Schema::new(omit(["b"], base())), "{}"),
242 r#"{"a":1,"c":true}"#
243 );
244 assert_eq!(
245 run(&Schema::new(omit(["z"], base())), r#"{"b":"x"}"#),
246 r#"{"b":"x","a":1,"c":true}"#
247 );
248 assert_eq!(
249 run(&Schema::new(omit(["b"], base())), r#"{"b":"x"}"#),
250 "ERR Validation failed for object \"{b:x}\" because the property \"b\" is not allowed."
251 );
252 assert_eq!(
253 run(&Schema::new(partial(base())), "{}"),
254 r#"{"a":1,"b":"","c":true}"#
255 );
256 assert_eq!(
257 run(&Schema::new(partial(any())), "{}"),
258 "ERR Partial needs an object shape"
259 );
260 assert_eq!(
261 run(
262 &Schema::new(extend(obj([("d", Spec::from(2))]), base())),
263 r#"{"b":"x"}"#
264 ),
265 r#"{"b":"x","a":1,"c":true,"d":2}"#
266 );
267 assert_eq!(
268 run(
269 &Schema::new(extend(obj([("a", Spec::from(Token::Number))]), base())),
270 r#"{"b":"x"}"#
271 ),
272 "ERR Validation failed for property \"a\" because the property is missing."
273 );
274 assert_eq!(
275 run(&Schema::new(extend(Token::Number, base())), "{}"),
276 "ERR Extend needs an object to extend with"
277 );
278 let b = buildize(base());
280 let picked = b.clone().pick("a");
281 assert_eq!(b.obj_children.len(), 3);
282 assert_eq!(picked.obj_children.len(), 1);
283 assert_eq!(
284 b.clone().omit(Names(vec!["a".into()])).obj_children.len(),
285 2
286 );
287 assert!(!b.clone().partial().obj_children["b"].required);
288 assert_eq!(b.extend(obj([("z", Spec::from(1))])).obj_children.len(), 4);
289 let mut d = buildize(base());
291 d.has_default = true;
292 d.default = j(r#"{"a":5,"b":"q","c":false}"#);
293 let p = pick("a", d);
294 assert_eq!(p.default, j(r#"{"a":5}"#));
295 let mut bare = Node::of(Kind::Object);
296 bare.obj_children.insert("a".into(), buildize(1));
297 assert!(pick("a", bare).default.is_undefined());
298 assert_eq!(
299 Names::from_value(&j(r#"["a","b"]"#), "Pick").unwrap(),
300 Names::from(["a", "b"])
301 );
302 assert_eq!(
303 Names::from_value(&j("\"a\""), "Pick").unwrap(),
304 Names::from("a")
305 );
306 assert_eq!(Names::from("a".to_string()), Names::from("a"));
307 assert_eq!(Names::from(vec!["a".to_string()]), Names::from("a"));
308 assert_eq!(
309 Names::from_value(&j("[1]"), "Pick").unwrap_err(),
310 "Pick needs a list of property names"
311 );
312 assert_eq!(
313 Names::from_value(&j("1"), "Omit").unwrap_err(),
314 "Omit needs a list of property names"
315 );
316 }
317}