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}