1use std::{collections::BTreeSet, fmt, net};
2
3use crate::{IntoResponse, Response};
4
5#[derive(Debug, Clone, Copy)]
6pub struct Value<'value> {
7 pub name: &'value str,
8 pub bytes: &'value [u8],
9}
10
11pub trait Values {
12 fn len(&self) -> usize;
13
14 fn value(&self, index: usize) -> Option<Value<'_>>;
15
16 fn is_empty(&self) -> bool {
17 self.len() == 0
18 }
19
20 fn name_matches(&self, actual: &str, expected: &str) -> bool {
21 actual == expected
22 }
23
24 fn names_are_case_insensitive(&self) -> bool {
25 false
26 }
27
28 fn strip_name_prefix<'name>(&self, actual: &'name str, prefix: &str) -> Option<&'name str> {
29 actual.strip_prefix(prefix)
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum UnknownFields {
35 Reject,
36 Ignore,
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub struct DecodeOptions {
41 unknown_fields: UnknownFields,
42}
43
44impl DecodeOptions {
45 pub const fn new(unknown_fields: UnknownFields) -> Self {
46 Self { unknown_fields }
47 }
48
49 pub const fn reject_unknown() -> Self {
50 Self::new(UnknownFields::Reject)
51 }
52
53 pub const fn ignore_unknown() -> Self {
54 Self::new(UnknownFields::Ignore)
55 }
56
57 pub const fn unknown_fields(self) -> UnknownFields {
58 self.unknown_fields
59 }
60}
61
62impl Default for DecodeOptions {
63 fn default() -> Self {
64 Self::reject_unknown()
65 }
66}
67
68pub trait Schema: Sized {
69 const UNKNOWN_FIELDS: Option<UnknownFields> = None;
70
71 fn decode<V: Values>(values: &V, options: DecodeOptions) -> Result<Self, ValidationErrors>;
72
73 fn metadata() -> SchemaMetadata;
74}
75
76#[derive(Debug, Clone, PartialEq)]
77pub struct SchemaMetadata {
78 name: Option<String>,
79 kind: SchemaKind,
80 format: Option<String>,
81 discriminator: Option<String>,
82}
83
84impl SchemaMetadata {
85 pub fn new(kind: SchemaKind) -> Self {
86 Self {
87 name: None,
88 kind,
89 format: None,
90 discriminator: None,
91 }
92 }
93
94 pub fn named(name: impl Into<String>, kind: SchemaKind) -> Self {
95 Self {
96 name: Some(name.into()),
97 kind,
98 format: None,
99 discriminator: None,
100 }
101 }
102
103 pub fn array(items: Self) -> Self {
104 Self::new(SchemaKind::Array(Box::new(items)))
105 }
106
107 pub fn name(&self) -> Option<&str> {
108 self.name.as_deref()
109 }
110
111 pub fn format(mut self, format: impl Into<String>) -> Self {
112 self.format = Some(format.into());
113 self
114 }
115
116 pub fn discriminator(mut self, property: impl Into<String>) -> Self {
117 self.discriminator = Some(property.into());
118 self
119 }
120
121 pub fn kind(&self) -> &SchemaKind {
122 &self.kind
123 }
124
125 pub fn format_value(&self) -> Option<&str> {
126 self.format.as_deref()
127 }
128
129 pub fn discriminator_property(&self) -> Option<&str> {
130 self.discriminator.as_deref()
131 }
132}
133
134#[derive(Debug, Clone, PartialEq)]
135pub enum SchemaKind {
136 String,
137 Integer,
138 Number,
139 Boolean,
140 Bytes,
141 Object(Vec<SchemaField>),
142 Enum(Vec<String>),
143 Array(Box<SchemaMetadata>),
144 Literal(String),
145 OneOf(Vec<SchemaMetadata>),
146}
147
148#[derive(Debug, Clone, PartialEq)]
149pub struct SchemaField {
150 name: String,
151 schema: SchemaMetadata,
152 required: bool,
153 minimum: Option<String>,
154 maximum: Option<String>,
155 minimum_length: Option<usize>,
156 maximum_length: Option<usize>,
157}
158
159impl SchemaField {
160 pub fn new(name: impl Into<String>, schema: SchemaMetadata, required: bool) -> Self {
161 Self {
162 name: name.into(),
163 schema,
164 required,
165 minimum: None,
166 maximum: None,
167 minimum_length: None,
168 maximum_length: None,
169 }
170 }
171
172 pub fn minimum(mut self, minimum: impl ToString) -> Self {
173 self.minimum = Some(minimum.to_string());
174 self
175 }
176
177 pub fn maximum(mut self, maximum: impl ToString) -> Self {
178 self.maximum = Some(maximum.to_string());
179 self
180 }
181
182 pub fn minimum_length(mut self, minimum: usize) -> Self {
183 self.minimum_length = Some(minimum);
184 self
185 }
186
187 pub fn maximum_length(mut self, maximum: usize) -> Self {
188 self.maximum_length = Some(maximum);
189 self
190 }
191
192 pub fn name(&self) -> &str {
193 &self.name
194 }
195
196 pub fn schema(&self) -> &SchemaMetadata {
197 &self.schema
198 }
199
200 pub fn required(&self) -> bool {
201 self.required
202 }
203
204 pub fn minimum_value(&self) -> Option<&str> {
205 self.minimum.as_deref()
206 }
207
208 pub fn maximum_value(&self) -> Option<&str> {
209 self.maximum.as_deref()
210 }
211
212 pub fn minimum_length_value(&self) -> Option<usize> {
213 self.minimum_length
214 }
215
216 pub fn maximum_length_value(&self) -> Option<usize> {
217 self.maximum_length
218 }
219}
220
221#[derive(Debug, Default, Clone, PartialEq, Eq)]
222pub struct ExtraFields {
223 entries: Vec<(String, Vec<u8>)>,
224 case_insensitive: bool,
225}
226
227impl ExtraFields {
228 pub fn get(&self, name: &str) -> Option<&[u8]> {
229 self.entries
230 .iter()
231 .find(|(actual, _)| self.name_matches(actual, name))
232 .map(|(_, value)| value.as_slice())
233 }
234
235 pub fn get_all<'fields>(
236 &'fields self,
237 name: &'fields str,
238 ) -> impl Iterator<Item = &'fields [u8]> + 'fields {
239 self.entries
240 .iter()
241 .filter(move |(actual, _)| self.name_matches(actual, name))
242 .map(|(_, value)| value.as_slice())
243 }
244
245 pub fn iter(&self) -> impl Iterator<Item = (&str, &[u8])> {
246 self.entries
247 .iter()
248 .map(|(name, value)| (name.as_str(), value.as_slice()))
249 }
250
251 pub fn is_empty(&self) -> bool {
252 self.entries.is_empty()
253 }
254
255 pub fn len(&self) -> usize {
256 self.entries.len()
257 }
258
259 fn name_matches(&self, actual: &str, expected: &str) -> bool {
260 if self.case_insensitive {
261 actual.eq_ignore_ascii_case(expected)
262 } else {
263 actual == expected
264 }
265 }
266}
267
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum ValidationRule {
270 Missing,
271 UnknownField,
272 Multiple,
273 InvalidEncoding,
274 InvalidType,
275 Minimum,
276 Maximum,
277 MinimumLength,
278 MaximumLength,
279 Custom,
280}
281
282#[derive(Debug, Clone, PartialEq, Eq)]
283pub struct ValidationIssue {
284 field: Option<String>,
285 rule: ValidationRule,
286 message: String,
287}
288
289impl ValidationIssue {
290 pub fn custom(message: impl Into<String>) -> Self {
291 Self {
292 field: None,
293 rule: ValidationRule::Custom,
294 message: message.into(),
295 }
296 }
297
298 pub fn field(&self) -> Option<&str> {
299 self.field.as_deref()
300 }
301
302 pub fn rule(&self) -> ValidationRule {
303 self.rule
304 }
305
306 pub fn message(&self) -> &str {
307 &self.message
308 }
309
310 pub(crate) fn new(
311 field: Option<impl Into<String>>,
312 rule: ValidationRule,
313 message: impl Into<String>,
314 ) -> Self {
315 Self {
316 field: field.map(Into::into),
317 rule,
318 message: message.into(),
319 }
320 }
321
322 #[doc(hidden)]
323 pub fn attach_field(mut self, field: &str) -> Self {
324 if self.field.is_none() {
325 self.field = Some(field.to_owned());
326 }
327
328 self
329 }
330
331 #[doc(hidden)]
332 pub fn prefix_field(mut self, prefix: &str) -> Self {
333 self.field = Some(match self.field {
334 Some(field) => format!("{prefix}.{field}"),
335 None => prefix.to_owned(),
336 });
337 self
338 }
339}
340
341#[derive(Debug, Default, Clone, PartialEq, Eq)]
342pub struct ValidationErrors {
343 issues: Vec<ValidationIssue>,
344}
345
346impl ValidationErrors {
347 pub fn new() -> Self {
348 Self::default()
349 }
350
351 pub fn from_issue(issue: ValidationIssue) -> Self {
352 Self {
353 issues: vec![issue],
354 }
355 }
356
357 pub fn issues(&self) -> &[ValidationIssue] {
358 &self.issues
359 }
360
361 pub fn is_empty(&self) -> bool {
362 self.issues.is_empty()
363 }
364
365 pub fn len(&self) -> usize {
366 self.issues.len()
367 }
368
369 pub(crate) fn push(&mut self, issue: ValidationIssue) {
370 self.issues.push(issue);
371 }
372
373 fn extend_nested(&mut self, prefix: &str, errors: Self) {
374 self.issues.extend(
375 errors
376 .issues
377 .into_iter()
378 .map(|issue| issue.prefix_field(prefix)),
379 );
380 }
381}
382
383impl fmt::Display for ValidationErrors {
384 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
385 for (index, issue) in self.issues.iter().enumerate() {
386 if index > 0 {
387 formatter.write_str("; ")?;
388 }
389
390 if let Some(field) = issue.field() {
391 write!(formatter, "{field}: ")?;
392 }
393
394 formatter.write_str(issue.message())?;
395 }
396
397 Ok(())
398 }
399}
400
401impl IntoResponse for ValidationErrors {
402 fn into_response(self) -> Response {
403 Response::error(400, self.to_string())
404 }
405}
406
407pub trait ValueSchema: Sized {
408 fn decode_value(bytes: &[u8]) -> Result<Self, String>;
409
410 fn metadata() -> SchemaMetadata {
411 SchemaMetadata::new(SchemaKind::String)
412 }
413}
414
415impl ValueSchema for String {
416 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
417 String::from_utf8(bytes.to_vec()).map_err(|_| "must be valid UTF-8".to_owned())
418 }
419}
420
421impl ValueSchema for Vec<u8> {
422 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
423 Ok(bytes.to_vec())
424 }
425
426 fn metadata() -> SchemaMetadata {
427 SchemaMetadata::new(SchemaKind::Bytes)
428 }
429}
430
431macro_rules! value_schema {
432 ($kind:ident: $($type:ty),+ $(,)?) => {
433 $(
434 impl ValueSchema for $type {
435 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
436 let value = std::str::from_utf8(bytes)
437 .map_err(|_| "must be valid UTF-8".to_owned())?;
438
439 value
440 .parse::<Self>()
441 .map_err(|_| format!("must be a valid {}", stringify!($type)))
442 }
443
444 fn metadata() -> SchemaMetadata {
445 SchemaMetadata::new(SchemaKind::$kind)
446 }
447 }
448 )+
449 };
450}
451
452value_schema!(Boolean: bool);
453value_schema!(Integer: u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
454value_schema!(Number: f32, f64);
455
456impl ValueSchema for net::Ipv4Addr {
457 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
458 decode_from_str(bytes, "IPv4 address")
459 }
460
461 fn metadata() -> SchemaMetadata {
462 SchemaMetadata::new(SchemaKind::String).format("ipv4")
463 }
464}
465
466impl ValueSchema for net::Ipv6Addr {
467 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
468 decode_from_str(bytes, "IPv6 address")
469 }
470
471 fn metadata() -> SchemaMetadata {
472 SchemaMetadata::new(SchemaKind::String).format("ipv6")
473 }
474}
475
476impl ValueSchema for net::IpAddr {
477 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
478 decode_from_str(bytes, "IP address")
479 }
480
481 fn metadata() -> SchemaMetadata {
482 SchemaMetadata::new(SchemaKind::OneOf(vec![
483 <net::Ipv4Addr as ValueSchema>::metadata(),
484 <net::Ipv6Addr as ValueSchema>::metadata(),
485 ]))
486 }
487}
488
489fn decode_from_str<T: std::str::FromStr>(bytes: &[u8], expected: &str) -> Result<T, String> {
490 let value = std::str::from_utf8(bytes).map_err(|_| "must be valid UTF-8".to_owned())?;
491 value
492 .parse()
493 .map_err(|_| format!("must be a valid {expected}"))
494}
495
496impl<T: ValueSchema> Schema for T {
497 fn decode<V: Values>(values: &V, options: DecodeOptions) -> Result<Self, ValidationErrors> {
498 let mut decoder = Decoder::new(values, options);
499 let value = decoder.single::<T>();
500 let errors = decoder.finish();
501
502 match value {
503 Some(value) if errors.is_empty() => Ok(value),
504 _ => Err(errors),
505 }
506 }
507
508 fn metadata() -> SchemaMetadata {
509 T::metadata()
510 }
511}
512
513#[doc(hidden)]
514pub trait Length {
515 fn length(&self) -> usize;
516}
517
518impl Length for String {
519 fn length(&self) -> usize {
520 self.chars().count()
521 }
522}
523
524impl<T> Length for Vec<T> {
525 fn length(&self) -> usize {
526 self.len()
527 }
528}
529
530#[doc(hidden)]
531pub struct Decoder<'values, V: Values> {
532 values: &'values V,
533 options: DecodeOptions,
534 consumed: Vec<bool>,
535 errors: ValidationErrors,
536}
537
538impl<'values, V: Values> Decoder<'values, V> {
539 pub fn new(values: &'values V, options: DecodeOptions) -> Self {
540 Self {
541 values,
542 options,
543 consumed: vec![false; values.len()],
544 errors: ValidationErrors::new(),
545 }
546 }
547
548 pub fn required<T: ValueSchema>(&mut self, name: &str) -> Option<T> {
549 let indexes = self.indexes(name);
550
551 match indexes.as_slice() {
552 [] => {
553 self.issue(Some(name), ValidationRule::Missing, "is required");
554 None
555 }
556 [index] => self.decode_at::<T>(name, *index),
557 _ => {
558 self.issue(
559 Some(name),
560 ValidationRule::Multiple,
561 "must appear exactly once",
562 );
563 None
564 }
565 }
566 }
567
568 pub fn optional<T: ValueSchema>(&mut self, name: &str) -> Option<Option<T>> {
569 let indexes = self.indexes(name);
570
571 match indexes.as_slice() {
572 [] => Some(None),
573 [index] => self.decode_at::<T>(name, *index).map(Some),
574 _ => {
575 self.issue(
576 Some(name),
577 ValidationRule::Multiple,
578 "must appear at most once",
579 );
580 None
581 }
582 }
583 }
584
585 pub fn repeated<T: ValueSchema>(&mut self, name: &str) -> Option<Vec<T>> {
586 let indexes = self.indexes(name);
587 let mut decoded = Vec::with_capacity(indexes.len());
588 let mut valid = true;
589
590 for index in indexes {
591 match self.decode_at::<T>(name, index) {
592 Some(value) => decoded.push(value),
593 None => valid = false,
594 }
595 }
596
597 valid.then_some(decoded)
598 }
599
600 pub fn defaulted<T: ValueSchema, F: FnOnce() -> T>(
601 &mut self,
602 name: &str,
603 default: F,
604 ) -> Option<T> {
605 let indexes = self.indexes(name);
606
607 match indexes.as_slice() {
608 [] => Some(default()),
609 [index] => self.decode_at::<T>(name, *index),
610 _ => {
611 self.issue(
612 Some(name),
613 ValidationRule::Multiple,
614 "must appear exactly once",
615 );
616 None
617 }
618 }
619 }
620
621 pub fn required_nested<T: Schema>(&mut self, name: &str) -> Option<T> {
622 let options = nested_options::<T>(self.options);
623 let values = self.nested_values(name);
624
625 if values.is_empty() {
626 self.issue(Some(name), ValidationRule::Missing, "is required");
627 return None;
628 }
629
630 match T::decode(&values, options) {
631 Ok(value) => Some(value),
632 Err(errors) => {
633 self.errors.extend_nested(name, errors);
634 None
635 }
636 }
637 }
638
639 pub fn optional_nested<T: Schema>(&mut self, name: &str) -> Option<Option<T>> {
640 let options = nested_options::<T>(self.options);
641 let values = self.nested_values(name);
642
643 if values.is_empty() {
644 return Some(None);
645 }
646
647 match T::decode(&values, options) {
648 Ok(value) => Some(Some(value)),
649 Err(errors) => {
650 self.errors.extend_nested(name, errors);
651 None
652 }
653 }
654 }
655
656 pub fn defaulted_nested<T: Schema, F: FnOnce() -> T>(
657 &mut self,
658 name: &str,
659 default: F,
660 ) -> Option<T> {
661 let options = nested_options::<T>(self.options);
662 let values = self.nested_values(name);
663
664 if values.is_empty() {
665 return Some(default());
666 }
667
668 match T::decode(&values, options) {
669 Ok(value) => Some(value),
670 Err(errors) => {
671 self.errors.extend_nested(name, errors);
672 None
673 }
674 }
675 }
676
677 pub fn repeated_nested<T: Schema>(&mut self, name: &str) -> Option<Vec<T>> {
678 let prefix = format!("{name}.");
679 let mut indexes = BTreeSet::new();
680 let mut valid = true;
681
682 for index in 0..self.values.len() {
683 let Some(value) = self.values.value(index) else {
684 continue;
685 };
686 let Some(remainder) = self.values.strip_name_prefix(value.name, &prefix) else {
687 continue;
688 };
689 let Some((item, field)) = remainder.split_once('.') else {
690 self.consumed[index] = true;
691 self.issue(
692 Some(value.name),
693 ValidationRule::InvalidType,
694 "must use `<field>.<index>.<nested-field>`",
695 );
696 valid = false;
697 continue;
698 };
699
700 if field.is_empty() {
701 self.consumed[index] = true;
702 self.issue(
703 Some(value.name),
704 ValidationRule::InvalidType,
705 "nested field name cannot be empty",
706 );
707 valid = false;
708 continue;
709 }
710
711 match item.parse::<usize>() {
712 Ok(item) => {
713 indexes.insert(item);
714 }
715 Err(_) => {
716 self.consumed[index] = true;
717 self.issue(
718 Some(value.name),
719 ValidationRule::InvalidType,
720 "nested item index must be a non-negative integer",
721 );
722 valid = false;
723 }
724 }
725 }
726
727 let mut decoded = Vec::with_capacity(indexes.len());
728 for index in indexes {
729 let item_name = format!("{name}.{index}");
730 let options = nested_options::<T>(self.options);
731 let values = self.nested_values(&item_name);
732
733 match T::decode(&values, options) {
734 Ok(value) => decoded.push(value),
735 Err(errors) => {
736 self.errors.extend_nested(&item_name, errors);
737 valid = false;
738 }
739 }
740 }
741
742 valid.then_some(decoded)
743 }
744
745 pub fn minimum<T: PartialOrd>(&mut self, name: &str, value: &T, minimum: T) {
746 if value < &minimum {
747 self.issue(Some(name), ValidationRule::Minimum, "is below the minimum");
748 }
749 }
750
751 pub fn maximum<T: PartialOrd>(&mut self, name: &str, value: &T, maximum: T) {
752 if value > &maximum {
753 self.issue(Some(name), ValidationRule::Maximum, "is above the maximum");
754 }
755 }
756
757 pub fn minimum_length<T: Length>(&mut self, name: &str, value: &T, minimum: usize) {
758 if value.length() < minimum {
759 self.issue(
760 Some(name),
761 ValidationRule::MinimumLength,
762 "is shorter than the minimum length",
763 );
764 }
765 }
766
767 pub fn maximum_length<T: Length>(&mut self, name: &str, value: &T, maximum: usize) {
768 if value.length() > maximum {
769 self.issue(
770 Some(name),
771 ValidationRule::MaximumLength,
772 "is longer than the maximum length",
773 );
774 }
775 }
776
777 pub fn custom(&mut self, name: &str, result: Result<(), ValidationIssue>) {
778 if let Err(issue) = result {
779 self.errors.push(issue.attach_field(name));
780 }
781 }
782
783 pub fn rest(&mut self) -> ExtraFields {
784 let mut entries = Vec::new();
785
786 for index in 0..self.values.len() {
787 if self.consumed[index] {
788 continue;
789 }
790
791 let Some(value) = self.values.value(index) else {
792 continue;
793 };
794
795 entries.push((value.name.to_owned(), value.bytes.to_vec()));
796 self.consumed[index] = true;
797 }
798
799 ExtraFields {
800 entries,
801 case_insensitive: self.values.names_are_case_insensitive(),
802 }
803 }
804
805 pub fn finish(mut self) -> ValidationErrors {
806 if self.options.unknown_fields == UnknownFields::Reject {
807 let mut unknown = Vec::<String>::new();
808
809 for index in 0..self.values.len() {
810 if self.consumed[index] {
811 continue;
812 }
813
814 let Some(value) = self.values.value(index) else {
815 continue;
816 };
817
818 if unknown
819 .iter()
820 .any(|name| self.values.name_matches(name, value.name))
821 {
822 continue;
823 }
824
825 unknown.push(value.name.to_owned());
826 self.issue(
827 Some(value.name),
828 ValidationRule::UnknownField,
829 "is not declared by the schema",
830 );
831 }
832 }
833
834 self.errors
835 }
836
837 fn single<T: ValueSchema>(&mut self) -> Option<T> {
838 for consumed in &mut self.consumed {
839 *consumed = true;
840 }
841
842 match self.values.len() {
843 0 => {
844 self.issue(None::<&str>, ValidationRule::Missing, "a value is required");
845 None
846 }
847 1 => self.decode_at::<T>("value", 0),
848 _ => {
849 self.issue(
850 None::<&str>,
851 ValidationRule::Multiple,
852 "exactly one value is required",
853 );
854 None
855 }
856 }
857 }
858
859 fn indexes(&mut self, name: &str) -> Vec<usize> {
860 let indexes = (0..self.values.len())
861 .filter(|index| {
862 self.values
863 .value(*index)
864 .is_some_and(|value| self.values.name_matches(value.name, name))
865 })
866 .collect::<Vec<_>>();
867
868 for index in &indexes {
869 self.consumed[*index] = true;
870 }
871
872 indexes
873 }
874
875 fn nested_values(&mut self, name: &str) -> NestedValues<'_> {
876 let prefix = format!("{name}.");
877 let mut values = Vec::new();
878
879 for index in 0..self.values.len() {
880 let Some(value) = self.values.value(index) else {
881 continue;
882 };
883 let Some(name) = self.values.strip_name_prefix(value.name, &prefix) else {
884 continue;
885 };
886
887 self.consumed[index] = true;
888 values.push(Value {
889 name,
890 bytes: value.bytes,
891 });
892 }
893
894 NestedValues {
895 values,
896 case_insensitive: self.values.names_are_case_insensitive(),
897 }
898 }
899
900 fn decode_at<T: ValueSchema>(&mut self, name: &str, index: usize) -> Option<T> {
901 let value = self.values.value(index)?;
902
903 match T::decode_value(value.bytes) {
904 Ok(value) => Some(value),
905 Err(message) => {
906 self.issue(Some(name), ValidationRule::InvalidType, message);
907 None
908 }
909 }
910 }
911
912 fn issue(
913 &mut self,
914 field: Option<impl Into<String>>,
915 rule: ValidationRule,
916 message: impl Into<String>,
917 ) {
918 self.errors.push(ValidationIssue::new(field, rule, message));
919 }
920}
921
922fn nested_options<T: Schema>(parent: DecodeOptions) -> DecodeOptions {
923 DecodeOptions::new(T::UNKNOWN_FIELDS.unwrap_or(parent.unknown_fields()))
924}
925
926struct NestedValues<'values> {
927 values: Vec<Value<'values>>,
928 case_insensitive: bool,
929}
930
931impl Values for NestedValues<'_> {
932 fn len(&self) -> usize {
933 self.values.len()
934 }
935
936 fn value(&self, index: usize) -> Option<Value<'_>> {
937 self.values.get(index).copied()
938 }
939
940 fn name_matches(&self, actual: &str, expected: &str) -> bool {
941 if self.case_insensitive {
942 actual.eq_ignore_ascii_case(expected)
943 } else {
944 actual == expected
945 }
946 }
947
948 fn names_are_case_insensitive(&self) -> bool {
949 self.case_insensitive
950 }
951
952 fn strip_name_prefix<'name>(&self, actual: &'name str, prefix: &str) -> Option<&'name str> {
953 if self.case_insensitive
954 && actual
955 .get(..prefix.len())
956 .is_some_and(|actual| actual.eq_ignore_ascii_case(prefix))
957 {
958 actual.get(prefix.len()..)
959 } else {
960 actual.strip_prefix(prefix)
961 }
962 }
963}
964
965#[cfg(test)]
966mod tests {
967 use super::{DecodeOptions, Schema, SchemaKind, Value, ValueSchema, Values};
968
969 struct TestValues<'value> {
970 entries: Vec<(&'value str, &'value [u8])>,
971 }
972
973 impl Values for TestValues<'_> {
974 fn len(&self) -> usize {
975 self.entries.len()
976 }
977
978 fn value(&self, index: usize) -> Option<Value<'_>> {
979 self.entries
980 .get(index)
981 .map(|(name, bytes)| Value { name, bytes })
982 }
983 }
984
985 #[derive(Debug, PartialEq, crate::Schema)]
986 struct Filter {
987 name: String,
988 minimum: u32,
989 }
990
991 #[derive(Debug, PartialEq, crate::Schema)]
992 struct Search {
993 #[schema(nested)]
994 filter: Filter,
995 #[schema(nested)]
996 paging: Option<Paging>,
997 }
998
999 #[derive(Debug, Default, PartialEq, crate::Schema)]
1000 struct Paging {
1001 page: u32,
1002 }
1003
1004 #[derive(Debug, PartialEq, crate::Schema)]
1005 struct NestedCollection {
1006 #[schema(nested)]
1007 filters: Vec<Filter>,
1008 #[schema(nested, default)]
1009 paging: Paging,
1010 }
1011
1012 #[derive(Debug, PartialEq, crate::Schema)]
1013 #[schema(tag = "type", rename_all = "snake_case")]
1014 enum Selection {
1015 All,
1016 Range { start: u32, end: u32 },
1017 }
1018
1019 #[derive(Debug, PartialEq, crate::Schema)]
1020 struct Formatted {
1021 address: std::net::IpAddr,
1022 #[schema(format = "uuid")]
1023 identifier: String,
1024 }
1025
1026 #[derive(Debug, PartialEq, crate::Schema)]
1027 #[schema(rename_all = "kebab-case")]
1028 enum Mode {
1029 FastMode,
1030 #[schema(rename = "safe")]
1031 SafeMode,
1032 }
1033
1034 #[derive(Debug, PartialEq, crate::Schema)]
1035 struct Wrapper<T> {
1036 value: T,
1037 }
1038
1039 #[derive(Debug, PartialEq)]
1040 struct Identifier(u64);
1041
1042 impl ValueSchema for Identifier {
1043 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
1044 let value = std::str::from_utf8(bytes)
1045 .map_err(|_| "must be valid UTF-8".to_owned())?
1046 .parse()
1047 .map_err(|_| "must be an identifier".to_owned())?;
1048 Ok(Self(value))
1049 }
1050 }
1051
1052 #[test]
1053 fn decodes_nested_fields_from_dotted_names() {
1054 let values = TestValues {
1055 entries: vec![("filter.name", b"gpu"), ("filter.minimum", b"4")],
1056 };
1057 let decoded = Search::decode(&values, DecodeOptions::reject_unknown()).unwrap();
1058
1059 assert_eq!(decoded.filter.name, "gpu");
1060 assert_eq!(decoded.filter.minimum, 4);
1061 assert_eq!(decoded.paging, None);
1062 }
1063
1064 #[test]
1065 fn derives_string_enums_with_rename_rules() {
1066 let fast = TestValues {
1067 entries: vec![("mode", b"fast-mode")],
1068 };
1069 let safe = TestValues {
1070 entries: vec![("mode", b"safe")],
1071 };
1072
1073 assert_eq!(
1074 Mode::decode(&fast, DecodeOptions::reject_unknown()).unwrap(),
1075 Mode::FastMode,
1076 );
1077 assert_eq!(
1078 Mode::decode(&safe, DecodeOptions::reject_unknown()).unwrap(),
1079 Mode::SafeMode,
1080 );
1081 }
1082
1083 #[test]
1084 fn derives_generic_schemas() {
1085 let values = TestValues {
1086 entries: vec![("value", b"42")],
1087 };
1088 let decoded = Wrapper::<u64>::decode(&values, DecodeOptions::reject_unknown()).unwrap();
1089
1090 assert_eq!(decoded.value, 42);
1091 }
1092
1093 #[test]
1094 fn accepts_custom_value_schemas() {
1095 let values = TestValues {
1096 entries: vec![("value", b"91")],
1097 };
1098 let decoded =
1099 Wrapper::<Identifier>::decode(&values, DecodeOptions::reject_unknown()).unwrap();
1100
1101 assert_eq!(decoded.value, Identifier(91));
1102 }
1103
1104 #[test]
1105 fn exposes_nested_openapi_metadata() {
1106 let metadata = Search::metadata();
1107 let SchemaKind::Object(fields) = metadata.kind() else {
1108 panic!("expected object metadata");
1109 };
1110
1111 assert_eq!(fields.len(), 2);
1112 assert_eq!(fields[0].name(), "filter");
1113 assert!(fields[0].required());
1114 assert!(!fields[1].required());
1115 assert!(matches!(fields[0].schema().kind(), SchemaKind::Object(_)));
1116 }
1117
1118 #[test]
1119 fn decodes_repeated_nested_fields_and_nested_defaults() {
1120 let values = TestValues {
1121 entries: vec![
1122 ("filters.0.name", b"gpu"),
1123 ("filters.0.minimum", b"4"),
1124 ("filters.1.name", b"cpu"),
1125 ("filters.1.minimum", b"8"),
1126 ],
1127 };
1128 let decoded = NestedCollection::decode(&values, DecodeOptions::reject_unknown()).unwrap();
1129
1130 assert_eq!(decoded.filters.len(), 2);
1131 assert_eq!(decoded.filters[0].name, "gpu");
1132 assert_eq!(decoded.filters[1].minimum, 8);
1133 assert_eq!(decoded.paging, Paging::default());
1134 }
1135
1136 #[test]
1137 fn decodes_tagged_enums_with_named_data() {
1138 let range = TestValues {
1139 entries: vec![("type", b"range"), ("start", b"2"), ("end", b"9")],
1140 };
1141 let all = TestValues {
1142 entries: vec![("type", b"all")],
1143 };
1144
1145 assert_eq!(
1146 Selection::decode(&range, DecodeOptions::reject_unknown()).unwrap(),
1147 Selection::Range { start: 2, end: 9 },
1148 );
1149 assert_eq!(
1150 Selection::decode(&all, DecodeOptions::reject_unknown()).unwrap(),
1151 Selection::All,
1152 );
1153
1154 let metadata = Selection::metadata();
1155 assert_eq!(metadata.discriminator_property(), Some("type"));
1156 assert!(matches!(metadata.kind(), SchemaKind::OneOf(variants) if variants.len() == 2));
1157 }
1158
1159 #[test]
1160 fn exposes_formats_and_decodes_standard_ip_types() {
1161 let values = TestValues {
1162 entries: vec![("address", b"127.0.0.1"), ("identifier", b"abc")],
1163 };
1164 let decoded = Formatted::decode(&values, DecodeOptions::reject_unknown()).unwrap();
1165
1166 assert_eq!(decoded.address, std::net::Ipv4Addr::LOCALHOST);
1167 let SchemaKind::Object(fields) = Formatted::metadata().kind().clone() else {
1168 panic!("expected object metadata");
1169 };
1170 assert_eq!(fields[1].schema().format_value(), Some("uuid"));
1171 }
1172}