├── test
├── test_helper.exs
├── fixtures
│ ├── vcr_cassettes
│ │ ├── blzservice.json
│ │ ├── europepmc.json
│ │ ├── countryinfoservice.json
│ │ ├── statsservice.json
│ │ └── countryinfoservice_complex.json
│ └── wsdls
│ │ ├── example.wsdl
│ │ ├── BLZService.wsdl
│ │ ├── StatsService.wsdl
│ │ ├── europepmc.wsdl
│ │ ├── CountryInfoService.wsdl
│ │ └── RATP.wsdl
└── castile_test.exs
├── .formatter.exs
├── .gitignore
├── lib
├── castile
│ ├── erlsom.ex
│ ├── soap.ex
│ └── wsdl.ex
└── castile.ex
├── config
└── config.exs
├── mix.exs
├── README.md
├── mix.lock
└── priv
├── soap.xsd
├── envelope.xsd
└── wsdl.xsd
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | ExUnit.start()
2 |
--------------------------------------------------------------------------------
/.formatter.exs:
--------------------------------------------------------------------------------
1 | # Used by "mix format"
2 | [
3 | inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"]
4 | ]
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Ignore package tarball (built via "mix hex.build").
23 | castile-*.tar
24 |
25 |
--------------------------------------------------------------------------------
/lib/castile/erlsom.ex:
--------------------------------------------------------------------------------
1 | defmodule Castile.Erlsom do
2 | @moduledoc """
3 | Erlsom parser records. Used for processing erlsom XSD metadata.
4 | """
5 | import Record
6 | @doc "The XSD model"
7 | defrecord :model, :model, [:types, :namespaces, :target_namespace, :type_hierarchy, :any_attribs, :value_fun]
8 | @doc "XSD type representation"
9 | defrecord :type, [:name, :tp, :els, :attrs, :anyAttr, :nillable, :nr, :nm, :mx, :mxd, :typeName]
10 | @doc "XSD element representation"
11 | defrecord :el, [:alts, :mn, :mx, :nillable, :nr]
12 | @doc "XSD alternative representation"
13 | defrecord :alt, [:tag, :type, :nxt, :mn, :mx, :rl, :anyInfo]
14 | @doc "XML attribute"
15 | defrecord :attr, :att, [:name, :nr, :opt, :tp]
16 |
17 | # erlsom internals
18 | defrecord :ns, [:uri, :prefix]
19 | defrecord :qname, [:uri, :local_part, :prefix, :mapped_prefix]
20 | end
21 |
--------------------------------------------------------------------------------
/lib/castile/soap.ex:
--------------------------------------------------------------------------------
1 | defmodule Castile.SOAP do
2 | @moduledoc """
3 | SOAP related records. Used when parsing Erlsom output.
4 | """
5 | import Record
6 | defrecord :soap_operation, :"soap:tOperation", [:attrs, :required, :action, :style]
7 | defrecord :soap_address, :"soap:tAddress", [:attrs, :required, :location]
8 | # elixir uses defrecord to interface with erlang but uses nil instead of the
9 | # erlang default: undefined...?!
10 | defrecord :soap_fault, :"soap:Fault", [attrs: :undefined, faultcode: :undefined, faultstring: :undefined, faultactor: :undefined, detail: :undefined]
11 | defrecord :soap_body, :"soap:Body", [attrs: :undefined, choice: :undefined]
12 | defrecord :soap_header, :"soap:Header", [attrs: :undefined, choice: :undefined]
13 | defrecord :soap_envelope, :"soap:Envelope", [attrs: :undefined, header: :undefined, body: :undefined, choice: :undefined]
14 | end
15 |
--------------------------------------------------------------------------------
/config/config.exs:
--------------------------------------------------------------------------------
1 | # This file is responsible for configuring your application
2 | # and its dependencies with the aid of the Mix.Config module.
3 | use Mix.Config
4 |
5 | # This configuration is loaded before any dependency and is restricted
6 | # to this project. If another project depends on this project, this
7 | # file won't be loaded nor affect the parent project. For this reason,
8 | # if you want to provide default values for your application for
9 | # 3rd-party users, it should be done in your "mix.exs" file.
10 |
11 | # You can configure your application as:
12 | #
13 | # config :castile, key: :value
14 | #
15 | # and access this configuration in your application as:
16 | #
17 | # Application.get_env(:castile, :key)
18 | #
19 | # You can also configure a 3rd-party app:
20 | #
21 | # config :logger, level: :info
22 | #
23 |
24 | # It is also possible to import configuration files, relative to this
25 | # directory. For example, you can emulate configuration per environment
26 | # by uncommenting the line below and defining dev.exs, test.exs and such.
27 | # Configuration from the imported file will override the ones defined
28 | # here (which is why it is important to import them last).
29 | #
30 | # import_config "#{Mix.env}.exs"
31 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule Castile.MixProject do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :castile,
7 | version: "1.0.1",
8 | elixir: "~> 1.6",
9 | start_permanent: Mix.env() == :prod,
10 | deps: deps(),
11 | name: "Castile",
12 | source_url: "https://github.com/polyfox/castile",
13 | description: "Castile is a modern Elixir SOAP API client.",
14 | package: [
15 | maintainers: ["Blaž Hrastnik"],
16 | licenses: ["MIT"],
17 | links: %{ "GitHub" => "https://github.com/polyfox/castile" },
18 | ],
19 | ]
20 | end
21 |
22 | # Run "mix help compile.app" to learn about applications.
23 | def application do
24 | [
25 | extra_applications: [:logger]
26 | ]
27 | end
28 |
29 | # Run "mix help deps" to learn about dependencies.
30 | defp deps do
31 | [
32 | # hex version is old and doesn't have write/3 or write related perf
33 | # improvements
34 | {:erlsom, "~> 1.4.2"},
35 | {:ex_doc, ">= 0.0.0", only: :dev},
36 | {:httpoison, "~> 0.13 or ~> 1.0"}, #, optional: true},
37 | {:credo, "~> 0.9.1", only: [:dev, :test], runtime: false},
38 | {:exvcr, "~> 0.10", only: :test}
39 | ]
40 | end
41 | end
42 |
--------------------------------------------------------------------------------
/test/fixtures/vcr_cassettes/blzservice.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "body": "70070010",
5 | "headers": {
6 | "Content-Type": "text/xml; encoding=utf-8",
7 | "SOAPAction": ""
8 | },
9 | "method": "post",
10 | "options": [],
11 | "request_body": "",
12 | "url": "http://www.thomas-bayer.com/axis2/services/BLZService"
13 | },
14 | "response": {
15 | "binary": false,
16 | "body": "Deutsche BankDEUTDEMMXXXMünchen80271",
17 | "headers": {
18 | "Server": "Apache-Coyote/1.1",
19 | "Content-Type": "text/xml;charset=UTF-8",
20 | "Date": "Wed, 25 Apr 2018 06:20:45 GMT",
21 | "Content-Length": "402"
22 | },
23 | "status_code": 200,
24 | "type": "ok"
25 | }
26 | }
27 | ]
--------------------------------------------------------------------------------
/test/fixtures/vcr_cassettes/europepmc.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "body": "7nonexistent",
5 | "headers": {
6 | "Content-Type": "text/xml; encoding=utf-8",
7 | "SOAPAction": ""
8 | },
9 | "method": "post",
10 | "options": [],
11 | "request_body": "",
12 | "url": "https://www.ebi.ac.uk:443/europepmc/webservices/soap"
13 | },
14 | "response": {
15 | "binary": false,
16 | "body": "S:ClientCannot find dispatch method for {https://www.europepmc.org/data}searchPublications",
17 | "headers": {
18 | "Server": "Apache-Coyote/1.1",
19 | "Content-Type": "text/xml;charset=utf-8",
20 | "Strict-Transport-Security": "max-age=0",
21 | "Date": "Fri, 27 Apr 2018 08:38:45 GMT",
22 | "Transfer-Encoding": "chunked",
23 | "Connection": "close"
24 | },
25 | "status_code": 500,
26 | "type": "ok"
27 | }
28 | }
29 | ]
--------------------------------------------------------------------------------
/test/fixtures/vcr_cassettes/countryinfoservice.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "body": "Netherlands",
5 | "headers": {
6 | "Content-Type": "text/xml; encoding=utf-8",
7 | "SOAPAction": ""
8 | },
9 | "method": "post",
10 | "options": [],
11 | "request_body": "",
12 | "url": "http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso"
13 | },
14 | "response": {
15 | "binary": false,
16 | "body": "\r\n\r\n \r\n \r\n NL\r\n \r\n \r\n",
17 | "headers": {
18 | "Cache-Control": "private, max-age=0",
19 | "Content-Type": "text/xml; charset=utf-8",
20 | "Server": "Microsoft-IIS/8.5",
21 | "Web-Service": "DataFlex 18.2",
22 | "Date": "Wed, 25 Apr 2018 06:20:09 GMT",
23 | "Content-Length": "341"
24 | },
25 | "status_code": 200,
26 | "type": "ok"
27 | }
28 | }
29 | ]
--------------------------------------------------------------------------------
/test/fixtures/vcr_cassettes/statsservice.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "body": "yesMinutes3028800000CurrentWeek-25200000",
5 | "headers": {
6 | "Content-Type": "text/xml; encoding=utf-8",
7 | "SOAPAction": ""
8 | },
9 | "method": "post",
10 | "options": [],
11 | "request_body": "",
12 | "url": "https://api.toptiersolutions.com:443/wssupervisor/v12/SupervisorWebService"
13 | },
14 | "response": {
15 | "binary": false,
16 | "body": "",
17 | "headers": {
18 | "Server": "Apache-Coyote/1.1",
19 | "Content-Type": "text/xml;charset=UTF-8",
20 | "Date": "Wed, 25 Apr 2018 06:20:45 GMT"
21 | },
22 | "status_code": 200,
23 | "type": "ok"
24 | }
25 | }
26 | ]
--------------------------------------------------------------------------------
/lib/castile/wsdl.ex:
--------------------------------------------------------------------------------
1 | defmodule Castile.WSDL do
2 | @moduledoc """
3 | WSDL related records. Used when parsing Erlsom output.
4 | """
5 | import Record
6 | defrecord :wsdl_definitions, :"wsdl:tDefinitions", [:attrs, :namespace, :name, :docs, :any, :imports, :types, :messages, :port_types, :bindings, :services]
7 | defrecord :wsdl_service, :"wsdl:tService", [:attrs, :name, :docs, :choice, :ports]
8 | defrecord :wsdl_port, :"wsdl:tPort", [:attrs, :name, :binding, :docs, :choice]
9 | defrecord :wsdl_binding, :"wsdl:tBinding", [:attrs, :name, :type, :docs, :choice, :ops]
10 | defrecord :wsdl_binding_operation, :"wsdl:tBindingOperation", [:attrs, :name, :docs, :choice, :input, :output, :fault]
11 | defrecord :wsdl_import, :"wsdl:tImport", [:attrs, :namespace, :location, :docs]
12 | defrecord :wsdl_port_type, :"wsdl:tPortType", [:attrs, :name, :docs, :operations]
13 | defrecord :wsdl_part, :"wsdl:tPart", [:attrs, :name, :element, :type, :docs]
14 | defrecord :wsdl_message, :"wsdl:tMessage", [:attrs, :name, :docs, :choice, :part]
15 | defrecord :wsdl_operation, :"wsdl:tOperation", [:attrs, :name, :parameterOrder, :docs, :any, :choice]
16 | defrecord :wsdl_request_response, :"wsdl:request-response-or-one-way-operation", [:attrs, :input, :output, :fault]
17 | defrecord :wsdl_param, :"wsdl:tParam", [:attrs, :name, :message, :docs]
18 | end
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Castile
2 |
3 | Castile is a modern Elixir SOAP API client.
4 | It borrows ideas heavily from [Detergent](https://github.com/devinus/detergent)/yaws.
5 |
6 | Why write another SOAP client? We really wanted to use Detergent/Detergentex,
7 | but ran into some issues (and also wanted to use HTTPoison as the HTTP client).
8 |
9 | Detergent itself hasn't really been updated since 2013 (a time when erlang
10 | itself had no maps even), and Detergentex being just a lightweight wrapper
11 | around it, has the same shortcomings (even worse, the detergent version on
12 | hex.pm that detergentex relies on has no support for HTTPS). Erlsom has also
13 | been updated in the mean time, improving XML write speed and adding support for
14 | binaries natively.
15 |
16 | [Castile](https://en.wikipedia.org/wiki/Castile_soap) is an attempt at a fresh
17 | start, with a small API that's easy to use in Elixir.
18 |
19 | At the moment, the supported subset is SOAP 1.1 with WSDL 1.1 (and
20 | document/literal format). SOAP 1.2 and WSDL 2.0 are on the roadmap, but none of
21 | the APIs we interact with use either of those, so I'm having problems finding
22 | a public API to test against (if you find one, please open an issue!).
23 |
24 | ## Installation
25 |
26 | Install from Hex.pm:
27 |
28 | ```elixir
29 | def deps do
30 | [
31 | {:castile, "~> 1.0"}
32 | ]
33 | end
34 | ```
35 |
36 | Docs can be found at [https://hexdocs.pm/castile](https://hexdocs.pm/castile).
37 |
38 | ## Usage
39 |
40 | ```elixir
41 | # It's recommended to do init_model at compile time, as an @attr
42 | @model = Castile.init_model("CountryInfoService.wsdl")
43 |
44 | # we take maps as input and return binaries
45 | {:ok, resp} = Castile.call(@model, :CountryISOCode, %{sCountryName: "Netherlands"})
46 | # "NL"
47 |
48 | # More complex results get cast into maps:
49 | model = Castile.init_model("BLZService.wsdl")
50 | {:ok, resp} = Castile.call(model, :getBank, %{blz: "70070010"})
51 | # resp => %{
52 | # bezeichnung: "Deutsche Bank",
53 | # bic: "DEUTDEMMXXX",
54 | # ort: "München",
55 | # plz: "80271"
56 | # }
57 | ```
58 |
59 | # TODO
60 |
61 | - [X] Faults (1.1)
62 | - [ ] HTTP client as adapter (specify module)
63 | - [ ] SOAP 1.2 support
64 | - [ ] RPC/encoding RPC/literal style (multiple bodies)
65 | - [ ] WSDL 2.0 support
66 | - [ ] Attachments/multipart
67 |
68 | # Thanks
69 |
70 | - [detergent](https://github.com/devinus/detergent)/yaws for the original SOAP
71 | implementation.
72 | - [bet365/soap](https://github.com/bet365/soap) for some of the ideas/fixes
73 | (simplifying the WSDL XSDs, already_imported schema patch)
74 |
--------------------------------------------------------------------------------
/test/castile_test.exs:
--------------------------------------------------------------------------------
1 | defmodule CastileTest do
2 | use ExUnit.Case
3 | use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
4 | #doctest Castile
5 |
6 | setup_all do
7 | HTTPoison.start
8 | end
9 |
10 | setup do
11 | path = Path.expand("fixtures/vcr_cassettes", __DIR__)
12 | ExVCR.Config.cassette_library_dir(path)
13 | :ok
14 | end
15 |
16 | test "init_model" do
17 | path = Path.expand("fixtures/wsdls/example.wsdl", __DIR__)
18 | model = Castile.init_model(path)
19 |
20 | assert Map.has_key?(model.operations, "store")
21 | assert Map.has_key?(model.operations, "retrieve")
22 | {:ok, xml} = Castile.convert(model, :contact, %{
23 | id: 10,
24 | first_name: "John",
25 | last_name: "Doe",
26 | projects: ["First project", "Second project"]
27 | })
28 | assert xml == ~s(10JohnDoeFirst projectSecond project)
29 | end
30 |
31 | describe "document/literal" do
32 | test "CountryInfoService" do
33 | use_cassette "CountryInfoService" do
34 | path = Path.expand("fixtures/wsdls/CountryInfoService.wsdl", __DIR__)
35 | model = Castile.init_model(path)
36 | {:ok, resp} = Castile.call(model, :CountryISOCode, %{sCountryName: "Netherlands"})
37 | assert resp == "NL"
38 | end
39 | end
40 |
41 | test "CountryInfoService_complex" do
42 | use_cassette "CountryInfoService_complex" do
43 | path = Path.expand("fixtures/wsdls/CountryInfoService.wsdl", __DIR__)
44 | model = Castile.init_model(path)
45 | {:ok, resp} = Castile.call(model, :ListOfCountryNamesGroupedByContinent)
46 | resp = List.first(resp[:tCountryCodeAndNameGroupedByContinent])
47 | assert get_in(resp, [:Continent, :sCode]) == "AF"
48 | end
49 | end
50 |
51 | test "BLZService" do
52 | use_cassette "BLZService" do
53 | path = Path.expand("fixtures/wsdls/BLZService.wsdl", __DIR__)
54 | model = Castile.init_model(path)
55 | {:ok, resp} = Castile.call(model, :getBank, %{blz: "70070010"})
56 | assert resp == %{
57 | bezeichnung: "Deutsche Bank",
58 | bic: "DEUTDEMMXXX",
59 | ort: "München",
60 | plz: "80271"
61 | }
62 | end
63 | end
64 |
65 | test "StatsService" do
66 | use_cassette "StatsService" do
67 | params = %{
68 | viewSettings: %{
69 | rollingPeriod: "Minutes30",
70 | shiftStart: 28_800_000,
71 | statisticsRange: "CurrentWeek",
72 | timeZone: -25_200_000
73 | }
74 | }
75 |
76 | path = Path.expand("fixtures/wsdls/StatsService.wsdl", __DIR__)
77 | model = Castile.init_model(path)
78 | assert {:ok, %{}} = Castile.call(model, :setSessionParameters, params)
79 | end
80 | end
81 |
82 | test "RATP" do
83 | end
84 |
85 | test "faults" do
86 | use_cassette "europepmc" do
87 | path = Path.expand("fixtures/wsdls/europepmc.wsdl", __DIR__)
88 | model = Castile.init_model(path)
89 | {:error, fault} = Castile.call(model, :searchPublications, %{
90 | queryString: "7",
91 | resultType: "nonexistent"
92 | })
93 |
94 | assert fault == %Castile.Fault{detail: nil, faultactor: nil, faultcode: "http://schemas.xmlsoap.org/soap/envelope/", faultstring: "Cannot find dispatch method for {https://www.europepmc.org/data}searchPublications"}
95 | end
96 | end
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"},
3 | "certifi": {:hex, :certifi, "2.3.1", "d0f424232390bf47d82da8478022301c561cf6445b5b5fb6a84d49a9e76d2639", [:rebar3], [{:parse_trans, "3.2.0", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
4 | "credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"},
5 | "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"},
6 | "erlsom": {:hex, :erlsom, "1.4.2", "5cddb82fb512f406f61162e511ae86582f824f0dccda788378b18a00d89c1b3f", [:rebar3], [], "hexpm"},
7 | "ex_doc": {:hex, :ex_doc, "0.18.4", "4406b8891cecf1352f49975c6d554e62e4341ceb41b9338949077b0d4a97b949", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"},
8 | "exactor": {:hex, :exactor, "2.2.4", "5efb4ddeb2c48d9a1d7c9b465a6fffdd82300eb9618ece5d34c3334d5d7245b1", [:mix], [], "hexpm"},
9 | "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, repo: "hexpm", optional: false]}], "hexpm"},
10 | "exvcr": {:hex, :exvcr, "0.10.2", "a66a0fa86d03153e5c21e38b1320d10b537038d7bc7b10dcc1ab7f0343569822", [:mix], [{:exactor, "~> 2.2", [hex: :exactor, repo: "hexpm", optional: false]}, {:exjsx, "~> 4.0", [hex: :exjsx, repo: "hexpm", optional: false]}, {:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: true]}, {:httpotion, "~> 3.1", [hex: :httpotion, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:meck, "~> 0.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"},
11 | "hackney": {:hex, :hackney, "1.13.0", "24edc8cd2b28e1c652593833862435c80661834f6c9344e84b6a2255e7aeef03", [:rebar3], [{:certifi, "2.3.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.2", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
12 | "httpoison": {:hex, :httpoison, "1.1.1", "96ed7ab79f78a31081bb523eefec205fd2900a02cda6dbc2300e7a1226219566", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
13 | "idna": {:hex, :idna, "5.1.2", "e21cb58a09f0228a9e0b95eaa1217f1bcfc31a1aaa6e1fdf2f53a33f7dbd9494", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
14 | "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"},
15 | "meck": {:hex, :meck, "0.8.11", "2c39e15ec87d847da6cf69b4a1c4af3fd850ae2a272e719e0e8751a7fe54771f", [:rebar3], [], "hexpm"},
16 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"},
17 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"},
18 | "parse_trans": {:hex, :parse_trans, "3.2.0", "2adfa4daf80c14dc36f522cf190eb5c4ee3e28008fc6394397c16f62a26258c2", [:rebar3], [], "hexpm"},
19 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
20 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"},
21 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], [], "hexpm"},
22 | "xml_builder": {:hex, :xml_builder, "2.1.0", "c249d5339427c13cae11e9d9d0e8b40d25d228b9ecc54029f24017385e60280b", [:mix], [], "hexpm"},
23 | }
24 |
--------------------------------------------------------------------------------
/test/fixtures/wsdls/example.wsdl:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/test/fixtures/wsdls/BLZService.wsdl:
--------------------------------------------------------------------------------
1 |
2 | BLZService
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/test/fixtures/wsdls/StatsService.wsdl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | Just another SOAP WSDL to test an empty response element. Cool, huh?
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/priv/soap.xsd:
--------------------------------------------------------------------------------
1 |
2 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | "encodingStyle" indicates any canonicalization conventions followed in the contents of the containing element. For example, the value "http://schemas.xmlsoap.org/soap/encoding/" indicates the pattern described in SOAP specification
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/priv/envelope.xsd:
--------------------------------------------------------------------------------
1 |
2 |
3 |
35 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Prose in the spec does not specify that attributes are allowed on the Body element
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | 'encodingStyle' indicates any canonicalization conventions followed in the contents of the containing element. For example, the value 'http://schemas.xmlsoap.org/soap/encoding/' indicates the pattern described in SOAP specification
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | Fault reporting structure
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/priv/wsdl.xsd:
--------------------------------------------------------------------------------
1 |
2 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | This type is extended by component types to allow them to be documented
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | This type is extended by component types to allow attributes from other namespaces to be added.
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | This type is extended by component types to allow elements from other namespaces to be added.
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
--------------------------------------------------------------------------------
/test/fixtures/wsdls/europepmc.wsdl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
--------------------------------------------------------------------------------
/lib/castile.ex:
--------------------------------------------------------------------------------
1 | defmodule Castile do
2 | @moduledoc """
3 | Documentation for Castile.
4 | """
5 | import Castile.{Erlsom, WSDL, SOAP}
6 |
7 | defmodule Model do
8 | @doc """
9 | Represents the WSDL model, containing the type XSD schema and all
10 | other WSDL metadata.
11 | """
12 | defstruct [:operations, :model]
13 | @type t :: %__MODULE__{operations: map, model: term}
14 | end
15 |
16 | defmodule Fault do
17 | @moduledoc """
18 | Represents a SOAP (1.1) fault.
19 | """
20 | defexception [:detail, :faultactor, :faultcode, :faultstring]
21 |
22 | @type t :: %__MODULE__{
23 | faultcode: String.t,
24 | faultstring: String.t,
25 | faultactor: String.t,
26 | detail: term
27 | }
28 |
29 | def message(exception) do
30 | exception.faultstring
31 | end
32 | end
33 |
34 | # TODO: erlsom value_fun might save us the trouble of transforms on parse
35 |
36 | @doc """
37 | Initializes a service model from a WSDL file. This parses the WSDL and it's
38 | imports recursively, building an internal model of all the XSD types and
39 | SOAP metadata for later use.
40 |
41 | Similar to Elixir's pre-compiled Regexes, it's recommended to do this at
42 | compile-time in an @attribute.
43 |
44 | ## Examples
45 |
46 | iex> model = Castile.init_model("CountryInfoService.wsdl")
47 | %Castile.Model{...}
48 | """
49 | # TODO: take namespaces as binary
50 | @spec init_model(Path.t, namespaces :: list) :: Model.t
51 | def init_model(wsdl_file, namespaces \\ []) do
52 | priv_dir = Application.app_dir(:castile, "priv")
53 | wsdl = Path.join([priv_dir, "wsdl.xsd"])
54 | {:ok, wsdl_model} = :erlsom.compile_xsd_file(
55 | Path.join([priv_dir, "soap.xsd"]),
56 | prefix: 'soap',
57 | include_files: [{'http://schemas.xmlsoap.org/wsdl/', 'wsdl', wsdl}],
58 | strict: true
59 | )
60 | # add the xsd model
61 | wsdl_model = :erlsom.add_xsd_model(wsdl_model)
62 |
63 | include_dir = Path.dirname(wsdl_file)
64 | options = [include_dirs: [include_dir]]
65 |
66 | # parse wsdl
67 | {model, wsdls} = parse_wsdls([wsdl_file], namespaces, wsdl_model, options, {nil, []})
68 |
69 | # now compile envelope.xsd, and add Model
70 | {:ok, envelope_model} = :erlsom.compile_xsd_file(Path.join([priv_dir, "envelope.xsd"]), prefix: 'soap', strict: true)
71 | soap_model = :erlsom.add_model(envelope_model, model)
72 | # TODO: detergent enables you to pass some sort of AddFiles that will stitch together the soap model
73 | # SoapModel2 = addModels(AddFiles, SoapModel),
74 |
75 | # finally, process all wsdls at once (this solves cases where the wsdl
76 | # definition is split in to two files, one with types+port types, plus rest)
77 | ports = get_ports(wsdls)
78 | operations = get_operations(wsdls, ports, model)
79 |
80 | %Model{operations: operations, model: soap_model}
81 | end
82 |
83 | defp parse_wsdls([], _namespaces, _wsdl_model, _opts, acc), do: acc
84 |
85 | defp parse_wsdls([path | rest], namespaces, wsdl_model, opts, {acc_model, acc_wsdl}) do
86 | {:ok, wsdl_file} = get_file(String.trim(path), opts)
87 | {:ok, parsed, _} = :erlsom.scan(wsdl_file, wsdl_model)
88 | # get xsd elements from wsdl to compile
89 | xsds = extract_wsdl_xsds(parsed)
90 | # Now we need to build a list: [{Namespace, Prefix, Xsd}, ...] for all the Xsds in the WSDL.
91 | # This list is used when a schema includes one of the other schemas. The AXIS java2wsdl
92 | # generates wsdls that depend on this feature.
93 | import_list = Enum.reduce(xsds, [], fn xsd, acc ->
94 | uri = :erlsom_lib.getTargetNamespaceFromXsd(xsd)
95 | case uri do
96 | :undefined -> acc
97 | _ ->
98 | prefix = :proplists.get_value(uri, namespaces, :undefined)
99 | [{uri, prefix, xsd} | acc]
100 | end
101 | end)
102 |
103 | model = add_schemas(xsds, opts, import_list, acc_model)
104 |
105 | acc = {model, [parsed | acc_wsdl]}
106 | imports = get_imports(parsed)
107 | # process imports (recursively, so that imports in the imported files are
108 | # processed as well).
109 | # For the moment, the namespace is ignored on operations etc.
110 | # this makes it a bit easier to deal with imported wsdl's.
111 | acc = parse_wsdls(imports, namespaces, wsdl_model, opts, acc)
112 | parse_wsdls(rest, namespaces, wsdl_model, opts, acc)
113 | end
114 |
115 | # compile each of the schemas, and add it to the model.
116 | defp add_schemas(xsds, opts, imports, acc_model \\ nil) do
117 | {model, _} = Enum.reduce(Enum.reject(xsds, &is_nil/1), {acc_model, []}, fn xsd, {acc, imported} ->
118 | tns = :erlsom_lib.getTargetNamespaceFromXsd(xsd)
119 | prefix = case List.keyfind(imports, tns, 0) do
120 | {_, p, _} -> p
121 | _ -> ''
122 | end
123 | opts = [
124 | {:prefix, prefix},
125 | {:include_files, imports},
126 | {:already_imported, imported},
127 | {:strict, true}
128 | | opts
129 | ]
130 | {:ok, model} = :erlsom_compile.compile_parsed_xsd(xsd, opts)
131 |
132 | model = case acc_model do
133 | nil -> model
134 | _ -> :erlsom.add_model(acc, model)
135 | end
136 | {model, [{tns, prefix} | imported]}
137 | end)
138 | model
139 | end
140 |
141 | defp get_file(uri, opts \\ []) do
142 | case URI.parse(uri) do
143 | %{scheme: scheme} when scheme in ["http", "https"] ->
144 | raise "Not implemented"
145 | # get_remote_file()
146 | _ ->
147 | if File.exists?(uri) do
148 | File.read(uri)
149 | else
150 | include = Keyword.get(opts, :include_dirs)
151 | find_file(uri, include)
152 | end
153 | end
154 | end
155 |
156 | defp find_file(_name, []), do: {:error, :enoent}
157 | defp find_file(name, [include | rest]) do
158 | path = Path.join([include, name])
159 | if File.exists?(path) do
160 | File.read(path)
161 | else
162 | find_file(name, rest)
163 | end
164 | end
165 |
166 | defp extract_wsdl_xsds(wsdl_definitions(types: types)) when is_list(types) do
167 | types
168 | |> Enum.map(fn {:"wsdl:tTypes", _attrs, _docs, types} -> types end)
169 | |> List.flatten()
170 | end
171 | defp extract_wsdl_xsds(wsdl_definitions()), do: []
172 |
173 | defp get_ports(wsdls) do
174 | Enum.reduce(wsdls, [], fn
175 | (wsdl_definitions(services: services), acc) when is_list(services) ->
176 | Enum.reduce(services, acc, fn service, acc ->
177 | wsdl_service(name: service_name, ports: ports) = service
178 | # TODO: ensure ports not :undefined
179 | Enum.reduce(ports, acc, fn
180 | wsdl_port(name: name, binding: binding, choice: choice), acc when is_list(choice) ->
181 | Enum.reduce(choice, acc, fn
182 | soap_address(location: location), acc ->
183 | [%{service: to_string(service_name), port: to_string(name), binding: binding, address: to_string(location)} | acc]
184 | _, acc -> acc # non-soap bindings are ignored
185 | end)
186 | _, acc -> acc
187 | end)
188 | end)
189 | _, acc -> acc
190 | end)
191 | end
192 |
193 | # TODO: having to say pos outside of the func is nasty but meh.
194 | defp get_node(wsdls, qname, type_pos, pos) do
195 | uri = :erlsom_lib.getUriFromQname(qname)
196 | local = :erlsom_lib.localName(qname)
197 |
198 | wsdls
199 | |> get_namespace(uri)
200 | |> elem(type_pos)
201 | |> List.keyfind(local, pos)
202 | end
203 |
204 | # get service -> port --> binding --> portType -> operation -> response-or-one-way -> param -|-|-> message
205 | # |-> bindingOperation --> message
206 | defp get_operations(wsdls, ports, model) do
207 | Enum.reduce(ports, %{}, fn (%{binding: binding} = port, acc) ->
208 | bind = get_node(wsdls, binding, wsdl_definitions(:bindings), wsdl_binding(:name))
209 | wsdl_binding(ops: ops, type: pt) = bind
210 |
211 | Enum.reduce(ops, acc, fn (wsdl_binding_operation(name: name, choice: choice), acc) ->
212 | case choice do
213 | [soap_operation(action: action)] ->
214 | # lookup Binding in PortType, and create a combined result
215 | port_type = get_node(wsdls, pt, wsdl_definitions(:port_types), wsdl_port_type(:name))
216 | operations = wsdl_port_type(port_type, :operations)
217 |
218 | operation = List.keyfind(operations, name, wsdl_operation(:name))
219 | params = wsdl_operation(operation, :choice)
220 | wsdl_request_response(input: input, output: output, fault: _fault) = params
221 |
222 | Map.put_new(acc, to_string(name), %{
223 | service: port.service,
224 | port: port.port,
225 | binding: binding,
226 | address: port.address,
227 | action: to_string(action),
228 | input: extract_type(wsdls, model, input),
229 | output: extract_type(wsdls, model, output),
230 | #fault: extract_type(wsdls, model, fault) TODO
231 | })
232 | _ -> acc
233 | end
234 | end)
235 | end)
236 | end
237 |
238 | defp get_namespace(wsdls, uri) when is_list(wsdls) do
239 | List.keyfind(wsdls, uri, wsdl_definitions(:namespace))
240 | end
241 |
242 | defp get_type(types, name) when is_list(types) do
243 | List.keyfind(types, name, type(:name))
244 | end
245 |
246 | defp get_imports(wsdl_definitions(imports: :undefined)), do: []
247 | defp get_imports(wsdl_definitions(imports: imports)) do
248 | Enum.map(imports, fn wsdl_import(location: location) -> to_string(location) end)
249 | end
250 |
251 | defp extract_type(wsdls, model, wsdl_param(message: message)) do
252 | parts =
253 | wsdls
254 | |> get_node(message, wsdl_definitions(:messages), wsdl_message(:name))
255 | |> wsdl_message(:part)
256 | extract_type(wsdls, model, parts)
257 | end
258 | defp extract_type(_wsdls, _model, [wsdl_part(element: :undefined)]) do
259 | raise "Unhandled"
260 | end
261 | defp extract_type(_wsdls, model, [wsdl_part(element: el)]) do
262 | local = :erlsom_lib.localName(el)
263 | uri = :erlsom_lib.getUriFromQname(el)
264 | prefix = :erlsom_lib.getPrefixFromModel(model, uri)
265 | case prefix do
266 | :undefined -> local
267 | nil -> local
268 | "" -> local
269 | _ -> prefix ++ ':' ++ local
270 | end
271 | |> List.to_atom()
272 | end
273 | defp extract_type(_, _, nil), do: nil
274 | defp extract_type(_, _, :undefined), do: nil
275 |
276 | @doc """
277 | Converts an operation's parameters into XML.
278 |
279 | ## Examples
280 |
281 | iex> Castile.call(model, :CountryISOCode, %{sCountryName: "Netherlands"})
282 | {:ok, "Netherlands"}
283 | """
284 | @spec convert(Model.t, operation :: atom, params :: map) :: {:ok, binary} | {:error, term}
285 | def convert(%Model{model: model()} = model, nil, _params) do
286 | []
287 | |> wrap_envelope()
288 | |> :erlsom.write(model.model, output: :binary)
289 | end
290 | def convert(%Model{model: model(types: types)} = model, type, params) do
291 | type
292 | |> cast_type(params, types)
293 | |> List.wrap()
294 | |> wrap_envelope()
295 | |> :erlsom.write(model.model, output: :binary)
296 | end
297 |
298 | defp resolve_element(nil, _types), do: nil
299 | defp resolve_element(name, types) do
300 | type(els: [el(alts: alts)]) = get_type(types, :_document)
301 |
302 | alts
303 | |> List.keyfind(name, alt(:tag))
304 | |> alt(:type)
305 | end
306 |
307 | @spec wrap_envelope(messages :: list, headers :: list) :: term
308 | defp wrap_envelope(messages, headers \\ [])
309 |
310 | defp wrap_envelope(messages, []) when is_list(messages) do
311 | soap_envelope(body: soap_body(choice: messages))
312 | end
313 |
314 | defp wrap_envelope(messages, headers) when is_list(messages) and is_list(headers) do
315 | soap_envelope(body: soap_body(choice: messages), header: soap_header(choice: headers))
316 | end
317 |
318 | @spec cast_type(name :: atom, input :: map, types :: term) :: tuple
319 | def cast_type(name, input, types) do
320 | type(els: els) = get_type(types, name)
321 |
322 | # TODO: check type(spec, :tp) and handle other things than :sequence
323 | vals = Enum.map(els, &convert_el(&1, input, types))
324 | List.to_tuple([name, [] | vals])
325 | end
326 |
327 | defp convert_el(el(alts: [alt(tag: tag, type: t, mn: 1, mx: 1)], mn: min, mx: max, nillable: nillable, nr: _nr), value, types) do
328 | conv = fn
329 | nil ->
330 | cond do
331 | min == 0 -> :undefined
332 | nillable == true -> nil
333 | true -> raise "Non-nillable type #{tag} found nil"
334 | end
335 | val ->
336 | case t do
337 | {:"#PCDATA", _} ->
338 | val # erlsom will cast these
339 | t when is_atom(t) ->
340 | cast_type(t, val, types)
341 | end
342 | end
343 |
344 | case value do
345 | v when is_list(v) -> Enum.map(v, fn v -> conv.(Map.get(v, tag)) end)
346 | v when is_map(v) -> conv.(Map.get(v, tag))
347 | end
348 | end
349 |
350 | @doc """
351 | Calls a SOAP service operation.
352 |
353 | ## Examples
354 |
355 | iex> Castile.call(model, :CountryISOCode, %{sCountryName: "Netherlands"})
356 | {:ok, "NL"}
357 | """
358 | @spec call(wsdl :: Model.t, operation :: atom, params :: map, headers :: list | map, opts :: list) :: {:ok, term} | {:error, %Fault{}} | {:error, term}
359 | def call(%Model{model: model(types: types)} = model, operation, params \\ %{}, headers \\ [], opts \\ []) do
360 | op = model.operations[to_string(operation)]
361 | input = resolve_element(op.input, types)
362 | {:ok, params} = convert(model, input, params)
363 |
364 | headers = [
365 | {"Content-Type", "text/xml; encoding=utf-8"},
366 | {"SOAPAction", op.action},
367 | {"User-Agent", "Castile/0.5.0"} | headers]
368 |
369 | case HTTPoison.post(op.address, params, headers, opts) do
370 | {:ok, %{status_code: 200, body: body}} ->
371 | # TODO: check content type for multipart
372 | # TODO: handle response headers
373 | {:ok, resp, _} = :erlsom.scan(body, model.model, output_encoding: :utf8)
374 |
375 | output = resolve_element(op.output, types)
376 | case resp do
377 | soap_envelope(body: soap_body(choice: [{^output, _, inner_body}])) ->
378 | # parse body further into a map
379 | {:ok, transform(inner_body, types)}
380 | soap_envelope(body: soap_body(choice: [{^output, _}])) ->
381 | # Response body is empty
382 | # skip parsing and return an empty map.
383 | {:ok, %{}}
384 | end
385 | {:ok, %{status_code: 500, body: body}} ->
386 | {:ok, resp, _} = :erlsom.scan(body, model.model, output_encoding: :utf8)
387 | soap_envelope(body: soap_body(choice: [soap_fault() = fault])) = resp
388 | {:error, transform(fault, types)}
389 | end
390 | end
391 |
392 | @doc """
393 | Same as call/4, but raises an exception instead.
394 | """
395 | @spec call!(wsdl :: Model.t, operation :: atom, params :: map, headers :: list | map) :: {:ok, term} | no_return()
396 | def call!(%Model{} = model, operation, params \\ %{}, headers \\ []) do
397 | case call(model, operation, params, headers) do
398 | {:ok, result} -> result
399 | {:error, fault} -> raise fault
400 | end
401 | end
402 |
403 | defp transform(soap_fault() = fault, types) do
404 | params = Enum.into(soap_fault(fault), %{}, fn
405 | {k, qname() = qname} -> {k, to_string(:erlsom_lib.getUriFromQname(qname))}
406 | {k, :undefined} -> {k, nil}
407 | {k, v} -> {k, transform(v, types)}
408 | end)
409 | struct(Fault, params)
410 | end
411 |
412 | defp transform(val, types) when is_tuple(val) do
413 | type(els: els) = get_type(types, elem(val, 0))
414 |
415 | # TODO if max unbounded, then instead of skipping it, use []
416 | Enum.reduce(els, %{}, fn el(alts: [alt(tag: tag, type: t, mn: 1, mx: 1)], mn: min, mx: max, nillable: nillable, nr: pos), acc ->
417 | val = elem(val, pos - 1)
418 | val = case t do
419 | {:"#PRCDATA", _} -> val
420 | :any -> val # TODO: improve the layout, %{key: %{:"#any" => %{ data }} is a bit redundant if there's only any
421 | _ ->
422 | # HAXX: improve
423 | if nillable && val == [] do
424 | nil
425 | else
426 | transform(val, types)
427 | end
428 | end
429 | Map.put(acc, tag, val)
430 | end)
431 | end
432 | defp transform(val, types) when is_list(val), do: Enum.map(val, &transform(&1, types))
433 | defp transform(:undefined, _types), do: nil
434 | defp transform(val, _types), do: val
435 | end
436 |
--------------------------------------------------------------------------------
/test/fixtures/wsdls/CountryInfoService.wsdl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 | Returns a list of continents ordered by name.
482 |
483 |
484 |
485 |
486 | Returns a list of continents ordered by code.
487 |
488 |
489 |
490 |
491 | Returns a list of currencies ordered by name.
492 |
493 |
494 |
495 |
496 | Returns a list of currencies ordered by code.
497 |
498 |
499 |
500 |
501 | Returns the name of the currency (if found)
502 |
503 |
504 |
505 |
506 | Returns a list of all stored counties ordered by ISO code
507 |
508 |
509 |
510 |
511 | Returns a list of all stored counties ordered by country name
512 |
513 |
514 |
515 |
516 | Returns a list of all stored counties grouped per continent
517 |
518 |
519 |
520 |
521 | Searches the database for a country by the passed ISO country code
522 |
523 |
524 |
525 |
526 | This function tries to found a country based on the passed country name.
527 |
528 |
529 |
530 |
531 | Returns the name of the captial city for the passed country code
532 |
533 |
534 |
535 |
536 | Returns the currency ISO code and name for the passed country ISO code
537 |
538 |
539 |
540 |
541 | Returns a link to a picture of the country flag
542 |
543 |
544 |
545 |
546 | Returns the internation phone code for the passed ISO country code
547 |
548 |
549 |
550 |
551 | Returns a struct with all the stored country information. Pass the ISO country code
552 |
553 |
554 |
555 |
556 | Returns an array with all countries and all the language information stored
557 |
558 |
559 |
560 |
561 | Returns a list of all countries that use the same currency code. Pass a ISO currency code
562 |
563 |
564 |
565 |
566 | Returns an array of languages ordered by name
567 |
568 |
569 |
570 |
571 | Returns an array of languages ordered by code
572 |
573 |
574 |
575 |
576 | Find a language name based on the passed ISO language code
577 |
578 |
579 |
580 |
581 | Find a language ISO code based on the passed language name
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
851 |
852 |
853 |
854 |
855 |
856 |
857 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
878 |
879 |
880 |
881 |
882 |
883 |
884 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
905 |
906 |
907 |
908 |
909 |
910 |
911 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
923 |
924 |
925 |
926 |
927 |
928 |
929 |
932 |
933 |
934 |
935 |
936 |
937 |
938 |
941 |
942 |
943 |
944 |
945 |
946 |
947 |
950 |
951 |
952 |
953 |
954 |
955 |
956 |
959 |
960 |
961 |
962 |
963 |
964 |
965 |
968 |
969 |
970 |
971 | This DataFlex Web Service opens up country information. 2 letter ISO codes are used for Country code. There are functions to retrieve the used Currency, Language, Capital City, Continent and Telephone code.
972 |
973 |
974 |
975 |
976 |
977 |
978 |
979 |
980 |
--------------------------------------------------------------------------------
/test/fixtures/vcr_cassettes/countryinfoservice_complex.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "request": {
4 | "body": "",
5 | "headers": {
6 | "Content-Type": "text/xml; encoding=utf-8",
7 | "SOAPAction": ""
8 | },
9 | "method": "post",
10 | "options": [],
11 | "request_body": "",
12 | "url": "http://webservices.oorsprong.org/websamples.countryinfo/CountryInfoService.wso"
13 | },
14 | "response": {
15 | "binary": false,
16 | "body": "\r\n\r\n \r\n \r\n \r\n \r\n \r\n AF\r\n Africa \r\n \r\n \r\n \r\n DZ\r\n Algeria\r\n \r\n \r\n AO\r\n Angola\r\n \r\n \r\n BJ\r\n Benin\r\n \r\n \r\n BW\r\n Botswana\r\n \r\n \r\n BF\r\n Burkina Faso\r\n \r\n \r\n BI\r\n Burundi\r\n \r\n \r\n CI\r\n Côte D'Ivoire (Ivory Coast)\r\n \r\n \r\n CM\r\n Cameroon\r\n \r\n \r\n CV\r\n Cape Verde\r\n \r\n \r\n CF\r\n Central African Republic\r\n \r\n \r\n TD\r\n Chad\r\n \r\n \r\n KM\r\n Comoros\r\n \r\n \r\n CG\r\n Congo\r\n \r\n \r\n CD\r\n Congo, Democratic Republic\r\n \r\n \r\n DJ\r\n Djibouti\r\n \r\n \r\n EG\r\n Egypt\r\n \r\n \r\n GQ\r\n Equatorial Guinea\r\n \r\n \r\n ER\r\n Eritrea\r\n \r\n \r\n ET\r\n Ethiopia\r\n \r\n \r\n GA\r\n Gabon\r\n \r\n \r\n GM\r\n Gambia\r\n \r\n \r\n GH\r\n Ghana\r\n \r\n \r\n GN\r\n Guinea\r\n \r\n \r\n GW\r\n Guinea-Bissau\r\n \r\n \r\n KE\r\n Kenya\r\n \r\n \r\n LS\r\n Lesotho\r\n \r\n \r\n LR\r\n Liberia\r\n \r\n \r\n LY\r\n Libyan Arab Jamahiriya\r\n \r\n \r\n MG\r\n Madagascar\r\n \r\n \r\n MW\r\n Malawi\r\n \r\n \r\n ML\r\n Mali\r\n \r\n \r\n MR\r\n Mauritania\r\n \r\n \r\n MU\r\n Mauritius\r\n \r\n \r\n YT\r\n Mayotte\r\n \r\n \r\n MA\r\n Morocco\r\n \r\n \r\n MZ\r\n Mozambique\r\n \r\n \r\n NA\r\n Namibia\r\n \r\n \r\n NE\r\n Niger\r\n \r\n \r\n NG\r\n Nigeria\r\n \r\n \r\n RE\r\n Reunion\r\n \r\n \r\n RW\r\n Rwanda\r\n \r\n \r\n ST\r\n Sao Tome & Principe\r\n \r\n \r\n SN\r\n Senegal\r\n \r\n \r\n SC\r\n Seychelles\r\n \r\n \r\n SL\r\n Sierra Leone\r\n \r\n \r\n SO\r\n Somalia\r\n \r\n \r\n ZA\r\n South Africa\r\n \r\n \r\n SD\r\n Sudan\r\n \r\n \r\n SZ\r\n Swaziland\r\n \r\n \r\n TZ\r\n Tanzania\r\n \r\n \r\n TG\r\n Togo\r\n \r\n \r\n TN\r\n Tunisia\r\n \r\n \r\n UG\r\n Uganda\r\n \r\n \r\n EH\r\n Western Sahara\r\n \r\n \r\n ZM\r\n Zambia\r\n \r\n \r\n ZW\r\n Zimbabwe\r\n \r\n \r\n \r\n \r\n \r\n AM\r\n The Americas \r\n \r\n \r\n \r\n AI\r\n Anguilla\r\n \r\n \r\n AG\r\n Antigua & Barbuda\r\n \r\n \r\n AR\r\n Argentina\r\n \r\n \r\n AW\r\n Aruba\r\n \r\n \r\n BS\r\n Bahamas\r\n \r\n \r\n BB\r\n Barbados\r\n \r\n \r\n BZ\r\n Belize\r\n \r\n \r\n BM\r\n Bermuda\r\n \r\n \r\n BO\r\n Bolivia\r\n \r\n \r\n BR\r\n Brazil\r\n \r\n \r\n CA\r\n Canada\r\n \r\n \r\n KY\r\n Cayman Islands\r\n \r\n \r\n CL\r\n Chile\r\n \r\n \r\n CO\r\n Colombia\r\n \r\n \r\n CR\r\n Costa Rica\r\n \r\n \r\n CU\r\n Cuba\r\n \r\n \r\n DM\r\n Dominica\r\n \r\n \r\n DO\r\n Dominican Republic\r\n \r\n \r\n EC\r\n Ecuador\r\n \r\n \r\n SV\r\n El Salvador\r\n \r\n \r\n FK\r\n Falkand Islands (Malvinas)\r\n \r\n \r\n GF\r\n French Guiana\r\n \r\n \r\n GD\r\n Grenada\r\n \r\n \r\n GP\r\n Guadeloupe\r\n \r\n \r\n GT\r\n Guatemala\r\n \r\n \r\n GY\r\n Guyana\r\n \r\n \r\n HT\r\n Haiti\r\n \r\n \r\n HN\r\n Honduras\r\n \r\n \r\n JM\r\n Jamaica\r\n \r\n \r\n MQ\r\n Martinique\r\n \r\n \r\n MX\r\n Mexico\r\n \r\n \r\n MS\r\n Montserrat\r\n \r\n \r\n AN\r\n Netherlands Antilles\r\n \r\n \r\n NI\r\n Nicaragua\r\n \r\n \r\n PA\r\n Panama\r\n \r\n \r\n PY\r\n Paraguay\r\n \r\n \r\n PE\r\n Peru\r\n \r\n \r\n PR\r\n Puerto Rico\r\n \r\n \r\n SH\r\n Saint Helena\r\n \r\n \r\n PM\r\n Saint Pierre And Micquelon\r\n \r\n \r\n GS\r\n South Georgia & South Sandwich Islands\r\n \r\n \r\n KN\r\n St. Kitts & Nevis\r\n \r\n \r\n LC\r\n St. Lucia\r\n \r\n \r\n VC\r\n St. Vincent & Grenadines\r\n \r\n \r\n SR\r\n Suriname\r\n \r\n \r\n TT\r\n Trinidad & Tobago\r\n \r\n \r\n TC\r\n Turks And Caicos Islands\r\n \r\n \r\n US\r\n United States\r\n \r\n \r\n UY\r\n Uruguay\r\n \r\n \r\n VE\r\n Venezuela\r\n \r\n \r\n VG\r\n Virgin Islands, British\r\n \r\n \r\n VI\r\n Virgin Islands, U.S.\r\n \r\n \r\n \r\n \r\n \r\n AN\r\n Antarctica \r\n \r\n \r\n \r\n AQ\r\n Antarctica\r\n \r\n \r\n BV\r\n Bouvet Island\r\n \r\n \r\n TF\r\n French Southern Territories\r\n \r\n \r\n \r\n \r\n \r\n AS\r\n Asia \r\n \r\n \r\n \r\n AF\r\n Afghanistan\r\n \r\n \r\n AM\r\n Armenia\r\n \r\n \r\n AZ\r\n Azerbaijan\r\n \r\n \r\n BH\r\n Bahrain\r\n \r\n \r\n BD\r\n Bangladesh\r\n \r\n \r\n BT\r\n Bhutan\r\n \r\n \r\n IO\r\n British Indian Ocean Territory\r\n \r\n \r\n BN\r\n Brunei-Darussalam\r\n \r\n \r\n KH\r\n Cambodia\r\n \r\n \r\n CN\r\n China\r\n \r\n \r\n CY\r\n Cyprus\r\n \r\n \r\n GE\r\n Georgia\r\n \r\n \r\n HK\r\n Hong Kong\r\n \r\n \r\n IN\r\n India\r\n \r\n \r\n ID\r\n Indonesia\r\n \r\n \r\n IR\r\n Iran\r\n \r\n \r\n IQ\r\n Iraq\r\n \r\n \r\n IL\r\n Israel\r\n \r\n \r\n JP\r\n Japan\r\n \r\n \r\n JO\r\n Jordan\r\n \r\n \r\n KZ\r\n Kazakhstan\r\n \r\n \r\n KW\r\n Kuwait\r\n \r\n \r\n KG\r\n Kyrgyzstan\r\n \r\n \r\n LA\r\n Laos\r\n \r\n \r\n LB\r\n Lebanon\r\n \r\n \r\n MO\r\n Macao\r\n \r\n \r\n MY\r\n Malaysia\r\n \r\n \r\n MV\r\n Maldives (Maladive Ilands)\r\n \r\n \r\n MN\r\n Mongolia\r\n \r\n \r\n MM\r\n Myanmar (Burma)\r\n \r\n \r\n NP\r\n Nepal\r\n \r\n \r\n KP\r\n North Korea\r\n \r\n \r\n OM\r\n Oman\r\n \r\n \r\n PK\r\n Pakistan\r\n \r\n \r\n PS\r\n Palestinian Territory, Occupied\r\n \r\n \r\n PH\r\n Philippines\r\n \r\n \r\n QA\r\n Quatar\r\n \r\n \r\n RU\r\n Russian Federation\r\n \r\n \r\n SA\r\n Saudi Arabia\r\n \r\n \r\n SG\r\n Singapore\r\n \r\n \r\n KR\r\n South Korea\r\n \r\n \r\n LK\r\n Sri Lanka\r\n \r\n \r\n SY\r\n Syrian Arab Republic\r\n \r\n \r\n TW\r\n Taiwan\r\n \r\n \r\n TJ\r\n Tajikistan\r\n \r\n \r\n TH\r\n Thailand\r\n \r\n \r\n TL\r\n Timor-Leste\r\n \r\n \r\n TR\r\n Turkey\r\n \r\n \r\n TM\r\n Turkmenistan\r\n \r\n \r\n AE\r\n United Arab Emirates\r\n \r\n \r\n UZ\r\n Uzbekistan\r\n \r\n \r\n VN\r\n Vietnam\r\n \r\n \r\n YE\r\n Yemen\r\n \r\n \r\n \r\n \r\n \r\n EU\r\n Europe \r\n \r\n \r\n \r\n AX\r\n Åland Islands\r\n \r\n \r\n AL\r\n Albania\r\n \r\n \r\n AD\r\n Andorra\r\n \r\n \r\n AT\r\n Austria\r\n \r\n \r\n BY\r\n Belarus\r\n \r\n \r\n BE\r\n Belgium\r\n \r\n \r\n BA\r\n Bosnia & Herzegovina\r\n \r\n \r\n BG\r\n Bulgaria\r\n \r\n \r\n HR\r\n Croatia\r\n \r\n \r\n CZ\r\n Czech Republic\r\n \r\n \r\n DK\r\n Denmark\r\n \r\n \r\n EE\r\n Estonia\r\n \r\n \r\n FO\r\n Faroe Islands\r\n \r\n \r\n FI\r\n Finland\r\n \r\n \r\n FR\r\n France\r\n \r\n \r\n DE\r\n Germany\r\n \r\n \r\n GI\r\n Gibraltar\r\n \r\n \r\n GR\r\n Greece\r\n \r\n \r\n GL\r\n Greenland\r\n \r\n \r\n HU\r\n Hungary\r\n \r\n \r\n IS\r\n Iceland\r\n \r\n \r\n IE\r\n Ireland\r\n \r\n \r\n IT\r\n Italy\r\n \r\n \r\n LV\r\n Latvia\r\n \r\n \r\n LI\r\n Liechtenstein\r\n \r\n \r\n LT\r\n Lithuania\r\n \r\n \r\n LU\r\n Luxembourg\r\n \r\n \r\n MK\r\n Macedonia (former YR)\r\n \r\n \r\n MT\r\n Malta\r\n \r\n \r\n MD\r\n Moldova, Republic of\r\n \r\n \r\n MC\r\n Monaco\r\n \r\n \r\n NL\r\n Netherlands\r\n \r\n \r\n NO\r\n Norway\r\n \r\n \r\n PL\r\n Poland\r\n \r\n \r\n PT\r\n Portugal\r\n \r\n \r\n RO\r\n Romania\r\n \r\n \r\n SM\r\n San Marino\r\n \r\n \r\n CS\r\n Serbia & Montenegro\r\n \r\n \r\n SK\r\n Slovakia\r\n \r\n \r\n SI\r\n Slovenia\r\n \r\n \r\n ES\r\n Spain\r\n \r\n \r\n SJ\r\n Svalbard And Jan Mayen\r\n \r\n \r\n SE\r\n Sweden\r\n \r\n \r\n CH\r\n Switzerland\r\n \r\n \r\n UA\r\n Ukraine\r\n \r\n \r\n GB\r\n United Kingdom\r\n \r\n \r\n VA\r\n Vatican City\r\n \r\n \r\n \r\n \r\n \r\n OC\r\n Ocenania \r\n \r\n \r\n \r\n AS\r\n American Samoa\r\n \r\n \r\n AU\r\n Australia\r\n \r\n \r\n CX\r\n Chrismas Island\r\n \r\n \r\n CC\r\n Cocos (Keeling) Islands\r\n \r\n \r\n CK\r\n Cook Islands\r\n \r\n \r\n FJ\r\n Fiji\r\n \r\n \r\n PF\r\n French Polynesia\r\n \r\n \r\n GU\r\n Guam\r\n \r\n \r\n HM\r\n Heard Island And McDonald Islands\r\n \r\n \r\n KI\r\n Kiribati\r\n \r\n \r\n MH\r\n Marshall Islands\r\n \r\n \r\n FM\r\n Micronesia (Federa States of)\r\n \r\n \r\n NR\r\n Nauru\r\n \r\n \r\n NC\r\n New Caledonia\r\n \r\n \r\n NZ\r\n New Zealand\r\n \r\n \r\n NU\r\n Niue\r\n \r\n \r\n NF\r\n Norfolk Island\r\n \r\n \r\n MP\r\n Northern Mariana Islands\r\n \r\n \r\n PW\r\n Palau\r\n \r\n \r\n PG\r\n Papua-New Guinea\r\n \r\n \r\n PN\r\n Pitcairn\r\n \r\n \r\n SB\r\n Solomon Islands\r\n \r\n \r\n TK\r\n Tokelau\r\n \r\n \r\n TO\r\n Tonga\r\n \r\n \r\n TV\r\n Tuvalu\r\n \r\n \r\n UM\r\n United States Minor Outlying Islands\r\n \r\n \r\n VU\r\n Vanuatu\r\n \r\n \r\n WF\r\n Wallis And Futuna\r\n \r\n \r\n WS\r\n Western Samoa\r\n \r\n \r\n \r\n \r\n \r\n \r\n",
17 | "headers": {
18 | "Cache-Control": "private, max-age=0",
19 | "Content-Type": "text/xml; charset=utf-8",
20 | "Server": "Microsoft-IIS/8.5",
21 | "Web-Service": "DataFlex 18.2",
22 | "Date": "Wed, 25 Apr 2018 08:57:52 GMT",
23 | "Content-Length": "41662"
24 | },
25 | "status_code": 200,
26 | "type": "ok"
27 | }
28 | }
29 | ]
--------------------------------------------------------------------------------
/test/fixtures/wsdls/RATP.wsdl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | RATP : Web Service Information Voyageur
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
422 |
423 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 |
618 |
619 |
620 |
621 |
622 |
623 |
624 |
625 |
626 |
627 |
628 |
629 |
630 |
631 |
632 |
633 |
634 |
635 |
636 |
637 |
638 |
639 |
640 |
641 |
642 |
643 |
644 |
645 |
646 |
647 |
648 |
649 |
650 |
651 |
652 |
653 |
654 |
655 |
656 |
657 |
658 |
659 |
660 |
661 |
662 |
663 |
664 |
665 |
666 |
667 |
668 |
669 |
670 |
671 |
672 |
673 |
674 |
675 |
676 |
677 |
678 |
679 |
680 |
681 |
682 |
683 |
684 |
685 |
686 |
687 |
688 |
689 |
690 |
691 |
692 |
693 |
694 |
695 |
696 |
697 |
698 |
699 |
700 |
701 |
702 |
703 |
704 |
705 |
706 |
707 |
708 |
709 |
710 |
711 |
712 |
713 |
714 |
715 |
716 |
717 |
718 |
719 |
720 |
721 |
722 |
723 |
724 |
725 |
726 |
727 |
728 |
729 |
730 |
731 |
732 |
733 |
734 |
735 |
736 |
737 |
738 |
739 |
740 |
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 |
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 |
770 |
771 |
772 |
773 |
774 |
775 |
776 |
777 |
778 |
779 |
780 |
781 |
782 |
783 |
784 |
785 |
786 |
787 |
788 |
789 |
790 |
791 |
792 |
793 |
794 |
795 |
796 |
797 |
798 |
799 |
800 |
801 |
802 |
803 |
804 |
805 |
806 |
807 |
808 |
809 |
810 |
811 |
812 |
813 |
814 |
815 |
816 |
817 |
818 |
819 |
820 |
821 |
822 |
823 |
824 |
825 |
826 |
827 |
828 |
829 |
830 |
831 |
832 |
833 |
834 |
835 |
836 |
837 |
838 |
839 |
840 |
841 |
842 |
843 |
844 |
845 |
846 |
847 |
848 |
849 |
850 |
851 |
852 |
853 |
854 |
855 |
856 |
857 |
858 |
859 |
860 |
861 |
862 |
863 |
864 |
865 |
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
876 |
877 |
878 |
879 |
880 |
881 |
882 |
883 |
884 |
885 |
886 |
887 |
888 |
889 |
890 |
891 |
892 |
893 |
894 |
895 |
896 |
897 |
898 |
899 |
900 |
901 |
902 |
903 |
904 |
905 |
906 |
907 |
908 |
909 |
910 |
911 |
912 |
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 |
922 |
923 |
924 |
925 |
926 |
927 |
928 |
929 |
930 |
931 |
932 |
933 |
934 |
935 |
936 |
937 |
938 |
939 |
940 |
941 |
942 |
943 |
944 |
945 |
946 |
947 |
948 |
949 |
950 |
951 |
952 |
953 |
954 |
955 |
--------------------------------------------------------------------------------