1use argon2::Argon2;
2use argon2::password_hash::{PasswordHasher, PasswordVerifier, phc::PasswordHash};
3use chrono::{DateTime, NaiveDate, NaiveTime, Utc};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7pub trait RangoField {
9 type Inner;
10 fn column_type() -> crate::schema::ColumnType;
11 fn into_inner(self) -> Self::Inner;
12}
13
14#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
17pub struct FieldBool(pub bool);
18
19#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct FieldSmallInt(pub i16);
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub struct FieldInt(pub i32);
24
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct FieldBigInt(pub i64);
27
28#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
31pub struct FieldAutoInt(pub Option<i32>);
32
33#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
36pub struct FieldAutoBigInt(pub Option<i64>);
37
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39pub struct FieldFloat(pub f32);
40
41#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
42pub struct FieldDouble(pub f64);
43
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct FieldDecimal<const PRECISION: u8, const SCALE: u8>(pub rust_decimal::Decimal);
46
47impl<const P: u8, const S: u8> FieldDecimal<P, S> {
48 pub fn from_decimal(mut value: rust_decimal::Decimal) -> Self {
51 value.rescale(u32::from(S));
52 Self(value)
53 }
54}
55
56#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
59pub struct FieldText(pub String);
60
61#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
66#[serde(try_from = "String")]
67pub struct FieldVarchar<const MIN: usize, const MAX: usize>(String);
68
69impl<const MIN: usize, const MAX: usize> FieldVarchar<MIN, MAX> {
70 pub fn new(s: impl Into<String>) -> Result<Self, FieldError> {
71 let s = s.into();
72 let len = s.chars().count();
73 if len < MIN {
74 return Err(FieldError(format!("Value too short (min {})", MIN)));
75 }
76 if len > MAX {
77 return Err(FieldError(format!("Value too long (max {})", MAX)));
78 }
79 Ok(Self(s))
80 }
81
82 pub fn as_str(&self) -> &str {
83 &self.0
84 }
85
86 pub fn into_inner(self) -> String {
87 self.0
88 }
89
90 #[doc(hidden)]
93 pub fn from_db(s: String) -> Self {
94 Self(s)
95 }
96}
97
98impl<const MIN: usize, const MAX: usize> TryFrom<String> for FieldVarchar<MIN, MAX> {
99 type Error = FieldError;
100 fn try_from(s: String) -> Result<Self, FieldError> {
101 Self::new(s)
102 }
103}
104
105impl<const MIN: usize, const MAX: usize> std::ops::Deref for FieldVarchar<MIN, MAX> {
106 type Target = str;
107 fn deref(&self) -> &str {
108 &self.0
109 }
110}
111
112impl<const MIN: usize, const MAX: usize> std::fmt::Display for FieldVarchar<MIN, MAX> {
113 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
114 f.write_str(&self.0)
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
120#[serde(try_from = "String")]
121pub struct FieldEmail(String);
122
123impl FieldEmail {
124 pub fn new(s: impl Into<String>) -> Result<Self, FieldError> {
125 let s = s.into();
126 if !s.contains('@') || s.chars().count() > 254 {
127 return Err(FieldError("Invalid email address".into()));
128 }
129 Ok(Self(s))
130 }
131
132 pub fn as_str(&self) -> &str {
133 &self.0
134 }
135
136 pub fn into_inner(self) -> String {
137 self.0
138 }
139
140 #[doc(hidden)]
142 pub fn from_db(s: String) -> Self {
143 Self(s)
144 }
145}
146
147impl TryFrom<String> for FieldEmail {
148 type Error = FieldError;
149 fn try_from(s: String) -> Result<Self, FieldError> {
150 Self::new(s)
151 }
152}
153
154impl std::ops::Deref for FieldEmail {
155 type Target = str;
156 fn deref(&self) -> &str {
157 &self.0
158 }
159}
160
161impl std::fmt::Display for FieldEmail {
162 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
163 f.write_str(&self.0)
164 }
165}
166
167#[derive(Clone, PartialEq)]
196pub struct FieldPassword<const MIN: usize, const MAX: usize> {
197 hash: String,
198}
199
200impl<const MIN: usize, const MAX: usize> std::fmt::Debug for FieldPassword<MIN, MAX> {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 write!(f, "FieldPassword(<redacted>)")
203 }
204}
205
206impl<const MIN: usize, const MAX: usize> FieldPassword<MIN, MAX> {
207 pub fn new(plain: impl AsRef<str>) -> Result<Self, FieldError> {
208 let plain = plain.as_ref();
209 let len = plain.chars().count();
210 if len < MIN {
211 return Err(FieldError(format!("Password too short (min {})", MIN)));
212 }
213 if len > MAX {
214 return Err(FieldError(format!("Password too long (max {})", MAX)));
215 }
216
217 let hash = Argon2::default()
218 .hash_password(plain.as_bytes())
219 .map_err(|e| FieldError(e.to_string()))?;
220
221 Ok(Self {
222 hash: hash.to_string(),
223 })
224 }
225
226 pub fn verify(&self, candidate: &str) -> bool {
227 let Ok(parsed) = PasswordHash::new(&self.hash) else {
228 return false;
229 };
230
231 Argon2::default()
232 .verify_password(candidate.as_bytes(), &parsed)
233 .is_ok()
234 }
235
236 pub fn from_hash(hash: String) -> Self {
237 Self { hash }
238 }
239
240 pub fn hash(&self) -> &str {
241 &self.hash
242 }
243}
244
245#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247#[serde(try_from = "String")]
248pub struct FieldUrl(String);
249
250impl FieldUrl {
251 pub fn new(s: impl Into<String>) -> Result<Self, FieldError> {
252 let s = s.into();
253 if !s.starts_with("http://") && !s.starts_with("https://") {
254 return Err(FieldError("URL must start with http:// or https://".into()));
255 }
256 Ok(Self(s))
257 }
258
259 pub fn as_str(&self) -> &str {
260 &self.0
261 }
262
263 pub fn into_inner(self) -> String {
264 self.0
265 }
266
267 #[doc(hidden)]
269 pub fn from_db(s: String) -> Self {
270 Self(s)
271 }
272}
273
274impl TryFrom<String> for FieldUrl {
275 type Error = FieldError;
276 fn try_from(s: String) -> Result<Self, FieldError> {
277 Self::new(s)
278 }
279}
280
281impl std::ops::Deref for FieldUrl {
282 type Target = str;
283 fn deref(&self) -> &str {
284 &self.0
285 }
286}
287
288impl std::fmt::Display for FieldUrl {
289 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
290 f.write_str(&self.0)
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
297pub struct FieldBytes(pub Vec<u8>);
298
299#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
302pub struct FieldUuid(pub Uuid);
303
304impl FieldUuid {
305 pub fn new() -> Self {
307 Self(Uuid::new_v4())
308 }
309}
310
311impl Default for FieldUuid {
312 fn default() -> Self {
313 Self::new()
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
320pub struct FieldDate(pub NaiveDate);
321
322#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
323pub struct FieldTime(pub NaiveTime);
324
325#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
326pub struct FieldDateTime(pub DateTime<Utc>);
327
328impl FieldDateTime {
329 pub fn now() -> Self {
330 Self(Utc::now())
331 }
332}
333
334#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337pub struct FieldJson(pub serde_json::Value);
338
339#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
343#[serde(try_from = "i64")]
344pub struct FieldRange<const MIN: i64, const MAX: i64>(i64);
345
346impl<const MIN: i64, const MAX: i64> FieldRange<MIN, MAX> {
347 pub fn new(val: i64) -> Result<Self, FieldError> {
348 if val < MIN || val > MAX {
349 return Err(FieldError(format!(
350 "Value {} out of range [{}, {}]",
351 val, MIN, MAX
352 )));
353 }
354 Ok(Self(val))
355 }
356
357 pub fn get(&self) -> i64 {
358 self.0
359 }
360
361 #[doc(hidden)]
363 pub fn from_db(v: i64) -> Self {
364 Self(v)
365 }
366}
367
368impl<const MIN: i64, const MAX: i64> TryFrom<i64> for FieldRange<MIN, MAX> {
369 type Error = FieldError;
370 fn try_from(v: i64) -> Result<Self, FieldError> {
371 Self::new(v)
372 }
373}
374
375#[derive(Debug, Clone, PartialEq)]
378pub struct FieldError(pub String);
379
380impl std::fmt::Display for FieldError {
381 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382 write!(f, "{}", self.0)
383 }
384}
385
386impl std::error::Error for FieldError {}
387
388use crate::query::{SqlValue, ToSqlValue};
391
392impl ToSqlValue for FieldBool {
393 fn to_sql_value(&self) -> SqlValue {
394 SqlValue::Bool(self.0)
395 }
396}
397impl ToSqlValue for FieldSmallInt {
398 fn to_sql_value(&self) -> SqlValue {
399 SqlValue::SmallInt(self.0)
400 }
401}
402impl ToSqlValue for FieldInt {
403 fn to_sql_value(&self) -> SqlValue {
404 SqlValue::Int(self.0)
405 }
406}
407impl ToSqlValue for FieldBigInt {
408 fn to_sql_value(&self) -> SqlValue {
409 SqlValue::BigInt(self.0)
410 }
411}
412impl ToSqlValue for FieldAutoInt {
413 fn to_sql_value(&self) -> SqlValue {
414 self.0.map_or(SqlValue::NullInt, SqlValue::Int)
415 }
416}
417impl ToSqlValue for FieldAutoBigInt {
418 fn to_sql_value(&self) -> SqlValue {
419 self.0.map_or(SqlValue::NullBigInt, SqlValue::BigInt)
420 }
421}
422impl ToSqlValue for FieldFloat {
423 fn to_sql_value(&self) -> SqlValue {
424 SqlValue::Float(self.0)
425 }
426}
427impl ToSqlValue for FieldDouble {
428 fn to_sql_value(&self) -> SqlValue {
429 SqlValue::Double(self.0)
430 }
431}
432impl ToSqlValue for FieldText {
433 fn to_sql_value(&self) -> SqlValue {
434 SqlValue::Text(self.0.clone())
435 }
436}
437impl ToSqlValue for FieldEmail {
438 fn to_sql_value(&self) -> SqlValue {
439 SqlValue::Text(self.0.clone())
440 }
441}
442impl ToSqlValue for FieldUrl {
443 fn to_sql_value(&self) -> SqlValue {
444 SqlValue::Text(self.0.clone())
445 }
446}
447impl ToSqlValue for FieldBytes {
448 fn to_sql_value(&self) -> SqlValue {
449 SqlValue::Bytes(self.0.clone())
450 }
451}
452impl ToSqlValue for Uuid {
454 fn to_sql_value(&self) -> SqlValue {
455 SqlValue::Uuid(*self)
456 }
457}
458impl ToSqlValue for i32 {
459 fn to_sql_value(&self) -> SqlValue {
460 SqlValue::Int(*self)
461 }
462}
463impl ToSqlValue for i64 {
464 fn to_sql_value(&self) -> SqlValue {
465 SqlValue::BigInt(*self)
466 }
467}
468impl ToSqlValue for FieldUuid {
469 fn to_sql_value(&self) -> SqlValue {
470 SqlValue::Uuid(self.0)
471 }
472}
473impl ToSqlValue for FieldDateTime {
474 fn to_sql_value(&self) -> SqlValue {
475 SqlValue::DateTime(self.0)
476 }
477}
478impl ToSqlValue for FieldDate {
479 fn to_sql_value(&self) -> SqlValue {
480 SqlValue::Date(self.0)
481 }
482}
483impl ToSqlValue for FieldTime {
484 fn to_sql_value(&self) -> SqlValue {
485 SqlValue::Time(self.0)
486 }
487}
488impl ToSqlValue for FieldJson {
489 fn to_sql_value(&self) -> SqlValue {
490 SqlValue::Json(self.0.clone())
491 }
492}
493
494impl<const MIN: usize, const MAX: usize> ToSqlValue for FieldVarchar<MIN, MAX> {
495 fn to_sql_value(&self) -> SqlValue {
496 SqlValue::Text(self.0.clone())
497 }
498}
499impl<const MIN: usize, const MAX: usize> ToSqlValue for FieldPassword<MIN, MAX> {
500 fn to_sql_value(&self) -> SqlValue {
501 SqlValue::Text(self.hash().to_string())
502 }
503}
504impl<const MIN: i64, const MAX: i64> ToSqlValue for FieldRange<MIN, MAX> {
505 fn to_sql_value(&self) -> SqlValue {
506 SqlValue::BigInt(self.0)
507 }
508}
509impl<const P: u8, const S: u8> ToSqlValue for FieldDecimal<P, S> {
510 fn to_sql_value(&self) -> SqlValue {
511 SqlValue::Decimal(self.0)
512 }
513}
514impl<const P: u8, const S: u8> ToSqlValue for Option<FieldDecimal<P, S>> {
515 fn to_sql_value(&self) -> SqlValue {
516 self.as_ref()
517 .map_or(SqlValue::NullDecimal, |v| v.to_sql_value())
518 }
519}
520
521impl ToSqlValue for Option<FieldBool> {
523 fn to_sql_value(&self) -> SqlValue {
524 self.as_ref()
525 .map_or(SqlValue::NullBool, |v| v.to_sql_value())
526 }
527}
528impl ToSqlValue for Option<FieldSmallInt> {
529 fn to_sql_value(&self) -> SqlValue {
530 self.as_ref()
531 .map_or(SqlValue::NullSmallInt, |v| v.to_sql_value())
532 }
533}
534impl ToSqlValue for Option<FieldInt> {
535 fn to_sql_value(&self) -> SqlValue {
536 self.as_ref()
537 .map_or(SqlValue::NullInt, |v| v.to_sql_value())
538 }
539}
540impl ToSqlValue for Option<FieldBigInt> {
541 fn to_sql_value(&self) -> SqlValue {
542 self.as_ref()
543 .map_or(SqlValue::NullBigInt, |v| v.to_sql_value())
544 }
545}
546impl ToSqlValue for Option<FieldFloat> {
547 fn to_sql_value(&self) -> SqlValue {
548 self.as_ref()
549 .map_or(SqlValue::NullFloat, |v| v.to_sql_value())
550 }
551}
552impl ToSqlValue for Option<FieldDouble> {
553 fn to_sql_value(&self) -> SqlValue {
554 self.as_ref()
555 .map_or(SqlValue::NullDouble, |v| v.to_sql_value())
556 }
557}
558impl ToSqlValue for Option<FieldText> {
559 fn to_sql_value(&self) -> SqlValue {
560 self.as_ref()
561 .map_or(SqlValue::NullText, |v| v.to_sql_value())
562 }
563}
564impl ToSqlValue for Option<FieldEmail> {
565 fn to_sql_value(&self) -> SqlValue {
566 self.as_ref()
567 .map_or(SqlValue::NullText, |v| v.to_sql_value())
568 }
569}
570impl ToSqlValue for Option<FieldUrl> {
571 fn to_sql_value(&self) -> SqlValue {
572 self.as_ref()
573 .map_or(SqlValue::NullText, |v| v.to_sql_value())
574 }
575}
576impl ToSqlValue for Option<FieldBytes> {
577 fn to_sql_value(&self) -> SqlValue {
578 self.as_ref()
579 .map_or(SqlValue::NullBytes, |v| v.to_sql_value())
580 }
581}
582impl ToSqlValue for Option<FieldUuid> {
583 fn to_sql_value(&self) -> SqlValue {
584 self.as_ref()
585 .map_or(SqlValue::NullUuid, |v| v.to_sql_value())
586 }
587}
588impl ToSqlValue for Option<FieldDateTime> {
589 fn to_sql_value(&self) -> SqlValue {
590 self.as_ref()
591 .map_or(SqlValue::NullDateTime, |v| v.to_sql_value())
592 }
593}
594impl ToSqlValue for Option<FieldDate> {
595 fn to_sql_value(&self) -> SqlValue {
596 self.as_ref()
597 .map_or(SqlValue::NullDate, |v| v.to_sql_value())
598 }
599}
600impl ToSqlValue for Option<FieldTime> {
601 fn to_sql_value(&self) -> SqlValue {
602 self.as_ref()
603 .map_or(SqlValue::NullTime, |v| v.to_sql_value())
604 }
605}
606impl ToSqlValue for Option<FieldJson> {
607 fn to_sql_value(&self) -> SqlValue {
608 self.as_ref()
609 .map_or(SqlValue::NullJson, |v| v.to_sql_value())
610 }
611}
612
613impl<const MIN: usize, const MAX: usize> ToSqlValue for Option<FieldVarchar<MIN, MAX>> {
615 fn to_sql_value(&self) -> SqlValue {
616 self.as_ref()
617 .map_or(SqlValue::NullText, |v| v.to_sql_value())
618 }
619}
620impl<const MIN: usize, const MAX: usize> ToSqlValue for Option<FieldPassword<MIN, MAX>> {
621 fn to_sql_value(&self) -> SqlValue {
622 self.as_ref()
623 .map_or(SqlValue::NullText, |v| v.to_sql_value())
624 }
625}
626impl<const MIN: i64, const MAX: i64> ToSqlValue for Option<FieldRange<MIN, MAX>> {
627 fn to_sql_value(&self) -> SqlValue {
628 self.as_ref()
629 .map_or(SqlValue::NullBigInt, |v| v.to_sql_value())
630 }
631}
632impl<M: crate::Model> ToSqlValue for Option<ForeignKey<M>> {
633 fn to_sql_value(&self) -> SqlValue {
634 self.as_ref()
635 .map_or_else(<M::Pk as crate::PrimaryKey>::null_value, |v| {
636 v.to_sql_value()
637 })
638 }
639}
640
641#[derive(Serialize, Deserialize)]
658#[serde(bound(
659 serialize = "<M as crate::Model>::Pk: Serialize",
660 deserialize = "<M as crate::Model>::Pk: serde::de::DeserializeOwned"
661))]
662pub struct ManyToMany<M: crate::Model>(
663 pub Vec<<M as crate::Model>::Pk>,
666 std::marker::PhantomData<M>,
667);
668
669impl<M: crate::Model> Clone for ManyToMany<M> {
671 fn clone(&self) -> Self {
672 Self(self.0.clone(), std::marker::PhantomData)
673 }
674}
675
676impl<M: crate::Model> Default for ManyToMany<M> {
677 fn default() -> Self {
678 Self::empty()
679 }
680}
681
682impl<M: crate::Model> std::fmt::Debug for ManyToMany<M> {
683 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
684 f.debug_tuple("ManyToMany").field(&self.0).finish()
685 }
686}
687
688impl<M: crate::Model> ManyToMany<M> {
689 pub fn empty() -> Self {
690 Self(Vec::new(), std::marker::PhantomData)
691 }
692}
693
694#[derive(Serialize, Deserialize)]
710#[serde(bound(
711 serialize = "<M as crate::Model>::Pk: Serialize",
712 deserialize = "<M as crate::Model>::Pk: serde::de::DeserializeOwned"
713))]
714pub struct ForeignKey<M: crate::Model>(pub <M as crate::Model>::Pk, std::marker::PhantomData<M>);
715
716impl<M: crate::Model> Clone for ForeignKey<M> {
718 fn clone(&self) -> Self {
719 *self
720 }
721}
722
723impl<M: crate::Model> Copy for ForeignKey<M> {}
724
725impl<M: crate::Model> PartialEq for ForeignKey<M> {
726 fn eq(&self, other: &Self) -> bool {
727 self.0 == other.0
728 }
729}
730
731impl<M: crate::Model> Eq for ForeignKey<M> {}
732
733impl<M: crate::Model> std::hash::Hash for ForeignKey<M> {
734 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
735 self.0.hash(state);
736 }
737}
738
739impl<M: crate::Model> std::fmt::Debug for ForeignKey<M> {
740 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
741 f.debug_tuple("ForeignKey").field(&self.0).finish()
742 }
743}
744
745impl<M: crate::Model> ForeignKey<M> {
746 pub fn new(id: <M as crate::Model>::Pk) -> Self {
747 Self(id, std::marker::PhantomData)
748 }
749
750 pub fn id(&self) -> <M as crate::Model>::Pk {
751 self.0
752 }
753}
754
755impl<M: crate::Model> ToSqlValue for ForeignKey<M> {
756 fn to_sql_value(&self) -> SqlValue {
757 self.0.to_sql_value()
758 }
759}
760
761impl From<bool> for FieldBool {
764 fn from(v: bool) -> Self {
765 Self(v)
766 }
767}
768impl From<i16> for FieldSmallInt {
769 fn from(v: i16) -> Self {
770 Self(v)
771 }
772}
773impl From<i32> for FieldInt {
774 fn from(v: i32) -> Self {
775 Self(v)
776 }
777}
778impl From<i64> for FieldBigInt {
779 fn from(v: i64) -> Self {
780 Self(v)
781 }
782}
783impl From<i32> for FieldAutoInt {
784 fn from(v: i32) -> Self {
785 Self(Some(v))
786 }
787}
788impl From<i64> for FieldAutoBigInt {
789 fn from(v: i64) -> Self {
790 Self(Some(v))
791 }
792}
793impl From<f32> for FieldFloat {
794 fn from(v: f32) -> Self {
795 Self(v)
796 }
797}
798impl From<f64> for FieldDouble {
799 fn from(v: f64) -> Self {
800 Self(v)
801 }
802}
803impl From<String> for FieldText {
804 fn from(v: String) -> Self {
805 Self(v)
806 }
807}
808impl From<&str> for FieldText {
809 fn from(v: &str) -> Self {
810 Self(v.to_string())
811 }
812}
813impl From<Vec<u8>> for FieldBytes {
814 fn from(v: Vec<u8>) -> Self {
815 Self(v)
816 }
817}
818impl From<Uuid> for FieldUuid {
819 fn from(v: Uuid) -> Self {
820 Self(v)
821 }
822}
823impl From<NaiveDate> for FieldDate {
824 fn from(v: NaiveDate) -> Self {
825 Self(v)
826 }
827}
828impl From<NaiveTime> for FieldTime {
829 fn from(v: NaiveTime) -> Self {
830 Self(v)
831 }
832}
833impl From<DateTime<Utc>> for FieldDateTime {
834 fn from(v: DateTime<Utc>) -> Self {
835 Self(v)
836 }
837}
838impl From<serde_json::Value> for FieldJson {
839 fn from(v: serde_json::Value) -> Self {
840 Self(v)
841 }
842}
843
844#[cfg(test)]
845mod tests {
846 use super::*;
847
848 #[test]
849 fn validated_fields_display_their_value() {
850 let name = FieldVarchar::<1, 10>::new("ada").unwrap();
851 let email = FieldEmail::new("ada@x.io").unwrap();
852 assert_eq!(format!("{name} <{email}>"), "ada <ada@x.io>");
853 }
854
855 #[test]
856 fn varchar_counts_chars_not_bytes() {
857 assert!(FieldVarchar::<1, 3>::new("éèà").is_ok());
859 assert!(FieldVarchar::<4, 10>::new("éèà").is_err());
860 }
861
862 #[test]
863 fn varchar_rejects_out_of_bounds() {
864 assert!(FieldVarchar::<1, 100>::new("").is_err());
865 assert!(FieldVarchar::<1, 100>::new("a".repeat(101)).is_err());
866 assert_eq!(FieldVarchar::<1, 100>::new("ok").unwrap().as_str(), "ok");
867 }
868
869 #[test]
870 fn deserialize_validates() {
871 assert!(serde_json::from_str::<FieldVarchar<1, 5>>("\"\"").is_err());
872 assert!(serde_json::from_str::<FieldEmail>("\"nope\"").is_err());
873 assert!(serde_json::from_str::<FieldUrl>("\"ftp://x\"").is_err());
874 assert!(serde_json::from_str::<FieldRange<0, 10>>("11").is_err());
875 assert!(serde_json::from_str::<FieldRange<0, 10>>("10").is_ok());
876 }
877
878 #[test]
879 fn field_password_new_rejects_too_short() {
880 assert!(FieldPassword::<8, 128>::new("short").is_err());
881 }
882
883 #[test]
884 fn field_password_new_rejects_too_long() {
885 let long = "a".repeat(129);
886 assert!(FieldPassword::<8, 128>::new(long).is_err());
887 }
888
889 #[test]
890 fn field_password_verify_accepts_correct_and_rejects_wrong() {
891 let password = FieldPassword::<8, 128>::new("correct horse battery staple").unwrap();
892 assert!(password.verify("correct horse battery staple"));
893 assert!(!password.verify("wrong password"));
894 }
895
896 #[test]
897 fn field_password_hash_is_argon2id() {
898 let password = FieldPassword::<8, 128>::new("secret password").unwrap();
899 assert!(password.hash().starts_with("$argon2id$"));
900 }
901
902 #[test]
903 fn field_password_debug_does_not_leak_plaintext() {
904 let password = FieldPassword::<8, 128>::new("super secret").unwrap();
905 let debug = format!("{:?}", password);
906 assert!(!debug.contains("super secret"));
907 assert!(debug.contains("FieldPassword(<redacted>)"));
908 }
909}
910
911use crate::column::FilterValue;
914
915macro_rules! filter_values {
916 ($($field:ty => $($value:ty),+;)+) => {$($(
917 impl FilterValue<$field> for $value {
918 fn into_sql_value(self) -> SqlValue {
919 self.into()
920 }
921 }
922 )+)+};
923}
924
925filter_values! {
926 FieldBool => bool;
927 FieldSmallInt => i16;
928 FieldInt => i32;
929 FieldBigInt => i64;
930 FieldAutoInt => i32;
931 FieldAutoBigInt => i64;
932 FieldFloat => f32;
933 FieldDouble => f64;
934 FieldText => String, &str;
935 FieldEmail => String, &str;
936 FieldUrl => String, &str;
937 FieldBytes => Vec<u8>;
938 FieldUuid => Uuid;
939 FieldDateTime => DateTime<Utc>;
940 FieldDate => NaiveDate;
941 FieldTime => NaiveTime;
942 FieldJson => serde_json::Value;
943}
944
945impl<const MIN: usize, const MAX: usize> FilterValue<FieldVarchar<MIN, MAX>> for String {
946 fn into_sql_value(self) -> SqlValue {
947 SqlValue::Text(self)
948 }
949}
950impl<const MIN: usize, const MAX: usize> FilterValue<FieldVarchar<MIN, MAX>> for &str {
951 fn into_sql_value(self) -> SqlValue {
952 SqlValue::Text(self.to_string())
953 }
954}
955impl<const MIN: i64, const MAX: i64> FilterValue<FieldRange<MIN, MAX>> for i64 {
956 fn into_sql_value(self) -> SqlValue {
957 SqlValue::BigInt(self)
958 }
959}
960impl<const P: u8, const S: u8> FilterValue<FieldDecimal<P, S>> for rust_decimal::Decimal {
961 fn into_sql_value(self) -> SqlValue {
962 SqlValue::Decimal(self)
963 }
964}
965impl<M: crate::Model<Pk = Uuid>> FilterValue<ForeignKey<M>> for Uuid {
966 fn into_sql_value(self) -> SqlValue {
967 SqlValue::Uuid(self)
968 }
969}
970impl<M: crate::Model<Pk = i32>> FilterValue<ForeignKey<M>> for i32 {
971 fn into_sql_value(self) -> SqlValue {
972 SqlValue::Int(self)
973 }
974}
975impl<M: crate::Model<Pk = i64>> FilterValue<ForeignKey<M>> for i64 {
976 fn into_sql_value(self) -> SqlValue {
977 SqlValue::BigInt(self)
978 }
979}
980impl<M: crate::Model<Pk = Uuid>> FilterValue<ForeignKey<M>> for FieldUuid {
981 fn into_sql_value(self) -> SqlValue {
982 SqlValue::Uuid(self.0)
983 }
984}