Class: Tina4::WSDL
- Inherits:
-
Object
- Object
- Tina4::WSDL
- 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
-
#request ⇒ Object
readonly
── Instance ─────────────────────────────────────────────────────────.
-
#service_url ⇒ Object
readonly
── Instance ─────────────────────────────────────────────────────────.
Class Method Summary collapse
-
.inherited(subclass) ⇒ Object
Ensure subclasses get their own copy of the operations registry.
-
.method_added(method_name) ⇒ Object
Hook into method definition to capture the pending operation.
-
.pending_wsdl_output ⇒ Object
Pending output hash waiting for the next method definition.
-
.wsdl_operation(output: {}) ⇒ Object
Mark the next defined method as a WSDL operation.
-
.wsdl_operations ⇒ Object
Registry of operations declared via wsdl_operation + def.
Instance Method Summary collapse
-
#generate_wsdl(endpoint_url = "") ⇒ Object
── WSDL generation ──────────────────────────────────────────────────.
-
#handle ⇒ Object
Main entry point.
-
#initialize(request = nil, service_url: "") ⇒ WSDL
constructor
A new instance of WSDL.
-
#on_request(request) ⇒ Object
Called before operation invocation.
-
#on_result(result) ⇒ Object
Called after operation returns.
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
#request ⇒ Object (readonly)
── Instance ─────────────────────────────────────────────────────────
106 107 108 |
# File 'lib/tina4/wsdl.rb', line 106 def request @request end |
#service_url ⇒ Object (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_output ⇒ Object
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_operations ⇒ Object
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, | # Request element parts << " <xsd:element name=\"#{op_name}\">" parts << " <xsd:complexType>" parts << " <xsd:sequence>" [: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>" [: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 |
#handle ⇒ Object
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 |