Tidy up and document the Paginated module

This commit is contained in:
Correl Roush 2018-01-18 13:52:21 -05:00
parent b73272864b
commit 28cf4c1d70
4 changed files with 178 additions and 56 deletions

View file

@ -51,31 +51,35 @@ update msg model =
case msg of
GotObjects data ->
let
updatePaginated new =
let
updated =
Paginated.update
(RemoteData.toMaybe model.objects)
new
in
case updated of
Paginated.Partial options items ->
( updated
, Paginated.request options
|> Paginated.httpRequest
|> RemoteData.sendRequest
|> Cmd.map GotObjects
)
next p =
case p of
Paginated.Complete _ ->
Cmd.none
Paginated.Complete items ->
( updated, Cmd.none )
Paginated.Partial request _ ->
Paginated.httpRequest request
|> RemoteData.sendRequest
|> Cmd.map GotObjects
( objects, cmd ) =
RemoteData.update
updatePaginated
(\b ->
case model.objects of
RemoteData.Success a ->
let
updated =
Paginated.update a b
in
( updated, next updated )
_ ->
( b, next b )
)
data
in
( { model | objects = objects }, cmd )
( { model | objects = objects }
, cmd
)
_ ->
( model, Cmd.none )

View file

@ -102,7 +102,7 @@ getObjects repo client =
[ "projects"
, Http.encodeUri (repo.owner ++ "/" ++ repo.name)
, "repository"
, "tree?recursive=true"
, "tree?recursive=true&per_page=100"
]
decoder
client

35
src/Http/Util.elm Normal file
View file

@ -0,0 +1,35 @@
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

View file

@ -1,6 +1,7 @@
module Paginated
exposing
( Request
, RequestOptions
, Response(..)
, request
, get
@ -8,17 +9,92 @@ module Paginated
, send
, update
, httpRequest
, links
)
{-| 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 Regex
import Time
{-| Describes an API request.
-}
type alias RequestOptions a =
{ method : String
, headers : List Http.Header
@ -30,20 +106,45 @@ type alias RequestOptions a =
}
{-| 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 (RequestOptions a) (List 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
@ -57,6 +158,8 @@ get url decoder =
}
{-| Build a POST request.
-}
post : String -> Http.Body -> Decoder a -> Request a
post url body decoder =
request
@ -70,6 +173,8 @@ post url body decoder =
}
{-| Send a `Request`.
-}
send :
(Result Http.Error (Response a) -> msg)
-> Request a
@ -79,22 +184,24 @@ send resultToMessage request =
httpRequest request
update : Maybe (Response a) -> Response a -> Response a
{-| Append two paginated responses, collecting the results within.
-}
update : Response a -> Response a -> Response a
update old new =
case ( old, new ) of
( Nothing, _ ) ->
new
( Just (Complete items), _ ) ->
( Complete items, _ ) ->
Complete items
( Just (Partial _ oldItems), Complete newItems ) ->
( Partial _ oldItems, Complete newItems ) ->
Complete (oldItems ++ newItems)
( Just (Partial _ oldItems), Partial request 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
@ -127,12 +234,8 @@ fromResponse options response =
nextPage =
Dict.get "Link" response.headers
|> Maybe.map links
|> Maybe.map Http.Util.links
|> Maybe.andThen (Dict.get "next")
newOptions : Result String (RequestOptions a)
newOptions =
Err "Next request not implemented"
in
case nextPage of
Nothing ->
@ -140,26 +243,6 @@ fromResponse options response =
Just url ->
Result.map
(Partial { options | url = url })
(Partial (request { options | url = url }))
items
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