webauthn_rp

WebAuthn Level 3 RP library.
git clone https://git.philomathiclife.com/repos/webauthn_rp
Log | Files | Refs | README

ser.rs (188024B)


      1 extern crate alloc;
      2 use super::{
      3     super::{
      4         super::response::ser::{Null, Type},
      5         auth::PrfInputOwned,
      6         ser::PrfHelper,
      7     },
      8     AuthenticatorAttachmentReq, AuthenticatorSelectionCriteria, Challenge, CoseAlgorithmIdentifier,
      9     CoseAlgorithmIdentifiers, CredProtect, CredentialCreationOptions,
     10     CredentialMediationRequirement, CrossPlatformHint, DisplayName, Extension, ExtensionInfo,
     11     ExtensionReq, FIVE_MINUTES, FourToSixtyThree, Hint, Nickname, PlatformHint, PrfInput,
     12     PublicKeyCredentialCreationOptions, PublicKeyCredentialDescriptor,
     13     PublicKeyCredentialUserEntity, RegistrationClientState, ResidentKeyRequirement, RpId,
     14     UserHandle, UserVerificationRequirement, Username,
     15 };
     16 #[cfg(doc)]
     17 use crate::response::AuthenticatorAttachment;
     18 use alloc::borrow::Cow;
     19 #[cfg(doc)]
     20 use core::str::FromStr;
     21 use core::{
     22     convert,
     23     error::Error as E,
     24     fmt::{self, Display, Formatter},
     25     marker::PhantomData,
     26     num::NonZeroU32,
     27     str,
     28 };
     29 use serde::{
     30     de::{Deserialize, Deserializer, Error, MapAccess, SeqAccess, Unexpected, Visitor},
     31     ser::{Serialize, SerializeSeq as _, SerializeStruct as _, Serializer},
     32 };
     33 impl Serialize for Nickname<'_> {
     34     /// Serializes `self` as a [`prim@str`].
     35     ///
     36     /// # Examples
     37     ///
     38     /// ```
     39     /// # use webauthn_rp::request::register::Nickname;
     40     /// assert_eq!(
     41     ///     serde_json::to_string(&Nickname::try_from("Giuseppe Luigi Lagrangia")?).unwrap(),
     42     ///     r#""Giuseppe Luigi Lagrangia""#
     43     /// );
     44     /// # Ok::<_, webauthn_rp::AggErr>(())
     45     /// ```
     46     #[inline]
     47     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     48     where
     49         S: Serializer,
     50     {
     51         serializer.serialize_str(self.0.as_ref())
     52     }
     53 }
     54 impl Serialize for DisplayName<'_> {
     55     /// Serializes `self` as a [`prim@str`].
     56     ///
     57     /// # Examples
     58     ///
     59     /// ```
     60     /// # use webauthn_rp::request::register::DisplayName;
     61     /// assert_eq!(
     62     ///     serde_json::to_string(&DisplayName::try_from("Terence Tao")?).unwrap(),
     63     ///     r#""Terence Tao""#
     64     /// );
     65     /// # Ok::<_, webauthn_rp::AggErr>(())
     66     /// ```
     67     #[inline]
     68     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     69     where
     70         S: Serializer,
     71     {
     72         serializer.serialize_str(self.as_ref())
     73     }
     74 }
     75 impl Serialize for Username<'_> {
     76     /// Serializes `self` as a [`prim@str`].
     77     ///
     78     /// # Examples
     79     ///
     80     /// ```
     81     /// # use webauthn_rp::request::register::Username;
     82     /// assert_eq!(
     83     ///     serde_json::to_string(&Username::try_from("john.von.neumann")?).unwrap(),
     84     ///     r#""john.von.neumann""#
     85     /// );
     86     /// # Ok::<_, webauthn_rp::AggErr>(())
     87     /// ```
     88     #[inline]
     89     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     90     where
     91         S: Serializer,
     92     {
     93         serializer.serialize_str(self.0.as_ref())
     94     }
     95 }
     96 /// `"type"`
     97 const TYPE: &str = "type";
     98 /// `"public-key"`
     99 const PUBLIC_KEY: &str = "public-key";
    100 /// `"alg"`
    101 const ALG: &str = "alg";
    102 /// [EdDSA](https://www.iana.org/assignments/cose/cose.xhtml#algorithms)
    103 const EDDSA: i16 = -8i16;
    104 /// [ES256](https://www.iana.org/assignments/cose/cose.xhtml#algorithms)
    105 const ES256: i16 = -7i16;
    106 /// [ES384](https://www.iana.org/assignments/cose/cose.xhtml#algorithms)
    107 const ES384: i16 = -35i16;
    108 /// [ML-DSA-44](https://www.iana.org/assignments/cose/cose.xhtml#algorithms)
    109 const MLDSA44: i16 = -48i16;
    110 /// [ML-DSA-65](https://www.iana.org/assignments/cose/cose.xhtml#algorithms)
    111 const MLDSA65: i16 = -49i16;
    112 /// [ML-DSA-87](https://www.iana.org/assignments/cose/cose.xhtml#algorithms)
    113 const MLDSA87: i16 = -50i16;
    114 /// [RS256](https://www.iana.org/assignments/cose/cose.xhtml#algorithms)
    115 const RS256: i16 = -257i16;
    116 impl Serialize for CoseAlgorithmIdentifier {
    117     /// Serializes `self` into a `struct` based on
    118     /// [`PublicKeyCredentialParameters`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialparameters).
    119     #[inline]
    120     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    121     where
    122         S: Serializer,
    123     {
    124         serializer
    125             .serialize_struct("PublicKeyCredentialParameters", 2)
    126             .and_then(|mut ser| {
    127                 ser.serialize_field(TYPE, PUBLIC_KEY).and_then(|()| {
    128                     ser.serialize_field(
    129                         ALG,
    130                         &match *self {
    131                             Self::Mldsa87 => MLDSA87,
    132                             Self::Mldsa65 => MLDSA65,
    133                             Self::Mldsa44 => MLDSA44,
    134                             Self::Eddsa => EDDSA,
    135                             Self::Es256 => ES256,
    136                             Self::Es384 => ES384,
    137                             Self::Rs256 => RS256,
    138                         },
    139                     )
    140                     .and_then(|()| ser.end())
    141                 })
    142             })
    143     }
    144 }
    145 impl Serialize for CoseAlgorithmIdentifiers {
    146     /// Serializes `self` to conform with
    147     /// [`pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-pubkeycredparams).
    148     ///
    149     /// # Examples
    150     ///
    151     /// ```
    152     /// # use webauthn_rp::request::register::{CoseAlgorithmIdentifier,CoseAlgorithmIdentifiers};
    153     /// assert_eq!(
    154     ///     serde_json::to_string(&CoseAlgorithmIdentifiers::ALL)?,
    155     ///     r#"[{"type":"public-key","alg":-50},{"type":"public-key","alg":-49},{"type":"public-key","alg":-48},{"type":"public-key","alg":-8},{"type":"public-key","alg":-7},{"type":"public-key","alg":-35},{"type":"public-key","alg":-257}]"#
    156     /// );
    157     /// assert_eq!(
    158     ///     serde_json::to_string(&CoseAlgorithmIdentifiers::default().remove(CoseAlgorithmIdentifier::Es384))?,
    159     ///     r#"[{"type":"public-key","alg":-50},{"type":"public-key","alg":-49},{"type":"public-key","alg":-48},{"type":"public-key","alg":-8},{"type":"public-key","alg":-7},{"type":"public-key","alg":-257}]"#
    160     /// );
    161     /// # Ok::<_, serde_json::Error>(())
    162     /// ```
    163     #[expect(
    164         clippy::arithmetic_side_effects,
    165         reason = "comment justifies correctness"
    166     )]
    167     #[inline]
    168     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    169     where
    170         S: Serializer,
    171     {
    172         // At most we add `1` seven times which clearly cannot overflow or `usize`.
    173         serializer
    174             .serialize_seq(Some(
    175                 usize::from(self.contains(CoseAlgorithmIdentifier::Mldsa87))
    176                     + usize::from(self.contains(CoseAlgorithmIdentifier::Mldsa65))
    177                     + usize::from(self.contains(CoseAlgorithmIdentifier::Mldsa44))
    178                     + usize::from(self.contains(CoseAlgorithmIdentifier::Eddsa))
    179                     + usize::from(self.contains(CoseAlgorithmIdentifier::Es256))
    180                     + usize::from(self.contains(CoseAlgorithmIdentifier::Es384))
    181                     + usize::from(self.contains(CoseAlgorithmIdentifier::Es384)),
    182             ))
    183             .and_then(|mut ser| {
    184                 if self.contains(CoseAlgorithmIdentifier::Mldsa87) {
    185                     ser.serialize_element(&CoseAlgorithmIdentifier::Mldsa87)
    186                 } else {
    187                     Ok(())
    188                 }
    189                 .and_then(|()| {
    190                     if self.contains(CoseAlgorithmIdentifier::Mldsa65) {
    191                         ser.serialize_element(&CoseAlgorithmIdentifier::Mldsa65)
    192                     } else {
    193                         Ok(())
    194                     }
    195                     .and_then(|()| {
    196                         if self.contains(CoseAlgorithmIdentifier::Mldsa44) {
    197                             ser.serialize_element(&CoseAlgorithmIdentifier::Mldsa44)
    198                         } else {
    199                             Ok(())
    200                         }
    201                         .and_then(|()| {
    202                             if self.contains(CoseAlgorithmIdentifier::Eddsa) {
    203                                 ser.serialize_element(&CoseAlgorithmIdentifier::Eddsa)
    204                             } else {
    205                                 Ok(())
    206                             }
    207                             .and_then(|()| {
    208                                 if self.contains(CoseAlgorithmIdentifier::Es256) {
    209                                     ser.serialize_element(&CoseAlgorithmIdentifier::Es256)
    210                                 } else {
    211                                     Ok(())
    212                                 }
    213                                 .and_then(|()| {
    214                                     if self.contains(CoseAlgorithmIdentifier::Es384) {
    215                                         ser.serialize_element(&CoseAlgorithmIdentifier::Es384)
    216                                     } else {
    217                                         Ok(())
    218                                     }
    219                                     .and_then(|()| {
    220                                         if self.contains(CoseAlgorithmIdentifier::Rs256) {
    221                                             ser.serialize_element(&CoseAlgorithmIdentifier::Rs256)
    222                                         } else {
    223                                             Ok(())
    224                                         }
    225                                         .and_then(|()| ser.end())
    226                                     })
    227                                 })
    228                             })
    229                         })
    230                     })
    231                 })
    232             })
    233     }
    234 }
    235 /// `"name"`.
    236 const NAME: &str = "name";
    237 /// `"id"`.
    238 const ID: &str = "id";
    239 /// `newtype` around `RpId` to be used to serialize `PublicKeyCredentialRpEntity`.
    240 struct PublicKeyCredentialRpEntity<'a>(&'a RpId);
    241 impl Serialize for PublicKeyCredentialRpEntity<'_> {
    242     /// Serializes `self` to conform with
    243     /// [`PublicKeyCredentialRpEntity`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialrpentity).
    244     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    245     where
    246         S: Serializer,
    247     {
    248         serializer
    249             .serialize_struct("PublicKeyCredentialRpEntity", 2)
    250             .and_then(|mut ser| {
    251                 ser.serialize_field(NAME, self.0)
    252                     .and_then(|()| ser.serialize_field(ID, self.0).and_then(|()| ser.end()))
    253             })
    254     }
    255 }
    256 // We implement this separately from `user_serialize` for proper documentation and example purposes.
    257 impl Serialize for UserHandle<1> {
    258     /// Serializes `self` to conform with
    259     /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-id).
    260     ///
    261     /// # Examples
    262     ///
    263     /// ```
    264     /// # use webauthn_rp::request::register::UserHandle;
    265     /// # #[cfg(feature = "custom")]
    266     /// // We create this manually purely for example. One should almost always
    267     /// // randomly generate this (e.g., `UserHandle::new`).
    268     /// let id = UserHandle::from([0]);
    269     /// # #[cfg(feature = "custom")]
    270     /// assert_eq!(serde_json::to_string(&id)?, r#""AA""#);
    271     /// # Ok::<_, serde_json::Error>(())
    272     /// ```
    273     #[inline]
    274     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    275     where
    276         S: Serializer,
    277     {
    278         serializer.serialize_str(base64url_nopad::encode_buffer(
    279             self.0.as_slice(),
    280             [0; base64url_nopad::encode_len(1)].as_mut_slice(),
    281         ))
    282     }
    283 }
    284 /// Implements [`Serialize`] for [`UserHandle`] of array of length of the passed `usize` literal.
    285 ///
    286 /// Only [`USER_HANDLE_MIN_LEN`]–[`USER_HANDLE_MAX_LEN`] inclusively are allowed to be passed.
    287 macro_rules! user_serialize {
    288     ( $( $x:literal),* ) => {
    289         $(
    290 impl Serialize for UserHandle<$x> {
    291     /// See [`UserHandle::serialize`].
    292     #[inline]
    293     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    294     where
    295         S: Serializer,
    296     {
    297 
    298         serializer.serialize_str(base64url_nopad::encode_buffer(self.0.as_slice(), [0; base64url_nopad::encode_len($x)].as_mut_slice()))
    299     }
    300 }
    301         )*
    302     };
    303 }
    304 // MUST only pass `2`–[`USER_HANDLE_MAX_LEN`] inclusively.
    305 user_serialize!(
    306     2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
    307     28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
    308     52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64
    309 );
    310 /// `"displayName"`.
    311 const DISPLAY_NAME: &str = "displayName";
    312 impl<const LEN: usize> Serialize for PublicKeyCredentialUserEntity<'_, '_, '_, LEN>
    313 where
    314     UserHandle<LEN>: Serialize,
    315 {
    316     /// Serializes `self` to conform with
    317     /// [`PublicKeyCredentialUserEntityJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialuserentityjson).
    318     ///
    319     /// # Examples
    320     ///
    321     /// ```
    322     /// # use webauthn_rp::request::register::{DisplayName, PublicKeyCredentialUserEntity, UserHandle};
    323     /// # #[cfg(feature = "custom")]
    324     /// // We create this manually purely for example. One should almost always
    325     /// // randomly generate this (e.g., `UserHandle::new`).
    326     /// let id = UserHandle::from([0]);
    327     /// # #[cfg(feature = "custom")]
    328     /// assert_eq!(
    329     ///     serde_json::to_string(&PublicKeyCredentialUserEntity {
    330     ///         name: "georg.cantor".try_into()?,
    331     ///         id: &id,
    332     ///         display_name: "Гео́рг Ка́нтор".try_into()?,
    333     ///     }).unwrap(),
    334     ///     r#"{"name":"georg.cantor","id":"AA","displayName":"Гео́рг Ка́нтор"}"#
    335     /// );
    336     /// // The display name gets serialized as an empty string
    337     /// // iff `Self::display_name` is `None`.
    338     /// # #[cfg(feature = "custom")]
    339     /// assert_eq!(
    340     ///     serde_json::to_string(&PublicKeyCredentialUserEntity {
    341     ///         name: "georg.cantor".try_into()?,
    342     ///         id: &id,
    343     ///         display_name: DisplayName::Blank,
    344     ///     }).unwrap(),
    345     ///     r#"{"name":"georg.cantor","id":"AA","displayName":""}"#
    346     /// );
    347     /// # Ok::<_, webauthn_rp::AggErr>(())
    348     /// ```
    349     #[inline]
    350     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    351     where
    352         S: Serializer,
    353     {
    354         serializer
    355             .serialize_struct("PublicKeyCredentialUserEntity", 3)
    356             .and_then(|mut ser| {
    357                 ser.serialize_field(NAME, &self.name).and_then(|()| {
    358                     ser.serialize_field(ID, &self.id).and_then(|()| {
    359                         ser.serialize_field(DISPLAY_NAME, &self.display_name)
    360                             .and_then(|()| ser.end())
    361                     })
    362                 })
    363             })
    364     }
    365 }
    366 /// `"required"`
    367 const REQUIRED: &str = "required";
    368 /// `"discouraged"`
    369 const DISCOURAGED: &str = "discouraged";
    370 /// `"preferred"`
    371 const PREFERRED: &str = "preferred";
    372 impl Serialize for ResidentKeyRequirement {
    373     /// Serializes `self` to conform with
    374     /// [`ResidentKeyRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement).
    375     ///
    376     /// # Examples
    377     ///
    378     /// ```
    379     /// # use webauthn_rp::request::register::ResidentKeyRequirement;
    380     /// assert_eq!(
    381     ///     serde_json::to_string(&ResidentKeyRequirement::Required)?,
    382     ///     r#""required""#
    383     /// );
    384     /// assert_eq!(
    385     ///     serde_json::to_string(&ResidentKeyRequirement::Discouraged)?,
    386     ///     r#""discouraged""#
    387     /// );
    388     /// assert_eq!(
    389     ///     serde_json::to_string(&ResidentKeyRequirement::Preferred)?,
    390     ///     r#""preferred""#
    391     /// );
    392     /// # Ok::<_, serde_json::Error>(())
    393     /// ```
    394     #[inline]
    395     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    396     where
    397         S: Serializer,
    398     {
    399         serializer.serialize_str(match *self {
    400             Self::Required => REQUIRED,
    401             Self::Discouraged => DISCOURAGED,
    402             Self::Preferred => PREFERRED,
    403         })
    404     }
    405 }
    406 impl Serialize for CrossPlatformHint {
    407     /// Serializes `self` to conform with
    408     /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-hints).
    409     ///
    410     /// # Examples
    411     ///
    412     /// ```
    413     /// # use webauthn_rp::request::register::CrossPlatformHint;
    414     /// assert_eq!(
    415     ///     serde_json::to_string(&CrossPlatformHint::None)?,
    416     ///     r#"[]"#
    417     /// );
    418     /// assert_eq!(
    419     ///     serde_json::to_string(&CrossPlatformHint::SecurityKey)?,
    420     ///     r#"["security-key"]"#
    421     /// );
    422     /// assert_eq!(
    423     ///     serde_json::to_string(&CrossPlatformHint::Hybrid)?,
    424     ///     r#"["hybrid"]"#
    425     /// );
    426     /// assert_eq!(
    427     ///     serde_json::to_string(&CrossPlatformHint::SecurityKeyHybrid)?,
    428     ///     r#"["security-key","hybrid"]"#
    429     /// );
    430     /// assert_eq!(
    431     ///     serde_json::to_string(&CrossPlatformHint::HybridSecurityKey)?,
    432     ///     r#"["hybrid","security-key"]"#
    433     /// );
    434     /// # Ok::<_, serde_json::Error>(())
    435     /// ```
    436     #[inline]
    437     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    438     where
    439         S: Serializer,
    440     {
    441         Hint::from(*self).serialize(serializer)
    442     }
    443 }
    444 impl Serialize for PlatformHint {
    445     /// Serializes `self` to conform with
    446     /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-hints).
    447     ///
    448     /// # Examples
    449     ///
    450     /// ```
    451     /// # use webauthn_rp::request::register::PlatformHint;
    452     /// assert_eq!(
    453     ///     serde_json::to_string(&PlatformHint::None)?,
    454     ///     r#"[]"#
    455     /// );
    456     /// assert_eq!(
    457     ///     serde_json::to_string(&PlatformHint::ClientDevice)?,
    458     ///     r#"["client-device"]"#
    459     /// );
    460     /// # Ok::<_, serde_json::Error>(())
    461     /// ```
    462     #[inline]
    463     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    464     where
    465         S: Serializer,
    466     {
    467         Hint::from(*self).serialize(serializer)
    468     }
    469 }
    470 /// `"platform"`.
    471 const PLATFORM: &str = "platform";
    472 /// `"cross-platform"`.
    473 const CROSS_PLATFORM: &str = "cross-platform";
    474 /// `"authenticatorAttachment"`.
    475 const AUTHENTICATOR_ATTACHMENT: &str = "authenticatorAttachment";
    476 /// `"residentKey"`.
    477 const RESIDENT_KEY: &str = "residentKey";
    478 /// `"requireResidentKey"`.
    479 const REQUIRE_RESIDENT_KEY: &str = "requireResidentKey";
    480 /// `"userVerification"`.
    481 const USER_VERIFICATION: &str = "userVerification";
    482 impl Serialize for AuthenticatorSelectionCriteria {
    483     /// Serializes `self` to conform with
    484     /// [`AuthenticatorSelectionCriteria`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticatorselectioncriteria).
    485     ///
    486     /// # Examples
    487     ///
    488     /// ```
    489     /// # use webauthn_rp::request::register::{AuthenticatorAttachmentReq, AuthenticatorSelectionCriteria, CrossPlatformHint};
    490     /// assert_eq!(
    491     ///     serde_json::to_string(&AuthenticatorSelectionCriteria::passkey())?,
    492     ///     r#"{"residentKey":"required","requireResidentKey":true,"userVerification":"required"}"#
    493     /// );
    494     /// assert_eq!(
    495     ///     serde_json::to_string(&AuthenticatorSelectionCriteria::second_factor())?,
    496     ///     r#"{"residentKey":"discouraged","requireResidentKey":false,"userVerification":"discouraged"}"#
    497     /// );
    498     /// let mut crit = AuthenticatorSelectionCriteria::passkey();
    499     /// crit.authenticator_attachment = AuthenticatorAttachmentReq::CrossPlatform(
    500     ///     CrossPlatformHint::SecurityKey,
    501     /// );
    502     /// assert_eq!(
    503     ///     serde_json::to_string(&crit)?,
    504     ///     r#"{"authenticatorAttachment":"cross-platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"}"#
    505     /// );
    506     /// # Ok::<_, serde_json::Error>(())
    507     /// ```
    508     #[inline]
    509     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    510     where
    511         S: Serializer,
    512     {
    513         let count = if matches!(
    514             self.authenticator_attachment,
    515             AuthenticatorAttachmentReq::None(_)
    516         ) {
    517             3
    518         } else {
    519             4
    520         };
    521         serializer
    522             .serialize_struct("AuthenticatorSelectionCriteria", count)
    523             .and_then(|mut ser| {
    524                 if count == 3 {
    525                     Ok(())
    526                 } else {
    527                     ser.serialize_field(
    528                         AUTHENTICATOR_ATTACHMENT,
    529                         if matches!(
    530                             self.authenticator_attachment,
    531                             AuthenticatorAttachmentReq::Platform(_)
    532                         ) {
    533                             PLATFORM
    534                         } else {
    535                             CROSS_PLATFORM
    536                         },
    537                     )
    538                 }
    539                 .and_then(|()| {
    540                     ser.serialize_field(RESIDENT_KEY, &self.resident_key)
    541                         .and_then(|()| {
    542                             ser.serialize_field(
    543                                 REQUIRE_RESIDENT_KEY,
    544                                 &matches!(self.resident_key, ResidentKeyRequirement::Required),
    545                             )
    546                             .and_then(|()| {
    547                                 ser.serialize_field(USER_VERIFICATION, &self.user_verification)
    548                                     .and_then(|()| ser.end())
    549                             })
    550                         })
    551                 })
    552             })
    553     }
    554 }
    555 /// Helper that serializes prf registration information to conform with
    556 /// [`AuthenticationExtensionsPRFInputs`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsprfinputs).
    557 struct Prf<'a, 'b>(PrfInput<'a, 'b>);
    558 impl Serialize for Prf<'_, '_> {
    559     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    560     where
    561         S: Serializer,
    562     {
    563         serializer.serialize_struct("Prf", 1).and_then(|mut ser| {
    564             ser.serialize_field("eval", &self.0)
    565                 .and_then(|()| ser.end())
    566         })
    567     }
    568 }
    569 /// `credProps` key name.
    570 const CRED_PROPS: &str = "credProps";
    571 /// `minPinLength` key name.
    572 const MIN_PIN_LENGTH: &str = "minPinLength";
    573 /// `prf` key name.
    574 const PRF: &str = "prf";
    575 /// `credentialProtectionPolicy` key name.
    576 const CREDENTIAL_PROTECTION_POLICY: &str = "credentialProtectionPolicy";
    577 /// `enforceCredentialProtectionPolicy` key name.
    578 const ENFORCE_CREDENTIAL_PROTECTION_POLICY: &str = "enforceCredentialProtectionPolicy";
    579 /// `"userVerificationOptional"`.
    580 const USER_VERIFICATION_OPTIONAL: &str = "userVerificationOptional";
    581 /// `"userVerificationOptionalWithCredentialIDList"`.
    582 const USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST: &str =
    583     "userVerificationOptionalWithCredentialIDList";
    584 /// `"userVerificationRequired"`.
    585 const USER_VERIFICATION_REQUIRED: &str = "userVerificationRequired";
    586 impl Serialize for Extension<'_, '_> {
    587     /// Serializes `self` to conform with
    588     /// [`AuthenticationExtensionsClientInputsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticationextensionsclientinputsjson).
    589     ///
    590     /// # Examples
    591     ///
    592     /// ```
    593     /// # use webauthn_rp::request::{
    594     /// #     register::{CredProtect, Extension, FourToSixtyThree},
    595     /// #     PrfInput, ExtensionInfo, ExtensionReq,
    596     /// # };
    597     /// assert_eq!(serde_json::to_string(&Extension::default())?, r#"{}"#);
    598     /// assert_eq!(
    599     ///     serde_json::to_string(&Extension {
    600     ///         cred_props: Some(ExtensionReq::Allow),
    601     ///         cred_protect: CredProtect::UserVerificationRequired(false, ExtensionInfo::RequireEnforceValue),
    602     ///         min_pin_length: Some((FourToSixtyThree::Sixteen, ExtensionInfo::AllowDontEnforceValue)),
    603     ///         prf: Some((PrfInput { first: [0].as_slice(), second: None, }, ExtensionInfo::AllowEnforceValue))
    604     ///     })?,
    605     ///     r#"{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"AA"}}}"#
    606     /// );
    607     /// # Ok::<_, serde_json::Error>(())
    608     /// ```
    609     #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")]
    610     #[expect(
    611         clippy::arithmetic_side_effects,
    612         reason = "comment explains how overflow is not possible"
    613     )]
    614     #[inline]
    615     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    616     where
    617         S: Serializer,
    618     {
    619         // The max is 1 + 2 + 1 + 1 = 5, so overflow is no concern.
    620         let count = usize::from(self.cred_props.is_some())
    621             + if matches!(self.cred_protect, CredProtect::None) {
    622                 0
    623             } else {
    624                 2
    625             }
    626             + usize::from(self.min_pin_length.is_some())
    627             + usize::from(self.prf.is_some());
    628         serializer
    629             .serialize_struct("Extension", count)
    630             .and_then(|mut ser| {
    631                 self.cred_props
    632                     .map_or(Ok(()), |_| ser.serialize_field(CRED_PROPS, &true))
    633                     .and_then(|()| {
    634                         if matches!(self.cred_protect, CredProtect::None) {
    635                             Ok(())
    636                         } else {
    637                             let enforce_policy;
    638                             // [`credProtect`](https://fidoalliance.org/specs/fido-v2.2-rd-20230321/fido-client-to-authenticator-protocol-v2.2-rd-20230321.html#sctn-credProtect-extension)
    639                             // is serialized by serializing its fields directly and not as a map of fields.
    640                             ser.serialize_field(
    641                                 CREDENTIAL_PROTECTION_POLICY,
    642                                 match self.cred_protect {
    643                                     CredProtect::None => unreachable!(
    644                                         "Extensions is incorrectly serializing credProtect"
    645                                     ),
    646                                     CredProtect::UserVerificationOptional(enforce, _) => {
    647                                         enforce_policy = enforce;
    648                                         USER_VERIFICATION_OPTIONAL
    649                                     }
    650                                     CredProtect::UserVerificationOptionalWithCredentialIdList(
    651                                         enforce,
    652                                         _,
    653                                     ) => {
    654                                         enforce_policy = enforce;
    655                                         USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST
    656                                     }
    657                                     CredProtect::UserVerificationRequired(enforce, _) => {
    658                                         enforce_policy = enforce;
    659                                         USER_VERIFICATION_REQUIRED
    660                                     }
    661                                 },
    662                             )
    663                             .and_then(|()| {
    664                                 ser.serialize_field(
    665                                     ENFORCE_CREDENTIAL_PROTECTION_POLICY,
    666                                     &enforce_policy,
    667                                 )
    668                             })
    669                         }
    670                         .and_then(|()| {
    671                             self.min_pin_length
    672                                 .map_or(Ok(()), |_| ser.serialize_field(MIN_PIN_LENGTH, &true))
    673                                 .and_then(|()| {
    674                                     self.prf
    675                                         .map_or(Ok(()), |(prf, _)| {
    676                                             ser.serialize_field(PRF, &Prf(prf))
    677                                         })
    678                                         .and_then(|()| ser.end())
    679                                 })
    680                         })
    681                     })
    682             })
    683     }
    684 }
    685 /// `"rp"`
    686 const RP: &str = "rp";
    687 /// `"user"`
    688 const USER: &str = "user";
    689 /// `"challenge"`
    690 const CHALLENGE: &str = "challenge";
    691 /// `"pubKeyCredParams"`
    692 const PUB_KEY_CRED_PARAMS: &str = "pubKeyCredParams";
    693 /// `"timeout"`
    694 const TIMEOUT: &str = "timeout";
    695 /// `"excludeCredentials"`
    696 const EXCLUDE_CREDENTIALS: &str = "excludeCredentials";
    697 /// `"authenticatorSelection"`
    698 const AUTHENTICATOR_SELECTION: &str = "authenticatorSelection";
    699 /// `"hints"`
    700 const HINTS: &str = "hints";
    701 /// `"attestation"`
    702 const ATTESTATION: &str = "attestation";
    703 /// `"attestationFormats"`
    704 const ATTESTATION_FORMATS: &str = "attestationFormats";
    705 /// `"extensions"`
    706 const EXTENSIONS: &str = "extensions";
    707 /// "none".
    708 const NONE: &str = "none";
    709 impl<'user_name, 'user_display_name, 'user_id, const USER_LEN: usize> Serialize
    710     for PublicKeyCredentialCreationOptions<
    711         '_,
    712         'user_name,
    713         'user_display_name,
    714         'user_id,
    715         '_,
    716         '_,
    717         USER_LEN,
    718     >
    719 where
    720     PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>: Serialize,
    721 {
    722     /// Serializes `self` to conform with
    723     /// [`PublicKeyCredentialCreationOptionsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptionsjson).
    724     #[inline]
    725     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    726     where
    727         S: Serializer,
    728     {
    729         serializer
    730             .serialize_struct("PublicKeyCredentialCreationOptions", 11)
    731             .and_then(|mut ser| {
    732                 ser.serialize_field(RP, &PublicKeyCredentialRpEntity(self.rp_id))
    733                     .and_then(|()| {
    734                         ser.serialize_field(USER, &self.user).and_then(|()| {
    735                             ser.serialize_field(CHALLENGE, &self.challenge)
    736                                 .and_then(|()| {
    737                                     ser.serialize_field(
    738                                         PUB_KEY_CRED_PARAMS,
    739                                         &self.pub_key_cred_params,
    740                                     )
    741                                     .and_then(|()| {
    742                                         ser.serialize_field(TIMEOUT, &self.timeout).and_then(
    743                                             |()| {
    744                                                 ser.serialize_field(
    745                                                     EXCLUDE_CREDENTIALS,
    746                                                     self.exclude_credentials.as_slice(),
    747                                                 )
    748                                                 .and_then(|()| {
    749                                                     ser.serialize_field(
    750                                                         AUTHENTICATOR_SELECTION,
    751                                                         &self.authenticator_selection,
    752                                                     )
    753                                                     .and_then(|()| {
    754                                                         ser.serialize_field(HINTS, &match self.authenticator_selection.authenticator_attachment {
    755                                                             AuthenticatorAttachmentReq::None(hint) => hint,
    756                                                             AuthenticatorAttachmentReq::Platform(hint) => hint.into(),
    757                                                             AuthenticatorAttachmentReq::CrossPlatform(hint) => hint.into(),
    758                                                         }).and_then(|()| {
    759                                                             ser.serialize_field(ATTESTATION, NONE).and_then(|()| {
    760                                                                 ser.serialize_field(ATTESTATION_FORMATS, [NONE].as_slice()).and_then(|()| {
    761                                                                     ser.serialize_field(EXTENSIONS, &self.extensions).and_then(|()| ser.end())
    762                                                                 })
    763                                                             })
    764                                                         })
    765                                                     })
    766                                                 })
    767                                             },
    768                                         )
    769                                     })
    770                                 })
    771                         })
    772                     })
    773             })
    774     }
    775 }
    776 /// `"mediation"`.
    777 const MEDIATION: &str = "mediation";
    778 /// `"publicKey"`.
    779 const PUBLIC_KEY_NO_HYPEN: &str = "publicKey";
    780 impl<
    781     'rp_id,
    782     'user_name,
    783     'user_display_name,
    784     'user_id,
    785     'prf_first,
    786     'prf_second,
    787     const USER_LEN: usize,
    788 > Serialize
    789     for CredentialCreationOptions<
    790         'rp_id,
    791         'user_name,
    792         'user_display_name,
    793         'user_id,
    794         'prf_first,
    795         'prf_second,
    796         USER_LEN,
    797     >
    798 where
    799     PublicKeyCredentialCreationOptions<
    800         'rp_id,
    801         'user_name,
    802         'user_display_name,
    803         'user_id,
    804         'prf_first,
    805         'prf_second,
    806         USER_LEN,
    807     >: Serialize,
    808 {
    809     /// Serializes `self` to conform with
    810     /// [`CredentialCreationOptions`](https://www.w3.org/TR/credential-management-1/#dictdef-credentialcreationoptions).
    811     ///
    812     /// Note [`signal`](https://www.w3.org/TR/credential-management-1/#dom-credentialcreationoptions-signal)
    813     /// is not present, and [`publicKey`](https://www.w3.org/TR/credential-management-1/#sctn-cred-type-registry)
    814     /// is serialized according to [`PublicKeyCredentialCreationOptions::serialize`].
    815     #[inline]
    816     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    817     where
    818         S: Serializer,
    819     {
    820         serializer
    821             .serialize_struct("CredentialCreationOptions", 2)
    822             .and_then(|mut ser| {
    823                 ser.serialize_field(MEDIATION, &self.mediation)
    824                     .and_then(|()| {
    825                         ser.serialize_field(PUBLIC_KEY_NO_HYPEN, &self.public_key)
    826                             .and_then(|()| ser.end())
    827                     })
    828             })
    829     }
    830 }
    831 impl<
    832     'rp_id,
    833     'user_name,
    834     'user_display_name,
    835     'user_id,
    836     'prf_first,
    837     'prf_second,
    838     const USER_LEN: usize,
    839 > Serialize
    840     for RegistrationClientState<
    841         'rp_id,
    842         'user_name,
    843         'user_display_name,
    844         'user_id,
    845         'prf_first,
    846         'prf_second,
    847         USER_LEN,
    848     >
    849 where
    850     CredentialCreationOptions<
    851         'rp_id,
    852         'user_name,
    853         'user_display_name,
    854         'user_id,
    855         'prf_first,
    856         'prf_second,
    857         USER_LEN,
    858     >: Serialize,
    859 {
    860     /// Serializes `self` according to [`CredentialCreationOptions::serialize`].
    861     ///
    862     /// # Examples
    863     ///
    864     /// ```
    865     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    866     /// # use webauthn_rp::{bin::Decode, response::bin::DecodeAuthTransportsErr};
    867     /// # use webauthn_rp::{
    868     /// #     request::{
    869     /// #         register::{
    870     /// #             FourToSixtyThree, UserHandle64, AuthenticatorAttachmentReq, CredentialCreationOptions, PublicKeyCredentialUserEntity, UserHandle
    871     /// #         },
    872     /// #         AsciiDomain, ExtensionInfo, Hint, RpId, PublicKeyCredentialDescriptor, Credentials, UserVerificationRequirement,
    873     /// #     },
    874     /// #     response::{AuthTransports, CredentialId},
    875     /// # };
    876     /// /// Retrieves the `AuthTransports` associated with the unique `cred_id`
    877     /// /// from the database.
    878     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    879     /// fn get_transports(cred_id: CredentialId<&[u8]>) -> Result<AuthTransports, DecodeAuthTransportsErr> {
    880     ///     // ⋮
    881     /// #     AuthTransports::decode(32)
    882     /// }
    883     /// let mut creds = Vec::with_capacity(1);
    884     /// // `CredentialId::try_from` only exists when `custom` is enabled; and even then, it is
    885     /// // likely never needed since the `CredentialId` was originally sent from the client and is likely
    886     /// // stored in a database which would be fetched by `UserHandle` or `Authentication::raw_id`.
    887     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    888     /// let id = CredentialId::try_from(vec![0; 16].into_boxed_slice())?;
    889     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    890     /// let transports = get_transports((&id).into())?;
    891     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    892     /// creds.push(PublicKeyCredentialDescriptor { id, transports });
    893     /// let rp_id = RpId::Domain(AsciiDomain::try_from("example.com".to_owned())?);
    894     /// let user_handle = UserHandle64::new();
    895     /// let mut options = CredentialCreationOptions::passkey(&rp_id, PublicKeyCredentialUserEntity { name: "pierre.de.fermat".try_into()?, id: &user_handle, display_name: "Pierre de Fermat".try_into()?, }, creds);
    896     /// options.public_key.authenticator_selection.authenticator_attachment = AuthenticatorAttachmentReq::None(Hint::SecurityKey);
    897     /// options.public_key.extensions.min_pin_length = Some((FourToSixtyThree::Sixteen, ExtensionInfo::RequireEnforceValue));
    898     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    899     /// let client_state = serde_json::to_string(&options.start_ceremony()?.1).unwrap_or_else(|_e| unreachable!("bug in RegistrationClientState::serialize"));
    900     /// let json = serde_json::json!({
    901     ///     "mediation":"required",
    902     ///     "publicKey":{
    903     ///         "rp":{
    904     ///             "name":"example.com",
    905     ///             "id":"example.com"
    906     ///         },
    907     ///         "user":{
    908     ///             "name":"pierre.de.fermat",
    909     ///             "id":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
    910     ///             "displayName":"Pierre de Fermat"
    911     ///         },
    912     ///         "challenge":"AAAAAAAAAAAAAAAAAAAAAA",
    913     ///         "pubKeyCredParams":[
    914     ///             {
    915     ///                 "type":"public-key",
    916     ///                 "alg":-50
    917     ///             },
    918     ///             {
    919     ///                 "type":"public-key",
    920     ///                 "alg":-49
    921     ///             },
    922     ///             {
    923     ///                 "type":"public-key",
    924     ///                 "alg":-48
    925     ///             },
    926     ///             {
    927     ///                 "type":"public-key",
    928     ///                 "alg":-8
    929     ///             },
    930     ///             {
    931     ///                 "type":"public-key",
    932     ///                 "alg":-7
    933     ///             },
    934     ///             {
    935     ///                 "type":"public-key",
    936     ///                 "alg":-35
    937     ///             },
    938     ///             {
    939     ///                 "type":"public-key",
    940     ///                 "alg":-257
    941     ///             },
    942     ///         ],
    943     ///         "timeout":300000,
    944     ///         "excludeCredentials":[
    945     ///             {
    946     ///                 "type":"public-key",
    947     ///                 "id":"AAAAAAAAAAAAAAAAAAAAAA",
    948     ///                 "transports":["usb"]
    949     ///             }
    950     ///         ],
    951     ///         "authenticatorSelection":{
    952     ///             "residentKey":"required",
    953     ///             "requireResidentKey":true,
    954     ///             "userVerification":"required"
    955     ///         },
    956     ///         "hints":[
    957     ///             "security-key"
    958     ///         ],
    959     ///         "attestation":"none",
    960     ///         "attestationFormats":[
    961     ///             "none"
    962     ///         ],
    963     ///         "extensions":{
    964     ///             "credentialProtectionPolicy":"userVerificationRequired",
    965     ///             "enforceCredentialProtectionPolicy":false,
    966     ///             "minPinLength":true
    967     ///         }
    968     ///     }
    969     /// }).to_string();
    970     /// // Since `Challenge`s are randomly generated, we don't know what it will be.
    971     /// // Similarly since we randomly generated a 64-byte `UserHandle`, we don't know what
    972     /// // it will be; thus we test the JSON string for everything except those two.
    973     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    974     /// assert_eq!(client_state.get(..124), json.get(..124));
    975     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    976     /// assert_eq!(client_state.get(210..259), json.get(210..259));
    977     /// # #[cfg(all(feature = "bin", feature = "custom"))]
    978     /// assert_eq!(client_state.get(281..), json.get(281..));
    979     /// # Ok::<_, webauthn_rp::AggErr>(())
    980     /// ```
    981     #[inline]
    982     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    983     where
    984         S: Serializer,
    985     {
    986         self.0.serialize(serializer)
    987     }
    988 }
    989 impl<'de: 'a, 'a> Deserialize<'de> for Nickname<'a> {
    990     /// Deserializes [`prim@str`] and parses it according to [`Self::try_from`].
    991     ///
    992     /// # Examples
    993     ///
    994     /// ```
    995     /// # use webauthn_rp::request::register::Nickname;
    996     /// assert_eq!(
    997     ///     serde_json::from_str::<Nickname>(r#""Henri Poincaré""#)?.as_ref(),
    998     ///     "Henri Poincaré"
    999     /// );
   1000     /// # Ok::<_, serde_json::Error>(())
   1001     ///```
   1002     #[inline]
   1003     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1004     where
   1005         D: Deserializer<'de>,
   1006     {
   1007         /// `Visitor` for `Nickname`.
   1008         struct NicknameVisitor<'b>(PhantomData<fn() -> &'b ()>);
   1009         impl<'d: 'b, 'b> Visitor<'d> for NicknameVisitor<'b> {
   1010             type Value = Nickname<'b>;
   1011             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1012                 formatter.write_str("Nickname")
   1013             }
   1014             fn visit_borrowed_str<E>(self, v: &'d str) -> Result<Self::Value, E>
   1015             where
   1016                 E: Error,
   1017             {
   1018                 Nickname::try_from(v).map_err(E::custom)
   1019             }
   1020             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   1021             where
   1022                 E: Error,
   1023             {
   1024                 Nickname::try_from(v)
   1025                     .map_err(E::custom)
   1026                     .map(|name| Nickname(Cow::Owned(name.0.into_owned())))
   1027             }
   1028         }
   1029         deserializer.deserialize_str(NicknameVisitor(PhantomData))
   1030     }
   1031 }
   1032 impl<'de: 'a, 'a> Deserialize<'de> for DisplayName<'a> {
   1033     /// Deserializes [`prim@str`] and parses it according to [`Self::try_from`].
   1034     ///
   1035     /// # Examples
   1036     ///
   1037     /// ```
   1038     /// # use webauthn_rp::request::register::DisplayName;
   1039     /// assert_eq!(
   1040     ///     serde_json::from_str::<DisplayName>(r#""Alexander Grothendieck""#)?.as_ref(),
   1041     ///     "Alexander Grothendieck"
   1042     /// );
   1043     /// # Ok::<_, serde_json::Error>(())
   1044     ///```
   1045     #[inline]
   1046     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1047     where
   1048         D: Deserializer<'de>,
   1049     {
   1050         /// `Visitor` for `DisplayName`.
   1051         struct DisplayNameVisitor<'b>(PhantomData<fn() -> &'b ()>);
   1052         impl<'d: 'b, 'b> Visitor<'d> for DisplayNameVisitor<'b> {
   1053             type Value = DisplayName<'b>;
   1054             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1055                 formatter.write_str("DisplayName")
   1056             }
   1057             fn visit_borrowed_str<E>(self, v: &'d str) -> Result<Self::Value, E>
   1058             where
   1059                 E: Error,
   1060             {
   1061                 DisplayName::try_from(v).map_err(E::custom)
   1062             }
   1063             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   1064             where
   1065                 E: Error,
   1066             {
   1067                 if v.is_empty() {
   1068                     Ok(DisplayName::Blank)
   1069                 } else {
   1070                     Nickname::try_from(v).map_err(E::custom).map(|name| {
   1071                         DisplayName::Nickname(Nickname(Cow::Owned(name.0.into_owned())))
   1072                     })
   1073                 }
   1074             }
   1075         }
   1076         deserializer.deserialize_str(DisplayNameVisitor(PhantomData))
   1077     }
   1078 }
   1079 impl<'de: 'a, 'a> Deserialize<'de> for Username<'a> {
   1080     /// Deserializes [`prim@str`] and parses it according to [`Self::try_from`].
   1081     ///
   1082     /// # Examples
   1083     ///
   1084     /// ```
   1085     /// # use webauthn_rp::request::register::Username;
   1086     /// assert_eq!(
   1087     ///     serde_json::from_str::<Username>(r#""augustin.cauchy""#)?.as_ref(),
   1088     ///     "augustin.cauchy"
   1089     /// );
   1090     /// # Ok::<_, serde_json::Error>(())
   1091     ///```
   1092     #[inline]
   1093     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1094     where
   1095         D: Deserializer<'de>,
   1096     {
   1097         /// `Visitor` for `Username`.
   1098         struct UsernameVisitor<'b>(PhantomData<fn() -> &'b ()>);
   1099         impl<'d: 'b, 'b> Visitor<'d> for UsernameVisitor<'b> {
   1100             type Value = Username<'b>;
   1101             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1102                 formatter.write_str("Username")
   1103             }
   1104             fn visit_borrowed_str<E>(self, v: &'d str) -> Result<Self::Value, E>
   1105             where
   1106                 E: Error,
   1107             {
   1108                 Username::try_from(v).map_err(E::custom)
   1109             }
   1110             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   1111             where
   1112                 E: Error,
   1113             {
   1114                 Username::try_from(v)
   1115                     .map_err(E::custom)
   1116                     .map(|name| Username(Cow::Owned(name.0.into_owned())))
   1117             }
   1118         }
   1119         deserializer.deserialize_str(UsernameVisitor(PhantomData))
   1120     }
   1121 }
   1122 impl<'de, const LEN: usize> Deserialize<'de> for UserHandle<LEN>
   1123 where
   1124     Self: Default,
   1125 {
   1126     /// Deserializes [`prim@str`] based on
   1127     /// [`userHandle`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorassertionresponsejson-userhandle).
   1128     ///
   1129     /// # Examples
   1130     ///
   1131     /// ```
   1132     /// # use webauthn_rp::request::register::{UserHandle, USER_HANDLE_MIN_LEN};
   1133     /// # #[cfg(feature = "custom")]
   1134     /// assert_eq!(
   1135     ///     serde_json::from_str::<UserHandle<USER_HANDLE_MIN_LEN>>(r#""AA""#)?,
   1136     ///     UserHandle::from([0])
   1137     /// );
   1138     /// # Ok::<_, serde_json::Error>(())
   1139     ///```
   1140     #[inline]
   1141     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1142     where
   1143         D: Deserializer<'de>,
   1144     {
   1145         /// `Visitor` for `UserHandle`.
   1146         struct UserHandleVisitor<const L: usize>;
   1147         impl<const L: usize> Visitor<'_> for UserHandleVisitor<L>
   1148         where
   1149             UserHandle<L>: Default,
   1150         {
   1151             type Value = UserHandle<L>;
   1152             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1153                 formatter.write_str("UserHandle")
   1154             }
   1155             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   1156             where
   1157                 E: Error,
   1158             {
   1159                 if base64url_nopad::encode_len(L) == v.len() {
   1160                     let mut data = [0; L];
   1161                     base64url_nopad::decode_buffer_exact(v.as_bytes(), data.as_mut_slice())
   1162                         .map_err(E::custom)
   1163                         .map(|()| UserHandle(data))
   1164                 } else {
   1165                     Err(E::invalid_value(
   1166                         Unexpected::Str(v),
   1167                         &format!("{L} bytes encoded in base64url without padding").as_str(),
   1168                     ))
   1169                 }
   1170             }
   1171         }
   1172         deserializer.deserialize_str(UserHandleVisitor)
   1173     }
   1174 }
   1175 impl<'de> Deserialize<'de> for CoseAlgorithmIdentifier {
   1176     /// Deserializes [`i16`] based on
   1177     /// [COSE Algorithms](https://www.iana.org/assignments/cose/cose.xhtml#algorithms).
   1178     #[inline]
   1179     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1180     where
   1181         D: Deserializer<'de>,
   1182     {
   1183         /// `Visitor` for `CoseAlgorithmIdentifier`.
   1184         ///
   1185         /// We visit all signed integral types sans `i8` just in case a `Deserializer` only implements one of them.
   1186         struct CoseAlgorithmIdentifierVisitor;
   1187         impl Visitor<'_> for CoseAlgorithmIdentifierVisitor {
   1188             type Value = CoseAlgorithmIdentifier;
   1189             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1190                 formatter.write_str("CoseAlgorithmIdentifier")
   1191             }
   1192             fn visit_i16<E>(self, v: i16) -> Result<Self::Value, E>
   1193             where
   1194                 E: Error,
   1195             {
   1196                 match v {
   1197                     RS256 => Ok(CoseAlgorithmIdentifier::Rs256),
   1198                     MLDSA87 => Ok(CoseAlgorithmIdentifier::Mldsa87),
   1199                     MLDSA65 => Ok(CoseAlgorithmIdentifier::Mldsa65),
   1200                     MLDSA44 => Ok(CoseAlgorithmIdentifier::Mldsa44),
   1201                     ES384 => Ok(CoseAlgorithmIdentifier::Es384),
   1202                     EDDSA => Ok(CoseAlgorithmIdentifier::Eddsa),
   1203                     ES256 => Ok(CoseAlgorithmIdentifier::Es256),
   1204                     _ => Err(E::invalid_value(
   1205                         Unexpected::Signed(i64::from(v)),
   1206                         &format!(
   1207                             "{MLDSA87}, {MLDSA65}, {MLDSA44}, {EDDSA}, {ES256}, {ES384}, or {RS256}"
   1208                         )
   1209                         .as_str(),
   1210                     )),
   1211                 }
   1212             }
   1213             fn visit_i32<E>(self, v: i32) -> Result<Self::Value, E>
   1214             where
   1215                 E: Error,
   1216             {
   1217                 i16::try_from(v)
   1218                     .map_err(E::custom)
   1219                     .and_then(|val| self.visit_i16(val))
   1220             }
   1221             fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
   1222             where
   1223                 E: Error,
   1224             {
   1225                 i16::try_from(v)
   1226                     .map_err(E::custom)
   1227                     .and_then(|val| self.visit_i16(val))
   1228             }
   1229         }
   1230         deserializer.deserialize_i16(CoseAlgorithmIdentifierVisitor)
   1231     }
   1232 }
   1233 /// Helper to deserialize `PublicKeyCredentialRpEntity` with an optional `RpId`.
   1234 ///
   1235 /// Used in [`ClientCredentialCreationOptions::deserialize`].
   1236 struct PublicKeyCredentialRpEntityHelper(Option<RpId>);
   1237 impl<'de> Deserialize<'de> for PublicKeyCredentialRpEntityHelper {
   1238     /// Conforms to the following schema:
   1239     ///
   1240     /// ```json
   1241     /// {
   1242     ///   "id": null | <RpId>,
   1243     ///   "name": null | "" | <RpId> | <Nickname>
   1244     /// }
   1245     /// ```
   1246     ///
   1247     /// None of the fields are required, and missing fields are interpreted the same as fields
   1248     /// with `null` values.
   1249     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1250     where
   1251         D: Deserializer<'de>,
   1252     {
   1253         /// `Visitor` for `PublicKeyCredentialRpEntityHelper`.
   1254         struct PublicKeyCredentialRpEntityHelperVisitor;
   1255         impl<'d> Visitor<'d> for PublicKeyCredentialRpEntityHelperVisitor {
   1256             type Value = PublicKeyCredentialRpEntityHelper;
   1257             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1258                 formatter.write_str("PublicKeyCredentialRpEntityHelper")
   1259             }
   1260             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
   1261             where
   1262                 A: MapAccess<'d>,
   1263             {
   1264                 /// Field for `PublicKeyCredentialRpEntityHelper`.
   1265                 enum Field {
   1266                     /// `id`.
   1267                     Id,
   1268                     /// `name`.
   1269                     Name,
   1270                 }
   1271                 impl<'e> Deserialize<'e> for Field {
   1272                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1273                     where
   1274                         D: Deserializer<'e>,
   1275                     {
   1276                         /// `Visitor` for `Field`.
   1277                         struct FieldVisitor;
   1278                         impl Visitor<'_> for FieldVisitor {
   1279                             type Value = Field;
   1280                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1281                                 write!(formatter, "'{ID}' or '{NAME}'")
   1282                             }
   1283                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   1284                             where
   1285                                 E: Error,
   1286                             {
   1287                                 match v {
   1288                                     ID => Ok(Field::Id),
   1289                                     NAME => Ok(Field::Name),
   1290                                     _ => Err(E::unknown_field(v, FIELDS)),
   1291                                 }
   1292                             }
   1293                         }
   1294                         deserializer.deserialize_identifier(FieldVisitor)
   1295                     }
   1296                 }
   1297                 /// Helper to deserialize `name`.
   1298                 struct Name;
   1299                 impl<'e> Deserialize<'e> for Name {
   1300                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1301                     where
   1302                         D: Deserializer<'e>,
   1303                     {
   1304                         /// `Visitor` for `Name`.
   1305                         struct NameVisitor;
   1306                         impl Visitor<'_> for NameVisitor {
   1307                             type Value = Name;
   1308                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1309                                 formatter.write_str("RpId name")
   1310                             }
   1311                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   1312                             where
   1313                                 E: Error,
   1314                             {
   1315                                 if v.is_empty() {
   1316                                     Ok(Name)
   1317                                 } else {
   1318                                     Nickname::try_from(v).map(|_n| Name).or_else(|_e| {
   1319                                         RpId::try_from(v.to_owned())
   1320                                             .map_err(E::custom)
   1321                                             .map(|_r| Name)
   1322                                     })
   1323                                 }
   1324                             }
   1325                         }
   1326                         deserializer.deserialize_str(NameVisitor)
   1327                     }
   1328                 }
   1329                 let mut id = None;
   1330                 let mut name = false;
   1331                 while let Some(key) = map.next_key()? {
   1332                     match key {
   1333                         Field::Id => {
   1334                             if id.is_some() {
   1335                                 return Err(Error::duplicate_field(ID));
   1336                             }
   1337                             id = map.next_value::<Option<_>>().map(Some)?;
   1338                         }
   1339                         Field::Name => {
   1340                             if name {
   1341                                 return Err(Error::duplicate_field(NAME));
   1342                             }
   1343                             name = map.next_value::<Option<Name>>().map(|_n| true)?;
   1344                         }
   1345                     }
   1346                 }
   1347                 Ok(PublicKeyCredentialRpEntityHelper(id.flatten()))
   1348             }
   1349         }
   1350         /// Fields for `PublicKeyCredentialRpEntityHelper`.
   1351         const FIELDS: &[&str; 2] = &[ID, NAME];
   1352         deserializer.deserialize_struct(
   1353             "PublicKeyCredentialRpEntityHelper",
   1354             FIELDS,
   1355             PublicKeyCredentialRpEntityHelperVisitor,
   1356         )
   1357     }
   1358 }
   1359 /// Similar to [`PublicKeyCredentialUserEntity`] except the [`UserHandle`] is owned, and all fields are
   1360 /// optional.
   1361 ///
   1362 /// This is primarily useful to assist [`ClientCredentialCreationOptions::deserialize`].
   1363 #[derive(Debug, Default)]
   1364 pub struct PublicKeyCredentialUserEntityOwned<'name, 'display_name, const LEN: usize> {
   1365     /// See [`PublicKeyCredentialUserEntity::name`].
   1366     pub name: Option<Username<'name>>,
   1367     /// See [`PublicKeyCredentialUserEntity::id`].
   1368     pub id: Option<UserHandle<LEN>>,
   1369     /// See [`PublicKeyCredentialUserEntity::display_name`].
   1370     pub display_name: Option<DisplayName<'display_name>>,
   1371 }
   1372 /// Error returned when converting a [`PublicKeyCredentialUserEntityOwned`] into a
   1373 /// [`PublicKeyCredentialUserEntity`] (e.g., via [`PublicKeyCredentialUserEntityOwned::with_id`]).
   1374 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   1375 pub enum PublicKeyCredentialUserEntityOwnedErr {
   1376     /// Variant returned when [`PublicKeyCredentialUserEntityOwned::name`] is `None`.
   1377     MissingName,
   1378     /// Variant returned when [`PublicKeyCredentialUserEntityOwned::id`] is `None`.
   1379     MissingId,
   1380     /// Variant returned when [`PublicKeyCredentialUserEntityOwned::display_name`] is `None`.
   1381     MissingDisplayName,
   1382 }
   1383 impl Display for PublicKeyCredentialUserEntityOwnedErr {
   1384     #[inline]
   1385     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   1386         f.write_str(match *self {
   1387             Self::MissingName => "user entity info did not have a username",
   1388             Self::MissingId => "user entity info did not have a user handle",
   1389             Self::MissingDisplayName => "user entity info did not have a user display name",
   1390         })
   1391     }
   1392 }
   1393 impl E for PublicKeyCredentialUserEntityOwnedErr {}
   1394 impl<const LEN: usize> PublicKeyCredentialUserEntityOwned<'_, '_, LEN> {
   1395     /// Returns a `PublicKeyCredentialUserEntity` based on `self`.
   1396     ///
   1397     /// # Errors
   1398     ///
   1399     /// Errors iff any of the fields in `self` are `None`.
   1400     #[inline]
   1401     pub fn as_entity(
   1402         &self,
   1403     ) -> Result<PublicKeyCredentialUserEntity<'_, '_, '_, LEN>, PublicKeyCredentialUserEntityOwnedErr>
   1404     {
   1405         self.name
   1406             .as_ref()
   1407             .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingName)
   1408             .and_then(|username| {
   1409                 self.id
   1410                     .as_ref()
   1411                     .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingId)
   1412                     .and_then(|id| {
   1413                         self.display_name
   1414                             .as_ref()
   1415                             .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingDisplayName)
   1416                             .map(|display| PublicKeyCredentialUserEntity {
   1417                                 name: username.into(),
   1418                                 id,
   1419                                 display_name: display.into(),
   1420                             })
   1421                     })
   1422             })
   1423     }
   1424     /// Returns a `PublicKeyCredentialUserEntity` based on `self` and `id`.
   1425     ///
   1426     /// Note `id` is used _unconditionally_ regardless if [`Self::id`] is `Some`.
   1427     ///
   1428     /// # Errors
   1429     ///
   1430     /// Errors iff [`Self::name`] or [`Self::display_name`] are `None`.
   1431     #[inline]
   1432     pub fn with_id<'id>(
   1433         &self,
   1434         id: &'id UserHandle<LEN>,
   1435     ) -> Result<
   1436         PublicKeyCredentialUserEntity<'_, '_, 'id, LEN>,
   1437         PublicKeyCredentialUserEntityOwnedErr,
   1438     > {
   1439         self.name
   1440             .as_ref()
   1441             .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingName)
   1442             .and_then(|username| {
   1443                 self.display_name
   1444                     .as_ref()
   1445                     .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingDisplayName)
   1446                     .map(|display| PublicKeyCredentialUserEntity {
   1447                         name: username.into(),
   1448                         id,
   1449                         display_name: display.into(),
   1450                     })
   1451             })
   1452     }
   1453     /// Returns a `PublicKeyCredentialUserEntity` based on `self`, `name`, and `display_name`.
   1454     ///
   1455     /// Note `name` and `display_name` are used _unconditionally_ regardless if [`Self::name`] or
   1456     /// [`Self::display_name`] are `Some`.
   1457     ///
   1458     /// # Errors
   1459     ///
   1460     /// Errors iff [`Self::id`] is `None`.
   1461     #[inline]
   1462     pub fn with_name_and_display_name<'name, 'display_name>(
   1463         &self,
   1464         name: Username<'name>,
   1465         display_name: DisplayName<'display_name>,
   1466     ) -> Result<
   1467         PublicKeyCredentialUserEntity<'name, 'display_name, '_, LEN>,
   1468         PublicKeyCredentialUserEntityOwnedErr,
   1469     > {
   1470         self.id
   1471             .as_ref()
   1472             .ok_or(PublicKeyCredentialUserEntityOwnedErr::MissingId)
   1473             .map(|id| PublicKeyCredentialUserEntity {
   1474                 name,
   1475                 id,
   1476                 display_name,
   1477             })
   1478     }
   1479 }
   1480 impl<'de: 'name + 'display_name, 'name, 'display_name, const LEN: usize> Deserialize<'de>
   1481     for PublicKeyCredentialUserEntityOwned<'name, 'display_name, LEN>
   1482 where
   1483     UserHandle<LEN>: Deserialize<'de>,
   1484 {
   1485     /// Deserializes a `struct` according to
   1486     /// [`PublicKeyCredentialUserEntityJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialuserentityjson).
   1487     ///
   1488     /// Note none of the fields are required and all of them are allowed to be `null`.
   1489     /// [`id`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-id) is deserialized
   1490     /// according to [`UserHandle::deserialize`],
   1491     /// [`name`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-name) is deserialized
   1492     /// according to [`Username::deserialize`], and
   1493     /// [`displayName`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialuserentityjson-displayname) is
   1494     /// deserialized according to [`DisplayName::deserialize`].
   1495     ///
   1496     /// Unknown or duplicate fields lead to an error. Missing fields are interpreted the same as if the field
   1497     /// were assigned `null`.
   1498     ///
   1499     /// # Examples
   1500     ///
   1501     /// ```
   1502     /// # use webauthn_rp::request::register::ser::PublicKeyCredentialUserEntityOwned;
   1503     /// let val = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 16>>(r#"{"name":"paul.erdos","displayName":"Erdős Pál"}"#)?;
   1504     /// assert!(val.name.is_some_and(|name| name.as_ref() == "paul.erdos"));
   1505     /// assert!(val.display_name.is_some_and(|display| display.as_ref() == "Erdős Pál"));
   1506     /// assert!(val.id.is_none());
   1507     /// # Ok::<_, serde_json::Error>(())
   1508     /// ```
   1509     #[inline]
   1510     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1511     where
   1512         D: Deserializer<'de>,
   1513     {
   1514         /// `Visitor` for `PublicKeyCredentialUserEntityOwned`.
   1515         struct PublicKeyCredentialUserEntityOwnedVisitor<'a, 'b, const L: usize>(
   1516             PhantomData<fn() -> (&'a (), &'b ())>,
   1517         );
   1518         impl<'d: 'a + 'b, 'a, 'b, const L: usize> Visitor<'d>
   1519             for PublicKeyCredentialUserEntityOwnedVisitor<'a, 'b, L>
   1520         where
   1521             UserHandle<L>: Deserialize<'d>,
   1522         {
   1523             type Value = PublicKeyCredentialUserEntityOwned<'a, 'b, L>;
   1524             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1525                 formatter.write_str("PublicKeyCredentialUserEntityOwned")
   1526             }
   1527             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
   1528             where
   1529                 A: MapAccess<'d>,
   1530             {
   1531                 /// Field for `PublicKeyCredentialUserEntityOwned`.
   1532                 enum Field {
   1533                     /// `id`.
   1534                     Id,
   1535                     /// `name`.
   1536                     Name,
   1537                     /// `displayName`
   1538                     DisplayName,
   1539                 }
   1540                 impl<'e> Deserialize<'e> for Field {
   1541                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1542                     where
   1543                         D: Deserializer<'e>,
   1544                     {
   1545                         /// `Visitor` for `Field`.
   1546                         struct FieldVisitor;
   1547                         impl Visitor<'_> for FieldVisitor {
   1548                             type Value = Field;
   1549                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1550                                 write!(formatter, "'{ID}', '{NAME}', or '{DISPLAY_NAME}'")
   1551                             }
   1552                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   1553                             where
   1554                                 E: Error,
   1555                             {
   1556                                 match v {
   1557                                     ID => Ok(Field::Id),
   1558                                     NAME => Ok(Field::Name),
   1559                                     DISPLAY_NAME => Ok(Field::DisplayName),
   1560                                     _ => Err(E::unknown_field(v, FIELDS)),
   1561                                 }
   1562                             }
   1563                         }
   1564                         deserializer.deserialize_identifier(FieldVisitor)
   1565                     }
   1566                 }
   1567                 let mut user_handle = None;
   1568                 let mut username = None;
   1569                 let mut display = None;
   1570                 while let Some(key) = map.next_key()? {
   1571                     match key {
   1572                         Field::Id => {
   1573                             if user_handle.is_some() {
   1574                                 return Err(Error::duplicate_field(ID));
   1575                             }
   1576                             user_handle = map.next_value::<Option<_>>().map(Some)?;
   1577                         }
   1578                         Field::Name => {
   1579                             if username.is_some() {
   1580                                 return Err(Error::duplicate_field(NAME));
   1581                             }
   1582                             username = map.next_value::<Option<_>>().map(Some)?;
   1583                         }
   1584                         Field::DisplayName => {
   1585                             if display.is_some() {
   1586                                 return Err(Error::duplicate_field(DISPLAY_NAME));
   1587                             }
   1588                             display = map.next_value::<Option<_>>().map(Some)?;
   1589                         }
   1590                     }
   1591                 }
   1592                 Ok(PublicKeyCredentialUserEntityOwned {
   1593                     id: user_handle.flatten(),
   1594                     name: username.flatten(),
   1595                     display_name: display.flatten(),
   1596                 })
   1597             }
   1598         }
   1599         /// Fields for `PublicKeyCredentialUserEntityOwned`.
   1600         const FIELDS: &[&str; 3] = &[ID, NAME, DISPLAY_NAME];
   1601         deserializer.deserialize_struct(
   1602             "PublicKeyCredentialUserEntityOwned",
   1603             FIELDS,
   1604             PublicKeyCredentialUserEntityOwnedVisitor(PhantomData),
   1605         )
   1606     }
   1607 }
   1608 /// `newtype` around `CoseAlgorithmIdentifier`.
   1609 struct PubParam(CoseAlgorithmIdentifier);
   1610 impl<'de> Deserialize<'de> for PubParam {
   1611     /// Conforms to the following schema:
   1612     ///
   1613     /// ```json
   1614     /// {
   1615     ///   "alg": <CoseAlgorithmIdentifier>,
   1616     ///   "type": "public-key",
   1617     /// }
   1618     /// ```
   1619     ///
   1620     /// `"alg"` is required.
   1621     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1622     where
   1623         D: Deserializer<'de>,
   1624     {
   1625         /// `Visitor` for `PubParam`.
   1626         struct PubParamVisitor;
   1627         impl<'d> Visitor<'d> for PubParamVisitor {
   1628             type Value = PubParam;
   1629             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1630                 formatter.write_str("PubParam")
   1631             }
   1632             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
   1633             where
   1634                 A: MapAccess<'d>,
   1635             {
   1636                 /// Field for `PubParam`.
   1637                 enum Field {
   1638                     /// `"type"`.
   1639                     Type,
   1640                     /// `"alg"`.
   1641                     Alg,
   1642                 }
   1643                 impl<'e> Deserialize<'e> for Field {
   1644                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1645                     where
   1646                         D: Deserializer<'e>,
   1647                     {
   1648                         /// `Visitor` for `Field`.
   1649                         struct FieldVisitor;
   1650                         impl Visitor<'_> for FieldVisitor {
   1651                             type Value = Field;
   1652                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1653                                 write!(formatter, "'{TYPE}' or '{ALG}'")
   1654                             }
   1655                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   1656                             where
   1657                                 E: Error,
   1658                             {
   1659                                 match v {
   1660                                     TYPE => Ok(Field::Type),
   1661                                     ALG => Ok(Field::Alg),
   1662                                     _ => Err(E::unknown_field(v, FIELDS)),
   1663                                 }
   1664                             }
   1665                         }
   1666                         deserializer.deserialize_identifier(FieldVisitor)
   1667                     }
   1668                 }
   1669                 let mut typ = false;
   1670                 let mut alg = None;
   1671                 while let Some(key) = map.next_key()? {
   1672                     match key {
   1673                         Field::Type => {
   1674                             if typ {
   1675                                 return Err(Error::duplicate_field(TYPE));
   1676                             }
   1677                             typ = map.next_value::<Type>().map(|_t| true)?;
   1678                         }
   1679                         Field::Alg => {
   1680                             if alg.is_some() {
   1681                                 return Err(Error::duplicate_field(ALG));
   1682                             }
   1683                             alg = map.next_value().map(Some)?;
   1684                         }
   1685                     }
   1686                 }
   1687                 alg.ok_or_else(|| Error::missing_field(ALG)).map(PubParam)
   1688             }
   1689         }
   1690         /// Fields for `PubParam`.
   1691         const FIELDS: &[&str; 2] = &[TYPE, ALG];
   1692         deserializer.deserialize_struct("PubParam", FIELDS, PubParamVisitor)
   1693     }
   1694 }
   1695 impl<'de> Deserialize<'de> for CoseAlgorithmIdentifiers {
   1696     /// Deserializes a sequence based on
   1697     /// [`pubKeyCredParams`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-pubkeycredparams)
   1698     /// except [`type`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialparameters-type) is not required.
   1699     ///
   1700     /// Note the sequence of [`CoseAlgorithmIdentifier`]s MUST match [`CoseAlgorithmIdentifier::cmp`] or an
   1701     /// error will occur (e.g., if [`CoseAlgorithmIdentifier::Mldsa87`] exists, then it must appear first).
   1702     ///
   1703     /// An empty sequence will be treated as [`Self::ALL`].
   1704     ///
   1705     /// Unknown or duplicate fields lead to an error.
   1706     ///
   1707     /// # Examples
   1708     ///
   1709     /// ```
   1710     /// # use webauthn_rp::request::register::CoseAlgorithmIdentifiers;
   1711     /// assert!(serde_json::from_str::<CoseAlgorithmIdentifiers>(r#"[{"type":"public-key","alg":-50},{"type":"public-key","alg":-49},{"type":"public-key","alg":-48},{"type":"public-key","alg":-8},{"type":"public-key","alg":-7},{"type":"public-key","alg":-35},{"type":"public-key","alg":-257}]"#).is_ok());
   1712     /// ```
   1713     #[expect(clippy::too_many_lines, reason = "132 is fine")]
   1714     #[inline]
   1715     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1716     where
   1717         D: Deserializer<'de>,
   1718     {
   1719         /// `Visitor` for `CoseAlgorithmIdentifiers`.
   1720         struct CoseAlgorithmIdentifiersVisitor;
   1721         impl<'d> Visitor<'d> for CoseAlgorithmIdentifiersVisitor {
   1722             type Value = CoseAlgorithmIdentifiers;
   1723             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1724                 formatter.write_str("CoseAlgorithmIdentifiers")
   1725             }
   1726             #[expect(clippy::too_many_lines, reason = "118 is fine")]
   1727             #[expect(clippy::else_if_without_else, reason = "prefer it this way")]
   1728             fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
   1729             where
   1730                 A: SeqAccess<'d>,
   1731             {
   1732                 let mut mldsa87 = false;
   1733                 let mut mldsa65 = false;
   1734                 let mut mldsa44 = false;
   1735                 let mut eddsa = false;
   1736                 let mut es256 = false;
   1737                 let mut es384 = false;
   1738                 let mut rs256 = false;
   1739                 while let Some(elem) = seq.next_element::<PubParam>()? {
   1740                     match elem.0 {
   1741                         CoseAlgorithmIdentifier::Mldsa87 => {
   1742                             if mldsa87 {
   1743                                 return Err(Error::custom(
   1744                                     "pubKeyCredParams contained duplicate ML-DSA-87 values",
   1745                                 ));
   1746                             } else if mldsa65 || mldsa44 || eddsa || es256 || es384 || rs256 {
   1747                                 return Err(Error::custom(
   1748                                     "pubKeyCredParams contained ML-DSA-87, but it wasn't the first value",
   1749                                 ));
   1750                             }
   1751                             mldsa87 = true;
   1752                         }
   1753                         CoseAlgorithmIdentifier::Mldsa65 => {
   1754                             if mldsa65 {
   1755                                 return Err(Error::custom(
   1756                                     "pubKeyCredParams contained duplicate ML-DSA-65 values",
   1757                                 ));
   1758                             } else if mldsa44 || eddsa || es256 || es384 || rs256 {
   1759                                 return Err(Error::custom(
   1760                                     "pubKeyCredParams contained ML-DSA-65, but it was preceded by Mldsa44, Eddsa, Es256, Es384, or Rs256",
   1761                                 ));
   1762                             }
   1763                             mldsa65 = true;
   1764                         }
   1765                         CoseAlgorithmIdentifier::Mldsa44 => {
   1766                             if mldsa44 {
   1767                                 return Err(Error::custom(
   1768                                     "pubKeyCredParams contained duplicate ML-DSA-44 values",
   1769                                 ));
   1770                             } else if eddsa || es256 || es384 || rs256 {
   1771                                 return Err(Error::custom(
   1772                                     "pubKeyCredParams contained ML-DSA-44, but it was preceded by Eddsa, Es256, Es384, or Rs256",
   1773                                 ));
   1774                             }
   1775                             mldsa44 = true;
   1776                         }
   1777                         CoseAlgorithmIdentifier::Eddsa => {
   1778                             if eddsa {
   1779                                 return Err(Error::custom(
   1780                                     "pubKeyCredParams contained duplicate EdDSA values",
   1781                                 ));
   1782                             } else if es256 || es384 || rs256 {
   1783                                 return Err(Error::custom(
   1784                                     "pubKeyCredParams contained Eddsa, but it was preceded by Es256, Es384, or Rs256",
   1785                                 ));
   1786                             }
   1787                             eddsa = true;
   1788                         }
   1789                         CoseAlgorithmIdentifier::Es256 => {
   1790                             if es256 {
   1791                                 return Err(Error::custom(
   1792                                     "pubKeyCredParams contained duplicate Es256 values",
   1793                                 ));
   1794                             } else if es384 || rs256 {
   1795                                 return Err(Error::custom(
   1796                                     "pubKeyCredParams contained Es256, but it was preceded by Es384 or Rs256",
   1797                                 ));
   1798                             }
   1799                             es256 = true;
   1800                         }
   1801                         CoseAlgorithmIdentifier::Es384 => {
   1802                             if es384 {
   1803                                 return Err(Error::custom(
   1804                                     "pubKeyCredParams contained duplicate Es384 values",
   1805                                 ));
   1806                             } else if rs256 {
   1807                                 return Err(Error::custom(
   1808                                     "pubKeyCredParams contained Es384, but it was preceded by Rs256",
   1809                                 ));
   1810                             }
   1811                             es384 = true;
   1812                         }
   1813                         CoseAlgorithmIdentifier::Rs256 => {
   1814                             if rs256 {
   1815                                 return Err(Error::custom(
   1816                                     "pubKeyCredParams contained duplicate Rs256 values",
   1817                                 ));
   1818                             }
   1819                             rs256 = true;
   1820                         }
   1821                     }
   1822                 }
   1823                 let mut algs = CoseAlgorithmIdentifiers(0);
   1824                 if mldsa87 {
   1825                     algs = algs.add(CoseAlgorithmIdentifier::Mldsa87);
   1826                 }
   1827                 if mldsa65 {
   1828                     algs = algs.add(CoseAlgorithmIdentifier::Mldsa65);
   1829                 }
   1830                 if mldsa44 {
   1831                     algs = algs.add(CoseAlgorithmIdentifier::Mldsa44);
   1832                 }
   1833                 if eddsa {
   1834                     algs = algs.add(CoseAlgorithmIdentifier::Eddsa);
   1835                 }
   1836                 if es256 {
   1837                     algs = algs.add(CoseAlgorithmIdentifier::Es256);
   1838                 }
   1839                 if es384 {
   1840                     algs = algs.add(CoseAlgorithmIdentifier::Es384);
   1841                 }
   1842                 if rs256 {
   1843                     algs = algs.add(CoseAlgorithmIdentifier::Rs256);
   1844                 }
   1845                 Ok(if algs.0 == 0 {
   1846                     CoseAlgorithmIdentifiers::ALL
   1847                 } else {
   1848                     algs
   1849                 })
   1850             }
   1851         }
   1852         deserializer.deserialize_seq(CoseAlgorithmIdentifiersVisitor)
   1853     }
   1854 }
   1855 /// Helper for `UserVerificatonRequirement::deserialize` and [`ResidentKeyRequirement::deserialize`].
   1856 enum Requirement {
   1857     /// Required.
   1858     Required,
   1859     /// Discouraged.
   1860     Discouraged,
   1861     /// Preferred.
   1862     Preferred,
   1863 }
   1864 impl<'de> Deserialize<'de> for Requirement {
   1865     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1866     where
   1867         D: Deserializer<'de>,
   1868     {
   1869         /// `Visitor` for `Requirement`.
   1870         struct RequirementVisitor;
   1871         impl Visitor<'_> for RequirementVisitor {
   1872             type Value = Requirement;
   1873             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1874                 write!(formatter, "'{REQUIRED}', '{DISCOURAGED}', or '{PREFERRED}'")
   1875             }
   1876             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   1877             where
   1878                 E: Error,
   1879             {
   1880                 match v {
   1881                     REQUIRED => Ok(Requirement::Required),
   1882                     DISCOURAGED => Ok(Requirement::Discouraged),
   1883                     PREFERRED => Ok(Requirement::Preferred),
   1884                     _ => Err(E::invalid_value(
   1885                         Unexpected::Str(v),
   1886                         &format!("'{REQUIRED}', '{DISCOURAGED}', or '{PREFERRED}'").as_str(),
   1887                     )),
   1888                 }
   1889             }
   1890         }
   1891         deserializer.deserialize_str(RequirementVisitor)
   1892     }
   1893 }
   1894 impl From<Requirement> for ResidentKeyRequirement {
   1895     #[inline]
   1896     fn from(value: Requirement) -> Self {
   1897         match value {
   1898             Requirement::Required => Self::Required,
   1899             Requirement::Discouraged => Self::Discouraged,
   1900             Requirement::Preferred => Self::Preferred,
   1901         }
   1902     }
   1903 }
   1904 impl From<Requirement> for UserVerificationRequirement {
   1905     #[inline]
   1906     fn from(value: Requirement) -> Self {
   1907         match value {
   1908             Requirement::Required => Self::Required,
   1909             Requirement::Discouraged => Self::Discouraged,
   1910             Requirement::Preferred => Self::Preferred,
   1911         }
   1912     }
   1913 }
   1914 impl<'de> Deserialize<'de> for ResidentKeyRequirement {
   1915     /// Deserializes [`prim@str`] based on
   1916     /// [`ResidentKeyRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-residentkeyrequirement).
   1917     ///
   1918     /// # Examples
   1919     ///
   1920     /// ```
   1921     /// # use webauthn_rp::request::register::ResidentKeyRequirement;
   1922     /// assert!(
   1923     ///     matches!(
   1924     ///         serde_json::from_str(r#""required""#)?,
   1925     ///         ResidentKeyRequirement::Required
   1926     ///     )
   1927     /// );
   1928     /// # Ok::<_, serde_json::Error>(())
   1929     /// ```
   1930     #[inline]
   1931     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1932     where
   1933         D: Deserializer<'de>,
   1934     {
   1935         Requirement::deserialize(deserializer).map(Self::from)
   1936     }
   1937 }
   1938 impl<'de> Deserialize<'de> for UserVerificationRequirement {
   1939     /// Deserializes [`prim@str`] based on
   1940     /// [`UserVerificationRequirement`](https://www.w3.org/TR/webauthn-3/#enumdef-userverificationrequirement).
   1941     ///
   1942     /// # Examples
   1943     ///
   1944     /// ```
   1945     /// # use webauthn_rp::request::UserVerificationRequirement;
   1946     /// assert!(
   1947     ///     matches!(
   1948     ///         serde_json::from_str(r#""required""#)?,
   1949     ///         UserVerificationRequirement::Required
   1950     ///     )
   1951     /// );
   1952     /// # Ok::<_, serde_json::Error>(())
   1953     /// ```
   1954     #[inline]
   1955     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1956     where
   1957         D: Deserializer<'de>,
   1958     {
   1959         Requirement::deserialize(deserializer).map(Self::from)
   1960     }
   1961 }
   1962 impl<'de> Deserialize<'de> for AuthenticatorAttachmentReq {
   1963     /// Deserializes a [`prim@str`] according to
   1964     /// [`AuthenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#enumdef-authenticatorattachment).
   1965     ///
   1966     /// Note the contained hint will be none (e.g., [`PlatformHint::None`]).
   1967     ///
   1968     /// # Examples
   1969     ///
   1970     /// ```
   1971     /// # use webauthn_rp::request::register::{AuthenticatorAttachmentReq, PlatformHint};
   1972     /// assert!(matches!(
   1973     ///     serde_json::from_str(r#""platform""#)?,
   1974     ///     AuthenticatorAttachmentReq::Platform(hint) if matches!(hint, PlatformHint::None)
   1975     /// ));
   1976     /// # Ok::<_, serde_json::Error>(())
   1977     /// ```
   1978     #[inline]
   1979     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   1980     where
   1981         D: Deserializer<'de>,
   1982     {
   1983         /// `Visitor` for `AuthenticatorAttachmentReq`.
   1984         struct AuthenticatorAttachmentReqVisitor;
   1985         impl Visitor<'_> for AuthenticatorAttachmentReqVisitor {
   1986             type Value = AuthenticatorAttachmentReq;
   1987             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   1988                 write!(formatter, "'{PLATFORM}' or '{CROSS_PLATFORM}'")
   1989             }
   1990             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   1991             where
   1992                 E: Error,
   1993             {
   1994                 match v {
   1995                     PLATFORM => Ok(AuthenticatorAttachmentReq::Platform(PlatformHint::None)),
   1996                     CROSS_PLATFORM => Ok(AuthenticatorAttachmentReq::CrossPlatform(
   1997                         CrossPlatformHint::None,
   1998                     )),
   1999                     _ => Err(E::invalid_value(
   2000                         Unexpected::Str(v),
   2001                         &format!("'{PLATFORM}' or '{CROSS_PLATFORM}'").as_str(),
   2002                     )),
   2003                 }
   2004             }
   2005         }
   2006         deserializer.deserialize_str(AuthenticatorAttachmentReqVisitor)
   2007     }
   2008 }
   2009 impl<'de> Deserialize<'de> for AuthenticatorSelectionCriteria {
   2010     /// Deserializes a `struct` based on
   2011     /// [`AuthenticatorSelectionCriteria`](https://www.w3.org/TR/webauthn-3/#dictdef-authenticatorselectioncriteria).
   2012     ///
   2013     /// Note that none of the fields are required, and all are allowed to be `null`. Additionally
   2014     /// [`residentKey`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-residentkey) and
   2015     /// [`requireResidentKey`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-requireresidentkey)
   2016     /// must be consistent (i.e., `requireResidentKey` iff `residentKey` is [`ResidentKeyRequirement::Required`]).
   2017     ///
   2018     /// Missing and `null` fields default to the corresponding [`Default`] value. Unknown and duplicate fields
   2019     /// lead to an error.
   2020     ///
   2021     /// # Examples
   2022     ///
   2023     /// ```
   2024     /// # use webauthn_rp::request::{Hint, register::{AuthenticatorAttachmentReq, AuthenticatorSelectionCriteria}};
   2025     /// assert!(
   2026     ///     matches!(
   2027     ///         serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"authenticatorAttachment":null,"residentKey":"required","requireResidentKey":true,"userVerification":"required"}"#)?.authenticator_attachment,
   2028     ///         AuthenticatorAttachmentReq::None(hints) if matches!(hints, Hint::None)
   2029     ///     )
   2030     /// );
   2031     /// # Ok::<_, serde_json::Error>(())
   2032     /// ```
   2033     #[expect(clippy::too_many_lines, reason = "144 isn't too bad")]
   2034     #[inline]
   2035     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   2036     where
   2037         D: Deserializer<'de>,
   2038     {
   2039         /// `Visitor` for `AuthenticatorSelectionCriteria`.
   2040         struct AuthenticatorSelectionCriteriaVisitor;
   2041         impl<'de> Visitor<'de> for AuthenticatorSelectionCriteriaVisitor {
   2042             type Value = AuthenticatorSelectionCriteria;
   2043             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   2044                 formatter.write_str("AuthenticatorSelectionCriteria")
   2045             }
   2046             #[expect(clippy::too_many_lines, reason = "121 isn't too bad")]
   2047             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
   2048             where
   2049                 A: MapAccess<'de>,
   2050             {
   2051                 /// Field for `AuthenticatorSelectionCriteria`.
   2052                 enum Field {
   2053                     /// `"authenticatorAttachment"`.
   2054                     AuthenticatorAttachment,
   2055                     /// `"residentKey"`.
   2056                     ResidentKey,
   2057                     /// `"requireResidentKey"`.
   2058                     RequireResidentKey,
   2059                     /// `"userVerification"`.
   2060                     UserVerification,
   2061                 }
   2062                 impl<'e> Deserialize<'e> for Field {
   2063                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   2064                     where
   2065                         D: Deserializer<'e>,
   2066                     {
   2067                         /// `Visitor` for `Field`.
   2068                         struct FieldVisitor;
   2069                         impl Visitor<'_> for FieldVisitor {
   2070                             type Value = Field;
   2071                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   2072                                 write!(
   2073                                     formatter,
   2074                                     "'{AUTHENTICATOR_ATTACHMENT}', '{RESIDENT_KEY}', '{REQUIRE_RESIDENT_KEY}', or '{USER_VERIFICATION}'"
   2075                                 )
   2076                             }
   2077                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   2078                             where
   2079                                 E: Error,
   2080                             {
   2081                                 match v {
   2082                                     AUTHENTICATOR_ATTACHMENT => Ok(Field::AuthenticatorAttachment),
   2083                                     RESIDENT_KEY => Ok(Field::ResidentKey),
   2084                                     REQUIRE_RESIDENT_KEY => Ok(Field::RequireResidentKey),
   2085                                     USER_VERIFICATION => Ok(Field::UserVerification),
   2086                                     _ => Err(Error::unknown_field(v, FIELDS)),
   2087                                 }
   2088                             }
   2089                         }
   2090                         deserializer.deserialize_identifier(FieldVisitor)
   2091                     }
   2092                 }
   2093                 let mut attach = None;
   2094                 let mut res_key = None;
   2095                 let mut res_req: Option<Option<bool>> = None;
   2096                 let mut uv = None;
   2097                 while let Some(key) = map.next_key()? {
   2098                     match key {
   2099                         Field::AuthenticatorAttachment => {
   2100                             if attach.is_some() {
   2101                                 return Err(Error::duplicate_field(AUTHENTICATOR_ATTACHMENT));
   2102                             }
   2103                             attach = map.next_value::<Option<_>>().map(Some)?;
   2104                         }
   2105                         Field::ResidentKey => {
   2106                             if res_key.is_some() {
   2107                                 return Err(Error::duplicate_field(RESIDENT_KEY));
   2108                             }
   2109                             res_key = map.next_value::<Option<_>>().and_then(|opt| {
   2110                                 opt.map_or(Ok(Some(None)), |res| res_req.map_or(Ok(Some(opt)), |req_opt| req_opt.map_or(Ok(Some(opt)), |req| {
   2111                                     match res {
   2112                                         ResidentKeyRequirement::Required => {
   2113                                             if req {
   2114                                                 Ok(Some(opt))
   2115                                             } else {
   2116                                                 Err(Error::custom(format!("'{RESIDENT_KEY}' is '{REQUIRED}', but '{REQUIRE_RESIDENT_KEY}' is false")))
   2117                                             }
   2118                                         }
   2119                                         ResidentKeyRequirement::Discouraged | ResidentKeyRequirement::Preferred => {
   2120                                             if req {
   2121                                                 Err(Error::custom(format!("'{RESIDENT_KEY}' is not '{REQUIRED}', but '{REQUIRE_RESIDENT_KEY}' is true")))
   2122                                             } else {
   2123                                                 Ok(Some(opt))
   2124                                             }
   2125                                         }
   2126                                     }
   2127                                 })))
   2128                             })?;
   2129                         }
   2130                         Field::RequireResidentKey => {
   2131                             if res_req.is_some() {
   2132                                 return Err(Error::duplicate_field(REQUIRE_RESIDENT_KEY));
   2133                             }
   2134                             res_req = map.next_value::<Option<_>>().and_then(|opt| {
   2135                                 opt.map_or(Ok(Some(None)), |req| res_key.map_or(Ok(Some(opt)), |req_opt| req_opt.map_or(Ok(Some(opt)), |res| {
   2136                                     match res {
   2137                                         ResidentKeyRequirement::Required => {
   2138                                             if req {
   2139                                                 Ok(Some(opt))
   2140                                             } else {
   2141                                                 Err(Error::custom(format!("'{RESIDENT_KEY}' is '{REQUIRED}', but '{REQUIRE_RESIDENT_KEY}' is false")))
   2142                                             }
   2143                                         }
   2144                                         ResidentKeyRequirement::Discouraged | ResidentKeyRequirement::Preferred => {
   2145                                             if req {
   2146                                                 Err(Error::custom(format!("'{RESIDENT_KEY}' is not '{REQUIRED}', but '{REQUIRE_RESIDENT_KEY}' is true")))
   2147                                             } else {
   2148                                                 Ok(Some(opt))
   2149                                             }
   2150                                         }
   2151                                     }
   2152                                 })))
   2153                             })?;
   2154                         }
   2155                         Field::UserVerification => {
   2156                             if uv.is_some() {
   2157                                 return Err(Error::duplicate_field(USER_VERIFICATION));
   2158                             }
   2159                             uv = map.next_value::<Option<_>>().map(Some)?;
   2160                         }
   2161                     }
   2162                 }
   2163                 Ok(AuthenticatorSelectionCriteria {
   2164                     authenticator_attachment: attach.flatten().unwrap_or_default(),
   2165                     resident_key: res_key.flatten().unwrap_or_else(|| {
   2166                         if res_req.flatten().is_some_and(convert::identity) {
   2167                             ResidentKeyRequirement::Required
   2168                         } else {
   2169                             ResidentKeyRequirement::Discouraged
   2170                         }
   2171                     }),
   2172                     user_verification: uv
   2173                         .flatten()
   2174                         .unwrap_or(UserVerificationRequirement::Preferred),
   2175                 })
   2176             }
   2177         }
   2178         /// Fields for `AuthenticatorSelectionCriteria`.
   2179         const FIELDS: &[&str; 4] = &[
   2180             AUTHENTICATOR_ATTACHMENT,
   2181             RESIDENT_KEY,
   2182             REQUIRE_RESIDENT_KEY,
   2183             USER_VERIFICATION,
   2184         ];
   2185         deserializer.deserialize_struct(
   2186             "AuthenticatorSelectionCriteria",
   2187             FIELDS,
   2188             AuthenticatorSelectionCriteriaVisitor,
   2189         )
   2190     }
   2191 }
   2192 /// Helper for [`ClientCredentialCreationOptions::deserialize`].
   2193 struct Attestation;
   2194 impl<'de> Deserialize<'de> for Attestation {
   2195     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   2196     where
   2197         D: Deserializer<'de>,
   2198     {
   2199         /// `Visitor` for `Attestation`.
   2200         struct AttestationVisitor;
   2201         impl Visitor<'_> for AttestationVisitor {
   2202             type Value = Attestation;
   2203             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   2204                 formatter.write_str(NONE)
   2205             }
   2206             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   2207             where
   2208                 E: Error,
   2209             {
   2210                 if v == NONE {
   2211                     Ok(Attestation)
   2212                 } else {
   2213                     Err(E::invalid_value(Unexpected::Str(v), &NONE))
   2214                 }
   2215             }
   2216         }
   2217         deserializer.deserialize_str(AttestationVisitor)
   2218     }
   2219 }
   2220 /// Helper for [`ClientCredentialCreationOptions::deserialize`].
   2221 struct AttestationFormats;
   2222 impl<'de> Deserialize<'de> for AttestationFormats {
   2223     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   2224     where
   2225         D: Deserializer<'de>,
   2226     {
   2227         /// `Visitor` for `AttestationFormats`.
   2228         struct AttestationFormatsVisitor;
   2229         impl<'d> Visitor<'d> for AttestationFormatsVisitor {
   2230             type Value = AttestationFormats;
   2231             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   2232                 formatter.write_str("AttestationFormats")
   2233             }
   2234             fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
   2235             where
   2236                 A: SeqAccess<'d>,
   2237             {
   2238                 seq.next_element::<Attestation>().and_then(|opt| {
   2239                     opt.map_or(Ok(AttestationFormats), |_f| {
   2240                         seq.next_element::<Attestation>().and_then(|opt2| {
   2241                             opt2.map_or(Ok(AttestationFormats), |_val| Err(Error::custom("attestationFormats must be an empty sequence or contain exactly one string whose value is 'none'")))
   2242                         })
   2243                     })
   2244                 })
   2245             }
   2246         }
   2247         deserializer.deserialize_seq(AttestationFormatsVisitor)
   2248     }
   2249 }
   2250 impl<'de> Deserialize<'de> for FourToSixtyThree {
   2251     /// Deserializes a `u8` based on [`Self::from_u8`].
   2252     ///
   2253     /// # Examples
   2254     ///
   2255     /// ```
   2256     /// # use webauthn_rp::request::register::FourToSixtyThree;
   2257     /// # use serde_json::Error;
   2258     /// assert_eq!(serde_json::from_str::<FourToSixtyThree>("4")?, FourToSixtyThree::Four);
   2259     /// assert_eq!(serde_json::from_str::<FourToSixtyThree>("63")?, FourToSixtyThree::SixtyThree);
   2260     /// assert!(serde_json::from_str::<FourToSixtyThree>("0").is_err());
   2261     /// assert!(serde_json::from_str::<FourToSixtyThree>("3").is_err());
   2262     /// assert!(serde_json::from_str::<FourToSixtyThree>("64").is_err());
   2263     /// # Ok::<_, Error>(())
   2264     /// ```
   2265     #[inline]
   2266     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   2267     where
   2268         D: Deserializer<'de>,
   2269     {
   2270         u8::deserialize(deserializer).and_then(|val| {
   2271             Self::from_u8(val).ok_or_else(|| {
   2272                 Error::invalid_value(
   2273                     Unexpected::Unsigned(u64::from(val)),
   2274                     &"integer inclusively between 4 and 63",
   2275                 )
   2276             })
   2277         })
   2278     }
   2279 }
   2280 /// Similar to [`Extension`] except [`PrfInputOwned`] is used.
   2281 ///
   2282 /// This is primarily useful to assist [`ClientCredentialCreationOptions::deserialize`].
   2283 #[derive(Debug, Default)]
   2284 pub struct ExtensionOwned {
   2285     /// See [`Extension::cred_props`].
   2286     pub cred_props: Option<ExtensionReq>,
   2287     /// See [`Extension::cred_protect`].
   2288     pub cred_protect: CredProtect,
   2289     /// See [`Extension::min_pin_length`].
   2290     pub min_pin_length: Option<(FourToSixtyThree, ExtensionInfo)>,
   2291     /// See [`Extension::prf`].
   2292     pub prf: Option<PrfInputOwned>,
   2293 }
   2294 impl ExtensionOwned {
   2295     /// Returns an `Extension` based on `self`.
   2296     ///
   2297     /// Note [`PrfInputOwned::ext_req`] is converted into an [`ExtensionInfo`] such that the value is enforced.
   2298     #[inline]
   2299     #[must_use]
   2300     pub fn as_extension(&self) -> Extension<'_, '_> {
   2301         Extension {
   2302             cred_props: self.cred_props,
   2303             cred_protect: self.cred_protect,
   2304             min_pin_length: self.min_pin_length,
   2305             prf: self.prf.as_ref().map(|prf| {
   2306                 (
   2307                     PrfInput {
   2308                         first: &prf.first,
   2309                         second: prf.second.as_deref(),
   2310                     },
   2311                     if matches!(prf.ext_req, ExtensionReq::Require) {
   2312                         ExtensionInfo::RequireEnforceValue
   2313                     } else {
   2314                         ExtensionInfo::AllowEnforceValue
   2315                     },
   2316                 )
   2317             }),
   2318         }
   2319     }
   2320     /// Returns an `Extension` based on `self` and `prf`.
   2321     ///
   2322     /// Note `prf` is used _unconditionally_ regardless if [`Self::prf`] is `Some`.
   2323     #[inline]
   2324     #[must_use]
   2325     pub const fn with_prf<'prf_first, 'prf_second>(
   2326         &self,
   2327         prf: (PrfInput<'prf_first, 'prf_second>, ExtensionInfo),
   2328     ) -> Extension<'prf_first, 'prf_second> {
   2329         Extension {
   2330             cred_props: self.cred_props,
   2331             cred_protect: self.cred_protect,
   2332             min_pin_length: self.min_pin_length,
   2333             prf: Some(prf),
   2334         }
   2335     }
   2336 }
   2337 impl<'de> Deserialize<'de> for ExtensionOwned {
   2338     /// Deserializes a `struct` according to the following pseudo-schema:
   2339     ///
   2340     /// ```json
   2341     /// {
   2342     ///   "credProps": null | false | true,
   2343     ///   "credentialProtectionPolicy": null | "userVerificationOptional" | "userVerificationOptionalWithCredentialIDList" | "userVerificationRequired",
   2344     ///   "enforceCredentialProtectionPolicy": null | false | true,
   2345     ///   "minPinLength": null | false | true,
   2346     ///   "prf": null | PRFJSON
   2347     /// }
   2348     /// // PRFJSON:
   2349     /// {
   2350     ///   "eval": PRFInputs
   2351     /// }
   2352     /// // PRFInputs:
   2353     /// {
   2354     ///   "first": <base64url-encoded string>,
   2355     ///   "second": null | <base64url-encoded string>
   2356     /// }
   2357     /// ```
   2358     ///
   2359     /// where the only required fields are `"eval"` and `"first"`. Additionally `"credentialProtectionPolicy"`
   2360     /// must exist if `"enforceCredentialProtectionPolicy"` exists, and it must not be `null` if the latter
   2361     /// is not `null`. If the former is defined and not `null` but the latter is not defined or is `null`, then
   2362     /// `false` will be used for the latter. Unknown or duplicate fields lead to an error.
   2363     ///
   2364     /// All extensions are not required to have a response sent back; but _if_ a response is sent back, its value
   2365     /// will be enforced. In the case of `"minPinLength"`, [`FourToSixtyThree::Four`] will be the minimum
   2366     /// length enforced (i.e., any valid response is guaranteed to satisfy since it will have length at least
   2367     /// as large).
   2368     ///
   2369     /// Unknown or duplicate fields lead to an error.
   2370     ///
   2371     /// # Examples
   2372     ///
   2373     /// ```
   2374     /// # use webauthn_rp::request::{ExtensionInfo, ExtensionReq, register::{CredProtect, FourToSixtyThree, ser::ExtensionOwned}};
   2375     /// let ext = serde_json::from_str::<ExtensionOwned>(
   2376     ///     r#"{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"","second":null}}}"#,
   2377     /// )?;
   2378     /// assert!(
   2379     ///     ext.cred_props
   2380     ///         .map_or(false, |req| matches!(req, ExtensionReq::Allow))
   2381     /// );
   2382     /// assert!(
   2383     ///     matches!(ext.cred_protect, CredProtect::UserVerificationRequired(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
   2384     /// );
   2385     /// assert!(ext.min_pin_length.map_or(false, |pin| pin.0 == FourToSixtyThree::Four
   2386     ///     && matches!(pin.1, ExtensionInfo::AllowEnforceValue)));
   2387     /// assert!(ext.prf.map_or(false, |prf| prf.first.is_empty()
   2388     ///     && prf.second.is_none()
   2389     ///     && matches!(prf.ext_req, ExtensionReq::Allow)));
   2390     /// # Ok::<_, serde_json::Error>(())
   2391     /// ```
   2392     #[expect(clippy::too_many_lines, reason = "want to keep logic internal")]
   2393     #[inline]
   2394     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   2395     where
   2396         D: Deserializer<'de>,
   2397     {
   2398         /// `Visitor` for `ExtensionOwned`.
   2399         struct ExtensionOwnedVisitor;
   2400         impl<'d> Visitor<'d> for ExtensionOwnedVisitor {
   2401             type Value = ExtensionOwned;
   2402             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   2403                 formatter.write_str("ExtensionOwned")
   2404             }
   2405             #[expect(clippy::too_many_lines, reason = "want to keep logic internal")]
   2406             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
   2407             where
   2408                 A: MapAccess<'d>,
   2409             {
   2410                 /// Field for `ExtensionOwned`.
   2411                 enum Field {
   2412                     /// `credProps`.
   2413                     CredProps,
   2414                     /// `credentialProtectionPolicy`.
   2415                     CredentialProtectionPolicy,
   2416                     /// `enforceCredentialProtectionPolicy`.
   2417                     EnforceCredentialProtectionPolicy,
   2418                     /// `minPinLength`.
   2419                     MinPinLength,
   2420                     /// `prf`
   2421                     Prf,
   2422                 }
   2423                 impl<'e> Deserialize<'e> for Field {
   2424                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   2425                     where
   2426                         D: Deserializer<'e>,
   2427                     {
   2428                         /// `Visitor` for `Field`.
   2429                         struct FieldVisitor;
   2430                         impl Visitor<'_> for FieldVisitor {
   2431                             type Value = Field;
   2432                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   2433                                 write!(
   2434                                     formatter,
   2435                                     "'{CRED_PROPS}', '{CREDENTIAL_PROTECTION_POLICY}', '{ENFORCE_CREDENTIAL_PROTECTION_POLICY}', '{MIN_PIN_LENGTH}', or '{PRF}'"
   2436                                 )
   2437                             }
   2438                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   2439                             where
   2440                                 E: Error,
   2441                             {
   2442                                 match v {
   2443                                     CRED_PROPS => Ok(Field::CredProps),
   2444                                     CREDENTIAL_PROTECTION_POLICY => {
   2445                                         Ok(Field::CredentialProtectionPolicy)
   2446                                     }
   2447                                     ENFORCE_CREDENTIAL_PROTECTION_POLICY => {
   2448                                         Ok(Field::EnforceCredentialProtectionPolicy)
   2449                                     }
   2450                                     MIN_PIN_LENGTH => Ok(Field::MinPinLength),
   2451                                     PRF => Ok(Field::Prf),
   2452                                     _ => Err(E::unknown_field(v, FIELDS)),
   2453                                 }
   2454                             }
   2455                         }
   2456                         deserializer.deserialize_identifier(FieldVisitor)
   2457                     }
   2458                 }
   2459                 /// Credential protection policy values.
   2460                 #[expect(clippy::enum_variant_names, reason = "consistent with ctap names")]
   2461                 enum Policy {
   2462                     /// `userVerificationOptional`.
   2463                     UserVerificationOptional,
   2464                     /// `userVerificationOptionalWithCredentialIdList`.
   2465                     UserVerificationOptionalWithCredentialIdLisit,
   2466                     /// `userVerificationRequired`.
   2467                     UserVerificationRequired,
   2468                 }
   2469                 impl<'e> Deserialize<'e> for Policy {
   2470                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   2471                     where
   2472                         D: Deserializer<'e>,
   2473                     {
   2474                         /// `Visitor` for `Policy`.
   2475                         struct PolicyVisitor;
   2476                         impl Visitor<'_> for PolicyVisitor {
   2477                             type Value = Policy;
   2478                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   2479                                 write!(
   2480                                     formatter,
   2481                                     "'{USER_VERIFICATION_OPTIONAL}', '{USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST}', or '{USER_VERIFICATION_REQUIRED}'"
   2482                                 )
   2483                             }
   2484                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   2485                             where
   2486                                 E: Error,
   2487                             {
   2488                                 match v {
   2489                                     USER_VERIFICATION_OPTIONAL => Ok(Policy::UserVerificationOptional),
   2490                                     USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST => Ok(Policy::UserVerificationOptionalWithCredentialIdLisit),
   2491                                     USER_VERIFICATION_REQUIRED => Ok(Policy::UserVerificationRequired),
   2492                                     _ => Err(E::invalid_value(Unexpected::Str(v), &format!("'{USER_VERIFICATION_OPTIONAL}', '{USER_VERIFICATION_OPTIONAL_WITH_CREDENTIAL_ID_LIST}', or '{USER_VERIFICATION_REQUIRED}'").as_str())),
   2493                                 }
   2494                             }
   2495                         }
   2496                         deserializer.deserialize_str(PolicyVisitor)
   2497                     }
   2498                 }
   2499                 let mut props: Option<Option<bool>> = None;
   2500                 let mut policy = None;
   2501                 let mut enforce = None;
   2502                 let mut pin: Option<Option<bool>> = None;
   2503                 let mut prf_inputs = None;
   2504                 while let Some(key) = map.next_key()? {
   2505                     match key {
   2506                         Field::CredProps => {
   2507                             if props.is_some() {
   2508                                 return Err(Error::duplicate_field(CRED_PROPS));
   2509                             }
   2510                             props = map.next_value().map(Some)?;
   2511                         }
   2512                         Field::CredentialProtectionPolicy => {
   2513                             if policy.is_some() {
   2514                                 return Err(Error::duplicate_field(CREDENTIAL_PROTECTION_POLICY));
   2515                             }
   2516                             policy = map.next_value::<Option<Policy>>().map(Some)?;
   2517                         }
   2518                         Field::EnforceCredentialProtectionPolicy => {
   2519                             if enforce.is_some() {
   2520                                 return Err(Error::duplicate_field(
   2521                                     ENFORCE_CREDENTIAL_PROTECTION_POLICY,
   2522                                 ));
   2523                             }
   2524                             enforce = map.next_value::<Option<_>>().map(Some)?;
   2525                         }
   2526                         Field::MinPinLength => {
   2527                             if pin.is_some() {
   2528                                 return Err(Error::duplicate_field(MIN_PIN_LENGTH));
   2529                             }
   2530                             pin = map.next_value().map(Some)?;
   2531                         }
   2532                         Field::Prf => {
   2533                             if prf_inputs.is_some() {
   2534                                 return Err(Error::duplicate_field(PRF));
   2535                             }
   2536                             prf_inputs = map
   2537                                 .next_value::<Option<PrfHelper>>()
   2538                                 .map(|opt| Some(opt.map(|p| p.0)))?;
   2539                         }
   2540                     }
   2541                 }
   2542                 policy.map_or_else(
   2543                     || {
   2544                         if enforce.is_some() {
   2545                             Err(Error::custom(format!("'{ENFORCE_CREDENTIAL_PROTECTION_POLICY}' must not exist when '{CREDENTIAL_PROTECTION_POLICY}' does not exist")))
   2546                         } else {
   2547                             Ok(CredProtect::None)
   2548                         }
   2549                     },
   2550                     |opt_policy| opt_policy.map_or_else(
   2551                         || {
   2552                             if enforce.is_some_and(|opt| opt.is_some()) {
   2553                                 Err(Error::custom(format!("'{ENFORCE_CREDENTIAL_PROTECTION_POLICY}' must be null or not exist when '{CREDENTIAL_PROTECTION_POLICY}' is null")))
   2554                             } else {
   2555                                 Ok(CredProtect::None)
   2556                             }
   2557                         },
   2558                         |cred_policy| {
   2559                             match cred_policy {
   2560                                 Policy::UserVerificationOptional => Ok(CredProtect::UserVerificationOptional(enforce.flatten().unwrap_or_default(), ExtensionInfo::AllowEnforceValue)),
   2561                                 Policy::UserVerificationOptionalWithCredentialIdLisit => Ok(CredProtect::UserVerificationOptionalWithCredentialIdList(enforce.flatten().unwrap_or_default(), ExtensionInfo::AllowEnforceValue)),
   2562                                 Policy::UserVerificationRequired => Ok(CredProtect::UserVerificationRequired(enforce.flatten().unwrap_or_default(), ExtensionInfo::AllowEnforceValue)),
   2563                             }
   2564                         }
   2565                     ),
   2566                 ).map(|cred_protect| {
   2567                     ExtensionOwned { cred_props: props.flatten().and_then(|p| p.then_some(ExtensionReq::Allow)), cred_protect, min_pin_length: pin.flatten().and_then(|m| m.then_some((FourToSixtyThree::Four, ExtensionInfo::AllowEnforceValue))), prf: prf_inputs.flatten(), }
   2568                 })
   2569             }
   2570         }
   2571         /// Fields for `ExtensionOwned`.
   2572         const FIELDS: &[&str; 5] = &[
   2573             CRED_PROPS,
   2574             CREDENTIAL_PROTECTION_POLICY,
   2575             ENFORCE_CREDENTIAL_PROTECTION_POLICY,
   2576             MIN_PIN_LENGTH,
   2577             PRF,
   2578         ];
   2579         deserializer.deserialize_struct("ExtensionOwned", FIELDS, ExtensionOwnedVisitor)
   2580     }
   2581 }
   2582 /// Similar to [`PublicKeyCredentialCreationOptions`] except the fields are based on owned data, and
   2583 /// [`Self::rp_id`] is optional.
   2584 ///
   2585 /// This is primarily useful to assist [`ClientCredentialCreationOptions::deserialize`].
   2586 #[derive(Debug)]
   2587 pub struct PublicKeyCredentialCreationOptionsOwned<
   2588     'user_name,
   2589     'user_display_name,
   2590     const USER_LEN: usize,
   2591 > {
   2592     /// See [`PublicKeyCredentialCreationOptions::rp_id`].
   2593     pub rp_id: Option<RpId>,
   2594     /// See [`PublicKeyCredentialCreationOptions::user`].
   2595     pub user: PublicKeyCredentialUserEntityOwned<'user_name, 'user_display_name, USER_LEN>,
   2596     /// See [`PublicKeyCredentialCreationOptions::pub_key_cred_params`].
   2597     pub pub_key_cred_params: CoseAlgorithmIdentifiers,
   2598     /// See [`PublicKeyCredentialCreationOptions::timeout`].
   2599     pub timeout: NonZeroU32,
   2600     /// See [`PublicKeyCredentialCreationOptions::authenticator_selection`].
   2601     pub authenticator_selection: AuthenticatorSelectionCriteria,
   2602     /// See [`PublicKeyCredentialCreationOptions::extensions`].
   2603     pub extensions: ExtensionOwned,
   2604 }
   2605 /// Error returned when converting a [`PublicKeyCredentialCreationOptionsOwned`] into a
   2606 /// [`PublicKeyCredentialCreationOptions`] (e.g., via [`PublicKeyCredentialCreationOptionsOwned::with_rp_id`]).
   2607 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
   2608 pub enum PublicKeyCredentialCreationOptionsOwnedErr {
   2609     /// Variant returned when [`PublicKeyCredentialCreationOptionsOwned::rp_id`] is `None`.
   2610     MissingRpId,
   2611     /// Variant returned when [`PublicKeyCredentialCreationOptionsOwned::user`] cannot be converted into a
   2612     /// a [`PublicKeyCredentialCreationOptions`].
   2613     UserEntity(PublicKeyCredentialUserEntityOwnedErr),
   2614 }
   2615 impl Display for PublicKeyCredentialCreationOptionsOwnedErr {
   2616     #[inline]
   2617     fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
   2618         match *self {
   2619             Self::MissingRpId => f.write_str("creation options did not have an RP ID"),
   2620             Self::UserEntity(err) => err.fmt(f),
   2621         }
   2622     }
   2623 }
   2624 impl E for PublicKeyCredentialCreationOptionsOwnedErr {}
   2625 impl From<PublicKeyCredentialUserEntityOwnedErr> for PublicKeyCredentialCreationOptionsOwnedErr {
   2626     #[inline]
   2627     fn from(value: PublicKeyCredentialUserEntityOwnedErr) -> Self {
   2628         Self::UserEntity(value)
   2629     }
   2630 }
   2631 impl<const USER_LEN: usize> PublicKeyCredentialCreationOptionsOwned<'_, '_, USER_LEN> {
   2632     /// Returns a `PublicKeyCredentialCreationOptions` based on `self` and `exclude_credentials`.
   2633     ///
   2634     /// # Errors
   2635     ///
   2636     /// Errors iff [`Self::rp_id`] is `None` or [`PublicKeyCredentialUserEntityOwned::as_entity`] errors.
   2637     #[inline]
   2638     pub fn as_options(
   2639         &self,
   2640         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   2641     ) -> Result<
   2642         PublicKeyCredentialCreationOptions<'_, '_, '_, '_, '_, '_, USER_LEN>,
   2643         PublicKeyCredentialCreationOptionsOwnedErr,
   2644     > {
   2645         self.rp_id
   2646             .as_ref()
   2647             .ok_or(PublicKeyCredentialCreationOptionsOwnedErr::MissingRpId)
   2648             .and_then(|rp_id| {
   2649                 self.user
   2650                     .as_entity()
   2651                     .map_err(PublicKeyCredentialCreationOptionsOwnedErr::UserEntity)
   2652                     .map(|user| PublicKeyCredentialCreationOptions {
   2653                         rp_id,
   2654                         user,
   2655                         challenge: Challenge::new(),
   2656                         pub_key_cred_params: self.pub_key_cred_params,
   2657                         timeout: self.timeout,
   2658                         exclude_credentials,
   2659                         authenticator_selection: self.authenticator_selection,
   2660                         extensions: self.extensions.as_extension(),
   2661                     })
   2662             })
   2663     }
   2664     /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, and `rp_id`.
   2665     ///
   2666     /// Note `rp_id` is used _unconditionally_ regardless if [`Self::rp_id`] is `Some`.
   2667     ///
   2668     /// # Errors
   2669     ///
   2670     /// Errors iff [`PublicKeyCredentialUserEntityOwned::as_entity`] errors.
   2671     #[inline]
   2672     pub fn with_rp_id<'rp_id>(
   2673         &self,
   2674         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   2675         rp_id: &'rp_id RpId,
   2676     ) -> Result<
   2677         PublicKeyCredentialCreationOptions<'rp_id, '_, '_, '_, '_, '_, USER_LEN>,
   2678         PublicKeyCredentialCreationOptionsOwnedErr,
   2679     > {
   2680         self.user
   2681             .as_entity()
   2682             .map_err(PublicKeyCredentialCreationOptionsOwnedErr::UserEntity)
   2683             .map(|user| PublicKeyCredentialCreationOptions {
   2684                 rp_id,
   2685                 user,
   2686                 challenge: Challenge::new(),
   2687                 pub_key_cred_params: self.pub_key_cred_params,
   2688                 timeout: self.timeout,
   2689                 exclude_credentials,
   2690                 authenticator_selection: self.authenticator_selection,
   2691                 extensions: self.extensions.as_extension(),
   2692             })
   2693     }
   2694     /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, and `user`.
   2695     ///
   2696     /// Note `user` is used _unconditionally_ regardless of what [`Self::user`] is.
   2697     ///
   2698     /// # Errors
   2699     ///
   2700     /// Errors iff [`Self::rp_id`] is `None`.
   2701     #[inline]
   2702     pub fn with_user<'user_name, 'user_display_name, 'user_id>(
   2703         &self,
   2704         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   2705         user: PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>,
   2706     ) -> Result<
   2707         PublicKeyCredentialCreationOptions<
   2708             '_,
   2709             'user_name,
   2710             'user_display_name,
   2711             'user_id,
   2712             '_,
   2713             '_,
   2714             USER_LEN,
   2715         >,
   2716         PublicKeyCredentialCreationOptionsOwnedErr,
   2717     > {
   2718         self.rp_id
   2719             .as_ref()
   2720             .ok_or(PublicKeyCredentialCreationOptionsOwnedErr::MissingRpId)
   2721             .map(|rp_id| PublicKeyCredentialCreationOptions {
   2722                 rp_id,
   2723                 user,
   2724                 challenge: Challenge::new(),
   2725                 pub_key_cred_params: self.pub_key_cred_params,
   2726                 timeout: self.timeout,
   2727                 exclude_credentials,
   2728                 authenticator_selection: self.authenticator_selection,
   2729                 extensions: self.extensions.as_extension(),
   2730             })
   2731     }
   2732     /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, and `extensions`.
   2733     ///
   2734     /// Note `extensions` is used _unconditionally_ regardless of what [`Self::extensions`] is.
   2735     ///
   2736     /// # Errors
   2737     ///
   2738     /// Errors iff [`Self::rp_id`] is `None` or [`PublicKeyCredentialUserEntityOwned::as_entity`] errors.
   2739     #[inline]
   2740     pub fn with_extensions<'prf_first, 'prf_second>(
   2741         &self,
   2742         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   2743         extensions: Extension<'prf_first, 'prf_second>,
   2744     ) -> Result<
   2745         PublicKeyCredentialCreationOptions<'_, '_, '_, '_, 'prf_first, 'prf_second, USER_LEN>,
   2746         PublicKeyCredentialCreationOptionsOwnedErr,
   2747     > {
   2748         self.rp_id
   2749             .as_ref()
   2750             .ok_or(PublicKeyCredentialCreationOptionsOwnedErr::MissingRpId)
   2751             .and_then(|rp_id| {
   2752                 self.user
   2753                     .as_entity()
   2754                     .map_err(PublicKeyCredentialCreationOptionsOwnedErr::UserEntity)
   2755                     .map(|user| PublicKeyCredentialCreationOptions {
   2756                         rp_id,
   2757                         user,
   2758                         challenge: Challenge::new(),
   2759                         pub_key_cred_params: self.pub_key_cred_params,
   2760                         timeout: self.timeout,
   2761                         exclude_credentials,
   2762                         authenticator_selection: self.authenticator_selection,
   2763                         extensions,
   2764                     })
   2765             })
   2766     }
   2767     /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, `rp_id`, and `user`.
   2768     ///
   2769     /// Note `rp_id` and `user` are used _unconditionally_ regardless if [`Self::rp_id`] is `Some` or what
   2770     /// [`Self::user`] is.
   2771     #[inline]
   2772     #[must_use]
   2773     pub fn with_rp_id_and_user<'rp_id, 'user_name, 'user_display_name, 'user_id>(
   2774         &self,
   2775         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   2776         rp_id: &'rp_id RpId,
   2777         user: PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>,
   2778     ) -> PublicKeyCredentialCreationOptions<
   2779         'rp_id,
   2780         'user_name,
   2781         'user_display_name,
   2782         'user_id,
   2783         '_,
   2784         '_,
   2785         USER_LEN,
   2786     > {
   2787         PublicKeyCredentialCreationOptions {
   2788             rp_id,
   2789             user,
   2790             challenge: Challenge::new(),
   2791             pub_key_cred_params: self.pub_key_cred_params,
   2792             timeout: self.timeout,
   2793             exclude_credentials,
   2794             authenticator_selection: self.authenticator_selection,
   2795             extensions: self.extensions.as_extension(),
   2796         }
   2797     }
   2798     /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, `rp_id`, and
   2799     /// `extensions`.
   2800     ///
   2801     /// Note `rp_id` and `extensions` are used _unconditionally_ regardless if [`Self::rp_id`] is `Some` or what
   2802     /// [`Self::extensions`] is.
   2803     ///
   2804     /// # Errors
   2805     ///
   2806     /// Errors iff [`PublicKeyCredentialUserEntityOwned::as_entity`] errors.
   2807     #[inline]
   2808     pub fn with_rp_id_and_extensions<'rp_id, 'prf_first, 'prf_second>(
   2809         &self,
   2810         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   2811         rp_id: &'rp_id RpId,
   2812         extensions: Extension<'prf_first, 'prf_second>,
   2813     ) -> Result<
   2814         PublicKeyCredentialCreationOptions<'rp_id, '_, '_, '_, 'prf_first, 'prf_second, USER_LEN>,
   2815         PublicKeyCredentialCreationOptionsOwnedErr,
   2816     > {
   2817         self.user
   2818             .as_entity()
   2819             .map_err(PublicKeyCredentialCreationOptionsOwnedErr::UserEntity)
   2820             .map(|user| PublicKeyCredentialCreationOptions {
   2821                 rp_id,
   2822                 user,
   2823                 challenge: Challenge::new(),
   2824                 pub_key_cred_params: self.pub_key_cred_params,
   2825                 timeout: self.timeout,
   2826                 exclude_credentials,
   2827                 authenticator_selection: self.authenticator_selection,
   2828                 extensions,
   2829             })
   2830     }
   2831     /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, `user`, and
   2832     /// `extensions`.
   2833     ///
   2834     /// Note `user` and `extensions` are used _unconditionally_ regardless of what the values of [`Self::user`]
   2835     /// or [`Self::extensions`] are.
   2836     ///
   2837     /// # Errors
   2838     ///
   2839     /// Errors iff [`Self::rp_id`] is `None`.
   2840     #[inline]
   2841     pub fn with_user_and_extensions<
   2842         'user_name,
   2843         'user_display_name,
   2844         'user_id,
   2845         'prf_first,
   2846         'prf_second,
   2847     >(
   2848         &self,
   2849         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   2850         user: PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>,
   2851         extensions: Extension<'prf_first, 'prf_second>,
   2852     ) -> Result<
   2853         PublicKeyCredentialCreationOptions<
   2854             '_,
   2855             'user_name,
   2856             'user_display_name,
   2857             'user_id,
   2858             'prf_first,
   2859             'prf_second,
   2860             USER_LEN,
   2861         >,
   2862         PublicKeyCredentialCreationOptionsOwnedErr,
   2863     > {
   2864         self.rp_id
   2865             .as_ref()
   2866             .ok_or(PublicKeyCredentialCreationOptionsOwnedErr::MissingRpId)
   2867             .map(|rp_id| PublicKeyCredentialCreationOptions {
   2868                 rp_id,
   2869                 user,
   2870                 challenge: Challenge::new(),
   2871                 pub_key_cred_params: self.pub_key_cred_params,
   2872                 timeout: self.timeout,
   2873                 exclude_credentials,
   2874                 authenticator_selection: self.authenticator_selection,
   2875                 extensions,
   2876             })
   2877     }
   2878     /// Returns a `PublicKeyCredentialCreationOptions` based on `self`, `exclude_credentials`, `rp_id`, `user`,
   2879     /// and `extensions`.
   2880     ///
   2881     /// Note `rp_id`, `user`, and `extensions` are used _unconditionally_ regardless if [`Self::rp_id`] is `Some`
   2882     /// or what the values of [`Self::user`] and [`Self::extensions`] are.
   2883     #[inline]
   2884     #[must_use]
   2885     pub fn with_rp_id_user_and_extensions<
   2886         'rp_id,
   2887         'user_name,
   2888         'user_display_name,
   2889         'user_id,
   2890         'prf_first,
   2891         'prf_second,
   2892     >(
   2893         &self,
   2894         exclude_credentials: Vec<PublicKeyCredentialDescriptor<Box<[u8]>>>,
   2895         rp_id: &'rp_id RpId,
   2896         user: PublicKeyCredentialUserEntity<'user_name, 'user_display_name, 'user_id, USER_LEN>,
   2897         extensions: Extension<'prf_first, 'prf_second>,
   2898     ) -> PublicKeyCredentialCreationOptions<
   2899         'rp_id,
   2900         'user_name,
   2901         'user_display_name,
   2902         'user_id,
   2903         'prf_first,
   2904         'prf_second,
   2905         USER_LEN,
   2906     > {
   2907         PublicKeyCredentialCreationOptions {
   2908             rp_id,
   2909             user,
   2910             challenge: Challenge::new(),
   2911             pub_key_cred_params: self.pub_key_cred_params,
   2912             timeout: self.timeout,
   2913             exclude_credentials,
   2914             authenticator_selection: self.authenticator_selection,
   2915             extensions,
   2916         }
   2917     }
   2918 }
   2919 impl<const USER_LEN: usize> Default for PublicKeyCredentialCreationOptionsOwned<'_, '_, USER_LEN> {
   2920     #[inline]
   2921     fn default() -> Self {
   2922         Self {
   2923             rp_id: None,
   2924             user: PublicKeyCredentialUserEntityOwned::default(),
   2925             pub_key_cred_params: CoseAlgorithmIdentifiers::default(),
   2926             timeout: FIVE_MINUTES,
   2927             authenticator_selection: AuthenticatorSelectionCriteria {
   2928                 authenticator_attachment: AuthenticatorAttachmentReq::None(Hint::None),
   2929                 resident_key: ResidentKeyRequirement::Discouraged,
   2930                 user_verification: UserVerificationRequirement::Preferred,
   2931             },
   2932             extensions: ExtensionOwned::default(),
   2933         }
   2934     }
   2935 }
   2936 impl<'de: 'user_name + 'user_display_name, 'user_name, 'user_display_name, const USER_LEN: usize>
   2937     Deserialize<'de>
   2938     for PublicKeyCredentialCreationOptionsOwned<'user_name, 'user_display_name, USER_LEN>
   2939 where
   2940     PublicKeyCredentialUserEntityOwned<'user_name, 'user_display_name, USER_LEN>: Deserialize<'de>,
   2941 {
   2942     /// Deserializes a `struct` based on
   2943     /// [`PublicKeyCredentialCreationOptionsJSON`](https://www.w3.org/TR/webauthn-3/#dictdef-publickeycredentialcreationoptionsjson).
   2944     ///
   2945     /// Note that none of the fields are required, and all are allowed to be `null`.
   2946     /// [`authenticatorAttachment`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorselectioncriteria-authenticatorattachment)
   2947     /// must be consistent with
   2948     /// [`hints`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-hints)
   2949     /// (e.g., if [`"platform"`](https://www.w3.org/TR/webauthn-3/#dom-authenticatorattachment-platform) is
   2950     /// requested, then `hints` must either not exist, be `null`, be empty, or be `["client-device"]`).
   2951     ///
   2952     /// If [`challenge`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-challenge)
   2953     /// exists, it must be `null`. If
   2954     /// [`excludeCredentials`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-excludecredentials)
   2955     /// exists, it must be `null` or empty. If
   2956     /// [`attestation`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-attestation)
   2957     /// exists, it must be `null`or `"none"`. If
   2958     /// [`attestationFormats`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-attestationformats)
   2959     /// exists, it must be `null`, empty, or `["none"]`.
   2960     ///
   2961     /// If [`timeout`](https://www.w3.org/TR/webauthn-3/#dom-publickeycredentialcreationoptionsjson-timeout) exists,
   2962     /// it must be `null` or positive. When it does not exist or is `null`, [`FIVE_MINUTES`] will be used.
   2963     ///
   2964     /// Fields that are missing or `null` will be replaced with their corresponding [`Default`] value.
   2965     ///
   2966     /// Unknown or duplicate fields lead to an error.
   2967     #[expect(clippy::too_many_lines, reason = "want to keep logic internal")]
   2968     #[inline]
   2969     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   2970     where
   2971         D: Deserializer<'de>,
   2972     {
   2973         /// `Visitor` for `PublicKeyCredentialCreationOptionsOwned`.
   2974         struct PublicKeyCredentialCreationOptionsOwnedVisitor<'a, 'b, const LEN: usize>(
   2975             PhantomData<fn() -> (&'a (), &'b ())>,
   2976         );
   2977         impl<'d: 'a + 'b, 'a, 'b, const LEN: usize> Visitor<'d>
   2978             for PublicKeyCredentialCreationOptionsOwnedVisitor<'a, 'b, LEN>
   2979         where
   2980             PublicKeyCredentialUserEntityOwned<'a, 'b, LEN>: Deserialize<'d>,
   2981         {
   2982             type Value = PublicKeyCredentialCreationOptionsOwned<'a, 'b, LEN>;
   2983             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   2984                 formatter.write_str("PublicKeyCredentialCreationOptionsOwned")
   2985             }
   2986             #[expect(clippy::too_many_lines, reason = "want to keep logic internal")]
   2987             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
   2988             where
   2989                 A: MapAccess<'d>,
   2990             {
   2991                 /// Field for `PublicKeyCredentialCreationOptionsOwned`.
   2992                 enum Field {
   2993                     /// `rp`.
   2994                     Rp,
   2995                     /// `user`.
   2996                     User,
   2997                     /// `challenge`.
   2998                     Challenge,
   2999                     /// `pubKeyCredParams`.
   3000                     PubKeyCredParams,
   3001                     /// `timeout`.
   3002                     Timeout,
   3003                     /// `excludeCredentials`.
   3004                     ExcludeCredentials,
   3005                     /// `authenticatorSelection`.
   3006                     AuthenticatorSelection,
   3007                     /// `hints`.
   3008                     Hints,
   3009                     /// `extensions`.
   3010                     Extensions,
   3011                     /// `attestation`.
   3012                     Attestation,
   3013                     /// `attestationFormats`.
   3014                     AttestationFormats,
   3015                 }
   3016                 impl<'e> Deserialize<'e> for Field {
   3017                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   3018                     where
   3019                         D: Deserializer<'e>,
   3020                     {
   3021                         /// `Visitor` for `Field`.
   3022                         struct FieldVisitor;
   3023                         impl Visitor<'_> for FieldVisitor {
   3024                             type Value = Field;
   3025                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   3026                                 write!(
   3027                                     formatter,
   3028                                     "'{RP}', '{USER}', '{CHALLENGE}', '{PUB_KEY_CRED_PARAMS}', '{TIMEOUT}', '{EXCLUDE_CREDENTIALS}', '{AUTHENTICATOR_SELECTION}', '{HINTS}', '{EXTENSIONS}', '{ATTESTATION}', or '{ATTESTATION_FORMATS}'"
   3029                                 )
   3030                             }
   3031                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   3032                             where
   3033                                 E: Error,
   3034                             {
   3035                                 match v {
   3036                                     RP => Ok(Field::Rp),
   3037                                     USER => Ok(Field::User),
   3038                                     CHALLENGE => Ok(Field::Challenge),
   3039                                     PUB_KEY_CRED_PARAMS => Ok(Field::PubKeyCredParams),
   3040                                     TIMEOUT => Ok(Field::Timeout),
   3041                                     EXCLUDE_CREDENTIALS => Ok(Field::ExcludeCredentials),
   3042                                     AUTHENTICATOR_SELECTION => Ok(Field::AuthenticatorSelection),
   3043                                     HINTS => Ok(Field::Hints),
   3044                                     EXTENSIONS => Ok(Field::Extensions),
   3045                                     ATTESTATION => Ok(Field::Attestation),
   3046                                     ATTESTATION_FORMATS => Ok(Field::AttestationFormats),
   3047                                     _ => Err(E::unknown_field(v, FIELDS)),
   3048                                 }
   3049                             }
   3050                         }
   3051                         deserializer.deserialize_identifier(FieldVisitor)
   3052                     }
   3053                 }
   3054                 let mut rp = None;
   3055                 let mut user_info = None;
   3056                 let mut chall = None;
   3057                 let mut params = None;
   3058                 let mut time = None;
   3059                 let mut exclude = None;
   3060                 let mut auth = None;
   3061                 let mut hint: Option<Hint> = None;
   3062                 let mut ext = None;
   3063                 let mut attest = None;
   3064                 let mut formats = None;
   3065                 while let Some(key) = map.next_key()? {
   3066                     match key {
   3067                         Field::Rp => {
   3068                             if rp.is_some() {
   3069                                 return Err(Error::duplicate_field(RP));
   3070                             }
   3071                             rp = map
   3072                                 .next_value::<Option<PublicKeyCredentialRpEntityHelper>>()
   3073                                 .map(|opt| opt.map(|val| val.0))
   3074                                 .map(Some)?;
   3075                         }
   3076                         Field::User => {
   3077                             if user_info.is_some() {
   3078                                 return Err(Error::duplicate_field(USER));
   3079                             }
   3080                             user_info = map.next_value::<Option<_>>().map(Some)?;
   3081                         }
   3082                         Field::Challenge => {
   3083                             if chall.is_some() {
   3084                                 return Err(Error::duplicate_field(CHALLENGE));
   3085                             }
   3086                             chall = map.next_value::<Null>().map(Some)?;
   3087                         }
   3088                         Field::PubKeyCredParams => {
   3089                             if params.is_some() {
   3090                                 return Err(Error::duplicate_field(PUB_KEY_CRED_PARAMS));
   3091                             }
   3092                             params = map.next_value::<Option<_>>().map(Some)?;
   3093                         }
   3094                         Field::Timeout => {
   3095                             if time.is_some() {
   3096                                 return Err(Error::duplicate_field(TIMEOUT));
   3097                             }
   3098                             time = map.next_value::<Option<_>>().map(Some)?;
   3099                         }
   3100                         Field::ExcludeCredentials => {
   3101                             if exclude.is_some() {
   3102                                 return Err(Error::duplicate_field(EXCLUDE_CREDENTIALS));
   3103                             }
   3104                             exclude = map.next_value::<Option<[(); 0]>>().map(Some)?;
   3105                         }
   3106                         Field::AuthenticatorSelection => {
   3107                             if auth.is_some() {
   3108                                 return Err(Error::duplicate_field(AUTHENTICATOR_SELECTION));
   3109                             }
   3110                             auth = map.next_value::<Option<AuthenticatorSelectionCriteria>>().and_then(|opt| {
   3111                                 opt.map_or(Ok(Some(AuthenticatorSelectionCriteria { authenticator_attachment: AuthenticatorAttachmentReq::default(), resident_key: ResidentKeyRequirement::Discouraged, user_verification: UserVerificationRequirement::Preferred, })), |mut crit| {
   3112                                     let h = hint.unwrap_or_default();
   3113                                     match crit.authenticator_attachment {
   3114                                         AuthenticatorAttachmentReq::None(ref mut hi) => {
   3115                                             *hi = h;
   3116                                             Ok(Some(crit))
   3117                                         }
   3118                                         AuthenticatorAttachmentReq::Platform(ref mut hi) => {
   3119                                             match h {
   3120                                                 Hint::None => Ok(Some(crit)),
   3121                                                 Hint::ClientDevice => {
   3122                                                     *hi = PlatformHint::ClientDevice;
   3123                                                     Ok(Some(crit))
   3124                                                 }
   3125                                                 Hint::SecurityKey | Hint::Hybrid | Hint::SecurityKeyClientDevice | Hint::ClientDeviceSecurityKey | Hint::SecurityKeyHybrid | Hint::HybridSecurityKey | Hint::ClientDeviceHybrid | Hint::HybridClientDevice | Hint::SecurityKeyClientDeviceHybrid | Hint::SecurityKeyHybridClientDevice | Hint::ClientDeviceSecurityKeyHybrid | Hint::ClientDeviceHybridSecurityKey | Hint::HybridSecurityKeyClientDevice | Hint::HybridClientDeviceSecurityKey => Err(Error::custom("'platform' authenticator attachment modality must coincide with no hints or 'client-device' hint")),
   3126                                             }
   3127                                         }
   3128                                         AuthenticatorAttachmentReq::CrossPlatform(ref mut hi) => {
   3129                                             match h {
   3130                                                 Hint::None => Ok(Some(crit)),
   3131                                                 Hint::SecurityKey => {
   3132                                                     *hi = CrossPlatformHint::SecurityKey;
   3133                                                     Ok(Some(crit))
   3134                                                 }
   3135                                                 Hint::Hybrid => {
   3136                                                     *hi = CrossPlatformHint::Hybrid;
   3137                                                     Ok(Some(crit))
   3138                                                 }
   3139                                                 Hint::SecurityKeyHybrid => {
   3140                                                     *hi = CrossPlatformHint::SecurityKeyHybrid;
   3141                                                     Ok(Some(crit))
   3142                                                 }
   3143                                                 Hint::HybridSecurityKey => {
   3144                                                     *hi = CrossPlatformHint::HybridSecurityKey;
   3145                                                     Ok(Some(crit))
   3146                                                 }
   3147                                                 Hint::ClientDevice | Hint::SecurityKeyClientDevice | Hint::ClientDeviceSecurityKey | Hint::ClientDeviceHybrid | Hint::HybridClientDevice | Hint::SecurityKeyClientDeviceHybrid | Hint::SecurityKeyHybridClientDevice | Hint::ClientDeviceSecurityKeyHybrid | Hint::ClientDeviceHybridSecurityKey | Hint::HybridSecurityKeyClientDevice | Hint::HybridClientDeviceSecurityKey => Err(Error::custom("'cross-platform' authenticator attachment modality must coincide with no hints or hints that lack 'client-device'")),
   3148                                             }
   3149                                         }
   3150                                     }
   3151                                 })
   3152                             })?;
   3153                         }
   3154                         Field::Hints => {
   3155                             if hint.is_some() {
   3156                                 return Err(Error::duplicate_field(HINTS));
   3157                             }
   3158                             hint = map.next_value::<Option<Hint>>().and_then(|opt| {
   3159                                 opt.map_or(Ok(Some(Hint::None)), |h| {
   3160                                     auth.as_mut().map_or(Ok(Some(h)), |crit| {
   3161                                         match crit.authenticator_attachment {
   3162                                             AuthenticatorAttachmentReq::None(ref mut hi) => {
   3163                                                 *hi = h;
   3164                                                 Ok(Some(h))
   3165                                             }
   3166                                             AuthenticatorAttachmentReq::Platform(ref mut hi) => {
   3167                                                 match h{
   3168                                                     Hint::None => Ok(Some(h)),
   3169                                                     Hint::ClientDevice => {
   3170                                                         *hi = PlatformHint::ClientDevice;
   3171                                                         Ok(Some(h))
   3172                                                     }
   3173                                                     Hint::SecurityKey | Hint::Hybrid | Hint::SecurityKeyClientDevice | Hint::ClientDeviceSecurityKey | Hint::SecurityKeyHybrid | Hint::HybridSecurityKey | Hint::ClientDeviceHybrid | Hint::HybridClientDevice | Hint::SecurityKeyClientDeviceHybrid | Hint::SecurityKeyHybridClientDevice | Hint::ClientDeviceSecurityKeyHybrid | Hint::ClientDeviceHybridSecurityKey | Hint::HybridSecurityKeyClientDevice | Hint::HybridClientDeviceSecurityKey => Err(Error::custom("'platform' authenticator attachment modality must coincide with no hints or 'client-device' hint")),
   3174                                                 }
   3175                                             }
   3176                                             AuthenticatorAttachmentReq::CrossPlatform(ref mut hi) => {
   3177                                                 match h {
   3178                                                     Hint::None => Ok(Some(h)),
   3179                                                     Hint::SecurityKey => {
   3180                                                         *hi = CrossPlatformHint::SecurityKey;
   3181                                                         Ok(Some(h))
   3182                                                     }
   3183                                                     Hint::Hybrid => {
   3184                                                         *hi = CrossPlatformHint::Hybrid;
   3185                                                         Ok(Some(h))
   3186                                                     }
   3187                                                     Hint::SecurityKeyHybrid => {
   3188                                                         *hi = CrossPlatformHint::SecurityKeyHybrid;
   3189                                                         Ok(Some(h))
   3190                                                     }
   3191                                                     Hint::HybridSecurityKey => {
   3192                                                         *hi = CrossPlatformHint::HybridSecurityKey;
   3193                                                         Ok(Some(h))
   3194                                                     }
   3195                                                     Hint::ClientDevice | Hint::SecurityKeyClientDevice | Hint::ClientDeviceSecurityKey | Hint::ClientDeviceHybrid | Hint::HybridClientDevice | Hint::SecurityKeyClientDeviceHybrid | Hint::SecurityKeyHybridClientDevice | Hint::ClientDeviceSecurityKeyHybrid | Hint::ClientDeviceHybridSecurityKey | Hint::HybridSecurityKeyClientDevice | Hint::HybridClientDeviceSecurityKey => Err(Error::custom("'cross-platform' authenticator attachment modality must coincide with no hints or hints that lack 'client-device'")),
   3196                                                 }
   3197                                             }
   3198                                         }
   3199                                     })
   3200                                 })
   3201                             })?;
   3202                         }
   3203                         Field::Extensions => {
   3204                             if ext.is_some() {
   3205                                 return Err(Error::duplicate_field(EXTENSIONS));
   3206                             }
   3207                             ext = map.next_value::<Option<_>>().map(Some)?;
   3208                         }
   3209                         Field::Attestation => {
   3210                             if attest.is_some() {
   3211                                 return Err(Error::duplicate_field(ATTESTATION));
   3212                             }
   3213                             attest = map.next_value::<Option<Attestation>>().map(Some)?;
   3214                         }
   3215                         Field::AttestationFormats => {
   3216                             if formats.is_some() {
   3217                                 return Err(Error::duplicate_field(ATTESTATION_FORMATS));
   3218                             }
   3219                             formats = map.next_value::<Option<AttestationFormats>>().map(Some)?;
   3220                         }
   3221                     }
   3222                 }
   3223                 Ok(PublicKeyCredentialCreationOptionsOwned {
   3224                     rp_id: rp.flatten().flatten(),
   3225                     user: user_info.flatten().unwrap_or_default(),
   3226                     pub_key_cred_params: params.flatten().unwrap_or_default(),
   3227                     timeout: time.flatten().unwrap_or(FIVE_MINUTES),
   3228                     authenticator_selection: auth.unwrap_or(AuthenticatorSelectionCriteria {
   3229                         authenticator_attachment: AuthenticatorAttachmentReq::None(Hint::None),
   3230                         resident_key: ResidentKeyRequirement::Discouraged,
   3231                         user_verification: UserVerificationRequirement::Preferred,
   3232                     }),
   3233                     extensions: ext.flatten().unwrap_or_default(),
   3234                 })
   3235             }
   3236         }
   3237         /// Fields for `PublicKeyCredentialCreationOptionsOwned`.
   3238         const FIELDS: &[&str; 11] = &[
   3239             RP,
   3240             USER,
   3241             CHALLENGE,
   3242             PUB_KEY_CRED_PARAMS,
   3243             TIMEOUT,
   3244             EXCLUDE_CREDENTIALS,
   3245             AUTHENTICATOR_SELECTION,
   3246             HINTS,
   3247             EXTENSIONS,
   3248             ATTESTATION,
   3249             ATTESTATION_FORMATS,
   3250         ];
   3251         deserializer.deserialize_struct(
   3252             "PublicKeyCredentialCreationOptionsOwned",
   3253             FIELDS,
   3254             PublicKeyCredentialCreationOptionsOwnedVisitor(PhantomData),
   3255         )
   3256     }
   3257 }
   3258 /// Deserializes client-supplied data to assist in the creation of [`CredentialCreationOptions`].
   3259 ///
   3260 /// It's common to tailor a registration ceremony based on a user's environment. The options that should be
   3261 /// used are then sent to the server. For example, [`CredentialMediationRequirement::Conditional`] ceremonies
   3262 /// typically work best for [`AuthenticatorAttachment::Platform`] authenticators; a subset of which cannot
   3263 /// rely on [`UserVerificationRequirement::Required`]. Unfortunately one may not want to use
   3264 /// [`UserVerificationRequirement::Preferred`] unconditionally either since security keys may benefit from
   3265 /// [`CredProtect::UserVerificationRequired`] which can typically only be used when
   3266 /// [`UserVerificationRequirement::Required`] is requested since many user agents error otherwise.
   3267 ///
   3268 /// To facilitate this, [`Self::deserialize`] can be used to deserialize the data sent from the client.
   3269 #[derive(Debug)]
   3270 pub struct ClientCredentialCreationOptions<'user_name, 'user_display_name, const USER_LEN: usize> {
   3271     /// See [`CredentialCreationOptions::mediation`].
   3272     pub mediation: CredentialMediationRequirement,
   3273     /// See [`CredentialCreationOptions::public_key`].
   3274     pub public_key:
   3275         PublicKeyCredentialCreationOptionsOwned<'user_name, 'user_display_name, USER_LEN>,
   3276 }
   3277 impl<'de: 'user_name + 'user_display_name, 'user_name, 'user_display_name, const USER_LEN: usize>
   3278     Deserialize<'de> for ClientCredentialCreationOptions<'user_name, 'user_display_name, USER_LEN>
   3279 where
   3280     PublicKeyCredentialCreationOptionsOwned<'user_name, 'user_display_name, USER_LEN>:
   3281         Deserialize<'de>,
   3282 {
   3283     /// Deserializes a `struct` according to the following pseudo-schema:
   3284     ///
   3285     /// ```json
   3286     /// {
   3287     ///   "mediation": null | "required" | "conditional",
   3288     ///   "publicKey": null | <PublicKeyCredentialCreationOptionsOwned>
   3289     /// }
   3290     /// ```
   3291     ///
   3292     /// where none of the fields are required and `"publicKey"` is deserialized according to
   3293     /// [`PublicKeyCredentialCreationOptionsOwned::deserialize`]. If any field is missing or is `null`, then
   3294     /// the corresponding [`Default`] `impl` will be used.
   3295     ///
   3296     /// Unknown or duplicate fields lead to an error.
   3297     #[inline]
   3298     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   3299     where
   3300         D: Deserializer<'de>,
   3301     {
   3302         /// `Visitor` for `ClientCredentialCreationOptions`.
   3303         struct ClientCredentialCreationOptionsVisitor<'a, 'b, const LEN: usize>(
   3304             PhantomData<fn() -> (&'a (), &'b ())>,
   3305         );
   3306         impl<'d: 'a + 'b, 'a, 'b, const LEN: usize> Visitor<'d>
   3307             for ClientCredentialCreationOptionsVisitor<'a, 'b, LEN>
   3308         where
   3309             PublicKeyCredentialCreationOptionsOwned<'a, 'b, LEN>: Deserialize<'d>,
   3310         {
   3311             type Value = ClientCredentialCreationOptions<'a, 'b, LEN>;
   3312             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   3313                 formatter.write_str("ClientCredentialCreationOptions")
   3314             }
   3315             fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
   3316             where
   3317                 A: MapAccess<'d>,
   3318             {
   3319                 /// Field in `ClientCredentialCreationOptions`.
   3320                 enum Field {
   3321                     /// `mediation`.
   3322                     Mediation,
   3323                     /// `publicKey`
   3324                     PublicKey,
   3325                 }
   3326                 impl<'e> Deserialize<'e> for Field {
   3327                     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
   3328                     where
   3329                         D: Deserializer<'e>,
   3330                     {
   3331                         /// `Visitor` for `Field`.
   3332                         struct FieldVisitor;
   3333                         impl Visitor<'_> for FieldVisitor {
   3334                             type Value = Field;
   3335                             fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
   3336                                 write!(formatter, "'{MEDIATION}' or '{PUBLIC_KEY_NO_HYPEN}'")
   3337                             }
   3338                             fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
   3339                             where
   3340                                 E: Error,
   3341                             {
   3342                                 match v {
   3343                                     MEDIATION => Ok(Field::Mediation),
   3344                                     PUBLIC_KEY_NO_HYPEN => Ok(Field::PublicKey),
   3345                                     _ => Err(E::unknown_field(v, FIELDS)),
   3346                                 }
   3347                             }
   3348                         }
   3349                         deserializer.deserialize_identifier(FieldVisitor)
   3350                     }
   3351                 }
   3352                 let mut med = None;
   3353                 let mut key = None;
   3354                 while let Some(k) = map.next_key()? {
   3355                     match k {
   3356                         Field::Mediation => {
   3357                             if med.is_some() {
   3358                                 return Err(Error::duplicate_field(MEDIATION));
   3359                             }
   3360                             med = map.next_value::<Option<_>>().map(Some)?;
   3361                         }
   3362                         Field::PublicKey => {
   3363                             if key.is_some() {
   3364                                 return Err(Error::duplicate_field(PUBLIC_KEY_NO_HYPEN));
   3365                             }
   3366                             key = map.next_value::<Option<_>>().map(Some)?;
   3367                         }
   3368                     }
   3369                 }
   3370                 Ok(ClientCredentialCreationOptions {
   3371                     mediation: med.flatten().unwrap_or_default(),
   3372                     public_key: key.flatten().unwrap_or_default(),
   3373                 })
   3374             }
   3375         }
   3376         /// Fields for `ClientCredentialCreationOptions`.
   3377         const FIELDS: &[&str; 2] = &[MEDIATION, PUBLIC_KEY_NO_HYPEN];
   3378         deserializer.deserialize_struct(
   3379             "ClientCredentialCreationOptions",
   3380             FIELDS,
   3381             ClientCredentialCreationOptionsVisitor(PhantomData),
   3382         )
   3383     }
   3384 }
   3385 #[cfg(test)]
   3386 mod test {
   3387     use super::{
   3388         AuthenticatorAttachmentReq, AuthenticatorSelectionCriteria,
   3389         ClientCredentialCreationOptions, CoseAlgorithmIdentifier, CoseAlgorithmIdentifiers,
   3390         CredProtect, CredentialMediationRequirement, CrossPlatformHint, ExtensionInfo,
   3391         ExtensionOwned, ExtensionReq, FIVE_MINUTES, FourToSixtyThree, Hint, NonZeroU32,
   3392         PlatformHint, PublicKeyCredentialCreationOptionsOwned, PublicKeyCredentialUserEntityOwned,
   3393         ResidentKeyRequirement, UserVerificationRequirement,
   3394     };
   3395     use serde_json::Error;
   3396     #[expect(
   3397         clippy::panic_in_result_fn,
   3398         clippy::unwrap_used,
   3399         reason = "OK in tests"
   3400     )]
   3401     #[expect(
   3402         clippy::cognitive_complexity,
   3403         clippy::too_many_lines,
   3404         reason = "a lot to test"
   3405     )]
   3406     #[test]
   3407     fn client_options() -> Result<(), Error> {
   3408         let mut err =
   3409             serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 16>>(r#"{"bob":true}"#)
   3410                 .unwrap_err();
   3411         assert_eq!(
   3412             err.to_string().get(..56),
   3413             Some("unknown field `bob`, expected `mediation` or `publicKey`")
   3414         );
   3415         err = serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 1>>(
   3416             r#"{"mediation":"required","mediation":"required"}"#,
   3417         )
   3418         .unwrap_err();
   3419         assert_eq!(
   3420             err.to_string().get(..27),
   3421             Some("duplicate field `mediation`")
   3422         );
   3423         let mut options = serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 1>>("{}")?;
   3424         assert!(matches!(
   3425             options.mediation,
   3426             CredentialMediationRequirement::Required
   3427         ));
   3428         assert!(options.public_key.rp_id.is_none());
   3429         assert!(options.public_key.user.name.is_none());
   3430         assert!(options.public_key.user.id.is_none());
   3431         assert!(options.public_key.user.display_name.is_none());
   3432         assert_eq!(
   3433             options.public_key.pub_key_cred_params.0,
   3434             CoseAlgorithmIdentifiers::ALL.0
   3435         );
   3436         assert_eq!(options.public_key.timeout, FIVE_MINUTES);
   3437         assert!(
   3438             matches!(options.public_key.authenticator_selection.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   3439         );
   3440         assert!(matches!(
   3441             options.public_key.authenticator_selection.resident_key,
   3442             ResidentKeyRequirement::Discouraged
   3443         ));
   3444         assert!(matches!(
   3445             options.public_key.authenticator_selection.user_verification,
   3446             UserVerificationRequirement::Preferred
   3447         ));
   3448         assert!(options.public_key.extensions.cred_props.is_none());
   3449         assert!(matches!(
   3450             options.public_key.extensions.cred_protect,
   3451             CredProtect::None
   3452         ));
   3453         assert!(options.public_key.extensions.min_pin_length.is_none());
   3454         assert!(options.public_key.extensions.prf.is_none());
   3455         options = serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 1>>(
   3456             r#"{"mediation":null,"publicKey":null}"#,
   3457         )?;
   3458         assert!(matches!(
   3459             options.mediation,
   3460             CredentialMediationRequirement::Required
   3461         ));
   3462         assert!(options.public_key.rp_id.is_none());
   3463         assert!(options.public_key.user.name.is_none());
   3464         assert!(options.public_key.user.id.is_none());
   3465         assert!(options.public_key.user.display_name.is_none());
   3466         assert_eq!(
   3467             options.public_key.pub_key_cred_params.0,
   3468             CoseAlgorithmIdentifiers::ALL.0
   3469         );
   3470         assert_eq!(options.public_key.timeout, FIVE_MINUTES);
   3471         assert!(
   3472             matches!(options.public_key.authenticator_selection.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   3473         );
   3474         assert!(matches!(
   3475             options.public_key.authenticator_selection.resident_key,
   3476             ResidentKeyRequirement::Discouraged
   3477         ));
   3478         assert!(matches!(
   3479             options.public_key.authenticator_selection.user_verification,
   3480             UserVerificationRequirement::Preferred
   3481         ));
   3482         assert!(options.public_key.extensions.cred_props.is_none());
   3483         assert!(matches!(
   3484             options.public_key.extensions.cred_protect,
   3485             CredProtect::None
   3486         ));
   3487         assert!(options.public_key.extensions.min_pin_length.is_none());
   3488         assert!(options.public_key.extensions.prf.is_none());
   3489         options = serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 1>>(
   3490             r#"{"publicKey":{}}"#,
   3491         )?;
   3492         assert!(options.public_key.rp_id.is_none());
   3493         assert!(options.public_key.user.name.is_none());
   3494         assert!(options.public_key.user.id.is_none());
   3495         assert!(options.public_key.user.display_name.is_none());
   3496         assert_eq!(
   3497             options.public_key.pub_key_cred_params.0,
   3498             CoseAlgorithmIdentifiers::ALL.0
   3499         );
   3500         assert_eq!(options.public_key.timeout, FIVE_MINUTES);
   3501         assert!(
   3502             matches!(options.public_key.authenticator_selection.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   3503         );
   3504         assert!(matches!(
   3505             options.public_key.authenticator_selection.resident_key,
   3506             ResidentKeyRequirement::Discouraged
   3507         ));
   3508         assert!(matches!(
   3509             options.public_key.authenticator_selection.user_verification,
   3510             UserVerificationRequirement::Preferred
   3511         ));
   3512         assert!(options.public_key.extensions.cred_props.is_none());
   3513         assert!(matches!(
   3514             options.public_key.extensions.cred_protect,
   3515             CredProtect::None
   3516         ));
   3517         assert!(options.public_key.extensions.min_pin_length.is_none());
   3518         assert!(options.public_key.extensions.prf.is_none());
   3519         options = serde_json::from_str::<ClientCredentialCreationOptions<'_, '_, 1>>(
   3520             r#"{"mediation":"conditional","publicKey":{"rp":{"name":"Example.com","id":"example.com"},"user":{"name":"bob","displayName":"Bob","id":"AQ"},"timeout":300000,"excludeCredentials":[],"attestation":"none","attestationFormats":["none"],"authenticatorSelection":{"authenticatorAttachment":"cross-platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"},"extensions":{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"","second":""}}},"pubKeyCredParams":[{"type":"public-key","alg":-8}],"hints":["security-key"],"challenge":null}}"#,
   3521         )?;
   3522         assert!(matches!(
   3523             options.mediation,
   3524             CredentialMediationRequirement::Conditional
   3525         ));
   3526         assert!(
   3527             options
   3528                 .public_key
   3529                 .rp_id
   3530                 .is_some_and(|val| val.as_ref() == "example.com")
   3531         );
   3532         assert!(
   3533             options
   3534                 .public_key
   3535                 .user
   3536                 .name
   3537                 .is_some_and(|val| val.as_ref() == "bob")
   3538         );
   3539         assert!(
   3540             options
   3541                 .public_key
   3542                 .user
   3543                 .display_name
   3544                 .is_some_and(|val| val.as_ref() == "Bob")
   3545         );
   3546         assert!(
   3547             options
   3548                 .public_key
   3549                 .user
   3550                 .id
   3551                 .is_some_and(|val| val.as_ref() == [1; 1])
   3552         );
   3553         assert_eq!(
   3554             options.public_key.pub_key_cred_params.0,
   3555             CoseAlgorithmIdentifiers::ALL
   3556                 .remove(CoseAlgorithmIdentifier::Mldsa87)
   3557                 .remove(CoseAlgorithmIdentifier::Mldsa65)
   3558                 .remove(CoseAlgorithmIdentifier::Mldsa44)
   3559                 .remove(CoseAlgorithmIdentifier::Es256)
   3560                 .remove(CoseAlgorithmIdentifier::Es384)
   3561                 .remove(CoseAlgorithmIdentifier::Rs256)
   3562                 .0
   3563         );
   3564         assert_eq!(options.public_key.timeout, FIVE_MINUTES);
   3565         assert!(
   3566             matches!(options.public_key.authenticator_selection.authenticator_attachment, AuthenticatorAttachmentReq::CrossPlatform(hint) if matches!(hint, CrossPlatformHint::SecurityKey))
   3567         );
   3568         assert!(matches!(
   3569             options.public_key.authenticator_selection.resident_key,
   3570             ResidentKeyRequirement::Required
   3571         ));
   3572         assert!(matches!(
   3573             options.public_key.authenticator_selection.user_verification,
   3574             UserVerificationRequirement::Required
   3575         ));
   3576         assert!(
   3577             options
   3578                 .public_key
   3579                 .extensions
   3580                 .cred_props
   3581                 .is_some_and(|req| matches!(req, ExtensionReq::Allow))
   3582         );
   3583         assert!(
   3584             matches!(options.public_key.extensions.cred_protect, CredProtect::UserVerificationRequired(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
   3585         );
   3586         assert!(
   3587             options
   3588                 .public_key
   3589                 .extensions
   3590                 .min_pin_length
   3591                 .is_some_and(|min| min.0 == FourToSixtyThree::Four
   3592                     && matches!(min.1, ExtensionInfo::AllowEnforceValue))
   3593         );
   3594         assert!(
   3595             options
   3596                 .public_key
   3597                 .extensions
   3598                 .prf
   3599                 .is_some_and(|prf| prf.first.is_empty()
   3600                     && prf.second.is_some_and(|p| p.is_empty())
   3601                     && matches!(prf.ext_req, ExtensionReq::Allow))
   3602         );
   3603         Ok(())
   3604     }
   3605     #[expect(
   3606         clippy::panic_in_result_fn,
   3607         clippy::unwrap_used,
   3608         reason = "OK in tests"
   3609     )]
   3610     #[expect(
   3611         clippy::cognitive_complexity,
   3612         clippy::too_many_lines,
   3613         reason = "a lot to test"
   3614     )]
   3615     #[test]
   3616     fn key_options() -> Result<(), Error> {
   3617         let mut err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 16>>(
   3618             r#"{"bob":true}"#,
   3619         )
   3620         .unwrap_err();
   3621         assert_eq!(
   3622             err.to_string().get(..201),
   3623             Some(
   3624                 "unknown field `bob`, expected one of `rp`, `user`, `challenge`, `pubKeyCredParams`, `timeout`, `excludeCredentials`, `authenticatorSelection`, `hints`, `extensions`, `attestation`, `attestationFormats`"
   3625             )
   3626         );
   3627         err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3628             r#"{"attestation":"none","attestation":"none"}"#,
   3629         )
   3630         .unwrap_err();
   3631         assert_eq!(
   3632             err.to_string().get(..29),
   3633             Some("duplicate field `attestation`")
   3634         );
   3635         err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3636             r#"{"authenticatorSelection":{"authenticatorAttachment":"platform"},"hints":["client-device", "security-key"]}"#,
   3637         ).unwrap_err();
   3638         assert_eq!(
   3639             err.to_string().get(..96),
   3640             Some(
   3641                 "'platform' authenticator attachment modality must coincide with no hints or 'client-device' hint"
   3642             )
   3643         );
   3644         err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3645             r#"{"challenge":"AAAAAAAAAAAAAAAAAAAAAA"}"#,
   3646         )
   3647         .unwrap_err();
   3648         assert_eq!(
   3649             err.to_string().get(..41),
   3650             Some("invalid type: Option value, expected null")
   3651         );
   3652         err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3653             r#"{"excludeCredentials":[{"type":"public-key","transports":["usb"],"id":"AAAAAAAAAAAAAAAAAAAAAA"}]}"#,
   3654         )
   3655         .unwrap_err();
   3656         assert_eq!(err.to_string().get(..19), Some("trailing characters"));
   3657         err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3658             r#"{"attestation":"foo"}"#,
   3659         )
   3660         .unwrap_err();
   3661         assert_eq!(
   3662             err.to_string().get(..27),
   3663             Some("invalid value: string \"foo\"")
   3664         );
   3665         err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3666             r#"{"attestationFormats":["none","none"]}"#,
   3667         )
   3668         .unwrap_err();
   3669         assert_eq!(
   3670             err.to_string().get(..96),
   3671             Some(
   3672                 "attestationFormats must be an empty sequence or contain exactly one string whose value is 'none'"
   3673             )
   3674         );
   3675         err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3676             r#"{"attestationFormats":["foo"]}"#,
   3677         )
   3678         .unwrap_err();
   3679         assert_eq!(
   3680             err.to_string().get(..42),
   3681             Some("invalid value: string \"foo\", expected none")
   3682         );
   3683         err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3684             r#"{"timeout":0}"#,
   3685         )
   3686         .unwrap_err();
   3687         assert_eq!(
   3688             err.to_string().get(..50),
   3689             Some("invalid value: integer `0`, expected a nonzero u32")
   3690         );
   3691         err = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3692             r#"{"timeout":4294967296}"#,
   3693         )
   3694         .unwrap_err();
   3695         assert_eq!(
   3696             err.to_string().get(..59),
   3697             Some("invalid value: integer `4294967296`, expected a nonzero u32")
   3698         );
   3699         let mut key =
   3700             serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>("{}")?;
   3701         assert!(key.rp_id.is_none());
   3702         assert!(key.user.name.is_none());
   3703         assert!(key.user.id.is_none());
   3704         assert!(key.user.display_name.is_none());
   3705         assert_eq!(key.pub_key_cred_params.0, CoseAlgorithmIdentifiers::ALL.0);
   3706         assert_eq!(key.timeout, FIVE_MINUTES);
   3707         assert!(
   3708             matches!(key.authenticator_selection.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   3709         );
   3710         assert!(matches!(
   3711             key.authenticator_selection.resident_key,
   3712             ResidentKeyRequirement::Discouraged
   3713         ));
   3714         assert!(matches!(
   3715             key.authenticator_selection.user_verification,
   3716             UserVerificationRequirement::Preferred
   3717         ));
   3718         assert!(key.extensions.cred_props.is_none());
   3719         assert!(matches!(key.extensions.cred_protect, CredProtect::None));
   3720         assert!(key.extensions.min_pin_length.is_none());
   3721         assert!(key.extensions.prf.is_none());
   3722         key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3723             r#"{"rp":null,"user":null,"timeout":null,"excludeCredentials":null,"attestation":null,"attestationFormats":null,"authenticatorSelection":null,"extensions":null,"pubKeyCredParams":null,"hints":null,"challenge":null}"#,
   3724         )?;
   3725         assert!(key.rp_id.is_none());
   3726         assert!(key.user.name.is_none());
   3727         assert!(key.user.id.is_none());
   3728         assert!(key.user.display_name.is_none());
   3729         assert_eq!(key.pub_key_cred_params.0, CoseAlgorithmIdentifiers::ALL.0);
   3730         assert_eq!(key.timeout, FIVE_MINUTES);
   3731         assert!(
   3732             matches!(key.authenticator_selection.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   3733         );
   3734         assert!(matches!(
   3735             key.authenticator_selection.resident_key,
   3736             ResidentKeyRequirement::Discouraged
   3737         ));
   3738         assert!(matches!(
   3739             key.authenticator_selection.user_verification,
   3740             UserVerificationRequirement::Preferred
   3741         ));
   3742         assert!(key.extensions.cred_props.is_none());
   3743         assert!(matches!(key.extensions.cred_protect, CredProtect::None));
   3744         assert!(key.extensions.min_pin_length.is_none());
   3745         assert!(key.extensions.prf.is_none());
   3746         key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3747             r#"{"rp":{},"user":{},"excludeCredentials":[],"attestationFormats":[],"authenticatorSelection":{},"extensions":{},"pubKeyCredParams":[],"hints":[]}"#,
   3748         )?;
   3749         assert!(key.rp_id.is_none());
   3750         assert!(key.user.name.is_none());
   3751         assert!(key.user.id.is_none());
   3752         assert!(key.user.display_name.is_none());
   3753         assert_eq!(key.pub_key_cred_params.0, CoseAlgorithmIdentifiers::ALL.0);
   3754         assert!(
   3755             matches!(key.authenticator_selection.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   3756         );
   3757         assert!(matches!(
   3758             key.authenticator_selection.resident_key,
   3759             ResidentKeyRequirement::Discouraged
   3760         ));
   3761         assert!(matches!(
   3762             key.authenticator_selection.user_verification,
   3763             UserVerificationRequirement::Preferred
   3764         ));
   3765         assert!(key.extensions.cred_props.is_none());
   3766         assert!(matches!(key.extensions.cred_protect, CredProtect::None));
   3767         assert!(key.extensions.min_pin_length.is_none());
   3768         assert!(key.extensions.prf.is_none());
   3769         key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3770             r#"{"rp":{"name":null,"id":null},"user":{"name":null,"id":null,"displayName":null},"authenticatorSelection":{"residentKey":null,"requireResidentKey":null,"userVerification":null,"authenticatorAttachment":null},"extensions":{"credProps":null,"credentialProtectionPolicy":null,"enforceCredentialProtectionPolicy":null,"minPinLength":null,"prf":null}}"#,
   3771         )?;
   3772         assert!(key.rp_id.is_none());
   3773         assert!(key.user.name.is_none());
   3774         assert!(key.user.id.is_none());
   3775         assert!(key.user.display_name.is_none());
   3776         assert_eq!(key.pub_key_cred_params.0, CoseAlgorithmIdentifiers::ALL.0);
   3777         assert!(
   3778             matches!(key.authenticator_selection.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   3779         );
   3780         assert!(matches!(
   3781             key.authenticator_selection.resident_key,
   3782             ResidentKeyRequirement::Discouraged
   3783         ));
   3784         assert!(matches!(
   3785             key.authenticator_selection.user_verification,
   3786             UserVerificationRequirement::Preferred
   3787         ));
   3788         assert!(key.extensions.cred_props.is_none());
   3789         assert!(matches!(key.extensions.cred_protect, CredProtect::None));
   3790         assert!(key.extensions.min_pin_length.is_none());
   3791         assert!(key.extensions.prf.is_none());
   3792         key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3793             r#"{"rp":{"name":"Example.com","id":"example.com"},"user":{"name":"bob","displayName":"Bob","id":"AQ"},"timeout":300000,"excludeCredentials":[],"attestation":"none","attestationFormats":["none"],"authenticatorSelection":{"authenticatorAttachment":"cross-platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"},"extensions":{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"","second":""}}},"pubKeyCredParams":[{"type":"public-key","alg":-8}],"hints":["security-key"],"challenge":null}"#,
   3794         )?;
   3795         assert!(key.rp_id.is_some_and(|val| val.as_ref() == "example.com"));
   3796         assert!(key.user.name.is_some_and(|val| val.as_ref() == "bob"));
   3797         assert!(
   3798             key.user
   3799                 .display_name
   3800                 .is_some_and(|val| val.as_ref() == "Bob")
   3801         );
   3802         assert!(key.user.id.is_some_and(|val| val.as_ref() == [1; 1]));
   3803         assert_eq!(
   3804             key.pub_key_cred_params.0,
   3805             CoseAlgorithmIdentifiers::ALL
   3806                 .remove(CoseAlgorithmIdentifier::Mldsa87)
   3807                 .remove(CoseAlgorithmIdentifier::Mldsa65)
   3808                 .remove(CoseAlgorithmIdentifier::Mldsa44)
   3809                 .remove(CoseAlgorithmIdentifier::Es256)
   3810                 .remove(CoseAlgorithmIdentifier::Es384)
   3811                 .remove(CoseAlgorithmIdentifier::Rs256)
   3812                 .0
   3813         );
   3814         assert_eq!(key.timeout, FIVE_MINUTES);
   3815         assert!(
   3816             matches!(key.authenticator_selection.authenticator_attachment, AuthenticatorAttachmentReq::CrossPlatform(hint) if matches!(hint, CrossPlatformHint::SecurityKey))
   3817         );
   3818         assert!(matches!(
   3819             key.authenticator_selection.resident_key,
   3820             ResidentKeyRequirement::Required
   3821         ));
   3822         assert!(matches!(
   3823             key.authenticator_selection.user_verification,
   3824             UserVerificationRequirement::Required
   3825         ));
   3826         assert!(
   3827             key.extensions
   3828                 .cred_props
   3829                 .is_some_and(|req| matches!(req, ExtensionReq::Allow))
   3830         );
   3831         assert!(
   3832             matches!(key.extensions.cred_protect, CredProtect::UserVerificationRequired(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
   3833         );
   3834         assert!(
   3835             key.extensions
   3836                 .min_pin_length
   3837                 .is_some_and(|min| min.0 == FourToSixtyThree::Four
   3838                     && matches!(min.1, ExtensionInfo::AllowEnforceValue))
   3839         );
   3840         assert!(key.extensions.prf.is_some_and(|prf| prf.first.is_empty()
   3841             && prf.second.is_some_and(|p| p.is_empty())
   3842             && matches!(prf.ext_req, ExtensionReq::Allow)));
   3843         key = serde_json::from_str::<PublicKeyCredentialCreationOptionsOwned<'_, '_, 1>>(
   3844             r#"{"timeout":4294967295}"#,
   3845         )?;
   3846         assert_eq!(key.timeout, NonZeroU32::MAX);
   3847         Ok(())
   3848     }
   3849     #[expect(
   3850         clippy::panic_in_result_fn,
   3851         clippy::unwrap_used,
   3852         reason = "OK in tests"
   3853     )]
   3854     #[expect(clippy::cognitive_complexity, reason = "a lot to test")]
   3855     #[test]
   3856     fn extension() -> Result<(), Error> {
   3857         let mut err = serde_json::from_str::<ExtensionOwned>(r#"{"bob":true}"#).unwrap_err();
   3858         assert_eq!(
   3859             err.to_string().get(..138),
   3860             Some(
   3861                 "unknown field `bob`, expected one of `credProps`, `credentialProtectionPolicy`, `enforceCredentialProtectionPolicy`, `minPinLength`, `prf`"
   3862             )
   3863         );
   3864         err = serde_json::from_str::<ExtensionOwned>(r#"{"credProps":true,"credProps":true}"#)
   3865             .unwrap_err();
   3866         assert_eq!(
   3867             err.to_string().get(..27),
   3868             Some("duplicate field `credProps`")
   3869         );
   3870         err =
   3871             serde_json::from_str::<ExtensionOwned>(r#"{"enforceCredentialProtectionPolicy":null}"#)
   3872                 .unwrap_err();
   3873         assert_eq!(
   3874             err.to_string().get(..84),
   3875             Some(
   3876                 "'enforceCredentialProtectionPolicy' must not exist when 'credentialProtectionPolicy'"
   3877             )
   3878         );
   3879         err = serde_json::from_str::<ExtensionOwned>(
   3880             r#"{"enforceCredentialProtectionPolicy":false,"credentialProtectionPolicy":null}"#,
   3881         )
   3882         .unwrap_err();
   3883         assert_eq!(
   3884             err.to_string().get(..103),
   3885             Some(
   3886                 "'enforceCredentialProtectionPolicy' must be null or not exist when 'credentialProtectionPolicy' is null"
   3887             )
   3888         );
   3889         let mut ext = serde_json::from_str::<ExtensionOwned>(
   3890             r#"{"credProps":true,"credentialProtectionPolicy":"userVerificationRequired","enforceCredentialProtectionPolicy":false,"minPinLength":true,"prf":{"eval":{"first":"","second":""}}}"#,
   3891         )?;
   3892         assert!(
   3893             ext.cred_props
   3894                 .is_some_and(|props| matches!(props, ExtensionReq::Allow))
   3895         );
   3896         assert!(
   3897             matches!(ext.cred_protect, CredProtect::UserVerificationRequired(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
   3898         );
   3899         assert!(
   3900             ext.min_pin_length
   3901                 .is_some_and(|min| min.0 == FourToSixtyThree::Four
   3902                     && matches!(min.1, ExtensionInfo::AllowEnforceValue))
   3903         );
   3904         assert!(ext.prf.is_some_and(|prf| prf.first.is_empty()
   3905             && prf.second.is_some_and(|v| v.is_empty())
   3906             && matches!(prf.ext_req, ExtensionReq::Allow)));
   3907         ext = serde_json::from_str::<ExtensionOwned>(
   3908             r#"{"credProps":null,"credentialProtectionPolicy":null,"enforceCredentialProtectionPolicy":null,"minPinLength":null,"prf":null}"#,
   3909         )?;
   3910         assert!(ext.cred_props.is_none());
   3911         assert!(matches!(ext.cred_protect, CredProtect::None));
   3912         assert!(ext.min_pin_length.is_none());
   3913         assert!(ext.prf.is_none());
   3914         ext = serde_json::from_str::<ExtensionOwned>("{}")?;
   3915         assert!(ext.cred_props.is_none());
   3916         assert!(matches!(ext.cred_protect, CredProtect::None));
   3917         assert!(ext.min_pin_length.is_none());
   3918         assert!(ext.prf.is_none());
   3919         ext = serde_json::from_str::<ExtensionOwned>(r#"{"credentialProtectionPolicy":null}"#)?;
   3920         assert!(matches!(ext.cred_protect, CredProtect::None));
   3921         ext = serde_json::from_str::<ExtensionOwned>(
   3922             r#"{"credentialProtectionPolicy":"userVerificationOptional"}"#,
   3923         )?;
   3924         assert!(
   3925             matches!(ext.cred_protect, CredProtect::UserVerificationOptional(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
   3926         );
   3927         ext = serde_json::from_str::<ExtensionOwned>(
   3928             r#"{"credentialProtectionPolicy":"userVerificationOptionalWithCredentialIDList","enforceCredentialProtectionPolicy":null}"#,
   3929         )?;
   3930         assert!(
   3931             matches!(ext.cred_protect, CredProtect::UserVerificationOptionalWithCredentialIdList(enforce, info) if !enforce && matches!(info, ExtensionInfo::AllowEnforceValue))
   3932         );
   3933         Ok(())
   3934     }
   3935     #[expect(
   3936         clippy::panic_in_result_fn,
   3937         clippy::unwrap_used,
   3938         reason = "OK in tests"
   3939     )]
   3940     #[test]
   3941     fn user_entity() -> Result<(), Error> {
   3942         let mut err = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 16>>(
   3943             r#"{"bob":true}"#,
   3944         )
   3945         .unwrap_err();
   3946         assert_eq!(
   3947             err.to_string().get(..64),
   3948             Some("unknown field `bob`, expected one of `id`, `name`, `displayName`")
   3949         );
   3950         err = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 1>>(
   3951             r#"{"name":"bob","name":"bob"}"#,
   3952         )
   3953         .unwrap_err();
   3954         assert_eq!(err.to_string().get(..22), Some("duplicate field `name`"));
   3955         let mut user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 1>>(
   3956             r#"{"id":"AQ","name":"bob","displayName":"Bob"}"#,
   3957         )?;
   3958         assert!(
   3959             user.id
   3960                 .is_some_and(|val| val.as_slice() == [1; 1].as_slice())
   3961         );
   3962         assert!(user.name.is_some_and(|val| val.as_ref() == "bob"));
   3963         assert!(user.display_name.is_some_and(|val| val.as_ref() == "Bob"));
   3964         user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 1>>(
   3965             r#"{"id":null,"name":null,"displayName":null}"#,
   3966         )?;
   3967         assert!(user.name.is_none());
   3968         assert!(user.display_name.is_none());
   3969         assert!(user.id.is_none());
   3970         user = serde_json::from_str::<PublicKeyCredentialUserEntityOwned<'_, '_, 1>>("{}")?;
   3971         assert!(user.name.is_none());
   3972         assert!(user.display_name.is_none());
   3973         assert!(user.id.is_none());
   3974         Ok(())
   3975     }
   3976     #[expect(
   3977         clippy::panic_in_result_fn,
   3978         clippy::unwrap_used,
   3979         reason = "OK in tests"
   3980     )]
   3981     #[expect(
   3982         clippy::cognitive_complexity,
   3983         clippy::too_many_lines,
   3984         reason = "a lot to test"
   3985     )]
   3986     #[test]
   3987     fn auth_crit() -> Result<(), Error> {
   3988         let mut err = serde_json::from_str::<AuthenticatorSelectionCriteria>("null").unwrap_err();
   3989         assert_eq!(
   3990             err.to_string().get(..59),
   3991             Some("invalid type: null, expected AuthenticatorSelectionCriteria")
   3992         );
   3993         err = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   3994             r#"{"residentKey":"required","requireResidentKey":false}"#,
   3995         )
   3996         .unwrap_err();
   3997         assert_eq!(
   3998             err.to_string().get(..62),
   3999             Some("'residentKey' is 'required', but 'requireResidentKey' is false")
   4000         );
   4001         err = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4002             r#"{"residentKey":"preferred","requireResidentKey":true}"#,
   4003         )
   4004         .unwrap_err();
   4005         assert_eq!(
   4006             err.to_string().get(..65),
   4007             Some("'residentKey' is not 'required', but 'requireResidentKey' is true")
   4008         );
   4009         err =
   4010             serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"residentKey":"prefered"}"#)
   4011                 .unwrap_err();
   4012         assert_eq!(
   4013             err.to_string().get(..84),
   4014             Some(
   4015                 "invalid value: string \"prefered\", expected 'required', 'discouraged', or 'preferred'"
   4016             )
   4017         );
   4018         err =
   4019             serde_json::from_str::<AuthenticatorSelectionCriteria>(r#"{"bob":true}"#).unwrap_err();
   4020         assert_eq!(
   4021             err.to_string().get(..119),
   4022             Some(
   4023                 "unknown field `bob`, expected one of `authenticatorAttachment`, `residentKey`, `requireResidentKey`, `userVerification`"
   4024             )
   4025         );
   4026         err = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4027             r#"{"requireResidentKey":true,"requireResidentKey":true}"#,
   4028         )
   4029         .unwrap_err();
   4030         assert_eq!(
   4031             err.to_string().get(..36),
   4032             Some("duplicate field `requireResidentKey`")
   4033         );
   4034         let mut crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4035             r#"{"authenticatorAttachment":"platform","residentKey":"required","requireResidentKey":true,"userVerification":"required"}"#,
   4036         )?;
   4037         assert!(
   4038             matches!(crit.authenticator_attachment, AuthenticatorAttachmentReq::Platform(hint) if matches!(hint, PlatformHint::None))
   4039         );
   4040         assert!(matches!(
   4041             crit.resident_key,
   4042             ResidentKeyRequirement::Required
   4043         ));
   4044         assert!(matches!(
   4045             crit.user_verification,
   4046             UserVerificationRequirement::Required
   4047         ));
   4048         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4049             r#"{"authenticatorAttachment":null,"residentKey":null,"requireResidentKey":null,"userVerification":null}"#,
   4050         )?;
   4051         assert!(
   4052             matches!(crit.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   4053         );
   4054         assert!(matches!(
   4055             crit.resident_key,
   4056             ResidentKeyRequirement::Discouraged
   4057         ));
   4058         assert!(matches!(
   4059             crit.user_verification,
   4060             UserVerificationRequirement::Preferred
   4061         ));
   4062         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>("{}")?;
   4063         assert!(
   4064             matches!(crit.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   4065         );
   4066         assert!(matches!(
   4067             crit.resident_key,
   4068             ResidentKeyRequirement::Discouraged
   4069         ));
   4070         assert!(matches!(
   4071             crit.user_verification,
   4072             UserVerificationRequirement::Preferred
   4073         ));
   4074         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4075             r#"{"residentKey":"preferred","requireResidentKey":false}"#,
   4076         )?;
   4077         assert!(
   4078             matches!(crit.authenticator_attachment, AuthenticatorAttachmentReq::None(hint) if matches!(hint, Hint::None))
   4079         );
   4080         assert!(matches!(
   4081             crit.resident_key,
   4082             ResidentKeyRequirement::Preferred
   4083         ));
   4084         assert!(matches!(
   4085             crit.user_verification,
   4086             UserVerificationRequirement::Preferred
   4087         ));
   4088         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4089             r#"{"residentKey":"preferred"}"#,
   4090         )?;
   4091         assert!(matches!(
   4092             crit.resident_key,
   4093             ResidentKeyRequirement::Preferred
   4094         ));
   4095         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4096             r#"{"requireResidentKey":true}"#,
   4097         )?;
   4098         assert!(matches!(
   4099             crit.resident_key,
   4100             ResidentKeyRequirement::Required
   4101         ));
   4102         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4103             r#"{"requireResidentKey":false}"#,
   4104         )?;
   4105         assert!(matches!(
   4106             crit.resident_key,
   4107             ResidentKeyRequirement::Discouraged
   4108         ));
   4109         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4110             r#"{"residentKey":"required"}"#,
   4111         )?;
   4112         assert!(matches!(
   4113             crit.resident_key,
   4114             ResidentKeyRequirement::Required
   4115         ));
   4116         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4117             r#"{"residentKey":"discouraged"}"#,
   4118         )?;
   4119         assert!(matches!(
   4120             crit.resident_key,
   4121             ResidentKeyRequirement::Discouraged
   4122         ));
   4123         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4124             r#"{"residentKey":"discouraged","requireResidentKey":null}"#,
   4125         )?;
   4126         assert!(matches!(
   4127             crit.resident_key,
   4128             ResidentKeyRequirement::Discouraged
   4129         ));
   4130         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4131             r#"{"residentKey":"required","requireResidentKey":null}"#,
   4132         )?;
   4133         assert!(matches!(
   4134             crit.resident_key,
   4135             ResidentKeyRequirement::Required
   4136         ));
   4137         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4138             r#"{"residentKey":null,"requireResidentKey":true}"#,
   4139         )?;
   4140         assert!(matches!(
   4141             crit.resident_key,
   4142             ResidentKeyRequirement::Required
   4143         ));
   4144         crit = serde_json::from_str::<AuthenticatorSelectionCriteria>(
   4145             r#"{"residentKey":null,"requireResidentKey":false}"#,
   4146         )?;
   4147         assert!(matches!(
   4148             crit.resident_key,
   4149             ResidentKeyRequirement::Discouraged
   4150         ));
   4151         Ok(())
   4152     }
   4153     #[expect(
   4154         clippy::panic_in_result_fn,
   4155         clippy::unwrap_used,
   4156         reason = "OK in tests"
   4157     )]
   4158     #[test]
   4159     fn cose_algs() -> Result<(), Error> {
   4160         let mut err = serde_json::from_str::<CoseAlgorithmIdentifiers>("null").unwrap_err();
   4161         assert_eq!(
   4162             err.to_string().get(..53),
   4163             Some("invalid type: null, expected CoseAlgorithmIdentifiers")
   4164         );
   4165         err = serde_json::from_str::<CoseAlgorithmIdentifiers>("[null]").unwrap_err();
   4166         assert_eq!(
   4167             err.to_string().get(..37),
   4168             Some("invalid type: null, expected PubParam")
   4169         );
   4170         err = serde_json::from_str::<CoseAlgorithmIdentifiers>("[{}]").unwrap_err();
   4171         assert_eq!(err.to_string().get(..19), Some("missing field `alg`"));
   4172         err = serde_json::from_str::<CoseAlgorithmIdentifiers>(
   4173             r#"[{"type":"public-key","alg":-7,"foo":true}]"#,
   4174         )
   4175         .unwrap_err();
   4176         assert_eq!(
   4177             err.to_string().get(..45),
   4178             Some("unknown field `foo`, expected `type` or `alg`")
   4179         );
   4180         err = serde_json::from_str::<CoseAlgorithmIdentifiers>(
   4181             r#"[{"type":"public-key","alg":-7,"alg":-7}]"#,
   4182         )
   4183         .unwrap_err();
   4184         assert_eq!(err.to_string().get(..21), Some("duplicate field `alg`"));
   4185         err = serde_json::from_str::<CoseAlgorithmIdentifiers>(
   4186             r#"[{"type":"public-key","alg":null}]"#,
   4187         )
   4188         .unwrap_err();
   4189         assert_eq!(
   4190             err.to_string().get(..52),
   4191             Some("invalid type: null, expected CoseAlgorithmIdentifier")
   4192         );
   4193         err = serde_json::from_str::<CoseAlgorithmIdentifiers>(r#"[{"type":null,"alg":-8}]"#)
   4194             .unwrap_err();
   4195         assert_eq!(
   4196             err.to_string().get(..39),
   4197             Some("invalid type: null, expected public-key")
   4198         );
   4199         err =
   4200             serde_json::from_str::<CoseAlgorithmIdentifiers>(r#"[{"type":"public-key","alg":-6}]"#)
   4201                 .unwrap_err();
   4202         assert_eq!(
   4203             err.to_string().get(..73),
   4204             Some("invalid value: integer `-6`, expected -50, -49, -48, -8, -7, -35, or -257")
   4205         );
   4206         err = serde_json::from_str::<CoseAlgorithmIdentifiers>(
   4207             r#"[{"type":"public-key","alg":-7},{"type":"public-key","alg":-7}]"#,
   4208         )
   4209         .unwrap_err();
   4210         assert_eq!(
   4211             err.to_string().get(..49),
   4212             Some("pubKeyCredParams contained duplicate Es256 values")
   4213         );
   4214         err = serde_json::from_str::<CoseAlgorithmIdentifiers>(
   4215             r#"[{"type":"public-key","alg":-7},{"type":"public-key","alg":-8}]"#,
   4216         )
   4217         .unwrap_err();
   4218         assert_eq!(
   4219             err.to_string().get(..79),
   4220             Some("pubKeyCredParams contained Eddsa, but it was preceded by Es256, Es384, or Rs256")
   4221         );
   4222         let mut alg = serde_json::from_str::<CoseAlgorithmIdentifiers>(
   4223             r#"[{"type":"public-key","alg":-8},{"alg":-7}]"#,
   4224         )?;
   4225         assert!(alg.contains(CoseAlgorithmIdentifier::Eddsa));
   4226         assert!(alg.contains(CoseAlgorithmIdentifier::Es256));
   4227         assert!(!alg.contains(CoseAlgorithmIdentifier::Mldsa87));
   4228         assert!(!alg.contains(CoseAlgorithmIdentifier::Mldsa65));
   4229         assert!(!alg.contains(CoseAlgorithmIdentifier::Mldsa44));
   4230         assert!(!alg.contains(CoseAlgorithmIdentifier::Es384));
   4231         assert!(!alg.contains(CoseAlgorithmIdentifier::Rs256));
   4232         alg = serde_json::from_str::<CoseAlgorithmIdentifiers>("[]")?;
   4233         assert!(alg.contains(CoseAlgorithmIdentifier::Mldsa87));
   4234         assert!(alg.contains(CoseAlgorithmIdentifier::Mldsa65));
   4235         assert!(alg.contains(CoseAlgorithmIdentifier::Mldsa44));
   4236         assert!(alg.contains(CoseAlgorithmIdentifier::Eddsa));
   4237         assert!(alg.contains(CoseAlgorithmIdentifier::Es256));
   4238         assert!(alg.contains(CoseAlgorithmIdentifier::Es384));
   4239         assert!(alg.contains(CoseAlgorithmIdentifier::Rs256));
   4240         Ok(())
   4241     }
   4242 }