1use nautilus_schema::ir::{
12 DefaultValue, EnumIr, FieldIr, FunctionCall, ResolvedFieldType, ScalarType,
13};
14use serde::Serialize;
15use std::collections::HashMap;
16
17#[derive(Debug, Clone, Serialize)]
24pub struct FilterOperator {
25 pub suffix: String,
26 pub type_name: String,
27}
28
29pub trait LanguageBackend {
41 fn scalar_to_type(&self, scalar: &ScalarType) -> &'static str;
43
44 fn array_type(&self, inner: &str) -> String;
48
49 fn not_in_suffix(&self) -> &'static str;
53
54 fn startswith_suffix(&self) -> &'static str;
58
59 fn endswith_suffix(&self) -> &'static str;
63
64 fn null_suffix(&self) -> &'static str;
68
69 fn is_auto_generated(&self, field: &FieldIr) -> bool {
76 if field.computed.is_some() {
77 return true;
78 }
79 if let Some(default) = &field.default_value {
80 matches!(
81 default,
82 DefaultValue::Function(f)
83 if f.name == "autoincrement" || f.name == "uuid" || f.name == "now"
84 )
85 } else {
86 false
87 }
88 }
89
90 fn numeric_operators(&self, type_name: &str) -> Vec<FilterOperator> {
93 let arr = self.array_type(type_name);
94 vec![
95 FilterOperator {
96 suffix: "lt".to_string(),
97 type_name: type_name.to_string(),
98 },
99 FilterOperator {
100 suffix: "lte".to_string(),
101 type_name: type_name.to_string(),
102 },
103 FilterOperator {
104 suffix: "gt".to_string(),
105 type_name: type_name.to_string(),
106 },
107 FilterOperator {
108 suffix: "gte".to_string(),
109 type_name: type_name.to_string(),
110 },
111 FilterOperator {
112 suffix: "in".to_string(),
113 type_name: arr.clone(),
114 },
115 FilterOperator {
116 suffix: self.not_in_suffix().to_string(),
117 type_name: arr,
118 },
119 ]
120 }
121
122 fn get_filter_operators_for_scalar(&self, scalar: &ScalarType) -> Vec<FilterOperator> {
124 let mut ops: Vec<FilterOperator> = Vec::new();
125
126 match scalar {
127 ScalarType::String => {
128 let str_t = self.scalar_to_type(&ScalarType::String);
129 let arr = self.array_type(str_t);
130 ops.push(FilterOperator {
131 suffix: "contains".to_string(),
132 type_name: str_t.to_string(),
133 });
134 ops.push(FilterOperator {
135 suffix: self.startswith_suffix().to_string(),
136 type_name: str_t.to_string(),
137 });
138 ops.push(FilterOperator {
139 suffix: self.endswith_suffix().to_string(),
140 type_name: str_t.to_string(),
141 });
142 ops.push(FilterOperator {
143 suffix: "in".to_string(),
144 type_name: arr.clone(),
145 });
146 ops.push(FilterOperator {
147 suffix: self.not_in_suffix().to_string(),
148 type_name: arr,
149 });
150 }
151 ScalarType::Int | ScalarType::BigInt => {
152 ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
153 }
154 ScalarType::Float => {
155 ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
156 }
157 ScalarType::Decimal { .. } => {
158 ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
159 }
160 ScalarType::DateTime => {
161 ops.extend(self.numeric_operators(self.scalar_to_type(scalar)));
162 }
163 ScalarType::Uuid => {
164 let uuid_t = self.scalar_to_type(&ScalarType::Uuid);
165 let arr = self.array_type(uuid_t);
166 ops.push(FilterOperator {
167 suffix: "in".to_string(),
168 type_name: arr.clone(),
169 });
170 ops.push(FilterOperator {
171 suffix: self.not_in_suffix().to_string(),
172 type_name: arr,
173 });
174 }
175 ScalarType::Xml | ScalarType::Char { .. } | ScalarType::VarChar { .. } => {
177 let str_t = self.scalar_to_type(scalar);
178 let arr = self.array_type(str_t);
179 ops.push(FilterOperator {
180 suffix: "contains".to_string(),
181 type_name: str_t.to_string(),
182 });
183 ops.push(FilterOperator {
184 suffix: self.startswith_suffix().to_string(),
185 type_name: str_t.to_string(),
186 });
187 ops.push(FilterOperator {
188 suffix: self.endswith_suffix().to_string(),
189 type_name: str_t.to_string(),
190 });
191 ops.push(FilterOperator {
192 suffix: "in".to_string(),
193 type_name: arr.clone(),
194 });
195 ops.push(FilterOperator {
196 suffix: self.not_in_suffix().to_string(),
197 type_name: arr,
198 });
199 }
200 ScalarType::Boolean | ScalarType::Bytes | ScalarType::Json | ScalarType::Jsonb => {}
202 }
203
204 ops.push(FilterOperator {
206 suffix: "not".to_string(),
207 type_name: self.scalar_to_type(scalar).to_string(),
208 });
209
210 ops
211 }
212
213 fn get_filter_operators_for_field(
216 &self,
217 field: &FieldIr,
218 enums: &HashMap<String, EnumIr>,
219 ) -> Vec<FilterOperator> {
220 let mut ops: Vec<FilterOperator> = Vec::new();
221
222 match &field.field_type {
223 ResolvedFieldType::Scalar(scalar) => {
224 ops = self.get_filter_operators_for_scalar(scalar);
225 }
226 ResolvedFieldType::Enum { enum_name } => {
227 let enum_type = if enums.contains_key(enum_name) {
228 enum_name.clone()
229 } else {
230 self.scalar_to_type(&ScalarType::String).to_string()
232 };
233 let arr = self.array_type(&enum_type);
234 ops.push(FilterOperator {
235 suffix: "in".to_string(),
236 type_name: arr.clone(),
237 });
238 ops.push(FilterOperator {
239 suffix: self.not_in_suffix().to_string(),
240 type_name: arr,
241 });
242 ops.push(FilterOperator {
243 suffix: "not".to_string(),
244 type_name: enum_type,
245 });
246 }
247 ResolvedFieldType::Relation(_) | ResolvedFieldType::CompositeType { .. } => {
248 }
250 }
251
252 if !field.is_required || self.is_auto_generated(field) {
254 ops.push(FilterOperator {
255 suffix: self.null_suffix().to_string(),
256 type_name: self.scalar_to_type(&ScalarType::Boolean).to_string(),
258 });
259 }
260
261 ops
262 }
263
264 fn null_literal(&self) -> &'static str;
268
269 fn true_literal(&self) -> &'static str;
271
272 fn false_literal(&self) -> &'static str;
274
275 fn string_literal(&self, s: &str) -> String;
277
278 fn empty_array_literal(&self) -> &'static str;
280
281 fn enum_variant_literal(&self, variant: &str) -> String {
283 variant.to_string()
284 }
285
286 fn relation_type(&self, target_model: &str) -> String {
290 target_model.to_string()
291 }
292
293 fn get_base_type(&self, field: &FieldIr, enums: &HashMap<String, EnumIr>) -> String {
297 match &field.field_type {
298 ResolvedFieldType::Scalar(scalar) => self.scalar_to_type(scalar).to_string(),
299 ResolvedFieldType::Enum { enum_name } => {
300 if enums.contains_key(enum_name) {
301 enum_name.clone()
302 } else {
303 self.scalar_to_type(&ScalarType::String).to_string()
304 }
305 }
306 ResolvedFieldType::CompositeType { type_name } => type_name.clone(),
307 ResolvedFieldType::Relation(rel) => self.relation_type(&rel.target_model),
308 }
309 }
310
311 fn get_default_value(&self, field: &FieldIr) -> Option<String> {
313 if let Some(default) = &field.default_value {
314 match default {
315 DefaultValue::Function(FunctionCall { name, .. })
316 if matches!(name.as_str(), "now" | "uuid" | "autoincrement") =>
317 {
318 return Some(self.null_literal().to_string());
319 }
320 DefaultValue::Function(_) => return None,
321 DefaultValue::String(s) => return Some(self.string_literal(s)),
322 DefaultValue::Number(n) => return Some(n.clone()),
323 DefaultValue::Boolean(b) => {
324 return Some(if *b {
325 self.true_literal().to_string()
326 } else {
327 self.false_literal().to_string()
328 });
329 }
330 DefaultValue::EnumVariant(v) => return Some(self.enum_variant_literal(v)),
331 }
332 }
333
334 if field.is_array {
335 Some(self.empty_array_literal().to_string())
336 } else if !field.is_required {
337 Some(self.null_literal().to_string())
338 } else {
339 None
340 }
341 }
342}
343
344pub struct PythonBackend;
346
347impl LanguageBackend for PythonBackend {
348 fn scalar_to_type(&self, scalar: &ScalarType) -> &'static str {
349 match scalar {
350 ScalarType::String => "str",
351 ScalarType::Int => "int",
352 ScalarType::BigInt => "int",
353 ScalarType::Float => "float",
354 ScalarType::Decimal { .. } => "Decimal",
355 ScalarType::Boolean => "bool",
356 ScalarType::DateTime => "datetime",
357 ScalarType::Bytes => "bytes",
358 ScalarType::Json => "Dict[str, Any]",
359 ScalarType::Uuid => "UUID",
360 ScalarType::Jsonb => "Dict[str, Any]",
361 ScalarType::Xml | ScalarType::Char { .. } | ScalarType::VarChar { .. } => "str",
362 }
363 }
364
365 fn array_type(&self, inner: &str) -> String {
366 format!("List[{}]", inner)
367 }
368
369 fn not_in_suffix(&self) -> &'static str {
370 "not_in"
371 }
372
373 fn startswith_suffix(&self) -> &'static str {
374 "startswith"
375 }
376
377 fn endswith_suffix(&self) -> &'static str {
378 "endswith"
379 }
380
381 fn null_suffix(&self) -> &'static str {
382 "is_null"
383 }
384
385 fn null_literal(&self) -> &'static str {
386 "None"
387 }
388 fn true_literal(&self) -> &'static str {
389 "True"
390 }
391 fn false_literal(&self) -> &'static str {
392 "False"
393 }
394 fn string_literal(&self, s: &str) -> String {
395 format!("\"{}\"", s)
396 }
397 fn empty_array_literal(&self) -> &'static str {
398 "Field(default_factory=list)"
399 }
400}
401
402pub struct JsBackend;
404
405impl LanguageBackend for JsBackend {
406 fn scalar_to_type(&self, scalar: &ScalarType) -> &'static str {
407 match scalar {
408 ScalarType::String => "string",
409 ScalarType::Int => "number",
410 ScalarType::BigInt => "number",
411 ScalarType::Float => "number",
412 ScalarType::Decimal { .. } => "string", ScalarType::Boolean => "boolean",
414 ScalarType::DateTime => "Date",
415 ScalarType::Bytes => "Buffer",
416 ScalarType::Json => "Record<string, unknown>",
417 ScalarType::Uuid => "string",
418 ScalarType::Jsonb => "Record<string, unknown>",
419 ScalarType::Xml | ScalarType::Char { .. } | ScalarType::VarChar { .. } => "string",
420 }
421 }
422
423 fn array_type(&self, inner: &str) -> String {
424 format!("{}[]", inner)
425 }
426
427 fn not_in_suffix(&self) -> &'static str {
428 "notIn"
429 }
430
431 fn startswith_suffix(&self) -> &'static str {
432 "startsWith"
433 }
434
435 fn endswith_suffix(&self) -> &'static str {
436 "endsWith"
437 }
438
439 fn null_suffix(&self) -> &'static str {
440 "isNull"
441 }
442
443 fn null_literal(&self) -> &'static str {
444 "null"
445 }
446 fn true_literal(&self) -> &'static str {
447 "true"
448 }
449 fn false_literal(&self) -> &'static str {
450 "false"
451 }
452 fn string_literal(&self, s: &str) -> String {
453 format!("'{}'", s)
454 }
455 fn empty_array_literal(&self) -> &'static str {
456 "[]"
457 }
458 fn enum_variant_literal(&self, variant: &str) -> String {
459 format!("'{}'", variant)
460 }
461 fn relation_type(&self, target_model: &str) -> String {
462 format!("{}Model", target_model)
463 }
464}