I now have this working and here is how it works.
Firstly, I define a set of call-back functions which will be notified when REST operations complete:
callbacks : Account.Service.Callbacks Model Msg
callbacks =
{ findAll = accountList
, findByExample = accountList
, create = \account -> \model -> ( model, Cmd.none )
, retrieve = \account -> \model -> ( model, Cmd.none )
, update = \account -> \model -> ( model, Cmd.none )
, delete = \response -> \model -> ( model, Cmd.none )
, error = error
}
The return type lets me update the model and issue follow on commands, just like update. In fact these callbacks will be invoked from within the update function of the REST module.
In the update function of the module that wants to use it, I need a Msg for events that are handed down to the REST module:
update' : Msg -> Model -> ( Model, Cmd Msg )
update' action model =
case action of
...
AccountApi action' ->
Account.Service.update callbacks action' model
And then to invoke a REST endpoint I have set up convenience functions to make the calls and Cmd.map the outcomes as events local to this module (which are then handed back down to the REST module):
Init ->
( { model | selected = Set.empty }, Account.Service.invokeFindAll AccountApi )
I pass all errors back to just one error handler, not one per endpoint. This may prove to be a limitation in the future, but seems ok for now. Mostly I am interested in handling 401 with a logout, 404 with a not found page, and anything else with a "whoops something went wrong" generic error message. Have not done the 404 yet, but the error handling function can be typed generically enough to work with any model or message type in the generic case:
error : Http.Error -> model -> ( model, Cmd msg )
error httpError model =
case httpError of
Http.BadResponse 401 message ->
( model, Auth.logout )
_ ->
( model, Cmd.none )
The use of callbacks simplifies the update function in the module using the REST module, as there is no need to define Msgs for every event resulting from the Http calls. I think the type of the Callbacks is very useful in guiding the caller as to what exactly they need to implement, whereas leaving it up to the caller to figure out what events they need to handle is much less helpful.
It does mean that the flow of control goes from the calling module to the REST module, back to the calling module, back to the REST module, so I have more code in total than I might otherwise need. On the other hand, I have now managed to code gen the full REST interface with its model, encoders/decoders and convenience fuctions to make the REST calls - so my priority was removing as much boiler plate as possible from the calling module.
The amount of boiler plate around json and REST compared with what you need in javascript does seem to be a weakness of Elm. In my adventures so far, if I had to pick an area for improvement that would help to make Elm a more popular language I think it would be this.
I am now able to talk json over REST to my services without having to hand code all this boiler plate, which feels really good. Now I can get on with the fun part of making a nice UI.