746 lines
15 KiB
Ruby
746 lines
15 KiB
Ruby
# Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
require "nokogiri"
|
|
require "test/unit"
|
|
|
|
class Peek
|
|
class Type
|
|
attr_accessor :parent, :children, :klass
|
|
|
|
def initialize(name)
|
|
@name = name
|
|
@children = []
|
|
end
|
|
|
|
def base?
|
|
return !children.empty?
|
|
end
|
|
end
|
|
|
|
@@types = {}
|
|
@@refs = {}
|
|
@@enums = {}
|
|
|
|
def self.types
|
|
return @@types
|
|
end
|
|
|
|
def self.refs
|
|
return @@refs
|
|
end
|
|
|
|
def self.enums
|
|
return @@enums
|
|
end
|
|
|
|
def self.ref(type)
|
|
refs[type] = true
|
|
end
|
|
|
|
def self.enum(type)
|
|
enums[type] = true
|
|
end
|
|
|
|
def self.enum?(type)
|
|
enums[type]
|
|
end
|
|
|
|
def self.register(name)
|
|
raise unless name
|
|
types[name] ||= Type.new(name)
|
|
end
|
|
|
|
def self.base?(name)
|
|
return unless c = types[name]
|
|
c.base?
|
|
end
|
|
|
|
def self.dump_interfaces(io)
|
|
types.keys.sort.each do |name|
|
|
next unless base?(name)
|
|
|
|
types[name].klass.dump_interface(io, name)
|
|
end
|
|
end
|
|
end
|
|
|
|
class EnumValue
|
|
def initialize(type, value)
|
|
@type = type
|
|
@value = value
|
|
end
|
|
|
|
def type_name
|
|
@type.name
|
|
end
|
|
|
|
def var_name
|
|
n = @type.name
|
|
v = var_value
|
|
if v == ""
|
|
n += "Null"
|
|
else
|
|
n += (v[0].capitalize + v[1..-1])
|
|
end
|
|
|
|
return n
|
|
end
|
|
|
|
def var_value
|
|
@value
|
|
end
|
|
|
|
def dump(io)
|
|
io.print "%s = %s(\"%s\")\n" % [var_name, type_name, var_value]
|
|
end
|
|
end
|
|
|
|
class Simple
|
|
include Test::Unit::Assertions
|
|
|
|
attr_accessor :name, :type
|
|
|
|
def initialize(node)
|
|
@node = node
|
|
end
|
|
|
|
def name
|
|
@name || @node["name"]
|
|
end
|
|
|
|
def type
|
|
@type || @node["type"]
|
|
end
|
|
|
|
def is_enum?
|
|
false
|
|
end
|
|
|
|
def dump_init(io)
|
|
# noop
|
|
end
|
|
|
|
def var_name
|
|
n = self.name
|
|
n = n[1..-1] if n[0] == "_" # Strip leading _
|
|
n = n[0].capitalize + n[1..-1] # Capitalize
|
|
return n
|
|
end
|
|
|
|
def vim_type?
|
|
ns, _ = self.type.split(":", 2)
|
|
ns == "vim25"
|
|
end
|
|
|
|
def vim_type(t = self.type)
|
|
ns, t = t.split(":", 2)
|
|
raise if ns != "vim25"
|
|
t
|
|
end
|
|
|
|
def base_type?
|
|
vim_type? && Peek.base?(vim_type)
|
|
end
|
|
|
|
def enum_type?
|
|
vim_type? && Peek.enum?(vim_type)
|
|
end
|
|
|
|
def any_type?
|
|
self.type == "xsd:anyType"
|
|
end
|
|
|
|
def var_type
|
|
t = self.type
|
|
prefix = ""
|
|
|
|
prefix += "[]" if slice?
|
|
|
|
if t =~ /^xsd:(.*)$/
|
|
t = $1
|
|
case t
|
|
when "string"
|
|
when "int"
|
|
when "boolean"
|
|
t = "bool"
|
|
if !slice? && optional?
|
|
prefix += "*"
|
|
self.need_omitempty = false
|
|
end
|
|
when "long"
|
|
t = "int64"
|
|
when "dateTime"
|
|
t = "time.Time"
|
|
if !slice? && optional?
|
|
prefix += "*"
|
|
self.need_omitempty = false
|
|
end
|
|
when "anyType"
|
|
t = "AnyType"
|
|
when "byte"
|
|
when "double"
|
|
t = "float64"
|
|
when "float"
|
|
t = "float32"
|
|
when "short"
|
|
t = "int16"
|
|
when "base64Binary"
|
|
t = "[]byte"
|
|
when "anyURI"
|
|
t = "url.URL"
|
|
else
|
|
raise "unknown type: %s" % t
|
|
end
|
|
else
|
|
t = vim_type
|
|
if base_type?
|
|
prefix += "Base"
|
|
else
|
|
prefix += "*" if !slice? && !enum_type? && optional?
|
|
end
|
|
end
|
|
|
|
prefix + t
|
|
end
|
|
|
|
def slice?
|
|
test_attr("maxOccurs", "unbounded")
|
|
end
|
|
|
|
def optional?
|
|
test_attr("minOccurs", "0")
|
|
end
|
|
|
|
def need_omitempty=(v)
|
|
@need_omitempty = v
|
|
end
|
|
|
|
def need_omitempty?
|
|
var_type # HACK: trigger setting need_omitempty if necessary
|
|
if @need_omitempty.nil?
|
|
@need_omitempty = optional?
|
|
else
|
|
@need_omitempty
|
|
end
|
|
end
|
|
|
|
def need_typeattr?
|
|
base_type? || any_type?
|
|
end
|
|
|
|
protected
|
|
|
|
def test_attr(attr, expected)
|
|
actual = @node.attr(attr)
|
|
if actual != nil
|
|
case actual
|
|
when expected
|
|
true
|
|
else
|
|
raise "%s=%s" % [value, type.attr(value)]
|
|
end
|
|
else
|
|
false
|
|
end
|
|
end
|
|
end
|
|
|
|
class Element < Simple
|
|
def initialize(node)
|
|
super(node)
|
|
end
|
|
|
|
def has_type?
|
|
!@node["type"].nil?
|
|
end
|
|
|
|
def child
|
|
cs = @node.element_children
|
|
assert_equal 1, cs.length
|
|
assert_equal "complexType", cs.first.name
|
|
|
|
t = ComplexType.new(cs.first)
|
|
t.name = self.name
|
|
t
|
|
end
|
|
|
|
def dump(io)
|
|
if has_type?
|
|
io.print "type %s %s\n\n" % [name, var_type]
|
|
else
|
|
child.dump(io)
|
|
end
|
|
end
|
|
|
|
def dump_init(io)
|
|
if has_type?
|
|
io.print "func init() {\n"
|
|
io.print "t[\"%s\"] = reflect.TypeOf((*%s)(nil)).Elem()\n" % [name, name]
|
|
io.print "}\n\n"
|
|
end
|
|
end
|
|
|
|
def dump_field(io)
|
|
tag = name
|
|
tag += ",omitempty" if need_omitempty?
|
|
tag += ",typeattr" if need_typeattr?
|
|
io.print "%s %s `xml:\"%s\"`\n" % [var_name, var_type, tag]
|
|
end
|
|
|
|
def peek(type=nil)
|
|
if has_type?
|
|
return if self.type =~ /^xsd:/
|
|
|
|
Peek.ref(vim_type)
|
|
else
|
|
child.peek()
|
|
end
|
|
end
|
|
end
|
|
|
|
class Attribute < Simple
|
|
def dump_field(io)
|
|
tag = name
|
|
tag += ",omitempty" if need_omitempty?
|
|
tag += ",attr"
|
|
io.print "%s %s `xml:\"%s\"`\n" % [var_name, var_type, tag]
|
|
end
|
|
end
|
|
|
|
class SimpleType < Simple
|
|
def is_enum?
|
|
true
|
|
end
|
|
|
|
def dump(io)
|
|
enums = @node.xpath(".//xsd:enumeration").map do |n|
|
|
EnumValue.new(self, n["value"])
|
|
end
|
|
|
|
io.print "type %s string\n\n" % name
|
|
io.print "const (\n"
|
|
enums.each { |e| e.dump(io) }
|
|
io.print ")\n\n"
|
|
end
|
|
|
|
def dump_init(io)
|
|
io.print "func init() {\n"
|
|
io.print "t[\"%s\"] = reflect.TypeOf((*%s)(nil)).Elem()\n" % [name, name]
|
|
io.print "}\n\n"
|
|
end
|
|
|
|
def peek
|
|
Peek.enum(name)
|
|
end
|
|
end
|
|
|
|
class ComplexType < Simple
|
|
class SimpleContent < Simple
|
|
def dump(io)
|
|
attr = Attribute.new(@node.at_xpath(".//xsd:attribute"))
|
|
attr.dump_field(io)
|
|
|
|
# HACK DELUXE(PN)
|
|
extension = @node.at_xpath(".//xsd:extension")
|
|
type = extension["base"].split(":", 2)[1]
|
|
io.print "Value %s `xml:\",chardata\"`\n" % type
|
|
end
|
|
|
|
def peek
|
|
end
|
|
end
|
|
|
|
class ComplexContent < Simple
|
|
def base
|
|
extension = @node.at_xpath(".//xsd:extension")
|
|
assert_not_nil extension
|
|
|
|
base = extension["base"]
|
|
assert_not_nil base
|
|
|
|
vim_type(base)
|
|
end
|
|
|
|
def dump(io)
|
|
Sequence.new(@node).dump(io, base)
|
|
end
|
|
|
|
def dump_interface(io, name)
|
|
Sequence.new(@node).dump_interface(io, name)
|
|
end
|
|
|
|
def peek
|
|
Sequence.new(@node).peek(base)
|
|
end
|
|
end
|
|
|
|
class Sequence < Simple
|
|
def sequence
|
|
sequence = @node.at_xpath(".//xsd:sequence")
|
|
if sequence != nil
|
|
sequence.element_children.map do |n|
|
|
Element.new(n)
|
|
end
|
|
else
|
|
nil
|
|
end
|
|
end
|
|
|
|
def dump(io, base = nil)
|
|
return unless elements = sequence
|
|
|
|
io.print "%s\n\n" % base
|
|
|
|
elements.each do |e|
|
|
e.dump_field(io)
|
|
end
|
|
end
|
|
|
|
def dump_interface(io, name)
|
|
method = "Get%s() *%s" % [name, name]
|
|
io.print "func (b *%s) %s { return b }\n" % [name, method]
|
|
io.print "type Base%s interface {\n" % name
|
|
io.print "%s\n" % method
|
|
io.print "}\n\n"
|
|
io.print "func init() {\n"
|
|
io.print "t[\"Base%s\"] = reflect.TypeOf((*%s)(nil)).Elem()\n" % [name, name]
|
|
io.print "}\n\n"
|
|
end
|
|
|
|
def peek(base = nil)
|
|
return unless elements = sequence
|
|
name = @node.attr("name")
|
|
return unless name
|
|
|
|
elements.each do |e|
|
|
e.peek(name)
|
|
end
|
|
|
|
c = Peek.register(name)
|
|
if base
|
|
c.parent = base
|
|
Peek.register(c.parent).children << name
|
|
end
|
|
end
|
|
end
|
|
|
|
def klass
|
|
@klass ||= begin
|
|
cs = @node.element_children
|
|
if !cs.empty?
|
|
assert_equal 1, cs.length
|
|
|
|
case cs.first.name
|
|
when "simpleContent"
|
|
SimpleContent.new(@node)
|
|
when "complexContent"
|
|
ComplexContent.new(@node)
|
|
when "sequence"
|
|
Sequence.new(@node)
|
|
else
|
|
raise "don't know what to do for element: %s..." % cs.first.name
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def dump_init(io)
|
|
io.print "func init() {\n"
|
|
io.print "t[\"%s\"] = reflect.TypeOf((*%s)(nil)).Elem()\n" % [name, name]
|
|
io.print "}\n\n"
|
|
end
|
|
|
|
def dump(io)
|
|
io.print "type %s struct {\n" % name
|
|
klass.dump(io) if klass
|
|
io.print "}\n\n"
|
|
end
|
|
|
|
def peek
|
|
Peek.register(name).klass = klass
|
|
klass.peek if klass
|
|
end
|
|
end
|
|
|
|
class Schema
|
|
include Test::Unit::Assertions
|
|
|
|
def initialize(xml, parent = nil)
|
|
@xml = Nokogiri::XML.parse(xml)
|
|
end
|
|
|
|
def targetNamespace
|
|
@xml.root["targetNamespace"]
|
|
end
|
|
|
|
# We have some assumptions about structure, make sure they hold.
|
|
def validate_assumptions!
|
|
# Every enumeration is part of a restriction
|
|
@xml.xpath(".//xsd:enumeration").each do |n|
|
|
assert_equal "restriction", n.parent.name
|
|
end
|
|
|
|
# See type == enum
|
|
@xml.xpath(".//xsd:restriction").each do |n|
|
|
# Every restriction has type xsd:string (it's an enum)
|
|
assert_equal "xsd:string", n["base"]
|
|
|
|
# Every restriction is part of a simpleType
|
|
assert_equal "simpleType", n.parent.name
|
|
|
|
# Every restriction is alone
|
|
assert_equal 1, n.parent.element_children.size
|
|
end
|
|
|
|
# See type == complex_content
|
|
@xml.xpath(".//xsd:complexContent").each do |n|
|
|
# complexContent is child of complexType
|
|
assert_equal "complexType", n.parent.name
|
|
|
|
end
|
|
|
|
# See type == complex_type
|
|
@xml.xpath(".//xsd:complexType").each do |n|
|
|
cc = n.element_children
|
|
|
|
# OK to have an empty complexType
|
|
next if cc.size == 0
|
|
|
|
# Require 1 element otherwise
|
|
assert_equal 1, cc.size
|
|
|
|
case cc.first.name
|
|
when "complexContent"
|
|
# complexContent has 1 "extension" element
|
|
cc = cc.first.element_children
|
|
assert_equal 1, cc.size
|
|
assert_equal "extension", cc.first.name
|
|
|
|
# extension has 1 "sequence" element
|
|
ec = cc.first.element_children
|
|
assert_equal 1, ec.size
|
|
assert_equal "sequence", ec.first.name
|
|
|
|
# sequence has N "element" elements
|
|
sc = ec.first.element_children
|
|
assert sc.all? { |e| e.name == "element" }
|
|
when "simpleContent"
|
|
# simpleContent has 1 "extension" element
|
|
cc = cc.first.element_children
|
|
assert_equal 1, cc.size
|
|
assert_equal "extension", cc.first.name
|
|
|
|
# extension has 1 or more "attribute" elements
|
|
ec = cc.first.element_children
|
|
assert_not_equal 0, ec.size
|
|
assert_equal "attribute", ec.first.name
|
|
when "sequence"
|
|
# sequence has N "element" elements
|
|
sc = cc.first.element_children
|
|
assert sc.all? { |e| e.name == "element" }
|
|
else
|
|
raise "unknown element: %s" % cc.first.name
|
|
end
|
|
end
|
|
|
|
includes.each do |i|
|
|
i.validate_assumptions!
|
|
end
|
|
end
|
|
|
|
def types
|
|
return to_enum(:types) unless block_given?
|
|
|
|
includes.each do |i|
|
|
i.types do |t|
|
|
yield t
|
|
end
|
|
end
|
|
|
|
@xml.root.children.each do |n|
|
|
case n.class.to_s
|
|
when "Nokogiri::XML::Text"
|
|
next
|
|
when "Nokogiri::XML::Element"
|
|
case n.name
|
|
when "include", "import"
|
|
next
|
|
when "element"
|
|
yield Element.new(n)
|
|
when "simpleType"
|
|
yield SimpleType.new(n)
|
|
when "complexType"
|
|
yield ComplexType.new(n)
|
|
else
|
|
raise "unknown child: %s" % n.name
|
|
end
|
|
else
|
|
raise "unknown type: %s" % n.class
|
|
end
|
|
end
|
|
end
|
|
|
|
def includes
|
|
@includes ||= @xml.root.xpath(".//xmlns:include").map do |n|
|
|
Schema.new(WSDL.read n["schemaLocation"])
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
class Operation
|
|
include Test::Unit::Assertions
|
|
|
|
def initialize(wsdl, operation_node)
|
|
@wsdl = wsdl
|
|
@operation_node = operation_node
|
|
end
|
|
|
|
def name
|
|
@operation_node["name"]
|
|
end
|
|
|
|
def remove_ns(x)
|
|
ns, x = x.split(":", 2)
|
|
assert_equal "vim25", ns
|
|
x
|
|
end
|
|
|
|
def find_type_for(type)
|
|
type = remove_ns(type)
|
|
|
|
message = @wsdl.message(type)
|
|
assert_not_nil message
|
|
|
|
part = message.at_xpath("./xmlns:part")
|
|
assert_not_nil message
|
|
|
|
remove_ns(part["element"])
|
|
end
|
|
|
|
def input
|
|
type = @operation_node.at_xpath("./xmlns:input").attr("message")
|
|
find_type_for(type)
|
|
end
|
|
|
|
def go_input
|
|
"types." + input
|
|
end
|
|
|
|
def output
|
|
type = @operation_node.at_xpath("./xmlns:output").attr("message")
|
|
find_type_for(type)
|
|
end
|
|
|
|
def go_output
|
|
"types." + output
|
|
end
|
|
|
|
def dump(io)
|
|
io.print <<EOS
|
|
type #{name}Body struct{
|
|
Req *#{go_input} `xml:"urn:vim25 #{input},omitempty"`
|
|
Res *#{go_output} `xml:"urn:vim25 #{output},omitempty"`
|
|
Fault_ *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"`
|
|
}
|
|
|
|
func (b *#{name}Body) Fault() *soap.Fault { return b.Fault_ }
|
|
|
|
EOS
|
|
|
|
io.print "func %s(ctx context.Context, r soap.RoundTripper, req *%s) (*%s, error) {\n" % [name, go_input, go_output]
|
|
io.print <<EOS
|
|
var reqBody, resBody #{name}Body
|
|
|
|
reqBody.Req = req
|
|
|
|
if err := r.RoundTrip(ctx, &reqBody, &resBody); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return resBody.Res, nil
|
|
EOS
|
|
|
|
io.print "}\n\n"
|
|
end
|
|
end
|
|
|
|
class WSDL
|
|
attr_reader :xml
|
|
|
|
PATH = File.expand_path("../sdk", __FILE__)
|
|
|
|
def self.read(file)
|
|
File.open(File.join(PATH, file))
|
|
end
|
|
|
|
def initialize(xml)
|
|
@xml = Nokogiri::XML.parse(xml)
|
|
end
|
|
|
|
def validate_assumptions!
|
|
schemas.each do |s|
|
|
s.validate_assumptions!
|
|
end
|
|
end
|
|
|
|
def types(&blk)
|
|
return to_enum(:types) unless block_given?
|
|
|
|
schemas.each do |s|
|
|
s.types(&blk)
|
|
end
|
|
end
|
|
|
|
def schemas
|
|
@schemas ||= @xml.xpath('.//xmlns:types/xsd:schema').map do |n|
|
|
Schema.new(n.to_xml)
|
|
end
|
|
end
|
|
|
|
def operations
|
|
@operations ||= @xml.xpath('.//xmlns:portType/xmlns:operation').map do |o|
|
|
Operation.new(self, o)
|
|
end
|
|
end
|
|
|
|
def message(type)
|
|
@messages ||= begin
|
|
h = {}
|
|
@xml.xpath('.//xmlns:message').each do |n|
|
|
h[n.attr("name")] = n
|
|
end
|
|
h
|
|
end
|
|
|
|
@messages[type]
|
|
end
|
|
|
|
def peek
|
|
types.
|
|
sort_by { |x| x.name }.
|
|
uniq { |x| x.name }.
|
|
select { |x| x.name[0] == x.name[0].upcase }. # Only capitalized methods for now...
|
|
each { |e| e.peek() }
|
|
end
|
|
|
|
def self.header(name)
|
|
return <<EOF
|
|
package #{name}
|
|
|
|
EOF
|
|
end
|
|
end
|