How do I call an API in an ELM app, my code won't handle the response data? - elm

New to Elm can anyone help me understand why this code returns my error "could not load the page"? I am pretty sure it has something to do with the data being returned being JSON and I have not yet figured out how to manage that.
Basically i am new to Elm and want to take it a little further by working with more JSON data from free APIs can anyone lend a hand?
import Browser
import Html exposing (Html, text, pre)
import Http
-- MAIN
main =
Browser.element
{ init = init
, update = update
, subscriptions = subscriptions
, view = view
}
-- MODEL
type Model
= Failure
| Loading
| Success String
init : () -> (Model, Cmd Msg)
init _ =
( Loading
, Http.get
{ url = "http://api.openweathermap.org/data/2.5/weather?q=naples&APPID=mykey"
, expect = Http.expectString GotText
}
)
-- UPDATE
type Msg
= GotText (Result Http.Error String)
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
GotText result ->
case result of
Ok fullText ->
(Success fullText, Cmd.none)
Err _ ->
(Failure, Cmd.none)
-- SUBSCRIPTIONS
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.none
-- VIEW
view : Model -> Html Msg
view model =
case model of
Failure ->
text "I was unable to load your book."
Loading ->
text "Loading..."
Success fullText ->
pre [] [ text fullText ]
UPDATE -
This works in Ellie but not locally compiling using Elm 19
Something is off with the body of the `init` definition:
39|> ( Loading
40|> , Http.get
41|> { url = "http://127.0.0.1:8080/test"
42|> , expect = Http.expectString GotText
43|> }
44|> )
The body is a tuple of type:
( Model, Json.Decode.Decoder a -> Http.Request a )
But the type annotation on `init` says it should be:
( Model, Cmd Msg )
-- TYPE MISMATCH ---------------------------------------------- src/Fizzbuzz.elm
The 1st argument to `get` is not what I expect:
40| , Http.get
41|> { url = "http://127.0.0.1:8080/test"
42|> , expect = Http.expectString GotText
43|> }
This argument is a record of type:
{ expect : b, url : String }
But `get` needs the 1st argument to be:
String
-- TOO MANY ARGS ---------------------------------------------- src/Fizzbuzz.elm
The `expectString` value is not a function, but it was given 1 argument.
42| , expect = Http.expectString GotText
^^^^^^^^^^^^^^^^^
Are there any missing commas? Or missing parentheses?
UPDATED - I made a change to try sending any JSON to Elm from my Go webserver to confirm some things leaned by your answers thanks.

You are trying to make a cross-domain request. The mechanism for doing this is called CORS (Cross-Origin Resource Sharing). To enable CORS requests, the server must explicitly allow requests from your domain using the access-control-allow-origin response header. The open weather API does not have this header, so when the browser tries to request the data it is blocked by browser security restrictions.
You'll need to do one of the following:
ask openweather to whitelist your domain for CORS response headers (unlikely to be possible)
proxy the openweather request from your own domain's servers
The latter is more likely to be possible, but it also means you'll be able to keep your API key a secret. If you are making weather requests from the client then anyone that loads your web page will be able to see the API key on the requests their browser makes as well as in the source code of your webpage.
The working example given by #5ndG uses an API that has an access-control-allow-origin response header that explicitly whitelists Ellie, which is why it works there. You can look at the requests and responses using your browser's dev tools to see that this is the case.
good luck!

Your code should manage fine with a JSON response. It will just give you a JSON string. (Though probably you will want to decode your JSON so you can use it.)
I tried your code with a testing API I found on Google that returns some JSON: https://jsonplaceholder.typicode.com/todos/1
It works fine, displaying the JSON string as expected, so perhaps your url has something wrong with it?
See this Ellie for the working example.
It might also be a good idea to not throw away the Http error in your update function; it provides useful information when debugging.

Related

karate-name not working for nested feature file calls [duplicate]

