@@ -26,6 +26,7 @@ pub enum TlsClientDetailsError {
2626 } ,
2727}
2828
29+ #[ repr( transparent) ]
2930#[ derive(
3031 Clone , Debug , Deserialize , Eq , Hash , JsonSchema , Ord , PartialEq , PartialOrd , Serialize ,
3132) ]
@@ -35,6 +36,40 @@ pub struct TlsClientDetails {
3536 pub tls : Option < Tls > ,
3637}
3738
39+ #[ repr( transparent) ]
40+ #[ derive(
41+ Clone , Debug , Deserialize , Eq , Hash , JsonSchema , Ord , PartialEq , PartialOrd , Serialize ,
42+ ) ]
43+ #[ serde( rename_all = "camelCase" ) ]
44+ pub struct TlsClientDetailsWithSecureDefaults {
45+ /// Configure a TLS connection. If not specified it will default to webPki validation.
46+ #[ serde( default = "default_web_pki_tls" ) ]
47+ pub tls : Option < Tls > ,
48+ }
49+
50+ impl std:: ops:: Deref for TlsClientDetailsWithSecureDefaults {
51+ type Target = TlsClientDetails ;
52+
53+ fn deref ( & self ) -> & TlsClientDetails {
54+ // SAFETY: both types are `#[repr(transparent)]` over `Option<Tls>`, so they share
55+ // the same memory layout and this cast is sound.
56+ //
57+ // This cannot silently break due to struct changes: `#[repr(transparent)]` requires
58+ // exactly one non-zero-sized field, so adding a second real field to either struct
59+ // is a compile error. The only scenario that would NOT be caught at compile time is
60+ // deliberately removing `#[repr(transparent)]` from one of the two structs.
61+ unsafe { & * ( self as * const Self as * const TlsClientDetails ) }
62+ }
63+ }
64+
65+ fn default_web_pki_tls ( ) -> Option < Tls > {
66+ Some ( Tls {
67+ verification : TlsVerification :: Server ( TlsServerVerification {
68+ ca_cert : CaCert :: WebPki { } ,
69+ } ) ,
70+ } )
71+ }
72+
3873impl TlsClientDetails {
3974 /// This functions adds
4075 ///
@@ -165,3 +200,51 @@ pub enum CaCert {
165200 /// so if you got provided with a CA cert but don't have access to the key you can still use this method.
166201 SecretClass ( String ) ,
167202}
203+
204+ #[ cfg( test) ]
205+ mod tests {
206+ use super :: * ;
207+ use crate :: utils:: yaml_from_str_singleton_map;
208+
209+ #[ test]
210+ fn tls_client_details_with_secure_defaults_deserialization ( ) {
211+ // No tls key at all → WebPki default kicks in
212+ let parsed: TlsClientDetailsWithSecureDefaults =
213+ yaml_from_str_singleton_map ( "{}" ) . expect ( "failed to deserialize empty input" ) ;
214+ assert_eq ! ( parsed. tls, default_web_pki_tls( ) ) ;
215+
216+ // Explicit null → opt out of TLS entirely
217+ let parsed: TlsClientDetailsWithSecureDefaults =
218+ yaml_from_str_singleton_map ( "tls: null" ) . expect ( "failed to deserialize tls: null" ) ;
219+ assert_eq ! ( parsed. tls, None ) ;
220+
221+ // Explicit SecretClass value is preserved as-is
222+ let parsed: TlsClientDetailsWithSecureDefaults = yaml_from_str_singleton_map (
223+ "tls:
224+ verification:
225+ server:
226+ caCert:
227+ secretClass: my-ca" ,
228+ )
229+ . expect ( "failed to deserialize secretClass" ) ;
230+ assert_eq ! (
231+ parsed. tls,
232+ Some ( Tls {
233+ verification: TlsVerification :: Server ( TlsServerVerification {
234+ ca_cert: CaCert :: SecretClass ( "my-ca" . to_owned( ) ) ,
235+ } ) ,
236+ } )
237+ ) ;
238+ }
239+
240+ #[ test]
241+ #[ allow( clippy:: explicit_auto_deref) ]
242+ fn tls_client_details_with_secure_defaults_deref ( ) {
243+ let secure: TlsClientDetailsWithSecureDefaults =
244+ yaml_from_str_singleton_map ( "{}" ) . expect ( "failed to deserialize" ) ;
245+
246+ // Deref must not panic and must expose the same tls value
247+ let tls_client_details: & TlsClientDetails = & * secure;
248+ assert_eq ! ( tls_client_details. tls, secure. tls) ;
249+ }
250+ }
0 commit comments