1use std::fmt;
10
11use crate::types::SurrealRecord;
12
13pub trait DynExpr: fmt::Debug + Send + Sync {
20 fn render_dyn(&self, buf: &mut String);
22}
23
24pub type DynExprBox = Box<dyn DynExpr>;
27
28impl DynExpr for Box<dyn DynExpr> {
29 fn render_dyn(&self, buf: &mut String) {
30 (**self).render_dyn(buf);
31 }
32}
33
34pub trait Expr: DynExpr {
41 fn ty_hint(&self) -> &'static str;
43}
44
45impl<E: DynExpr> Expr for E {
48 fn ty_hint(&self) -> &'static str {
49 "any"
50 }
51}
52
53pub trait SurrealQL: fmt::Debug + Clone + Send + Sync + 'static {
61 fn surreal_type() -> &'static str;
63 fn render_literal(value: &Self, buf: &mut String);
65}
66
67impl SurrealQL for String {
68 fn surreal_type() -> &'static str {
69 "string"
70 }
71 fn render_literal(value: &Self, buf: &mut String) {
72 let escaped = value.replace('\\', "\\\\").replace('\'', "\\'");
73 buf.push('\'');
74 buf.push_str(&escaped);
75 buf.push('\'');
76 }
77}
78
79impl SurrealQL for bool {
80 fn surreal_type() -> &'static str {
81 "bool"
82 }
83 fn render_literal(value: &Self, buf: &mut String) {
84 buf.push_str(if *value { "true" } else { "false" });
85 }
86}
87
88macro_rules! surreal_display {
89 ($t:ty, $name:literal) => {
90 impl SurrealQL for $t {
91 fn surreal_type() -> &'static str {
92 $name
93 }
94 fn render_literal(value: &Self, buf: &mut String) {
95 use std::fmt::Write;
96 let _ = write!(buf, "{value}");
97 }
98 }
99 };
100}
101surreal_display!(i64, "int");
102surreal_display!(i32, "int");
103surreal_display!(i16, "int");
104surreal_display!(i8, "int");
105surreal_display!(f64, "float");
106surreal_display!(f32, "float");
107surreal_display!(u32, "int");
108surreal_display!(u64, "int");
109surreal_display!(u16, "int");
110surreal_display!(u8, "int");
111
112impl SurrealQL for chrono::DateTime<chrono::Utc> {
113 fn surreal_type() -> &'static str {
114 "datetime"
115 }
116 fn render_literal(value: &Self, buf: &mut String) {
117 buf.push_str("d'");
121 buf.push_str(&value.to_rfc3339());
122 buf.push('\'');
123 }
124}
125
126impl SurrealQL for uuid::Uuid {
127 fn surreal_type() -> &'static str {
128 "uuid"
129 }
130 fn render_literal(value: &Self, buf: &mut String) {
131 use std::fmt::Write;
132 buf.push_str("u'");
135 let _ = write!(buf, "{value}");
136 buf.push('\'');
137 }
138}
139
140impl SurrealQL for serde_json::Value {
141 fn surreal_type() -> &'static str {
142 "object"
143 }
144 fn render_literal(value: &Self, buf: &mut String) {
145 use std::fmt::Write;
149 let _ = write!(buf, "{value}");
150 }
151}
152
153macro_rules! geometry_surrealql {
156 ($t:ident, $name:literal) => {
157 impl SurrealQL for crate::types::$t {
158 fn surreal_type() -> &'static str {
159 $name
160 }
161 fn render_literal(value: &Self, buf: &mut String) {
162 if let Ok(s) = serde_json::to_string(value) {
163 buf.push_str(&s);
164 }
165 }
166 }
167 };
168}
169geometry_surrealql!(Point, "geometry<point>");
170geometry_surrealql!(LineString, "geometry<line>");
171geometry_surrealql!(Polygon, "geometry<polygon>");
172
173impl<T: SurrealQL> SurrealQL for Option<T> {
174 fn surreal_type() -> &'static str {
175 T::surreal_type()
176 }
177 fn render_literal(value: &Self, buf: &mut String) {
178 match value {
179 Some(v) => T::render_literal(v, buf),
180 None => buf.push_str("NONE"),
181 }
182 }
183}
184
185impl<V: SurrealQL> SurrealQL for Vec<V> {
186 fn surreal_type() -> &'static str {
187 "array"
190 }
191 fn render_literal(value: &Self, buf: &mut String) {
192 buf.push('[');
193 for (i, v) in value.iter().enumerate() {
194 if i > 0 {
195 buf.push_str(", ");
196 }
197 V::render_literal(v, buf);
198 }
199 buf.push(']');
200 }
201}
202
203impl SurrealQL for std::time::Duration {
204 fn surreal_type() -> &'static str {
205 "duration"
206 }
207 fn render_literal(value: &Self, buf: &mut String) {
208 use std::fmt::Write;
209 let secs = value.as_secs();
213 let nanos = value.subsec_nanos();
214 if secs == 0 && nanos == 0 {
215 buf.push_str("0ns");
216 return;
217 }
218 if secs > 0 {
219 let _ = write!(buf, "{secs}s");
220 }
221 if nanos > 0 {
222 let _ = write!(buf, "{nanos}ns");
223 }
224 }
225}
226
227impl<T: crate::types::SurrealRecord> SurrealQL for crate::types::Thing<T> {
228 fn surreal_type() -> &'static str {
229 "record"
230 }
231 fn render_literal(value: &Self, buf: &mut String) {
232 buf.push_str(T::table_name());
233 buf.push(':');
234 value.key.render_id(buf);
235 }
236}
237
238#[derive(Debug, Clone)]
243pub struct Literal<V: SurrealQL>(pub V);
244
245impl<V: SurrealQL> DynExpr for Literal<V> {
246 fn render_dyn(&self, buf: &mut String) {
247 V::render_literal(&self.0, buf);
248 }
249}
250
251pub struct Column<T: SurrealRecord, V: SurrealQL> {
259 pub name: &'static str,
261 pub surreal_type: &'static str,
263 #[doc(hidden)]
264 pub _marker: std::marker::PhantomData<(T, V)>,
265}
266
267impl<T: SurrealRecord, V: SurrealQL> std::fmt::Debug for Column<T, V> {
268 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
269 f.debug_struct("Column").field("name", &self.name).finish()
270 }
271}
272
273impl<T: SurrealRecord, V: SurrealQL> Clone for Column<T, V> {
274 fn clone(&self) -> Self {
275 *self
276 }
277}
278impl<T: SurrealRecord, V: SurrealQL> Copy for Column<T, V> {}
279
280impl<T: SurrealRecord, V: SurrealQL> DynExpr for Column<T, V> {
281 fn render_dyn(&self, buf: &mut String) {
282 buf.push_str(self.name);
283 }
284}
285
286#[derive(Debug, Clone, Copy)]
294pub struct Ident(pub &'static str);
295
296pub fn ident(name: &'static str) -> Ident {
298 Ident(name)
299}
300
301impl Ident {
302 fn dyn_box(&self) -> Box<dyn DynExpr> {
303 Box::new(Raw(self.0.to_string()))
304 }
305
306 pub fn eq<V: SurrealQL>(&self, v: V) -> EqExpr {
307 EqExpr {
308 left: self.dyn_box(),
309 right: Box::new(Literal(v)),
310 }
311 }
312 pub fn ne<V: SurrealQL>(&self, v: V) -> NeExpr {
313 NeExpr {
314 left: self.dyn_box(),
315 right: Box::new(Literal(v)),
316 }
317 }
318 pub fn gt<V: SurrealQL>(&self, v: V) -> GtExpr {
319 GtExpr {
320 left: self.dyn_box(),
321 right: Box::new(Literal(v)),
322 }
323 }
324 pub fn lt<V: SurrealQL>(&self, v: V) -> LtExpr {
325 LtExpr {
326 left: self.dyn_box(),
327 right: Box::new(Literal(v)),
328 }
329 }
330 pub fn gte<V: SurrealQL>(&self, v: V) -> GteExpr {
331 GteExpr {
332 left: self.dyn_box(),
333 right: Box::new(Literal(v)),
334 }
335 }
336 pub fn lte<V: SurrealQL>(&self, v: V) -> LteExpr {
337 LteExpr {
338 left: self.dyn_box(),
339 right: Box::new(Literal(v)),
340 }
341 }
342 pub fn contains<V: SurrealQL>(&self, v: V) -> ContainsExpr {
343 ContainsExpr {
344 haystack: self.dyn_box(),
345 needle: Box::new(Literal(v)),
346 }
347 }
348 pub fn eq_expr(&self, rhs: impl DynExpr + 'static) -> EqExpr {
350 EqExpr {
351 left: self.dyn_box(),
352 right: Box::new(rhs),
353 }
354 }
355 pub fn ne_expr(&self, rhs: impl DynExpr + 'static) -> NeExpr {
356 NeExpr {
357 left: self.dyn_box(),
358 right: Box::new(rhs),
359 }
360 }
361 pub fn is_none(&self) -> Raw {
363 Raw(format!("{} IS NONE", self.0))
364 }
365}
366
367impl DynExpr for Ident {
368 fn render_dyn(&self, buf: &mut String) {
369 buf.push_str(self.0);
370 }
371}
372
373#[derive(Debug, Clone)]
380pub struct Raw(pub String);
381
382impl Raw {
383 pub fn new(s: impl Into<String>) -> Self {
384 Self(s.into())
385 }
386}
387
388impl DynExpr for Raw {
389 fn render_dyn(&self, buf: &mut String) {
390 buf.push_str(&self.0);
391 }
392}
393
394#[derive(Debug, Clone)]
396pub struct NoneLit;
397impl DynExpr for NoneLit {
398 fn render_dyn(&self, buf: &mut String) {
399 buf.push_str("NONE");
400 }
401}
402
403#[derive(Debug)]
410pub struct RecordLink {
411 table: &'static str,
412 key: Box<dyn DynExpr>,
413}
414
415impl RecordLink {
416 pub fn new<V: SurrealQL>(table: &'static str, key: V) -> Self {
418 Self {
419 table,
420 key: Box::new(Literal(key)),
421 }
422 }
423 pub fn from_expr(table: &'static str, key: impl DynExpr + 'static) -> Self {
425 Self {
426 table,
427 key: Box::new(key),
428 }
429 }
430}
431
432impl DynExpr for RecordLink {
433 fn render_dyn(&self, buf: &mut String) {
434 buf.push_str("type::record('");
435 buf.push_str(self.table);
436 buf.push_str("', ");
437 self.key.render_dyn(buf);
438 buf.push(')');
439 }
440}
441
442#[derive(Debug, Clone, Copy, PartialEq, Eq)]
448enum Dir {
449 Out,
451 In,
453 Both,
455}
456
457impl Dir {
458 fn arrow(self) -> &'static str {
459 match self {
460 Dir::Out => "->",
461 Dir::In => "<-",
462 Dir::Both => "<->",
463 }
464 }
465}
466
467#[derive(Debug)]
470struct Step {
471 dir: Dir,
472 edge: &'static str,
473 dest: Option<&'static str>,
474 filter: Option<Box<dyn DynExpr>>,
475}
476
477impl Step {
478 fn render(&self, buf: &mut String) {
479 buf.push_str(self.dir.arrow());
480 match &self.filter {
481 Some(f) => {
483 buf.push('(');
484 buf.push_str(self.edge);
485 buf.push_str(" WHERE ");
486 f.render_dyn(buf);
487 buf.push(')');
488 }
489 None => buf.push_str(self.edge),
490 }
491 if let Some(dest) = self.dest {
492 buf.push_str(self.dir.arrow());
493 buf.push_str(dest);
494 }
495 }
496}
497
498#[derive(Debug)]
500enum Tail {
501 Field(String),
502 All,
503}
504
505#[derive(Debug)]
519pub struct Path {
520 start: Option<Box<dyn DynExpr>>,
521 recurse: Option<String>,
522 steps: Vec<Step>,
523 tail: Option<Tail>,
524}
525
526impl Path {
527 fn from_step(dir: Dir, edge: &'static str) -> Self {
528 Self {
529 start: None,
530 recurse: None,
531 steps: vec![Step {
532 dir,
533 edge,
534 dest: None,
535 filter: None,
536 }],
537 tail: None,
538 }
539 }
540
541 pub fn out<E: crate::types::SurrealEdge>() -> Self {
543 Self::from_step(Dir::Out, E::edge_name())
544 }
545 pub fn inn<E: crate::types::SurrealEdge>() -> Self {
547 Self::from_step(Dir::In, E::edge_name())
548 }
549 pub fn both<E: crate::types::SurrealEdge>() -> Self {
551 Self::from_step(Dir::Both, E::edge_name())
552 }
553
554 pub fn out_edge(edge: &'static str) -> Self {
556 Self::from_step(Dir::Out, edge)
557 }
558 pub fn in_edge(edge: &'static str) -> Self {
560 Self::from_step(Dir::In, edge)
561 }
562 pub fn both_edge(edge: &'static str) -> Self {
564 Self::from_step(Dir::Both, edge)
565 }
566
567 pub fn from_record<V: SurrealQL>(mut self, start: V) -> Self {
570 self.start = Some(Box::new(Literal(start)));
571 self
572 }
573
574 pub fn from_expr(mut self, start: impl DynExpr + 'static) -> Self {
577 self.start = Some(Box::new(start));
578 self
579 }
580
581 fn last_mut(&mut self) -> &mut Step {
582 self.steps.last_mut().expect("path always has ≥1 step")
583 }
584
585 pub fn to<T: SurrealRecord>(mut self) -> Self {
587 self.last_mut().dest = Some(T::table_name());
588 self
589 }
590 pub fn to_table(mut self, table: &'static str) -> Self {
592 self.last_mut().dest = Some(table);
593 self
594 }
595
596 pub fn where_(mut self, expr: impl DynExpr + 'static) -> Self {
598 self.last_mut().filter = Some(Box::new(expr));
599 self
600 }
601
602 pub fn then_out<E: crate::types::SurrealEdge>(self) -> Self {
604 self.push_step(Dir::Out, E::edge_name())
605 }
606 pub fn then_in<E: crate::types::SurrealEdge>(self) -> Self {
608 self.push_step(Dir::In, E::edge_name())
609 }
610 pub fn then_both<E: crate::types::SurrealEdge>(self) -> Self {
612 self.push_step(Dir::Both, E::edge_name())
613 }
614 pub fn then_out_edge(self, edge: &'static str) -> Self {
616 self.push_step(Dir::Out, edge)
617 }
618 pub fn then_in_edge(self, edge: &'static str) -> Self {
620 self.push_step(Dir::In, edge)
621 }
622
623 fn push_step(mut self, dir: Dir, edge: &'static str) -> Self {
624 self.steps.push(Step {
625 dir,
626 edge,
627 dest: None,
628 filter: None,
629 });
630 self
631 }
632
633 pub fn recurse_all(mut self) -> Self {
637 self.recurse = Some("..".to_string());
638 self
639 }
640 pub fn recurse_up_to(mut self, max: u32) -> Self {
642 self.recurse = Some(format!("..{max}"));
643 self
644 }
645 pub fn recurse_range(mut self, min: u32, max: u32) -> Self {
647 self.recurse = Some(format!("{min}..{max}"));
648 self
649 }
650 pub fn recurse_exact(mut self, n: u32) -> Self {
652 self.recurse = Some(format!("{n}"));
653 self
654 }
655
656 pub fn field(mut self, name: impl Into<String>) -> Self {
658 self.tail = Some(Tail::Field(name.into()));
659 self
660 }
661 pub fn all(mut self) -> Self {
663 self.tail = Some(Tail::All);
664 self
665 }
666
667 pub fn contains<V: SurrealQL>(self, value: V) -> ContainsExpr {
669 ContainsExpr {
670 haystack: Box::new(self),
671 needle: Box::new(Literal(value)),
672 }
673 }
674 pub fn eq_expr(self, rhs: impl DynExpr + 'static) -> EqExpr {
676 EqExpr {
677 left: Box::new(self),
678 right: Box::new(rhs),
679 }
680 }
681}
682
683impl DynExpr for Path {
684 fn render_dyn(&self, buf: &mut String) {
685 match (&self.start, &self.recurse) {
686 (Some(start), Some(range)) => {
688 start.render_dyn(buf);
689 buf.push_str(".{");
690 buf.push_str(range);
691 buf.push('}');
692 }
693 (None, Some(range)) => {
695 buf.push_str("@.{");
696 buf.push_str(range);
697 buf.push('}');
698 }
699 (Some(start), None) => start.render_dyn(buf),
701 (None, None) => {}
702 }
703 for step in &self.steps {
704 step.render(buf);
705 }
706 match &self.tail {
707 Some(Tail::Field(f)) => {
708 buf.push('.');
709 buf.push_str(f);
710 }
711 Some(Tail::All) => buf.push_str(".*"),
712 None => {}
713 }
714 }
715}
716
717#[derive(Debug)]
723pub struct Func {
724 name: &'static str,
725 args: Vec<Box<dyn DynExpr>>,
726}
727
728impl Func {
729 pub fn new(name: &'static str, args: Vec<Box<dyn DynExpr>>) -> Self {
730 Self { name, args }
731 }
732 pub fn of(name: &'static str, ident: &'static str) -> Self {
735 Self {
736 name,
737 args: vec![Box::new(Raw(ident.to_string()))],
738 }
739 }
740}
741
742impl DynExpr for Func {
743 fn render_dyn(&self, buf: &mut String) {
744 buf.push_str(self.name);
745 buf.push('(');
746 for (i, a) in self.args.iter().enumerate() {
747 if i > 0 {
748 buf.push_str(", ");
749 }
750 a.render_dyn(buf);
751 }
752 buf.push(')');
753 }
754}
755
756macro_rules! binop {
761 ($name:ident, $op:literal) => {
762 #[derive(Debug)]
763 pub struct $name {
764 pub(crate) left: Box<dyn DynExpr>,
765 pub(crate) right: Box<dyn DynExpr>,
766 }
767 impl DynExpr for $name {
768 fn render_dyn(&self, buf: &mut String) {
769 self.left.render_dyn(buf);
770 buf.push(' ');
771 buf.push_str($op);
772 buf.push(' ');
773 self.right.render_dyn(buf);
774 }
775 }
776 };
777}
778
779binop!(EqExpr, "=");
780binop!(NeExpr, "!=");
781binop!(GtExpr, ">");
782binop!(LtExpr, "<");
783binop!(GteExpr, ">=");
784binop!(LteExpr, "<=");
785binop!(AndExpr, "AND");
786binop!(OrExpr, "OR");
787
788#[derive(Debug)]
789pub struct NotExpr {
790 pub(crate) inner: Box<dyn DynExpr>,
791}
792
793impl DynExpr for NotExpr {
794 fn render_dyn(&self, buf: &mut String) {
795 buf.push_str("NOT ");
796 self.inner.render_dyn(buf);
797 }
798}
799
800#[derive(Debug)]
801pub struct ContainsExpr {
802 pub(crate) haystack: Box<dyn DynExpr>,
803 pub(crate) needle: Box<dyn DynExpr>,
804}
805
806impl DynExpr for ContainsExpr {
807 fn render_dyn(&self, buf: &mut String) {
808 self.haystack.render_dyn(buf);
809 buf.push_str(" CONTAINS ");
810 self.needle.render_dyn(buf);
811 }
812}
813
814impl<T: SurrealRecord, V: SurrealQL> Column<T, V> {
819 pub fn eq(&self, value: V) -> EqExpr {
820 EqExpr {
821 left: self.dyn_box(),
822 right: Box::new(Literal(value)),
823 }
824 }
825 pub fn ne(&self, value: V) -> NeExpr {
826 NeExpr {
827 left: self.dyn_box(),
828 right: Box::new(Literal(value)),
829 }
830 }
831 pub fn gt(&self, value: V) -> GtExpr {
832 GtExpr {
833 left: self.dyn_box(),
834 right: Box::new(Literal(value)),
835 }
836 }
837 pub fn lt(&self, value: V) -> LtExpr {
838 LtExpr {
839 left: self.dyn_box(),
840 right: Box::new(Literal(value)),
841 }
842 }
843 pub fn gte(&self, value: V) -> GteExpr {
844 GteExpr {
845 left: self.dyn_box(),
846 right: Box::new(Literal(value)),
847 }
848 }
849 pub fn lte(&self, value: V) -> LteExpr {
850 LteExpr {
851 left: self.dyn_box(),
852 right: Box::new(Literal(value)),
853 }
854 }
855 pub fn contains(&self, value: V) -> ContainsExpr {
856 ContainsExpr {
857 haystack: self.dyn_box(),
858 needle: Box::new(Literal(value)),
859 }
860 }
861
862 pub fn eq_expr(&self, rhs: impl DynExpr + 'static) -> EqExpr {
865 EqExpr {
866 left: self.dyn_box(),
867 right: Box::new(rhs),
868 }
869 }
870 pub fn ne_expr(&self, rhs: impl DynExpr + 'static) -> NeExpr {
871 NeExpr {
872 left: self.dyn_box(),
873 right: Box::new(rhs),
874 }
875 }
876 pub fn is_none(&self) -> Raw {
878 Raw(format!("{} IS NONE", self.name))
879 }
880
881 fn dyn_box(&self) -> Box<dyn DynExpr> {
882 Box::new(Self {
883 name: self.name,
884 surreal_type: self.surreal_type,
885 _marker: self._marker,
886 })
887 }
888}
889
890macro_rules! combinators {
892 ($($t:ty),* $(,)?) => {$(
893 impl $t {
894 pub fn and(self, other: impl DynExpr + 'static) -> AndExpr {
895 AndExpr { left: Box::new(self), right: Box::new(other) }
896 }
897 pub fn or(self, other: impl DynExpr + 'static) -> OrExpr {
898 OrExpr { left: Box::new(self), right: Box::new(other) }
899 }
900 }
901 )*};
902}
903combinators!(
904 EqExpr,
905 NeExpr,
906 GtExpr,
907 LtExpr,
908 GteExpr,
909 LteExpr,
910 AndExpr,
911 OrExpr,
912 ContainsExpr,
913 NotExpr,
914 Raw
915);
916
917#[derive(Debug)]
919pub struct Grouped(pub Box<dyn DynExpr>);
920
921impl Grouped {
922 pub fn new(inner: impl DynExpr + 'static) -> Self {
923 Self(Box::new(inner))
924 }
925}
926
927impl DynExpr for Grouped {
928 fn render_dyn(&self, buf: &mut String) {
929 buf.push('(');
930 self.0.render_dyn(buf);
931 buf.push(')');
932 }
933}
934
935combinators!(Grouped, Func, Path);
936
937#[derive(Debug)]
943pub struct Projection {
944 expr: Box<dyn DynExpr>,
945 alias: Option<&'static str>,
946}
947
948impl Projection {
949 pub fn new(expr: impl DynExpr + 'static) -> Self {
951 Self {
952 expr: Box::new(expr),
953 alias: None,
954 }
955 }
956 pub fn aliased(expr: impl DynExpr + 'static, alias: &'static str) -> Self {
958 Self {
959 expr: Box::new(expr),
960 alias: Some(alias),
961 }
962 }
963 pub fn render(&self, buf: &mut String) {
964 self.expr.render_dyn(buf);
965 if let Some(a) = self.alias {
966 buf.push_str(" AS ");
967 buf.push_str(a);
968 }
969 }
970}
971
972pub fn col(name: &'static str) -> Projection {
974 Projection::new(Raw(name.to_string()))
975}
976
977pub fn field(raw: &'static str, alias: &'static str) -> Projection {
979 Projection::aliased(Raw(raw.to_string()), alias)
980}
981
982#[derive(Debug, Clone)]
987pub struct ColumnMeta {
988 pub name: &'static str,
989 pub surreal_type: &'static str,
990}
991
992pub struct ColumnSet<T: SurrealRecord> {
994 pub cols: &'static [ColumnMeta],
995 pub _marker: std::marker::PhantomData<T>,
996}
997
998impl<T: SurrealRecord> std::fmt::Debug for ColumnSet<T> {
999 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1000 f.debug_struct("ColumnSet")
1001 .field("cols", &self.cols)
1002 .finish()
1003 }
1004}
1005
1006impl<T: SurrealRecord> DynExpr for ColumnSet<T> {
1007 fn render_dyn(&self, buf: &mut String) {
1008 buf.push('*');
1009 }
1010}
1011
1012#[derive(Debug, Clone, Copy)]
1018pub enum Order {
1019 Asc,
1021 Desc,
1023}
1024
1025impl Order {
1026 pub fn render_suffix(&self) -> &'static str {
1027 match self {
1028 Order::Asc => "ASC",
1029 Order::Desc => "DESC",
1030 }
1031 }
1032}
1033
1034impl std::fmt::Display for Order {
1035 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1036 f.write_str(self.render_suffix())
1037 }
1038}