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
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}