├── 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 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 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 | 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 | -------------------------------------------------------------------------------- /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 | 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 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 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 | --------------------------------------------------------------------------------