parsing json return from http in erlang - json

I test with this code :
get_fee(Transaction,SourceNumber,Amount, Currency) ->
Url = lists:concat(["http://localhost/test.php","?transaction=", Transaction, "&saccount=", SourceNumber,Amount,"&currency=",Currency]),
inets:start(),
{Flag, Response} = http:request(get, {Url, []}, [], []),
case Flag of
ok ->
{ { _, ReturnCode, _ }, _, Body } = Response,
if ReturnCode =:= 200 ->
{ok,{_,[{_,Code},{_,Permission},{_,Payer},{_,Payee}]}} = json:decode_string(Body),
case Permission of true ->
if Code =:= 200 ->
{ok,{Code, Payer, Payee}};
Code =:= 204 ->
{nok,{Code, not_found}};
true ->
{nok,{Code, parameter_error}}
end;
false ->
{nok,{Code, parameter_error}}
end;
true->
{error, http_error}
end;
error ->
case Response of
nxdomain -> {error, dns_error};
_ -> {error, network_error}
end
end.
the response from the http is : {"code":200,"permission":true,"fee_payer":0,"fee_payee":19}
But now I like to do the same think but the return of http in this case for example is :
{"CIN":"08321224","Name":21}
so I have just CIN and Name in this case
I try to change the previous
get_fee(Num) ->
Url = lists:concat(["http://localhost/GTW/Operation.php","?ACCOUNT_NUM=", Num]),
inets:start(),
{Flag, Response} = http:request(get, {Url, []}, [], []),
case Flag of
ok ->
{ { _, ReturnCode, _ }, _, Body } = Response,
%% for debug
io:format("~p~n",[ReturnCode]),
if ReturnCode =:= "08321224" ->
{ok,{_,[{_,CIN},{_,Name}]}} = json:decode_string(Body),
case Name of 21 ->
io:format(CIN),
io:format(Name),
if CIN =:= "08321224"->
{ok,{CIN, Name}};
CIN =:= 204 ->
{nok,{CIN, not_found}};
true ->
{nok,{CIN, parameter_error}}
end;
false ->
{nok,{CIN, parameter_error}}
end;
true->
{error, http_error}
end;
error ->
case Response of
nxdomain -> {error, dns_error};
_ -> {error, network_error}
%% for debug
%%io:format("pass2~n ~p~n",[Response]),
end
end.
but it displys :
test:get_fee("0001").
200
{error,http_error}

So I'll nitpick on the style here, because you will be far better off if you follow the semantical idea of Erlang:
get_fee(Num) ->
Url = lists:concat(["http://localhost/GTW/Operation.php","?ACCOUNT_NUM=", Num]),
inets:start(),
This is the wrong place to start inets. It should be started outside this function as you only need to
do this one.
{Flag, Response} = http:request(get, {Url, []}, [], []),
This part is better coded with a pattern match. The discrimination of Flag and Response can
be decoded directly with a simple match. Write,
case http:request(get, {Url, []}, [], []) of
{ok, {{_, 200, _}, _, Body}} ->
{ok, R} = json:decode_string(Body),
get_fee_decode_(get_cin(R), get_name(R));
{error, Reason} -> {error, Reason}
end.
I would recommend against changing {error, nxdomain} to {error, dns_error} since nxdomain perfectly
codes this case in any case. Just pass the error tuple to the caller and have him handle it.
get_fee_decode_("08321224" = CIN, 21 = Name) -> {ok, {CIN, Name}};
get_fee_decode_("204" = CIN, 21) -> {nok, {CIN, not_found}};
get_fee_decode_(CIN, _Name) -> {nok, {CIN, parameter_error}};
Introduce a new function like this to handle the inner parts of your code base. And hoist the matching to
top-level. This helps in the long run by decoupling your code into functions.
Do note that in a JSON structure, there is no order on an "object" so you can't assume that the structure is
{"code":200,"permission":true,"fee_payer":0,"fee_payee":19}
But a decode does not have to preserve this structure, according to JSON. So a valid decode may be:
[{"fee_payee", 19}, {"fee_payer", 0}, {"permission", true}, {"code", 200}]
This will fail to match in your code and you are setting yourself up for some nasty errors later on.
You want something along the lines of:
get_fee_payer(PL) -> proplists:get_value("fee_payer", PL).
Another thing which will be a problem with your programming style is that you are hiding error-information cases. In Erlang, you can often get away with only handling the "happy path" through the code and leave all the error handling out until you know what kind of errors are there in the code base. Then you can begin adding in error handling slowly. Defensive programming is not a thing you should be doing if you can avoid it.

