Using Elm pipelines with andThen to decode a multi-object type
Decode the remote api responses into response types that have two parts: RequestStatus and response-specific type.
This post shares wow I use pipelines and andThen to Decode a multi-object type.
My payload from the remote service has the following form:
{
action: "UpdateCardRequest"
errorCode: 0
message: "ok"
successStatus: true
jsonBody: {ID: "M949U4J8", TeamID: "J888Y3B1", BoardID: "A954N8A2", StageID: "U345M9U4",…}
}
This response consists of two logically distinct pieces of information, a response status (action, errorCode, message and successStatus) and the context specific payload (jsonBody).
Because of this I want to decode the remote api response into Response types that have two parts RequestStatus and response-specific type.
type LoadingTeamResponse
= FetchTeamResponse RequestStatus Team
type LoadedTeamResponse
= CreateBoardStageResponse RequestStatus Stage
| CreateStageCardResponse RequestStatus Card
| UpdateStageCardResponse RequestStatus Card
type alias RequestStatus =
{ action : String
, errorCode : Int
, successStatus : Bool
, message : String
}
My initial approach was to use map4 from the Decoder module, which works but feels clumsy.
postFormResponseDecoder : Decoder ApiResponse
postFormResponseDecoder =
D.map4 (\a b c d -> PostFormRedirect (RequestStatus a b c) d)
(D.field "action" D.string)
(D.field "errorCode" D.int)
(D.field "successStatus" D.bool)
(D.maybe (D.field "jsonBody" Team.teamDecoder))
Instead, I used a helper function that allowed for a more concise api.
onApiResponse : Decoder a -> (RequestStatus -> a -> b) -> Decoder b
onApiResponse fieldDecoder responseMessage =
(D.succeed ApiHTTPResponse
|> required "action" D.string
|> required "errorCode" D.int
|> required "successStatus" D.bool
|> required "message" D.string
|> required "jsonBody" fieldDecoder
)
|> D.andThen
(\a ->
D.succeed (responseMessage (RequestStatus a.action a.errorCode a.successStatus a.message) a.jsonBody)
)
I can now write my decoders using the onApiResponse. The pattern feels more concise.
fetchTeamResponseDecoder : Decoder LoadingTeamResponse
fetchTeamResponseDecoder =
onApiResponse teamDecoder FetchTeamResponse
apiGetTeam : String -> Cmd Msg
apiGetTeam teamID =
Http.get
{ url = getRouteURL "getTeam" ++ teamID
, expect = Http.expectJson LoadingApiResponse fetchTeamResponseDecoder
}