1use std::collections::HashMap;
10use std::sync::OnceLock;
11
12use regex::Regex;
13use serde::de::DeserializeOwned;
14
15use crate::Value;
16use crate::error::{ValidationError, ValidationErrorKind};
17
18struct RegexCache {
24 cache: std::sync::RwLock<std::collections::HashMap<String, Regex>>,
25}
26
27impl RegexCache {
28 fn new() -> Self {
29 Self {
30 cache: std::sync::RwLock::new(std::collections::HashMap::new()),
31 }
32 }
33
34 fn get_or_compile(&self, pattern: &str) -> Result<Regex, regex::Error> {
35 {
38 let cache = self.cache.read().unwrap_or_else(|e| e.into_inner());
39 if let Some(regex) = cache.get(pattern) {
40 return Ok(regex.clone());
41 }
42 }
43
44 let regex = Regex::new(pattern)?;
46 {
47 let mut cache = self.cache.write().unwrap_or_else(|e| e.into_inner());
48 cache.insert(pattern.to_string(), regex.clone());
49 }
50 Ok(regex)
51 }
52}
53
54fn regex_cache() -> &'static RegexCache {
56 static CACHE: OnceLock<RegexCache> = OnceLock::new();
57 CACHE.get_or_init(RegexCache::new)
58}
59
60pub fn matches_pattern(value: &str, pattern: &str) -> bool {
84 match regex_cache().get_or_compile(pattern) {
85 Ok(regex) => regex.is_match(value),
86 Err(e) => {
87 tracing::warn!(
89 pattern = pattern,
90 error = %e,
91 "Invalid regex pattern in validation, treating as non-match"
92 );
93 false
94 }
95 }
96}
97
98pub fn validate_pattern(pattern: &str) -> Option<String> {
102 match Regex::new(pattern) {
103 Ok(_) => None,
104 Err(e) => Some(format!("invalid regex pattern: {e}")),
105 }
106}
107
108pub fn is_valid_credit_card(value: &str) -> bool {
145 let digits: Vec<u32> = value
147 .chars()
148 .filter(|c| c.is_ascii_digit())
149 .filter_map(|c| c.to_digit(10))
150 .collect();
151
152 if digits.len() < 13 || digits.len() > 19 {
154 return false;
155 }
156
157 let mut sum = 0u32;
159 let len = digits.len();
160
161 for (i, &digit) in digits.iter().enumerate() {
162 let position_from_right = len - i;
165 let is_double_position = position_from_right % 2 == 0;
166
167 let value = if is_double_position {
168 let doubled = digit * 2;
169 if doubled > 9 { doubled - 9 } else { doubled }
170 } else {
171 digit
172 };
173
174 sum += value;
175 }
176
177 sum % 10 == 0
178}
179
180#[derive(Debug, Clone)]
188pub enum ValidateInput {
189 Dict(HashMap<String, Value>),
191 Json(String),
193 JsonValue(serde_json::Value),
195}
196
197impl From<HashMap<String, Value>> for ValidateInput {
198 fn from(map: HashMap<String, Value>) -> Self {
199 ValidateInput::Dict(map)
200 }
201}
202
203impl From<String> for ValidateInput {
204 fn from(json: String) -> Self {
205 ValidateInput::Json(json)
206 }
207}
208
209impl From<&str> for ValidateInput {
210 fn from(json: &str) -> Self {
211 ValidateInput::Json(json.to_string())
212 }
213}
214
215impl From<serde_json::Value> for ValidateInput {
216 fn from(value: serde_json::Value) -> Self {
217 ValidateInput::JsonValue(value)
218 }
219}
220
221#[derive(Debug, Clone, Default)]
225pub struct ValidateOptions {
226 pub strict: bool,
228 pub from_attributes: bool,
231 pub context: Option<HashMap<String, serde_json::Value>>,
233 pub update: Option<HashMap<String, serde_json::Value>>,
235}
236
237impl ValidateOptions {
238 pub fn new() -> Self {
240 Self::default()
241 }
242
243 pub fn strict(mut self) -> Self {
245 self.strict = true;
246 self
247 }
248
249 pub fn from_attributes(mut self) -> Self {
251 self.from_attributes = true;
252 self
253 }
254
255 pub fn with_context(mut self, context: HashMap<String, serde_json::Value>) -> Self {
257 self.context = Some(context);
258 self
259 }
260
261 pub fn with_update(mut self, update: HashMap<String, serde_json::Value>) -> Self {
263 self.update = Some(update);
264 self
265 }
266}
267
268pub type ValidateResult<T> = std::result::Result<T, ValidationError>;
270
271pub trait ModelValidate: Sized {
276 fn model_validate(
298 input: impl Into<ValidateInput>,
299 options: ValidateOptions,
300 ) -> ValidateResult<Self>;
301
302 fn model_validate_json(json: &str) -> ValidateResult<Self> {
304 Self::model_validate(json, ValidateOptions::default())
305 }
306
307 fn model_validate_dict(dict: HashMap<String, Value>) -> ValidateResult<Self> {
309 Self::model_validate(dict, ValidateOptions::default())
310 }
311}
312
313impl<T: DeserializeOwned> ModelValidate for T {
317 fn model_validate(
318 input: impl Into<ValidateInput>,
319 options: ValidateOptions,
320 ) -> ValidateResult<Self> {
321 let input = input.into();
322
323 let mut json_value = match input {
325 ValidateInput::Dict(dict) => {
326 let map: serde_json::Map<String, serde_json::Value> = dict
328 .into_iter()
329 .map(|(k, v)| (k, value_to_json(v)))
330 .collect();
331 serde_json::Value::Object(map)
332 }
333 ValidateInput::Json(json_str) => serde_json::from_str(&json_str).map_err(|e| {
334 let mut err = ValidationError::new();
335 err.add(
336 "_json",
337 ValidationErrorKind::Custom,
338 format!("Invalid JSON: {e}"),
339 );
340 err
341 })?,
342 ValidateInput::JsonValue(value) => value,
343 };
344
345 if let Some(update) = options.update {
347 if let serde_json::Value::Object(ref mut map) = json_value {
348 for (key, value) in update {
349 map.insert(key, value);
350 }
351 }
352 }
353
354 if options.strict {
356 serde_json::from_value(json_value).map_err(|e| {
359 let mut err = ValidationError::new();
360 err.add(
361 "_model",
362 ValidationErrorKind::Custom,
363 format!("Validation failed: {e}"),
364 );
365 err
366 })
367 } else {
368 serde_json::from_value(json_value).map_err(|e| {
370 let mut err = ValidationError::new();
371 err.add(
372 "_model",
373 ValidationErrorKind::Custom,
374 format!("Validation failed: {e}"),
375 );
376 err
377 })
378 }
379 }
380}
381
382#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
388pub enum DumpMode {
389 #[default]
391 Json,
392 Python,
394}
395
396#[derive(Debug, Clone, Default)]
400pub struct DumpOptions {
401 pub mode: DumpMode,
409 pub include: Option<std::collections::HashSet<String>>,
411 pub exclude: Option<std::collections::HashSet<String>>,
413 pub by_alias: bool,
418 pub exclude_unset: bool,
431 pub exclude_defaults: bool,
433 pub exclude_none: bool,
435 pub exclude_computed_fields: bool,
437 pub round_trip: bool,
443 pub indent: Option<usize>,
445}
446
447impl DumpOptions {
448 pub fn new() -> Self {
450 Self::default()
451 }
452
453 pub fn json(mut self) -> Self {
455 self.mode = DumpMode::Json;
456 self
457 }
458
459 pub fn python(mut self) -> Self {
461 self.mode = DumpMode::Python;
462 self
463 }
464
465 pub fn include(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
467 self.include = Some(fields.into_iter().map(Into::into).collect());
468 self
469 }
470
471 pub fn exclude(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
473 self.exclude = Some(fields.into_iter().map(Into::into).collect());
474 self
475 }
476
477 pub fn by_alias(mut self) -> Self {
479 self.by_alias = true;
480 self
481 }
482
483 pub fn exclude_unset(mut self) -> Self {
485 self.exclude_unset = true;
486 self
487 }
488
489 pub fn exclude_defaults(mut self) -> Self {
491 self.exclude_defaults = true;
492 self
493 }
494
495 pub fn exclude_none(mut self) -> Self {
497 self.exclude_none = true;
498 self
499 }
500
501 pub fn exclude_computed_fields(mut self) -> Self {
503 self.exclude_computed_fields = true;
504 self
505 }
506
507 pub fn round_trip(mut self) -> Self {
509 self.round_trip = true;
510 self
511 }
512
513 pub fn indent(mut self, spaces: usize) -> Self {
518 self.indent = Some(spaces);
519 self
520 }
521}
522
523pub type DumpResult = std::result::Result<serde_json::Value, serde_json::Error>;
525
526pub(crate) fn dump_options_unsupported(msg: impl Into<String>) -> serde_json::Error {
527 serde_json::Error::io(std::io::Error::new(
528 std::io::ErrorKind::InvalidInput,
529 msg.into(),
530 ))
531}
532
533pub trait ModelDump {
537 fn model_dump(&self, options: DumpOptions) -> DumpResult;
555
556 fn model_dump_json(&self) -> std::result::Result<String, serde_json::Error> {
558 let value = self.model_dump(DumpOptions::default())?;
559 serde_json::to_string(&value)
560 }
561
562 fn model_dump_json_pretty(&self) -> std::result::Result<String, serde_json::Error> {
564 let value = self.model_dump(DumpOptions::default())?;
565 serde_json::to_string_pretty(&value)
566 }
567
568 fn model_dump_json_with_options(
590 &self,
591 options: DumpOptions,
592 ) -> std::result::Result<String, serde_json::Error> {
593 let value = self.model_dump(DumpOptions {
594 indent: None, ..options.clone()
596 })?;
597
598 match options.indent {
599 Some(spaces) => {
600 let indent_bytes = " ".repeat(spaces).into_bytes();
601 let formatter = serde_json::ser::PrettyFormatter::with_indent(&indent_bytes);
602 let mut writer = Vec::new();
603 let mut ser = serde_json::Serializer::with_formatter(&mut writer, formatter);
604 serde::Serialize::serialize(&value, &mut ser)?;
605 String::from_utf8(writer).map_err(|e| {
607 serde_json::Error::io(std::io::Error::new(
608 std::io::ErrorKind::InvalidData,
609 format!("UTF-8 encoding error: {e}"),
610 ))
611 })
612 }
613 None => serde_json::to_string(&value),
614 }
615 }
616}
617
618impl<T: serde::Serialize> ModelDump for T {
620 fn model_dump(&self, options: DumpOptions) -> DumpResult {
621 if options.exclude_unset {
622 return Err(dump_options_unsupported(
623 "DumpOptions.exclude_unset requires fields_set tracking; use SqlModelValidate::sql_model_validate_tracked(...) or the tracked!(Type { .. }) macro",
624 ));
625 }
626 if options.by_alias || options.exclude_defaults || options.exclude_computed_fields {
627 return Err(dump_options_unsupported(
628 "DumpOptions.by_alias/exclude_defaults/exclude_computed_fields require SqlModelDump",
629 ));
630 }
631
632 let mut value = serde_json::to_value(self)?;
634
635 if let serde_json::Value::Object(ref mut map) = value {
637 if let Some(ref include) = options.include {
639 map.retain(|k, _| include.contains(k));
640 }
641
642 if let Some(ref exclude) = options.exclude {
644 map.retain(|k, _| !exclude.contains(k));
645 }
646
647 if options.exclude_none {
649 map.retain(|_, v| !v.is_null());
650 }
651
652 }
655
656 Ok(value)
657 }
658}
659
660fn value_to_json(value: Value) -> serde_json::Value {
662 match value {
663 Value::Null => serde_json::Value::Null,
664 Value::Bool(b) => serde_json::Value::Bool(b),
665 Value::TinyInt(i) => serde_json::Value::Number(i.into()),
666 Value::SmallInt(i) => serde_json::Value::Number(i.into()),
667 Value::Int(i) => serde_json::Value::Number(i.into()),
668 Value::BigInt(i) => serde_json::Value::Number(i.into()),
669 Value::Float(f) => serde_json::Number::from_f64(f64::from(f))
670 .map_or(serde_json::Value::Null, serde_json::Value::Number),
671 Value::Double(f) => serde_json::Number::from_f64(f)
672 .map_or(serde_json::Value::Null, serde_json::Value::Number),
673 Value::Decimal(s) => serde_json::Value::String(s),
674 Value::Text(s) => serde_json::Value::String(s),
675 Value::Bytes(b) => {
676 use std::fmt::Write;
678 let hex = b
679 .iter()
680 .fold(String::with_capacity(b.len() * 2), |mut acc, byte| {
681 let _ = write!(acc, "{byte:02x}");
682 acc
683 });
684 serde_json::Value::String(hex)
685 }
686 Value::Date(d) => serde_json::Value::Number(d.into()),
688 Value::Time(t) => serde_json::Value::Number(t.into()),
690 Value::Timestamp(ts) => serde_json::Value::Number(ts.into()),
692 Value::TimestampTz(ts) => serde_json::Value::Number(ts.into()),
694 Value::Uuid(u) => {
696 use std::fmt::Write;
697 let hex = u.iter().fold(String::with_capacity(32), |mut acc, b| {
698 let _ = write!(acc, "{b:02x}");
699 acc
700 });
701 let formatted = format!(
703 "{}-{}-{}-{}-{}",
704 &hex[0..8],
705 &hex[8..12],
706 &hex[12..16],
707 &hex[16..20],
708 &hex[20..32]
709 );
710 serde_json::Value::String(formatted)
711 }
712 Value::Json(j) => j,
713 Value::Array(arr) => serde_json::Value::Array(arr.into_iter().map(value_to_json).collect()),
714 Value::Default => serde_json::Value::Null,
715 }
716}
717
718use crate::Model;
723
724pub fn apply_validation_aliases(json: &mut serde_json::Value, fields: &[crate::FieldInfo]) {
734 if let serde_json::Value::Object(map) = json {
735 let mut alias_map: HashMap<&str, &str> = HashMap::new();
737 for field in fields {
738 if let Some(alias) = field.validation_alias {
740 alias_map.insert(alias, field.name);
741 }
742 if let Some(alias) = field.alias {
744 alias_map.entry(alias).or_insert(field.name);
745 }
746 }
747
748 let renames: Vec<(String, &str)> = map
750 .keys()
751 .filter_map(|k| alias_map.get(k.as_str()).map(|v| (k.clone(), *v)))
752 .collect();
753
754 for (old_key, new_key) in renames {
756 if let Some(value) = map.remove(&old_key) {
757 map.entry(new_key.to_string()).or_insert(value);
759 }
760 }
761 }
762}
763
764pub fn apply_serialization_aliases(json: &mut serde_json::Value, fields: &[crate::FieldInfo]) {
774 if let serde_json::Value::Object(map) = json {
775 let mut alias_map: HashMap<&str, &str> = HashMap::new();
777 for field in fields {
778 if let Some(alias) = field.serialization_alias {
780 alias_map.insert(field.name, alias);
781 } else if let Some(alias) = field.alias {
782 alias_map.insert(field.name, alias);
784 }
785 }
786
787 let renames: Vec<(String, &str)> = map
789 .keys()
790 .filter_map(|k| alias_map.get(k.as_str()).map(|v| (k.clone(), *v)))
791 .collect();
792
793 for (old_key, new_key) in renames {
795 if let Some(value) = map.remove(&old_key) {
796 map.insert(new_key.to_string(), value);
797 }
798 }
799 }
800}
801
802pub trait SqlModelValidate: Model + DeserializeOwned + Sized {
822 fn sql_model_validate(
824 input: impl Into<ValidateInput>,
825 options: ValidateOptions,
826 ) -> ValidateResult<Self> {
827 let input = input.into();
828
829 let mut json_value = match input {
831 ValidateInput::Dict(dict) => {
832 let map: serde_json::Map<String, serde_json::Value> = dict
833 .into_iter()
834 .map(|(k, v)| (k, value_to_json(v)))
835 .collect();
836 serde_json::Value::Object(map)
837 }
838 ValidateInput::Json(json_str) => serde_json::from_str(&json_str).map_err(|e| {
839 let mut err = ValidationError::new();
840 err.add(
841 "_json",
842 ValidationErrorKind::Custom,
843 format!("Invalid JSON: {e}"),
844 );
845 err
846 })?,
847 ValidateInput::JsonValue(value) => value,
848 };
849
850 apply_validation_aliases(&mut json_value, Self::fields());
852
853 if let Some(update) = options.update {
855 if let serde_json::Value::Object(ref mut map) = json_value {
856 for (key, value) in update {
857 map.insert(key, value);
858 }
859 }
860 }
861
862 serde_json::from_value(json_value).map_err(|e| {
864 let mut err = ValidationError::new();
865 err.add(
866 "_model",
867 ValidationErrorKind::Custom,
868 format!("Validation failed: {e}"),
869 );
870 err
871 })
872 }
873
874 fn sql_model_validate_tracked(
878 input: impl Into<ValidateInput>,
879 options: ValidateOptions,
880 ) -> ValidateResult<crate::TrackedModel<Self>> {
881 let input = input.into();
882
883 let mut json_value = match input {
884 ValidateInput::Dict(dict) => {
885 let map: serde_json::Map<String, serde_json::Value> = dict
886 .into_iter()
887 .map(|(k, v)| (k, value_to_json(v)))
888 .collect();
889 serde_json::Value::Object(map)
890 }
891 ValidateInput::Json(json_str) => serde_json::from_str(&json_str).map_err(|e| {
892 let mut err = ValidationError::new();
893 err.add(
894 "_json",
895 ValidationErrorKind::Custom,
896 format!("Invalid JSON: {e}"),
897 );
898 err
899 })?,
900 ValidateInput::JsonValue(value) => value,
901 };
902
903 apply_validation_aliases(&mut json_value, Self::fields());
904
905 if let Some(update) = options.update {
906 if let serde_json::Value::Object(ref mut map) = json_value {
907 for (key, value) in update {
908 map.insert(key, value);
909 }
910 }
911 }
912
913 let mut fields_set = crate::FieldsSet::empty(Self::fields().len());
915 if let serde_json::Value::Object(ref map) = json_value {
916 for (idx, field) in Self::fields().iter().enumerate() {
917 if map.contains_key(field.name) {
918 fields_set.set(idx);
919 }
920 }
921 }
922
923 let model = serde_json::from_value(json_value).map_err(|e| {
924 let mut err = ValidationError::new();
925 err.add(
926 "_model",
927 ValidationErrorKind::Custom,
928 format!("Validation failed: {e}"),
929 );
930 err
931 })?;
932
933 Ok(crate::TrackedModel::new(model, fields_set))
934 }
935
936 fn sql_model_validate_json(json: &str) -> ValidateResult<Self> {
938 Self::sql_model_validate(json, ValidateOptions::default())
939 }
940
941 fn sql_model_validate_dict(dict: HashMap<String, Value>) -> ValidateResult<Self> {
943 Self::sql_model_validate(dict, ValidateOptions::default())
944 }
945}
946
947impl<T: Model + DeserializeOwned> SqlModelValidate for T {}
949
950pub trait SqlModelDump: Model + serde::Serialize {
976 fn sql_model_dump(&self, options: DumpOptions) -> DumpResult {
978 if options.exclude_unset {
979 return Err(dump_options_unsupported(
980 "DumpOptions.exclude_unset requires fields_set tracking; use SqlModelValidate::sql_model_validate_tracked(...) or the tracked!(Type { .. }) macro",
981 ));
982 }
983
984 let mut value = serde_json::to_value(self)?;
986
987 if let serde_json::Value::Object(ref mut map) = value {
989 for field in Self::fields() {
991 if field.exclude {
992 map.remove(field.name);
993 }
994 }
995
996 if options.exclude_computed_fields {
998 let computed_field_names: std::collections::HashSet<&str> = Self::fields()
999 .iter()
1000 .filter(|f| f.computed)
1001 .map(|f| f.name)
1002 .collect();
1003 map.retain(|k, _| !computed_field_names.contains(k.as_str()));
1004 }
1005
1006 if options.exclude_defaults {
1008 for field in Self::fields() {
1009 if let Some(default_json) = field.default_json {
1010 if let Some(current_value) = map.get(field.name) {
1011 if let Ok(default_value) =
1013 serde_json::from_str::<serde_json::Value>(default_json)
1014 {
1015 if current_value == &default_value {
1016 map.remove(field.name);
1017 }
1018 }
1019 }
1020 }
1021 }
1022 }
1023 }
1024
1025 if options.by_alias {
1027 apply_serialization_aliases(&mut value, Self::fields());
1028 }
1029
1030 if let serde_json::Value::Object(ref mut map) = value {
1032 if let Some(ref include) = options.include {
1034 map.retain(|k, _| include.contains(k));
1035 }
1036
1037 if let Some(ref exclude) = options.exclude {
1039 map.retain(|k, _| !exclude.contains(k));
1040 }
1041
1042 if options.exclude_none {
1044 map.retain(|_, v| !v.is_null());
1045 }
1046 }
1047
1048 Ok(value)
1049 }
1050
1051 fn sql_model_dump_json(&self) -> std::result::Result<String, serde_json::Error> {
1053 let value = self.sql_model_dump(DumpOptions::default())?;
1054 serde_json::to_string(&value)
1055 }
1056
1057 fn sql_model_dump_json_pretty(&self) -> std::result::Result<String, serde_json::Error> {
1059 let value = self.sql_model_dump(DumpOptions::default())?;
1060 serde_json::to_string_pretty(&value)
1061 }
1062
1063 fn sql_model_dump_json_by_alias(&self) -> std::result::Result<String, serde_json::Error> {
1065 let value = self.sql_model_dump(DumpOptions::default().by_alias())?;
1066 serde_json::to_string(&value)
1067 }
1068
1069 fn sql_model_dump_json_with_options(
1089 &self,
1090 options: DumpOptions,
1091 ) -> std::result::Result<String, serde_json::Error> {
1092 let value = self.sql_model_dump(DumpOptions {
1093 indent: None, ..options.clone()
1095 })?;
1096
1097 match options.indent {
1098 Some(spaces) => {
1099 let indent_bytes = " ".repeat(spaces).into_bytes();
1100 let formatter = serde_json::ser::PrettyFormatter::with_indent(&indent_bytes);
1101 let mut writer = Vec::new();
1102 let mut ser = serde_json::Serializer::with_formatter(&mut writer, formatter);
1103 serde::Serialize::serialize(&value, &mut ser)?;
1104 String::from_utf8(writer).map_err(|e| {
1106 serde_json::Error::io(std::io::Error::new(
1107 std::io::ErrorKind::InvalidData,
1108 format!("UTF-8 encoding error: {e}"),
1109 ))
1110 })
1111 }
1112 None => serde_json::to_string(&value),
1113 }
1114 }
1115}
1116
1117impl<T: Model + serde::Serialize> SqlModelDump for T {}
1119
1120#[derive(Debug, Clone)]
1128pub enum UpdateInput {
1129 Dict(HashMap<String, serde_json::Value>),
1131 JsonValue(serde_json::Value),
1133}
1134
1135impl From<HashMap<String, serde_json::Value>> for UpdateInput {
1136 fn from(map: HashMap<String, serde_json::Value>) -> Self {
1137 UpdateInput::Dict(map)
1138 }
1139}
1140
1141impl From<serde_json::Value> for UpdateInput {
1142 fn from(value: serde_json::Value) -> Self {
1143 UpdateInput::JsonValue(value)
1144 }
1145}
1146
1147impl From<HashMap<String, Value>> for UpdateInput {
1148 fn from(map: HashMap<String, Value>) -> Self {
1149 let json_map: HashMap<String, serde_json::Value> = map
1150 .into_iter()
1151 .map(|(k, v)| (k, value_to_json(v)))
1152 .collect();
1153 UpdateInput::Dict(json_map)
1154 }
1155}
1156
1157#[derive(Debug, Clone, Default)]
1159pub struct UpdateOptions {
1160 pub update_fields: Option<std::collections::HashSet<String>>,
1162}
1163
1164impl UpdateOptions {
1165 pub fn new() -> Self {
1167 Self::default()
1168 }
1169
1170 pub fn update_fields(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
1172 self.update_fields = Some(fields.into_iter().map(Into::into).collect());
1173 self
1174 }
1175}
1176
1177pub trait SqlModelUpdate: Model + serde::Serialize + DeserializeOwned {
1207 fn sqlmodel_update(
1223 &mut self,
1224 input: impl Into<UpdateInput>,
1225 options: UpdateOptions,
1226 ) -> ValidateResult<()> {
1227 let input = input.into();
1228
1229 let update_map = match input {
1231 UpdateInput::Dict(map) => map,
1232 UpdateInput::JsonValue(value) => {
1233 if let serde_json::Value::Object(map) = value {
1234 map.into_iter().collect()
1235 } else {
1236 let mut err = ValidationError::new();
1237 err.add(
1238 "_update",
1239 ValidationErrorKind::Custom,
1240 "Update input must be an object".to_string(),
1241 );
1242 return Err(err);
1243 }
1244 }
1245 };
1246
1247 let mut current = serde_json::to_value(&*self).map_err(|e| {
1249 let mut err = ValidationError::new();
1250 err.add(
1251 "_model",
1252 ValidationErrorKind::Custom,
1253 format!("Failed to serialize model: {e}"),
1254 );
1255 err
1256 })?;
1257
1258 let valid_fields: std::collections::HashSet<&str> =
1260 Self::fields().iter().map(|f| f.name).collect();
1261
1262 if let serde_json::Value::Object(ref mut current_map) = current {
1264 for (key, value) in update_map {
1265 if !valid_fields.contains(key.as_str()) {
1267 let mut err = ValidationError::new();
1268 err.add(
1269 &key,
1270 ValidationErrorKind::Custom,
1271 format!("Unknown field: {key}"),
1272 );
1273 return Err(err);
1274 }
1275
1276 if let Some(ref allowed) = options.update_fields {
1278 if !allowed.contains(&key) {
1279 continue; }
1281 }
1282
1283 current_map.insert(key, value);
1285 }
1286 }
1287
1288 let updated: Self = serde_json::from_value(current).map_err(|e| {
1290 let mut err = ValidationError::new();
1291 err.add(
1292 "_model",
1293 ValidationErrorKind::Custom,
1294 format!("Update failed validation: {e}"),
1295 );
1296 err
1297 })?;
1298
1299 *self = updated;
1301
1302 Ok(())
1303 }
1304
1305 fn sqlmodel_update_dict(
1307 &mut self,
1308 dict: HashMap<String, serde_json::Value>,
1309 ) -> ValidateResult<()> {
1310 self.sqlmodel_update(dict, UpdateOptions::default())
1311 }
1312
1313 fn sqlmodel_update_from(&mut self, source: &Self, options: UpdateOptions) -> ValidateResult<()>
1335 where
1336 Self: Sized,
1337 {
1338 let source_json = serde_json::to_value(source).map_err(|e| {
1340 let mut err = ValidationError::new();
1341 err.add(
1342 "_source",
1343 ValidationErrorKind::Custom,
1344 format!("Failed to serialize source model: {e}"),
1345 );
1346 err
1347 })?;
1348
1349 let update_map: HashMap<String, serde_json::Value> =
1351 if let serde_json::Value::Object(map) = source_json {
1352 map.into_iter().filter(|(_, v)| !v.is_null()).collect()
1353 } else {
1354 let mut err = ValidationError::new();
1355 err.add(
1356 "_source",
1357 ValidationErrorKind::Custom,
1358 "Source model must serialize to an object".to_string(),
1359 );
1360 return Err(err);
1361 };
1362
1363 self.sqlmodel_update(update_map, options)
1364 }
1365}
1366
1367impl<T: Model + serde::Serialize + DeserializeOwned> SqlModelUpdate for T {}
1369
1370#[cfg(test)]
1371mod tests {
1372 use super::*;
1373 use serde::{Deserialize, Serialize};
1374
1375 #[test]
1376 fn test_matches_email_pattern() {
1377 let email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
1378
1379 assert!(matches_pattern("test@example.com", email_pattern));
1380 assert!(matches_pattern("user.name+tag@domain.org", email_pattern));
1381 assert!(!matches_pattern("invalid", email_pattern));
1382 assert!(!matches_pattern("@example.com", email_pattern));
1383 assert!(!matches_pattern("test@", email_pattern));
1384 }
1385
1386 #[test]
1387 fn test_matches_url_pattern() {
1388 let url_pattern = r"^https?://[^\s/$.?#].[^\s]*$";
1389
1390 assert!(matches_pattern("https://example.com", url_pattern));
1391 assert!(matches_pattern("http://example.com/path", url_pattern));
1392 assert!(!matches_pattern("ftp://example.com", url_pattern));
1393 assert!(!matches_pattern("not a url", url_pattern));
1394 }
1395
1396 #[test]
1397 fn test_matches_phone_pattern() {
1398 let phone_pattern = r"^\+?[1-9]\d{1,14}$";
1399
1400 assert!(matches_pattern("+12025551234", phone_pattern));
1401 assert!(matches_pattern("12025551234", phone_pattern));
1402 assert!(!matches_pattern("0123456789", phone_pattern)); assert!(!matches_pattern("abc", phone_pattern));
1404 }
1405
1406 #[test]
1407 fn test_matches_uuid_pattern() {
1408 let uuid_pattern =
1409 r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
1410
1411 assert!(matches_pattern(
1412 "550e8400-e29b-41d4-a716-446655440000",
1413 uuid_pattern
1414 ));
1415 assert!(matches_pattern(
1416 "550E8400-E29B-41D4-A716-446655440000",
1417 uuid_pattern
1418 ));
1419 assert!(!matches_pattern("invalid-uuid", uuid_pattern));
1420 assert!(!matches_pattern(
1421 "550e8400e29b41d4a716446655440000",
1422 uuid_pattern
1423 ));
1424 }
1425
1426 #[test]
1427 fn test_matches_alphanumeric_pattern() {
1428 let alphanumeric_pattern = r"^[a-zA-Z0-9]+$";
1429
1430 assert!(matches_pattern("abc123", alphanumeric_pattern));
1431 assert!(matches_pattern("ABC", alphanumeric_pattern));
1432 assert!(matches_pattern("123", alphanumeric_pattern));
1433 assert!(!matches_pattern("abc-123", alphanumeric_pattern));
1434 assert!(!matches_pattern("hello world", alphanumeric_pattern));
1435 }
1436
1437 #[test]
1438 fn test_invalid_pattern_returns_false() {
1439 let invalid_pattern = r"[unclosed";
1441 assert!(!matches_pattern("anything", invalid_pattern));
1442 }
1443
1444 #[test]
1445 fn test_validate_pattern_valid() {
1446 assert!(validate_pattern(r"^[a-z]+$").is_none());
1447 assert!(validate_pattern(r"^\d{4}-\d{2}-\d{2}$").is_none());
1448 }
1449
1450 #[test]
1451 fn test_validate_pattern_invalid() {
1452 let result = validate_pattern(r"[unclosed");
1453 assert!(result.is_some());
1454 assert!(result.unwrap().contains("invalid regex pattern"));
1455 }
1456
1457 #[test]
1458 fn test_regex_caching() {
1459 let pattern = r"^test\d+$";
1460
1461 assert!(matches_pattern("test123", pattern));
1463
1464 assert!(matches_pattern("test456", pattern));
1466 assert!(!matches_pattern("invalid", pattern));
1467 }
1468
1469 #[test]
1470 fn test_empty_string() {
1471 let pattern = r"^.+$"; assert!(!matches_pattern("", pattern));
1473
1474 let empty_allowed = r"^.*$"; assert!(matches_pattern("", empty_allowed));
1476 }
1477
1478 #[test]
1479 fn test_special_characters() {
1480 let pattern = r"^[a-z]+$";
1481 assert!(!matches_pattern("hello<script>", pattern));
1482 assert!(!matches_pattern("test'; DROP TABLE users;--", pattern));
1483 }
1484
1485 #[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
1490 struct TestUser {
1491 name: String,
1492 age: i32,
1493 #[serde(default)]
1494 active: bool,
1495 }
1496
1497 #[test]
1498 fn test_model_validate_from_json() {
1499 let json = r#"{"name": "Alice", "age": 30}"#;
1500 let user: TestUser = TestUser::model_validate_json(json).unwrap();
1501 assert_eq!(user.name, "Alice");
1502 assert_eq!(user.age, 30);
1503 assert!(!user.active); }
1505
1506 #[test]
1507 fn test_model_validate_from_json_value() {
1508 let json_value = serde_json::json!({"name": "Bob", "age": 25, "active": true});
1509 let user: TestUser =
1510 TestUser::model_validate(json_value, ValidateOptions::default()).unwrap();
1511 assert_eq!(user.name, "Bob");
1512 assert_eq!(user.age, 25);
1513 assert!(user.active);
1514 }
1515
1516 #[test]
1517 fn test_model_validate_from_dict() {
1518 let mut dict = HashMap::new();
1519 dict.insert("name".to_string(), Value::Text("Charlie".to_string()));
1520 dict.insert("age".to_string(), Value::Int(35));
1521 dict.insert("active".to_string(), Value::Bool(true));
1522
1523 let user: TestUser = TestUser::model_validate_dict(dict).unwrap();
1524 assert_eq!(user.name, "Charlie");
1525 assert_eq!(user.age, 35);
1526 assert!(user.active);
1527 }
1528
1529 #[test]
1530 fn test_model_validate_invalid_json() {
1531 let json = r#"{"name": "Invalid"}"#; let result: ValidateResult<TestUser> = TestUser::model_validate_json(json);
1533 assert!(result.is_err());
1534 let err = result.unwrap_err();
1535 assert!(!err.is_empty());
1536 }
1537
1538 #[test]
1539 fn test_model_validate_malformed_json() {
1540 let json = r#"{"name": "Alice", age: 30}"#; let result: ValidateResult<TestUser> = TestUser::model_validate_json(json);
1542 assert!(result.is_err());
1543 let err = result.unwrap_err();
1544 assert!(
1545 err.errors
1546 .iter()
1547 .any(|e| e.message.contains("Invalid JSON"))
1548 );
1549 }
1550
1551 #[test]
1552 fn test_model_validate_with_update() {
1553 let json = r#"{"name": "Original", "age": 20}"#;
1554 let mut update = HashMap::new();
1555 update.insert("name".to_string(), serde_json::json!("Updated"));
1556
1557 let options = ValidateOptions::new().with_update(update);
1558 let user: TestUser = TestUser::model_validate(json, options).unwrap();
1559 assert_eq!(user.name, "Updated"); assert_eq!(user.age, 20);
1561 }
1562
1563 #[test]
1564 fn test_model_validate_strict_mode() {
1565 let json = r#"{"name": "Alice", "age": 30}"#;
1566 let options = ValidateOptions::new().strict();
1567 let user: TestUser = TestUser::model_validate(json, options).unwrap();
1568 assert_eq!(user.name, "Alice");
1569 assert_eq!(user.age, 30);
1570 }
1571
1572 #[test]
1573 fn test_validate_options_builder() {
1574 let mut context = HashMap::new();
1575 context.insert("key".to_string(), serde_json::json!("value"));
1576
1577 let options = ValidateOptions::new()
1578 .strict()
1579 .from_attributes()
1580 .with_context(context.clone());
1581
1582 assert!(options.strict);
1583 assert!(options.from_attributes);
1584 assert!(options.context.is_some());
1585 assert_eq!(
1586 options.context.unwrap().get("key"),
1587 Some(&serde_json::json!("value"))
1588 );
1589 }
1590
1591 #[test]
1592 fn test_validate_input_from_conversions() {
1593 let input: ValidateInput = "{}".to_string().into();
1595 assert!(matches!(input, ValidateInput::Json(_)));
1596
1597 let input: ValidateInput = "{}".into();
1599 assert!(matches!(input, ValidateInput::Json(_)));
1600
1601 let input: ValidateInput = serde_json::json!({}).into();
1603 assert!(matches!(input, ValidateInput::JsonValue(_)));
1604
1605 let map: HashMap<String, Value> = HashMap::new();
1607 let input: ValidateInput = map.into();
1608 assert!(matches!(input, ValidateInput::Dict(_)));
1609 }
1610
1611 #[test]
1612 fn test_value_to_json_conversions() {
1613 assert_eq!(value_to_json(Value::Null), serde_json::Value::Null);
1614 assert_eq!(value_to_json(Value::Bool(true)), serde_json::json!(true));
1615 assert_eq!(value_to_json(Value::Int(42)), serde_json::json!(42));
1616 assert_eq!(value_to_json(Value::BigInt(100)), serde_json::json!(100));
1617 assert_eq!(
1618 value_to_json(Value::Text("hello".to_string())),
1619 serde_json::json!("hello")
1620 );
1621 let uuid_bytes: [u8; 16] = [
1623 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
1624 0x00, 0x00,
1625 ];
1626 assert_eq!(
1627 value_to_json(Value::Uuid(uuid_bytes)),
1628 serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
1629 );
1630
1631 let arr = vec![Value::Int(1), Value::Int(2), Value::Int(3)];
1633 assert_eq!(
1634 value_to_json(Value::Array(arr)),
1635 serde_json::json!([1, 2, 3])
1636 );
1637 }
1638
1639 #[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
1644 struct TestProduct {
1645 name: String,
1646 price: f64,
1647 #[serde(skip_serializing_if = "Option::is_none")]
1648 description: Option<String>,
1649 }
1650
1651 #[test]
1652 fn test_model_dump_default() {
1653 let product = TestProduct {
1654 name: "Widget".to_string(),
1655 price: 19.99,
1656 description: Some("A useful widget".to_string()),
1657 };
1658 let json = product.model_dump(DumpOptions::default()).unwrap();
1659 assert_eq!(json["name"], "Widget");
1660 assert_eq!(json["price"], 19.99);
1661 assert_eq!(json["description"], "A useful widget");
1662 }
1663
1664 #[test]
1665 fn test_model_dump_json() {
1666 let product = TestProduct {
1667 name: "Gadget".to_string(),
1668 price: 29.99,
1669 description: None,
1670 };
1671 let json_str = product.model_dump_json().unwrap();
1672 assert!(json_str.contains("Gadget"));
1673 assert!(json_str.contains("29.99"));
1674 }
1675
1676 #[test]
1677 fn test_model_dump_json_pretty() {
1678 let product = TestProduct {
1679 name: "Gadget".to_string(),
1680 price: 29.99,
1681 description: None,
1682 };
1683 let json_str = product.model_dump_json_pretty().unwrap();
1684 assert!(json_str.contains('\n'));
1686 assert!(json_str.contains("Gadget"));
1687 }
1688
1689 #[test]
1690 fn test_model_dump_json_with_options_compact() {
1691 let product = TestProduct {
1692 name: "Widget".to_string(),
1693 price: 19.99,
1694 description: Some("A widget".to_string()),
1695 };
1696
1697 let json_str = product
1699 .model_dump_json_with_options(DumpOptions::default())
1700 .unwrap();
1701 assert!(!json_str.contains('\n')); assert!(json_str.contains("Widget"));
1703 assert!(json_str.contains("19.99"));
1704 }
1705
1706 #[test]
1707 fn test_model_dump_json_with_options_indent() {
1708 let product = TestProduct {
1709 name: "Widget".to_string(),
1710 price: 19.99,
1711 description: Some("A widget".to_string()),
1712 };
1713
1714 let json_str = product
1716 .model_dump_json_with_options(DumpOptions::default().indent(2))
1717 .unwrap();
1718 assert!(json_str.contains('\n')); assert!(json_str.contains(" \"name\"")); assert!(json_str.contains("Widget"));
1721
1722 let json_str = product
1724 .model_dump_json_with_options(DumpOptions::default().indent(4))
1725 .unwrap();
1726 assert!(json_str.contains(" \"name\"")); }
1728
1729 #[test]
1730 fn test_model_dump_json_with_options_combined() {
1731 let product = TestProduct {
1732 name: "Widget".to_string(),
1733 price: 19.99,
1734 description: Some("A widget".to_string()),
1735 };
1736
1737 let json_str = product
1739 .model_dump_json_with_options(DumpOptions::default().exclude(["price"]).indent(2))
1740 .unwrap();
1741 assert!(json_str.contains('\n')); assert!(json_str.contains("Widget"));
1743 assert!(!json_str.contains("19.99")); }
1745
1746 #[test]
1747 fn test_dump_options_indent_builder() {
1748 let options = DumpOptions::new().indent(4);
1749 assert_eq!(options.indent, Some(4));
1750
1751 let options2 = DumpOptions::new()
1753 .indent(2)
1754 .by_alias()
1755 .exclude(["password"]);
1756 assert_eq!(options2.indent, Some(2));
1757 assert!(options2.by_alias);
1758 assert!(options2.exclude.unwrap().contains("password"));
1759 }
1760
1761 #[test]
1762 fn test_model_dump_include() {
1763 let product = TestProduct {
1764 name: "Widget".to_string(),
1765 price: 19.99,
1766 description: Some("A widget".to_string()),
1767 };
1768 let options = DumpOptions::new().include(["name"]);
1769 let json = product.model_dump(options).unwrap();
1770 assert!(json.get("name").is_some());
1771 assert!(json.get("price").is_none());
1772 assert!(json.get("description").is_none());
1773 }
1774
1775 #[test]
1776 fn test_model_dump_exclude() {
1777 let product = TestProduct {
1778 name: "Widget".to_string(),
1779 price: 19.99,
1780 description: Some("A widget".to_string()),
1781 };
1782 let options = DumpOptions::new().exclude(["description"]);
1783 let json = product.model_dump(options).unwrap();
1784 assert!(json.get("name").is_some());
1785 assert!(json.get("price").is_some());
1786 assert!(json.get("description").is_none());
1787 }
1788
1789 #[test]
1790 fn test_model_dump_exclude_none() {
1791 let product = TestProduct {
1792 name: "Widget".to_string(),
1793 price: 19.99,
1794 description: None,
1795 };
1796 let options = DumpOptions::new().exclude_none();
1799 let json = product.model_dump(options).unwrap();
1800 assert!(json.get("name").is_some());
1801 }
1803
1804 #[test]
1805 fn test_dump_options_builder() {
1806 let options = DumpOptions::new()
1807 .json()
1808 .include(["name", "age"])
1809 .exclude(["password"])
1810 .by_alias()
1811 .exclude_none()
1812 .exclude_defaults()
1813 .round_trip();
1814
1815 assert_eq!(options.mode, DumpMode::Json);
1816 assert!(options.include.is_some());
1817 assert!(options.exclude.is_some());
1818 assert!(options.by_alias);
1819 assert!(options.exclude_none);
1820 assert!(options.exclude_defaults);
1821 assert!(options.round_trip);
1822 }
1823
1824 #[test]
1825 fn test_dump_mode_default() {
1826 assert_eq!(DumpMode::default(), DumpMode::Json);
1827 }
1828
1829 #[test]
1830 fn test_model_dump_include_exclude_combined() {
1831 let user = TestUser {
1832 name: "Alice".to_string(),
1833 age: 30,
1834 active: true,
1835 };
1836 let options = DumpOptions::new().include(["name", "age"]).exclude(["age"]);
1838 let json = user.model_dump(options).unwrap();
1839 assert!(json.get("name").is_some());
1841 assert!(json.get("age").is_none());
1842 assert!(json.get("active").is_none());
1843 }
1844
1845 #[test]
1846 fn test_model_dump_accepts_python_mode_and_round_trip() {
1847 let product = TestProduct {
1848 name: "Widget".to_string(),
1849 price: 19.99,
1850 description: Some("A useful widget".to_string()),
1851 };
1852 let json = product
1853 .model_dump(DumpOptions::default().python().round_trip())
1854 .unwrap();
1855
1856 assert_eq!(json["name"], "Widget");
1857 assert_eq!(json["price"], 19.99);
1858 assert_eq!(json["description"], "A useful widget");
1859 }
1860
1861 use crate::{FieldInfo, Row, SqlType};
1866
1867 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1869 struct TestAliasedUser {
1870 id: i64,
1871 name: String,
1872 email: String,
1873 }
1874
1875 impl Model for TestAliasedUser {
1876 const TABLE_NAME: &'static str = "users";
1877 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1878
1879 fn fields() -> &'static [FieldInfo] {
1880 static FIELDS: &[FieldInfo] = &[
1881 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
1882 FieldInfo::new("name", "name", SqlType::Text)
1883 .validation_alias("userName")
1884 .serialization_alias("displayName"),
1885 FieldInfo::new("email", "email", SqlType::Text).alias("emailAddress"), ];
1887 FIELDS
1888 }
1889
1890 fn to_row(&self) -> Vec<(&'static str, Value)> {
1891 vec![
1892 ("id", Value::BigInt(self.id)),
1893 ("name", Value::Text(self.name.clone())),
1894 ("email", Value::Text(self.email.clone())),
1895 ]
1896 }
1897
1898 fn from_row(row: &Row) -> crate::Result<Self> {
1899 Ok(Self {
1900 id: row.get_named("id")?,
1901 name: row.get_named("name")?,
1902 email: row.get_named("email")?,
1903 })
1904 }
1905
1906 fn primary_key_value(&self) -> Vec<Value> {
1907 vec![Value::BigInt(self.id)]
1908 }
1909
1910 fn is_new(&self) -> bool {
1911 false
1912 }
1913 }
1914
1915 #[test]
1916 fn test_apply_validation_aliases() {
1917 let fields = TestAliasedUser::fields();
1918
1919 let mut json = serde_json::json!({
1921 "id": 1,
1922 "userName": "Alice",
1923 "email": "alice@example.com"
1924 });
1925 apply_validation_aliases(&mut json, fields);
1926
1927 assert_eq!(json["name"], "Alice");
1929 assert!(json.get("userName").is_none());
1930
1931 let mut json2 = serde_json::json!({
1933 "id": 1,
1934 "name": "Bob",
1935 "emailAddress": "bob@example.com"
1936 });
1937 apply_validation_aliases(&mut json2, fields);
1938
1939 assert_eq!(json2["email"], "bob@example.com");
1941 assert!(json2.get("emailAddress").is_none());
1942 }
1943
1944 #[test]
1945 fn test_apply_serialization_aliases() {
1946 let fields = TestAliasedUser::fields();
1947
1948 let mut json = serde_json::json!({
1949 "id": 1,
1950 "name": "Alice",
1951 "email": "alice@example.com"
1952 });
1953 apply_serialization_aliases(&mut json, fields);
1954
1955 assert_eq!(json["displayName"], "Alice");
1957 assert!(json.get("name").is_none());
1958
1959 assert_eq!(json["emailAddress"], "alice@example.com");
1961 assert!(json.get("email").is_none());
1962 }
1963
1964 #[test]
1965 fn test_sql_model_validate_with_validation_alias() {
1966 let json = r#"{"id": 1, "userName": "Alice", "email": "alice@example.com"}"#;
1968 let user: TestAliasedUser = TestAliasedUser::sql_model_validate_json(json).unwrap();
1969
1970 assert_eq!(user.id, 1);
1971 assert_eq!(user.name, "Alice");
1972 assert_eq!(user.email, "alice@example.com");
1973 }
1974
1975 #[test]
1976 fn test_sql_model_validate_with_regular_alias() {
1977 let json = r#"{"id": 1, "name": "Bob", "emailAddress": "bob@example.com"}"#;
1979 let user: TestAliasedUser = TestAliasedUser::sql_model_validate_json(json).unwrap();
1980
1981 assert_eq!(user.id, 1);
1982 assert_eq!(user.name, "Bob");
1983 assert_eq!(user.email, "bob@example.com");
1984 }
1985
1986 #[test]
1987 fn test_sql_model_validate_with_field_name() {
1988 let json = r#"{"id": 1, "name": "Charlie", "email": "charlie@example.com"}"#;
1990 let user: TestAliasedUser = TestAliasedUser::sql_model_validate_json(json).unwrap();
1991
1992 assert_eq!(user.id, 1);
1993 assert_eq!(user.name, "Charlie");
1994 assert_eq!(user.email, "charlie@example.com");
1995 }
1996
1997 #[test]
1998 fn test_sql_model_dump_by_alias() {
1999 let user = TestAliasedUser {
2000 id: 1,
2001 name: "Alice".to_string(),
2002 email: "alice@example.com".to_string(),
2003 };
2004
2005 let json = user
2006 .sql_model_dump(DumpOptions::default().by_alias())
2007 .unwrap();
2008
2009 assert_eq!(json["displayName"], "Alice");
2011 assert!(json.get("name").is_none());
2012
2013 assert_eq!(json["emailAddress"], "alice@example.com");
2015 assert!(json.get("email").is_none());
2016 }
2017
2018 #[test]
2019 fn test_sql_model_dump_without_alias() {
2020 let user = TestAliasedUser {
2021 id: 1,
2022 name: "Alice".to_string(),
2023 email: "alice@example.com".to_string(),
2024 };
2025
2026 let json = user.sql_model_dump(DumpOptions::default()).unwrap();
2028
2029 assert_eq!(json["name"], "Alice");
2030 assert_eq!(json["email"], "alice@example.com");
2031 assert!(json.get("displayName").is_none());
2032 assert!(json.get("emailAddress").is_none());
2033 }
2034
2035 #[test]
2036 fn test_sql_model_dump_accepts_python_mode_and_round_trip() {
2037 let user = TestAliasedUser {
2038 id: 1,
2039 name: "Alice".to_string(),
2040 email: "alice@example.com".to_string(),
2041 };
2042 let json = user
2043 .sql_model_dump(DumpOptions::default().python().round_trip())
2044 .unwrap();
2045
2046 assert_eq!(json["name"], "Alice");
2047 assert_eq!(json["email"], "alice@example.com");
2048 }
2049
2050 #[test]
2051 fn test_tracked_model_dump_accepts_python_mode_and_round_trip() {
2052 let user = TestAliasedUser {
2053 id: 1,
2054 name: "Alice".to_string(),
2055 email: "alice@example.com".to_string(),
2056 };
2057 let tracked = crate::TrackedModel::all_fields_set(user);
2058 let json = tracked
2059 .sql_model_dump(DumpOptions::default().python().round_trip())
2060 .unwrap();
2061
2062 assert_eq!(json["name"], "Alice");
2063 assert_eq!(json["email"], "alice@example.com");
2064 }
2065
2066 #[test]
2067 fn test_alias_does_not_overwrite_existing() {
2068 let fields = TestAliasedUser::fields();
2069
2070 let mut json = serde_json::json!({
2072 "id": 1,
2073 "name": "FieldName",
2074 "userName": "AliasName",
2075 "email": "test@example.com"
2076 });
2077 apply_validation_aliases(&mut json, fields);
2078
2079 assert_eq!(json["name"], "FieldName");
2081 assert!(json.get("userName").is_none());
2083 }
2084
2085 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2091 struct TestUserWithComputed {
2092 id: i64,
2093 first_name: String,
2094 last_name: String,
2095 #[serde(default)]
2096 full_name: String, }
2098
2099 impl Model for TestUserWithComputed {
2100 const TABLE_NAME: &'static str = "users";
2101 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2102
2103 fn fields() -> &'static [FieldInfo] {
2104 static FIELDS: &[FieldInfo] = &[
2105 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2106 FieldInfo::new("first_name", "first_name", SqlType::Text),
2107 FieldInfo::new("last_name", "last_name", SqlType::Text),
2108 FieldInfo::new("full_name", "full_name", SqlType::Text).computed(true),
2109 ];
2110 FIELDS
2111 }
2112
2113 fn to_row(&self) -> Vec<(&'static str, Value)> {
2114 vec![
2116 ("id", Value::BigInt(self.id)),
2117 ("first_name", Value::Text(self.first_name.clone())),
2118 ("last_name", Value::Text(self.last_name.clone())),
2119 ]
2120 }
2121
2122 fn from_row(row: &Row) -> crate::Result<Self> {
2123 Ok(Self {
2124 id: row.get_named("id")?,
2125 first_name: row.get_named("first_name")?,
2126 last_name: row.get_named("last_name")?,
2127 full_name: String::new(),
2129 })
2130 }
2131
2132 fn primary_key_value(&self) -> Vec<Value> {
2133 vec![Value::BigInt(self.id)]
2134 }
2135
2136 fn is_new(&self) -> bool {
2137 false
2138 }
2139 }
2140
2141 #[test]
2142 fn test_computed_field_included_by_default() {
2143 let user = TestUserWithComputed {
2144 id: 1,
2145 first_name: "John".to_string(),
2146 last_name: "Doe".to_string(),
2147 full_name: "John Doe".to_string(),
2148 };
2149
2150 let json = user.sql_model_dump(DumpOptions::default()).unwrap();
2152
2153 assert_eq!(json["id"], 1);
2154 assert_eq!(json["first_name"], "John");
2155 assert_eq!(json["last_name"], "Doe");
2156 assert_eq!(json["full_name"], "John Doe"); }
2158
2159 #[test]
2160 fn test_computed_field_excluded_with_option() {
2161 let user = TestUserWithComputed {
2162 id: 1,
2163 first_name: "John".to_string(),
2164 last_name: "Doe".to_string(),
2165 full_name: "John Doe".to_string(),
2166 };
2167
2168 let json = user
2170 .sql_model_dump(DumpOptions::default().exclude_computed_fields())
2171 .unwrap();
2172
2173 assert_eq!(json["id"], 1);
2174 assert_eq!(json["first_name"], "John");
2175 assert_eq!(json["last_name"], "Doe");
2176 assert!(json.get("full_name").is_none()); }
2178
2179 #[test]
2180 fn test_computed_field_not_in_to_row() {
2181 let user = TestUserWithComputed {
2182 id: 1,
2183 first_name: "Jane".to_string(),
2184 last_name: "Smith".to_string(),
2185 full_name: "Jane Smith".to_string(),
2186 };
2187
2188 let row = user.to_row();
2190
2191 assert_eq!(row.len(), 3);
2193 let field_names: Vec<&str> = row.iter().map(|(name, _)| *name).collect();
2194 assert!(field_names.contains(&"id"));
2195 assert!(field_names.contains(&"first_name"));
2196 assert!(field_names.contains(&"last_name"));
2197 assert!(!field_names.contains(&"full_name")); }
2199
2200 #[test]
2201 fn test_computed_field_select_fields_excludes() {
2202 let fields = TestUserWithComputed::fields();
2203
2204 let computed: Vec<&FieldInfo> = fields.iter().filter(|f| f.computed).collect();
2206 assert_eq!(computed.len(), 1);
2207 assert_eq!(computed[0].name, "full_name");
2208
2209 let non_computed: Vec<&FieldInfo> = fields.iter().filter(|f| !f.computed).collect();
2211 assert_eq!(non_computed.len(), 3);
2212 }
2213
2214 #[test]
2215 fn test_computed_field_with_other_dump_options() {
2216 let user = TestUserWithComputed {
2217 id: 1,
2218 first_name: "John".to_string(),
2219 last_name: "Doe".to_string(),
2220 full_name: "John Doe".to_string(),
2221 };
2222
2223 let json = user
2225 .sql_model_dump(DumpOptions::default().exclude_computed_fields().include([
2226 "id",
2227 "first_name",
2228 "full_name",
2229 ]))
2230 .unwrap();
2231
2232 assert!(json.get("id").is_some());
2235 assert!(json.get("first_name").is_some());
2236 assert!(json.get("full_name").is_none()); assert!(json.get("last_name").is_none()); }
2239
2240 #[test]
2241 fn test_dump_options_exclude_computed_fields_builder() {
2242 let options = DumpOptions::new().exclude_computed_fields();
2243 assert!(options.exclude_computed_fields);
2244
2245 let options2 = DumpOptions::new()
2247 .exclude_computed_fields()
2248 .by_alias()
2249 .exclude_none();
2250 assert!(options2.exclude_computed_fields);
2251 assert!(options2.by_alias);
2252 assert!(options2.exclude_none);
2253 }
2254
2255 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2257 struct TestUserWithComputedAndAlias {
2258 id: i64,
2259 first_name: String,
2260 #[serde(default)]
2261 display_name: String, }
2263
2264 impl Model for TestUserWithComputedAndAlias {
2265 const TABLE_NAME: &'static str = "users";
2266 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2267
2268 fn fields() -> &'static [FieldInfo] {
2269 static FIELDS: &[FieldInfo] = &[
2270 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2271 FieldInfo::new("first_name", "first_name", SqlType::Text)
2272 .serialization_alias("firstName"),
2273 FieldInfo::new("display_name", "display_name", SqlType::Text)
2274 .computed(true)
2275 .serialization_alias("displayName"),
2276 ];
2277 FIELDS
2278 }
2279
2280 fn to_row(&self) -> Vec<(&'static str, Value)> {
2281 vec![
2282 ("id", Value::BigInt(self.id)),
2283 ("first_name", Value::Text(self.first_name.clone())),
2284 ]
2285 }
2286
2287 fn from_row(row: &Row) -> crate::Result<Self> {
2288 Ok(Self {
2289 id: row.get_named("id")?,
2290 first_name: row.get_named("first_name")?,
2291 display_name: String::new(),
2292 })
2293 }
2294
2295 fn primary_key_value(&self) -> Vec<Value> {
2296 vec![Value::BigInt(self.id)]
2297 }
2298
2299 fn is_new(&self) -> bool {
2300 false
2301 }
2302 }
2303
2304 #[test]
2305 fn test_exclude_computed_with_by_alias() {
2306 let user = TestUserWithComputedAndAlias {
2309 id: 1,
2310 first_name: "John".to_string(),
2311 display_name: "John Doe".to_string(),
2312 };
2313
2314 let json = user
2316 .sql_model_dump(DumpOptions::default().by_alias())
2317 .unwrap();
2318 assert_eq!(json["firstName"], "John"); assert_eq!(json["displayName"], "John Doe"); assert!(json.get("first_name").is_none()); assert!(json.get("display_name").is_none()); let json = user
2325 .sql_model_dump(DumpOptions::default().exclude_computed_fields())
2326 .unwrap();
2327 assert_eq!(json["first_name"], "John");
2328 assert!(json.get("display_name").is_none()); let json = user
2334 .sql_model_dump(DumpOptions::default().by_alias().exclude_computed_fields())
2335 .unwrap();
2336 assert_eq!(json["firstName"], "John"); assert!(json.get("displayName").is_none()); assert!(json.get("display_name").is_none()); }
2340
2341 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2347 struct TestModelWithDefaults {
2348 id: i64,
2349 name: String,
2350 count: i32, active: bool, score: f64, label: String, }
2355
2356 impl Model for TestModelWithDefaults {
2357 const TABLE_NAME: &'static str = "test_defaults";
2358 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2359
2360 fn fields() -> &'static [FieldInfo] {
2361 static FIELDS: &[FieldInfo] = &[
2362 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2363 FieldInfo::new("name", "name", SqlType::Text),
2364 FieldInfo::new("count", "count", SqlType::Integer).default_json("0"),
2365 FieldInfo::new("active", "active", SqlType::Boolean).default_json("false"),
2366 FieldInfo::new("score", "score", SqlType::Double).default_json("0.0"),
2367 FieldInfo::new("label", "label", SqlType::Text).default_json("\"default\""),
2368 ];
2369 FIELDS
2370 }
2371
2372 fn to_row(&self) -> Vec<(&'static str, Value)> {
2373 vec![
2374 ("id", Value::BigInt(self.id)),
2375 ("name", Value::Text(self.name.clone())),
2376 ("count", Value::Int(self.count)),
2377 ("active", Value::Bool(self.active)),
2378 ("score", Value::Double(self.score)),
2379 ("label", Value::Text(self.label.clone())),
2380 ]
2381 }
2382
2383 fn from_row(row: &Row) -> crate::Result<Self> {
2384 Ok(Self {
2385 id: row.get_named("id")?,
2386 name: row.get_named("name")?,
2387 count: row.get_named("count")?,
2388 active: row.get_named("active")?,
2389 score: row.get_named("score")?,
2390 label: row.get_named("label")?,
2391 })
2392 }
2393
2394 fn primary_key_value(&self) -> Vec<Value> {
2395 vec![Value::BigInt(self.id)]
2396 }
2397
2398 fn is_new(&self) -> bool {
2399 false
2400 }
2401 }
2402
2403 #[test]
2404 fn test_exclude_defaults_all_at_default() {
2405 let model = TestModelWithDefaults {
2406 id: 1,
2407 name: "Test".to_string(),
2408 count: 0, active: false, score: 0.0, label: "default".to_string(), };
2413
2414 let json = model
2415 .sql_model_dump(DumpOptions::default().exclude_defaults())
2416 .unwrap();
2417
2418 assert!(json.get("id").is_some());
2420 assert!(json.get("name").is_some());
2421
2422 assert!(json.get("count").is_none());
2424 assert!(json.get("active").is_none());
2425 assert!(json.get("score").is_none());
2426 assert!(json.get("label").is_none());
2427 }
2428
2429 #[test]
2430 fn test_exclude_defaults_none_at_default() {
2431 let model = TestModelWithDefaults {
2432 id: 1,
2433 name: "Test".to_string(),
2434 count: 42, active: true, score: 3.5, label: "custom".to_string(), };
2439
2440 let json = model
2441 .sql_model_dump(DumpOptions::default().exclude_defaults())
2442 .unwrap();
2443
2444 assert!(json.get("id").is_some());
2446 assert!(json.get("name").is_some());
2447 assert!(json.get("count").is_some());
2448 assert!(json.get("active").is_some());
2449 assert!(json.get("score").is_some());
2450 assert!(json.get("label").is_some());
2451
2452 assert_eq!(json["count"], 42);
2454 assert_eq!(json["active"], true);
2455 assert_eq!(json["score"], 3.5);
2456 assert_eq!(json["label"], "custom");
2457 }
2458
2459 #[test]
2460 fn test_exclude_defaults_mixed() {
2461 let model = TestModelWithDefaults {
2462 id: 1,
2463 name: "Test".to_string(),
2464 count: 0, active: true, score: 0.0, label: "custom".to_string(), };
2469
2470 let json = model
2471 .sql_model_dump(DumpOptions::default().exclude_defaults())
2472 .unwrap();
2473
2474 assert!(json.get("id").is_some());
2475 assert!(json.get("name").is_some());
2476
2477 assert!(json.get("count").is_none());
2479 assert!(json.get("score").is_none());
2480
2481 assert!(json.get("active").is_some());
2483 assert!(json.get("label").is_some());
2484 assert_eq!(json["active"], true);
2485 assert_eq!(json["label"], "custom");
2486 }
2487
2488 #[test]
2489 fn test_exclude_defaults_without_flag() {
2490 let model = TestModelWithDefaults {
2491 id: 1,
2492 name: "Test".to_string(),
2493 count: 0, active: false, score: 0.0, label: "default".to_string(), };
2498
2499 let json = model.sql_model_dump(DumpOptions::default()).unwrap();
2501
2502 assert!(json.get("id").is_some());
2503 assert!(json.get("name").is_some());
2504 assert!(json.get("count").is_some());
2505 assert!(json.get("active").is_some());
2506 assert!(json.get("score").is_some());
2507 assert!(json.get("label").is_some());
2508 }
2509
2510 #[test]
2511 fn test_exclude_defaults_with_by_alias() {
2512 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2516 struct TestAliasWithDefaults {
2517 id: i64,
2518 count: i32,
2519 }
2520
2521 impl Model for TestAliasWithDefaults {
2522 const TABLE_NAME: &'static str = "test";
2523 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2524
2525 fn fields() -> &'static [FieldInfo] {
2526 static FIELDS: &[FieldInfo] = &[
2527 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2528 FieldInfo::new("count", "count", SqlType::Integer)
2529 .default_json("0")
2530 .serialization_alias("itemCount"),
2531 ];
2532 FIELDS
2533 }
2534
2535 fn to_row(&self) -> Vec<(&'static str, Value)> {
2536 vec![
2537 ("id", Value::BigInt(self.id)),
2538 ("count", Value::Int(self.count)),
2539 ]
2540 }
2541
2542 fn from_row(row: &Row) -> crate::Result<Self> {
2543 Ok(Self {
2544 id: row.get_named("id")?,
2545 count: row.get_named("count")?,
2546 })
2547 }
2548
2549 fn primary_key_value(&self) -> Vec<Value> {
2550 vec![Value::BigInt(self.id)]
2551 }
2552
2553 fn is_new(&self) -> bool {
2554 false
2555 }
2556 }
2557
2558 let model_at_default = TestAliasWithDefaults { id: 1, count: 0 };
2560 let json = model_at_default
2561 .sql_model_dump(DumpOptions::default().exclude_defaults().by_alias())
2562 .unwrap();
2563
2564 assert!(json.get("count").is_none());
2566 assert!(json.get("itemCount").is_none());
2567
2568 let model_not_at_default = TestAliasWithDefaults { id: 1, count: 5 };
2570 let json = model_not_at_default
2571 .sql_model_dump(DumpOptions::default().exclude_defaults().by_alias())
2572 .unwrap();
2573
2574 assert!(json.get("count").is_none()); assert_eq!(json["itemCount"], 5); }
2578
2579 #[test]
2580 fn test_field_info_default_json() {
2581 let field1 = FieldInfo::new("count", "count", SqlType::Integer).default_json("0");
2583 assert_eq!(field1.default_json, Some("0"));
2584 assert!(field1.has_default);
2585
2586 let field2 =
2587 FieldInfo::new("name", "name", SqlType::Text).default_json_opt(Some("\"hello\""));
2588 assert_eq!(field2.default_json, Some("\"hello\""));
2589 assert!(field2.has_default);
2590
2591 let field3 = FieldInfo::new("name", "name", SqlType::Text).default_json_opt(None);
2592 assert_eq!(field3.default_json, None);
2593 assert!(!field3.has_default);
2594
2595 let field4 = FieldInfo::new("flag", "flag", SqlType::Boolean).has_default(true);
2596 assert!(field4.has_default);
2597 assert_eq!(field4.default_json, None); }
2599
2600 #[test]
2603 fn test_sqlmodel_update_from_dict() {
2604 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2605 struct TestUser {
2606 id: i64,
2607 name: String,
2608 age: i32,
2609 }
2610
2611 impl Model for TestUser {
2612 const TABLE_NAME: &'static str = "users";
2613 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2614
2615 fn fields() -> &'static [FieldInfo] {
2616 static FIELDS: &[FieldInfo] = &[
2617 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2618 FieldInfo::new("name", "name", SqlType::Text),
2619 FieldInfo::new("age", "age", SqlType::Integer),
2620 ];
2621 FIELDS
2622 }
2623
2624 fn to_row(&self) -> Vec<(&'static str, Value)> {
2625 vec![
2626 ("id", Value::BigInt(self.id)),
2627 ("name", Value::Text(self.name.clone())),
2628 ("age", Value::Int(self.age)),
2629 ]
2630 }
2631
2632 fn from_row(row: &Row) -> crate::Result<Self> {
2633 Ok(Self {
2634 id: row.get_named("id")?,
2635 name: row.get_named("name")?,
2636 age: row.get_named("age")?,
2637 })
2638 }
2639
2640 fn primary_key_value(&self) -> Vec<Value> {
2641 vec![Value::BigInt(self.id)]
2642 }
2643
2644 fn is_new(&self) -> bool {
2645 false
2646 }
2647 }
2648
2649 let mut user = TestUser {
2650 id: 1,
2651 name: "Alice".to_string(),
2652 age: 30,
2653 };
2654
2655 let update = HashMap::from([("name".to_string(), serde_json::json!("Bob"))]);
2657 user.sqlmodel_update(update, UpdateOptions::default())
2658 .unwrap();
2659
2660 assert_eq!(user.name, "Bob");
2661 assert_eq!(user.age, 30); }
2663
2664 #[test]
2665 fn test_sqlmodel_update_with_update_fields_filter() {
2666 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2667 struct TestUser {
2668 id: i64,
2669 name: String,
2670 age: i32,
2671 }
2672
2673 impl Model for TestUser {
2674 const TABLE_NAME: &'static str = "users";
2675 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2676
2677 fn fields() -> &'static [FieldInfo] {
2678 static FIELDS: &[FieldInfo] = &[
2679 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2680 FieldInfo::new("name", "name", SqlType::Text),
2681 FieldInfo::new("age", "age", SqlType::Integer),
2682 ];
2683 FIELDS
2684 }
2685
2686 fn to_row(&self) -> Vec<(&'static str, Value)> {
2687 vec![
2688 ("id", Value::BigInt(self.id)),
2689 ("name", Value::Text(self.name.clone())),
2690 ("age", Value::Int(self.age)),
2691 ]
2692 }
2693
2694 fn from_row(row: &Row) -> crate::Result<Self> {
2695 Ok(Self {
2696 id: row.get_named("id")?,
2697 name: row.get_named("name")?,
2698 age: row.get_named("age")?,
2699 })
2700 }
2701
2702 fn primary_key_value(&self) -> Vec<Value> {
2703 vec![Value::BigInt(self.id)]
2704 }
2705
2706 fn is_new(&self) -> bool {
2707 false
2708 }
2709 }
2710
2711 let mut user = TestUser {
2712 id: 1,
2713 name: "Alice".to_string(),
2714 age: 30,
2715 };
2716
2717 let update = HashMap::from([
2719 ("name".to_string(), serde_json::json!("Bob")),
2720 ("age".to_string(), serde_json::json!(25)),
2721 ]);
2722 user.sqlmodel_update(update, UpdateOptions::default().update_fields(["name"]))
2723 .unwrap();
2724
2725 assert_eq!(user.name, "Bob"); assert_eq!(user.age, 30); }
2728
2729 #[test]
2730 fn test_sqlmodel_update_invalid_field_error() {
2731 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2732 struct TestUser {
2733 id: i64,
2734 name: String,
2735 }
2736
2737 impl Model for TestUser {
2738 const TABLE_NAME: &'static str = "users";
2739 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2740
2741 fn fields() -> &'static [FieldInfo] {
2742 static FIELDS: &[FieldInfo] = &[
2743 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2744 FieldInfo::new("name", "name", SqlType::Text),
2745 ];
2746 FIELDS
2747 }
2748
2749 fn to_row(&self) -> Vec<(&'static str, Value)> {
2750 vec![
2751 ("id", Value::BigInt(self.id)),
2752 ("name", Value::Text(self.name.clone())),
2753 ]
2754 }
2755
2756 fn from_row(row: &Row) -> crate::Result<Self> {
2757 Ok(Self {
2758 id: row.get_named("id")?,
2759 name: row.get_named("name")?,
2760 })
2761 }
2762
2763 fn primary_key_value(&self) -> Vec<Value> {
2764 vec![Value::BigInt(self.id)]
2765 }
2766
2767 fn is_new(&self) -> bool {
2768 false
2769 }
2770 }
2771
2772 let mut user = TestUser {
2773 id: 1,
2774 name: "Alice".to_string(),
2775 };
2776
2777 let update = HashMap::from([("invalid_field".to_string(), serde_json::json!("value"))]);
2779 let result = user.sqlmodel_update(update, UpdateOptions::default());
2780
2781 assert!(result.is_err());
2782 let err = result.unwrap_err();
2783 assert!(err.errors.iter().any(|e| e.field == "invalid_field"));
2784 }
2785
2786 #[test]
2787 fn test_sqlmodel_update_from_model() {
2788 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2789 struct TestUser {
2790 id: i64,
2791 name: String,
2792 email: Option<String>,
2793 }
2794
2795 impl Model for TestUser {
2796 const TABLE_NAME: &'static str = "users";
2797 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2798
2799 fn fields() -> &'static [FieldInfo] {
2800 static FIELDS: &[FieldInfo] = &[
2801 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2802 FieldInfo::new("name", "name", SqlType::Text),
2803 FieldInfo::new("email", "email", SqlType::Text).nullable(true),
2804 ];
2805 FIELDS
2806 }
2807
2808 fn to_row(&self) -> Vec<(&'static str, Value)> {
2809 vec![
2810 ("id", Value::BigInt(self.id)),
2811 ("name", Value::Text(self.name.clone())),
2812 ("email", self.email.clone().map_or(Value::Null, Value::Text)),
2813 ]
2814 }
2815
2816 fn from_row(row: &Row) -> crate::Result<Self> {
2817 Ok(Self {
2818 id: row.get_named("id")?,
2819 name: row.get_named("name")?,
2820 email: row.get_named("email").ok(),
2821 })
2822 }
2823
2824 fn primary_key_value(&self) -> Vec<Value> {
2825 vec![Value::BigInt(self.id)]
2826 }
2827
2828 fn is_new(&self) -> bool {
2829 false
2830 }
2831 }
2832
2833 let mut user = TestUser {
2834 id: 1,
2835 name: "Alice".to_string(),
2836 email: Some("alice@example.com".to_string()),
2837 };
2838
2839 let patch = TestUser {
2841 id: 0, name: "Bob".to_string(),
2843 email: None, };
2845
2846 user.sqlmodel_update_from(&patch, UpdateOptions::default())
2847 .unwrap();
2848
2849 assert_eq!(user.name, "Bob"); assert_eq!(user.email, Some("alice@example.com".to_string())); }
2852
2853 #[test]
2854 fn test_sqlmodel_update_dict_convenience() {
2855 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2856 struct TestItem {
2857 id: i64,
2858 count: i32,
2859 }
2860
2861 impl Model for TestItem {
2862 const TABLE_NAME: &'static str = "items";
2863 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2864
2865 fn fields() -> &'static [FieldInfo] {
2866 static FIELDS: &[FieldInfo] = &[
2867 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2868 FieldInfo::new("count", "count", SqlType::Integer),
2869 ];
2870 FIELDS
2871 }
2872
2873 fn to_row(&self) -> Vec<(&'static str, Value)> {
2874 vec![
2875 ("id", Value::BigInt(self.id)),
2876 ("count", Value::Int(self.count)),
2877 ]
2878 }
2879
2880 fn from_row(row: &Row) -> crate::Result<Self> {
2881 Ok(Self {
2882 id: row.get_named("id")?,
2883 count: row.get_named("count")?,
2884 })
2885 }
2886
2887 fn primary_key_value(&self) -> Vec<Value> {
2888 vec![Value::BigInt(self.id)]
2889 }
2890
2891 fn is_new(&self) -> bool {
2892 false
2893 }
2894 }
2895
2896 let mut item = TestItem { id: 1, count: 10 };
2897
2898 item.sqlmodel_update_dict(HashMap::from([(
2900 "count".to_string(),
2901 serde_json::json!(20),
2902 )]))
2903 .unwrap();
2904
2905 assert_eq!(item.count, 20);
2906 }
2907
2908 #[test]
2913 fn test_credit_card_valid_visa() {
2914 assert!(is_valid_credit_card("4539578763621486"));
2916 }
2917
2918 #[test]
2919 fn test_credit_card_valid_mastercard() {
2920 assert!(is_valid_credit_card("5425233430109903"));
2922 }
2923
2924 #[test]
2925 fn test_credit_card_valid_amex() {
2926 assert!(is_valid_credit_card("374245455400126"));
2928 }
2929
2930 #[test]
2931 fn test_credit_card_with_spaces() {
2932 assert!(is_valid_credit_card("4539 5787 6362 1486"));
2934 }
2935
2936 #[test]
2937 fn test_credit_card_with_dashes() {
2938 assert!(is_valid_credit_card("4539-5787-6362-1486"));
2940 }
2941
2942 #[test]
2943 fn test_credit_card_invalid_luhn() {
2944 assert!(!is_valid_credit_card("1234567890123456"));
2946 }
2947
2948 #[test]
2949 fn test_credit_card_too_short() {
2950 assert!(!is_valid_credit_card("123456789012"));
2952 }
2953
2954 #[test]
2955 fn test_credit_card_too_long() {
2956 assert!(!is_valid_credit_card("12345678901234567890"));
2958 }
2959
2960 #[test]
2961 fn test_credit_card_empty() {
2962 assert!(!is_valid_credit_card(""));
2963 }
2964
2965 #[test]
2966 fn test_credit_card_non_numeric() {
2967 assert!(!is_valid_credit_card("453957876362abcd"));
2969 }
2970
2971 #[test]
2972 fn test_credit_card_all_zeros() {
2973 assert!(is_valid_credit_card("0000000000000000"));
2976 }
2977
2978 #[test]
2979 fn test_credit_card_valid_discover() {
2980 assert!(is_valid_credit_card("6011111111111117"));
2982 }
2983
2984 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2989 struct Address {
2990 street: String,
2991 city: String,
2992 #[serde(skip_serializing_if = "Option::is_none")]
2993 zip: Option<String>,
2994 }
2995
2996 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2997 struct Person {
2998 name: String,
2999 age: i32,
3000 address: Address,
3001 #[serde(skip_serializing_if = "Option::is_none")]
3002 spouse: Option<Box<Person>>,
3003 }
3004
3005 #[test]
3006 fn test_nested_model_dump_basic() {
3007 let person = Person {
3008 name: "Alice".to_string(),
3009 age: 30,
3010 address: Address {
3011 street: "123 Main St".to_string(),
3012 city: "Springfield".to_string(),
3013 zip: Some("12345".to_string()),
3014 },
3015 spouse: None,
3016 };
3017
3018 let json = person.model_dump(DumpOptions::default()).unwrap();
3019 assert_eq!(json["name"], "Alice");
3020 assert_eq!(json["age"], 30);
3021 assert_eq!(json["address"]["street"], "123 Main St");
3022 assert_eq!(json["address"]["city"], "Springfield");
3023 assert_eq!(json["address"]["zip"], "12345");
3024 }
3025
3026 #[test]
3027 fn test_nested_model_dump_exclude_top_level() {
3028 let person = Person {
3029 name: "Alice".to_string(),
3030 age: 30,
3031 address: Address {
3032 street: "123 Main St".to_string(),
3033 city: "Springfield".to_string(),
3034 zip: Some("12345".to_string()),
3035 },
3036 spouse: None,
3037 };
3038
3039 let json = person
3041 .model_dump(DumpOptions::default().exclude(["age"]))
3042 .unwrap();
3043 assert!(json.get("name").is_some());
3044 assert!(json.get("age").is_none());
3045 assert!(json.get("address").is_some()); assert_eq!(json["address"]["city"], "Springfield");
3048 }
3049
3050 #[test]
3051 fn test_nested_model_dump_exclude_nested_limitation() {
3052 let person = Person {
3056 name: "Alice".to_string(),
3057 age: 30,
3058 address: Address {
3059 street: "123 Main St".to_string(),
3060 city: "Springfield".to_string(),
3061 zip: Some("12345".to_string()),
3062 },
3063 spouse: None,
3064 };
3065
3066 let json = person
3068 .model_dump(DumpOptions::default().exclude(["address.zip"]))
3069 .unwrap();
3070 assert_eq!(json["address"]["zip"], "12345");
3072 }
3073
3074 #[test]
3075 fn test_deeply_nested_model_dump() {
3076 let person = Person {
3077 name: "Alice".to_string(),
3078 age: 30,
3079 address: Address {
3080 street: "123 Main St".to_string(),
3081 city: "Springfield".to_string(),
3082 zip: None,
3083 },
3084 spouse: Some(Box::new(Person {
3085 name: "Bob".to_string(),
3086 age: 32,
3087 address: Address {
3088 street: "456 Oak Ave".to_string(),
3089 city: "Springfield".to_string(),
3090 zip: Some("12346".to_string()),
3091 },
3092 spouse: None,
3093 })),
3094 };
3095
3096 let json = person.model_dump(DumpOptions::default()).unwrap();
3097 assert_eq!(json["name"], "Alice");
3098 assert_eq!(json["spouse"]["name"], "Bob");
3099 assert_eq!(json["spouse"]["address"]["street"], "456 Oak Ave");
3100 }
3101
3102 #[test]
3103 fn test_nested_model_exclude_none() {
3104 let person = Person {
3105 name: "Alice".to_string(),
3106 age: 30,
3107 address: Address {
3108 street: "123 Main St".to_string(),
3109 city: "Springfield".to_string(),
3110 zip: None, },
3112 spouse: None, };
3114
3115 let json = person
3116 .model_dump(DumpOptions::default().exclude_none())
3117 .unwrap();
3118 assert!(json.get("name").is_some());
3119 assert!(json.get("spouse").is_none());
3121 }
3124}