Elm Http.post needs to produces Http.Request Int but send needs Http.Request String - elm

I decode response from Http.post request, where return type is Integer:
responseDecoder : Decoder Int
responseDecoder =
field "data" (field "createDeadline" (field "id" int))
My problem is that I use Http.send which needs String:
createDeadline value =
Http.send Resolved (Http.post deadlineUrl (encodeBody value |> Http.jsonBody) responseDecoder)
And I dont know how to change the return type. My error message is following:
The 2nd argument to `send` is not what I expect:
113| Http.send Resolved (Http.post deadlineUrl (encodeBody value |> Http.jsonBody) responseDecoder)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This `post` call produces:
Http.Request Int
But `send` needs the 2nd argument to be:
Http.Request String
Hint: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
Hint: Want to convert an Int into a String? Use the String.fromInt function!
Can anyone help with this? I'm just playing around with Elm, but I got stuck here.

My problem is that I use Http.send which needs String
According to the docs, send function requires the argument to be of type Request a, where a can be any type (Int or String or something else).
The problem that you have is just what the hint in the compile error says:
Hint: I always figure out the argument types from left to right. If an argument
is acceptable, I assume it is “correct” and move on. So the problem may actually
be in one of the previous arguments!
Thus, it seems that you've already defined somewhere, that you're expecting String and the compiler infered the type to Request String. For example, you might have the Resolved defined something like the following:
type Msg = Resolved (Result Http.Error String)
And the compiler inferred the polymorphic type of send : (Result Error a -> msg) -> Request a -> Cmd msg to something specific, because it already sees that the first argument is or type (Result Error String -> msg):
send : (Result Error String -> msg) -> Request String -> Cmd msg
So, in this case the solution is either to change the expecting type:
type Msg = Resolved (Result Http.Error Int)
Or change the decoder and decode the response to String:
responseDecoder : Decoder String
responseDecoder =
Json.Decode.map String.fromInt (field "data" (field "createDeadline" (field "id" int)))

Related

TYPE MISMATCH - This function cannot handle the argument sent through the (|>) pipe:

I am a super elm begginer and trying to make app.
Currently I am struggling to make landing page and http request to a server.
But, I am stuck here...
I have init function something like this below.
init : () -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
init flags url key =
Model key TopPage
|> goTo (Route.parse url)
The definition of my Model is below.
-- MODEL
type alias Model =
{ key : Nav.Key
, page : Page
, name : String
, tags : List Tag
, jwt : String }
and, goTo function is below.
goTo : Maybe Route -> Model -> ( Model, Cmd Msg )
goTo maybeRoute model =
case maybeRoute of
Nothing ->
( { model | page = NotFound }, Cmd.none )
Just Route.Top ->
( { model | page = TopPage }, Cmd.none )
...
type Route is below.
type Route
= Top
| User String
| Repo String String
parse : Url -> Maybe Route
parse url =
Url.Parser.parse parser url
parser : Parser (Route -> a) a
parser =
oneOf
[ map Top top
, map User string
, map Repo (string </> string)
]
but following error has occured.
-- TYPE MISMATCH -------------------------------------------------- src/Main.elm
This function cannot handle the argument sent through the (|>) pipe:
54| Model key TopPage
55| |> goTo (Route.parse url)
^^^^^^^^^^^^^^^^^^^^^
The argument is:
String -> List Tag -> String -> Model
But (|>) is piping it a function that expects:
Model
What did I make mistake here?....
Your Model type has five fields, but in the line
Model key TopPage
you are only providing values for the first two of the five. You are missing values for the name, tags and jwt fields. Provide values for these and the problem should go away.
When you declare a type alias such as Model, Elm creates a constructor function also named Model. Elm functions support partial application, in that if you pass in values for some but not all of the arguments, you end up with a function that takes in the rest of the values. You provided two arguments, so you end up with a function that takes three arguments and returns a Model.
There are two ways of building a value of a type. Given a simple example of a Person type alias:
type alias Person = { name : String, age : Int }
You can construct a value by specifying all fields (note that you don't have to specify Person in the constructor; Elm's compiler is smart enough to know it by its shape):
jane : Person
jane = { name = "Jane", age = 35 }
Or you can build a value by using the type name and specify each field's values in the order in which they were defined. In this style, you can think of Person acting like a function with two parameters that returns a Person value.
jane : Person
jane = Person "Jane" 35
In each case, you have to specify all fields of the type when you construct it in order to obtain a complete Person value. However, that is not the complete story. It is possible to leave off the age parameter when constructing a Person, but the result isn't a Person, it's a function that takes an age and returns a Person. In other words,
janeAged : Int -> Person
janeAged = Person "Jane"
You can strip off as many parameters from the end as you'd like to make more variations on that constructor, even stripping out all parameters:
somebody : String -> Int -> Person
somebody = Person
Back to your example. You are constructing a Model value by only specifying two parameters (Model key TopPage). The value of that expression does not result in a Model, but in a function that takes three more parameters to create a Model. And that's why the error message indicated you need three parameters to construct a model.
You need to specify all values of Model when creating it.

