Class: Tina4::WSDL

Inherits:
Object
  • Object
show all
Defined in:
lib/tina4/wsdl.rb

Overview

SOAP 1.1 / WSDL server — zero-dependency, mirrors tina4-python’s wsdl module.

Usage (class-based):

class Calculator < Tina4::WSDL
  wsdl_operation output: { Result: :int }
  def add(a, b)
    { Result: a.to_i + b.to_i }
  end
end

# In a route handler:
service = Calculator.new(request)
response.call(service.handle)

Supported:

- WSDL 1.1 generation from Ruby type declarations
- SOAP 1.1 request/response handling via REXML
- Lifecycle hooks (on_request, on_result)
- Auto type mapping (Integer -> int, String -> string, Float -> double, etc.)
- XML escaping on all response values
- SOAP fault responses on errors

Defined Under Namespace

Classes: Service

Constant Summary collapse

NS_SOAP =
"http://schemas.xmlsoap.org/wsdl/soap/"
NS_WSDL =
"http://schemas.xmlsoap.org/wsdl/"
NS_XSD =
"http://www.w3.org/2001/XMLSchema"
NS_SOAP_ENV =
"http://schemas.xmlsoap.org/soap/envelope/"
RUBY_TO_XSD =
{
  :int        => "xsd:int",
  :integer    => "xsd:int",
  :string     => "xsd:string",
  :float      => "xsd:double",
  :double     => "xsd:double",
  :boolean    => "xsd:boolean",
  :bool       => "xsd:boolean",
  :date       => "xsd:date",
  :datetime   => "xsd:dateTime",
  :base64     => "xsd:base64Binary",
  Integer     => "xsd:int",
  String      => "xsd:string",
  Float       => "xsd:double",
  TrueClass   => "xsd:boolean",
  FalseClass  => "xsd:boolean"
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(request = nil, service_url: "") ⇒ WSDL

Returns a new instance of WSDL.



108
109
110
111
112
# File 'lib/tina4/wsdl.rb', line 108

def initialize(request = nil, service_url: "")
  @request     = request
  @service_url = service_url.empty? ? infer_url : service_url
  @operations  = discover_operations
end

Instance Attribute Details

#requestObject (readonly)

── Instance ─────────────────────────────────────────────────────────



106
107
108
# File 'lib/tina4/wsdl.rb', line 106

def request
  @request
end

#service_urlObject (readonly)

── Instance ─────────────────────────────────────────────────────────



106
107
108
# File 'lib/tina4/wsdl.rb', line 106

def service_url
  @service_url
end

Class Method Details

.inherited(subclass) ⇒ Object

Ensure subclasses get their own copy of the operations registry.



98
99
100
101
# File 'lib/tina4/wsdl.rb', line 98

def inherited(subclass)
  super
  subclass.instance_variable_set(:@wsdl_operations, wsdl_operations.dup)
end

.method_added(method_name) ⇒ Object

Hook into method definition to capture the pending operation.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/tina4/wsdl.rb', line 79

def method_added(method_name)
  super
  return unless @pending_wsdl_output

  output = @pending_wsdl_output
  @pending_wsdl_output = nil

  # Infer input parameter names from the method signature.
  params = instance_method(method_name).parameters
  input = {}
  params.each do |_kind, name|
    next if name.nil?
    input[name] = :string # default; callers can rely on type coercion
  end

  wsdl_operations[method_name.to_s] = { input: input, output: output }
end

.pending_wsdl_outputObject

Pending output hash waiting for the next method definition.



63
64
65
# File 'lib/tina4/wsdl.rb', line 63

def pending_wsdl_output
  @pending_wsdl_output
end

.wsdl_operation(output: {}) ⇒ Object

Mark the next defined method as a WSDL operation.

wsdl_operation output: { Result: :int }
def add(a, b) ...

Input parameters are inferred from the method signature. The output hash maps response element names to XSD type symbols.



74
75
76
# File 'lib/tina4/wsdl.rb', line 74

def wsdl_operation(output: {})
  @pending_wsdl_output = output
end

.wsdl_operationsObject

Registry of operations declared via wsdl_operation + def. Each entry: { input: { name => type, … }, output: { name => type, … } }



58
59
60
# File 'lib/tina4/wsdl.rb', line 58

def wsdl_operations
  @wsdl_operations ||= {}
end

Instance Method Details

#generate_wsdl(endpoint_url = "") ⇒ Object

── WSDL generation ──────────────────────────────────────────────────



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
# File 'lib/tina4/wsdl.rb', line 158

def generate_wsdl(endpoint_url = "")
  @service_url = endpoint_url unless endpoint_url.empty?
  service_name = self.class.name ? self.class.name.split("::").last : "AnonymousService"
  tns = "urn:#{service_name}"

  parts = []
  parts << '<?xml version="1.0" encoding="UTF-8"?>'
  parts << "<definitions name=\"#{service_name}\""
  parts << "  targetNamespace=\"#{tns}\""
  parts << "  xmlns:tns=\"#{tns}\""
  parts << "  xmlns:soap=\"#{NS_SOAP}\""
  parts << "  xmlns:xsd=\"#{NS_XSD}\""
  parts << "  xmlns=\"#{NS_WSDL}\">"
  parts << ""

  # Types
  parts << "  <types>"
  parts << "    <xsd:schema targetNamespace=\"#{tns}\">"

  @operations.each do |op_name, meta|
    # Request element
    parts << "      <xsd:element name=\"#{op_name}\">"
    parts << "        <xsd:complexType>"
    parts << "          <xsd:sequence>"
    meta[:input].each do |pname, ptype|
      xsd = xsd_type(ptype)
      parts << "            <xsd:element name=\"#{pname}\" type=\"#{xsd}\"/>"
    end
    parts << "          </xsd:sequence>"
    parts << "        </xsd:complexType>"
    parts << "      </xsd:element>"

    # Response element
    parts << "      <xsd:element name=\"#{op_name}Response\">"
    parts << "        <xsd:complexType>"
    parts << "          <xsd:sequence>"
    meta[:output].each do |rname, rtype|
      xsd = xsd_type(rtype)
      parts << "            <xsd:element name=\"#{rname}\" type=\"#{xsd}\"/>"
    end
    parts << "          </xsd:sequence>"
    parts << "        </xsd:complexType>"
    parts << "      </xsd:element>"
  end

  parts << "    </xsd:schema>"
  parts << "  </types>"
  parts << ""

  # Messages
  @operations.each_key do |op_name|
    parts << "  <message name=\"#{op_name}Input\">"
    parts << "    <part name=\"parameters\" element=\"tns:#{op_name}\"/>"
    parts << "  </message>"
    parts << "  <message name=\"#{op_name}Output\">"
    parts << "    <part name=\"parameters\" element=\"tns:#{op_name}Response\"/>"
    parts << "  </message>"
  end
  parts << ""

  # PortType
  parts << "  <portType name=\"#{service_name}PortType\">"
  @operations.each_key do |op_name|
    parts << "    <operation name=\"#{op_name}\">"
    parts << "      <input message=\"tns:#{op_name}Input\"/>"
    parts << "      <output message=\"tns:#{op_name}Output\"/>"
    parts << "    </operation>"
  end
  parts << "  </portType>"
  parts << ""

  # Binding
  parts << "  <binding name=\"#{service_name}Binding\" type=\"tns:#{service_name}PortType\">"
  parts << "    <soap:binding style=\"document\" transport=\"http://schemas.xmlsoap.org/soap/http\"/>"
  @operations.each_key do |op_name|
    parts << "    <operation name=\"#{op_name}\">"
    parts << "      <soap:operation soapAction=\"#{tns}/#{op_name}\"/>"
    parts << '      <input><soap:body use="literal"/></input>'
    parts << '      <output><soap:body use="literal"/></output>'
    parts << "    </operation>"
  end
  parts << "  </binding>"
  parts << ""

  # Service
  parts << "  <service name=\"#{service_name}\">"
  parts << "    <port name=\"#{service_name}Port\" binding=\"tns:#{service_name}Binding\">"
  parts << "      <soap:address location=\"#{@service_url}\"/>"
  parts << "    </port>"
  parts << "  </service>"

  parts << "</definitions>"
  parts.join("\n")
end

#handleObject

Main entry point. Returns WSDL XML on GET/?wsdl, or processes a SOAP request on POST.



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
# File 'lib/tina4/wsdl.rb', line 116

def handle
  return generate_wsdl if @request.nil?

  method = if @request.respond_to?(:method)
             @request.method.to_s.upcase
           elsif @request.respond_to?(:body) && @request.body && !@request.body.to_s.empty?
             "POST"
           else
             "GET"
           end

  params = (@request.respond_to?(:params) ? @request.params : nil) || {}
  url    = (@request.respond_to?(:url) ? @request.url : nil) || ""

  if method == "GET" || params.key?("wsdl") || params.key?(:wsdl) || url.end_with?("?wsdl")
    return generate_wsdl
  end

  body = if @request.respond_to?(:body)
           @request.body.is_a?(String) ? @request.body : @request.body.to_s
         else
           ""
         end

  process_soap(body)
end

#on_request(request) ⇒ Object

Called before operation invocation. Override to validate/log.



146
147
148
# File 'lib/tina4/wsdl.rb', line 146

def on_request(request)
  # no-op
end

#on_result(result) ⇒ Object

Called after operation returns. Override to transform/audit. Must return the (possibly modified) result.



152
153
154
# File 'lib/tina4/wsdl.rb', line 152

def on_result(result)
  result
end