Elm StartApp decoding http request - elm

I'm trying to decode a http request to pokéapi in Elm, using StartApp as a base. Though I'm getting an error I don't really know how to fix:
The right argument of (|>) is causing a type mismatch.
76│ Http.getString testUrl
77│ |> Task.map parseMon
78│> |> Task.map OnPokemonLoaded
(|>) is expecting the right argument to be a:
Task Http.Error (Result String Pokemon) -> a
But the right argument is:
Task Http.Error (Result Http.Error Pokemon) -> Task Http.Error Action
The code it's talking about is:
-- Fetching test mon
testUrl : String
testUrl = "http://pokeapi.co/api/v2/pokemon/1/"
fetchTest : Effects.Effects Action
fetchTest =
Http.getString testUrl
|> Task.map parseMon
|> Task.map OnPokemonLoaded --line 78
|> Effects.task
parseMon : String -> Result String Pokemon.Pokemon
parseMon json = Json.Decode.decodeString Pokemon.decoder json
OnPokemonLoaded is one of my actions: OnPokemonLoaded (Result Http.Error Pokemon). Pokemon.decoder is a simple json decoder: decoder : Decoder Pokemon.
I'm still new to Elm, and only just trying out StartApp and Effects. The error seems to explain the problem pretty well, but I'm still a little lost as to how it should work.
So, how should I request and decode the json properly?

The use of Http.getString and parseMon is unnecessary. Instead, you can use Http.get and pass your Json decoder, then map it to a Result to get the functionality you're after:
fetchTest : Effects.Effects Action
fetchTest =
Http.get Pokemon.decoder testUrl
|> Task.toResult
|> Task.map OnPokemonLoaded
|> Effects.task

Related

How do you iterate a List (Maybe a)

I have the following graphQL result:
[Just { details = Just "Engine failure at 33 seconds and loss of
vehicle", launch_year = Just "2006", links = Just { article_link =
Just
"https://www.space.com/2196-spacex-inaugural-falcon-1-rocket-lost-launch.html"
}, mission_name = Just "FalconSat" }]
Based on the following types:
type alias Launch =
{ mission_name : Maybe String
, details : Maybe String
, launch_year : Maybe String
, links : Maybe LaunchLinks
}
type alias Launches =
Maybe (List (Maybe Launch))
type alias LaunchLinks =
{ article_link : Maybe String
}
I want to List.map through and display the results in unordered list. I started with this:
renderLaunch : Launches -> Html Msg
renderLaunch launches =
div [] <|
case launches of
Nothing ->
[ text "Nothing here" ]
Just launch ->
launch
|> List.map (\x -> x)
|> ul []
But I keep getting this error:
This function cannot handle the argument sent through the (|>) pipe:
141| launch 142| |> List.map (\x
-> x) 143| |> ul []
^^^^^ The argument is:
List (Maybe Launch)
But (|>) is piping it a function that expects:
List (Html msg)
The problem is that the Just launch case needs to result in a List (Html msg) but the code results in a different type being returned.
When you are using List.map (\x -> x), it is essentially a no-op. You are iterating over a List (Maybe Launch) and returning the same thing. I'd recommend creating another function that takes a Maybe Launch value and use that as your mapping function. For example:
displayLaunch : Maybe Launch -> Html Msg
displayLaunch launch =
case launch of
Nothing -> text "No launch"
Just l -> text (Debug.toString l)
Now you can plug that into your mapping function:
Just launch ->
launch
|> List.map displayLaunch
|> ul []
But, whoops! Now you get a new error indicating:
The 2nd branch is:
Html Msg
But all the previous branches result in:
List (Html msg)
The problem here is that we are now returning a ul from the Just launch branch and we need to return a list of html. You can use List.singleton to create a list with just one item:
Just launch ->
launch
|> List.map displayLaunch
|> ul []
|> List.singleton

Getting type signatures for a function in elm