Avoid Http Race Condition in Elm

Let's assume we have a text input field and on every change of its content we send an Http request to a search API. Now, we don't have any guarantee that the Http responses get back to elm in the same order that we sent the requests.
What's the easiest way to make sure we react to the response corresponding to the latest request – rather than the latest response, which might correspond to an outdated search string? Is there an easy way to attach the query string to the message returned by Elm's http effect? Or any other way we can link the response to the request by which it was triggered?
I'd like to avoid including the query in the response of the search API if possible. Another remedy would be to debounce the search, but that would just decrease the probability of using the wrong response, whereas we'd like to eliminate it.
Thanks for your help!
Example:
import Html
import Html exposing (..)
import Html.Events exposing (onClick, onInput)
import Http
import Json.Decode as Decode
main = Html.program
{ init = ( { searchText = "", result = "" }, Cmd.none )
, update = update
, subscriptions = (\model -> Sub.none)
, view = view
}
type alias Model =
{ searchText : String
, result: SearchResult
}
type alias SearchResult = String
type Msg
= NewSearchText String
| ReceivedResponse (Result Http.Error SearchResult)
update msg model =
case msg of
NewSearchText newText ->
( { model | searchText = newText}
, getSearchResult newText
)
ReceivedResponse (Result.Ok response) ->
( { model | result = response }
, Cmd.none
)
ReceivedResponse (Result.Err error) ->
Debug.crash <| (toString error)
getSearchResult : String -> Cmd Msg
getSearchResult query =
let
url = "http://thebackend.com/search?query=" ++ query
request : Http.Request SearchResult
request = Http.get url Decode.string
in
Http.send ReceivedResponse request
view model =
div []
[ Html.input [onInput (\text -> NewSearchText text)] []
, Html.text model.result
]
Yes, it is possible to attach the query string to the response. First, augment your message type to handle the additional data:
type Msg
= NewSearchText String
| ReceivedResponse String (Result Http.Error SearchResult)
Then, change your Http.send call to attach the query text to the ReceivedResponse message:
Http.send (ReceivedResponse query) request
Finally, in your update, grab the query in your pattern match on the resulting Msg:
case msg of
ReceivedResponse query (Ok response) ->
...
ReceivedResponse query (Err err) ->
...
Why does this work?
The Http.send function's first argument can be an arbitrary function that consumes a Result Http.Error SearchResult and turns it into a Msg. In your original code, that function is just ReceivedResponse, the Msg constructor. When the Msg type is updated so that ReceivedResponse takes two arguments, the ReceivedResponse constructor function becomes a curried two-argument function, and ReceivedResponse "some query here" is a one-argument function that takes in a Result and returns a Msg.
Here's one way:
Add two integers to your model:
requestsSent : Int -- the number of requests made.
lastReceived : Int -- the latest request that you've processed.
Modify ReceivedResponse to have an Int as the first value:
| ReceivedResponse Int (Result Http.Error SearchResult)
Now, whenever you make a request, increment requestsSent by 1 in the model and "tag" the request by partially applying ReceivedResponse:
Http.send (ReceivedResponse model.requestsSent) request
In your update function, check if the Int in the ReceivedResponse is greater than lastReceived or not. If it is, process it, and set the value of lastReceived to this response's Int. If it isn't, discard it, because you've already processed a newer request.

Objects into JSON decoder through Elm ports

