From 9368410d4b122c390d56cb987ec33d375533aeb1 Mon Sep 17 00:00:00 2001 From: Correl Roush Date: Thu, 18 Jan 2018 14:37:03 -0500 Subject: [PATCH] Extract the Paginated module into its own library --- elm-package.json | 1 + src/Http/Util.elm | 35 ------ src/Paginated.elm | 248 --------------------------------------- tests/PaginatedTests.elm | 32 ----- tests/elm-package.json | 1 + 5 files changed, 2 insertions(+), 315 deletions(-) delete mode 100644 src/Http/Util.elm delete mode 100644 src/Paginated.elm delete mode 100644 tests/PaginatedTests.elm diff --git a/elm-package.json b/elm-package.json index 4cd56bd..d40ed40 100644 --- a/elm-package.json +++ b/elm-package.json @@ -9,6 +9,7 @@ "exposed-modules": [], "dependencies": { "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0", + "correl/elm-paginated": "1.0.0 <= v < 2.0.0", "elm-community/maybe-extra": "4.0.0 <= v < 5.0.0", "elm-lang/core": "5.1.1 <= v < 6.0.0", "elm-lang/html": "2.0.0 <= v < 3.0.0", diff --git a/src/Http/Util.elm b/src/Http/Util.elm deleted file mode 100644 index 8669c48..0000000 --- a/src/Http/Util.elm +++ /dev/null @@ -1,35 +0,0 @@ -module Http.Util exposing (links) - -import Dict exposing (Dict) -import Maybe.Extra -import Regex - - -{-| Parse an HTTP Link header into a dictionary. For example, to look -for a link to additional results in an API response, you could do the -following: - - Dict.get "Link" response.headers - |> Maybe.map links - |> Maybe.andThen (Dict.get "next") - --} -links : String -> Dict String String -links s = - let - toTuples xs = - case xs of - [ Just a, Just b ] -> - Just ( b, a ) - - _ -> - Nothing - in - Regex.find - Regex.All - (Regex.regex "<(.*?)>; rel=\"(.*?)\"") - s - |> List.map .submatches - |> List.map toTuples - |> Maybe.Extra.values - |> Dict.fromList diff --git a/src/Paginated.elm b/src/Paginated.elm deleted file mode 100644 index 7cbf860..0000000 --- a/src/Paginated.elm +++ /dev/null @@ -1,248 +0,0 @@ -module Paginated - exposing - ( Request - , RequestOptions - , Response(..) - , request - , get - , post - , send - , update - , httpRequest - ) - -{-| Facilitates fetching data from a paginated JSON API. - - import Http - import Json.Decode exposing (string) - import Paginated exposing (Response(..)) - - type alias Model = - { results : Maybe (Paginated.Response String) } - - type Msg - = Search - | Results (Result Http.Error (Paginated.Response String)) - - update : Msg -> Model -> ( Model, Cmd Msg ) - update msg model = - case msg of - Search -> - ( model, doSearch ) - - Results (Ok response) -> - case response of - Partial request results -> - ( { model - | results = - Maybe.map (\x -> Paginated.update x response) - model.results - } - , Paginated.send Results request - ) - - Complete results -> - ( { model - | results = - Maybe.map (\x -> Paginated.update x response) - model.results - } - , Cmd.none - ) - - Results (Err _) -> - ( model, Cmd.none ) - - doSearch : Cmd Msg - doSearch = - Paginated.send Results <| - Paginated.get "http://example.com/items" string - - -# Requests and Responses - -@docs Request, Response, get, post - - -## Custom requests - -@docs RequestOptions, request - - -## Converting - -@docs httpRequest - - -# Sending requests - -@docs send - - -# Handling responses - -@docs Response, update - --} - -import Dict exposing (Dict) -import Http -import Http.Util -import Json.Decode exposing (Decoder) -import Maybe.Extra -import Time - - -{-| Describes an API request. --} -type alias RequestOptions a = - { method : String - , headers : List Http.Header - , url : String - , body : Http.Body - , decoder : Decoder a - , timeout : Maybe Time.Time - , withCredentials : Bool - } - - -{-| Encapsulates an API request for a list of items of type `a`. --} -type Request a - = Request (RequestOptions a) - - -{-| Describes an API response. - -A response may either be Partial (there are more pages of results yet -to be fetched), or Complete (all records have been fetched). The -response includes all of the items fetched in order. - --} -type Response a - = Partial (Request a) (List a) - | Complete (List a) - - -{-| Create a custom request, allowing the specification of HTTP -headers and other options. For example: - - Paginated.request - { method = "GET" - , headers = [Http.header "Private-Token" "XXXXXXXXXXXXXXXX"] - , url = url - , body = Http.emptyBody - , decoder = decoder - , timeout = Nothing - , withCredentials = False - } - --} -request : RequestOptions a -> Request a -request = - Request - - -{-| Build a GET request. --} -get : String -> Decoder a -> Request a -get url decoder = - request - { method = "GET" - , headers = [] - , url = url - , body = Http.emptyBody - , decoder = decoder - , timeout = Nothing - , withCredentials = False - } - - -{-| Build a POST request. --} -post : String -> Http.Body -> Decoder a -> Request a -post url body decoder = - request - { method = "POST" - , headers = [] - , url = url - , body = body - , decoder = decoder - , timeout = Nothing - , withCredentials = False - } - - -{-| Send a `Request`. --} -send : - (Result Http.Error (Response a) -> msg) - -> Request a - -> Cmd msg -send resultToMessage request = - Http.send resultToMessage <| - httpRequest request - - -{-| Append two paginated responses, collecting the results within. --} -update : Response a -> Response a -> Response a -update old new = - case ( old, new ) of - ( Complete items, _ ) -> - Complete items - - ( Partial _ oldItems, Complete newItems ) -> - Complete (oldItems ++ newItems) - - ( Partial _ oldItems, Partial request newItems ) -> - Partial request (oldItems ++ newItems) - - -{-| Convert a `Request` to a `Http.Request` that can then be sent via -`Http.send`. --} -httpRequest : Request a -> Http.Request (Response a) -httpRequest (Request options) = - Http.request - { method = options.method - , headers = options.headers - , url = options.url - , body = options.body - , expect = expect options - , timeout = options.timeout - , withCredentials = options.withCredentials - } - - -expect : RequestOptions a -> Http.Expect (Response a) -expect options = - Http.expectStringResponse (fromResponse options) - - -fromResponse : - RequestOptions a - -> Http.Response String - -> Result String (Response a) -fromResponse options response = - let - items : Result String (List a) - items = - Json.Decode.decodeString - (Json.Decode.list options.decoder) - response.body - - nextPage = - Dict.get "Link" response.headers - |> Maybe.map Http.Util.links - |> Maybe.andThen (Dict.get "next") - in - case nextPage of - Nothing -> - Result.map Complete items - - Just url -> - Result.map - (Partial (request { options | url = url })) - items - diff --git a/tests/PaginatedTests.elm b/tests/PaginatedTests.elm deleted file mode 100644 index cb8be5b..0000000 --- a/tests/PaginatedTests.elm +++ /dev/null @@ -1,32 +0,0 @@ -module PaginatedTests exposing (..) - -import Dict -import Expect -import Paginated -import Test exposing (..) - - -suite : Test -suite = - describe "Paginated" - [ test "Parse links" <| - \() -> - let - header = - String.join ", " - [ "; rel=\"prev\"" - , "; rel=\"next\"" - , "; rel=\"first\"" - , "; rel=\"last\"" - ] - - expected = - Dict.fromList - [ ( "prev", "https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=1&per_page=3" ) - , ( "next", "https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=3&per_page=3" ) - , ( "first", "https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=1&per_page=3" ) - , ( "last", "https://gitlab.example.com/api/v4/projects/8/issues/8/notes?page=3&per_page=3" ) - ] - in - Expect.equalDicts expected (Paginated.links header) - ] diff --git a/tests/elm-package.json b/tests/elm-package.json index 826702f..f5a9523 100644 --- a/tests/elm-package.json +++ b/tests/elm-package.json @@ -10,6 +10,7 @@ "exposed-modules": [], "dependencies": { "NoRedInk/elm-decode-pipeline": "3.0.0 <= v < 4.0.0", + "correl/elm-paginated": "1.0.0 <= v < 2.0.0", "eeue56/elm-html-test": "5.1.2 <= v < 6.0.0", "elm-community/elm-test": "4.0.0 <= v < 5.0.0", "elm-community/maybe-extra": "4.0.0 <= v < 5.0.0",