@@ -643,22 +643,9 @@ impl<T: Serialize> Paginated<T> {
643643}
644644
645645impl < T : Serialize + Send > crate :: response:: IntoResponse for Paginated < T > {
646- /// Convert to an HTTP response.
647- ///
648- /// The response includes `X-Total-Count`, `X-Total-Pages`, and RFC 8288
649- /// `Link` headers. Navigation links (first/prev/next/last) are generated as
650- /// **relative query strings** (e.g. `?page=2&per_page=20`) because
651- /// `IntoResponse` does not have access to the original request URI.
652- ///
653- /// If you need absolute URLs in the Link header, wrap this type in a
654- /// `ResponseModifier` or interceptor that has access to the request URI,
655- /// or build the response manually using [`Paginated::link_header`] and
656- /// [`Paginated::to_body_with_path`].
657646 fn into_response ( self ) -> crate :: response:: Response {
658- // Use an empty base path since IntoResponse has no access to the
659- // request URI. Navigation links will be relative query strings only
660- // (e.g. `?page=2`). Callers that need absolute URLs should use
661- // link_header(base_path) / to_body_with_path(base_path) directly.
647+ // Use a generic base path since we don't have access to the request URI
648+ // in IntoResponse. Users can override via ResponseModifier or interceptors.
662649 let base_path = "" ;
663650 let link_header = self . link_header ( base_path) ;
664651 let body = self . to_body_with_path ( base_path) ;
@@ -881,150 +868,4 @@ mod tests {
881868 let resource = user. with_links ( ) . self_link ( "/users/1" ) ;
882869 assert ! ( resource. links. contains_key( "self" ) ) ;
883870 }
884-
885- // ─── IntoResponse tests ─────────────────────────────────────────────────
886-
887- /// Collect a [`crate::response::Body`] into [`bytes::Bytes`] synchronously
888- /// using a one-shot tokio runtime (avoids pulling in `#[tokio::test]`).
889- fn collect_body ( body : crate :: response:: Body ) -> bytes:: Bytes {
890- use http_body_util:: BodyExt ;
891- tokio:: runtime:: Builder :: new_current_thread ( )
892- . build ( )
893- . unwrap ( )
894- . block_on ( async { body. collect ( ) . await . unwrap ( ) . to_bytes ( ) } )
895- }
896-
897- #[ test]
898- fn test_paginated_into_response_status_and_headers ( ) {
899- use crate :: response:: IntoResponse ;
900-
901- let users = vec ! [
902- User { id: 1 , name: "Alice" . to_string( ) } ,
903- User { id: 2 , name: "Bob" . to_string( ) } ,
904- ] ;
905- let paginated = Paginated :: new ( users, 1 , 10 , 25 ) ;
906- let response = paginated. into_response ( ) ;
907-
908- assert_eq ! ( response. status( ) , http:: StatusCode :: OK ) ;
909- assert_eq ! (
910- response. headers( ) . get( http:: header:: CONTENT_TYPE ) . unwrap( ) ,
911- "application/json"
912- ) ;
913- assert_eq ! (
914- response. headers( ) . get( "X-Total-Count" ) . unwrap( ) ,
915- "25"
916- ) ;
917- assert_eq ! (
918- response. headers( ) . get( "X-Total-Pages" ) . unwrap( ) ,
919- "3" // ceil(25 / 10) = 3
920- ) ;
921- // Link header should be present (non-first page has prev/next/first/last)
922- assert ! ( response. headers( ) . contains_key( http:: header:: LINK ) ) ;
923- }
924-
925- #[ test]
926- fn test_paginated_into_response_json_body ( ) {
927- use crate :: response:: IntoResponse ;
928-
929- let users = vec ! [ User { id: 42 , name: "Carol" . to_string( ) } ] ;
930- let paginated = Paginated :: new ( users, 2 , 5 , 10 ) ;
931- let response = paginated. into_response ( ) ;
932-
933- let ( parts, body) = response. into_parts ( ) ;
934- assert_eq ! ( parts. status, http:: StatusCode :: OK ) ;
935-
936- let bytes = collect_body ( body) ;
937- let json: serde_json:: Value = serde_json:: from_slice ( & bytes) . unwrap ( ) ;
938-
939- assert ! ( json. get( "items" ) . is_some( ) ) ;
940- assert ! ( json. get( "meta" ) . is_some( ) ) ;
941- // Links use the HAL `_links` convention
942- assert ! ( json. get( "_links" ) . is_some( ) ) ;
943-
944- let items = json[ "items" ] . as_array ( ) . unwrap ( ) ;
945- assert_eq ! ( items. len( ) , 1 ) ;
946- assert_eq ! ( items[ 0 ] [ "id" ] , 42 ) ;
947-
948- let meta = & json[ "meta" ] ;
949- assert_eq ! ( meta[ "page" ] , 2 ) ;
950- assert_eq ! ( meta[ "per_page" ] , 5 ) ;
951- assert_eq ! ( meta[ "total" ] , 10 ) ;
952- assert_eq ! ( meta[ "total_pages" ] , 2 ) ;
953- }
954-
955- #[ test]
956- fn test_paginated_into_response_empty_items ( ) {
957- use crate :: response:: IntoResponse ;
958-
959- let paginated: Paginated < User > = Paginated :: new ( vec ! [ ] , 1 , 10 , 0 ) ;
960- let response = paginated. into_response ( ) ;
961-
962- assert_eq ! ( response. status( ) , http:: StatusCode :: OK ) ;
963- assert_eq ! ( response. headers( ) . get( "X-Total-Count" ) . unwrap( ) , "0" ) ;
964- assert_eq ! ( response. headers( ) . get( "X-Total-Pages" ) . unwrap( ) , "0" ) ;
965- // At minimum the `first` link is always included in the Link header
966- let link = response. headers ( ) . get ( http:: header:: LINK ) ;
967- let link_str = link. map ( |v| v. to_str ( ) . unwrap_or ( "" ) ) . unwrap_or ( "" ) ;
968- // Empty result set has no next or prev links
969- assert ! ( !link_str. contains( "rel=\" next\" " ) ) ;
970- assert ! ( !link_str. contains( "rel=\" prev\" " ) ) ;
971- }
972-
973- #[ test]
974- fn test_cursor_paginated_into_response_status_and_headers ( ) {
975- use crate :: response:: IntoResponse ;
976-
977- let users = vec ! [ User { id: 1 , name: "Dave" . to_string( ) } ] ;
978- let paginated = CursorPaginated :: new ( users, Some ( "cursor_abc" . to_string ( ) ) , true ) ;
979- let response = paginated. into_response ( ) ;
980-
981- assert_eq ! ( response. status( ) , http:: StatusCode :: OK ) ;
982- assert_eq ! (
983- response. headers( ) . get( http:: header:: CONTENT_TYPE ) . unwrap( ) ,
984- "application/json"
985- ) ;
986- }
987-
988- #[ test]
989- fn test_cursor_paginated_into_response_json_body ( ) {
990- use crate :: response:: IntoResponse ;
991-
992- let users = vec ! [ User { id: 7 , name: "Eve" . to_string( ) } ] ;
993- let paginated =
994- CursorPaginated :: new ( users, Some ( "next_cursor_xyz" . to_string ( ) ) , true ) ;
995- let response = paginated. into_response ( ) ;
996-
997- let ( _parts, body) = response. into_parts ( ) ;
998- let bytes = collect_body ( body) ;
999- let json: serde_json:: Value = serde_json:: from_slice ( & bytes) . unwrap ( ) ;
1000-
1001- assert ! ( json. get( "items" ) . is_some( ) ) ;
1002- assert ! ( json. get( "meta" ) . is_some( ) ) ;
1003-
1004- let items = json[ "items" ] . as_array ( ) . unwrap ( ) ;
1005- assert_eq ! ( items. len( ) , 1 ) ;
1006- assert_eq ! ( items[ 0 ] [ "id" ] , 7 ) ;
1007-
1008- let meta = & json[ "meta" ] ;
1009- assert_eq ! ( meta[ "next_cursor" ] , "next_cursor_xyz" ) ;
1010- assert_eq ! ( meta[ "has_more" ] , true ) ;
1011- }
1012-
1013- #[ test]
1014- fn test_cursor_paginated_into_response_last_page ( ) {
1015- use crate :: response:: IntoResponse ;
1016-
1017- let users = vec ! [ User { id: 9 , name: "Frank" . to_string( ) } ] ;
1018- let paginated = CursorPaginated :: new ( users, None , false ) ;
1019- let response = paginated. into_response ( ) ;
1020-
1021- let ( _parts, body) = response. into_parts ( ) ;
1022- let bytes = collect_body ( body) ;
1023- let json: serde_json:: Value = serde_json:: from_slice ( & bytes) . unwrap ( ) ;
1024-
1025- let meta = & json[ "meta" ] ;
1026- // next_cursor should be omitted when None
1027- assert ! ( meta. get( "next_cursor" ) . is_none( ) ) ;
1028- assert_eq ! ( meta[ "has_more" ] , false ) ;
1029- }
1030871}
0 commit comments