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 case msg of
GotObjects data -> GotObjects data ->
let let
updatePaginated new = next p =
let case p of
updated = Paginated.Complete _ ->
Paginated.update Cmd.none
(RemoteData.toMaybe model.objects)
new
in
case updated of
Paginated.Partial options items ->
( updated
, Paginated.request options
|> Paginated.httpRequest
|> RemoteData.sendRequest
|> Cmd.map GotObjects
)
Paginated.Complete items -> Paginated.Partial request _ ->
( updated, Cmd.none ) Paginated.httpRequest request
|> RemoteData.sendRequest
|> Cmd.map GotObjects
( objects, cmd ) = ( objects, cmd ) =
RemoteData.update 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 data
in in
( { model | objects = objects }, cmd ) ( { model | objects = objects }
, cmd
)
_ -> _ ->
( model, Cmd.none ) ( model, Cmd.none )

View file

@ -102,7 +102,7 @@ getObjects repo client =
[ "projects" [ "projects"
, Http.encodeUri (repo.owner ++ "/" ++ repo.name) , Http.encodeUri (repo.owner ++ "/" ++ repo.name)
, "repository" , "repository"
, "tree?recursive=true" , "tree?recursive=true&per_page=100"
] ]
decoder decoder
client 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 module Paginated
exposing exposing
( Request ( Request
, RequestOptions
, Response(..) , Response(..)
, request , request
, get , get
@ -8,17 +9,92 @@ module Paginated
, send , send
, update , update
, httpRequest , 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 Dict exposing (Dict)
import Http import Http
import Http.Util
import Json.Decode exposing (Decoder) import Json.Decode exposing (Decoder)
import Maybe.Extra import Maybe.Extra
import Regex
import Time import Time
{-| Describes an API request.
-}
type alias RequestOptions a = type alias RequestOptions a =
{ method : String { method : String
, headers : List Http.Header , 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 type Request a
= Request (RequestOptions 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 type Response a
= Partial (RequestOptions a) (List a) = Partial (Request a) (List a)
| Complete (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 : RequestOptions a -> Request a
request = request =
Request Request
{-| Build a GET request.
-}
get : String -> Decoder a -> Request a get : String -> Decoder a -> Request a
get url decoder = get url decoder =
request request
@ -57,6 +158,8 @@ get url decoder =
} }
{-| Build a POST request.
-}
post : String -> Http.Body -> Decoder a -> Request a post : String -> Http.Body -> Decoder a -> Request a
post url body decoder = post url body decoder =
request request
@ -70,6 +173,8 @@ post url body decoder =
} }
{-| Send a `Request`.
-}
send : send :
(Result Http.Error (Response a) -> msg) (Result Http.Error (Response a) -> msg)
-> Request a -> Request a
@ -79,22 +184,24 @@ send resultToMessage request =
httpRequest 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 = update old new =
case ( old, new ) of case ( old, new ) of
( Nothing, _ ) -> ( Complete items, _ ) ->
new
( Just (Complete items), _ ) ->
Complete items Complete items
( Just (Partial _ oldItems), Complete newItems ) -> ( Partial _ oldItems, Complete newItems ) ->
Complete (oldItems ++ newItems) Complete (oldItems ++ newItems)
( Just (Partial _ oldItems), Partial request newItems ) -> ( Partial _ oldItems, Partial request newItems ) ->
Partial request (oldItems ++ 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 a -> Http.Request (Response a)
httpRequest (Request options) = httpRequest (Request options) =
Http.request Http.request
@ -127,12 +234,8 @@ fromResponse options response =
nextPage = nextPage =
Dict.get "Link" response.headers Dict.get "Link" response.headers
|> Maybe.map links |> Maybe.map Http.Util.links
|> Maybe.andThen (Dict.get "next") |> Maybe.andThen (Dict.get "next")
newOptions : Result String (RequestOptions a)
newOptions =
Err "Next request not implemented"
in in
case nextPage of case nextPage of
Nothing -> Nothing ->
@ -140,26 +243,6 @@ fromResponse options response =
Just url -> Just url ->
Result.map Result.map
(Partial { options | url = url }) (Partial (request { options | url = url }))
items 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