I am doing a POC on karate-gatling to reuse my tests. I have referred to documentation and installed those versions. First of all awesome work as usual, very easy to setup and get going.
I am calling a feature file from MySimualtion.scala which has three other abstract feature calls as follows:
* def tranRef = TransactionReferenceUtils.generateTransactionReferenceStartWith('09')
* set payloadR /transaction_reference = tranRef
POST API >> /sending/v1/dm
* call read('classpath:../InitiateAbstract.feature')
* match responseStatus == 200
GET API By Reference >> /sending/v1/dm?ref={ref}
* call read('classpath:../RetrieveByRefAbstract.feature') {ref: #(tranRef)}
* match responseStatus == 200
GET API By Id>> /sending/v1/dm/{id}
* call read('classpath:../RetrieveByIdAbstract.feature') {id: #(pmId)}
* match responseStatus == 200
Abstract features use url keyword to invoke APIs.
MySimulation.scala looks like this
class MySimulation extends Simulation {
val protocol = karateProtocol(
"/sending/v1/dm?ref={ref}" -> Nil,
"/send/v1/dm/{id}" -> Nil,
"/sending/v1/dm" -> Nil
)
protocol.nameResolver = (req, ctx) => req.getUrlAndPath()
val create = scenario("create").exec(karateFeature("classpath:com/mastercard/send/xb/Testcases/Rem1Shot/Remit1ShotWithFrwdFeesRetrieve.feature"))
setUp(
create.inject(rampUsers(2) during (5 seconds)).protocols(protocol)
)
}
Now the issue is, in reports GET request with {id} and POST request is getting aggregated but GET requests with ref are reported individually.
I have also tried using nameResolver with getUrlAndPath, still no luck.
I am not sure if I am missing anything here.
Note:
There was another issue where I was not able to aggregate the GET request with id by using following protocol but now its fine when I include full uri.
"/dm/{id}" -> Nil,
"/dm" -> Nil
For that get request, pass a fake header and use that to control the nameResolver: https://github.com/intuit/karate/tree/master/karate-gatling#nameresolver
I would have expected /sending/v1/{dm} or something like that to work.
Note that in theory you can write some custom Scala code to parse the URL and do the name resolution. If you feel this should be made easier, submit a feature request, or better still, contribute code !

Karate-Gatling: Requests with query parameter in reports are not getting aggregated

I am doing a POC on karate-gatling to reuse my tests. I have referred to documentation and installed those versions. First of all awesome work as usual, very easy to setup and get going.
I am calling a feature file from MySimualtion.scala which has three other abstract feature calls as follows:
* def tranRef = TransactionReferenceUtils.generateTransactionReferenceStartWith('09')
* set payloadR /transaction_reference = tranRef
POST API >> /sending/v1/dm
* call read('classpath:../InitiateAbstract.feature')
* match responseStatus == 200
GET API By Reference >> /sending/v1/dm?ref={ref}
* call read('classpath:../RetrieveByRefAbstract.feature') {ref: #(tranRef)}
* match responseStatus == 200
GET API By Id>> /sending/v1/dm/{id}
* call read('classpath:../RetrieveByIdAbstract.feature') {id: #(pmId)}
* match responseStatus == 200
Abstract features use url keyword to invoke APIs.
MySimulation.scala looks like this
class MySimulation extends Simulation {
val protocol = karateProtocol(
"/sending/v1/dm?ref={ref}" -> Nil,
"/send/v1/dm/{id}" -> Nil,
"/sending/v1/dm" -> Nil
)
protocol.nameResolver = (req, ctx) => req.getUrlAndPath()
val create = scenario("create").exec(karateFeature("classpath:com/mastercard/send/xb/Testcases/Rem1Shot/Remit1ShotWithFrwdFeesRetrieve.feature"))
setUp(
create.inject(rampUsers(2) during (5 seconds)).protocols(protocol)
)
}
Now the issue is, in reports GET request with {id} and POST request is getting aggregated but GET requests with ref are reported individually.
I have also tried using nameResolver with getUrlAndPath, still no luck.
I am not sure if I am missing anything here.
Note:
There was another issue where I was not able to aggregate the GET request with id by using following protocol but now its fine when I include full uri.
"/dm/{id}" -> Nil,
"/dm" -> Nil
For that get request, pass a fake header and use that to control the nameResolver: https://github.com/intuit/karate/tree/master/karate-gatling#nameresolver
I would have expected /sending/v1/{dm} or something like that to work.
Note that in theory you can write some custom Scala code to parse the URL and do the name resolution. If you feel this should be made easier, submit a feature request, or better still, contribute code !

Idiomatic authentication in Elm

I'm trying to wrap my head around Elm. I have experience in Haskell, and a bit of Erlang.
I want to complete the following exercise:
User is shown a login form
On submit, the frontend makes a request to localhost/auth to try and receive an auth token.
On success, the homepage is shown, which fetches some data.
On failure, the login screen displays an error.
This is quite basic, but hopefully complex enough to model the behaviour of a real webapp.
My first problem is with the Model. I only need the data if the client is authenticated. Should I wrap this in something similar to a Maybe monad?
type Model
= NoAuth String String
| AuthFetching
| AuthFailed err
| AuthSuccess String
And then the login screen can display a spinner, and error, or redirect to a new page.
This feels like it ties the rest of the applications state to the authentication state. Although is is "correct", it feels wrong to have the whole model be a (variant type?) with only one record.
It "feels" more correct to have the model like so:
type FetchStatus
= Loading
| Success val
| Err err
type Model =
{ token : RequestStatus String
, data : List number
}
But whenever you update the model, you now need to check if token is still present - i.e. pattern match within the record. In the first example, you only needed to pattern match on the whole model, which is a bit simpler.
And to hold the login form state, I'd need to add extra fields:
type Model =
{ token : RequestStatus String
, data : List number
, username : String
, password : String
}
Which feels incorrect because the password should not be held in memory after login. I'd hold these in records, but I cannot use records in custom type declarations.
All in all, I'm a bit confused. Can someone please shed some light on the most "correct", idiomatic way to do this?
All authorization related stuff should be handled on the backend side. Elm's job is only to display what server has sent to it. In my opinion the first option you proposed is the best for such a little example, but in more real-life application the typesystem would be more complex:
type LoginForm =
{ username : String
, password : String
}
type Activity
= Login LoginForm
| LoginSuccess
| LoginFailure String
type Model =
{ loggedUser : Maybe String
, activity : Activity
, ...
}
You don't need (and shouldn't) keep password on frontend. You also shouldn't perform any authorizations on the client side, as the client may easily replace any script in his browser. The backend will track whether the user is logged in by eg. session cookies. In this scenario even if the loggedUser value is set to Just "someguy" and "someguy" is not marked as logged in the server database, any action that requires authorization shall fail.
Summarizing, handling login and giving permissions to access any content is a job for backend. Elm is frontend language, so it's only purpose here is to display things.

Extract report results with CloudConnect

I would like to extract raw report results within the CloudConnect process.
So far I have managed to get response from the raw report API end point - https://secure.gooddata.com/gdc/app/projects/{project_id}/execute/raw/
This response contains URI to the file and if I put that URI to browser, file is uploaded.
I have tried passing this URI to the following readers without success:
CSV Reader produces the following error:
------------------- Error details ------------------
Component [CSV Reader:CSV_READER] finished with status ERROR.
Parsing error: Unexpected end of file in record 1, field 1 ("date"),
metadata "outOfStock";
value: Raw record data is not available, please turn on verbose mode.
File Download - I don't know how to pass the URI through the port to "URL to Downlaod" parameter.
HTTP Connector again I don't see how to pass URI from the port.
What is the way to do this?
EDIT
If I use the HTTP Connector as suggested by #Filip, I get the following error:
Error details:
Component [HTTP connector:HTTP_CONNECTOR] finished with status ERROR. hostname in
certificate didn't match: xxx.com != secure.gooddata.com OR secure.gooddata.com
I have tried setting header to X-GDC-CHECK-DOMAIN: false with no effect.
The HTTP connector is the right component to go with. Leave the URL property empty and use the component’s property called “Input mapping”, where in the graphic editor you can assign the input edge field to the URL field.
Solution from GoodData support:
HTTP connector can be also used, but it is very complex, because
logging in to GoodData has to be created. REST connector has it built
in.
If you want to run the example graph, you have to be logged in in
CloudConnect with a user who has access to the project from where you
would like to export the report. You also have to change URL to
the one of white-labeled account in both REST connector components and change project
and report definition in the first REST connector.
So the graph that works looks like this:
Here are the main fields that you will need to set for each element:
Get Results URI - set params for POST request:
Request URL = https://secure.gooddata.com/gdc/app/projects/${GDC_PROJECT_ID}/execute/raw/
Request Body =
{
"report_req": {
"reportDefinition": "gdc/md/${GDC_PROJECT_ID}/obj/${OBJECT_ID}"
}
}
Get URI from Response - just map uri value to corresponding field:
<Mapping cloverField="uri" xpath="uri"/>
Load Results - make sure it is connected to metadata with two fields, one for response with data, other to pass through the uri.
Load Results - you will need to exclude uri field to process the data:
Exclude Fields = uri

Can I read raw POST data with Ocamlnet CGI?

I'm writing some web services using Ocamlnet. More specifically, I'm using Netcgi_scgi in combination with Apache 2.
It looks like all of the functionality is directed at presenting me with an already-parsed view of the user input.
Is there any way for me to access the input stream directly so I can parse the raw POST data myself?
Also, is there a way for me to prevent the Netcgi code from automatically trying to parse the POST data? (I found that, the way I'm doing it, it fails and throws an exception if it doesn't receive the expected key-value format, so my handler never even gets called.)
I would ideally like to be able to support JSON-RPC, which calls for the POST data to be a JSON object with no argument name associated with it. (Well, the spec is not protocol specific so that isn't spelled out, but it seems to me to be the most reasonable interpretation. The old (1.2) spec explicitly shows it done that way.)
Easy way would be (using plex) allowing application/json mime
Nethttpd_plex.nethttpd_factory
~config_cgi:Netcgi.({
default_config with
default_exn_handler = false;
permitted_input_content_types = [
"application/json";
] # default_config.permitted_input_content_types})
Then you will get content of request in single parameter BODY (if client properly sends Content-Type: application/json header), so you can do something like
let get_json_request (cgi : Netcgi.cgi_activation) =
cgi # argument_value "BODY"
|> Yojson.Basic.from_string
let generate_page (cgi : Netcgi.cgi_activation) =
get_json_request cgi |> process_json_request
A little harder (but probably better) way would be providing your own dyn_activation and dyn_handler for your handler or creating new service (look for default_dynamic_service in ocamlnet source)... but I newer went down that path...
Use cgi_argument:
let with_arg arg f = Io.unwind ~protect:(fun arg -> arg#finalize ()) f arg
let get_arg cgi name =
try Some (with_arg (cgi#argument name) (fun arg -> arg#value))
with Not_found -> None
let parse ?default ~validate ~parse cgi name =
match default, get_arg cgi name with
| None , None -> argf "Missing parameter \"%s\"" name
| Some v, None -> v
| _ , Some p ->
try parse (Pcre.extract ~rex:validate ~full_match:false p)
with Not_found -> argf "Invalid parameter \"%s\"" name
References
Netcgi:cgi_argument
How to Write a Simple Web Application using Ocamlnet