Skip to main content

serverkit/
schemaval.rs

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}