I'm using elm 0.18.
Let's say I have a function that strings together a bunch of stuff that I threw together in a hurry. It works, but I'm not sure what it's type signature is, and I'd like elm to tell me (or hint for me) that type signature.
For example, I use graphql and have a function that takes a graphql string, a decoder (which also doesn't have a type signature), and a Cmd Msg, and runs it through HttpBuilder.
graphQLPost graphiql decoder msg =
HttpBuilder.post (url ++ "api")
|> HttpBuilder.withStringBody "text/plain" graphiql
|> HttpBuilder.withExpect (Http.expectJson decoder)
|> HttpBuilder.send msg
This works, though I don't know why. I tried fitting it with the type signature graphQLPost : String -> Json.Decode.Decoder -> Cmd Msg, but I get an error.
Figuring out this type signature is not as important to me as finding a way to induce them through elm. Is there a command that I can enter into elm-repl or something that will tell me the signature?
Elm REPL will do this for you:
> import Http
> import HttpBuilder
> type Msg = Msg
> url = "..."
"..." : String
> graphQLPost graphiql decoder msg = \
| HttpBuilder.post (url ++ "api") \
| |> HttpBuilder.withStringBody "text/plain" graphiql \
| |> HttpBuilder.withExpect (Http.expectJson decoder) \
| |> HttpBuilder.send msg
<function>
: String
-> Json.Decode.Decoder a
-> (Result.Result Http.Error a -> msg)
-> Platform.Cmd.Cmd msg
When you write a function and hit <Enter>, it shows you the signature. In this case the signature is:
graphQLPost : String
-> Json.Decode.Decoder a
-> (Result.Result Http.Error a -> msg)
-> Platform.Cmd.Cmd msg
Running elm-make with the --warn option will cause the compiler to suggest that you include a type annotation on functions that don't have one, and it will provide one for you to copy and paste in.
Also, some editor integrations, such as the Visual Studio Code language extension for Elm, will display these kinds of warnings as a hint icon that you can click to add the missing type annotation automatically. You can set a keyboard shortcut for this to do it without your hands leaving the keyboard.

Recursion related exception: Unable to get property 'tag' of undefined or null reference

I receive the following error after performing an HTTP post:
Unable to get property 'tag' of undefined or null reference
I believe the error occurs when executing the following decoder function:
sourceDecoder : Decoder JsonSource
sourceDecoder =
Decode.map5 JsonSource
...
(field "Links" providerLinksDecoder)
Decoder Dependencies:
providerLinksDecoder : Decoder JsonProviderLinks
providerLinksDecoder =
Decode.map JsonLinkFields
(field "Links" <| Decode.list (Decode.lazy (\_ -> linkDecoder)))
|> Decode.map JsonProviderLinks
linkDecoder : Decoder JsonLink
linkDecoder =
Decode.map6 JsonLink
(field "Profile" profileDecoder)
...
profileDecoder : Decoder JsonProfile
profileDecoder =
Decode.map7 JsonProfile
...
(field "Sources" <| Decode.list (Decode.lazy (\_ -> sourceDecoder)))
Appendix:
type JsonProviderLinks
= JsonProviderLinks JsonLinkFields
type alias JsonLinkFields =
{ links : List JsonLink
}
The source code can be found on here.
Note: I attempted to research this error and came across this page.
As a result, I attempted to use the Decode.lazy function. However, my attempt failed.
There's a lot of decoders that rely on other decoders in your examples. You've changed some of them to use Decode.lazy, but not all, and that error you've received will happen when there's some out of control recursion.
You don't need a list to be able to use lazy. Try - as a first step towards debugging, at least - to change all decoders that reference other decoders to use Decode.lazy. For example:
sourceDecoder : Decoder JsonSource
sourceDecoder =
Decode.map5 JsonSource
...
(field "Links" (Decode.lazy (\_ -> providerLinksDecoder)))

Elm, JSON decoder: How to decode an empty string?

What's the best way to handle an empty (no string at all) response?
Although the response code is 200, Elm returns an error because an empty response is not a valid JSON.
Here is my current code:
decodeAlwaysTrue : Json.Decode.Decoder Bool
decodeAlwaysTrue =
Json.Decode.succeed True
Http.send Http.defaultSettings httpConfig
|> Http.fromJson decodeAlwaysTrue
|> Task.perform FetchFail DeleteUserSuccess
EDIT1:
This a POST action so I can't use getString.
You could use the getString function from the Http module. That will give you back whatever string is returned from the HTTP request without attempting to convert is to a Json value.
If you instead need to use Http.send then you could do something like this:
Http.send Http.defaultSettings httpConfig
|> Task.perform FetchFail (always DeleteUserSuccess)
This assumes that DeleteUserSuccess is changed to be defined with no type parameter:
type Msg =
...
DeleteUserSuccess
It looks like you are never getting back a Json response so you'll probably be better using Http.getString
type Result = FetchFail Error
| DeleteUserSuccess
Http.getString address
|> Task.perform FetchFail (\s -> DeleteUserSuccess)
Since the successful get doesn't contain any information you can just ignore it and return DeleteUserSuccess regardless of the content of the string.

Elm read HTTP response body for non-200 response

How to read HTTP response body for a non 200 HTTP status
getJson : String -> String -> Effects Action
getJson url credentials =
Http.send Http.defaultSettings
{ verb = "GET"
, headers = [("Authorization", "Basic " ++ credentials)]
, url = url
, body = Http.empty
}
|> Http.fromJson decodeAccessToken
|> Task.toResult
|> Task.map UpdateAccessTokenFromServer
|> Effects.task
The above promotes the error from
Task.toResult : Task Http.Error a -> Task x (Result Http.Error a)
The value of which becomes
(BadResponse 400 ("Bad Request"))
My server responds with what is wrong with the request as a JSON payload in the response body. Please help me retrieve that from the Task x a into ServerResult below.
type alias ServerResult = { status : Int, message : String }
The Http package (v3.0.0) does not expose an easy way to treat HTTP codes outside of the 200 to 300 range as non-error responses. Looking at the source code, the handleResponse function is looking between the hardcoded 200 to 300 range
However, with a bit of copy and pasting from that Http package source code, you can create a custom function to replace Http.fromJson in order to handle HTTP status codes outside the normal "success" range.
Here's an example of the bare minimum you'll need to copy and paste to create a custom myFromJson function that acts the same as the Http package except for the fact it also treats a 400 as a success:
myFromJson : Json.Decoder a -> Task Http.RawError Http.Response -> Task Http.Error a
myFromJson decoder response =
let decode str =
case Json.decodeString decoder str of
Ok v -> Task.succeed v
Err msg -> Task.fail (Http.UnexpectedPayload msg)
in
Task.mapError promoteError response
`Task.andThen` myHandleResponse decode
myHandleResponse : (String -> Task Http.Error a) -> Http.Response -> Task Http.Error a
myHandleResponse handle response =
if (200 <= response.status && response.status < 300) || response.status == 400 then
case response.value of
Http.Text str ->
handle str
_ ->
Task.fail (Http.UnexpectedPayload "Response body is a blob, expecting a string.")
else
Task.fail (Http.BadResponse response.status response.statusText)
-- copied verbatim from Http package because it was not exposed
promoteError : Http.RawError -> Http.Error
promoteError rawError =
case rawError of
Http.RawTimeout -> Http.Timeout
Http.RawNetworkError -> Http.NetworkError
Again, that code snippet is almost entirely copy and pasted except for that 400 status check. Copying and pasting like that is usually a last resort, but because of the library restrictions, it seems to be one of your only options at this point.