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
282impl ValidationRule {
283 pub const fn as_str(self) -> &'static str {
284 match self {
285 Self::Missing => "missing",
286 Self::UnknownField => "unknown_field",
287 Self::Multiple => "multiple",
288 Self::InvalidEncoding => "invalid_encoding",
289 Self::InvalidType => "invalid_type",
290 Self::Minimum => "minimum",
291 Self::Maximum => "maximum",
292 Self::MinimumLength => "minimum_length",
293 Self::MaximumLength => "maximum_length",
294 Self::Custom => "custom",
295 }
296 }
297}
298
299#[derive(Debug, Clone, PartialEq, Eq)]
300pub struct ValidationIssue {
301 field: Option<String>,
302 rule: ValidationRule,
303 code: Option<String>,
304 message: String,
305}
306
307impl ValidationIssue {
308 pub fn custom(message: impl Into<String>) -> Self {
309 Self {
310 field: None,
311 rule: ValidationRule::Custom,
312 code: None,
313 message: message.into(),
314 }
315 }
316
317 pub fn coded(code: impl Into<String>, message: impl Into<String>) -> Self {
318 Self {
319 field: None,
320 rule: ValidationRule::Custom,
321 code: Some(code.into()),
322 message: message.into(),
323 }
324 }
325
326 pub fn field(&self) -> Option<&str> {
327 self.field.as_deref()
328 }
329
330 pub fn rule(&self) -> ValidationRule {
331 self.rule
332 }
333
334 pub fn code(&self) -> &str {
335 self.code.as_deref().unwrap_or(self.rule.as_str())
336 }
337
338 pub fn message(&self) -> &str {
339 &self.message
340 }
341
342 pub(crate) fn new(
343 field: Option<impl Into<String>>,
344 rule: ValidationRule,
345 message: impl Into<String>,
346 ) -> Self {
347 Self {
348 field: field.map(Into::into),
349 rule,
350 code: None,
351 message: message.into(),
352 }
353 }
354
355 #[doc(hidden)]
356 pub fn attach_field(mut self, field: &str) -> Self {
357 if self.field.is_none() {
358 self.field = Some(field.to_owned());
359 }
360
361 self
362 }
363
364 #[doc(hidden)]
365 pub fn prefix_field(mut self, prefix: &str) -> Self {
366 self.field = Some(match self.field {
367 Some(field) => format!("{prefix}.{field}"),
368 None => prefix.to_owned(),
369 });
370 self
371 }
372}
373
374#[derive(Debug, Default, Clone, PartialEq, Eq)]
375pub struct ValidationErrors {
376 issues: Vec<ValidationIssue>,
377}
378
379impl ValidationErrors {
380 pub fn new() -> Self {
381 Self::default()
382 }
383
384 pub fn from_issue(issue: ValidationIssue) -> Self {
385 Self {
386 issues: vec![issue],
387 }
388 }
389
390 pub fn issues(&self) -> &[ValidationIssue] {
391 &self.issues
392 }
393
394 pub fn is_empty(&self) -> bool {
395 self.issues.is_empty()
396 }
397
398 pub fn len(&self) -> usize {
399 self.issues.len()
400 }
401
402 pub(crate) fn push(&mut self, issue: ValidationIssue) {
403 self.issues.push(issue);
404 }
405
406 fn extend_nested(&mut self, prefix: &str, errors: Self) {
407 self.issues.extend(
408 errors
409 .issues
410 .into_iter()
411 .map(|issue| issue.prefix_field(prefix)),
412 );
413 }
414}
415
416impl fmt::Display for ValidationErrors {
417 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
418 for (index, issue) in self.issues.iter().enumerate() {
419 if index > 0 {
420 formatter.write_str("; ")?;
421 }
422
423 if let Some(field) = issue.field() {
424 write!(formatter, "{field}: ")?;
425 }
426
427 formatter.write_str(issue.message())?;
428 }
429
430 Ok(())
431 }
432}
433
434impl IntoResponse for ValidationErrors {
435 fn into_response(self) -> Response {
436 Response::pending_validation(
437 crate::Error::new(
438 400,
439 "request.validation.invalid",
440 "Request validation failed",
441 ),
442 self,
443 )
444 }
445}
446
447pub trait ValueSchema: Sized {
448 fn decode_value(bytes: &[u8]) -> Result<Self, String>;
449
450 fn metadata() -> SchemaMetadata {
451 SchemaMetadata::new(SchemaKind::String)
452 }
453}
454
455impl ValueSchema for String {
456 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
457 String::from_utf8(bytes.to_vec()).map_err(|_| "must be valid UTF-8".to_owned())
458 }
459}
460
461impl ValueSchema for Vec<u8> {
462 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
463 Ok(bytes.to_vec())
464 }
465
466 fn metadata() -> SchemaMetadata {
467 SchemaMetadata::new(SchemaKind::Bytes)
468 }
469}
470
471macro_rules! value_schema {
472 ($kind:ident: $($type:ty),+ $(,)?) => {
473 $(
474 impl ValueSchema for $type {
475 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
476 let value = std::str::from_utf8(bytes)
477 .map_err(|_| "must be valid UTF-8".to_owned())?;
478
479 value
480 .parse::<Self>()
481 .map_err(|_| format!("must be a valid {}", stringify!($type)))
482 }
483
484 fn metadata() -> SchemaMetadata {
485 SchemaMetadata::new(SchemaKind::$kind)
486 }
487 }
488 )+
489 };
490}
491
492value_schema!(Boolean: bool);
493value_schema!(Integer: u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize);
494value_schema!(Number: f32, f64);
495
496impl ValueSchema for net::Ipv4Addr {
497 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
498 decode_from_str(bytes, "IPv4 address")
499 }
500
501 fn metadata() -> SchemaMetadata {
502 SchemaMetadata::new(SchemaKind::String).format("ipv4")
503 }
504}
505
506impl ValueSchema for net::Ipv6Addr {
507 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
508 decode_from_str(bytes, "IPv6 address")
509 }
510
511 fn metadata() -> SchemaMetadata {
512 SchemaMetadata::new(SchemaKind::String).format("ipv6")
513 }
514}
515
516impl ValueSchema for net::IpAddr {
517 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
518 decode_from_str(bytes, "IP address")
519 }
520
521 fn metadata() -> SchemaMetadata {
522 SchemaMetadata::new(SchemaKind::OneOf(vec![
523 <net::Ipv4Addr as ValueSchema>::metadata(),
524 <net::Ipv6Addr as ValueSchema>::metadata(),
525 ]))
526 }
527}
528
529fn decode_from_str<T: std::str::FromStr>(bytes: &[u8], expected: &str) -> Result<T, String> {
530 let value = std::str::from_utf8(bytes).map_err(|_| "must be valid UTF-8".to_owned())?;
531 value
532 .parse()
533 .map_err(|_| format!("must be a valid {expected}"))
534}
535
536impl<T: ValueSchema> Schema for T {
537 fn decode<V: Values>(values: &V, options: DecodeOptions) -> Result<Self, ValidationErrors> {
538 let mut decoder = Decoder::new(values, options);
539 let value = decoder.single::<T>();
540 let errors = decoder.finish();
541
542 match value {
543 Some(value) if errors.is_empty() => Ok(value),
544 _ => Err(errors),
545 }
546 }
547
548 fn metadata() -> SchemaMetadata {
549 T::metadata()
550 }
551}
552
553#[doc(hidden)]
554pub trait Length {
555 fn length(&self) -> usize;
556}
557
558impl Length for String {
559 fn length(&self) -> usize {
560 self.chars().count()
561 }
562}
563
564impl<T> Length for Vec<T> {
565 fn length(&self) -> usize {
566 self.len()
567 }
568}
569
570#[doc(hidden)]
571pub struct Decoder<'values, V: Values> {
572 values: &'values V,
573 options: DecodeOptions,
574 consumed: Vec<bool>,
575 errors: ValidationErrors,
576}
577
578impl<'values, V: Values> Decoder<'values, V> {
579 pub fn new(values: &'values V, options: DecodeOptions) -> Self {
580 Self {
581 values,
582 options,
583 consumed: vec![false; values.len()],
584 errors: ValidationErrors::new(),
585 }
586 }
587
588 pub fn required<T: ValueSchema>(&mut self, name: &str) -> Option<T> {
589 let indexes = self.indexes(name);
590
591 match indexes.as_slice() {
592 [] => {
593 self.issue(Some(name), ValidationRule::Missing, "is required");
594 None
595 }
596 [index] => self.decode_at::<T>(name, *index),
597 _ => {
598 self.issue(
599 Some(name),
600 ValidationRule::Multiple,
601 "must appear exactly once",
602 );
603 None
604 }
605 }
606 }
607
608 pub fn optional<T: ValueSchema>(&mut self, name: &str) -> Option<Option<T>> {
609 let indexes = self.indexes(name);
610
611 match indexes.as_slice() {
612 [] => Some(None),
613 [index] => self.decode_at::<T>(name, *index).map(Some),
614 _ => {
615 self.issue(
616 Some(name),
617 ValidationRule::Multiple,
618 "must appear at most once",
619 );
620 None
621 }
622 }
623 }
624
625 pub fn repeated<T: ValueSchema>(&mut self, name: &str) -> Option<Vec<T>> {
626 let indexes = self.indexes(name);
627 let mut decoded = Vec::with_capacity(indexes.len());
628 let mut valid = true;
629
630 for index in indexes {
631 match self.decode_at::<T>(name, index) {
632 Some(value) => decoded.push(value),
633 None => valid = false,
634 }
635 }
636
637 valid.then_some(decoded)
638 }
639
640 pub fn defaulted<T: ValueSchema, F: FnOnce() -> T>(
641 &mut self,
642 name: &str,
643 default: F,
644 ) -> Option<T> {
645 let indexes = self.indexes(name);
646
647 match indexes.as_slice() {
648 [] => Some(default()),
649 [index] => self.decode_at::<T>(name, *index),
650 _ => {
651 self.issue(
652 Some(name),
653 ValidationRule::Multiple,
654 "must appear exactly once",
655 );
656 None
657 }
658 }
659 }
660
661 pub fn required_nested<T: Schema>(&mut self, name: &str) -> Option<T> {
662 let options = nested_options::<T>(self.options);
663 let values = self.nested_values(name);
664
665 if values.is_empty() {
666 self.issue(Some(name), ValidationRule::Missing, "is required");
667 return None;
668 }
669
670 match T::decode(&values, options) {
671 Ok(value) => Some(value),
672 Err(errors) => {
673 self.errors.extend_nested(name, errors);
674 None
675 }
676 }
677 }
678
679 pub fn optional_nested<T: Schema>(&mut self, name: &str) -> Option<Option<T>> {
680 let options = nested_options::<T>(self.options);
681 let values = self.nested_values(name);
682
683 if values.is_empty() {
684 return Some(None);
685 }
686
687 match T::decode(&values, options) {
688 Ok(value) => Some(Some(value)),
689 Err(errors) => {
690 self.errors.extend_nested(name, errors);
691 None
692 }
693 }
694 }
695
696 pub fn defaulted_nested<T: Schema, F: FnOnce() -> T>(
697 &mut self,
698 name: &str,
699 default: F,
700 ) -> Option<T> {
701 let options = nested_options::<T>(self.options);
702 let values = self.nested_values(name);
703
704 if values.is_empty() {
705 return Some(default());
706 }
707
708 match T::decode(&values, options) {
709 Ok(value) => Some(value),
710 Err(errors) => {
711 self.errors.extend_nested(name, errors);
712 None
713 }
714 }
715 }
716
717 pub fn repeated_nested<T: Schema>(&mut self, name: &str) -> Option<Vec<T>> {
718 let prefix = format!("{name}.");
719 let mut indexes = BTreeSet::new();
720 let mut valid = true;
721
722 for index in 0..self.values.len() {
723 let Some(value) = self.values.value(index) else {
724 continue;
725 };
726 let Some(remainder) = self.values.strip_name_prefix(value.name, &prefix) else {
727 continue;
728 };
729 let Some((item, field)) = remainder.split_once('.') else {
730 self.consumed[index] = true;
731 self.issue(
732 Some(value.name),
733 ValidationRule::InvalidType,
734 "must use `<field>.<index>.<nested-field>`",
735 );
736 valid = false;
737 continue;
738 };
739
740 if field.is_empty() {
741 self.consumed[index] = true;
742 self.issue(
743 Some(value.name),
744 ValidationRule::InvalidType,
745 "nested field name cannot be empty",
746 );
747 valid = false;
748 continue;
749 }
750
751 match item.parse::<usize>() {
752 Ok(item) => {
753 indexes.insert(item);
754 }
755 Err(_) => {
756 self.consumed[index] = true;
757 self.issue(
758 Some(value.name),
759 ValidationRule::InvalidType,
760 "nested item index must be a non-negative integer",
761 );
762 valid = false;
763 }
764 }
765 }
766
767 let mut decoded = Vec::with_capacity(indexes.len());
768 for index in indexes {
769 let item_name = format!("{name}.{index}");
770 let options = nested_options::<T>(self.options);
771 let values = self.nested_values(&item_name);
772
773 match T::decode(&values, options) {
774 Ok(value) => decoded.push(value),
775 Err(errors) => {
776 self.errors.extend_nested(&item_name, errors);
777 valid = false;
778 }
779 }
780 }
781
782 valid.then_some(decoded)
783 }
784
785 pub fn minimum<T: PartialOrd>(&mut self, name: &str, value: &T, minimum: T) {
786 if value < &minimum {
787 self.issue(Some(name), ValidationRule::Minimum, "is below the minimum");
788 }
789 }
790
791 pub fn maximum<T: PartialOrd>(&mut self, name: &str, value: &T, maximum: T) {
792 if value > &maximum {
793 self.issue(Some(name), ValidationRule::Maximum, "is above the maximum");
794 }
795 }
796
797 pub fn minimum_length<T: Length>(&mut self, name: &str, value: &T, minimum: usize) {
798 if value.length() < minimum {
799 self.issue(
800 Some(name),
801 ValidationRule::MinimumLength,
802 "is shorter than the minimum length",
803 );
804 }
805 }
806
807 pub fn maximum_length<T: Length>(&mut self, name: &str, value: &T, maximum: usize) {
808 if value.length() > maximum {
809 self.issue(
810 Some(name),
811 ValidationRule::MaximumLength,
812 "is longer than the maximum length",
813 );
814 }
815 }
816
817 pub fn custom(&mut self, name: &str, result: Result<(), ValidationIssue>) {
818 if let Err(issue) = result {
819 self.errors.push(issue.attach_field(name));
820 }
821 }
822
823 pub fn rest(&mut self) -> ExtraFields {
824 let mut entries = Vec::new();
825
826 for index in 0..self.values.len() {
827 if self.consumed[index] {
828 continue;
829 }
830
831 let Some(value) = self.values.value(index) else {
832 continue;
833 };
834
835 entries.push((value.name.to_owned(), value.bytes.to_vec()));
836 self.consumed[index] = true;
837 }
838
839 ExtraFields {
840 entries,
841 case_insensitive: self.values.names_are_case_insensitive(),
842 }
843 }
844
845 pub fn finish(mut self) -> ValidationErrors {
846 if self.options.unknown_fields == UnknownFields::Reject {
847 let mut unknown = Vec::<String>::new();
848
849 for index in 0..self.values.len() {
850 if self.consumed[index] {
851 continue;
852 }
853
854 let Some(value) = self.values.value(index) else {
855 continue;
856 };
857
858 if unknown
859 .iter()
860 .any(|name| self.values.name_matches(name, value.name))
861 {
862 continue;
863 }
864
865 unknown.push(value.name.to_owned());
866 self.issue(
867 Some(value.name),
868 ValidationRule::UnknownField,
869 "is not declared by the schema",
870 );
871 }
872 }
873
874 self.errors
875 }
876
877 fn single<T: ValueSchema>(&mut self) -> Option<T> {
878 for consumed in &mut self.consumed {
879 *consumed = true;
880 }
881
882 match self.values.len() {
883 0 => {
884 self.issue(None::<&str>, ValidationRule::Missing, "a value is required");
885 None
886 }
887 1 => self.decode_at::<T>("value", 0),
888 _ => {
889 self.issue(
890 None::<&str>,
891 ValidationRule::Multiple,
892 "exactly one value is required",
893 );
894 None
895 }
896 }
897 }
898
899 fn indexes(&mut self, name: &str) -> Vec<usize> {
900 let indexes = (0..self.values.len())
901 .filter(|index| {
902 self.values
903 .value(*index)
904 .is_some_and(|value| self.values.name_matches(value.name, name))
905 })
906 .collect::<Vec<_>>();
907
908 for index in &indexes {
909 self.consumed[*index] = true;
910 }
911
912 indexes
913 }
914
915 fn nested_values(&mut self, name: &str) -> NestedValues<'_> {
916 let prefix = format!("{name}.");
917 let mut values = Vec::new();
918
919 for index in 0..self.values.len() {
920 let Some(value) = self.values.value(index) else {
921 continue;
922 };
923 let Some(name) = self.values.strip_name_prefix(value.name, &prefix) else {
924 continue;
925 };
926
927 self.consumed[index] = true;
928 values.push(Value {
929 name,
930 bytes: value.bytes,
931 });
932 }
933
934 NestedValues {
935 values,
936 case_insensitive: self.values.names_are_case_insensitive(),
937 }
938 }
939
940 fn decode_at<T: ValueSchema>(&mut self, name: &str, index: usize) -> Option<T> {
941 let value = self.values.value(index)?;
942
943 match T::decode_value(value.bytes) {
944 Ok(value) => Some(value),
945 Err(message) => {
946 self.issue(Some(name), ValidationRule::InvalidType, message);
947 None
948 }
949 }
950 }
951
952 fn issue(
953 &mut self,
954 field: Option<impl Into<String>>,
955 rule: ValidationRule,
956 message: impl Into<String>,
957 ) {
958 self.errors.push(ValidationIssue::new(field, rule, message));
959 }
960}
961
962fn nested_options<T: Schema>(parent: DecodeOptions) -> DecodeOptions {
963 DecodeOptions::new(T::UNKNOWN_FIELDS.unwrap_or(parent.unknown_fields()))
964}
965
966struct NestedValues<'values> {
967 values: Vec<Value<'values>>,
968 case_insensitive: bool,
969}
970
971impl Values for NestedValues<'_> {
972 fn len(&self) -> usize {
973 self.values.len()
974 }
975
976 fn value(&self, index: usize) -> Option<Value<'_>> {
977 self.values.get(index).copied()
978 }
979
980 fn name_matches(&self, actual: &str, expected: &str) -> bool {
981 if self.case_insensitive {
982 actual.eq_ignore_ascii_case(expected)
983 } else {
984 actual == expected
985 }
986 }
987
988 fn names_are_case_insensitive(&self) -> bool {
989 self.case_insensitive
990 }
991
992 fn strip_name_prefix<'name>(&self, actual: &'name str, prefix: &str) -> Option<&'name str> {
993 if self.case_insensitive
994 && actual
995 .get(..prefix.len())
996 .is_some_and(|actual| actual.eq_ignore_ascii_case(prefix))
997 {
998 actual.get(prefix.len()..)
999 } else {
1000 actual.strip_prefix(prefix)
1001 }
1002 }
1003}
1004
1005#[cfg(test)]
1006mod tests {
1007 use super::{DecodeOptions, Schema, SchemaKind, Value, ValueSchema, Values};
1008
1009 struct TestValues<'value> {
1010 entries: Vec<(&'value str, &'value [u8])>,
1011 }
1012
1013 impl Values for TestValues<'_> {
1014 fn len(&self) -> usize {
1015 self.entries.len()
1016 }
1017
1018 fn value(&self, index: usize) -> Option<Value<'_>> {
1019 self.entries
1020 .get(index)
1021 .map(|(name, bytes)| Value { name, bytes })
1022 }
1023 }
1024
1025 #[derive(Debug, PartialEq, crate::Schema)]
1026 struct Filter {
1027 name: String,
1028 minimum: u32,
1029 }
1030
1031 #[derive(Debug, PartialEq, crate::Schema)]
1032 struct Search {
1033 #[schema(nested)]
1034 filter: Filter,
1035 #[schema(nested)]
1036 paging: Option<Paging>,
1037 }
1038
1039 #[derive(Debug, Default, PartialEq, crate::Schema)]
1040 struct Paging {
1041 page: u32,
1042 }
1043
1044 #[derive(Debug, PartialEq, crate::Schema)]
1045 struct NestedCollection {
1046 #[schema(nested)]
1047 filters: Vec<Filter>,
1048 #[schema(nested, default)]
1049 paging: Paging,
1050 }
1051
1052 #[derive(Debug, PartialEq, crate::Schema)]
1053 #[schema(tag = "type", rename_all = "snake_case")]
1054 enum Selection {
1055 All,
1056 Range { start: u32, end: u32 },
1057 }
1058
1059 #[derive(Debug, PartialEq, crate::Schema)]
1060 struct Formatted {
1061 address: std::net::IpAddr,
1062 #[schema(format = "uuid")]
1063 identifier: String,
1064 }
1065
1066 #[derive(Debug, PartialEq, crate::Schema)]
1067 #[schema(rename_all = "kebab-case")]
1068 enum Mode {
1069 FastMode,
1070 #[schema(rename = "safe")]
1071 SafeMode,
1072 }
1073
1074 #[derive(Debug, PartialEq, crate::Schema)]
1075 struct Wrapper<T> {
1076 value: T,
1077 }
1078
1079 #[derive(Debug, PartialEq)]
1080 struct Identifier(u64);
1081
1082 impl ValueSchema for Identifier {
1083 fn decode_value(bytes: &[u8]) -> Result<Self, String> {
1084 let value = std::str::from_utf8(bytes)
1085 .map_err(|_| "must be valid UTF-8".to_owned())?
1086 .parse()
1087 .map_err(|_| "must be an identifier".to_owned())?;
1088 Ok(Self(value))
1089 }
1090 }
1091
1092 #[test]
1093 fn decodes_nested_fields_from_dotted_names() {
1094 let values = TestValues {
1095 entries: vec![("filter.name", b"gpu"), ("filter.minimum", b"4")],
1096 };
1097 let decoded = Search::decode(&values, DecodeOptions::reject_unknown()).unwrap();
1098
1099 assert_eq!(decoded.filter.name, "gpu");
1100 assert_eq!(decoded.filter.minimum, 4);
1101 assert_eq!(decoded.paging, None);
1102 }
1103
1104 #[test]
1105 fn derives_string_enums_with_rename_rules() {
1106 let fast = TestValues {
1107 entries: vec![("mode", b"fast-mode")],
1108 };
1109 let safe = TestValues {
1110 entries: vec![("mode", b"safe")],
1111 };
1112
1113 assert_eq!(
1114 Mode::decode(&fast, DecodeOptions::reject_unknown()).unwrap(),
1115 Mode::FastMode,
1116 );
1117 assert_eq!(
1118 Mode::decode(&safe, DecodeOptions::reject_unknown()).unwrap(),
1119 Mode::SafeMode,
1120 );
1121 }
1122
1123 #[test]
1124 fn derives_generic_schemas() {
1125 let values = TestValues {
1126 entries: vec![("value", b"42")],
1127 };
1128 let decoded = Wrapper::<u64>::decode(&values, DecodeOptions::reject_unknown()).unwrap();
1129
1130 assert_eq!(decoded.value, 42);
1131 }
1132
1133 #[test]
1134 fn accepts_custom_value_schemas() {
1135 let values = TestValues {
1136 entries: vec![("value", b"91")],
1137 };
1138 let decoded =
1139 Wrapper::<Identifier>::decode(&values, DecodeOptions::reject_unknown()).unwrap();
1140
1141 assert_eq!(decoded.value, Identifier(91));
1142 }
1143
1144 #[test]
1145 fn exposes_nested_openapi_metadata() {
1146 let metadata = Search::metadata();
1147 let SchemaKind::Object(fields) = metadata.kind() else {
1148 panic!("expected object metadata");
1149 };
1150
1151 assert_eq!(fields.len(), 2);
1152 assert_eq!(fields[0].name(), "filter");
1153 assert!(fields[0].required());
1154 assert!(!fields[1].required());
1155 assert!(matches!(fields[0].schema().kind(), SchemaKind::Object(_)));
1156 }
1157
1158 #[test]
1159 fn decodes_repeated_nested_fields_and_nested_defaults() {
1160 let values = TestValues {
1161 entries: vec![
1162 ("filters.0.name", b"gpu"),
1163 ("filters.0.minimum", b"4"),
1164 ("filters.1.name", b"cpu"),
1165 ("filters.1.minimum", b"8"),
1166 ],
1167 };
1168 let decoded = NestedCollection::decode(&values, DecodeOptions::reject_unknown()).unwrap();
1169
1170 assert_eq!(decoded.filters.len(), 2);
1171 assert_eq!(decoded.filters[0].name, "gpu");
1172 assert_eq!(decoded.filters[1].minimum, 8);
1173 assert_eq!(decoded.paging, Paging::default());
1174 }
1175
1176 #[test]
1177 fn decodes_tagged_enums_with_named_data() {
1178 let range = TestValues {
1179 entries: vec![("type", b"range"), ("start", b"2"), ("end", b"9")],
1180 };
1181 let all = TestValues {
1182 entries: vec![("type", b"all")],
1183 };
1184
1185 assert_eq!(
1186 Selection::decode(&range, DecodeOptions::reject_unknown()).unwrap(),
1187 Selection::Range { start: 2, end: 9 },
1188 );
1189 assert_eq!(
1190 Selection::decode(&all, DecodeOptions::reject_unknown()).unwrap(),
1191 Selection::All,
1192 );
1193
1194 let metadata = Selection::metadata();
1195 assert_eq!(metadata.discriminator_property(), Some("type"));
1196 assert!(matches!(metadata.kind(), SchemaKind::OneOf(variants) if variants.len() == 2));
1197 }
1198
1199 #[test]
1200 fn exposes_formats_and_decodes_standard_ip_types() {
1201 let values = TestValues {
1202 entries: vec![("address", b"127.0.0.1"), ("identifier", b"abc")],
1203 };
1204 let decoded = Formatted::decode(&values, DecodeOptions::reject_unknown()).unwrap();
1205
1206 assert_eq!(decoded.address, std::net::Ipv4Addr::LOCALHOST);
1207 let SchemaKind::Object(fields) = Formatted::metadata().kind().clone() else {
1208 panic!("expected object metadata");
1209 };
1210 assert_eq!(fields[1].schema().format_value(), Some("uuid"));
1211 }
1212}