1use serde_json::{json, Map, Number, Value};
8
9use crate::schema::Schema;
10
11fn with_loc(mut m: Map<String, Value>, schema: &Schema) -> Map<String, Value> {
13 m.insert("source".to_string(), json!(schema.source()));
14 m.insert(
15 "database".to_string(),
16 schema.database().map(|s| json!(s)).unwrap_or(Value::Null),
17 );
18 m.insert(
19 "schema".to_string(),
20 schema.schema().map(|s| json!(s)).unwrap_or(Value::Null),
21 );
22 m
23}
24
25pub fn cmd_find(schema: &Schema, filter: &Value, projection: Option<&Value>) -> Value {
26 with_loc(
27 json!({
28 "kind": "find",
29 "collection": schema.collection,
30 "filter": filter,
31 "projection": projection.cloned().unwrap_or(Value::Null),
32 })
33 .as_object()
34 .cloned()
35 .unwrap_or_default(),
36 schema,
37 )
38 .into()
39}
40
41pub fn cmd_aggregate(schema: &Schema, pipeline: &[Value]) -> Value {
42 with_loc(
43 json!({
44 "kind": "aggregate",
45 "collection": schema.collection,
46 "pipeline": pipeline,
47 })
48 .as_object()
49 .cloned()
50 .unwrap_or_default(),
51 schema,
52 )
53 .into()
54}
55
56pub fn cmd_count_documents(schema: &Schema, filter: &Value) -> Value {
57 with_loc(
58 json!({
59 "kind": "countDocuments",
60 "collection": schema.collection,
61 "filter": filter,
62 })
63 .as_object()
64 .cloned()
65 .unwrap_or_default(),
66 schema,
67 )
68 .into()
69}
70
71pub fn cmd_find_one(schema: &Schema, filter: &Value, projection: Option<&Value>) -> Value {
72 with_loc(
73 json!({
74 "kind": "findOne",
75 "collection": schema.collection,
76 "filter": filter,
77 "projection": projection.cloned().unwrap_or(Value::Null),
78 })
79 .as_object()
80 .cloned()
81 .unwrap_or_default(),
82 schema,
83 )
84 .into()
85}
86
87pub fn cmd_insert_one(schema: &Schema, doc: &Value) -> Value {
88 with_loc(
89 json!({
90 "kind": "insertOne",
91 "collection": schema.collection,
92 "doc": doc,
93 })
94 .as_object()
95 .cloned()
96 .unwrap_or_default(),
97 schema,
98 )
99 .into()
100}
101
102pub fn cmd_insert_many(schema: &Schema, docs: &[Value]) -> Value {
103 with_loc(
104 json!({
105 "kind": "insertMany",
106 "collection": schema.collection,
107 "docs": docs,
108 })
109 .as_object()
110 .cloned()
111 .unwrap_or_default(),
112 schema,
113 )
114 .into()
115}
116
117pub fn cmd_find_one_and_update(
118 schema: &Schema,
119 filter: &Value,
120 update: &Value,
121 options: &Value,
122) -> Value {
123 with_loc(
124 json!({
125 "kind": "findOneAndUpdate",
126 "collection": schema.collection,
127 "filter": filter,
128 "update": update,
129 "options": options,
130 })
131 .as_object()
132 .cloned()
133 .unwrap_or_default(),
134 schema,
135 )
136 .into()
137}
138
139pub fn cmd_update_many(schema: &Schema, filter: &Value, update: &Value) -> Value {
140 with_loc(
141 json!({
142 "kind": "updateMany",
143 "collection": schema.collection,
144 "filter": filter,
145 "update": update,
146 })
147 .as_object()
148 .cloned()
149 .unwrap_or_default(),
150 schema,
151 )
152 .into()
153}
154
155pub fn cmd_delete_many(schema: &Schema, filter: &Value) -> Value {
156 with_loc(
157 json!({
158 "kind": "deleteMany",
159 "collection": schema.collection,
160 "filter": filter,
161 })
162 .as_object()
163 .cloned()
164 .unwrap_or_default(),
165 schema,
166 )
167 .into()
168}
169
170pub fn apply_route_override(plan: &mut Value, route_override: &Value) {
182 let ov = match route_override.as_object() {
183 Some(o) if !o.is_empty() => o,
184 _ => return,
185 };
186 let src = ov
187 .get("source")
188 .and_then(|v| v.as_str())
189 .filter(|s| !s.is_empty());
190 let db_present = ov.contains_key("database");
191 let db = ov.get("database");
192 let sc_present = ov.contains_key("schema");
193 let sc = ov.get("schema");
194 override_walk(plan, src, db_present, db, sc_present, sc);
195}
196
197fn override_walk(
198 v: &mut Value,
199 src: Option<&str>,
200 db_present: bool,
201 db: Option<&Value>,
202 sc_present: bool,
203 sc: Option<&Value>,
204) {
205 match v {
206 Value::Object(map) => {
207 let is_cmd = map.contains_key("kind") && map.contains_key("collection");
208 if is_cmd {
209 if let Some(s) = src {
210 map.insert("source".to_string(), json!(s));
211 }
212 if db_present {
213 map.insert("database".to_string(), db.cloned().unwrap_or(Value::Null));
214 }
215 if sc_present {
216 map.insert("schema".to_string(), sc.cloned().unwrap_or(Value::Null));
217 }
218 }
219 for (_, child) in map.iter_mut() {
220 override_walk(child, src, db_present, db, sc_present, sc);
221 }
222 }
223 Value::Array(items) => {
224 for it in items.iter_mut() {
225 override_walk(it, src, db_present, db, sc_present, sc);
226 }
227 }
228 _ => {}
229 }
230}
231
232pub(super) fn to_number(v: Option<&Value>) -> f64 {
236 match v {
237 Some(Value::Number(n)) => {
238 let f = n.as_f64().unwrap_or(0.0);
239 if f.is_nan() {
240 0.0
241 } else {
242 f.trunc()
243 }
244 }
245 Some(Value::Bool(b)) => {
246 if *b {
247 1.0
248 } else {
249 0.0
250 }
251 }
252 Some(Value::String(s)) => {
253 let t = s.trim();
254 if t.is_empty() {
255 return 0.0;
256 }
257 if is_int_str(t) {
258 t.parse::<i64>().map(|i| i as f64).unwrap_or(0.0)
259 } else {
260 match t.parse::<f64>() {
261 Ok(f) if !f.is_nan() => f,
262 _ => 0.0,
263 }
264 }
265 }
266 _ => 0.0,
267 }
268}
269
270fn is_int_str(s: &str) -> bool {
271 let body = s.strip_prefix(['+', '-']).unwrap_or(s);
272 !body.is_empty() && body.bytes().all(|b| b.is_ascii_digit())
273}
274
275pub(super) fn num_value(f: f64) -> Value {
277 if !f.is_finite() {
278 return json!(0);
279 }
280 if f.fract() == 0.0 && f.abs() <= 9_007_199_254_740_992.0 {
281 json!(f as i64)
282 } else {
283 Value::Number(Number::from_f64(f).unwrap_or_else(|| Number::from(0)))
284 }
285}