Skip to content

Commit

Permalink
added support for xml schema imports /cc #27
Browse files Browse the repository at this point in the history
unfortunately, these specs come with a lot of schema fixtures.
  • Loading branch information
rubiii committed May 27, 2013
1 parent 4010e5f commit f9ac977
Show file tree
Hide file tree
Showing 42 changed files with 248 additions and 63 deletions.
116 changes: 116 additions & 0 deletions docs/version4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
title: Version 4
---


Wasabi 4.0 is under active development and currently only available via GitHub.
To give it a try, just add it to your Gemfile.

``` ruby
gem 'wasabi', github: 'savonrb/wasabi'
```

You can also find a summary of the changes in the
[Changelog](https://github.com/savonrb/wasabi/blob/master/CHANGELOG.md).


The big rewrite
---------------

Wasabi 4.0 is based on everything I learned while helping people talk to SOAP services using Ruby for the past five years.
The goal for this is to follow the specifications as close as possible while verifying them against real world services.



#### Services and endpoints

"A WSDL document defines [services as collections of network endpoints](http://www.w3.org/TR/wsdl#_introduction), or ports".
Multiple services, multiple ports. Endpoint URLs are defined per port and each port references a binding which in turn
defines a set of operations. This means you need to know about the services and ports defined by the WSDL.

``` xml
<wsdl:service name="AuthenticationService">
<wsdl:port binding="tns:AuthenticationServiceBinding" name="AuthenticationServicePort">
<soap:address location="http://example.com/validation/1.0/AuthenticationService"/>
</wsdl:port>
</wsdl:service>
```

Create a new Wasabi instance with a URL or a path to a local WSDL document.

``` ruby
wsdl = Wasabi.new('http://example.com?wsdl')
```

Wasabi also accepts an optional `HTTPI::Request` object which it uses to fetch remote WSDL files and resolve imports.
This allows you to [pre-configure any HTTP request](http://httpirb.com/#options).

``` ruby
request = HTTPI::Request.new
request.proxy = 'http://localhost:8447'

wsdl = Wasabi.new('example.wsdl', request)
```

Now you can call the `#services` method for a summary of the services and ports defined by the WSDL.

``` ruby
wsdl.services
```

This returns a list of service and ports along with information about the port's type and location.
The type is a namespace which in this example indicates a SOAP 1.1 port. The location is the actual
location of the service.

Remember that there could be multiple services with multiple ports which each reference a different
set of operations. Also, Wasabi currently only returns SOAP 1.1 and 1.2 services and ports.
Selection the proper ports should probably be left to any client library, but this is just how
it curently works.

``` ruby
{
'ExampleService' => {
:ports => {
'ExamplePort' => {
:type => 'http://schemas.xmlsoap.org/wsdl/soap/',
:location => 'http://example.com'
}
}
}
}
```


#### Operations

Knowing the name of a service and port gives you access to its operations.

``` ruby
operations = wsdl.operations('ExampleService', 'ExamplePort')
```

This returns a list of operation names and objects which can be asked anything needed to call this operation.

``` ruby
{
'someOperation' => <Wasabi::Operation>,
'anotherOperation' => <Wasabi::Operation>
}
```

There's also the `#operation` shortcut method which accepts the name of a service and port and the
name of an operation to get a single operation object.

``` ruby
operation = wsdl.operation('ExampleService', 'ExamplePort', 'someOperation')
```

Operations should know everything you need to create an HTTP request and call the operation.

``` ruby
operation.name # => 'authenticate'
operation.nsid # => 'tns'
operation.input # => 'authenticate'
operation.soap_action # => 'urn:authenticate'
operation.endpoint # => 'http://v1.example.com'
```
13 changes: 9 additions & 4 deletions lib/wasabi/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,15 @@ def collect_sections(mapping)
end

def schema_nodes
@schema_nodes ||= begin
types = @document.root.at_xpath('wsdl:types', 'wsdl' => Wasabi::WSDL)
types ? types.element_children : []
end
@schema_nodes ||= schema_nodes! || []
end

def schema_nodes!
root = @document.root
return [root] if root.name == 'schema'

types = root.at_xpath('wsdl:types', 'wsdl' => Wasabi::WSDL)
types.element_children if types
end

end
Expand Down
33 changes: 26 additions & 7 deletions lib/wasabi/importer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,43 @@ def import(location)
documents = DocumentCollection.new
schemas = SchemaCollection.new

import! [location] do |document|
import_document(location) do |document|
documents << document
schemas.push(document.schemas)
end

# resolve xml schema imports
import_schemas(schemas) do |schema_location|
import_document(schema_location) do |document|
schemas.push(document.schemas)
end
end

[documents, schemas]
end

private

def import!(locations, &block)
locations.each do |location|
xml = @resolver.resolve(location)
document = Document.new Nokogiri.XML(xml), @wsdl
def import_document(location, &block)
xml = @resolver.resolve(location)
document = Document.new Nokogiri.XML(xml), @wsdl

block.call(document)

# resolve wsdl imports
document.imports.each do |import_location|
import_document(import_location, &block)
end
end

block.call(document)
def import_schemas(schemas)
schemas.each do |schema|
schema.imports.each do |namespace, schema_location|
next unless schema_location
# TODO: also skip if the schema was already imported

import! document.imports, &block
yield(schema_location)
end
end
end

Expand Down
4 changes: 3 additions & 1 deletion lib/wasabi/message_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,10 @@ def build_type_element(part)
# and its type and returns an Element with that type.
def build_element(part)
local, namespace = expand_qname(part[:element], part[:namespaces])
schema = @wsdl.schemas.find_by_namespace(namespace)
raise "Unable to find schema for #{namespace.inspect}" unless schema

element = @wsdl.schemas.element(namespace, local)
element = schema.elements.fetch(local)

name = element.name
type = find_type_for_element(element)
Expand Down
20 changes: 8 additions & 12 deletions lib/wasabi/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
class Wasabi
class Schema

SCHEMA_TYPES = %w[element complexType simpleType]

def initialize(schema, wsdl)
@schema = schema
@wsdl = wsdl
Expand All @@ -15,30 +13,28 @@ def initialize(schema, wsdl)
@elements = {}
@complex_types = {}
@simple_types = {}
@imports = {}

parse_types
parse
end

attr_accessor :target_namespace, :element_form_default,
attr_accessor :target_namespace, :element_form_default, :imports,
:elements, :complex_types, :simple_types

private

def parse_types
def parse
schema = {
:target_namespace => @target_namespace,
:element_form_default => @element_form_default
}

@schema.element_children.each do |node|
next unless SCHEMA_TYPES.include? node.name

name = node['name']

case node.name
when 'element' then @elements[name] = Type::Element.new(node, @wsdl, schema)
when 'complexType' then @complex_types[name] = Type::ComplexType.new(node, @wsdl, schema)
when 'simpleType' then @simple_types[name] = Type::SimpleType.new(node, @wsdl, schema)
when 'element' then @elements[node['name']] = Type::Element.new(node, @wsdl, schema)
when 'complexType' then @complex_types[node['name']] = Type::ComplexType.new(node, @wsdl, schema)
when 'simpleType' then @simple_types[node['name']] = Type::SimpleType.new(node, @wsdl, schema)
when 'import' then @imports[node['namespace']] = node['schemaLocation']
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/wasabi/schema_collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def simple_type(namespace, name)
find_by_namespace(namespace).simple_types[name]
end

# TODO: maybe store by namespace?
# TODO: store by namespace instead?
def find_by_namespace(namespace)
find { |schema| schema.target_namespace == namespace }
end
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions spec/fixtures/bookt/bookt0.xsd

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions spec/fixtures/bookt/bookt1.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><xs:schema attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/"><xs:element name="anyType" nillable="true" type="xs:anyType"/><xs:element name="anyURI" nillable="true" type="xs:anyURI"/><xs:element name="base64Binary" nillable="true" type="xs:base64Binary"/><xs:element name="boolean" nillable="true" type="xs:boolean"/><xs:element name="byte" nillable="true" type="xs:byte"/><xs:element name="dateTime" nillable="true" type="xs:dateTime"/><xs:element name="decimal" nillable="true" type="xs:decimal"/><xs:element name="double" nillable="true" type="xs:double"/><xs:element name="float" nillable="true" type="xs:float"/><xs:element name="int" nillable="true" type="xs:int"/><xs:element name="long" nillable="true" type="xs:long"/><xs:element name="QName" nillable="true" type="xs:QName"/><xs:element name="short" nillable="true" type="xs:short"/><xs:element name="string" nillable="true" type="xs:string"/><xs:element name="unsignedByte" nillable="true" type="xs:unsignedByte"/><xs:element name="unsignedInt" nillable="true" type="xs:unsignedInt"/><xs:element name="unsignedLong" nillable="true" type="xs:unsignedLong"/><xs:element name="unsignedShort" nillable="true" type="xs:unsignedShort"/><xs:element name="char" nillable="true" type="tns:char"/><xs:simpleType name="char"><xs:restriction base="xs:int"/></xs:simpleType><xs:element name="duration" nillable="true" type="tns:duration"/><xs:simpleType name="duration"><xs:restriction base="xs:duration"><xs:pattern value="\-?P(\d*D)?(T(\d*H)?(\d*M)?(\d*(\.\d*)?S)?)?"/><xs:minInclusive value="-P10675199DT2H48M5.4775808S"/><xs:maxInclusive value="P10675199DT2H48M5.4775807S"/></xs:restriction></xs:simpleType><xs:element name="guid" nillable="true" type="tns:guid"/><xs:simpleType name="guid"><xs:restriction base="xs:string"><xs:pattern value="[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}"/></xs:restriction></xs:simpleType><xs:attribute name="FactoryType" type="xs:QName"/><xs:attribute name="Id" type="xs:ID"/><xs:attribute name="Ref" type="xs:IDREF"/></xs:schema>
1 change: 1 addition & 0 deletions spec/fixtures/bookt/bookt10.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><xs:schema elementFormDefault="qualified" targetNamespace="http://connect.bookt.com/Schemas/Person.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://connect.bookt.com/Schemas/Person.xsd"><xs:import schemaLocation="http://connect.bookt.com/svc/connect.svc?xsd=xsd2" namespace="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/><xs:complexType name="Person"><xs:sequence><xs:element minOccurs="0" name="Address1" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Address2" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="AltID" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="CellPhone" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="City" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Company" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Country" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="FaxNumber" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="FirstName" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Greeting" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="HomePhone" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="ID" type="xs:int"/><xs:element minOccurs="0" name="Initial" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="LastName" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Latitude" type="xs:decimal"/><xs:element minOccurs="0" name="LeadSource" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Longitude" type="xs:decimal"/><xs:element minOccurs="0" name="Notes" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="PostalCode" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Prefix" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="PrimaryEmail" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="State" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Status" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Suffix" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Tags" nillable="true" type="q1:ArrayOfstring" xmlns:q1="http://schemas.microsoft.com/2003/10/Serialization/Arrays"/><xs:element minOccurs="0" name="Title" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Type" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="WebSite" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="WorkPhone" nillable="true" type="xs:string"/></xs:sequence></xs:complexType><xs:element name="Person" nillable="true" type="tns:Person"/></xs:schema>
1 change: 1 addition & 0 deletions spec/fixtures/bookt/bookt11.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><xs:schema elementFormDefault="qualified" targetNamespace="http://connect.bookt.com/Schemas/CreditCard.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://connect.bookt.com/Schemas/CreditCard.xsd"><xs:complexType name="CreditCard"><xs:sequence><xs:element minOccurs="0" name="Address" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="CardNumber" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="City" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="ExpiresOn" type="xs:dateTime"/><xs:element minOccurs="0" name="NameOnCard" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="PostalCode" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="SecurityCode" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="State" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Type" nillable="true" type="xs:string"/></xs:sequence></xs:complexType><xs:element name="CreditCard" nillable="true" type="tns:CreditCard"/></xs:schema>
1 change: 1 addition & 0 deletions spec/fixtures/bookt/bookt12.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><xs:schema elementFormDefault="qualified" targetNamespace="http://connect.bookt.com/Schemas/Statement.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://connect.bookt.com/Schemas/Statement.xsd"><xs:import schemaLocation="http://connect.bookt.com/svc/connect.svc?xsd=xsd13" namespace="http://connect.bookt.com/Schemas/StatementDetail.xsd"/><xs:complexType name="Statement"><xs:sequence><xs:element minOccurs="0" name="Currency" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Description" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Details" nillable="true" type="q1:ArrayOfStatementDetail" xmlns:q1="http://connect.bookt.com/Schemas/StatementDetail.xsd"/><xs:element minOccurs="0" name="DueOn" type="xs:dateTime"/><xs:element minOccurs="0" name="ID" type="xs:int"/><xs:element minOccurs="0" name="Notes" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Status" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Total" type="xs:decimal"/><xs:element minOccurs="0" name="Type" nillable="true" type="xs:string"/></xs:sequence></xs:complexType><xs:element name="Statement" nillable="true" type="tns:Statement"/></xs:schema>
1 change: 1 addition & 0 deletions spec/fixtures/bookt/bookt13.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><xs:schema elementFormDefault="qualified" targetNamespace="http://connect.bookt.com/Schemas/StatementDetail.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://connect.bookt.com/Schemas/StatementDetail.xsd"><xs:complexType name="ArrayOfStatementDetail"><xs:sequence><xs:element minOccurs="0" maxOccurs="unbounded" name="StatementDetail" nillable="true" type="tns:StatementDetail"/></xs:sequence></xs:complexType><xs:element name="ArrayOfStatementDetail" nillable="true" type="tns:ArrayOfStatementDetail"/><xs:complexType name="StatementDetail"><xs:sequence><xs:element minOccurs="0" name="Amount" type="xs:decimal"/><xs:element minOccurs="0" name="Currency" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="ID" type="xs:int"/><xs:element minOccurs="0" name="Notes" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Quantity" type="xs:decimal"/><xs:element minOccurs="0" name="Status" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="SubTotal" type="xs:decimal"/><xs:element minOccurs="0" name="Type" nillable="true" type="xs:string"/></xs:sequence></xs:complexType><xs:element name="StatementDetail" nillable="true" type="tns:StatementDetail"/></xs:schema>
1 change: 1 addition & 0 deletions spec/fixtures/bookt/bookt14.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8"?><xs:schema elementFormDefault="qualified" targetNamespace="http://connect.bookt.com/Schemas/Event.xsd" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://connect.bookt.com/Schemas/Event.xsd"><xs:import schemaLocation="http://connect.bookt.com/svc/connect.svc?xsd=xsd10" namespace="http://connect.bookt.com/Schemas/Person.xsd"/><xs:import schemaLocation="http://connect.bookt.com/svc/connect.svc?xsd=xsd12" namespace="http://connect.bookt.com/Schemas/Statement.xsd"/><xs:complexType name="Event"><xs:sequence><xs:element minOccurs="0" name="AddedOn" type="xs:dateTime"/><xs:element minOccurs="0" name="AltID" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="CheckIn" type="xs:dateTime"/><xs:element minOccurs="0" name="CheckOut" type="xs:dateTime"/><xs:element minOccurs="0" name="CompletedOn" type="xs:dateTime"/><xs:element minOccurs="0" name="ID" type="xs:int"/><xs:element minOccurs="0" name="Lead" nillable="true" type="q1:Person" xmlns:q1="http://connect.bookt.com/Schemas/Person.xsd"/><xs:element minOccurs="0" name="Message" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="MessageFormat" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="NumAdults" type="xs:int"/><xs:element minOccurs="0" name="NumChildren" type="xs:int"/><xs:element minOccurs="0" name="PropertyID" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Statement" nillable="true" type="q2:Statement" xmlns:q2="http://connect.bookt.com/Schemas/Statement.xsd"/><xs:element minOccurs="0" name="Status" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Subject" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="Type" nillable="true" type="xs:string"/><xs:element minOccurs="0" name="UnitID" nillable="true" type="xs:string"/></xs:sequence></xs:complexType><xs:element name="Event" nillable="true" type="tns:Event"/></xs:schema>
Loading

0 comments on commit f9ac977

Please sign in to comment.