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.is_multiple_of(2);
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.is_multiple_of(10)
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 && let serde_json::Value::Object(ref mut map) = json_value
348 {
349 for (key, value) in update {
350 map.insert(key, value);
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 && let serde_json::Value::Object(ref mut map) = json_value
856 {
857 for (key, value) in update {
858 map.insert(key, value);
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 && let serde_json::Value::Object(ref mut map) = json_value
907 {
908 for (key, value) in update {
909 map.insert(key, value);
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 && let Some(current_value) = map.get(field.name)
1011 {
1012 if let Ok(default_value) =
1014 serde_json::from_str::<serde_json::Value>(default_json)
1015 && current_value == &default_value
1016 {
1017 map.remove(field.name);
1018 }
1019 }
1020 }
1021 }
1022 }
1023
1024 if options.by_alias {
1026 apply_serialization_aliases(&mut value, Self::fields());
1027 }
1028
1029 if let serde_json::Value::Object(ref mut map) = value {
1031 if let Some(ref include) = options.include {
1033 map.retain(|k, _| include.contains(k));
1034 }
1035
1036 if let Some(ref exclude) = options.exclude {
1038 map.retain(|k, _| !exclude.contains(k));
1039 }
1040
1041 if options.exclude_none {
1043 map.retain(|_, v| !v.is_null());
1044 }
1045 }
1046
1047 Ok(value)
1048 }
1049
1050 fn sql_model_dump_json(&self) -> std::result::Result<String, serde_json::Error> {
1052 let value = self.sql_model_dump(DumpOptions::default())?;
1053 serde_json::to_string(&value)
1054 }
1055
1056 fn sql_model_dump_json_pretty(&self) -> std::result::Result<String, serde_json::Error> {
1058 let value = self.sql_model_dump(DumpOptions::default())?;
1059 serde_json::to_string_pretty(&value)
1060 }
1061
1062 fn sql_model_dump_json_by_alias(&self) -> std::result::Result<String, serde_json::Error> {
1064 let value = self.sql_model_dump(DumpOptions::default().by_alias())?;
1065 serde_json::to_string(&value)
1066 }
1067
1068 fn sql_model_dump_json_with_options(
1088 &self,
1089 options: DumpOptions,
1090 ) -> std::result::Result<String, serde_json::Error> {
1091 let value = self.sql_model_dump(DumpOptions {
1092 indent: None, ..options.clone()
1094 })?;
1095
1096 match options.indent {
1097 Some(spaces) => {
1098 let indent_bytes = " ".repeat(spaces).into_bytes();
1099 let formatter = serde_json::ser::PrettyFormatter::with_indent(&indent_bytes);
1100 let mut writer = Vec::new();
1101 let mut ser = serde_json::Serializer::with_formatter(&mut writer, formatter);
1102 serde::Serialize::serialize(&value, &mut ser)?;
1103 String::from_utf8(writer).map_err(|e| {
1105 serde_json::Error::io(std::io::Error::new(
1106 std::io::ErrorKind::InvalidData,
1107 format!("UTF-8 encoding error: {e}"),
1108 ))
1109 })
1110 }
1111 None => serde_json::to_string(&value),
1112 }
1113 }
1114}
1115
1116impl<T: Model + serde::Serialize> SqlModelDump for T {}
1118
1119#[derive(Debug, Clone)]
1127pub enum UpdateInput {
1128 Dict(HashMap<String, serde_json::Value>),
1130 JsonValue(serde_json::Value),
1132}
1133
1134impl From<HashMap<String, serde_json::Value>> for UpdateInput {
1135 fn from(map: HashMap<String, serde_json::Value>) -> Self {
1136 UpdateInput::Dict(map)
1137 }
1138}
1139
1140impl From<serde_json::Value> for UpdateInput {
1141 fn from(value: serde_json::Value) -> Self {
1142 UpdateInput::JsonValue(value)
1143 }
1144}
1145
1146impl From<HashMap<String, Value>> for UpdateInput {
1147 fn from(map: HashMap<String, Value>) -> Self {
1148 let json_map: HashMap<String, serde_json::Value> = map
1149 .into_iter()
1150 .map(|(k, v)| (k, value_to_json(v)))
1151 .collect();
1152 UpdateInput::Dict(json_map)
1153 }
1154}
1155
1156#[derive(Debug, Clone, Default)]
1158pub struct UpdateOptions {
1159 pub update_fields: Option<std::collections::HashSet<String>>,
1161}
1162
1163impl UpdateOptions {
1164 pub fn new() -> Self {
1166 Self::default()
1167 }
1168
1169 pub fn update_fields(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
1171 self.update_fields = Some(fields.into_iter().map(Into::into).collect());
1172 self
1173 }
1174}
1175
1176pub trait SqlModelUpdate: Model + serde::Serialize + DeserializeOwned {
1206 fn sqlmodel_update(
1222 &mut self,
1223 input: impl Into<UpdateInput>,
1224 options: UpdateOptions,
1225 ) -> ValidateResult<()> {
1226 let input = input.into();
1227
1228 let update_map = match input {
1230 UpdateInput::Dict(map) => map,
1231 UpdateInput::JsonValue(value) => {
1232 if let serde_json::Value::Object(map) = value {
1233 map.into_iter().collect()
1234 } else {
1235 let mut err = ValidationError::new();
1236 err.add(
1237 "_update",
1238 ValidationErrorKind::Custom,
1239 "Update input must be an object".to_string(),
1240 );
1241 return Err(err);
1242 }
1243 }
1244 };
1245
1246 let mut current = serde_json::to_value(&*self).map_err(|e| {
1248 let mut err = ValidationError::new();
1249 err.add(
1250 "_model",
1251 ValidationErrorKind::Custom,
1252 format!("Failed to serialize model: {e}"),
1253 );
1254 err
1255 })?;
1256
1257 let valid_fields: std::collections::HashSet<&str> =
1259 Self::fields().iter().map(|f| f.name).collect();
1260
1261 if let serde_json::Value::Object(ref mut current_map) = current {
1263 for (key, value) in update_map {
1264 if !valid_fields.contains(key.as_str()) {
1266 let mut err = ValidationError::new();
1267 err.add(
1268 &key,
1269 ValidationErrorKind::Custom,
1270 format!("Unknown field: {key}"),
1271 );
1272 return Err(err);
1273 }
1274
1275 if let Some(ref allowed) = options.update_fields
1277 && !allowed.contains(&key)
1278 {
1279 continue; }
1281
1282 current_map.insert(key, value);
1284 }
1285 }
1286
1287 let updated: Self = serde_json::from_value(current).map_err(|e| {
1289 let mut err = ValidationError::new();
1290 err.add(
1291 "_model",
1292 ValidationErrorKind::Custom,
1293 format!("Update failed validation: {e}"),
1294 );
1295 err
1296 })?;
1297
1298 *self = updated;
1300
1301 Ok(())
1302 }
1303
1304 fn sqlmodel_update_dict(
1306 &mut self,
1307 dict: HashMap<String, serde_json::Value>,
1308 ) -> ValidateResult<()> {
1309 self.sqlmodel_update(dict, UpdateOptions::default())
1310 }
1311
1312 fn sqlmodel_update_from(&mut self, source: &Self, options: UpdateOptions) -> ValidateResult<()>
1334 where
1335 Self: Sized,
1336 {
1337 let source_json = serde_json::to_value(source).map_err(|e| {
1339 let mut err = ValidationError::new();
1340 err.add(
1341 "_source",
1342 ValidationErrorKind::Custom,
1343 format!("Failed to serialize source model: {e}"),
1344 );
1345 err
1346 })?;
1347
1348 let update_map: HashMap<String, serde_json::Value> =
1350 if let serde_json::Value::Object(map) = source_json {
1351 map.into_iter().filter(|(_, v)| !v.is_null()).collect()
1352 } else {
1353 let mut err = ValidationError::new();
1354 err.add(
1355 "_source",
1356 ValidationErrorKind::Custom,
1357 "Source model must serialize to an object".to_string(),
1358 );
1359 return Err(err);
1360 };
1361
1362 self.sqlmodel_update(update_map, options)
1363 }
1364}
1365
1366impl<T: Model + serde::Serialize + DeserializeOwned> SqlModelUpdate for T {}
1368
1369#[cfg(test)]
1370mod tests {
1371 use super::*;
1372 use serde::{Deserialize, Serialize};
1373
1374 #[test]
1375 fn test_matches_email_pattern() {
1376 let email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
1377
1378 assert!(matches_pattern("test@example.com", email_pattern));
1379 assert!(matches_pattern("user.name+tag@domain.org", email_pattern));
1380 assert!(!matches_pattern("invalid", email_pattern));
1381 assert!(!matches_pattern("@example.com", email_pattern));
1382 assert!(!matches_pattern("test@", email_pattern));
1383 }
1384
1385 #[test]
1386 fn test_matches_url_pattern() {
1387 let url_pattern = r"^https?://[^\s/$.?#].[^\s]*$";
1388
1389 assert!(matches_pattern("https://example.com", url_pattern));
1390 assert!(matches_pattern("http://example.com/path", url_pattern));
1391 assert!(!matches_pattern("ftp://example.com", url_pattern));
1392 assert!(!matches_pattern("not a url", url_pattern));
1393 }
1394
1395 #[test]
1396 fn test_matches_phone_pattern() {
1397 let phone_pattern = r"^\+?[1-9]\d{1,14}$";
1398
1399 assert!(matches_pattern("+12025551234", phone_pattern));
1400 assert!(matches_pattern("12025551234", phone_pattern));
1401 assert!(!matches_pattern("0123456789", phone_pattern)); assert!(!matches_pattern("abc", phone_pattern));
1403 }
1404
1405 #[test]
1406 fn test_matches_uuid_pattern() {
1407 let uuid_pattern =
1408 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}$";
1409
1410 assert!(matches_pattern(
1411 "550e8400-e29b-41d4-a716-446655440000",
1412 uuid_pattern
1413 ));
1414 assert!(matches_pattern(
1415 "550E8400-E29B-41D4-A716-446655440000",
1416 uuid_pattern
1417 ));
1418 assert!(!matches_pattern("invalid-uuid", uuid_pattern));
1419 assert!(!matches_pattern(
1420 "550e8400e29b41d4a716446655440000",
1421 uuid_pattern
1422 ));
1423 }
1424
1425 #[test]
1426 fn test_matches_alphanumeric_pattern() {
1427 let alphanumeric_pattern = r"^[a-zA-Z0-9]+$";
1428
1429 assert!(matches_pattern("abc123", alphanumeric_pattern));
1430 assert!(matches_pattern("ABC", alphanumeric_pattern));
1431 assert!(matches_pattern("123", alphanumeric_pattern));
1432 assert!(!matches_pattern("abc-123", alphanumeric_pattern));
1433 assert!(!matches_pattern("hello world", alphanumeric_pattern));
1434 }
1435
1436 #[test]
1437 fn test_invalid_pattern_returns_false() {
1438 let invalid_pattern = r"[unclosed";
1440 assert!(!matches_pattern("anything", invalid_pattern));
1441 }
1442
1443 #[test]
1444 fn test_validate_pattern_valid() {
1445 assert!(validate_pattern(r"^[a-z]+$").is_none());
1446 assert!(validate_pattern(r"^\d{4}-\d{2}-\d{2}$").is_none());
1447 }
1448
1449 #[test]
1450 fn test_validate_pattern_invalid() {
1451 let result = validate_pattern(r"[unclosed");
1452 assert!(result.is_some());
1453 assert!(result.unwrap().contains("invalid regex pattern"));
1454 }
1455
1456 #[test]
1457 fn test_regex_caching() {
1458 let pattern = r"^test\d+$";
1459
1460 assert!(matches_pattern("test123", pattern));
1462
1463 assert!(matches_pattern("test456", pattern));
1465 assert!(!matches_pattern("invalid", pattern));
1466 }
1467
1468 #[test]
1469 fn test_empty_string() {
1470 let pattern = r"^.+$"; assert!(!matches_pattern("", pattern));
1472
1473 let empty_allowed = r"^.*$"; assert!(matches_pattern("", empty_allowed));
1475 }
1476
1477 #[test]
1478 fn test_special_characters() {
1479 let pattern = r"^[a-z]+$";
1480 assert!(!matches_pattern("hello<script>", pattern));
1481 assert!(!matches_pattern("test'; DROP TABLE users;--", pattern));
1482 }
1483
1484 #[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
1489 struct TestUser {
1490 name: String,
1491 age: i32,
1492 #[serde(default)]
1493 active: bool,
1494 }
1495
1496 #[test]
1497 fn test_model_validate_from_json() {
1498 let json = r#"{"name": "Alice", "age": 30}"#;
1499 let user: TestUser = TestUser::model_validate_json(json).unwrap();
1500 assert_eq!(user.name, "Alice");
1501 assert_eq!(user.age, 30);
1502 assert!(!user.active); }
1504
1505 #[test]
1506 fn test_model_validate_from_json_value() {
1507 let json_value = serde_json::json!({"name": "Bob", "age": 25, "active": true});
1508 let user: TestUser =
1509 TestUser::model_validate(json_value, ValidateOptions::default()).unwrap();
1510 assert_eq!(user.name, "Bob");
1511 assert_eq!(user.age, 25);
1512 assert!(user.active);
1513 }
1514
1515 #[test]
1516 fn test_model_validate_from_dict() {
1517 let mut dict = HashMap::new();
1518 dict.insert("name".to_string(), Value::Text("Charlie".to_string()));
1519 dict.insert("age".to_string(), Value::Int(35));
1520 dict.insert("active".to_string(), Value::Bool(true));
1521
1522 let user: TestUser = TestUser::model_validate_dict(dict).unwrap();
1523 assert_eq!(user.name, "Charlie");
1524 assert_eq!(user.age, 35);
1525 assert!(user.active);
1526 }
1527
1528 #[test]
1529 fn test_model_validate_invalid_json() {
1530 let json = r#"{"name": "Invalid"}"#; let result: ValidateResult<TestUser> = TestUser::model_validate_json(json);
1532 assert!(result.is_err());
1533 let err = result.unwrap_err();
1534 assert!(!err.is_empty());
1535 }
1536
1537 #[test]
1538 fn test_model_validate_malformed_json() {
1539 let json = r#"{"name": "Alice", age: 30}"#; let result: ValidateResult<TestUser> = TestUser::model_validate_json(json);
1541 assert!(result.is_err());
1542 let err = result.unwrap_err();
1543 assert!(
1544 err.errors
1545 .iter()
1546 .any(|e| e.message.contains("Invalid JSON"))
1547 );
1548 }
1549
1550 #[test]
1551 fn test_model_validate_with_update() {
1552 let json = r#"{"name": "Original", "age": 20}"#;
1553 let mut update = HashMap::new();
1554 update.insert("name".to_string(), serde_json::json!("Updated"));
1555
1556 let options = ValidateOptions::new().with_update(update);
1557 let user: TestUser = TestUser::model_validate(json, options).unwrap();
1558 assert_eq!(user.name, "Updated"); assert_eq!(user.age, 20);
1560 }
1561
1562 #[test]
1563 fn test_model_validate_strict_mode() {
1564 let json = r#"{"name": "Alice", "age": 30}"#;
1565 let options = ValidateOptions::new().strict();
1566 let user: TestUser = TestUser::model_validate(json, options).unwrap();
1567 assert_eq!(user.name, "Alice");
1568 assert_eq!(user.age, 30);
1569 }
1570
1571 #[test]
1572 fn test_validate_options_builder() {
1573 let mut context = HashMap::new();
1574 context.insert("key".to_string(), serde_json::json!("value"));
1575
1576 let options = ValidateOptions::new()
1577 .strict()
1578 .from_attributes()
1579 .with_context(context.clone());
1580
1581 assert!(options.strict);
1582 assert!(options.from_attributes);
1583 assert!(options.context.is_some());
1584 assert_eq!(
1585 options.context.unwrap().get("key"),
1586 Some(&serde_json::json!("value"))
1587 );
1588 }
1589
1590 #[test]
1591 fn test_validate_input_from_conversions() {
1592 let input: ValidateInput = "{}".to_string().into();
1594 assert!(matches!(input, ValidateInput::Json(_)));
1595
1596 let input: ValidateInput = "{}".into();
1598 assert!(matches!(input, ValidateInput::Json(_)));
1599
1600 let input: ValidateInput = serde_json::json!({}).into();
1602 assert!(matches!(input, ValidateInput::JsonValue(_)));
1603
1604 let map: HashMap<String, Value> = HashMap::new();
1606 let input: ValidateInput = map.into();
1607 assert!(matches!(input, ValidateInput::Dict(_)));
1608 }
1609
1610 #[test]
1611 fn test_value_to_json_conversions() {
1612 assert_eq!(value_to_json(Value::Null), serde_json::Value::Null);
1613 assert_eq!(value_to_json(Value::Bool(true)), serde_json::json!(true));
1614 assert_eq!(value_to_json(Value::Int(42)), serde_json::json!(42));
1615 assert_eq!(value_to_json(Value::BigInt(100)), serde_json::json!(100));
1616 assert_eq!(
1617 value_to_json(Value::Text("hello".to_string())),
1618 serde_json::json!("hello")
1619 );
1620 let uuid_bytes: [u8; 16] = [
1622 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
1623 0x00, 0x00,
1624 ];
1625 assert_eq!(
1626 value_to_json(Value::Uuid(uuid_bytes)),
1627 serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
1628 );
1629
1630 let arr = vec![Value::Int(1), Value::Int(2), Value::Int(3)];
1632 assert_eq!(
1633 value_to_json(Value::Array(arr)),
1634 serde_json::json!([1, 2, 3])
1635 );
1636 }
1637
1638 #[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
1643 struct TestProduct {
1644 name: String,
1645 price: f64,
1646 #[serde(skip_serializing_if = "Option::is_none")]
1647 description: Option<String>,
1648 }
1649
1650 #[test]
1651 fn test_model_dump_default() {
1652 let product = TestProduct {
1653 name: "Widget".to_string(),
1654 price: 19.99,
1655 description: Some("A useful widget".to_string()),
1656 };
1657 let json = product.model_dump(DumpOptions::default()).unwrap();
1658 assert_eq!(json["name"], "Widget");
1659 assert_eq!(json["price"], 19.99);
1660 assert_eq!(json["description"], "A useful widget");
1661 }
1662
1663 #[test]
1664 fn test_model_dump_json() {
1665 let product = TestProduct {
1666 name: "Gadget".to_string(),
1667 price: 29.99,
1668 description: None,
1669 };
1670 let json_str = product.model_dump_json().unwrap();
1671 assert!(json_str.contains("Gadget"));
1672 assert!(json_str.contains("29.99"));
1673 }
1674
1675 #[test]
1676 fn test_model_dump_json_pretty() {
1677 let product = TestProduct {
1678 name: "Gadget".to_string(),
1679 price: 29.99,
1680 description: None,
1681 };
1682 let json_str = product.model_dump_json_pretty().unwrap();
1683 assert!(json_str.contains('\n'));
1685 assert!(json_str.contains("Gadget"));
1686 }
1687
1688 #[test]
1689 fn test_model_dump_json_with_options_compact() {
1690 let product = TestProduct {
1691 name: "Widget".to_string(),
1692 price: 19.99,
1693 description: Some("A widget".to_string()),
1694 };
1695
1696 let json_str = product
1698 .model_dump_json_with_options(DumpOptions::default())
1699 .unwrap();
1700 assert!(!json_str.contains('\n')); assert!(json_str.contains("Widget"));
1702 assert!(json_str.contains("19.99"));
1703 }
1704
1705 #[test]
1706 fn test_model_dump_json_with_options_indent() {
1707 let product = TestProduct {
1708 name: "Widget".to_string(),
1709 price: 19.99,
1710 description: Some("A widget".to_string()),
1711 };
1712
1713 let json_str = product
1715 .model_dump_json_with_options(DumpOptions::default().indent(2))
1716 .unwrap();
1717 assert!(json_str.contains('\n')); assert!(json_str.contains(" \"name\"")); assert!(json_str.contains("Widget"));
1720
1721 let json_str = product
1723 .model_dump_json_with_options(DumpOptions::default().indent(4))
1724 .unwrap();
1725 assert!(json_str.contains(" \"name\"")); }
1727
1728 #[test]
1729 fn test_model_dump_json_with_options_combined() {
1730 let product = TestProduct {
1731 name: "Widget".to_string(),
1732 price: 19.99,
1733 description: Some("A widget".to_string()),
1734 };
1735
1736 let json_str = product
1738 .model_dump_json_with_options(DumpOptions::default().exclude(["price"]).indent(2))
1739 .unwrap();
1740 assert!(json_str.contains('\n')); assert!(json_str.contains("Widget"));
1742 assert!(!json_str.contains("19.99")); }
1744
1745 #[test]
1746 fn test_dump_options_indent_builder() {
1747 let options = DumpOptions::new().indent(4);
1748 assert_eq!(options.indent, Some(4));
1749
1750 let options2 = DumpOptions::new()
1752 .indent(2)
1753 .by_alias()
1754 .exclude(["password"]);
1755 assert_eq!(options2.indent, Some(2));
1756 assert!(options2.by_alias);
1757 assert!(options2.exclude.unwrap().contains("password"));
1758 }
1759
1760 #[test]
1761 fn test_model_dump_include() {
1762 let product = TestProduct {
1763 name: "Widget".to_string(),
1764 price: 19.99,
1765 description: Some("A widget".to_string()),
1766 };
1767 let options = DumpOptions::new().include(["name"]);
1768 let json = product.model_dump(options).unwrap();
1769 assert!(json.get("name").is_some());
1770 assert!(json.get("price").is_none());
1771 assert!(json.get("description").is_none());
1772 }
1773
1774 #[test]
1775 fn test_model_dump_exclude() {
1776 let product = TestProduct {
1777 name: "Widget".to_string(),
1778 price: 19.99,
1779 description: Some("A widget".to_string()),
1780 };
1781 let options = DumpOptions::new().exclude(["description"]);
1782 let json = product.model_dump(options).unwrap();
1783 assert!(json.get("name").is_some());
1784 assert!(json.get("price").is_some());
1785 assert!(json.get("description").is_none());
1786 }
1787
1788 #[test]
1789 fn test_model_dump_exclude_none() {
1790 let product = TestProduct {
1791 name: "Widget".to_string(),
1792 price: 19.99,
1793 description: None,
1794 };
1795 let options = DumpOptions::new().exclude_none();
1798 let json = product.model_dump(options).unwrap();
1799 assert!(json.get("name").is_some());
1800 }
1802
1803 #[test]
1804 fn test_dump_options_builder() {
1805 let options = DumpOptions::new()
1806 .json()
1807 .include(["name", "age"])
1808 .exclude(["password"])
1809 .by_alias()
1810 .exclude_none()
1811 .exclude_defaults()
1812 .round_trip();
1813
1814 assert_eq!(options.mode, DumpMode::Json);
1815 assert!(options.include.is_some());
1816 assert!(options.exclude.is_some());
1817 assert!(options.by_alias);
1818 assert!(options.exclude_none);
1819 assert!(options.exclude_defaults);
1820 assert!(options.round_trip);
1821 }
1822
1823 #[test]
1824 fn test_dump_mode_default() {
1825 assert_eq!(DumpMode::default(), DumpMode::Json);
1826 }
1827
1828 #[test]
1829 fn test_model_dump_include_exclude_combined() {
1830 let user = TestUser {
1831 name: "Alice".to_string(),
1832 age: 30,
1833 active: true,
1834 };
1835 let options = DumpOptions::new().include(["name", "age"]).exclude(["age"]);
1837 let json = user.model_dump(options).unwrap();
1838 assert!(json.get("name").is_some());
1840 assert!(json.get("age").is_none());
1841 assert!(json.get("active").is_none());
1842 }
1843
1844 #[test]
1845 fn test_model_dump_accepts_python_mode_and_round_trip() {
1846 let product = TestProduct {
1847 name: "Widget".to_string(),
1848 price: 19.99,
1849 description: Some("A useful widget".to_string()),
1850 };
1851 let json = product
1852 .model_dump(DumpOptions::default().python().round_trip())
1853 .unwrap();
1854
1855 assert_eq!(json["name"], "Widget");
1856 assert_eq!(json["price"], 19.99);
1857 assert_eq!(json["description"], "A useful widget");
1858 }
1859
1860 use crate::{FieldInfo, Row, SqlType};
1865
1866 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1868 struct TestAliasedUser {
1869 id: i64,
1870 name: String,
1871 email: String,
1872 }
1873
1874 impl Model for TestAliasedUser {
1875 const TABLE_NAME: &'static str = "users";
1876 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1877
1878 fn fields() -> &'static [FieldInfo] {
1879 static FIELDS: &[FieldInfo] = &[
1880 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
1881 FieldInfo::new("name", "name", SqlType::Text)
1882 .validation_alias("userName")
1883 .serialization_alias("displayName"),
1884 FieldInfo::new("email", "email", SqlType::Text).alias("emailAddress"), ];
1886 FIELDS
1887 }
1888
1889 fn to_row(&self) -> Vec<(&'static str, Value)> {
1890 vec![
1891 ("id", Value::BigInt(self.id)),
1892 ("name", Value::Text(self.name.clone())),
1893 ("email", Value::Text(self.email.clone())),
1894 ]
1895 }
1896
1897 fn from_row(row: &Row) -> crate::Result<Self> {
1898 Ok(Self {
1899 id: row.get_named("id")?,
1900 name: row.get_named("name")?,
1901 email: row.get_named("email")?,
1902 })
1903 }
1904
1905 fn primary_key_value(&self) -> Vec<Value> {
1906 vec![Value::BigInt(self.id)]
1907 }
1908
1909 fn is_new(&self) -> bool {
1910 false
1911 }
1912 }
1913
1914 #[test]
1915 fn test_apply_validation_aliases() {
1916 let fields = TestAliasedUser::fields();
1917
1918 let mut json = serde_json::json!({
1920 "id": 1,
1921 "userName": "Alice",
1922 "email": "alice@example.com"
1923 });
1924 apply_validation_aliases(&mut json, fields);
1925
1926 assert_eq!(json["name"], "Alice");
1928 assert!(json.get("userName").is_none());
1929
1930 let mut json2 = serde_json::json!({
1932 "id": 1,
1933 "name": "Bob",
1934 "emailAddress": "bob@example.com"
1935 });
1936 apply_validation_aliases(&mut json2, fields);
1937
1938 assert_eq!(json2["email"], "bob@example.com");
1940 assert!(json2.get("emailAddress").is_none());
1941 }
1942
1943 #[test]
1944 fn test_apply_serialization_aliases() {
1945 let fields = TestAliasedUser::fields();
1946
1947 let mut json = serde_json::json!({
1948 "id": 1,
1949 "name": "Alice",
1950 "email": "alice@example.com"
1951 });
1952 apply_serialization_aliases(&mut json, fields);
1953
1954 assert_eq!(json["displayName"], "Alice");
1956 assert!(json.get("name").is_none());
1957
1958 assert_eq!(json["emailAddress"], "alice@example.com");
1960 assert!(json.get("email").is_none());
1961 }
1962
1963 #[test]
1964 fn test_sql_model_validate_with_validation_alias() {
1965 let json = r#"{"id": 1, "userName": "Alice", "email": "alice@example.com"}"#;
1967 let user: TestAliasedUser = TestAliasedUser::sql_model_validate_json(json).unwrap();
1968
1969 assert_eq!(user.id, 1);
1970 assert_eq!(user.name, "Alice");
1971 assert_eq!(user.email, "alice@example.com");
1972 }
1973
1974 #[test]
1975 fn test_sql_model_validate_with_regular_alias() {
1976 let json = r#"{"id": 1, "name": "Bob", "emailAddress": "bob@example.com"}"#;
1978 let user: TestAliasedUser = TestAliasedUser::sql_model_validate_json(json).unwrap();
1979
1980 assert_eq!(user.id, 1);
1981 assert_eq!(user.name, "Bob");
1982 assert_eq!(user.email, "bob@example.com");
1983 }
1984
1985 #[test]
1986 fn test_sql_model_validate_with_field_name() {
1987 let json = r#"{"id": 1, "name": "Charlie", "email": "charlie@example.com"}"#;
1989 let user: TestAliasedUser = TestAliasedUser::sql_model_validate_json(json).unwrap();
1990
1991 assert_eq!(user.id, 1);
1992 assert_eq!(user.name, "Charlie");
1993 assert_eq!(user.email, "charlie@example.com");
1994 }
1995
1996 #[test]
1997 fn test_sql_model_dump_by_alias() {
1998 let user = TestAliasedUser {
1999 id: 1,
2000 name: "Alice".to_string(),
2001 email: "alice@example.com".to_string(),
2002 };
2003
2004 let json = user
2005 .sql_model_dump(DumpOptions::default().by_alias())
2006 .unwrap();
2007
2008 assert_eq!(json["displayName"], "Alice");
2010 assert!(json.get("name").is_none());
2011
2012 assert_eq!(json["emailAddress"], "alice@example.com");
2014 assert!(json.get("email").is_none());
2015 }
2016
2017 #[test]
2018 fn test_sql_model_dump_without_alias() {
2019 let user = TestAliasedUser {
2020 id: 1,
2021 name: "Alice".to_string(),
2022 email: "alice@example.com".to_string(),
2023 };
2024
2025 let json = user.sql_model_dump(DumpOptions::default()).unwrap();
2027
2028 assert_eq!(json["name"], "Alice");
2029 assert_eq!(json["email"], "alice@example.com");
2030 assert!(json.get("displayName").is_none());
2031 assert!(json.get("emailAddress").is_none());
2032 }
2033
2034 #[test]
2035 fn test_sql_model_dump_accepts_python_mode_and_round_trip() {
2036 let user = TestAliasedUser {
2037 id: 1,
2038 name: "Alice".to_string(),
2039 email: "alice@example.com".to_string(),
2040 };
2041 let json = user
2042 .sql_model_dump(DumpOptions::default().python().round_trip())
2043 .unwrap();
2044
2045 assert_eq!(json["name"], "Alice");
2046 assert_eq!(json["email"], "alice@example.com");
2047 }
2048
2049 #[test]
2050 fn test_tracked_model_dump_accepts_python_mode_and_round_trip() {
2051 let user = TestAliasedUser {
2052 id: 1,
2053 name: "Alice".to_string(),
2054 email: "alice@example.com".to_string(),
2055 };
2056 let tracked = crate::TrackedModel::all_fields_set(user);
2057 let json = tracked
2058 .sql_model_dump(DumpOptions::default().python().round_trip())
2059 .unwrap();
2060
2061 assert_eq!(json["name"], "Alice");
2062 assert_eq!(json["email"], "alice@example.com");
2063 }
2064
2065 #[test]
2066 fn test_alias_does_not_overwrite_existing() {
2067 let fields = TestAliasedUser::fields();
2068
2069 let mut json = serde_json::json!({
2071 "id": 1,
2072 "name": "FieldName",
2073 "userName": "AliasName",
2074 "email": "test@example.com"
2075 });
2076 apply_validation_aliases(&mut json, fields);
2077
2078 assert_eq!(json["name"], "FieldName");
2080 assert!(json.get("userName").is_none());
2082 }
2083
2084 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2090 struct TestUserWithComputed {
2091 id: i64,
2092 first_name: String,
2093 last_name: String,
2094 #[serde(default)]
2095 full_name: String, }
2097
2098 impl Model for TestUserWithComputed {
2099 const TABLE_NAME: &'static str = "users";
2100 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2101
2102 fn fields() -> &'static [FieldInfo] {
2103 static FIELDS: &[FieldInfo] = &[
2104 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2105 FieldInfo::new("first_name", "first_name", SqlType::Text),
2106 FieldInfo::new("last_name", "last_name", SqlType::Text),
2107 FieldInfo::new("full_name", "full_name", SqlType::Text).computed(true),
2108 ];
2109 FIELDS
2110 }
2111
2112 fn to_row(&self) -> Vec<(&'static str, Value)> {
2113 vec![
2115 ("id", Value::BigInt(self.id)),
2116 ("first_name", Value::Text(self.first_name.clone())),
2117 ("last_name", Value::Text(self.last_name.clone())),
2118 ]
2119 }
2120
2121 fn from_row(row: &Row) -> crate::Result<Self> {
2122 Ok(Self {
2123 id: row.get_named("id")?,
2124 first_name: row.get_named("first_name")?,
2125 last_name: row.get_named("last_name")?,
2126 full_name: String::new(),
2128 })
2129 }
2130
2131 fn primary_key_value(&self) -> Vec<Value> {
2132 vec![Value::BigInt(self.id)]
2133 }
2134
2135 fn is_new(&self) -> bool {
2136 false
2137 }
2138 }
2139
2140 #[test]
2141 fn test_computed_field_included_by_default() {
2142 let user = TestUserWithComputed {
2143 id: 1,
2144 first_name: "John".to_string(),
2145 last_name: "Doe".to_string(),
2146 full_name: "John Doe".to_string(),
2147 };
2148
2149 let json = user.sql_model_dump(DumpOptions::default()).unwrap();
2151
2152 assert_eq!(json["id"], 1);
2153 assert_eq!(json["first_name"], "John");
2154 assert_eq!(json["last_name"], "Doe");
2155 assert_eq!(json["full_name"], "John Doe"); }
2157
2158 #[test]
2159 fn test_computed_field_excluded_with_option() {
2160 let user = TestUserWithComputed {
2161 id: 1,
2162 first_name: "John".to_string(),
2163 last_name: "Doe".to_string(),
2164 full_name: "John Doe".to_string(),
2165 };
2166
2167 let json = user
2169 .sql_model_dump(DumpOptions::default().exclude_computed_fields())
2170 .unwrap();
2171
2172 assert_eq!(json["id"], 1);
2173 assert_eq!(json["first_name"], "John");
2174 assert_eq!(json["last_name"], "Doe");
2175 assert!(json.get("full_name").is_none()); }
2177
2178 #[test]
2179 fn test_computed_field_not_in_to_row() {
2180 let user = TestUserWithComputed {
2181 id: 1,
2182 first_name: "Jane".to_string(),
2183 last_name: "Smith".to_string(),
2184 full_name: "Jane Smith".to_string(),
2185 };
2186
2187 let row = user.to_row();
2189
2190 assert_eq!(row.len(), 3);
2192 let field_names: Vec<&str> = row.iter().map(|(name, _)| *name).collect();
2193 assert!(field_names.contains(&"id"));
2194 assert!(field_names.contains(&"first_name"));
2195 assert!(field_names.contains(&"last_name"));
2196 assert!(!field_names.contains(&"full_name")); }
2198
2199 #[test]
2200 fn test_computed_field_select_fields_excludes() {
2201 let fields = TestUserWithComputed::fields();
2202
2203 let computed: Vec<&FieldInfo> = fields.iter().filter(|f| f.computed).collect();
2205 assert_eq!(computed.len(), 1);
2206 assert_eq!(computed[0].name, "full_name");
2207
2208 let non_computed: Vec<&FieldInfo> = fields.iter().filter(|f| !f.computed).collect();
2210 assert_eq!(non_computed.len(), 3);
2211 }
2212
2213 #[test]
2214 fn test_computed_field_with_other_dump_options() {
2215 let user = TestUserWithComputed {
2216 id: 1,
2217 first_name: "John".to_string(),
2218 last_name: "Doe".to_string(),
2219 full_name: "John Doe".to_string(),
2220 };
2221
2222 let json = user
2224 .sql_model_dump(DumpOptions::default().exclude_computed_fields().include([
2225 "id",
2226 "first_name",
2227 "full_name",
2228 ]))
2229 .unwrap();
2230
2231 assert!(json.get("id").is_some());
2234 assert!(json.get("first_name").is_some());
2235 assert!(json.get("full_name").is_none()); assert!(json.get("last_name").is_none()); }
2238
2239 #[test]
2240 fn test_dump_options_exclude_computed_fields_builder() {
2241 let options = DumpOptions::new().exclude_computed_fields();
2242 assert!(options.exclude_computed_fields);
2243
2244 let options2 = DumpOptions::new()
2246 .exclude_computed_fields()
2247 .by_alias()
2248 .exclude_none();
2249 assert!(options2.exclude_computed_fields);
2250 assert!(options2.by_alias);
2251 assert!(options2.exclude_none);
2252 }
2253
2254 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2256 struct TestUserWithComputedAndAlias {
2257 id: i64,
2258 first_name: String,
2259 #[serde(default)]
2260 display_name: String, }
2262
2263 impl Model for TestUserWithComputedAndAlias {
2264 const TABLE_NAME: &'static str = "users";
2265 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2266
2267 fn fields() -> &'static [FieldInfo] {
2268 static FIELDS: &[FieldInfo] = &[
2269 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2270 FieldInfo::new("first_name", "first_name", SqlType::Text)
2271 .serialization_alias("firstName"),
2272 FieldInfo::new("display_name", "display_name", SqlType::Text)
2273 .computed(true)
2274 .serialization_alias("displayName"),
2275 ];
2276 FIELDS
2277 }
2278
2279 fn to_row(&self) -> Vec<(&'static str, Value)> {
2280 vec![
2281 ("id", Value::BigInt(self.id)),
2282 ("first_name", Value::Text(self.first_name.clone())),
2283 ]
2284 }
2285
2286 fn from_row(row: &Row) -> crate::Result<Self> {
2287 Ok(Self {
2288 id: row.get_named("id")?,
2289 first_name: row.get_named("first_name")?,
2290 display_name: String::new(),
2291 })
2292 }
2293
2294 fn primary_key_value(&self) -> Vec<Value> {
2295 vec![Value::BigInt(self.id)]
2296 }
2297
2298 fn is_new(&self) -> bool {
2299 false
2300 }
2301 }
2302
2303 #[test]
2304 fn test_exclude_computed_with_by_alias() {
2305 let user = TestUserWithComputedAndAlias {
2308 id: 1,
2309 first_name: "John".to_string(),
2310 display_name: "John Doe".to_string(),
2311 };
2312
2313 let json = user
2315 .sql_model_dump(DumpOptions::default().by_alias())
2316 .unwrap();
2317 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
2324 .sql_model_dump(DumpOptions::default().exclude_computed_fields())
2325 .unwrap();
2326 assert_eq!(json["first_name"], "John");
2327 assert!(json.get("display_name").is_none()); let json = user
2333 .sql_model_dump(DumpOptions::default().by_alias().exclude_computed_fields())
2334 .unwrap();
2335 assert_eq!(json["firstName"], "John"); assert!(json.get("displayName").is_none()); assert!(json.get("display_name").is_none()); }
2339
2340 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2346 struct TestModelWithDefaults {
2347 id: i64,
2348 name: String,
2349 count: i32, active: bool, score: f64, label: String, }
2354
2355 impl Model for TestModelWithDefaults {
2356 const TABLE_NAME: &'static str = "test_defaults";
2357 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2358
2359 fn fields() -> &'static [FieldInfo] {
2360 static FIELDS: &[FieldInfo] = &[
2361 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2362 FieldInfo::new("name", "name", SqlType::Text),
2363 FieldInfo::new("count", "count", SqlType::Integer).default_json("0"),
2364 FieldInfo::new("active", "active", SqlType::Boolean).default_json("false"),
2365 FieldInfo::new("score", "score", SqlType::Double).default_json("0.0"),
2366 FieldInfo::new("label", "label", SqlType::Text).default_json("\"default\""),
2367 ];
2368 FIELDS
2369 }
2370
2371 fn to_row(&self) -> Vec<(&'static str, Value)> {
2372 vec![
2373 ("id", Value::BigInt(self.id)),
2374 ("name", Value::Text(self.name.clone())),
2375 ("count", Value::Int(self.count)),
2376 ("active", Value::Bool(self.active)),
2377 ("score", Value::Double(self.score)),
2378 ("label", Value::Text(self.label.clone())),
2379 ]
2380 }
2381
2382 fn from_row(row: &Row) -> crate::Result<Self> {
2383 Ok(Self {
2384 id: row.get_named("id")?,
2385 name: row.get_named("name")?,
2386 count: row.get_named("count")?,
2387 active: row.get_named("active")?,
2388 score: row.get_named("score")?,
2389 label: row.get_named("label")?,
2390 })
2391 }
2392
2393 fn primary_key_value(&self) -> Vec<Value> {
2394 vec![Value::BigInt(self.id)]
2395 }
2396
2397 fn is_new(&self) -> bool {
2398 false
2399 }
2400 }
2401
2402 #[test]
2403 fn test_exclude_defaults_all_at_default() {
2404 let model = TestModelWithDefaults {
2405 id: 1,
2406 name: "Test".to_string(),
2407 count: 0, active: false, score: 0.0, label: "default".to_string(), };
2412
2413 let json = model
2414 .sql_model_dump(DumpOptions::default().exclude_defaults())
2415 .unwrap();
2416
2417 assert!(json.get("id").is_some());
2419 assert!(json.get("name").is_some());
2420
2421 assert!(json.get("count").is_none());
2423 assert!(json.get("active").is_none());
2424 assert!(json.get("score").is_none());
2425 assert!(json.get("label").is_none());
2426 }
2427
2428 #[test]
2429 fn test_exclude_defaults_none_at_default() {
2430 let model = TestModelWithDefaults {
2431 id: 1,
2432 name: "Test".to_string(),
2433 count: 42, active: true, score: 3.5, label: "custom".to_string(), };
2438
2439 let json = model
2440 .sql_model_dump(DumpOptions::default().exclude_defaults())
2441 .unwrap();
2442
2443 assert!(json.get("id").is_some());
2445 assert!(json.get("name").is_some());
2446 assert!(json.get("count").is_some());
2447 assert!(json.get("active").is_some());
2448 assert!(json.get("score").is_some());
2449 assert!(json.get("label").is_some());
2450
2451 assert_eq!(json["count"], 42);
2453 assert_eq!(json["active"], true);
2454 assert_eq!(json["score"], 3.5);
2455 assert_eq!(json["label"], "custom");
2456 }
2457
2458 #[test]
2459 fn test_exclude_defaults_mixed() {
2460 let model = TestModelWithDefaults {
2461 id: 1,
2462 name: "Test".to_string(),
2463 count: 0, active: true, score: 0.0, label: "custom".to_string(), };
2468
2469 let json = model
2470 .sql_model_dump(DumpOptions::default().exclude_defaults())
2471 .unwrap();
2472
2473 assert!(json.get("id").is_some());
2474 assert!(json.get("name").is_some());
2475
2476 assert!(json.get("count").is_none());
2478 assert!(json.get("score").is_none());
2479
2480 assert!(json.get("active").is_some());
2482 assert!(json.get("label").is_some());
2483 assert_eq!(json["active"], true);
2484 assert_eq!(json["label"], "custom");
2485 }
2486
2487 #[test]
2488 fn test_exclude_defaults_without_flag() {
2489 let model = TestModelWithDefaults {
2490 id: 1,
2491 name: "Test".to_string(),
2492 count: 0, active: false, score: 0.0, label: "default".to_string(), };
2497
2498 let json = model.sql_model_dump(DumpOptions::default()).unwrap();
2500
2501 assert!(json.get("id").is_some());
2502 assert!(json.get("name").is_some());
2503 assert!(json.get("count").is_some());
2504 assert!(json.get("active").is_some());
2505 assert!(json.get("score").is_some());
2506 assert!(json.get("label").is_some());
2507 }
2508
2509 #[test]
2510 fn test_exclude_defaults_with_by_alias() {
2511 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2515 struct TestAliasWithDefaults {
2516 id: i64,
2517 count: i32,
2518 }
2519
2520 impl Model for TestAliasWithDefaults {
2521 const TABLE_NAME: &'static str = "test";
2522 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2523
2524 fn fields() -> &'static [FieldInfo] {
2525 static FIELDS: &[FieldInfo] = &[
2526 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2527 FieldInfo::new("count", "count", SqlType::Integer)
2528 .default_json("0")
2529 .serialization_alias("itemCount"),
2530 ];
2531 FIELDS
2532 }
2533
2534 fn to_row(&self) -> Vec<(&'static str, Value)> {
2535 vec![
2536 ("id", Value::BigInt(self.id)),
2537 ("count", Value::Int(self.count)),
2538 ]
2539 }
2540
2541 fn from_row(row: &Row) -> crate::Result<Self> {
2542 Ok(Self {
2543 id: row.get_named("id")?,
2544 count: row.get_named("count")?,
2545 })
2546 }
2547
2548 fn primary_key_value(&self) -> Vec<Value> {
2549 vec![Value::BigInt(self.id)]
2550 }
2551
2552 fn is_new(&self) -> bool {
2553 false
2554 }
2555 }
2556
2557 let model_at_default = TestAliasWithDefaults { id: 1, count: 0 };
2559 let json = model_at_default
2560 .sql_model_dump(DumpOptions::default().exclude_defaults().by_alias())
2561 .unwrap();
2562
2563 assert!(json.get("count").is_none());
2565 assert!(json.get("itemCount").is_none());
2566
2567 let model_not_at_default = TestAliasWithDefaults { id: 1, count: 5 };
2569 let json = model_not_at_default
2570 .sql_model_dump(DumpOptions::default().exclude_defaults().by_alias())
2571 .unwrap();
2572
2573 assert!(json.get("count").is_none()); assert_eq!(json["itemCount"], 5); }
2577
2578 #[test]
2579 fn test_field_info_default_json() {
2580 let field1 = FieldInfo::new("count", "count", SqlType::Integer).default_json("0");
2582 assert_eq!(field1.default_json, Some("0"));
2583 assert!(field1.has_default);
2584
2585 let field2 =
2586 FieldInfo::new("name", "name", SqlType::Text).default_json_opt(Some("\"hello\""));
2587 assert_eq!(field2.default_json, Some("\"hello\""));
2588 assert!(field2.has_default);
2589
2590 let field3 = FieldInfo::new("name", "name", SqlType::Text).default_json_opt(None);
2591 assert_eq!(field3.default_json, None);
2592 assert!(!field3.has_default);
2593
2594 let field4 = FieldInfo::new("flag", "flag", SqlType::Boolean).has_default(true);
2595 assert!(field4.has_default);
2596 assert_eq!(field4.default_json, None); }
2598
2599 #[test]
2602 fn test_sqlmodel_update_from_dict() {
2603 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2604 struct TestUser {
2605 id: i64,
2606 name: String,
2607 age: i32,
2608 }
2609
2610 impl Model for TestUser {
2611 const TABLE_NAME: &'static str = "users";
2612 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2613
2614 fn fields() -> &'static [FieldInfo] {
2615 static FIELDS: &[FieldInfo] = &[
2616 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2617 FieldInfo::new("name", "name", SqlType::Text),
2618 FieldInfo::new("age", "age", SqlType::Integer),
2619 ];
2620 FIELDS
2621 }
2622
2623 fn to_row(&self) -> Vec<(&'static str, Value)> {
2624 vec![
2625 ("id", Value::BigInt(self.id)),
2626 ("name", Value::Text(self.name.clone())),
2627 ("age", Value::Int(self.age)),
2628 ]
2629 }
2630
2631 fn from_row(row: &Row) -> crate::Result<Self> {
2632 Ok(Self {
2633 id: row.get_named("id")?,
2634 name: row.get_named("name")?,
2635 age: row.get_named("age")?,
2636 })
2637 }
2638
2639 fn primary_key_value(&self) -> Vec<Value> {
2640 vec![Value::BigInt(self.id)]
2641 }
2642
2643 fn is_new(&self) -> bool {
2644 false
2645 }
2646 }
2647
2648 let mut user = TestUser {
2649 id: 1,
2650 name: "Alice".to_string(),
2651 age: 30,
2652 };
2653
2654 let update = HashMap::from([("name".to_string(), serde_json::json!("Bob"))]);
2656 user.sqlmodel_update(update, UpdateOptions::default())
2657 .unwrap();
2658
2659 assert_eq!(user.name, "Bob");
2660 assert_eq!(user.age, 30); }
2662
2663 #[test]
2664 fn test_sqlmodel_update_with_update_fields_filter() {
2665 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2666 struct TestUser {
2667 id: i64,
2668 name: String,
2669 age: i32,
2670 }
2671
2672 impl Model for TestUser {
2673 const TABLE_NAME: &'static str = "users";
2674 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2675
2676 fn fields() -> &'static [FieldInfo] {
2677 static FIELDS: &[FieldInfo] = &[
2678 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2679 FieldInfo::new("name", "name", SqlType::Text),
2680 FieldInfo::new("age", "age", SqlType::Integer),
2681 ];
2682 FIELDS
2683 }
2684
2685 fn to_row(&self) -> Vec<(&'static str, Value)> {
2686 vec![
2687 ("id", Value::BigInt(self.id)),
2688 ("name", Value::Text(self.name.clone())),
2689 ("age", Value::Int(self.age)),
2690 ]
2691 }
2692
2693 fn from_row(row: &Row) -> crate::Result<Self> {
2694 Ok(Self {
2695 id: row.get_named("id")?,
2696 name: row.get_named("name")?,
2697 age: row.get_named("age")?,
2698 })
2699 }
2700
2701 fn primary_key_value(&self) -> Vec<Value> {
2702 vec![Value::BigInt(self.id)]
2703 }
2704
2705 fn is_new(&self) -> bool {
2706 false
2707 }
2708 }
2709
2710 let mut user = TestUser {
2711 id: 1,
2712 name: "Alice".to_string(),
2713 age: 30,
2714 };
2715
2716 let update = HashMap::from([
2718 ("name".to_string(), serde_json::json!("Bob")),
2719 ("age".to_string(), serde_json::json!(25)),
2720 ]);
2721 user.sqlmodel_update(update, UpdateOptions::default().update_fields(["name"]))
2722 .unwrap();
2723
2724 assert_eq!(user.name, "Bob"); assert_eq!(user.age, 30); }
2727
2728 #[test]
2729 fn test_sqlmodel_update_invalid_field_error() {
2730 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2731 struct TestUser {
2732 id: i64,
2733 name: String,
2734 }
2735
2736 impl Model for TestUser {
2737 const TABLE_NAME: &'static str = "users";
2738 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2739
2740 fn fields() -> &'static [FieldInfo] {
2741 static FIELDS: &[FieldInfo] = &[
2742 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2743 FieldInfo::new("name", "name", SqlType::Text),
2744 ];
2745 FIELDS
2746 }
2747
2748 fn to_row(&self) -> Vec<(&'static str, Value)> {
2749 vec![
2750 ("id", Value::BigInt(self.id)),
2751 ("name", Value::Text(self.name.clone())),
2752 ]
2753 }
2754
2755 fn from_row(row: &Row) -> crate::Result<Self> {
2756 Ok(Self {
2757 id: row.get_named("id")?,
2758 name: row.get_named("name")?,
2759 })
2760 }
2761
2762 fn primary_key_value(&self) -> Vec<Value> {
2763 vec![Value::BigInt(self.id)]
2764 }
2765
2766 fn is_new(&self) -> bool {
2767 false
2768 }
2769 }
2770
2771 let mut user = TestUser {
2772 id: 1,
2773 name: "Alice".to_string(),
2774 };
2775
2776 let update = HashMap::from([("invalid_field".to_string(), serde_json::json!("value"))]);
2778 let result = user.sqlmodel_update(update, UpdateOptions::default());
2779
2780 assert!(result.is_err());
2781 let err = result.unwrap_err();
2782 assert!(err.errors.iter().any(|e| e.field == "invalid_field"));
2783 }
2784
2785 #[test]
2786 fn test_sqlmodel_update_from_model() {
2787 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2788 struct TestUser {
2789 id: i64,
2790 name: String,
2791 email: Option<String>,
2792 }
2793
2794 impl Model for TestUser {
2795 const TABLE_NAME: &'static str = "users";
2796 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2797
2798 fn fields() -> &'static [FieldInfo] {
2799 static FIELDS: &[FieldInfo] = &[
2800 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2801 FieldInfo::new("name", "name", SqlType::Text),
2802 FieldInfo::new("email", "email", SqlType::Text).nullable(true),
2803 ];
2804 FIELDS
2805 }
2806
2807 fn to_row(&self) -> Vec<(&'static str, Value)> {
2808 vec![
2809 ("id", Value::BigInt(self.id)),
2810 ("name", Value::Text(self.name.clone())),
2811 ("email", self.email.clone().map_or(Value::Null, Value::Text)),
2812 ]
2813 }
2814
2815 fn from_row(row: &Row) -> crate::Result<Self> {
2816 Ok(Self {
2817 id: row.get_named("id")?,
2818 name: row.get_named("name")?,
2819 email: row.get_named("email").ok(),
2820 })
2821 }
2822
2823 fn primary_key_value(&self) -> Vec<Value> {
2824 vec![Value::BigInt(self.id)]
2825 }
2826
2827 fn is_new(&self) -> bool {
2828 false
2829 }
2830 }
2831
2832 let mut user = TestUser {
2833 id: 1,
2834 name: "Alice".to_string(),
2835 email: Some("alice@example.com".to_string()),
2836 };
2837
2838 let patch = TestUser {
2840 id: 0, name: "Bob".to_string(),
2842 email: None, };
2844
2845 user.sqlmodel_update_from(&patch, UpdateOptions::default())
2846 .unwrap();
2847
2848 assert_eq!(user.name, "Bob"); assert_eq!(user.email, Some("alice@example.com".to_string())); }
2851
2852 #[test]
2853 fn test_sqlmodel_update_dict_convenience() {
2854 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2855 struct TestItem {
2856 id: i64,
2857 count: i32,
2858 }
2859
2860 impl Model for TestItem {
2861 const TABLE_NAME: &'static str = "items";
2862 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2863
2864 fn fields() -> &'static [FieldInfo] {
2865 static FIELDS: &[FieldInfo] = &[
2866 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2867 FieldInfo::new("count", "count", SqlType::Integer),
2868 ];
2869 FIELDS
2870 }
2871
2872 fn to_row(&self) -> Vec<(&'static str, Value)> {
2873 vec![
2874 ("id", Value::BigInt(self.id)),
2875 ("count", Value::Int(self.count)),
2876 ]
2877 }
2878
2879 fn from_row(row: &Row) -> crate::Result<Self> {
2880 Ok(Self {
2881 id: row.get_named("id")?,
2882 count: row.get_named("count")?,
2883 })
2884 }
2885
2886 fn primary_key_value(&self) -> Vec<Value> {
2887 vec![Value::BigInt(self.id)]
2888 }
2889
2890 fn is_new(&self) -> bool {
2891 false
2892 }
2893 }
2894
2895 let mut item = TestItem { id: 1, count: 10 };
2896
2897 item.sqlmodel_update_dict(HashMap::from([(
2899 "count".to_string(),
2900 serde_json::json!(20),
2901 )]))
2902 .unwrap();
2903
2904 assert_eq!(item.count, 20);
2905 }
2906
2907 #[test]
2912 fn test_credit_card_valid_visa() {
2913 assert!(is_valid_credit_card("4539578763621486"));
2915 }
2916
2917 #[test]
2918 fn test_credit_card_valid_mastercard() {
2919 assert!(is_valid_credit_card("5425233430109903"));
2921 }
2922
2923 #[test]
2924 fn test_credit_card_valid_amex() {
2925 assert!(is_valid_credit_card("374245455400126"));
2927 }
2928
2929 #[test]
2930 fn test_credit_card_with_spaces() {
2931 assert!(is_valid_credit_card("4539 5787 6362 1486"));
2933 }
2934
2935 #[test]
2936 fn test_credit_card_with_dashes() {
2937 assert!(is_valid_credit_card("4539-5787-6362-1486"));
2939 }
2940
2941 #[test]
2942 fn test_credit_card_invalid_luhn() {
2943 assert!(!is_valid_credit_card("1234567890123456"));
2945 }
2946
2947 #[test]
2948 fn test_credit_card_too_short() {
2949 assert!(!is_valid_credit_card("123456789012"));
2951 }
2952
2953 #[test]
2954 fn test_credit_card_too_long() {
2955 assert!(!is_valid_credit_card("12345678901234567890"));
2957 }
2958
2959 #[test]
2960 fn test_credit_card_empty() {
2961 assert!(!is_valid_credit_card(""));
2962 }
2963
2964 #[test]
2965 fn test_credit_card_non_numeric() {
2966 assert!(!is_valid_credit_card("453957876362abcd"));
2968 }
2969
2970 #[test]
2971 fn test_credit_card_all_zeros() {
2972 assert!(is_valid_credit_card("0000000000000000"));
2975 }
2976
2977 #[test]
2978 fn test_credit_card_valid_discover() {
2979 assert!(is_valid_credit_card("6011111111111117"));
2981 }
2982
2983 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2988 struct Address {
2989 street: String,
2990 city: String,
2991 #[serde(skip_serializing_if = "Option::is_none")]
2992 zip: Option<String>,
2993 }
2994
2995 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2996 struct Person {
2997 name: String,
2998 age: i32,
2999 address: Address,
3000 #[serde(skip_serializing_if = "Option::is_none")]
3001 spouse: Option<Box<Person>>,
3002 }
3003
3004 #[test]
3005 fn test_nested_model_dump_basic() {
3006 let person = Person {
3007 name: "Alice".to_string(),
3008 age: 30,
3009 address: Address {
3010 street: "123 Main St".to_string(),
3011 city: "Springfield".to_string(),
3012 zip: Some("12345".to_string()),
3013 },
3014 spouse: None,
3015 };
3016
3017 let json = person.model_dump(DumpOptions::default()).unwrap();
3018 assert_eq!(json["name"], "Alice");
3019 assert_eq!(json["age"], 30);
3020 assert_eq!(json["address"]["street"], "123 Main St");
3021 assert_eq!(json["address"]["city"], "Springfield");
3022 assert_eq!(json["address"]["zip"], "12345");
3023 }
3024
3025 #[test]
3026 fn test_nested_model_dump_exclude_top_level() {
3027 let person = Person {
3028 name: "Alice".to_string(),
3029 age: 30,
3030 address: Address {
3031 street: "123 Main St".to_string(),
3032 city: "Springfield".to_string(),
3033 zip: Some("12345".to_string()),
3034 },
3035 spouse: None,
3036 };
3037
3038 let json = person
3040 .model_dump(DumpOptions::default().exclude(["age"]))
3041 .unwrap();
3042 assert!(json.get("name").is_some());
3043 assert!(json.get("age").is_none());
3044 assert!(json.get("address").is_some()); assert_eq!(json["address"]["city"], "Springfield");
3047 }
3048
3049 #[test]
3050 fn test_nested_model_dump_exclude_nested_limitation() {
3051 let person = Person {
3055 name: "Alice".to_string(),
3056 age: 30,
3057 address: Address {
3058 street: "123 Main St".to_string(),
3059 city: "Springfield".to_string(),
3060 zip: Some("12345".to_string()),
3061 },
3062 spouse: None,
3063 };
3064
3065 let json = person
3067 .model_dump(DumpOptions::default().exclude(["address.zip"]))
3068 .unwrap();
3069 assert_eq!(json["address"]["zip"], "12345");
3071 }
3072
3073 #[test]
3074 fn test_deeply_nested_model_dump() {
3075 let person = Person {
3076 name: "Alice".to_string(),
3077 age: 30,
3078 address: Address {
3079 street: "123 Main St".to_string(),
3080 city: "Springfield".to_string(),
3081 zip: None,
3082 },
3083 spouse: Some(Box::new(Person {
3084 name: "Bob".to_string(),
3085 age: 32,
3086 address: Address {
3087 street: "456 Oak Ave".to_string(),
3088 city: "Springfield".to_string(),
3089 zip: Some("12346".to_string()),
3090 },
3091 spouse: None,
3092 })),
3093 };
3094
3095 let json = person.model_dump(DumpOptions::default()).unwrap();
3096 assert_eq!(json["name"], "Alice");
3097 assert_eq!(json["spouse"]["name"], "Bob");
3098 assert_eq!(json["spouse"]["address"]["street"], "456 Oak Ave");
3099 }
3100
3101 #[test]
3102 fn test_nested_model_exclude_none() {
3103 let person = Person {
3104 name: "Alice".to_string(),
3105 age: 30,
3106 address: Address {
3107 street: "123 Main St".to_string(),
3108 city: "Springfield".to_string(),
3109 zip: None, },
3111 spouse: None, };
3113
3114 let json = person
3115 .model_dump(DumpOptions::default().exclude_none())
3116 .unwrap();
3117 assert!(json.get("name").is_some());
3118 assert!(json.get("spouse").is_none());
3120 }
3123}