You changed:
if ReturnCode =:= 200 ->
to:
if ReturnCode =:= "08321224" ->
However, that needs to stay the same in your version. 200 is the HTTP status code for "OK" - the first step here is to verify that the server actually processed the request and returned a positive reply. You'll find that number only in Body - that is what the if CIN =:= "08321224"-> part is for.

Related

ejabberd add_channel API for MIX

Currently, there is no api for creating a MIX channel.
I'm written a custom module for the same.
So far, I have written the following code. But I'm not how to proceed further.
I would really appreciate someone's guidance here. Thanks in advance.
-module(mod_custom).
-behaviour(gen_mod).
-include("logger.hrl").
-export([start/2, stop/1, reload/3, mod_options/1,
get_commands_spec/0, depends/2]).
-export([
% Create channel
add_channel/4
]).
-include("ejabberd_commands.hrl").
-include("ejabberd_sm.hrl").
-include("xmpp.hrl").
start(_Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()).
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
true ->
ok
end.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[].
get_commands_spec() ->
[
#ejabberd_commands{name = add_channel, tags = [group],
desc = "Create a WhatsApp like group",
module = ?MODULE, function = add_channel,
args = [{jid, binary}, {channel, binary}, {id, binary}],
args_example = [<<"admin#localhost">>, <<"testgroup123#localhost">>, <<"abc123456">>],
args_desc = ["Admin JID", "Channel JID", "Unique ID"],
result = {res, rescode}}
].
add_channel(JID, Channel, ID) ->
%%% Create channel code goes here...
ok.
mod_options(_) -> [].
Try something like this:
-module(mod_custom).
-behaviour(gen_mod).
-export([start/2, stop/1, reload/3, mod_options/1,
get_commands_spec/0, depends/2]).
-export([create_channel/3]).
-include("logger.hrl").
-include("ejabberd_commands.hrl").
-include("ejabberd_sm.hrl").
-include_lib("xmpp/include/xmpp.hrl").
start(_Host, _Opts) ->
ejabberd_commands:register_commands(get_commands_spec()).
stop(Host) ->
case gen_mod:is_loaded_elsewhere(Host, ?MODULE) of
false ->
ejabberd_commands:unregister_commands(get_commands_spec());
true ->
ok
end.
reload(_Host, _NewOpts, _OldOpts) ->
ok.
depends(_Host, _Opts) ->
[].
get_commands_spec() ->
[#ejabberd_commands{name = create_channel, tags = [group],
desc = "Create a WhatsApp like group",
module = ?MODULE, function = create_channel,
args = [{from, binary},
{channel, binary},
{service, binary}],
args_example = [<<"admin#localhost">>,
<<"testgroup123">>,
<<"mix.localhost">>],
args_desc = ["From JID", "Channel Name", "MIX Service"],
result = {res, rescode}}
].
create_channel(From, ChannelName, Service) ->
try xmpp:decode(
#xmlel{name = <<"iq">>,
attrs = [{<<"to">>, Service},
{<<"from">>, From},
{<<"type">>, <<"set">>},
{<<"id">>, p1_rand:get_string()}],
children =
[#xmlel{name = <<"create">>,
attrs = [{<<"channel">>, ChannelName},
{<<"xmlns">>, ?NS_MIX_CORE_0}]}
]},
?NS_CLIENT, []) of
#iq{type = set} = Iq ->
case mod_mix:process_mix_core(Iq) of
#iq{type = result} ->
ok;
_ ->
{error, unexpected_response}
end
catch _:{xmpp_codec, Why} ->
{error, xmpp:format_error(Why)}
end.
mod_options(_) -> [].

Beginner in Elixir : parse CSV file

