Class: Wasabi::Parser
- Inherits:
-
Object
- Object
- Wasabi::Parser
- Defined in:
- lib/wasabi/parser.rb
Overview
Wasabi::Parser
Parses WSDL documents and remembers their important parts.
Defined Under Namespace
Classes: MessageRef
Constant Summary collapse
- XSD =
"http://www.w3.org/2001/XMLSchema"- WSDL =
"http://schemas.xmlsoap.org/wsdl/"- SOAP_1_1 =
"http://schemas.xmlsoap.org/wsdl/soap/"- SOAP_1_2 =
"http://schemas.xmlsoap.org/wsdl/soap12/"
Instance Attribute Summary collapse
-
#deferred_types ⇒ Object
Returns a map of deferred type Proc objects.
-
#document ⇒ Object
Returns the Nokogiri document.
-
#element_form_default ⇒ Object
Returns the elementFormDefault value.
-
#endpoint ⇒ Object
Returns the SOAP endpoint.
-
#namespace ⇒ Object
Returns the target namespace.
-
#namespaces ⇒ Object
Returns a map from namespace identifier to namespace URI.
-
#operations ⇒ Object
Returns the SOAP operations.
-
#service_name ⇒ Object
Returns the SOAP Service Name.
-
#types ⇒ Object
Returns a map from a type name to a Hash with type information.
Instance Method Summary collapse
-
#faults_for(operation) ⇒ Object
An operation may declare any number of faults, or none at all.
-
#initialize(document) ⇒ Parser
constructor
A new instance of Parser.
-
#input_for(operation) ⇒ Object
wasabi reports a single input and a single output per operation.
-
#message_ref_for(operation, port_type_element, element_name, index) ⇒ MessageRef
Resolves a single portType input/output/fault element to the message it points at, walking portType -> message -> part -> element.
- #output_for(operation) ⇒ Object
- #parse ⇒ Object
- #parse_deferred_types ⇒ Object
- #parse_endpoint ⇒ Object
- #parse_messages ⇒ Object
- #parse_namespaces ⇒ Object
- #parse_operations ⇒ Object
- #parse_operations_parameters ⇒ Object
- #parse_port_type_operations ⇒ Object
- #parse_port_types ⇒ Object
- #parse_service_name ⇒ Object
- #parse_types ⇒ Object
- #parse_url(url) ⇒ Object
-
#port_type_operation_for(operation) ⇒ Object
Looks up the portType operation matching a binding operation, walking up to the portType via the binding's type attribute.
- #process_type(namespace, type, name) ⇒ Object
-
#resolve_messages(operation, element_name) ⇒ Array<MessageRef>
Resolves the wsdl messages an operation's input, output, or fault elements point at, by walking up to the portType and down into the messages.
- #schemas ⇒ Object
- #section(section_name) ⇒ Object
- #sections ⇒ Object
- #service ⇒ Object
Constructor Details
#initialize(document) ⇒ Parser
Returns a new instance of Parser.
21 22 23 24 25 26 27 28 29 |
# File 'lib/wasabi/parser.rb', line 21 def initialize(document) self.document = document self.operations = {} self.namespaces = {} self.service_name = "" self.types = {} self.deferred_types = [] self.element_form_default = :unqualified end |
Instance Attribute Details
#deferred_types ⇒ Object
Returns a map of deferred type Proc objects.
47 48 49 |
# File 'lib/wasabi/parser.rb', line 47 def deferred_types @deferred_types end |
#document ⇒ Object
Returns the Nokogiri document.
32 33 34 |
# File 'lib/wasabi/parser.rb', line 32 def document @document end |
#element_form_default ⇒ Object
Returns the elementFormDefault value.
56 57 58 |
# File 'lib/wasabi/parser.rb', line 56 def element_form_default @element_form_default end |
#endpoint ⇒ Object
Returns the SOAP endpoint.
50 51 52 |
# File 'lib/wasabi/parser.rb', line 50 def endpoint @endpoint end |
#namespace ⇒ Object
Returns the target namespace.
35 36 37 |
# File 'lib/wasabi/parser.rb', line 35 def namespace @namespace end |
#namespaces ⇒ Object
Returns a map from namespace identifier to namespace URI.
38 39 40 |
# File 'lib/wasabi/parser.rb', line 38 def namespaces @namespaces end |
#operations ⇒ Object
Returns the SOAP operations.
41 42 43 |
# File 'lib/wasabi/parser.rb', line 41 def operations @operations end |
#service_name ⇒ Object
Returns the SOAP Service Name
53 54 55 |
# File 'lib/wasabi/parser.rb', line 53 def service_name @service_name end |
#types ⇒ Object
Returns a map from a type name to a Hash with type information.
44 45 46 |
# File 'lib/wasabi/parser.rb', line 44 def types @types end |
Instance Method Details
#faults_for(operation) ⇒ Object
An operation may declare any number of faults, or none at all.
256 257 258 |
# File 'lib/wasabi/parser.rb', line 256 def faults_for(operation) (operation, "fault") end |
#input_for(operation) ⇒ Object
wasabi reports a single input and a single output per operation. When the portType or its message cannot be resolved or doesn't exist like with a one-way operation, it falls back to the operation name.
247 248 249 |
# File 'lib/wasabi/parser.rb', line 247 def input_for(operation) (operation, "input").first || MessageRef.new(nil, operation["name"]) end |
#message_ref_for(operation, port_type_element, element_name, index) ⇒ MessageRef
Resolves a single portType input/output/fault element to the message it points at, walking portType -> message -> part -> element. Falls back to the operation name when the message cannot be resolved.
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 |
# File 'lib/wasabi/parser.rb', line 289 def (operation, port_type_element, element_name, index) operation_name = operation["name"] # If the message attribute contains a colon, the message is namespaced. parts = port_type_element.attribute("message").to_s.split(":", 2) , = (parts.size == 2) ? parts : [nil, *parts] = nil = nil # When there is a parts attribute in the soap:body element, use that value # to look up the message part from the messages array. input_output_element = operation.element_children.select { |node| node.name == element_name }.at(index) if input_output_element soap_body_element = input_output_element.element_children.find { |node| node.name == "body" } soap_body_parts = soap_body_element["parts"] if soap_body_element end # look for any message part that matches the soap body parts = @messages[] = &.element_children&.find do |node| soap_body_parts.nil? ? (node.name == "part") : (node.name == "part" && node["name"] == soap_body_parts) end if && (port_element = .attribute("element")) = port_element.to_s if .include?(":") , = .split(":") else = end end # If the message is not found, we should use the operation name as the message name # for document style operations applies only to output if element_name == "output" # if the operation is document style and theres no port_message_part, we should # use the operation_name soap_operation = operation.element_children.find { |node| node.name == "operation" } if .nil? && (soap_operation.nil? || soap_operation["style"] != "rpc") = = .nil? ? operation_name : end end # Fall back to the name of the binding operation if MessageRef.new(, ) else MessageRef.new(, operation_name) end end |
#output_for(operation) ⇒ Object
251 252 253 |
# File 'lib/wasabi/parser.rb', line 251 def output_for(operation) (operation, "output").first || MessageRef.new(nil, operation["name"]) end |
#parse ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/wasabi/parser.rb', line 58 def parse parse_namespaces parse_endpoint parse_service_name parse_port_types parse_port_type_operations parse_operations parse_operations_parameters parse_types parse_deferred_types end |
#parse_deferred_types ⇒ Object
240 241 242 |
# File 'lib/wasabi/parser.rb', line 240 def parse_deferred_types deferred_types.each(&:call) end |
#parse_endpoint ⇒ Object
83 84 85 86 87 88 89 90 |
# File 'lib/wasabi/parser.rb', line 83 def parse_endpoint if (service_node = service) endpoint = service_node.at_xpath(".//soap11:address/@location", "soap11" => SOAP_1_1) endpoint ||= service_node.at_xpath(service_node, ".//soap12:address/@location", "soap12" => SOAP_1_2) end @endpoint = parse_url(endpoint) if endpoint end |
#parse_messages ⇒ Object
104 105 106 107 |
# File 'lib/wasabi/parser.rb', line 104 def = document.root.element_children.select { |node| node.name == "message" } @messages = .map { |node| [node["name"], node] }.to_h end |
#parse_namespaces ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/wasabi/parser.rb', line 71 def parse_namespaces element_form_default = schemas.first && schemas.first["elementFormDefault"] @element_form_default = element_form_default.to_s.to_sym if element_form_default namespace = document.root["targetNamespace"] @namespace = namespace.to_s if namespace @namespaces = @document.namespaces.each_with_object({}) do |(key, value), memo| memo[key.sub("xmlns:", "")] = value end end |
#parse_operations ⇒ Object
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 |
# File 'lib/wasabi/parser.rb', line 139 def parse_operations operations = document.xpath("wsdl:definitions/wsdl:binding/wsdl:operation", "wsdl" => WSDL) operations.each do |operation| name = operation.attribute("name").to_s snakecase_name = Wasabi::CoreExt::String.snakecase(name).to_sym # TODO: check for soap namespace? soap_operation = operation.element_children.find { |node| node.name == "operation" } soap_action = soap_operation["soapAction"] if soap_operation soap_document = soap_operation["style"] == "document" if soap_operation if soap_action || soap_document soap_action = soap_action.to_s action = (soap_action && !soap_action.empty?) ? soap_action : name # There should be a matching portType for each binding, so we will lookup the input from there. input = input_for(operation) output = output_for(operation) faults = faults_for(operation) # Store namespace identifier so this operation can be mapped to the proper namespace. operation_entry = { name: name, action: action, input: input.type, output: output.type, namespace_identifier: input.namespace_id } operation_entry[:fault] = faults.map(&:type) if faults.any? @operations[snakecase_name] = operation_entry elsif !@operations[snakecase_name] @operations[snakecase_name] = {action: name, input: name} end end end |
#parse_operations_parameters ⇒ Object
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/wasabi/parser.rb', line 123 def parse_operations_parameters document.xpath("wsdl:definitions/wsdl:types/*[local-name()='schema']/*[local-name()='element']", "wsdl" => WSDL).each do |element| name = Wasabi::CoreExt::String.snakecase(element.attribute("name").to_s).to_sym if (operation = @operations[name]) element.xpath("*[local-name() ='complexType']/*[local-name() ='sequence']/*[local-name() ='element']").each do |child_element| attr_name = child_element.attribute("name").to_s attr_type = ((attr_type = child_element.attribute("type").to_s.split(":")).size > 1) ? attr_type[1] : attr_type[0] operation[:parameters] ||= {} operation[:parameters][attr_name.to_sym] = {name: attr_name, type: attr_type} end end end end |
#parse_port_type_operations ⇒ Object
114 115 116 117 118 119 120 121 |
# File 'lib/wasabi/parser.rb', line 114 def parse_port_type_operations @port_type_operations = {} @port_types.each do |port_type_name, port_type| operations = port_type.element_children.select { |node| node.name == "operation" } @port_type_operations[port_type_name] = operations.map { |node| [node["name"], node] }.to_h end end |
#parse_port_types ⇒ Object
109 110 111 112 |
# File 'lib/wasabi/parser.rb', line 109 def parse_port_types port_types = document.root.element_children.select { |node| node.name == "portType" } @port_types = port_types.map { |node| [node["name"], node] }.to_h end |
#parse_service_name ⇒ Object
99 100 101 102 |
# File 'lib/wasabi/parser.rb', line 99 def parse_service_name service_name = document.root["name"] @service_name = service_name.to_s if service_name end |
#parse_types ⇒ Object
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/wasabi/parser.rb', line 175 def parse_types schemas.each do |schema| schema_namespace = schema["targetNamespace"] schema.element_children.each do |node| namespace = schema_namespace || @namespace case node.name when "element" complex_type = node.at_xpath("./xs:complexType", "xs" => XSD) process_type namespace, complex_type, node["name"].to_s if complex_type when "complexType" process_type namespace, node, node["name"].to_s end end end end |
#parse_url(url) ⇒ Object
92 93 94 95 96 97 |
# File 'lib/wasabi/parser.rb', line 92 def parse_url(url) unescaped_url = Addressable::URI.unescape(url.to_s) escaped_url = Addressable::URI.escape(unescaped_url) URI(escaped_url) rescue URI::InvalidURIError, Addressable::URI::InvalidURIError end |
#port_type_operation_for(operation) ⇒ Object
Looks up the portType operation matching a binding operation, walking up to the portType via the binding's type attribute.
279 280 281 282 |
# File 'lib/wasabi/parser.rb', line 279 def port_type_operation_for(operation) binding_type = operation.parent["type"].to_s.split(":").last @port_type_operations.dig(binding_type, operation["name"]) end |
#process_type(namespace, type, name) ⇒ Object
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 |
# File 'lib/wasabi/parser.rb', line 193 def process_type(namespace, type, name) @types[namespace] ||= {} @types[namespace][name] ||= {namespace: namespace} @types[namespace][name][:order!] = [] type.xpath("./xs:sequence/xs:element", "xs" => XSD).each do |inner| element_name = inner.attribute("name").to_s @types[namespace][name][element_name] = {type: inner.attribute("type").to_s} [:nillable, :minOccurs, :maxOccurs].each do |attr| if (v = inner.attribute(attr.to_s)) @types[namespace][name][element_name][attr] = v.to_s end end @types[namespace][name][:order!] << element_name end type.xpath("./xs:complexContent/xs:extension/xs:sequence/xs:element", "xs" => XSD).each do |inner_element| element_name = inner_element.attribute("name").to_s @types[namespace][name][element_name] = {type: inner_element.attribute("type").to_s} @types[namespace][name][:order!] << element_name end type.xpath("./xs:complexContent/xs:extension[@base]", "xs" => XSD).each do |inherits| base = inherits.attribute("base").value.match(/\w+$/).to_s if @types[namespace][base] # Reverse merge because we don't want subclass attributes to be overriden by base class @types[namespace][name] = types[namespace][base].merge(types[namespace][name]) @types[namespace][name][:order!] = @types[namespace][base][:order!] | @types[namespace][name][:order!] @types[namespace][name][:base_type] = base else p = proc do if @types[namespace][base] # Reverse merge because we don't want subclass attributes to be overriden by base class @types[namespace][name] = @types[namespace][base].merge(@types[namespace][name]) @types[namespace][name][:order!] = @types[namespace][base][:order!] | @types[namespace][name][:order!] @types[namespace][name][:base_type] = base end end deferred_types << p end end end |
#resolve_messages(operation, element_name) ⇒ Array<MessageRef>
Resolves the wsdl messages an operation's input, output, or fault elements point at, by walking up to the portType and down into the messages. Returns one MessageRef per matching element, so none when there are no matches.
265 266 267 268 269 270 271 272 273 274 275 |
# File 'lib/wasabi/parser.rb', line 265 def (operation, element_name) port_type_operation = port_type_operation_for(operation) # Sometimes portTypes are actually included in a separate WSDL, # so the portType operation cannot be resolved here. port_type_elements = port_type_operation&.element_children&.select { |node| node.name == element_name } || [] port_type_elements.each_with_index.map do |port_type_element, index| (operation, port_type_element, element_name, index) end end |
#schemas ⇒ Object
342 343 344 345 |
# File 'lib/wasabi/parser.rb', line 342 def schemas types = section("types").first types ? types.element_children : [] end |
#section(section_name) ⇒ Object
352 353 354 |
# File 'lib/wasabi/parser.rb', line 352 def section(section_name) sections[section_name] || [] end |
#sections ⇒ Object
356 357 358 |
# File 'lib/wasabi/parser.rb', line 356 def sections @sections ||= document.root.element_children.group_by { |node| node.name } end |
#service ⇒ Object
347 348 349 350 |
# File 'lib/wasabi/parser.rb', line 347 def service services = section("service") services&.first # service nodes could be imported? end |