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,
432 pub exclude_defaults: bool,
434 pub exclude_none: bool,
436 pub exclude_computed_fields: bool,
438 pub round_trip: bool,
445 pub indent: Option<usize>,
447}
448
449impl DumpOptions {
450 pub fn new() -> Self {
452 Self::default()
453 }
454
455 pub fn json(mut self) -> Self {
457 self.mode = DumpMode::Json;
458 self
459 }
460
461 pub fn python(mut self) -> Self {
463 self.mode = DumpMode::Python;
464 self
465 }
466
467 pub fn include(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
469 self.include = Some(fields.into_iter().map(Into::into).collect());
470 self
471 }
472
473 pub fn exclude(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
475 self.exclude = Some(fields.into_iter().map(Into::into).collect());
476 self
477 }
478
479 pub fn by_alias(mut self) -> Self {
481 self.by_alias = true;
482 self
483 }
484
485 pub fn exclude_unset(mut self) -> Self {
487 self.exclude_unset = true;
488 self
489 }
490
491 pub fn exclude_defaults(mut self) -> Self {
493 self.exclude_defaults = true;
494 self
495 }
496
497 pub fn exclude_none(mut self) -> Self {
499 self.exclude_none = true;
500 self
501 }
502
503 pub fn exclude_computed_fields(mut self) -> Self {
505 self.exclude_computed_fields = true;
506 self
507 }
508
509 pub fn round_trip(mut self) -> Self {
511 self.round_trip = true;
512 self
513 }
514
515 pub fn indent(mut self, spaces: usize) -> Self {
520 self.indent = Some(spaces);
521 self
522 }
523}
524
525pub type DumpResult = std::result::Result<serde_json::Value, serde_json::Error>;
527
528pub trait ModelDump {
532 fn model_dump(&self, options: DumpOptions) -> DumpResult;
550
551 fn model_dump_json(&self) -> std::result::Result<String, serde_json::Error> {
553 let value = self.model_dump(DumpOptions::default())?;
554 serde_json::to_string(&value)
555 }
556
557 fn model_dump_json_pretty(&self) -> std::result::Result<String, serde_json::Error> {
559 let value = self.model_dump(DumpOptions::default())?;
560 serde_json::to_string_pretty(&value)
561 }
562
563 fn model_dump_json_with_options(
585 &self,
586 options: DumpOptions,
587 ) -> std::result::Result<String, serde_json::Error> {
588 let value = self.model_dump(DumpOptions {
589 indent: None, ..options.clone()
591 })?;
592
593 match options.indent {
594 Some(spaces) => {
595 let indent_bytes = " ".repeat(spaces).into_bytes();
596 let formatter = serde_json::ser::PrettyFormatter::with_indent(&indent_bytes);
597 let mut writer = Vec::new();
598 let mut ser = serde_json::Serializer::with_formatter(&mut writer, formatter);
599 serde::Serialize::serialize(&value, &mut ser)?;
600 String::from_utf8(writer).map_err(|e| {
602 serde_json::Error::io(std::io::Error::new(
603 std::io::ErrorKind::InvalidData,
604 format!("UTF-8 encoding error: {e}"),
605 ))
606 })
607 }
608 None => serde_json::to_string(&value),
609 }
610 }
611}
612
613impl<T: serde::Serialize> ModelDump for T {
615 fn model_dump(&self, options: DumpOptions) -> DumpResult {
616 let mut value = serde_json::to_value(self)?;
618
619 if let serde_json::Value::Object(ref mut map) = value {
621 if let Some(ref include) = options.include {
623 map.retain(|k, _| include.contains(k));
624 }
625
626 if let Some(ref exclude) = options.exclude {
628 map.retain(|k, _| !exclude.contains(k));
629 }
630
631 if options.exclude_none {
633 map.retain(|_, v| !v.is_null());
634 }
635
636 }
643
644 Ok(value)
645 }
646}
647
648fn value_to_json(value: Value) -> serde_json::Value {
650 match value {
651 Value::Null => serde_json::Value::Null,
652 Value::Bool(b) => serde_json::Value::Bool(b),
653 Value::TinyInt(i) => serde_json::Value::Number(i.into()),
654 Value::SmallInt(i) => serde_json::Value::Number(i.into()),
655 Value::Int(i) => serde_json::Value::Number(i.into()),
656 Value::BigInt(i) => serde_json::Value::Number(i.into()),
657 Value::Float(f) => serde_json::Number::from_f64(f64::from(f))
658 .map_or(serde_json::Value::Null, serde_json::Value::Number),
659 Value::Double(f) => serde_json::Number::from_f64(f)
660 .map_or(serde_json::Value::Null, serde_json::Value::Number),
661 Value::Decimal(s) => serde_json::Value::String(s),
662 Value::Text(s) => serde_json::Value::String(s),
663 Value::Bytes(b) => {
664 use std::fmt::Write;
666 let hex = b
667 .iter()
668 .fold(String::with_capacity(b.len() * 2), |mut acc, byte| {
669 let _ = write!(acc, "{byte:02x}");
670 acc
671 });
672 serde_json::Value::String(hex)
673 }
674 Value::Date(d) => serde_json::Value::Number(d.into()),
676 Value::Time(t) => serde_json::Value::Number(t.into()),
678 Value::Timestamp(ts) => serde_json::Value::Number(ts.into()),
680 Value::TimestampTz(ts) => serde_json::Value::Number(ts.into()),
682 Value::Uuid(u) => {
684 use std::fmt::Write;
685 let hex = u.iter().fold(String::with_capacity(32), |mut acc, b| {
686 let _ = write!(acc, "{b:02x}");
687 acc
688 });
689 let formatted = format!(
691 "{}-{}-{}-{}-{}",
692 &hex[0..8],
693 &hex[8..12],
694 &hex[12..16],
695 &hex[16..20],
696 &hex[20..32]
697 );
698 serde_json::Value::String(formatted)
699 }
700 Value::Json(j) => j,
701 Value::Array(arr) => serde_json::Value::Array(arr.into_iter().map(value_to_json).collect()),
702 Value::Default => serde_json::Value::Null,
703 }
704}
705
706use crate::Model;
711
712pub fn apply_validation_aliases(json: &mut serde_json::Value, fields: &[crate::FieldInfo]) {
722 if let serde_json::Value::Object(map) = json {
723 let mut alias_map: HashMap<&str, &str> = HashMap::new();
725 for field in fields {
726 if let Some(alias) = field.validation_alias {
728 alias_map.insert(alias, field.name);
729 }
730 if let Some(alias) = field.alias {
732 alias_map.entry(alias).or_insert(field.name);
733 }
734 }
735
736 let renames: Vec<(String, &str)> = map
738 .keys()
739 .filter_map(|k| alias_map.get(k.as_str()).map(|v| (k.clone(), *v)))
740 .collect();
741
742 for (old_key, new_key) in renames {
744 if let Some(value) = map.remove(&old_key) {
745 map.entry(new_key.to_string()).or_insert(value);
747 }
748 }
749 }
750}
751
752pub fn apply_serialization_aliases(json: &mut serde_json::Value, fields: &[crate::FieldInfo]) {
762 if let serde_json::Value::Object(map) = json {
763 let mut alias_map: HashMap<&str, &str> = HashMap::new();
765 for field in fields {
766 if let Some(alias) = field.serialization_alias {
768 alias_map.insert(field.name, alias);
769 } else if let Some(alias) = field.alias {
770 alias_map.insert(field.name, alias);
772 }
773 }
774
775 let renames: Vec<(String, &str)> = map
777 .keys()
778 .filter_map(|k| alias_map.get(k.as_str()).map(|v| (k.clone(), *v)))
779 .collect();
780
781 for (old_key, new_key) in renames {
783 if let Some(value) = map.remove(&old_key) {
784 map.insert(new_key.to_string(), value);
785 }
786 }
787 }
788}
789
790pub trait SqlModelValidate: Model + DeserializeOwned + Sized {
810 fn sql_model_validate(
812 input: impl Into<ValidateInput>,
813 options: ValidateOptions,
814 ) -> ValidateResult<Self> {
815 let input = input.into();
816
817 let mut json_value = match input {
819 ValidateInput::Dict(dict) => {
820 let map: serde_json::Map<String, serde_json::Value> = dict
821 .into_iter()
822 .map(|(k, v)| (k, value_to_json(v)))
823 .collect();
824 serde_json::Value::Object(map)
825 }
826 ValidateInput::Json(json_str) => serde_json::from_str(&json_str).map_err(|e| {
827 let mut err = ValidationError::new();
828 err.add(
829 "_json",
830 ValidationErrorKind::Custom,
831 format!("Invalid JSON: {e}"),
832 );
833 err
834 })?,
835 ValidateInput::JsonValue(value) => value,
836 };
837
838 apply_validation_aliases(&mut json_value, Self::fields());
840
841 if let Some(update) = options.update {
843 if let serde_json::Value::Object(ref mut map) = json_value {
844 for (key, value) in update {
845 map.insert(key, value);
846 }
847 }
848 }
849
850 serde_json::from_value(json_value).map_err(|e| {
852 let mut err = ValidationError::new();
853 err.add(
854 "_model",
855 ValidationErrorKind::Custom,
856 format!("Validation failed: {e}"),
857 );
858 err
859 })
860 }
861
862 fn sql_model_validate_json(json: &str) -> ValidateResult<Self> {
864 Self::sql_model_validate(json, ValidateOptions::default())
865 }
866
867 fn sql_model_validate_dict(dict: HashMap<String, Value>) -> ValidateResult<Self> {
869 Self::sql_model_validate(dict, ValidateOptions::default())
870 }
871}
872
873impl<T: Model + DeserializeOwned> SqlModelValidate for T {}
875
876pub trait SqlModelDump: Model + serde::Serialize {
902 fn sql_model_dump(&self, options: DumpOptions) -> DumpResult {
904 let mut value = serde_json::to_value(self)?;
906
907 if let serde_json::Value::Object(ref mut map) = value {
909 if options.exclude_computed_fields {
911 let computed_field_names: std::collections::HashSet<&str> = Self::fields()
912 .iter()
913 .filter(|f| f.computed)
914 .map(|f| f.name)
915 .collect();
916 map.retain(|k, _| !computed_field_names.contains(k.as_str()));
917 }
918
919 if options.exclude_defaults {
921 for field in Self::fields() {
922 if let Some(default_json) = field.default_json {
923 if let Some(current_value) = map.get(field.name) {
924 if let Ok(default_value) =
926 serde_json::from_str::<serde_json::Value>(default_json)
927 {
928 if current_value == &default_value {
929 map.remove(field.name);
930 }
931 }
932 }
933 }
934 }
935 }
936 }
937
938 if options.by_alias {
940 apply_serialization_aliases(&mut value, Self::fields());
941 }
942
943 if let serde_json::Value::Object(ref mut map) = value {
945 if let Some(ref include) = options.include {
947 map.retain(|k, _| include.contains(k));
948 }
949
950 if let Some(ref exclude) = options.exclude {
952 map.retain(|k, _| !exclude.contains(k));
953 }
954
955 if options.exclude_none {
957 map.retain(|_, v| !v.is_null());
958 }
959 }
960
961 Ok(value)
962 }
963
964 fn sql_model_dump_json(&self) -> std::result::Result<String, serde_json::Error> {
966 let value = self.sql_model_dump(DumpOptions::default())?;
967 serde_json::to_string(&value)
968 }
969
970 fn sql_model_dump_json_pretty(&self) -> std::result::Result<String, serde_json::Error> {
972 let value = self.sql_model_dump(DumpOptions::default())?;
973 serde_json::to_string_pretty(&value)
974 }
975
976 fn sql_model_dump_json_by_alias(&self) -> std::result::Result<String, serde_json::Error> {
978 let value = self.sql_model_dump(DumpOptions::default().by_alias())?;
979 serde_json::to_string(&value)
980 }
981
982 fn sql_model_dump_json_with_options(
1002 &self,
1003 options: DumpOptions,
1004 ) -> std::result::Result<String, serde_json::Error> {
1005 let value = self.sql_model_dump(DumpOptions {
1006 indent: None, ..options.clone()
1008 })?;
1009
1010 match options.indent {
1011 Some(spaces) => {
1012 let indent_bytes = " ".repeat(spaces).into_bytes();
1013 let formatter = serde_json::ser::PrettyFormatter::with_indent(&indent_bytes);
1014 let mut writer = Vec::new();
1015 let mut ser = serde_json::Serializer::with_formatter(&mut writer, formatter);
1016 serde::Serialize::serialize(&value, &mut ser)?;
1017 String::from_utf8(writer).map_err(|e| {
1019 serde_json::Error::io(std::io::Error::new(
1020 std::io::ErrorKind::InvalidData,
1021 format!("UTF-8 encoding error: {e}"),
1022 ))
1023 })
1024 }
1025 None => serde_json::to_string(&value),
1026 }
1027 }
1028}
1029
1030impl<T: Model + serde::Serialize> SqlModelDump for T {}
1032
1033#[derive(Debug, Clone)]
1041pub enum UpdateInput {
1042 Dict(HashMap<String, serde_json::Value>),
1044 JsonValue(serde_json::Value),
1046}
1047
1048impl From<HashMap<String, serde_json::Value>> for UpdateInput {
1049 fn from(map: HashMap<String, serde_json::Value>) -> Self {
1050 UpdateInput::Dict(map)
1051 }
1052}
1053
1054impl From<serde_json::Value> for UpdateInput {
1055 fn from(value: serde_json::Value) -> Self {
1056 UpdateInput::JsonValue(value)
1057 }
1058}
1059
1060impl From<HashMap<String, Value>> for UpdateInput {
1061 fn from(map: HashMap<String, Value>) -> Self {
1062 let json_map: HashMap<String, serde_json::Value> = map
1063 .into_iter()
1064 .map(|(k, v)| (k, value_to_json(v)))
1065 .collect();
1066 UpdateInput::Dict(json_map)
1067 }
1068}
1069
1070#[derive(Debug, Clone, Default)]
1072pub struct UpdateOptions {
1073 pub update_fields: Option<std::collections::HashSet<String>>,
1075}
1076
1077impl UpdateOptions {
1078 pub fn new() -> Self {
1080 Self::default()
1081 }
1082
1083 pub fn update_fields(mut self, fields: impl IntoIterator<Item = impl Into<String>>) -> Self {
1085 self.update_fields = Some(fields.into_iter().map(Into::into).collect());
1086 self
1087 }
1088}
1089
1090pub trait SqlModelUpdate: Model + serde::Serialize + DeserializeOwned {
1120 fn sqlmodel_update(
1136 &mut self,
1137 input: impl Into<UpdateInput>,
1138 options: UpdateOptions,
1139 ) -> ValidateResult<()> {
1140 let input = input.into();
1141
1142 let update_map = match input {
1144 UpdateInput::Dict(map) => map,
1145 UpdateInput::JsonValue(value) => {
1146 if let serde_json::Value::Object(map) = value {
1147 map.into_iter().collect()
1148 } else {
1149 let mut err = ValidationError::new();
1150 err.add(
1151 "_update",
1152 ValidationErrorKind::Custom,
1153 "Update input must be an object".to_string(),
1154 );
1155 return Err(err);
1156 }
1157 }
1158 };
1159
1160 let mut current = serde_json::to_value(&*self).map_err(|e| {
1162 let mut err = ValidationError::new();
1163 err.add(
1164 "_model",
1165 ValidationErrorKind::Custom,
1166 format!("Failed to serialize model: {e}"),
1167 );
1168 err
1169 })?;
1170
1171 let valid_fields: std::collections::HashSet<&str> =
1173 Self::fields().iter().map(|f| f.name).collect();
1174
1175 if let serde_json::Value::Object(ref mut current_map) = current {
1177 for (key, value) in update_map {
1178 if !valid_fields.contains(key.as_str()) {
1180 let mut err = ValidationError::new();
1181 err.add(
1182 &key,
1183 ValidationErrorKind::Custom,
1184 format!("Unknown field: {key}"),
1185 );
1186 return Err(err);
1187 }
1188
1189 if let Some(ref allowed) = options.update_fields {
1191 if !allowed.contains(&key) {
1192 continue; }
1194 }
1195
1196 current_map.insert(key, value);
1198 }
1199 }
1200
1201 let updated: Self = serde_json::from_value(current).map_err(|e| {
1203 let mut err = ValidationError::new();
1204 err.add(
1205 "_model",
1206 ValidationErrorKind::Custom,
1207 format!("Update failed validation: {e}"),
1208 );
1209 err
1210 })?;
1211
1212 *self = updated;
1214
1215 Ok(())
1216 }
1217
1218 fn sqlmodel_update_dict(
1220 &mut self,
1221 dict: HashMap<String, serde_json::Value>,
1222 ) -> ValidateResult<()> {
1223 self.sqlmodel_update(dict, UpdateOptions::default())
1224 }
1225
1226 fn sqlmodel_update_from(&mut self, source: &Self, options: UpdateOptions) -> ValidateResult<()>
1248 where
1249 Self: Sized,
1250 {
1251 let source_json = serde_json::to_value(source).map_err(|e| {
1253 let mut err = ValidationError::new();
1254 err.add(
1255 "_source",
1256 ValidationErrorKind::Custom,
1257 format!("Failed to serialize source model: {e}"),
1258 );
1259 err
1260 })?;
1261
1262 let update_map: HashMap<String, serde_json::Value> =
1264 if let serde_json::Value::Object(map) = source_json {
1265 map.into_iter().filter(|(_, v)| !v.is_null()).collect()
1266 } else {
1267 let mut err = ValidationError::new();
1268 err.add(
1269 "_source",
1270 ValidationErrorKind::Custom,
1271 "Source model must serialize to an object".to_string(),
1272 );
1273 return Err(err);
1274 };
1275
1276 self.sqlmodel_update(update_map, options)
1277 }
1278}
1279
1280impl<T: Model + serde::Serialize + DeserializeOwned> SqlModelUpdate for T {}
1282
1283#[cfg(test)]
1284mod tests {
1285 use super::*;
1286 use serde::{Deserialize, Serialize};
1287
1288 #[test]
1289 fn test_matches_email_pattern() {
1290 let email_pattern = r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$";
1291
1292 assert!(matches_pattern("test@example.com", email_pattern));
1293 assert!(matches_pattern("user.name+tag@domain.org", email_pattern));
1294 assert!(!matches_pattern("invalid", email_pattern));
1295 assert!(!matches_pattern("@example.com", email_pattern));
1296 assert!(!matches_pattern("test@", email_pattern));
1297 }
1298
1299 #[test]
1300 fn test_matches_url_pattern() {
1301 let url_pattern = r"^https?://[^\s/$.?#].[^\s]*$";
1302
1303 assert!(matches_pattern("https://example.com", url_pattern));
1304 assert!(matches_pattern("http://example.com/path", url_pattern));
1305 assert!(!matches_pattern("ftp://example.com", url_pattern));
1306 assert!(!matches_pattern("not a url", url_pattern));
1307 }
1308
1309 #[test]
1310 fn test_matches_phone_pattern() {
1311 let phone_pattern = r"^\+?[1-9]\d{1,14}$";
1312
1313 assert!(matches_pattern("+12025551234", phone_pattern));
1314 assert!(matches_pattern("12025551234", phone_pattern));
1315 assert!(!matches_pattern("0123456789", phone_pattern)); assert!(!matches_pattern("abc", phone_pattern));
1317 }
1318
1319 #[test]
1320 fn test_matches_uuid_pattern() {
1321 let uuid_pattern =
1322 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}$";
1323
1324 assert!(matches_pattern(
1325 "550e8400-e29b-41d4-a716-446655440000",
1326 uuid_pattern
1327 ));
1328 assert!(matches_pattern(
1329 "550E8400-E29B-41D4-A716-446655440000",
1330 uuid_pattern
1331 ));
1332 assert!(!matches_pattern("invalid-uuid", uuid_pattern));
1333 assert!(!matches_pattern(
1334 "550e8400e29b41d4a716446655440000",
1335 uuid_pattern
1336 ));
1337 }
1338
1339 #[test]
1340 fn test_matches_alphanumeric_pattern() {
1341 let alphanumeric_pattern = r"^[a-zA-Z0-9]+$";
1342
1343 assert!(matches_pattern("abc123", alphanumeric_pattern));
1344 assert!(matches_pattern("ABC", alphanumeric_pattern));
1345 assert!(matches_pattern("123", alphanumeric_pattern));
1346 assert!(!matches_pattern("abc-123", alphanumeric_pattern));
1347 assert!(!matches_pattern("hello world", alphanumeric_pattern));
1348 }
1349
1350 #[test]
1351 fn test_invalid_pattern_returns_false() {
1352 let invalid_pattern = r"[unclosed";
1354 assert!(!matches_pattern("anything", invalid_pattern));
1355 }
1356
1357 #[test]
1358 fn test_validate_pattern_valid() {
1359 assert!(validate_pattern(r"^[a-z]+$").is_none());
1360 assert!(validate_pattern(r"^\d{4}-\d{2}-\d{2}$").is_none());
1361 }
1362
1363 #[test]
1364 fn test_validate_pattern_invalid() {
1365 let result = validate_pattern(r"[unclosed");
1366 assert!(result.is_some());
1367 assert!(result.unwrap().contains("invalid regex pattern"));
1368 }
1369
1370 #[test]
1371 fn test_regex_caching() {
1372 let pattern = r"^test\d+$";
1373
1374 assert!(matches_pattern("test123", pattern));
1376
1377 assert!(matches_pattern("test456", pattern));
1379 assert!(!matches_pattern("invalid", pattern));
1380 }
1381
1382 #[test]
1383 fn test_empty_string() {
1384 let pattern = r"^.+$"; assert!(!matches_pattern("", pattern));
1386
1387 let empty_allowed = r"^.*$"; assert!(matches_pattern("", empty_allowed));
1389 }
1390
1391 #[test]
1392 fn test_special_characters() {
1393 let pattern = r"^[a-z]+$";
1394 assert!(!matches_pattern("hello<script>", pattern));
1395 assert!(!matches_pattern("test'; DROP TABLE users;--", pattern));
1396 }
1397
1398 #[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
1403 struct TestUser {
1404 name: String,
1405 age: i32,
1406 #[serde(default)]
1407 active: bool,
1408 }
1409
1410 #[test]
1411 fn test_model_validate_from_json() {
1412 let json = r#"{"name": "Alice", "age": 30}"#;
1413 let user: TestUser = TestUser::model_validate_json(json).unwrap();
1414 assert_eq!(user.name, "Alice");
1415 assert_eq!(user.age, 30);
1416 assert!(!user.active); }
1418
1419 #[test]
1420 fn test_model_validate_from_json_value() {
1421 let json_value = serde_json::json!({"name": "Bob", "age": 25, "active": true});
1422 let user: TestUser =
1423 TestUser::model_validate(json_value, ValidateOptions::default()).unwrap();
1424 assert_eq!(user.name, "Bob");
1425 assert_eq!(user.age, 25);
1426 assert!(user.active);
1427 }
1428
1429 #[test]
1430 fn test_model_validate_from_dict() {
1431 let mut dict = HashMap::new();
1432 dict.insert("name".to_string(), Value::Text("Charlie".to_string()));
1433 dict.insert("age".to_string(), Value::Int(35));
1434 dict.insert("active".to_string(), Value::Bool(true));
1435
1436 let user: TestUser = TestUser::model_validate_dict(dict).unwrap();
1437 assert_eq!(user.name, "Charlie");
1438 assert_eq!(user.age, 35);
1439 assert!(user.active);
1440 }
1441
1442 #[test]
1443 fn test_model_validate_invalid_json() {
1444 let json = r#"{"name": "Invalid"}"#; let result: ValidateResult<TestUser> = TestUser::model_validate_json(json);
1446 assert!(result.is_err());
1447 let err = result.unwrap_err();
1448 assert!(!err.is_empty());
1449 }
1450
1451 #[test]
1452 fn test_model_validate_malformed_json() {
1453 let json = r#"{"name": "Alice", age: 30}"#; let result: ValidateResult<TestUser> = TestUser::model_validate_json(json);
1455 assert!(result.is_err());
1456 let err = result.unwrap_err();
1457 assert!(
1458 err.errors
1459 .iter()
1460 .any(|e| e.message.contains("Invalid JSON"))
1461 );
1462 }
1463
1464 #[test]
1465 fn test_model_validate_with_update() {
1466 let json = r#"{"name": "Original", "age": 20}"#;
1467 let mut update = HashMap::new();
1468 update.insert("name".to_string(), serde_json::json!("Updated"));
1469
1470 let options = ValidateOptions::new().with_update(update);
1471 let user: TestUser = TestUser::model_validate(json, options).unwrap();
1472 assert_eq!(user.name, "Updated"); assert_eq!(user.age, 20);
1474 }
1475
1476 #[test]
1477 fn test_model_validate_strict_mode() {
1478 let json = r#"{"name": "Alice", "age": 30}"#;
1479 let options = ValidateOptions::new().strict();
1480 let user: TestUser = TestUser::model_validate(json, options).unwrap();
1481 assert_eq!(user.name, "Alice");
1482 assert_eq!(user.age, 30);
1483 }
1484
1485 #[test]
1486 fn test_validate_options_builder() {
1487 let mut context = HashMap::new();
1488 context.insert("key".to_string(), serde_json::json!("value"));
1489
1490 let options = ValidateOptions::new()
1491 .strict()
1492 .from_attributes()
1493 .with_context(context.clone());
1494
1495 assert!(options.strict);
1496 assert!(options.from_attributes);
1497 assert!(options.context.is_some());
1498 assert_eq!(
1499 options.context.unwrap().get("key"),
1500 Some(&serde_json::json!("value"))
1501 );
1502 }
1503
1504 #[test]
1505 fn test_validate_input_from_conversions() {
1506 let input: ValidateInput = "{}".to_string().into();
1508 assert!(matches!(input, ValidateInput::Json(_)));
1509
1510 let input: ValidateInput = "{}".into();
1512 assert!(matches!(input, ValidateInput::Json(_)));
1513
1514 let input: ValidateInput = serde_json::json!({}).into();
1516 assert!(matches!(input, ValidateInput::JsonValue(_)));
1517
1518 let map: HashMap<String, Value> = HashMap::new();
1520 let input: ValidateInput = map.into();
1521 assert!(matches!(input, ValidateInput::Dict(_)));
1522 }
1523
1524 #[test]
1525 fn test_value_to_json_conversions() {
1526 assert_eq!(value_to_json(Value::Null), serde_json::Value::Null);
1527 assert_eq!(value_to_json(Value::Bool(true)), serde_json::json!(true));
1528 assert_eq!(value_to_json(Value::Int(42)), serde_json::json!(42));
1529 assert_eq!(value_to_json(Value::BigInt(100)), serde_json::json!(100));
1530 assert_eq!(
1531 value_to_json(Value::Text("hello".to_string())),
1532 serde_json::json!("hello")
1533 );
1534 let uuid_bytes: [u8; 16] = [
1536 0x55, 0x0e, 0x84, 0x00, 0xe2, 0x9b, 0x41, 0xd4, 0xa7, 0x16, 0x44, 0x66, 0x55, 0x44,
1537 0x00, 0x00,
1538 ];
1539 assert_eq!(
1540 value_to_json(Value::Uuid(uuid_bytes)),
1541 serde_json::json!("550e8400-e29b-41d4-a716-446655440000")
1542 );
1543
1544 let arr = vec![Value::Int(1), Value::Int(2), Value::Int(3)];
1546 assert_eq!(
1547 value_to_json(Value::Array(arr)),
1548 serde_json::json!([1, 2, 3])
1549 );
1550 }
1551
1552 #[derive(Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
1557 struct TestProduct {
1558 name: String,
1559 price: f64,
1560 #[serde(skip_serializing_if = "Option::is_none")]
1561 description: Option<String>,
1562 }
1563
1564 #[test]
1565 fn test_model_dump_default() {
1566 let product = TestProduct {
1567 name: "Widget".to_string(),
1568 price: 19.99,
1569 description: Some("A useful widget".to_string()),
1570 };
1571 let json = product.model_dump(DumpOptions::default()).unwrap();
1572 assert_eq!(json["name"], "Widget");
1573 assert_eq!(json["price"], 19.99);
1574 assert_eq!(json["description"], "A useful widget");
1575 }
1576
1577 #[test]
1578 fn test_model_dump_json() {
1579 let product = TestProduct {
1580 name: "Gadget".to_string(),
1581 price: 29.99,
1582 description: None,
1583 };
1584 let json_str = product.model_dump_json().unwrap();
1585 assert!(json_str.contains("Gadget"));
1586 assert!(json_str.contains("29.99"));
1587 }
1588
1589 #[test]
1590 fn test_model_dump_json_pretty() {
1591 let product = TestProduct {
1592 name: "Gadget".to_string(),
1593 price: 29.99,
1594 description: None,
1595 };
1596 let json_str = product.model_dump_json_pretty().unwrap();
1597 assert!(json_str.contains('\n'));
1599 assert!(json_str.contains("Gadget"));
1600 }
1601
1602 #[test]
1603 fn test_model_dump_json_with_options_compact() {
1604 let product = TestProduct {
1605 name: "Widget".to_string(),
1606 price: 19.99,
1607 description: Some("A widget".to_string()),
1608 };
1609
1610 let json_str = product
1612 .model_dump_json_with_options(DumpOptions::default())
1613 .unwrap();
1614 assert!(!json_str.contains('\n')); assert!(json_str.contains("Widget"));
1616 assert!(json_str.contains("19.99"));
1617 }
1618
1619 #[test]
1620 fn test_model_dump_json_with_options_indent() {
1621 let product = TestProduct {
1622 name: "Widget".to_string(),
1623 price: 19.99,
1624 description: Some("A widget".to_string()),
1625 };
1626
1627 let json_str = product
1629 .model_dump_json_with_options(DumpOptions::default().indent(2))
1630 .unwrap();
1631 assert!(json_str.contains('\n')); assert!(json_str.contains(" \"name\"")); assert!(json_str.contains("Widget"));
1634
1635 let json_str = product
1637 .model_dump_json_with_options(DumpOptions::default().indent(4))
1638 .unwrap();
1639 assert!(json_str.contains(" \"name\"")); }
1641
1642 #[test]
1643 fn test_model_dump_json_with_options_combined() {
1644 let product = TestProduct {
1645 name: "Widget".to_string(),
1646 price: 19.99,
1647 description: Some("A widget".to_string()),
1648 };
1649
1650 let json_str = product
1652 .model_dump_json_with_options(DumpOptions::default().exclude(["price"]).indent(2))
1653 .unwrap();
1654 assert!(json_str.contains('\n')); assert!(json_str.contains("Widget"));
1656 assert!(!json_str.contains("19.99")); }
1658
1659 #[test]
1660 fn test_dump_options_indent_builder() {
1661 let options = DumpOptions::new().indent(4);
1662 assert_eq!(options.indent, Some(4));
1663
1664 let options2 = DumpOptions::new()
1666 .indent(2)
1667 .by_alias()
1668 .exclude(["password"]);
1669 assert_eq!(options2.indent, Some(2));
1670 assert!(options2.by_alias);
1671 assert!(options2.exclude.unwrap().contains("password"));
1672 }
1673
1674 #[test]
1675 fn test_model_dump_include() {
1676 let product = TestProduct {
1677 name: "Widget".to_string(),
1678 price: 19.99,
1679 description: Some("A widget".to_string()),
1680 };
1681 let options = DumpOptions::new().include(["name"]);
1682 let json = product.model_dump(options).unwrap();
1683 assert!(json.get("name").is_some());
1684 assert!(json.get("price").is_none());
1685 assert!(json.get("description").is_none());
1686 }
1687
1688 #[test]
1689 fn test_model_dump_exclude() {
1690 let product = TestProduct {
1691 name: "Widget".to_string(),
1692 price: 19.99,
1693 description: Some("A widget".to_string()),
1694 };
1695 let options = DumpOptions::new().exclude(["description"]);
1696 let json = product.model_dump(options).unwrap();
1697 assert!(json.get("name").is_some());
1698 assert!(json.get("price").is_some());
1699 assert!(json.get("description").is_none());
1700 }
1701
1702 #[test]
1703 fn test_model_dump_exclude_none() {
1704 let product = TestProduct {
1705 name: "Widget".to_string(),
1706 price: 19.99,
1707 description: None,
1708 };
1709 let options = DumpOptions::new().exclude_none();
1712 let json = product.model_dump(options).unwrap();
1713 assert!(json.get("name").is_some());
1714 }
1716
1717 #[test]
1718 fn test_dump_options_builder() {
1719 let options = DumpOptions::new()
1720 .json()
1721 .include(["name", "age"])
1722 .exclude(["password"])
1723 .by_alias()
1724 .exclude_none()
1725 .exclude_defaults()
1726 .round_trip();
1727
1728 assert_eq!(options.mode, DumpMode::Json);
1729 assert!(options.include.is_some());
1730 assert!(options.exclude.is_some());
1731 assert!(options.by_alias);
1732 assert!(options.exclude_none);
1733 assert!(options.exclude_defaults);
1734 assert!(options.round_trip);
1735 }
1736
1737 #[test]
1738 fn test_dump_mode_default() {
1739 assert_eq!(DumpMode::default(), DumpMode::Json);
1740 }
1741
1742 #[test]
1743 fn test_model_dump_include_exclude_combined() {
1744 let user = TestUser {
1745 name: "Alice".to_string(),
1746 age: 30,
1747 active: true,
1748 };
1749 let options = DumpOptions::new().include(["name", "age"]).exclude(["age"]);
1751 let json = user.model_dump(options).unwrap();
1752 assert!(json.get("name").is_some());
1754 assert!(json.get("age").is_none());
1755 assert!(json.get("active").is_none());
1756 }
1757
1758 use crate::{FieldInfo, Row, SqlType};
1763
1764 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1766 struct TestAliasedUser {
1767 id: i64,
1768 name: String,
1769 email: String,
1770 }
1771
1772 impl Model for TestAliasedUser {
1773 const TABLE_NAME: &'static str = "users";
1774 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1775
1776 fn fields() -> &'static [FieldInfo] {
1777 static FIELDS: &[FieldInfo] = &[
1778 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
1779 FieldInfo::new("name", "name", SqlType::Text)
1780 .validation_alias("userName")
1781 .serialization_alias("displayName"),
1782 FieldInfo::new("email", "email", SqlType::Text).alias("emailAddress"), ];
1784 FIELDS
1785 }
1786
1787 fn to_row(&self) -> Vec<(&'static str, Value)> {
1788 vec![
1789 ("id", Value::BigInt(self.id)),
1790 ("name", Value::Text(self.name.clone())),
1791 ("email", Value::Text(self.email.clone())),
1792 ]
1793 }
1794
1795 fn from_row(row: &Row) -> crate::Result<Self> {
1796 Ok(Self {
1797 id: row.get_named("id")?,
1798 name: row.get_named("name")?,
1799 email: row.get_named("email")?,
1800 })
1801 }
1802
1803 fn primary_key_value(&self) -> Vec<Value> {
1804 vec![Value::BigInt(self.id)]
1805 }
1806
1807 fn is_new(&self) -> bool {
1808 false
1809 }
1810 }
1811
1812 #[test]
1813 fn test_apply_validation_aliases() {
1814 let fields = TestAliasedUser::fields();
1815
1816 let mut json = serde_json::json!({
1818 "id": 1,
1819 "userName": "Alice",
1820 "email": "alice@example.com"
1821 });
1822 apply_validation_aliases(&mut json, fields);
1823
1824 assert_eq!(json["name"], "Alice");
1826 assert!(json.get("userName").is_none());
1827
1828 let mut json2 = serde_json::json!({
1830 "id": 1,
1831 "name": "Bob",
1832 "emailAddress": "bob@example.com"
1833 });
1834 apply_validation_aliases(&mut json2, fields);
1835
1836 assert_eq!(json2["email"], "bob@example.com");
1838 assert!(json2.get("emailAddress").is_none());
1839 }
1840
1841 #[test]
1842 fn test_apply_serialization_aliases() {
1843 let fields = TestAliasedUser::fields();
1844
1845 let mut json = serde_json::json!({
1846 "id": 1,
1847 "name": "Alice",
1848 "email": "alice@example.com"
1849 });
1850 apply_serialization_aliases(&mut json, fields);
1851
1852 assert_eq!(json["displayName"], "Alice");
1854 assert!(json.get("name").is_none());
1855
1856 assert_eq!(json["emailAddress"], "alice@example.com");
1858 assert!(json.get("email").is_none());
1859 }
1860
1861 #[test]
1862 fn test_sql_model_validate_with_validation_alias() {
1863 let json = r#"{"id": 1, "userName": "Alice", "email": "alice@example.com"}"#;
1865 let user: TestAliasedUser = TestAliasedUser::sql_model_validate_json(json).unwrap();
1866
1867 assert_eq!(user.id, 1);
1868 assert_eq!(user.name, "Alice");
1869 assert_eq!(user.email, "alice@example.com");
1870 }
1871
1872 #[test]
1873 fn test_sql_model_validate_with_regular_alias() {
1874 let json = r#"{"id": 1, "name": "Bob", "emailAddress": "bob@example.com"}"#;
1876 let user: TestAliasedUser = TestAliasedUser::sql_model_validate_json(json).unwrap();
1877
1878 assert_eq!(user.id, 1);
1879 assert_eq!(user.name, "Bob");
1880 assert_eq!(user.email, "bob@example.com");
1881 }
1882
1883 #[test]
1884 fn test_sql_model_validate_with_field_name() {
1885 let json = r#"{"id": 1, "name": "Charlie", "email": "charlie@example.com"}"#;
1887 let user: TestAliasedUser = TestAliasedUser::sql_model_validate_json(json).unwrap();
1888
1889 assert_eq!(user.id, 1);
1890 assert_eq!(user.name, "Charlie");
1891 assert_eq!(user.email, "charlie@example.com");
1892 }
1893
1894 #[test]
1895 fn test_sql_model_dump_by_alias() {
1896 let user = TestAliasedUser {
1897 id: 1,
1898 name: "Alice".to_string(),
1899 email: "alice@example.com".to_string(),
1900 };
1901
1902 let json = user
1903 .sql_model_dump(DumpOptions::default().by_alias())
1904 .unwrap();
1905
1906 assert_eq!(json["displayName"], "Alice");
1908 assert!(json.get("name").is_none());
1909
1910 assert_eq!(json["emailAddress"], "alice@example.com");
1912 assert!(json.get("email").is_none());
1913 }
1914
1915 #[test]
1916 fn test_sql_model_dump_without_alias() {
1917 let user = TestAliasedUser {
1918 id: 1,
1919 name: "Alice".to_string(),
1920 email: "alice@example.com".to_string(),
1921 };
1922
1923 let json = user.sql_model_dump(DumpOptions::default()).unwrap();
1925
1926 assert_eq!(json["name"], "Alice");
1927 assert_eq!(json["email"], "alice@example.com");
1928 assert!(json.get("displayName").is_none());
1929 assert!(json.get("emailAddress").is_none());
1930 }
1931
1932 #[test]
1933 fn test_alias_does_not_overwrite_existing() {
1934 let fields = TestAliasedUser::fields();
1935
1936 let mut json = serde_json::json!({
1938 "id": 1,
1939 "name": "FieldName",
1940 "userName": "AliasName",
1941 "email": "test@example.com"
1942 });
1943 apply_validation_aliases(&mut json, fields);
1944
1945 assert_eq!(json["name"], "FieldName");
1947 assert!(json.get("userName").is_none());
1949 }
1950
1951 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
1957 struct TestUserWithComputed {
1958 id: i64,
1959 first_name: String,
1960 last_name: String,
1961 #[serde(default)]
1962 full_name: String, }
1964
1965 impl Model for TestUserWithComputed {
1966 const TABLE_NAME: &'static str = "users";
1967 const PRIMARY_KEY: &'static [&'static str] = &["id"];
1968
1969 fn fields() -> &'static [FieldInfo] {
1970 static FIELDS: &[FieldInfo] = &[
1971 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
1972 FieldInfo::new("first_name", "first_name", SqlType::Text),
1973 FieldInfo::new("last_name", "last_name", SqlType::Text),
1974 FieldInfo::new("full_name", "full_name", SqlType::Text).computed(true),
1975 ];
1976 FIELDS
1977 }
1978
1979 fn to_row(&self) -> Vec<(&'static str, Value)> {
1980 vec![
1982 ("id", Value::BigInt(self.id)),
1983 ("first_name", Value::Text(self.first_name.clone())),
1984 ("last_name", Value::Text(self.last_name.clone())),
1985 ]
1986 }
1987
1988 fn from_row(row: &Row) -> crate::Result<Self> {
1989 Ok(Self {
1990 id: row.get_named("id")?,
1991 first_name: row.get_named("first_name")?,
1992 last_name: row.get_named("last_name")?,
1993 full_name: String::new(),
1995 })
1996 }
1997
1998 fn primary_key_value(&self) -> Vec<Value> {
1999 vec![Value::BigInt(self.id)]
2000 }
2001
2002 fn is_new(&self) -> bool {
2003 false
2004 }
2005 }
2006
2007 #[test]
2008 fn test_computed_field_included_by_default() {
2009 let user = TestUserWithComputed {
2010 id: 1,
2011 first_name: "John".to_string(),
2012 last_name: "Doe".to_string(),
2013 full_name: "John Doe".to_string(),
2014 };
2015
2016 let json = user.sql_model_dump(DumpOptions::default()).unwrap();
2018
2019 assert_eq!(json["id"], 1);
2020 assert_eq!(json["first_name"], "John");
2021 assert_eq!(json["last_name"], "Doe");
2022 assert_eq!(json["full_name"], "John Doe"); }
2024
2025 #[test]
2026 fn test_computed_field_excluded_with_option() {
2027 let user = TestUserWithComputed {
2028 id: 1,
2029 first_name: "John".to_string(),
2030 last_name: "Doe".to_string(),
2031 full_name: "John Doe".to_string(),
2032 };
2033
2034 let json = user
2036 .sql_model_dump(DumpOptions::default().exclude_computed_fields())
2037 .unwrap();
2038
2039 assert_eq!(json["id"], 1);
2040 assert_eq!(json["first_name"], "John");
2041 assert_eq!(json["last_name"], "Doe");
2042 assert!(json.get("full_name").is_none()); }
2044
2045 #[test]
2046 fn test_computed_field_not_in_to_row() {
2047 let user = TestUserWithComputed {
2048 id: 1,
2049 first_name: "Jane".to_string(),
2050 last_name: "Smith".to_string(),
2051 full_name: "Jane Smith".to_string(),
2052 };
2053
2054 let row = user.to_row();
2056
2057 assert_eq!(row.len(), 3);
2059 let field_names: Vec<&str> = row.iter().map(|(name, _)| *name).collect();
2060 assert!(field_names.contains(&"id"));
2061 assert!(field_names.contains(&"first_name"));
2062 assert!(field_names.contains(&"last_name"));
2063 assert!(!field_names.contains(&"full_name")); }
2065
2066 #[test]
2067 fn test_computed_field_select_fields_excludes() {
2068 let fields = TestUserWithComputed::fields();
2069
2070 let computed: Vec<&FieldInfo> = fields.iter().filter(|f| f.computed).collect();
2072 assert_eq!(computed.len(), 1);
2073 assert_eq!(computed[0].name, "full_name");
2074
2075 let non_computed: Vec<&FieldInfo> = fields.iter().filter(|f| !f.computed).collect();
2077 assert_eq!(non_computed.len(), 3);
2078 }
2079
2080 #[test]
2081 fn test_computed_field_with_other_dump_options() {
2082 let user = TestUserWithComputed {
2083 id: 1,
2084 first_name: "John".to_string(),
2085 last_name: "Doe".to_string(),
2086 full_name: "John Doe".to_string(),
2087 };
2088
2089 let json = user
2091 .sql_model_dump(DumpOptions::default().exclude_computed_fields().include([
2092 "id",
2093 "first_name",
2094 "full_name",
2095 ]))
2096 .unwrap();
2097
2098 assert!(json.get("id").is_some());
2101 assert!(json.get("first_name").is_some());
2102 assert!(json.get("full_name").is_none()); assert!(json.get("last_name").is_none()); }
2105
2106 #[test]
2107 fn test_dump_options_exclude_computed_fields_builder() {
2108 let options = DumpOptions::new().exclude_computed_fields();
2109 assert!(options.exclude_computed_fields);
2110
2111 let options2 = DumpOptions::new()
2113 .exclude_computed_fields()
2114 .by_alias()
2115 .exclude_none();
2116 assert!(options2.exclude_computed_fields);
2117 assert!(options2.by_alias);
2118 assert!(options2.exclude_none);
2119 }
2120
2121 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2123 struct TestUserWithComputedAndAlias {
2124 id: i64,
2125 first_name: String,
2126 #[serde(default)]
2127 display_name: String, }
2129
2130 impl Model for TestUserWithComputedAndAlias {
2131 const TABLE_NAME: &'static str = "users";
2132 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2133
2134 fn fields() -> &'static [FieldInfo] {
2135 static FIELDS: &[FieldInfo] = &[
2136 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2137 FieldInfo::new("first_name", "first_name", SqlType::Text)
2138 .serialization_alias("firstName"),
2139 FieldInfo::new("display_name", "display_name", SqlType::Text)
2140 .computed(true)
2141 .serialization_alias("displayName"),
2142 ];
2143 FIELDS
2144 }
2145
2146 fn to_row(&self) -> Vec<(&'static str, Value)> {
2147 vec![
2148 ("id", Value::BigInt(self.id)),
2149 ("first_name", Value::Text(self.first_name.clone())),
2150 ]
2151 }
2152
2153 fn from_row(row: &Row) -> crate::Result<Self> {
2154 Ok(Self {
2155 id: row.get_named("id")?,
2156 first_name: row.get_named("first_name")?,
2157 display_name: String::new(),
2158 })
2159 }
2160
2161 fn primary_key_value(&self) -> Vec<Value> {
2162 vec![Value::BigInt(self.id)]
2163 }
2164
2165 fn is_new(&self) -> bool {
2166 false
2167 }
2168 }
2169
2170 #[test]
2171 fn test_exclude_computed_with_by_alias() {
2172 let user = TestUserWithComputedAndAlias {
2175 id: 1,
2176 first_name: "John".to_string(),
2177 display_name: "John Doe".to_string(),
2178 };
2179
2180 let json = user
2182 .sql_model_dump(DumpOptions::default().by_alias())
2183 .unwrap();
2184 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
2191 .sql_model_dump(DumpOptions::default().exclude_computed_fields())
2192 .unwrap();
2193 assert_eq!(json["first_name"], "John");
2194 assert!(json.get("display_name").is_none()); let json = user
2200 .sql_model_dump(DumpOptions::default().by_alias().exclude_computed_fields())
2201 .unwrap();
2202 assert_eq!(json["firstName"], "John"); assert!(json.get("displayName").is_none()); assert!(json.get("display_name").is_none()); }
2206
2207 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2213 struct TestModelWithDefaults {
2214 id: i64,
2215 name: String,
2216 count: i32, active: bool, score: f64, label: String, }
2221
2222 impl Model for TestModelWithDefaults {
2223 const TABLE_NAME: &'static str = "test_defaults";
2224 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2225
2226 fn fields() -> &'static [FieldInfo] {
2227 static FIELDS: &[FieldInfo] = &[
2228 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2229 FieldInfo::new("name", "name", SqlType::Text),
2230 FieldInfo::new("count", "count", SqlType::Integer).default_json("0"),
2231 FieldInfo::new("active", "active", SqlType::Boolean).default_json("false"),
2232 FieldInfo::new("score", "score", SqlType::Double).default_json("0.0"),
2233 FieldInfo::new("label", "label", SqlType::Text).default_json("\"default\""),
2234 ];
2235 FIELDS
2236 }
2237
2238 fn to_row(&self) -> Vec<(&'static str, Value)> {
2239 vec![
2240 ("id", Value::BigInt(self.id)),
2241 ("name", Value::Text(self.name.clone())),
2242 ("count", Value::Int(self.count)),
2243 ("active", Value::Bool(self.active)),
2244 ("score", Value::Double(self.score)),
2245 ("label", Value::Text(self.label.clone())),
2246 ]
2247 }
2248
2249 fn from_row(row: &Row) -> crate::Result<Self> {
2250 Ok(Self {
2251 id: row.get_named("id")?,
2252 name: row.get_named("name")?,
2253 count: row.get_named("count")?,
2254 active: row.get_named("active")?,
2255 score: row.get_named("score")?,
2256 label: row.get_named("label")?,
2257 })
2258 }
2259
2260 fn primary_key_value(&self) -> Vec<Value> {
2261 vec![Value::BigInt(self.id)]
2262 }
2263
2264 fn is_new(&self) -> bool {
2265 false
2266 }
2267 }
2268
2269 #[test]
2270 fn test_exclude_defaults_all_at_default() {
2271 let model = TestModelWithDefaults {
2272 id: 1,
2273 name: "Test".to_string(),
2274 count: 0, active: false, score: 0.0, label: "default".to_string(), };
2279
2280 let json = model
2281 .sql_model_dump(DumpOptions::default().exclude_defaults())
2282 .unwrap();
2283
2284 assert!(json.get("id").is_some());
2286 assert!(json.get("name").is_some());
2287
2288 assert!(json.get("count").is_none());
2290 assert!(json.get("active").is_none());
2291 assert!(json.get("score").is_none());
2292 assert!(json.get("label").is_none());
2293 }
2294
2295 #[test]
2296 fn test_exclude_defaults_none_at_default() {
2297 let model = TestModelWithDefaults {
2298 id: 1,
2299 name: "Test".to_string(),
2300 count: 42, active: true, score: 3.5, label: "custom".to_string(), };
2305
2306 let json = model
2307 .sql_model_dump(DumpOptions::default().exclude_defaults())
2308 .unwrap();
2309
2310 assert!(json.get("id").is_some());
2312 assert!(json.get("name").is_some());
2313 assert!(json.get("count").is_some());
2314 assert!(json.get("active").is_some());
2315 assert!(json.get("score").is_some());
2316 assert!(json.get("label").is_some());
2317
2318 assert_eq!(json["count"], 42);
2320 assert_eq!(json["active"], true);
2321 assert_eq!(json["score"], 3.5);
2322 assert_eq!(json["label"], "custom");
2323 }
2324
2325 #[test]
2326 fn test_exclude_defaults_mixed() {
2327 let model = TestModelWithDefaults {
2328 id: 1,
2329 name: "Test".to_string(),
2330 count: 0, active: true, score: 0.0, label: "custom".to_string(), };
2335
2336 let json = model
2337 .sql_model_dump(DumpOptions::default().exclude_defaults())
2338 .unwrap();
2339
2340 assert!(json.get("id").is_some());
2341 assert!(json.get("name").is_some());
2342
2343 assert!(json.get("count").is_none());
2345 assert!(json.get("score").is_none());
2346
2347 assert!(json.get("active").is_some());
2349 assert!(json.get("label").is_some());
2350 assert_eq!(json["active"], true);
2351 assert_eq!(json["label"], "custom");
2352 }
2353
2354 #[test]
2355 fn test_exclude_defaults_without_flag() {
2356 let model = TestModelWithDefaults {
2357 id: 1,
2358 name: "Test".to_string(),
2359 count: 0, active: false, score: 0.0, label: "default".to_string(), };
2364
2365 let json = model.sql_model_dump(DumpOptions::default()).unwrap();
2367
2368 assert!(json.get("id").is_some());
2369 assert!(json.get("name").is_some());
2370 assert!(json.get("count").is_some());
2371 assert!(json.get("active").is_some());
2372 assert!(json.get("score").is_some());
2373 assert!(json.get("label").is_some());
2374 }
2375
2376 #[test]
2377 fn test_exclude_defaults_with_by_alias() {
2378 #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
2382 struct TestAliasWithDefaults {
2383 id: i64,
2384 count: i32,
2385 }
2386
2387 impl Model for TestAliasWithDefaults {
2388 const TABLE_NAME: &'static str = "test";
2389 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2390
2391 fn fields() -> &'static [FieldInfo] {
2392 static FIELDS: &[FieldInfo] = &[
2393 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2394 FieldInfo::new("count", "count", SqlType::Integer)
2395 .default_json("0")
2396 .serialization_alias("itemCount"),
2397 ];
2398 FIELDS
2399 }
2400
2401 fn to_row(&self) -> Vec<(&'static str, Value)> {
2402 vec![
2403 ("id", Value::BigInt(self.id)),
2404 ("count", Value::Int(self.count)),
2405 ]
2406 }
2407
2408 fn from_row(row: &Row) -> crate::Result<Self> {
2409 Ok(Self {
2410 id: row.get_named("id")?,
2411 count: row.get_named("count")?,
2412 })
2413 }
2414
2415 fn primary_key_value(&self) -> Vec<Value> {
2416 vec![Value::BigInt(self.id)]
2417 }
2418
2419 fn is_new(&self) -> bool {
2420 false
2421 }
2422 }
2423
2424 let model_at_default = TestAliasWithDefaults { id: 1, count: 0 };
2426 let json = model_at_default
2427 .sql_model_dump(DumpOptions::default().exclude_defaults().by_alias())
2428 .unwrap();
2429
2430 assert!(json.get("count").is_none());
2432 assert!(json.get("itemCount").is_none());
2433
2434 let model_not_at_default = TestAliasWithDefaults { id: 1, count: 5 };
2436 let json = model_not_at_default
2437 .sql_model_dump(DumpOptions::default().exclude_defaults().by_alias())
2438 .unwrap();
2439
2440 assert!(json.get("count").is_none()); assert_eq!(json["itemCount"], 5); }
2444
2445 #[test]
2446 fn test_field_info_default_json() {
2447 let field1 = FieldInfo::new("count", "count", SqlType::Integer).default_json("0");
2449 assert_eq!(field1.default_json, Some("0"));
2450 assert!(field1.has_default);
2451
2452 let field2 =
2453 FieldInfo::new("name", "name", SqlType::Text).default_json_opt(Some("\"hello\""));
2454 assert_eq!(field2.default_json, Some("\"hello\""));
2455 assert!(field2.has_default);
2456
2457 let field3 = FieldInfo::new("name", "name", SqlType::Text).default_json_opt(None);
2458 assert_eq!(field3.default_json, None);
2459 assert!(!field3.has_default);
2460
2461 let field4 = FieldInfo::new("flag", "flag", SqlType::Boolean).has_default(true);
2462 assert!(field4.has_default);
2463 assert_eq!(field4.default_json, None); }
2465
2466 #[test]
2469 fn test_sqlmodel_update_from_dict() {
2470 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2471 struct TestUser {
2472 id: i64,
2473 name: String,
2474 age: i32,
2475 }
2476
2477 impl Model for TestUser {
2478 const TABLE_NAME: &'static str = "users";
2479 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2480
2481 fn fields() -> &'static [FieldInfo] {
2482 static FIELDS: &[FieldInfo] = &[
2483 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2484 FieldInfo::new("name", "name", SqlType::Text),
2485 FieldInfo::new("age", "age", SqlType::Integer),
2486 ];
2487 FIELDS
2488 }
2489
2490 fn to_row(&self) -> Vec<(&'static str, Value)> {
2491 vec![
2492 ("id", Value::BigInt(self.id)),
2493 ("name", Value::Text(self.name.clone())),
2494 ("age", Value::Int(self.age)),
2495 ]
2496 }
2497
2498 fn from_row(row: &Row) -> crate::Result<Self> {
2499 Ok(Self {
2500 id: row.get_named("id")?,
2501 name: row.get_named("name")?,
2502 age: row.get_named("age")?,
2503 })
2504 }
2505
2506 fn primary_key_value(&self) -> Vec<Value> {
2507 vec![Value::BigInt(self.id)]
2508 }
2509
2510 fn is_new(&self) -> bool {
2511 false
2512 }
2513 }
2514
2515 let mut user = TestUser {
2516 id: 1,
2517 name: "Alice".to_string(),
2518 age: 30,
2519 };
2520
2521 let update = HashMap::from([("name".to_string(), serde_json::json!("Bob"))]);
2523 user.sqlmodel_update(update, UpdateOptions::default())
2524 .unwrap();
2525
2526 assert_eq!(user.name, "Bob");
2527 assert_eq!(user.age, 30); }
2529
2530 #[test]
2531 fn test_sqlmodel_update_with_update_fields_filter() {
2532 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2533 struct TestUser {
2534 id: i64,
2535 name: String,
2536 age: i32,
2537 }
2538
2539 impl Model for TestUser {
2540 const TABLE_NAME: &'static str = "users";
2541 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2542
2543 fn fields() -> &'static [FieldInfo] {
2544 static FIELDS: &[FieldInfo] = &[
2545 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2546 FieldInfo::new("name", "name", SqlType::Text),
2547 FieldInfo::new("age", "age", SqlType::Integer),
2548 ];
2549 FIELDS
2550 }
2551
2552 fn to_row(&self) -> Vec<(&'static str, Value)> {
2553 vec![
2554 ("id", Value::BigInt(self.id)),
2555 ("name", Value::Text(self.name.clone())),
2556 ("age", Value::Int(self.age)),
2557 ]
2558 }
2559
2560 fn from_row(row: &Row) -> crate::Result<Self> {
2561 Ok(Self {
2562 id: row.get_named("id")?,
2563 name: row.get_named("name")?,
2564 age: row.get_named("age")?,
2565 })
2566 }
2567
2568 fn primary_key_value(&self) -> Vec<Value> {
2569 vec![Value::BigInt(self.id)]
2570 }
2571
2572 fn is_new(&self) -> bool {
2573 false
2574 }
2575 }
2576
2577 let mut user = TestUser {
2578 id: 1,
2579 name: "Alice".to_string(),
2580 age: 30,
2581 };
2582
2583 let update = HashMap::from([
2585 ("name".to_string(), serde_json::json!("Bob")),
2586 ("age".to_string(), serde_json::json!(25)),
2587 ]);
2588 user.sqlmodel_update(update, UpdateOptions::default().update_fields(["name"]))
2589 .unwrap();
2590
2591 assert_eq!(user.name, "Bob"); assert_eq!(user.age, 30); }
2594
2595 #[test]
2596 fn test_sqlmodel_update_invalid_field_error() {
2597 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2598 struct TestUser {
2599 id: i64,
2600 name: String,
2601 }
2602
2603 impl Model for TestUser {
2604 const TABLE_NAME: &'static str = "users";
2605 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2606
2607 fn fields() -> &'static [FieldInfo] {
2608 static FIELDS: &[FieldInfo] = &[
2609 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2610 FieldInfo::new("name", "name", SqlType::Text),
2611 ];
2612 FIELDS
2613 }
2614
2615 fn to_row(&self) -> Vec<(&'static str, Value)> {
2616 vec![
2617 ("id", Value::BigInt(self.id)),
2618 ("name", Value::Text(self.name.clone())),
2619 ]
2620 }
2621
2622 fn from_row(row: &Row) -> crate::Result<Self> {
2623 Ok(Self {
2624 id: row.get_named("id")?,
2625 name: row.get_named("name")?,
2626 })
2627 }
2628
2629 fn primary_key_value(&self) -> Vec<Value> {
2630 vec![Value::BigInt(self.id)]
2631 }
2632
2633 fn is_new(&self) -> bool {
2634 false
2635 }
2636 }
2637
2638 let mut user = TestUser {
2639 id: 1,
2640 name: "Alice".to_string(),
2641 };
2642
2643 let update = HashMap::from([("invalid_field".to_string(), serde_json::json!("value"))]);
2645 let result = user.sqlmodel_update(update, UpdateOptions::default());
2646
2647 assert!(result.is_err());
2648 let err = result.unwrap_err();
2649 assert!(err.errors.iter().any(|e| e.field == "invalid_field"));
2650 }
2651
2652 #[test]
2653 fn test_sqlmodel_update_from_model() {
2654 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2655 struct TestUser {
2656 id: i64,
2657 name: String,
2658 email: Option<String>,
2659 }
2660
2661 impl Model for TestUser {
2662 const TABLE_NAME: &'static str = "users";
2663 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2664
2665 fn fields() -> &'static [FieldInfo] {
2666 static FIELDS: &[FieldInfo] = &[
2667 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2668 FieldInfo::new("name", "name", SqlType::Text),
2669 FieldInfo::new("email", "email", SqlType::Text).nullable(true),
2670 ];
2671 FIELDS
2672 }
2673
2674 fn to_row(&self) -> Vec<(&'static str, Value)> {
2675 vec![
2676 ("id", Value::BigInt(self.id)),
2677 ("name", Value::Text(self.name.clone())),
2678 ("email", self.email.clone().map_or(Value::Null, Value::Text)),
2679 ]
2680 }
2681
2682 fn from_row(row: &Row) -> crate::Result<Self> {
2683 Ok(Self {
2684 id: row.get_named("id")?,
2685 name: row.get_named("name")?,
2686 email: row.get_named("email").ok(),
2687 })
2688 }
2689
2690 fn primary_key_value(&self) -> Vec<Value> {
2691 vec![Value::BigInt(self.id)]
2692 }
2693
2694 fn is_new(&self) -> bool {
2695 false
2696 }
2697 }
2698
2699 let mut user = TestUser {
2700 id: 1,
2701 name: "Alice".to_string(),
2702 email: Some("alice@example.com".to_string()),
2703 };
2704
2705 let patch = TestUser {
2707 id: 0, name: "Bob".to_string(),
2709 email: None, };
2711
2712 user.sqlmodel_update_from(&patch, UpdateOptions::default())
2713 .unwrap();
2714
2715 assert_eq!(user.name, "Bob"); assert_eq!(user.email, Some("alice@example.com".to_string())); }
2718
2719 #[test]
2720 fn test_sqlmodel_update_dict_convenience() {
2721 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2722 struct TestItem {
2723 id: i64,
2724 count: i32,
2725 }
2726
2727 impl Model for TestItem {
2728 const TABLE_NAME: &'static str = "items";
2729 const PRIMARY_KEY: &'static [&'static str] = &["id"];
2730
2731 fn fields() -> &'static [FieldInfo] {
2732 static FIELDS: &[FieldInfo] = &[
2733 FieldInfo::new("id", "id", SqlType::BigInt).primary_key(true),
2734 FieldInfo::new("count", "count", SqlType::Integer),
2735 ];
2736 FIELDS
2737 }
2738
2739 fn to_row(&self) -> Vec<(&'static str, Value)> {
2740 vec![
2741 ("id", Value::BigInt(self.id)),
2742 ("count", Value::Int(self.count)),
2743 ]
2744 }
2745
2746 fn from_row(row: &Row) -> crate::Result<Self> {
2747 Ok(Self {
2748 id: row.get_named("id")?,
2749 count: row.get_named("count")?,
2750 })
2751 }
2752
2753 fn primary_key_value(&self) -> Vec<Value> {
2754 vec![Value::BigInt(self.id)]
2755 }
2756
2757 fn is_new(&self) -> bool {
2758 false
2759 }
2760 }
2761
2762 let mut item = TestItem { id: 1, count: 10 };
2763
2764 item.sqlmodel_update_dict(HashMap::from([(
2766 "count".to_string(),
2767 serde_json::json!(20),
2768 )]))
2769 .unwrap();
2770
2771 assert_eq!(item.count, 20);
2772 }
2773
2774 #[test]
2779 fn test_credit_card_valid_visa() {
2780 assert!(is_valid_credit_card("4539578763621486"));
2782 }
2783
2784 #[test]
2785 fn test_credit_card_valid_mastercard() {
2786 assert!(is_valid_credit_card("5425233430109903"));
2788 }
2789
2790 #[test]
2791 fn test_credit_card_valid_amex() {
2792 assert!(is_valid_credit_card("374245455400126"));
2794 }
2795
2796 #[test]
2797 fn test_credit_card_with_spaces() {
2798 assert!(is_valid_credit_card("4539 5787 6362 1486"));
2800 }
2801
2802 #[test]
2803 fn test_credit_card_with_dashes() {
2804 assert!(is_valid_credit_card("4539-5787-6362-1486"));
2806 }
2807
2808 #[test]
2809 fn test_credit_card_invalid_luhn() {
2810 assert!(!is_valid_credit_card("1234567890123456"));
2812 }
2813
2814 #[test]
2815 fn test_credit_card_too_short() {
2816 assert!(!is_valid_credit_card("123456789012"));
2818 }
2819
2820 #[test]
2821 fn test_credit_card_too_long() {
2822 assert!(!is_valid_credit_card("12345678901234567890"));
2824 }
2825
2826 #[test]
2827 fn test_credit_card_empty() {
2828 assert!(!is_valid_credit_card(""));
2829 }
2830
2831 #[test]
2832 fn test_credit_card_non_numeric() {
2833 assert!(!is_valid_credit_card("453957876362abcd"));
2835 }
2836
2837 #[test]
2838 fn test_credit_card_all_zeros() {
2839 assert!(is_valid_credit_card("0000000000000000"));
2842 }
2843
2844 #[test]
2845 fn test_credit_card_valid_discover() {
2846 assert!(is_valid_credit_card("6011111111111117"));
2848 }
2849
2850 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2855 struct Address {
2856 street: String,
2857 city: String,
2858 #[serde(skip_serializing_if = "Option::is_none")]
2859 zip: Option<String>,
2860 }
2861
2862 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2863 struct Person {
2864 name: String,
2865 age: i32,
2866 address: Address,
2867 #[serde(skip_serializing_if = "Option::is_none")]
2868 spouse: Option<Box<Person>>,
2869 }
2870
2871 #[test]
2872 fn test_nested_model_dump_basic() {
2873 let person = Person {
2874 name: "Alice".to_string(),
2875 age: 30,
2876 address: Address {
2877 street: "123 Main St".to_string(),
2878 city: "Springfield".to_string(),
2879 zip: Some("12345".to_string()),
2880 },
2881 spouse: None,
2882 };
2883
2884 let json = person.model_dump(DumpOptions::default()).unwrap();
2885 assert_eq!(json["name"], "Alice");
2886 assert_eq!(json["age"], 30);
2887 assert_eq!(json["address"]["street"], "123 Main St");
2888 assert_eq!(json["address"]["city"], "Springfield");
2889 assert_eq!(json["address"]["zip"], "12345");
2890 }
2891
2892 #[test]
2893 fn test_nested_model_dump_exclude_top_level() {
2894 let person = Person {
2895 name: "Alice".to_string(),
2896 age: 30,
2897 address: Address {
2898 street: "123 Main St".to_string(),
2899 city: "Springfield".to_string(),
2900 zip: Some("12345".to_string()),
2901 },
2902 spouse: None,
2903 };
2904
2905 let json = person
2907 .model_dump(DumpOptions::default().exclude(["age"]))
2908 .unwrap();
2909 assert!(json.get("name").is_some());
2910 assert!(json.get("age").is_none());
2911 assert!(json.get("address").is_some()); assert_eq!(json["address"]["city"], "Springfield");
2914 }
2915
2916 #[test]
2917 fn test_nested_model_dump_exclude_nested_limitation() {
2918 let person = Person {
2922 name: "Alice".to_string(),
2923 age: 30,
2924 address: Address {
2925 street: "123 Main St".to_string(),
2926 city: "Springfield".to_string(),
2927 zip: Some("12345".to_string()),
2928 },
2929 spouse: None,
2930 };
2931
2932 let json = person
2934 .model_dump(DumpOptions::default().exclude(["address.zip"]))
2935 .unwrap();
2936 assert_eq!(json["address"]["zip"], "12345");
2938 }
2939
2940 #[test]
2941 fn test_deeply_nested_model_dump() {
2942 let person = Person {
2943 name: "Alice".to_string(),
2944 age: 30,
2945 address: Address {
2946 street: "123 Main St".to_string(),
2947 city: "Springfield".to_string(),
2948 zip: None,
2949 },
2950 spouse: Some(Box::new(Person {
2951 name: "Bob".to_string(),
2952 age: 32,
2953 address: Address {
2954 street: "456 Oak Ave".to_string(),
2955 city: "Springfield".to_string(),
2956 zip: Some("12346".to_string()),
2957 },
2958 spouse: None,
2959 })),
2960 };
2961
2962 let json = person.model_dump(DumpOptions::default()).unwrap();
2963 assert_eq!(json["name"], "Alice");
2964 assert_eq!(json["spouse"]["name"], "Bob");
2965 assert_eq!(json["spouse"]["address"]["street"], "456 Oak Ave");
2966 }
2967
2968 #[test]
2969 fn test_nested_model_exclude_none() {
2970 let person = Person {
2971 name: "Alice".to_string(),
2972 age: 30,
2973 address: Address {
2974 street: "123 Main St".to_string(),
2975 city: "Springfield".to_string(),
2976 zip: None, },
2978 spouse: None, };
2980
2981 let json = person
2982 .model_dump(DumpOptions::default().exclude_none())
2983 .unwrap();
2984 assert!(json.get("name").is_some());
2985 assert!(json.get("spouse").is_none());
2987 }
2990}