Hello I'm a beginner in Elixir and I want to parse and stock a CSV file in an Elixir object.
But it's display that:
** (FunctionClauseError) no function clause matching in anonymous fn/1 in Siren.parseCSV/0
The following arguments were given to anonymous fn/1 in Siren.parseCSV/0:
# 1
["41", "5", "59", "N", "80", "39", "0", "W", "Youngstown", "OH"]
anonymous fn/1 in Siren.parseCSV/0
(elixir 1.10.3) lib/stream.ex:482: anonymous fn/4 in Stream.filter/2
(elixir 1.10.3) lib/stream.ex:1449: Stream.do_element_resource/6
(elixir 1.10.3) lib/stream.ex:1609: Enumerable.Stream.do_each/4
(elixir 1.10.3) lib/enum.ex:959: Enum.find/3
(mix 1.10.3) lib/mix/task.ex:330: Mix.Task.run_task/3
(mix 1.10.3) lib/mix/cli.ex:82: Mix.CLI.run_task/2
Here my code:
defmodule Siren do
def parseCSV do
IO.puts("Let's parse CSV file...")
File.stream!("../name.csv")
|> Stream.map(&String.trim(&1))
|> Stream.map(&String.split(&1, ","))
|> Stream.filter(fn
["LatD" | _] -> false
end)
|> Enum.find(fn State -> String
[LatD, LatM, LatS, NS, LonD, LonM, LonS, EW, City, State] ->
IO.puts("find -> #{State}")
true
end)
end
end
And the csv file:
LatD,LatM,LatS,NS,LonD,LonM,LonS,EW,City,State
41,5,59,N,80,39,0,W,Youngstown,OH
42,52,48,N,97,23,23,W,Yankton,SD
46,35,59,N,120,30,36,W,Yakima,WA
42,16,12,N,71,48,0,W,Worcester,MA
43,37,48,N,89,46,11,W,WisconsinDells,WI
36,5,59,N,80,15,0,W,Winston-Salem,NC
49,52,48,N,97,9,0,W,Winnipeg,MB
39,11,23,N,78,9,36,W,Winchester,VA
34,14,24,N,77,55,11,W,Wilmington,NC
39,45,0,N,75,33,0,W,Wilmington,DE
48,9,0,N,103,37,12,W,Williston,ND
41,15,0,N,77,0,0,W,Williamsport,PA
37,40,48,N,82,16,47,W,Williamson,WV
33,54,0,N,98,29,23,W,WichitaFalls,TX
37,41,23,N,97,20,23,W,Wichita,KS
40,4,11,N,80,43,12,W,Wheeling,WV
26,43,11,N,80,3,0,W,WestPalmBeach,FL
47,25,11,N,120,19,11,W,Wenatchee,WA
41,25,11,N,122,23,23,W,Weed,CA
The first issue is here:
|> Stream.filter(fn
["LatD" | _] -> false
end)
all the lines should pass this and the only first one matches the given clauses. This would fix the issue
|> Stream.filter(fn
["LatD" | _] -> false
_ -> true
end)
or
|> Stream.reject(&match?(["LatD" | _], &1))
Enum.find(fn State -> String after looks unclear and would be surely the next issue. I failed to understand what have you tried to achieve here.
The general advice would be: don’t reinvent the wheel and use NimbleCSV written by José Valim to parse CSVs, because there are lot of corner cases (like commas inside quotes in any field etc,) handled properly in the aforementioned library.
Aleksei Matiushkin gave you the right answer but also you have this function:
fn
State ->
String
[LatD, LatM, LatS, NS, LonD, LonM, LonS, EW, City, State] ->
IO.puts("find -> #{State}")
true
end
It accepts two possible values, either State which is an atom, or a list of 10 specific atoms.
What you want to do is use variables, and variables in Elixir start with a lowercase letter or an underscore if it has to be ignored.
fn
state ->
String
[latd, latm, lats, ns, lond, lonm, lons, ew, city, state] ->
IO.puts("find -> #{state}")
true
end
But in this case, the first clause of the function will always match anything because it acts like a catch-all clause.
What you probably want is:
fn
[_latd, _latm, _lats, _ns, _lond, _lonm, _lons, _ew, _city, state] ->
IO.puts("find -> #{state}")
# here decide if you want to return true or false,
# for instance `state == NC`
true
end

No function clause matching in Regex.match?/2 on Elixir w/ Phoenix

I have a route in Phoenix that needs to check the JSON parameters with REGEX.
In my routine, I am creating a list of errors to report in the body in case one or more regexes fail.
But whenever I run the code, I get the "FunctionClauseError" error on "Regex.match?". I have tried String.match, but they evaluate to the same function.
Here is my code:
def postServidor(conn, parameters) do
reasons = []
error = False
if not Regex.match?(~r/^(19[0-9]{2}|2[0-9]{3})-(0[1-9]|1[012])-([123]0|[012][1-9]|31)$/, Map.get(parameters, "data_nascimento")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[data_nascimento] missing or failed to match API requirements. It should look like this: 1969-02-12"}]}
end
if not Regex.match?(~r/^([A-Z][a-z]+([ ]?[a-z]?['-]?[A-Z][a-z]+)*)$/, Map.get(parameters, "nome")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[name] missing or failed to match API requirements. It should look like this: Firstname Middlename(optional) Lastname"}]}
end
if not Regex.match?(~r/^([A-Z][a-z]+([ ]?[a-z]?['-]?[A-Z][a-z]+)*)$/, Map.get(parameters, "nome_identificacao")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[nome_identificacao] missing or failed to match API requirements. It should look like this: Firstname Middlename(optional) Lastname"}]}
end
if not Regex.match?(~r/\b[MF]{1}\b/, Map.get(parameters, "sexo")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[sexo] missing or failed to match API requirements. It should look like this: M for male, F for female"}]}
end
if not Regex.match?( ~r/\b[0-9]+\b/, Map.get(parameters, "id_pessoa")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[id_pessoa] missing or failed to match API requirements. It should be numeric. "}]}
end
if not Regex.match?(~r/\b[0-9]+\b/, Map.get(parameters, "matricula_interna")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[matricula_interna] missing or failed to match API requirements. It should be numeric. "}]}
end
if not Regex.match?(~r/\b[0-9]+\b/, Map.get(parameters, "siape")) do
{error, reasons} = {True, reasons ++ [%{Reason => "[siape] missing or failed to match API requirements. It should be numeric. "}]}
end
if error = True do
json put_status(conn, 400),reasons
else
IO.puts("ok")
end
end
Regex.match?/2 requires a string as its second argument. You're using Map.get/3 to potentially return that string, but if the key isn't found in the map, it defaults to nil, and that's my guess as to what's happening. If the key is not found in your map, it's passing nil to Regex.match?/2, for which there will be no function clause that matches. You can either fix your map to correctly have the key, or you can provide your own default string to use as a third parameter to Map.get/3. For example, Map.get(parameters, "data_nascimento", "some default").

How do functions work in Elixir?

In book "Programming Elixir" Dave Thomas shows an example
handle_open = fn
{:ok, file} -> "Read data: #{IO.read(file, :line)}"
{_, error} -> "Error: #{:file.format_error(error)}"
end
handle_open.(File.open("code/intro/hello.exs"))
handle_open.(File.open("nonexistent"))
I can't understand why second call of function goes into second flow.
Also why we don't pass an argument into func:
handle_open = fn(file)
?
This is a multi-clause anonymous function. handle_open has 2 clauses, one to match arguments with the pattern {:ok, file} and one with {_, error}. The first pattern that matches the arguments is executed. The given code is almost [1] equivalent to the following:
handle_open = fn arg ->
case arg do
{:ok, file} -> ...
{_, error} -> ...
end
end
So if the file exists, File.open will return {:ok, file} and the first clause will be executed. If it doesn't, {:error, error} will be returned which will match the second clause and execute that.
[1]: "Almost" because the error raised when none of the patterns match will be slightly different for the two cases.
This actually just mimics the pattern-match behaviour of Elixir.
Like you can have pattern match on your Module functions as well.
defmodule MyModule do
def read_file(path) do
File.open(path) |> handle_file()
end
defp handle_file({:ok, file}) do
// File there..
end
defp handle_file({_, error}) do
// Could not open file.. because of `error`
end
end
Like in the book example, I would try to open the file inside a case clause and delegate the outcome to the appropriate function:
defmodule FileReader do
def read_file(path) do
case File.open(path) do
{:ok, file} -> handle_file(file)
{_, error} -> handle_error(error)
end
end
defp handle_file(file) do
// File there..
end
defp handle_error(error) do
//
end
end

How do exceptions work in Haskell (part two)?

I have the following code:
{-# LANGUAGE DeriveDataTypeable #-}
import Prelude hiding (catch)
import Control.Exception (throwIO, Exception)
import Control.Monad (when)
import Data.Maybe
import Data.Word (Word16)
import Data.Typeable (Typeable)
import System.Environment (getArgs)
data ArgumentParserException = WrongArgumentCount | InvalidPortNumber
deriving (Show, Typeable)
instance Exception ArgumentParserException
data Arguments = Arguments Word16 FilePath String
main = do
args <- return []
when (length args /= 3) (throwIO WrongArgumentCount)
let [portStr, cert, pw] = args
let portInt = readMaybe portStr :: Maybe Integer
when (portInt == Nothing) (throwIO InvalidPortNumber)
let portNum = fromJust portInt
when (portNum < 0 || portNum > 65535) (throwIO InvalidPortNumber)
return $ Arguments (fromInteger portNum) cert pw
-- Newer 'base' has Text.Read.readMaybe but alas, that doesn't come with
-- the latest Haskell platform, so let's not rely on it
readMaybe :: Read a => String -> Maybe a
readMaybe s = case reads s of
[(x, "")] -> Just x
_ -> Nothing
Its behavior differs when compiled with optimizations on and off:
crabgrass:~/tmp/signserv/src% ghc -fforce-recomp Main.hs && ./Main
Main: WrongArgumentCount
crabgrass:~/tmp/signserv/src% ghc -O -fforce-recomp Main.hs && ./Main
Main: Main.hs:20:9-34: Irrefutable pattern failed for pattern [portStr, cert, pw]
Why is this? I am aware that imprecise exceptions can be chosen from arbitrarily; but here we are choosing from one precise and one imprecise exception, so that caveat should not apply.
I would agree with hammar, this looks like a bug. And it seems fixed in HEAD since some time. With an older ghc-7.7.20130312 as well as with today's HEAD ghc-7.7.20130521, the WrongArgumentCount exception is raised and all the other code of main is removed (bully for the optimiser). Still broken in 7.6.3, however.
The behaviour changed with the 7.2 series, I get the expected WrongArgumentCount from 7.0.4, and the (optimised) core makes that clear:
Main.main1 =
\ (s_a11H :: GHC.Prim.State# GHC.Prim.RealWorld) ->
case GHC.List.$wlen
# GHC.Base.String (GHC.Types.[] # GHC.Base.String) 0
of _ {
__DEFAULT ->
case GHC.Prim.raiseIO#
# GHC.Exception.SomeException # () Main.main7 s_a11H
of _ { (# new_s_a11K, _ #) ->
Main.main2 new_s_a11K
};
3 -> Main.main2 s_a11H
}
when the length of the empty list is different from 3, raise WrongArgumentCount, otherwise try to do the rest.
With 7.2 and later, the evaluation of the length is moved behind the parsing of portStr:
Main.main1 =
\ (eta_Xw :: GHC.Prim.State# GHC.Prim.RealWorld) ->
case Main.main7 of _ {
[] -> case Data.Maybe.fromJust1 of wild1_00 { };
: ds_dTy ds1_dTz ->
case ds_dTy of _ { (x_aOz, ds2_dTA) ->
case ds2_dTA of _ {
[] ->
case ds1_dTz of _ {
[] ->
case GHC.List.$wlen
# [GHC.Types.Char] (GHC.Types.[] # [GHC.Types.Char]) 0
of _ {
__DEFAULT ->
case GHC.Prim.raiseIO#
# GHC.Exception.SomeException # () Main.main6 eta_Xw
of wild4_00 {
};
3 ->
where
Main.main7 =
Text.ParserCombinators.ReadP.run
# GHC.Integer.Type.Integer Main.main8 Main.main3
Main.main8 =
GHC.Read.$fReadInteger5
GHC.Read.$fReadInteger_$sconvertInt
Text.ParserCombinators.ReadPrec.minPrec
# GHC.Integer.Type.Integer
(Text.ParserCombinators.ReadP.$fMonadP_$creturn
# GHC.Integer.Type.Integer)
Main.main3 = case lvl_r1YS of wild_00 { }
lvl_r1YS =
Control.Exception.Base.irrefutPatError
# ([GHC.Types.Char], [GHC.Types.Char], [GHC.Types.Char])
"Except.hs:21:9-34|[portStr, cert, pw]"
Since throwIO is supposed to respect ordering of IO actions,
The throwIO variant should be used in preference to throw to raise an exception within the IO monad because it guarantees ordering with respect to other IO operations, whereas throw does not.
that should not happen.
You can force the correct ordering by using a NOINLINE variant of when, or by performing an effectful IO action before throwing, so it seems that when the inliner sees that the when does nothing except possibly throwing, it decides that order doesn't matter.
(Sorry, not a real answer, but try to fit that in a comment ;)