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