I am passing an array of objects via ports into my Elm app. An example of an one of the objects in the array is:
{
FullName: 'Foo Bar',
Location: 'Here'
}
As you can see the keys in the object start with a capital, so I need to decode these in Elm. In my Elm code I have a type for the Person
type alias Person =
{ fullName : String
, location : String
}
and the port:
port getPeople : (List Json.Decode.Value -> msg) -> Sub msg
Finally I have a decoder (I am using Elm Decode Pipeline) to parse the data into the Person type.
peopleDecoder : Decoder Person
peopleDecoder =
decode Person
|> required "FullName" string
|> required "Location" string
My question is how do I map the incoming port data into the Person type? I know I could do this in JS but I'd rather do it in my Elm code.
Json.Decode.decodeValue can decode a Json.Decode.Value, but it returns a Result String (List Person).
If you defined your Msg like this:
type Msg
= GetPeople (Result String (List Person))
You could set up your subscription like this:
port getPeople : (Json.Decode.Value -> msg) -> Sub msg
subscriptions : Model -> Sub Msg
subscriptions model =
getPeople (GetPeople << decodeValue (list peopleDecoder))
(Note that the first argument in the port has been changed to just a Value instead of List Value)

How to convert Task Http.RawError Http.Response to Task String (Int, Int)

There are a type and a task
type Msg
= Fail String
| Success (Int, Int)
makeRequest =
let
req =
{ verb = "GET"
, headers = []
, url = "http://localhost:8080"
, body = empty
}
in
Task.perform Fail Success <| send defaultSettings req
The argument of Fail constructor is for error message (just "Error"), first argument of Succeess is for status from Http.Response, second is for size of value from Http.Response.
How to convert Task Http.RawError Http.Response to Task String (Int, Int)?
I'm looking at Task.map and Tsk.mapError and I don't understand how to combine them. Am I on a right way?
Yes, you can use Task.map and Task.mapError to achieve your results.
First off, you'll need a way to determine the size of your Http Response. Since it can be either a string or binary blob, and blob is not yet supported, you could define a function like this:
httpValueSize : Http.Value -> Int
httpValueSize val =
case val of
Text str -> String.length str
Blob blob -> Debug.crash "Blobs have no implementation yet"
Now you can use the mapping functions in your task like this:
send defaultSettings req
|> Task.map (\r -> (r.status, httpValueSize r.value))
|> Task.mapError (always "Error")
|> Task.perform Fail Success
You could also do this without the mapping functions like so:
send defaultSettings req
|> Task.perform (always <| Fail "Error") (\r -> Success (r.status, httpValueSize r.value))

Elm: Json decoder timestamp to Date

I'm trying to convert a timestamp (ex: "1493287973015") from a JSON to a Date type.
So far I created this custom decoder:
stringToDate : Decoder String -> Decoder Date
stringToDate decoder =
customDecoder decoder Date.fromTime
But it doesn't work because it has return a Result, not a Date:
Function `customDecoder` is expecting the 2nd argument to be:
Time.Time -> Result String a
But it is:
Time.Time -> Date.Date
Is there a way to do a conversion?
Assuming your JSON is actually placing the numeric value inside quotes (meaning you are parsing the JSON value "1493287973015" and not 1493287973015), your decoder could look like this:
import Json.Decode exposing (..)
import Date
import String
stringToDate : Decoder Date.Date
stringToDate =
string
|> andThen (\val ->
case String.toFloat val of
Err err -> fail err
Ok ms -> succeed <| Date.fromTime ms)
Notice that stringToDate doesn't get passed any parameters, as opposed to your example in which you were attempting to pass a Decoder String as a parameter. That's not quite how decoders work.
Instead, this can be done by building upon more primitive decoders, in this case, we start with the decoder string from Json.Decode.
The andThen portion then takes the string value given by the decoder, and tries to parse it to a float. If it is a valid Float, it is fed into Date.fromTime, otherwise, it's a failure.
The fail and succeed functions wrap up the normal values you're dealing with into Decoder Date.Date contexts so they can be returned.
Two things, a JSON may actually have the milliseconds as an integer, not a string and things have changed since v 0.19 of Elm.
Given that your JSON looks something like.
{
...
"someTime": 1552483995395,
...
}
Then this would decode to a Time.Posix:
import Json.Decode as Decode
decodeTime : Decode.Decoder Time.Posix
decodeTime =
Decode.int
|> Decode.andThen
(\ms ->
Decode.succeed <| Time.millisToPosix ms
)