Skip to main content

kcode_speaker_types/
lib.rs

1#![forbid(unsafe_code)]
2
3use serde::de::{self, SeqAccess, Visitor};
4use serde::ser::SerializeTuple;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use std::fmt;
7
8pub const FEATURE_COUNT: usize = 35;
9
10const MAX_KEY_BYTES: usize = 128;
11const PERCEIVED_AGE_INDEX: usize = 11;
12const FEATURE_BITS: u64 = (1_u64 << FEATURE_COUNT) - 1;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct TypeError {
16    kind: ErrorKind,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20enum ErrorKind {
21    InvalidKey,
22    InvalidObjectId,
23    FeatureValue {
24        index: usize,
25        value: u8,
26        maximum: u8,
27    },
28    EmptyFeatureMask,
29    UnknownFeatureBits,
30    SegmentOrdinal,
31    SegmentTime,
32}
33
34impl TypeError {
35    const fn new(kind: ErrorKind) -> Self {
36        Self { kind }
37    }
38}
39
40impl fmt::Display for TypeError {
41    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
42        match self.kind {
43            ErrorKind::InvalidKey => formatter.write_str(
44                "key must contain 1 to 128 ASCII letters, digits, or . _ - / : characters",
45            ),
46            ErrorKind::InvalidObjectId => {
47                formatter.write_str("object ID must contain exactly eight base64url characters")
48            }
49            ErrorKind::FeatureValue {
50                index,
51                value,
52                maximum,
53            } => write!(
54                formatter,
55                "feature at index {index} has value {value}, exceeding {maximum}"
56            ),
57            ErrorKind::EmptyFeatureMask => {
58                formatter.write_str("feature mask must contain at least one bit")
59            }
60            ErrorKind::UnknownFeatureBits => {
61                formatter.write_str("feature mask may contain only bits 0 through 34")
62            }
63            ErrorKind::SegmentOrdinal => {
64                formatter.write_str("segment ordinal must be less than segment count")
65            }
66            ErrorKind::SegmentTime => {
67                formatter.write_str("segment start must be earlier than segment end")
68            }
69        }
70    }
71}
72
73impl std::error::Error for TypeError {}
74
75#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
76#[serde(transparent)]
77pub struct Key(String);
78
79impl Key {
80    pub fn parse(value: &str) -> Result<Self, TypeError> {
81        if valid_key(value) {
82            Ok(Self(value.to_owned()))
83        } else {
84            Err(TypeError::new(ErrorKind::InvalidKey))
85        }
86    }
87}
88
89fn valid_key(value: &str) -> bool {
90    let bytes = value.as_bytes();
91    (1..=MAX_KEY_BYTES).contains(&bytes.len())
92        && bytes.iter().all(|byte| {
93            byte.is_ascii_alphanumeric() || matches!(*byte, b'.' | b'_' | b'-' | b'/' | b':')
94        })
95}
96
97impl AsRef<str> for Key {
98    fn as_ref(&self) -> &str {
99        &self.0
100    }
101}
102
103impl<'de> Deserialize<'de> for Key {
104    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
105    where
106        D: Deserializer<'de>,
107    {
108        let value = String::deserialize(deserializer)?;
109        Self::parse(&value).map_err(de::Error::custom)
110    }
111}
112
113#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
114#[serde(transparent)]
115pub struct ObjectId(String);
116
117impl ObjectId {
118    pub fn parse(value: &str) -> Result<Self, TypeError> {
119        let bytes = value.as_bytes();
120        if bytes.len() == 8
121            && bytes
122                .iter()
123                .all(|byte| byte.is_ascii_alphanumeric() || matches!(*byte, b'-' | b'_'))
124        {
125            Ok(Self(value.to_owned()))
126        } else {
127            Err(TypeError::new(ErrorKind::InvalidObjectId))
128        }
129    }
130}
131
132impl AsRef<str> for ObjectId {
133    fn as_ref(&self) -> &str {
134        &self.0
135    }
136}
137
138impl<'de> Deserialize<'de> for ObjectId {
139    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
140    where
141        D: Deserializer<'de>,
142    {
143        let value = String::deserialize(deserializer)?;
144        Self::parse(&value).map_err(de::Error::custom)
145    }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct FeatureVector([u8; FEATURE_COUNT]);
150
151impl FeatureVector {
152    pub fn new(values: [u8; FEATURE_COUNT]) -> Result<Self, TypeError> {
153        for (index, value) in values.iter().copied().enumerate() {
154            let maximum = if index == PERCEIVED_AGE_INDEX {
155                120
156            } else {
157                100
158            };
159            if value > maximum {
160                return Err(TypeError::new(ErrorKind::FeatureValue {
161                    index,
162                    value,
163                    maximum,
164                }));
165            }
166        }
167        Ok(Self(values))
168    }
169}
170
171impl AsRef<[u8; FEATURE_COUNT]> for FeatureVector {
172    fn as_ref(&self) -> &[u8; FEATURE_COUNT] {
173        &self.0
174    }
175}
176
177impl Serialize for FeatureVector {
178    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
179    where
180        S: Serializer,
181    {
182        let mut tuple = serializer.serialize_tuple(FEATURE_COUNT)?;
183        for value in self.0 {
184            tuple.serialize_element(&value)?;
185        }
186        tuple.end()
187    }
188}
189
190struct FeatureVectorVisitor;
191
192impl<'de> Visitor<'de> for FeatureVectorVisitor {
193    type Value = FeatureVector;
194
195    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
196        write!(formatter, "an array of {FEATURE_COUNT} feature values")
197    }
198
199    fn visit_seq<A>(self, mut sequence: A) -> Result<Self::Value, A::Error>
200    where
201        A: SeqAccess<'de>,
202    {
203        let mut values = [0; FEATURE_COUNT];
204        for (index, slot) in values.iter_mut().enumerate() {
205            *slot = sequence
206                .next_element()?
207                .ok_or_else(|| de::Error::invalid_length(index, &self))?;
208        }
209        if sequence.next_element::<de::IgnoredAny>()?.is_some() {
210            return Err(de::Error::invalid_length(FEATURE_COUNT + 1, &self));
211        }
212        FeatureVector::new(values).map_err(de::Error::custom)
213    }
214}
215
216impl<'de> Deserialize<'de> for FeatureVector {
217    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
218    where
219        D: Deserializer<'de>,
220    {
221        deserializer.deserialize_tuple(FEATURE_COUNT, FeatureVectorVisitor)
222    }
223}
224
225#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
226pub struct FeatureMask(u64);
227
228impl FeatureMask {
229    pub const fn from_bits(bits: u64) -> Result<Self, TypeError> {
230        if bits == 0 {
231            Err(TypeError::new(ErrorKind::EmptyFeatureMask))
232        } else if bits & !FEATURE_BITS != 0 {
233            Err(TypeError::new(ErrorKind::UnknownFeatureBits))
234        } else {
235            Ok(Self(bits))
236        }
237    }
238}
239
240impl From<FeatureMask> for u64 {
241    fn from(mask: FeatureMask) -> Self {
242        mask.0
243    }
244}
245
246impl Serialize for FeatureMask {
247    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
248    where
249        S: Serializer,
250    {
251        serializer.serialize_u64(self.0)
252    }
253}
254
255impl<'de> Deserialize<'de> for FeatureMask {
256    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
257    where
258        D: Deserializer<'de>,
259    {
260        let bits = u64::deserialize(deserializer)?;
261        Self::from_bits(bits).map_err(de::Error::custom)
262    }
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
266pub enum RecordingKind {
267    VoiceNote,
268    Meeting,
269    Call,
270    Other,
271}
272
273#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
274pub struct SegmentRef {
275    pub source_object: ObjectId,
276    pub clip_object: ObjectId,
277    pub ordinal: u16,
278    pub segment_count: u16,
279    pub start_ms: u64,
280    pub end_ms: u64,
281    pub policy: Key,
282}
283
284impl SegmentRef {
285    pub fn validate(&self) -> Result<(), TypeError> {
286        if self.ordinal >= self.segment_count {
287            Err(TypeError::new(ErrorKind::SegmentOrdinal))
288        } else if self.start_ms >= self.end_ms {
289            Err(TypeError::new(ErrorKind::SegmentTime))
290        } else {
291            Ok(())
292        }
293    }
294}
295
296#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
297pub struct LabeledSample {
298    pub sample_id: Key,
299    pub attempt_id: Key,
300    pub speaker_id: Key,
301    pub cohort_id: Key,
302    pub group_id: Key,
303    pub clip_object: ObjectId,
304    pub primary_language: Key,
305    pub recording_kind: RecordingKind,
306    pub usable_speech_ms: u32,
307    pub recording_quality: u8,
308    pub features: FeatureVector,
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314    use serde::de::IntoDeserializer;
315    use serde::ser::{Impossible, SerializeStruct};
316
317    const CONST_FEATURE_MASK: FeatureMask = match FeatureMask::from_bits(1) {
318        Ok(mask) => mask,
319        Err(_) => panic!("one feature bit is valid"),
320    };
321
322    #[derive(Debug)]
323    struct CodecError(String);
324
325    impl fmt::Display for CodecError {
326        fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
327            formatter.write_str(&self.0)
328        }
329    }
330
331    impl std::error::Error for CodecError {}
332
333    impl serde::ser::Error for CodecError {
334        fn custom<T: fmt::Display>(message: T) -> Self {
335            Self(message.to_string())
336        }
337    }
338
339    impl serde::de::Error for CodecError {
340        fn custom<T: fmt::Display>(message: T) -> Self {
341            Self(message.to_string())
342        }
343    }
344
345    #[derive(Debug)]
346    enum Value {
347        U8(u8),
348        U16(u16),
349        U32(u32),
350        U64(u64),
351        String(String),
352        Sequence(Vec<Value>),
353        Struct(Vec<(String, Value)>),
354        Variant(String),
355    }
356
357    struct ValueSerializer;
358
359    impl Serializer for ValueSerializer {
360        type Ok = Value;
361        type Error = CodecError;
362        type SerializeSeq = Impossible<Value, CodecError>;
363        type SerializeTuple = TupleBuilder;
364        type SerializeTupleStruct = Impossible<Value, CodecError>;
365        type SerializeTupleVariant = Impossible<Value, CodecError>;
366        type SerializeMap = Impossible<Value, CodecError>;
367        type SerializeStruct = StructBuilder;
368        type SerializeStructVariant = Impossible<Value, CodecError>;
369
370        fn serialize_u8(self, value: u8) -> Result<Self::Ok, Self::Error> {
371            Ok(Value::U8(value))
372        }
373
374        fn serialize_u16(self, value: u16) -> Result<Self::Ok, Self::Error> {
375            Ok(Value::U16(value))
376        }
377
378        fn serialize_u32(self, value: u32) -> Result<Self::Ok, Self::Error> {
379            Ok(Value::U32(value))
380        }
381
382        fn serialize_u64(self, value: u64) -> Result<Self::Ok, Self::Error> {
383            Ok(Value::U64(value))
384        }
385
386        fn serialize_str(self, value: &str) -> Result<Self::Ok, Self::Error> {
387            Ok(Value::String(value.to_owned()))
388        }
389
390        fn serialize_unit_variant(
391            self,
392            _name: &'static str,
393            _variant_index: u32,
394            variant: &'static str,
395        ) -> Result<Self::Ok, Self::Error> {
396            Ok(Value::Variant(variant.to_owned()))
397        }
398
399        fn serialize_newtype_struct<T>(
400            self,
401            _name: &'static str,
402            value: &T,
403        ) -> Result<Self::Ok, Self::Error>
404        where
405            T: ?Sized + Serialize,
406        {
407            value.serialize(self)
408        }
409
410        fn serialize_some<T>(self, value: &T) -> Result<Self::Ok, Self::Error>
411        where
412            T: ?Sized + Serialize,
413        {
414            value.serialize(self)
415        }
416
417        fn serialize_tuple(self, length: usize) -> Result<Self::SerializeTuple, Self::Error> {
418            Ok(TupleBuilder(Vec::with_capacity(length)))
419        }
420
421        fn serialize_struct(
422            self,
423            _name: &'static str,
424            length: usize,
425        ) -> Result<Self::SerializeStruct, Self::Error> {
426            Ok(StructBuilder(Vec::with_capacity(length)))
427        }
428
429        fn serialize_bool(self, _value: bool) -> Result<Self::Ok, Self::Error> {
430            unsupported()
431        }
432
433        fn serialize_i8(self, _value: i8) -> Result<Self::Ok, Self::Error> {
434            unsupported()
435        }
436
437        fn serialize_i16(self, _value: i16) -> Result<Self::Ok, Self::Error> {
438            unsupported()
439        }
440
441        fn serialize_i32(self, _value: i32) -> Result<Self::Ok, Self::Error> {
442            unsupported()
443        }
444
445        fn serialize_i64(self, _value: i64) -> Result<Self::Ok, Self::Error> {
446            unsupported()
447        }
448
449        fn serialize_i128(self, _value: i128) -> Result<Self::Ok, Self::Error> {
450            unsupported()
451        }
452
453        fn serialize_u128(self, _value: u128) -> Result<Self::Ok, Self::Error> {
454            unsupported()
455        }
456
457        fn serialize_f32(self, _value: f32) -> Result<Self::Ok, Self::Error> {
458            unsupported()
459        }
460
461        fn serialize_f64(self, _value: f64) -> Result<Self::Ok, Self::Error> {
462            unsupported()
463        }
464
465        fn serialize_char(self, _value: char) -> Result<Self::Ok, Self::Error> {
466            unsupported()
467        }
468
469        fn serialize_bytes(self, _value: &[u8]) -> Result<Self::Ok, Self::Error> {
470            unsupported()
471        }
472
473        fn serialize_none(self) -> Result<Self::Ok, Self::Error> {
474            unsupported()
475        }
476
477        fn serialize_unit(self) -> Result<Self::Ok, Self::Error> {
478            unsupported()
479        }
480
481        fn serialize_unit_struct(self, _name: &'static str) -> Result<Self::Ok, Self::Error> {
482            unsupported()
483        }
484
485        fn serialize_newtype_variant<T>(
486            self,
487            _name: &'static str,
488            _variant_index: u32,
489            _variant: &'static str,
490            _value: &T,
491        ) -> Result<Self::Ok, Self::Error>
492        where
493            T: ?Sized + Serialize,
494        {
495            unsupported()
496        }
497
498        fn serialize_seq(self, _length: Option<usize>) -> Result<Self::SerializeSeq, Self::Error> {
499            unsupported()
500        }
501
502        fn serialize_tuple_struct(
503            self,
504            _name: &'static str,
505            _length: usize,
506        ) -> Result<Self::SerializeTupleStruct, Self::Error> {
507            unsupported()
508        }
509
510        fn serialize_tuple_variant(
511            self,
512            _name: &'static str,
513            _variant_index: u32,
514            _variant: &'static str,
515            _length: usize,
516        ) -> Result<Self::SerializeTupleVariant, Self::Error> {
517            unsupported()
518        }
519
520        fn serialize_map(self, _length: Option<usize>) -> Result<Self::SerializeMap, Self::Error> {
521            unsupported()
522        }
523
524        fn serialize_struct_variant(
525            self,
526            _name: &'static str,
527            _variant_index: u32,
528            _variant: &'static str,
529            _length: usize,
530        ) -> Result<Self::SerializeStructVariant, Self::Error> {
531            unsupported()
532        }
533    }
534
535    struct TupleBuilder(Vec<Value>);
536
537    impl serde::ser::SerializeTuple for TupleBuilder {
538        type Ok = Value;
539        type Error = CodecError;
540
541        fn serialize_element<T>(&mut self, value: &T) -> Result<(), Self::Error>
542        where
543            T: ?Sized + Serialize,
544        {
545            self.0.push(value.serialize(ValueSerializer)?);
546            Ok(())
547        }
548
549        fn end(self) -> Result<Self::Ok, Self::Error> {
550            Ok(Value::Sequence(self.0))
551        }
552    }
553
554    struct StructBuilder(Vec<(String, Value)>);
555
556    impl SerializeStruct for StructBuilder {
557        type Ok = Value;
558        type Error = CodecError;
559
560        fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<(), Self::Error>
561        where
562            T: ?Sized + Serialize,
563        {
564            self.0
565                .push((key.to_owned(), value.serialize(ValueSerializer)?));
566            Ok(())
567        }
568
569        fn end(self) -> Result<Self::Ok, Self::Error> {
570            Ok(Value::Struct(self.0))
571        }
572    }
573
574    fn unsupported<T>() -> Result<T, CodecError> {
575        Err(CodecError("unsupported test value".to_owned()))
576    }
577
578    impl<'de> IntoDeserializer<'de, CodecError> for Value {
579        type Deserializer = Self;
580
581        fn into_deserializer(self) -> Self::Deserializer {
582            self
583        }
584    }
585
586    impl<'de> Deserializer<'de> for Value {
587        type Error = CodecError;
588
589        fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
590        where
591            V: Visitor<'de>,
592        {
593            match self {
594                Self::U8(value) => visitor.visit_u8(value),
595                Self::U16(value) => visitor.visit_u16(value),
596                Self::U32(value) => visitor.visit_u32(value),
597                Self::U64(value) => visitor.visit_u64(value),
598                Self::String(value) => visitor.visit_string(value),
599                Self::Sequence(values) => {
600                    visitor.visit_seq(serde::de::value::SeqDeserializer::<_, CodecError>::new(
601                        values.into_iter(),
602                    ))
603                }
604                Self::Struct(fields) => {
605                    visitor.visit_map(serde::de::value::MapDeserializer::<_, CodecError>::new(
606                        fields.into_iter(),
607                    ))
608                }
609                Self::Variant(name) => visitor.visit_enum(serde::de::value::StringDeserializer::<
610                    CodecError,
611                >::new(name)),
612            }
613        }
614
615        serde::forward_to_deserialize_any! {
616            bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
617            bytes byte_buf option unit unit_struct newtype_struct seq tuple tuple_struct
618            map struct enum identifier ignored_any
619        }
620    }
621
622    fn round_trip<T>(value: &T) -> T
623    where
624        T: Serialize + for<'de> Deserialize<'de>,
625    {
626        let encoded = value.serialize(ValueSerializer).expect("serialize");
627        T::deserialize(encoded).expect("deserialize")
628    }
629
630    fn key(value: &str) -> Key {
631        Key::parse(value).expect("valid key")
632    }
633
634    fn object(value: &str) -> ObjectId {
635        ObjectId::parse(value).expect("valid object ID")
636    }
637
638    fn segment() -> SegmentRef {
639        SegmentRef {
640            source_object: object("source_1"),
641            clip_object: object("clip___1"),
642            ordinal: 0,
643            segment_count: 1,
644            start_ms: 10,
645            end_ms: 20,
646            policy: key("policy:v1"),
647        }
648    }
649
650    #[test]
651    fn accepts_all_key_characters_and_boundaries() {
652        for value in ["a", "Az09._-/:", &"x".repeat(MAX_KEY_BYTES)] {
653            assert!(Key::parse(value).is_ok(), "{value:?}");
654        }
655    }
656
657    #[test]
658    fn rejects_invalid_key_shapes() {
659        for value in [
660            "",
661            &"x".repeat(MAX_KEY_BYTES + 1),
662            "has space",
663            "line\nbreak",
664            "é",
665            "key+value",
666        ] {
667            assert!(Key::parse(value).is_err(), "{value:?}");
668        }
669    }
670
671    #[test]
672    fn accepts_and_rejects_object_id_shapes() {
673        for value in ["Ab09-_xy", "________", "00000000"] {
674            assert!(ObjectId::parse(value).is_ok(), "{value:?}");
675        }
676        for value in [
677            "",
678            "1234567",
679            "123456789",
680            "abc+defg",
681            "abc/defg",
682            "abc=defg",
683            "pending:",
684            "pending:N",
685            "é1234567",
686        ] {
687            assert!(ObjectId::parse(value).is_err(), "{value:?}");
688        }
689    }
690
691    #[test]
692    fn validates_feature_boundaries_and_age_exception() {
693        let mut values = [100; FEATURE_COUNT];
694        values[PERCEIVED_AGE_INDEX] = 120;
695        assert_eq!(
696            FeatureVector::new(values)
697                .expect("boundary values")
698                .as_ref(),
699            &values
700        );
701
702        let mut invalid_age = [0; FEATURE_COUNT];
703        invalid_age[PERCEIVED_AGE_INDEX] = 121;
704        assert!(FeatureVector::new(invalid_age).is_err());
705
706        let mut invalid_other = [0; FEATURE_COUNT];
707        invalid_other[0] = 101;
708        assert!(FeatureVector::new(invalid_other).is_err());
709        invalid_other[0] = 0;
710        invalid_other[FEATURE_COUNT - 1] = 101;
711        assert!(FeatureVector::new(invalid_other).is_err());
712    }
713
714    #[test]
715    fn validates_feature_mask_bits() {
716        assert_eq!(u64::from(CONST_FEATURE_MASK), 1);
717        assert!(FeatureMask::from_bits(0).is_err());
718        assert!(FeatureMask::from_bits(1_u64 << FEATURE_COUNT).is_err());
719        assert!(FeatureMask::from_bits(u64::MAX).is_err());
720
721        let mask = FeatureMask::from_bits(FEATURE_BITS).expect("all defined bits");
722        assert_eq!(u64::from(mask), FEATURE_BITS);
723    }
724
725    #[test]
726    fn validates_segment_bounds() {
727        let mut value = segment();
728        assert!(value.validate().is_ok());
729
730        value.ordinal = value.segment_count;
731        assert!(value.validate().is_err());
732        value.ordinal = 0;
733        value.segment_count = 0;
734        assert!(value.validate().is_err());
735
736        value.segment_count = 1;
737        value.start_ms = value.end_ms;
738        assert!(value.validate().is_err());
739        value.start_ms = value.end_ms + 1;
740        assert!(value.validate().is_err());
741    }
742
743    #[test]
744    fn serde_round_trips_public_values() {
745        let feature = FeatureVector::new([42; FEATURE_COUNT]).expect("features");
746        assert_eq!(round_trip(&key("sample:1")), key("sample:1"));
747        assert_eq!(round_trip(&object("object_1")), object("object_1"));
748        assert_eq!(round_trip(&feature), feature);
749
750        let mask = FeatureMask::from_bits(5).expect("mask");
751        assert_eq!(round_trip(&mask), mask);
752
753        for kind in [
754            RecordingKind::VoiceNote,
755            RecordingKind::Meeting,
756            RecordingKind::Call,
757            RecordingKind::Other,
758        ] {
759            assert_eq!(round_trip(&kind), kind);
760        }
761    }
762
763    #[test]
764    fn serde_round_trips_shared_records() {
765        let segment = segment();
766        assert_eq!(round_trip(&segment), segment);
767
768        let sample = LabeledSample {
769            sample_id: key("sample:1"),
770            attempt_id: key("attempt:1"),
771            speaker_id: key("speaker:1"),
772            cohort_id: key("cohort:1"),
773            group_id: key("group:1"),
774            clip_object: object("clip___1"),
775            primary_language: key("en-US"),
776            recording_kind: RecordingKind::VoiceNote,
777            usable_speech_ms: 12_345,
778            recording_quality: 88,
779            features: FeatureVector::new([50; FEATURE_COUNT]).expect("features"),
780        };
781        assert_eq!(round_trip(&sample), sample);
782    }
783
784    #[test]
785    fn serde_rejects_invalid_validated_values() {
786        assert!(Key::deserialize(Value::String("not valid".to_owned())).is_err());
787        assert!(ObjectId::deserialize(Value::String("pending:".to_owned())).is_err());
788        assert!(FeatureMask::deserialize(Value::U64(0)).is_err());
789
790        let mut values = (0..FEATURE_COUNT).map(|_| Value::U8(0)).collect::<Vec<_>>();
791        values[PERCEIVED_AGE_INDEX] = Value::U8(121);
792        assert!(FeatureVector::deserialize(Value::Sequence(values)).is_err());
793    }
794}