11#[ cfg( test) ]
22use std:: cell:: Cell ;
33use std:: {
4- fmt,
4+ fmt:: Display ,
55 net:: Ipv4Addr ,
66 str:: FromStr ,
77 time:: { SystemTime , UNIX_EPOCH } ,
@@ -15,12 +15,12 @@ use axum::{
1515} ;
1616use bytes:: Bytes ;
1717use futures:: StreamExt ;
18+ use headers_accept:: Accept ;
1819pub use lh_types:: ForkName ;
1920use lh_types:: test_utils:: { SeedableRng , TestRandom , XorShiftRng } ;
20- use mediatype:: { MediaType , MediaTypeList , names} ;
2121use rand:: { Rng , distr:: Alphanumeric } ;
2222use reqwest:: {
23- Response , StatusCode ,
23+ Response ,
2424 header:: { ACCEPT , CONTENT_TYPE , HeaderMap } ,
2525} ;
2626use serde:: { Serialize , de:: DeserializeOwned } ;
@@ -42,6 +42,10 @@ use crate::{
4242 types:: { BlsPublicKey , Chain , Jwt , JwtClaims , ModuleId } ,
4343} ;
4444
45+ const APPLICATION_JSON : & str = "application/json" ;
46+ const APPLICATION_OCTET_STREAM : & str = "application/octet-stream" ;
47+ const WILDCARD : & str = "*/*" ;
48+
4549const MILLIS_PER_SECOND : u64 = 1_000 ;
4650pub const CONSENSUS_VERSION_HEADER : & str = "Eth-Consensus-Version" ;
4751
@@ -421,23 +425,52 @@ pub fn get_user_agent_with_version(req_headers: &HeaderMap) -> eyre::Result<Head
421425 Ok ( HeaderValue :: from_str ( & format ! ( "commit-boost/{HEADER_VERSION_VALUE} {ua}" ) ) ?)
422426}
423427
424- /// Parse ACCEPT header, default to JSON if missing or mal-formatted
425- pub fn get_accept_header ( req_headers : & HeaderMap ) -> Accept {
426- Accept :: from_str (
427- req_headers. get ( ACCEPT ) . and_then ( |value| value. to_str ( ) . ok ( ) ) . unwrap_or ( "application/json" ) ,
428+ /// Parse the ACCEPT header to get the type of response to encode the body with,
429+ /// defaulting to JSON if missing. Returns an error if malformed or unsupported
430+ /// types are requested. Supports requests with multiple ACCEPT headers or
431+ /// headers with multiple media types.
432+ pub fn get_accept_type ( req_headers : & HeaderMap ) -> eyre:: Result < EncodingType > {
433+ let accept = Accept :: from_str (
434+ req_headers. get ( ACCEPT ) . and_then ( |value| value. to_str ( ) . ok ( ) ) . unwrap_or ( APPLICATION_JSON ) ,
428435 )
429- . unwrap_or ( Accept :: Json )
436+ . map_err ( |e| eyre:: eyre!( "invalid accept header: {e}" ) ) ?;
437+
438+ if accept. media_types ( ) . count ( ) == 0 {
439+ // No valid media types found, default to JSON
440+ return Ok ( EncodingType :: Json ) ;
441+ }
442+
443+ // Get the SSZ and JSON media types if present
444+ let mut ssz_type = false ;
445+ let mut json_type = false ;
446+ let mut unsupported_type = false ;
447+ accept. media_types ( ) . for_each ( |mt| match mt. essence ( ) . to_string ( ) . as_str ( ) {
448+ APPLICATION_OCTET_STREAM => ssz_type = true ,
449+ APPLICATION_JSON | WILDCARD => json_type = true ,
450+ _ => unsupported_type = true ,
451+ } ) ;
452+
453+ // If SSZ is present, prioritize it
454+ if ssz_type {
455+ return Ok ( EncodingType :: Ssz ) ;
456+ }
457+ // If there aren't any unsupported types, use JSON
458+ if !unsupported_type {
459+ return Ok ( EncodingType :: Json ) ;
460+ }
461+ Err ( eyre:: eyre!( "unsupported accept type" ) )
430462}
431463
432- /// Parse CONTENT TYPE header, default to JSON if missing or mal-formatted
433- pub fn get_content_type_header ( req_headers : & HeaderMap ) -> ContentType {
434- ContentType :: from_str (
464+ /// Parse CONTENT TYPE header to get the encoding type of the body, defaulting
465+ /// to JSON if missing or malformed.
466+ pub fn get_content_type ( req_headers : & HeaderMap ) -> EncodingType {
467+ EncodingType :: from_str (
435468 req_headers
436469 . get ( CONTENT_TYPE )
437470 . and_then ( |value| value. to_str ( ) . ok ( ) )
438- . unwrap_or ( "application/json" ) ,
471+ . unwrap_or ( APPLICATION_JSON ) ,
439472 )
440- . unwrap_or ( ContentType :: Json )
473+ . unwrap_or ( EncodingType :: Json )
441474}
442475
443476/// Parse CONSENSUS_VERSION header
@@ -451,133 +484,91 @@ pub fn get_consensus_version_header(req_headers: &HeaderMap) -> Option<ForkName>
451484 . ok ( )
452485}
453486
487+ /// Enum for types that can be used to encode incoming request bodies or
488+ /// outgoing response bodies
454489#[ derive( Debug , Clone , Copy , PartialEq ) ]
455- pub enum ContentType {
490+ pub enum EncodingType {
491+ /// Body is UTF-8 encoded as JSON
456492 Json ,
493+
494+ /// Body is raw bytes representing an SSZ object
457495 Ssz ,
458496}
459497
460- impl std:: fmt:: Display for ContentType {
498+ impl std:: fmt:: Display for EncodingType {
461499 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
462500 match self {
463- ContentType :: Json => write ! ( f, "application/json" ) ,
464- ContentType :: Ssz => write ! ( f, "application/octet-stream" ) ,
501+ EncodingType :: Json => write ! ( f, "application/json" ) ,
502+ EncodingType :: Ssz => write ! ( f, "application/octet-stream" ) ,
465503 }
466504 }
467505}
468506
469- impl FromStr for ContentType {
507+ impl FromStr for EncodingType {
470508 type Err = String ;
471509 fn from_str ( value : & str ) -> Result < Self , Self :: Err > {
472510 match value {
473- "application/json" => Ok ( ContentType :: Json ) ,
474- "application/octet-stream" => Ok ( ContentType :: Ssz ) ,
475- _ => Ok ( ContentType :: Json ) ,
511+ "application/json" | "" => Ok ( EncodingType :: Json ) ,
512+ "application/octet-stream" => Ok ( EncodingType :: Ssz ) ,
513+ _ => Err ( format ! ( "unsupported encoding type: {value}" ) ) ,
476514 }
477515 }
478516}
479517
480- #[ derive( Debug , Clone , Copy , PartialEq ) ]
481- pub enum Accept {
482- Json ,
483- Ssz ,
484- Any ,
518+ pub enum BodyDeserializeError {
519+ SerdeJsonError ( serde_json:: Error ) ,
520+ SszDecodeError ( ssz:: DecodeError ) ,
521+ UnsupportedMediaType ,
485522}
486523
487- impl fmt :: Display for Accept {
488- fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
524+ impl Display for BodyDeserializeError {
525+ fn fmt ( & self , f : & mut std :: fmt:: Formatter < ' _ > ) -> std :: fmt:: Result {
489526 match self {
490- Accept :: Ssz => write ! ( f, "application/octet-stream" ) ,
491- Accept :: Json => write ! ( f, "application/json" ) ,
492- Accept :: Any => write ! ( f, "*/*" ) ,
527+ BodyDeserializeError :: SerdeJsonError ( e) => write ! ( f, "JSON deserialization error: {e}" ) ,
528+ BodyDeserializeError :: SszDecodeError ( e) => {
529+ write ! ( f, "SSZ deserialization error: {:?}" , e)
530+ }
531+ BodyDeserializeError :: UnsupportedMediaType => write ! ( f, "unsupported media type" ) ,
493532 }
494533 }
495534}
496535
497- impl FromStr for Accept {
498- type Err = String ;
499-
500- fn from_str ( s : & str ) -> Result < Self , Self :: Err > {
501- let media_type_list = MediaTypeList :: new ( s) ;
502-
503- // [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2
504- // find the highest q-factor supported accept type
505- let mut highest_q = 0_u16 ;
506- let mut accept_type = None ;
507-
508- const APPLICATION : & str = names:: APPLICATION . as_str ( ) ;
509- const OCTET_STREAM : & str = names:: OCTET_STREAM . as_str ( ) ;
510- const JSON : & str = names:: JSON . as_str ( ) ;
511- const STAR : & str = names:: _STAR. as_str ( ) ;
512- const Q : & str = names:: Q . as_str ( ) ;
513-
514- media_type_list. into_iter ( ) . for_each ( |item| {
515- if let Ok ( MediaType { ty, subty, suffix : _, params } ) = item {
516- let q_accept = match ( ty. as_str ( ) , subty. as_str ( ) ) {
517- ( APPLICATION , OCTET_STREAM ) => Some ( Accept :: Ssz ) ,
518- ( APPLICATION , JSON ) => Some ( Accept :: Json ) ,
519- ( STAR , STAR ) => Some ( Accept :: Any ) ,
520- _ => None ,
521- }
522- . map ( |item_accept_type| {
523- let q_val = params
524- . iter ( )
525- . find_map ( |( n, v) | match n. as_str ( ) {
526- Q => {
527- Some ( ( v. as_str ( ) . parse :: < f32 > ( ) . unwrap_or ( 0_f32 ) * 1000_f32 ) as u16 )
528- }
529- _ => None ,
530- } )
531- . or ( Some ( 1000_u16 ) ) ;
532-
533- ( q_val. unwrap ( ) , item_accept_type)
534- } ) ;
535-
536- match q_accept {
537- Some ( ( q, accept) ) if q > highest_q => {
538- highest_q = q;
539- accept_type = Some ( accept) ;
540- }
541- _ => ( ) ,
542- }
536+ pub async fn deserialize_body < T > (
537+ headers : & HeaderMap ,
538+ body : Bytes ,
539+ ) -> Result < T , BodyDeserializeError >
540+ where
541+ T : serde:: de:: DeserializeOwned + ssz:: Decode + ' static ,
542+ {
543+ if headers. contains_key ( CONTENT_TYPE ) {
544+ return match get_content_type ( headers) {
545+ EncodingType :: Json => {
546+ serde_json:: from_slice :: < T > ( & body) . map_err ( BodyDeserializeError :: SerdeJsonError )
543547 }
544- } ) ;
545- accept_type. ok_or_else ( || "accept header is not supported" . to_string ( ) )
548+ EncodingType :: Ssz => {
549+ T :: from_ssz_bytes ( & body) . map_err ( BodyDeserializeError :: SszDecodeError )
550+ }
551+ } ;
546552 }
553+
554+ Err ( BodyDeserializeError :: UnsupportedMediaType )
547555}
548556
549557#[ must_use]
550- #[ derive( Debug , Clone , Copy , Default ) ]
551- pub struct JsonOrSsz < T > ( pub T ) ;
558+ #[ derive( Debug , Clone , Default ) ]
559+ pub struct RawRequest {
560+ pub body_bytes : Bytes ,
561+ }
552562
553- impl < T , S > FromRequest < S > for JsonOrSsz < T >
563+ impl < S > FromRequest < S > for RawRequest
554564where
555- T : serde:: de:: DeserializeOwned + ssz:: Decode + ' static ,
556565 S : Send + Sync ,
557566{
558567 type Rejection = AxumResponse ;
559568
560569 async fn from_request ( req : Request , _state : & S ) -> Result < Self , Self :: Rejection > {
561- let headers = req. headers ( ) . clone ( ) ;
562- let content_type = headers. get ( CONTENT_TYPE ) . and_then ( |value| value. to_str ( ) . ok ( ) ) ;
563-
564570 let bytes = Bytes :: from_request ( req, _state) . await . map_err ( IntoResponse :: into_response) ?;
565-
566- if let Some ( content_type) = content_type {
567- if content_type. starts_with ( & ContentType :: Json . to_string ( ) ) {
568- let payload: T = serde_json:: from_slice ( & bytes)
569- . map_err ( |_| StatusCode :: BAD_REQUEST . into_response ( ) ) ?;
570- return Ok ( Self ( payload) ) ;
571- }
572-
573- if content_type. starts_with ( & ContentType :: Ssz . to_string ( ) ) {
574- let payload = T :: from_ssz_bytes ( & bytes)
575- . map_err ( |_| StatusCode :: BAD_REQUEST . into_response ( ) ) ?;
576- return Ok ( Self ( payload) ) ;
577- }
578- }
579-
580- Err ( StatusCode :: UNSUPPORTED_MEDIA_TYPE . into_response ( ) )
571+ Ok ( Self { body_bytes : bytes } )
581572 }
582573}
583574
0 commit comments