github.com/vmware/govmomi@v0.37.2/gen/vim_wsdl.rb (about)

     1  # Copyright (c) 2014-2021 VMware, Inc. All Rights Reserved.
     2  #
     3  # Licensed under the Apache License, Version 2.0 (the "License");
     4  # you may not use this file except in compliance with the License.
     5  # You may obtain a copy of the License at
     6  #
     7  #     http://www.apache.org/licenses/LICENSE-2.0
     8  #
     9  # Unless required by applicable law or agreed to in writing, software
    10  # distributed under the License is distributed on an "AS IS" BASIS,
    11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  # See the License for the specific language governing permissions and
    13  # limitations under the License.
    14  
    15  require "nokogiri"
    16  require "test/unit"
    17  
    18  # SINCE_API_FORMAT is used to capture the minimum API version for which some API
    19  # symbol is valid.
    20  SINCE_API_FORMAT = /^\*\*\*Since:\*\*\* \w+? API (?:Release )?(.+)$/
    21  
    22  # ENCLOSED_BY_ASTERIK_FORMAT is used to capture words enclosed by a single
    23  # asterik on either side.
    24  ENCLOSED_BY_ASTERIK_FORMAT = /\*([^\s]+)\*/
    25  
    26  # POSSIBLE_VALUE_FORMAT is used to capture a possible enum value.
    27  POSSIBLE_VALUE_FORMAT = /^- `([^`]+?)`(?:: (.*))?$/
    28  
    29  $namespaces = %w(vim25)
    30  $force_base_interface_for_types = ENV['FORCE_BASE_INTERFACE_FOR_TYPES']
    31  
    32  def sanitize_line(line)
    33    line.gsub!("***Required privileges:***", "Required privileges:")
    34    line.gsub!(ENCLOSED_BY_ASTERIK_FORMAT, '`\1`')
    35    if line.start_with?("- ") || line.start_with?("  ")
    36      line = "    " + line
    37    end
    38    return line
    39  end
    40  
    41  def valid_ns?(t)
    42    $namespaces.include?(t)
    43  end
    44  
    45  def ucfirst(v)
    46    x = "ArrayOf"
    47    if v.start_with?(x)
    48      # example: ArrayOfvslmInfrastructureObjectPolicy -> ArrayOfVslm...
    49      return x + ucfirst(v[x.length..-1])
    50    end
    51  
    52    # example: vslmInfrastructureObjectPolicy -. VslmInfrastructureObjectPolicy
    53    v[0].capitalize + v[1..-1]
    54  end
    55  
    56  def init_type(io, name, kind, minApiVersion=nil, minApiVersionsForValues=nil)
    57    t = "reflect.TypeOf((*#{ucfirst kind})(nil)).Elem()"
    58  
    59    io.print "func init() {\n"
    60  
    61    if $target == "vim25"
    62      io.print "t[\"#{name}\"] = #{t}\n"
    63      if minApiVersion != nil
    64        io.print "minAPIVersionForType[\"#{name}\"] = \"#{minApiVersion}\"\n"
    65      end
    66      if minApiVersionsForValues != nil
    67        io.print "minAPIVersionForEnumValue[\"#{name}\"] = map[string]string{\n"
    68        minApiVersionsForValues.each do |k, v|
    69          io.print "\t\t\"#{k}\": \"#{v}\",\n"
    70        end
    71        io.print "}\n"
    72      end
    73    else
    74      unless name.start_with? "Base"
    75        name = "#{$target}:#{name}"
    76      end
    77      io.print "types.Add(\"#{name}\", #{t})\n"
    78      if minApiVersion != nil
    79        io.print "types.AddMinAPIVersionForType(\"#{name}\", \"#{minApiVersion}\")\n"
    80      end
    81      if minApiVersionsForValues != nil
    82        minApiVersionsForValues.each do |k, v|
    83          io.print "types.AddMinAPIVersionForEnumValue(\"#{name}\", \"#{k}\", \"#{v}\")\n"
    84        end
    85      end
    86    end
    87  
    88    io.print "}\n\n"
    89  end
    90  
    91  class Peek
    92    class Type
    93      attr_accessor :parent, :children, :klass
    94  
    95      def initialize(name)
    96        @name = name
    97        @children = []
    98      end
    99  
   100      def base?
   101        # VrpResourceAllocationInfo is removed in 6.7, so base will no longer generated
   102        return false if ["ResourceAllocationInfo", "FaultDomainId"].include?(@name)
   103  
   104        return !children.empty? || $force_base_interface_for_types.split(",").include?(@name)
   105      end
   106    end
   107  
   108    @@types = {}
   109    @@refs = {}
   110    @@enums = {}
   111  
   112    def self.types
   113      return @@types
   114    end
   115  
   116    def self.refs
   117      return @@refs
   118    end
   119  
   120    def self.enums
   121      return @@enums
   122    end
   123  
   124    def self.ref(type)
   125      refs[type] = true
   126    end
   127  
   128    def self.enum(type)
   129      enums[type] = true
   130    end
   131  
   132    def self.enum?(type)
   133      enums[type]
   134    end
   135  
   136    def self.register(name)
   137      raise unless name
   138      types[name] ||= Type.new(name)
   139    end
   140  
   141    def self.base?(name)
   142      return unless c = types[name]
   143      c.base?
   144    end
   145  
   146    def self.dump_interfaces(io)
   147      types.keys.sort.each do |name|
   148        next unless base?(name)
   149        klass = types[name].klass
   150        klass.dump_interface(io, name) if klass
   151      end
   152    end
   153  end
   154  
   155  class EnumValue
   156    attr_reader :comments
   157  
   158    def initialize(type, value, comments)
   159      @type = type
   160      @value = value
   161      @comments = comments
   162    end
   163  
   164    def type_name
   165      ucfirst(@type.name)
   166    end
   167  
   168    def var_name
   169      n = ucfirst(@type.name)
   170      v = var_value
   171      if v == ""
   172        n += "Null"
   173      else
   174        n += ucfirst(v)
   175      end
   176  
   177      return n
   178    end
   179  
   180    def var_value
   181      @value
   182    end
   183  
   184    def dump(io)
   185      if @comments
   186        io.print @comments
   187      end
   188      io.print "%s = %s(\"%s\")\n" % [var_name, type_name, var_value]
   189    end
   190  end
   191  
   192  class Simple
   193    include Test::Unit::Assertions
   194  
   195    attr_accessor :name, :type
   196    attr_reader :vijson, :vijson_props
   197  
   198    def initialize(node, vijson)
   199      @node = node
   200      @vijson = vijson
   201  
   202      if vijson != nil && name != nil
   203        ucfirstName = ucfirst(name)
   204        if vijson.has_key?(ucfirstName)
   205          if vijson[ucfirstName].has_key?("properties")
   206            @vijson_props = vijson[ucfirstName]["properties"]
   207          end
   208        end
   209      end
   210    end
   211  
   212    def name
   213      @name || @node["name"]
   214    end
   215  
   216    def type
   217      @type || @node["type"]
   218    end
   219  
   220    def is_enum?
   221      false
   222    end
   223  
   224    def dump_init(io)
   225      # noop
   226    end
   227  
   228    def var_name
   229      n = self.name
   230      n = n[1..-1] if n[0] == "_" # Strip leading _
   231      n = ucfirst(n)
   232      return n
   233    end
   234  
   235    def ns(t = self.type)
   236      t.split(":", 2)[0]
   237    end
   238  
   239    def vim_type?
   240      valid_ns? ns
   241    end
   242  
   243    def vim_type(t = self.type)
   244      ns, kind = t.split(":", 2)
   245      if ! valid_ns? ns
   246          raise
   247      end
   248      ucfirst(kind)
   249    end
   250  
   251    def base_type?
   252      vim_type? && (Peek.base?(vim_type) || $force_base_interface_for_types.split(",").include?(vim_type))
   253    end
   254  
   255    def enum_type?
   256      vim_type? && Peek.enum?(vim_type)
   257    end
   258  
   259    def any_type?
   260      self.type == "xsd:anyType"
   261    end
   262  
   263    def pointer_type?
   264      ["UnitNumber"].include?(var_name) or
   265        optional? && ["IpPoolId", "OwnerId", "GroupId", "MaxWaitSeconds", "Reservation", "Limit", "OverheadLimit", "ResourceReductionToToleratePercent"].include?(var_name)
   266    end
   267  
   268    def var_type
   269      t = self.type
   270      prefix = ""
   271  
   272      if slice?
   273        prefix += "[]"
   274        if ["AffinitySet"].include?(var_name)
   275          self.need_omitempty = false
   276        end
   277      end
   278  
   279      if t =~ /^xsd:(.*)$/
   280        t = $1
   281        case t
   282        when "string"
   283          if ["IpPoolName"].include?(var_name)
   284            self.need_omitempty = false
   285          end
   286        when "int"
   287          if pointer_type?
   288            prefix += "*"
   289            self.need_omitempty = false
   290            self.json_omitempty = true
   291          end
   292          t = "int32"
   293        when "boolean"
   294          t = "bool"
   295          if !slice? && optional?
   296            prefix += "*"
   297            self.need_omitempty = false
   298            self.json_omitempty = true
   299          end
   300        when "long"
   301          if pointer_type?
   302            prefix += "*"
   303            self.need_omitempty = false
   304            self.json_omitempty = true
   305          end
   306          t = "int64"
   307        when "dateTime"
   308          t = "time.Time"
   309          if !slice? && optional?
   310            prefix += "*"
   311            self.need_omitempty = false
   312            self.json_omitempty = true
   313          end
   314        when "anyType"
   315          pkg = ""
   316          if $target != "vim25"
   317            pkg = "types."
   318          end
   319          t = "#{pkg}AnyType"
   320          if ["Value", "Val"].include?(var_name)
   321            self.need_omitempty = false
   322          end
   323        when "byte"
   324        when "double"
   325          t = "float64"
   326        when "float"
   327          t = "float32"
   328        when "short"
   329          t = "int16"
   330        when "base64Binary"
   331          t = "[]byte"
   332        when "anyURI"
   333          t = "string"
   334        else
   335          raise "unknown type: %s" % t
   336        end
   337      else
   338        pkg = ""
   339        if $target != self.ns
   340          pkg = "types."
   341        end
   342  
   343        t = vim_type
   344  
   345        if base_type?
   346          prefix += "#{pkg}Base"
   347        else
   348          t = pkg + t
   349          prefix += "*" if !slice? && !enum_type? && optional?
   350        end
   351      end
   352  
   353      prefix + t
   354    end
   355  
   356    def slice?
   357      test_attr("maxOccurs", "unbounded")
   358    end
   359  
   360    def optional?
   361      test_attr("minOccurs", "0")
   362    end
   363  
   364    def need_omitempty=(v)
   365      @need_omitempty = v
   366    end
   367  
   368    def json_omitempty=(v)
   369      @json_omitempty = v
   370    end
   371  
   372    def need_omitempty?
   373      var_type # HACK: trigger setting need_omitempty if necessary
   374      if @need_omitempty.nil?
   375        @need_omitempty = optional?
   376      else
   377        @need_omitempty
   378      end
   379    end
   380  
   381    def json_omitempty?
   382      var_type # HACK: trigger setting json_omitempty if necessary
   383      if @json_omitempty.nil?
   384        @json_omitempty = need_omitempty?
   385      else
   386        @json_omitempty
   387      end
   388    end
   389  
   390    def need_typeattr?
   391      base_type? || any_type?
   392    end
   393  
   394    protected
   395  
   396    def test_attr(attr, expected)
   397      actual = @node.attr(attr)
   398      if actual != nil
   399        case actual
   400        when expected
   401          true
   402        else
   403          raise "%s=%s" % [value, type.attr(value)]
   404        end
   405      else
   406        false
   407      end
   408    end
   409  end
   410  
   411  class Element < Simple
   412    def initialize(node, vijson)
   413      super(node, vijson)
   414    end
   415  
   416    def has_type?
   417      !@node["type"].nil?
   418    end
   419  
   420    def child
   421      cs = @node.element_children
   422      assert_equal 1, cs.length
   423      assert_equal "complexType", cs.first.name
   424  
   425      t = ComplexType.new(cs.first, @vijson)
   426      t.name = self.name
   427      t
   428    end
   429  
   430    def dump(io)
   431      if has_type?
   432        ucfirstName = ucfirst(name)
   433        if @vijson != nil
   434          if @vijson.has_key?(ucfirstName)
   435            if @vijson[ucfirstName].has_key?("description")
   436              @vijson[ucfirstName]["description"].each_line do |line|
   437                io.print "// #{sanitize_line(line)}"
   438              end
   439            end
   440          end
   441        end
   442        io.print "type %s %s\n\n" % [ucfirstName, var_type]
   443      else
   444        child.dump(io)
   445      end
   446    end
   447  
   448    def dump_init(io)
   449      if has_type?
   450        init_type io, name, name
   451      end
   452    end
   453  
   454    def dump_field(io, json_tag="", vijson_props=nil)
   455      xmlTag = name
   456      xmlTag += ",omitempty" if need_omitempty?
   457      xmlTag += ",typeattr" if need_typeattr?
   458      tag = "%s %s `xml:\"%s\"" % [var_name, var_type, xmlTag]
   459  
   460      jsonTag = ""
   461      if json_tag != ""
   462        jsonTag = json_tag # Caller-provided JSON tag
   463      elsif var_name == "This" && var_type == "ManagedObjectReference"
   464        jsonTag = "-"      # For marshal/unmarshal operations using a type
   465                           # discriminator
   466      else
   467        jsonTag = name
   468        jsonTag += ",omitempty" if json_omitempty?
   469      end
   470      tag += " json:\"%s\"" % [jsonTag]
   471  
   472      # Print the field's comments as well as determining whether or not the field
   473      # has a comment with a line that matches the following regex with a
   474      # capturing group to parse the API version:
   475      #
   476      #   ***Since:*** vSphere API (.+)$
   477      #
   478      # If the comments do contain this line, it will not be printed, instead the
   479      # captured version is added to the field's Go tags to persist the minimum
   480      # API version for the field.
   481      if vijson_props != nil
   482        if vijson_props.has_key?(name)
   483          if vijson_props[name].has_key?("description")
   484            comments = []
   485            vijson_props[name]["description"].each_line do |line|
   486              m = line.match(SINCE_API_FORMAT)
   487              if m == nil
   488                comments.append("// #{sanitize_line(line)}")
   489              else
   490                tag += " vim:\"%s\"" % [m[1]]
   491                comments.pop(1)
   492              end
   493            end
   494            io.print comments.join()
   495          end
   496        end
   497      end
   498  
   499      io.print "%s`\n" % [tag]
   500    end
   501  
   502    def peek(type=nil)
   503      if has_type?
   504        return if self.type =~ /^xsd:/
   505  
   506        Peek.ref(vim_type)
   507      else
   508        child.peek()
   509      end
   510    end
   511  end
   512  
   513  class Attribute < Simple
   514    def dump_field(io)
   515      xmlTag = name
   516      xmlTag += ",omitempty" if need_omitempty?
   517      xmlTag += ",attr"
   518      xmlTag += ",typeattr" if need_typeattr?
   519      tag = "%s %s `xml:\"%s\"" % [var_name, var_type, xmlTag]
   520  
   521      jsonTag = name
   522      jsonTag += ",omitempty" if json_omitempty?
   523      tag += " json:\"%s\"" % [jsonTag]
   524  
   525      io.print "%s`\n" % [tag]
   526    end
   527  end
   528  
   529  class SimpleType < Simple
   530    def is_enum?
   531      true
   532    end
   533  
   534    def dump(io)
   535      ucfirstName = ucfirst(name)
   536      posValCmnts = {}
   537      if @vijson != nil
   538        ucfirstNameEnum = ucfirstName + "_enum"
   539        if @vijson.has_key?(ucfirstNameEnum)
   540          if @vijson[ucfirstNameEnum].has_key?("description")
   541            comments = []
   542            posValCur = nil
   543            posValSectionActive = false
   544            @vijson[ucfirstNameEnum]["description"].each_line do |line|
   545              if line.match?(SINCE_API_FORMAT)
   546                comments.pop(1)
   547                if posValCur != nil
   548                  posValCmnts[posValCur].pop(1)
   549                end
   550              elsif line.start_with?("Possible values:")
   551                comments.pop(1)
   552                posValSectionActive = true
   553              elsif posValSectionActive
   554                if line == ""
   555                  comments.pop(1)
   556                  posValSectionActive = false
   557                else
   558                  m = line.match(POSSIBLE_VALUE_FORMAT)
   559                  if m != nil
   560                    posValCur = m[1]
   561                    if m[2] == nil
   562                      posValCmnts[posValCur] = []
   563                    elsif !line.match?(SINCE_API_FORMAT)
   564                      posValCmnts[posValCur] = ["// #{sanitize_line(m[2])}\n"]
   565                    end
   566                  else
   567                    line.sub!(/^\s{2}/, '')
   568                    if line.match?(SINCE_API_FORMAT)
   569                      posValCmnts[posValCur].pop(1)
   570                    else
   571                      posValCmnts[posValCur].append("// #{sanitize_line(line)}")
   572                    end
   573                  end
   574                end
   575              else
   576                comments.append("// #{sanitize_line(line)}")
   577              end
   578            end
   579            io.print comments.join()
   580          end
   581        end
   582      end
   583      io.print "type %s string\n\n" % ucfirstName
   584  
   585      enums = @node.xpath(".//xsd:enumeration").map do |n|
   586        comments = nil
   587        if posValCmnts.has_key?(n["value"])
   588          comments = posValCmnts[n["value"]].join()
   589        end
   590        EnumValue.new(self, n["value"], comments)
   591      end
   592  
   593      io.print "const (\n"
   594      enums.each { |e| e.dump(io) }
   595      io.print ")\n\n"
   596    end
   597  
   598    def dump_init(io)
   599      ucfirstName = ucfirst(name)
   600      minApiVersion = nil
   601      minApiVersionsForValues = {}
   602      if @vijson != nil
   603        ucfirstNameEnum = ucfirstName + "_enum"
   604        if @vijson.has_key?(ucfirstNameEnum)
   605          if @vijson[ucfirstNameEnum].has_key?("description")
   606            posValCur = nil
   607            posValSectionActive = false
   608            @vijson[ucfirstNameEnum]["description"].each_line do |line|
   609              m = line.match(SINCE_API_FORMAT)
   610              if m != nil
   611                minApiVersion = m[1]
   612              elsif line.start_with?("Possible values:")
   613                posValSectionActive = true
   614              elsif posValSectionActive
   615                if line == ""
   616                  posValSectionActive = false
   617                else
   618                  m = line.match(POSSIBLE_VALUE_FORMAT)
   619                  if m != nil
   620                    posValCur = m[1]
   621                    if m[2] != nil
   622                      m = m[2].match(SINCE_API_FORMAT)
   623                      if m != nil
   624                        minApiVersionsForValues[posValCur] = m[1]
   625                      end
   626                    end
   627                  else
   628                    line.sub!(/^\s{2}/, '')
   629                    m = line.match(SINCE_API_FORMAT)
   630                    if m != nil
   631                      minApiVersionsForValues[posValCur] = m[1]
   632                    end
   633                  end
   634                end
   635              end
   636            end
   637          end
   638        end
   639      end
   640  
   641      if minApiVersionsForValues.size() == 0
   642        minApiVersionsForValues = nil
   643      end
   644  
   645      init_type io, name, name, minApiVersion, minApiVersionsForValues
   646    end
   647  
   648    def peek
   649      Peek.enum(name)
   650    end
   651  end
   652  
   653  class ComplexType < Simple
   654    class SimpleContent < Simple
   655      def dump(io)
   656        attr = Attribute.new(@node.at_xpath(".//xsd:attribute"), @vijson)
   657        attr.dump_field(io)
   658  
   659        # HACK DELUXE(PN)
   660        extension = @node.at_xpath(".//xsd:extension")
   661        type = extension["base"].split(":", 2)[1]
   662        io.print "Value %s `xml:\",chardata\" json:\"value\"`\n" % type
   663        io.print "ServerGUID %s `xml:\"serverGuid,attr,omitempty\" json:\"serverGuid,omitempty\"`\n" % type
   664      end
   665  
   666      def peek
   667      end
   668    end
   669  
   670    class ComplexContent < Simple
   671      def base
   672        extension = @node.at_xpath(".//xsd:extension")
   673        assert_not_nil extension
   674  
   675        base = extension["base"]
   676        assert_not_nil base
   677  
   678        base
   679      end
   680  
   681      def dump(io)
   682        Sequence.new(@node, @vijson).dump(io, base)
   683      end
   684  
   685      def dump_interface(io, name)
   686        Sequence.new(@node, @vijson).dump_interface(io, name)
   687      end
   688  
   689      def peek
   690        Sequence.new(@node, @vijson).peek(vim_type(base))
   691      end
   692    end
   693  
   694    class Sequence < Simple
   695      attr_accessor :array_of
   696  
   697      def initialize(node, vijson, array_of=false)
   698        super(node, vijson)
   699        self.array_of = array_of
   700      end
   701  
   702      def sequence
   703        sequence = @node.at_xpath(".//xsd:sequence")
   704        if sequence != nil
   705          sequence.element_children.map do |n|
   706            Element.new(n, @vijson)
   707          end
   708        else
   709          nil
   710        end
   711      end
   712  
   713      def dump(io, base = nil)
   714        return unless elements = sequence
   715        if base != nil
   716          kind = vim_type(base)
   717  
   718          pkg = ""
   719          if $target != ns(base)
   720            pkg = "types."
   721          end
   722          io.print "#{pkg}#{kind}\n\n"
   723        end
   724  
   725        elements.each do |e|
   726          e.dump_field(io, json_tag=self.array_of ? "_value" : "", vijson_props=@vijson_props)
   727        end
   728      end
   729  
   730      def dump_interface(io, name)
   731        method = "Get%s() *%s" % [name, name]
   732        io.print "func (b *%s) %s { return b }\n" % [name, method]
   733        io.print "type Base%s interface {\n" % name
   734        io.print "%s\n" % method
   735        io.print "}\n\n"
   736        init_type io, "Base#{name}", name
   737      end
   738  
   739      def peek(base = nil)
   740        return unless elements = sequence
   741        name = @node.attr("name")
   742        return unless name
   743  
   744        elements.each do |e|
   745          e.peek(name)
   746        end
   747  
   748        c = Peek.register(name)
   749        if base
   750          c.parent = base
   751          Peek.register(c.parent).children << name
   752        end
   753      end
   754    end
   755  
   756    def klass
   757      @klass ||= begin
   758                   cs = @node.element_children
   759                   if !cs.empty?
   760                     assert_equal 1, cs.length
   761  
   762                     case cs.first.name
   763                     when "simpleContent"
   764                       SimpleContent.new(@node, @vijson)
   765                     when "complexContent"
   766                       ComplexContent.new(@node, @vijson)
   767                     when "sequence"
   768                       Sequence.new(@node, @vijson, self.name.start_with?("ArrayOf"))
   769                     else
   770                       raise "don't know what to do for element: %s..." % cs.first.name
   771                     end
   772                   end
   773                 end
   774    end
   775  
   776    def dump_init(io)
   777      minApiVersion = nil
   778      ucfirstName = ucfirst(name)
   779      if @vijson != nil
   780        if @vijson.has_key?(ucfirstName)
   781          if @vijson[ucfirstName].has_key?("description")
   782            @vijson[ucfirstName]["description"].each_line do |line|
   783              m = line.match(SINCE_API_FORMAT)
   784              if m != nil
   785                minApiVersion = m[1]
   786                break
   787              end
   788            end
   789          end
   790        end
   791      end
   792      init_type io, name, name, minApiVersion
   793    end
   794  
   795    def dump(io)
   796      ucfirstName = ucfirst(name)
   797      if @vijson != nil
   798        if @vijson.has_key?(ucfirstName)
   799          if @vijson[ucfirstName].has_key?("description")
   800            comments = []
   801            @vijson[ucfirstName]["description"].each_line do |line|
   802              if line.match?(SINCE_API_FORMAT)
   803                comments.pop(1)
   804              else
   805                comments.append("// #{sanitize_line(line)}")
   806              end
   807            end
   808            io.print comments.join()
   809          end
   810        end
   811      end
   812      io.print "type %s struct {\n" % ucfirstName
   813      klass.dump(io) if klass
   814      io.print "}\n\n"
   815    end
   816  
   817    def peek
   818      Peek.register(name).klass = klass
   819      klass.peek if klass
   820    end
   821  end
   822  
   823  class Schema
   824    include Test::Unit::Assertions
   825  
   826    attr_accessor :namespace
   827    attr_reader :vijson
   828  
   829    def initialize(xml, vijson)
   830      @xml = Nokogiri::XML.parse(xml)
   831      @vijson = vijson
   832      @namespace = @xml.root.attr("targetNamespace").split(":", 2)[1]
   833      @xml
   834    end
   835  
   836    # We have some assumptions about structure, make sure they hold.
   837    def validate_assumptions!
   838      # Every enumeration is part of a restriction
   839      @xml.xpath(".//xsd:enumeration").each do |n|
   840        assert_equal "restriction", n.parent.name
   841      end
   842  
   843      # See type == enum
   844      @xml.xpath(".//xsd:restriction").each do |n|
   845        # Every restriction has type xsd:string (it's an enum)
   846        assert_equal "xsd:string", n["base"]
   847  
   848        # Every restriction is part of a simpleType
   849        assert_equal "simpleType", n.parent.name
   850  
   851        # Every restriction is alone
   852        assert_equal 1, n.parent.element_children.size
   853      end
   854  
   855      # See type == complex_content
   856      @xml.xpath(".//xsd:complexContent").each do |n|
   857        # complexContent is child of complexType
   858        assert_equal "complexType", n.parent.name
   859  
   860      end
   861  
   862      # See type == complex_type
   863      @xml.xpath(".//xsd:complexType").each do |n|
   864        cc = n.element_children
   865  
   866        # OK to have an empty complexType
   867        next if cc.size == 0
   868  
   869        # Require 1 element otherwise
   870        assert_equal 1, cc.size
   871  
   872        case cc.first.name
   873        when "complexContent"
   874          # complexContent has 1 "extension" element
   875          cc = cc.first.element_children
   876          assert_equal 1, cc.size
   877          assert_equal "extension", cc.first.name
   878  
   879          # extension has 1 "sequence" element
   880          ec = cc.first.element_children
   881          assert_equal 1, ec.size
   882          assert_equal "sequence", ec.first.name
   883  
   884          # sequence has N "element" elements
   885          sc = ec.first.element_children
   886          assert sc.all? { |e| e.name == "element" }
   887        when "simpleContent"
   888          # simpleContent has 1 "extension" element
   889          cc = cc.first.element_children
   890          assert_equal 1, cc.size
   891          assert_equal "extension", cc.first.name
   892  
   893          # extension has 1 or more "attribute" elements
   894          ec = cc.first.element_children
   895          assert_not_equal 0, ec.size
   896          assert_equal "attribute", ec.first.name
   897        when "sequence"
   898          # sequence has N "element" elements
   899          sc = cc.first.element_children
   900          assert sc.all? { |e| e.name == "element" }
   901        else
   902          raise "unknown element: %s" % cc.first.name
   903        end
   904      end
   905  
   906      imports.each do |i|
   907        i.validate_assumptions!
   908      end
   909  
   910      includes.each do |i|
   911        i.validate_assumptions!
   912      end
   913    end
   914  
   915    def types
   916      return to_enum(:types) unless block_given?
   917  
   918      if $target != self.namespace
   919        return
   920      end
   921  
   922      imports.each do |i|
   923        i.types do |t|
   924          yield t
   925        end
   926      end
   927  
   928      includes.each do |i|
   929        i.types do |t|
   930          yield t
   931        end
   932      end
   933  
   934      @xml.root.children.each do |n|
   935        case n.class.to_s
   936        when "Nokogiri::XML::Text"
   937          next
   938        when "Nokogiri::XML::Element"
   939          case n.name
   940          when "include", "import"
   941            next
   942          when "element"
   943            e = Element.new(n, @vijson)
   944            if e.has_type? && e.vim_type?
   945              if e.ns == $target
   946                yield e
   947              end
   948            else
   949              yield e
   950            end
   951          when "simpleType"
   952            yield SimpleType.new(n, @vijson)
   953          when "complexType"
   954            yield ComplexType.new(n, @vijson)
   955          else
   956            raise "unknown child: %s" % n.name
   957          end
   958        else
   959          raise "unknown type: %s" % n.class
   960        end
   961      end
   962    end
   963  
   964    def imports
   965      @imports ||= @xml.root.xpath(".//xmlns:import").map do |n|
   966        Schema.new(WSDL.read(n["schemaLocation"]), @vijson)
   967      end
   968    end
   969  
   970    def includes
   971      @includes ||= @xml.root.xpath(".//xmlns:include").map do |n|
   972        Schema.new(WSDL.read(n["schemaLocation"]), @vijson)
   973      end
   974    end
   975  end
   976  
   977  
   978  class Operation
   979    include Test::Unit::Assertions
   980  
   981    def initialize(wsdl, operation_node)
   982      @wsdl = wsdl
   983      @operation_node = operation_node
   984    end
   985  
   986    def name
   987      @operation_node["name"]
   988    end
   989  
   990    def namespace
   991      type = @operation_node.at_xpath("./xmlns:input").attr("message")
   992      keep_ns(type)
   993    end
   994  
   995    def remove_ns(x)
   996      ns, x = x.split(":", 2)
   997      if ! valid_ns? ns
   998          raise
   999      end
  1000      x
  1001    end
  1002  
  1003    def keep_ns(x)
  1004      ns, x = x.split(":", 2)
  1005      if ! valid_ns? ns
  1006          raise
  1007      end
  1008      ns
  1009    end
  1010  
  1011    def find_type_for(type)
  1012      type = remove_ns(type)
  1013  
  1014      message = @wsdl.message(type)
  1015      assert_not_nil message
  1016  
  1017      part = message.at_xpath("./xmlns:part")
  1018      assert_not_nil message
  1019  
  1020      remove_ns(part["element"])
  1021    end
  1022  
  1023    def input
  1024      type = @operation_node.at_xpath("./xmlns:input").attr("message")
  1025      find_type_for(type)
  1026    end
  1027  
  1028    def go_input
  1029      "types." + ucfirst(input)
  1030    end
  1031  
  1032    def output
  1033      type = @operation_node.at_xpath("./xmlns:output").attr("message")
  1034      find_type_for(type)
  1035    end
  1036  
  1037    def go_output
  1038      "types." + ucfirst(output)
  1039    end
  1040  
  1041    def dump(io)
  1042      func = ucfirst(name)
  1043      if namespace != "vim25"
  1044        tag = "urn:#{namespace} "
  1045      end
  1046      io.print <<EOS
  1047    type #{func}Body struct{
  1048      Req *#{go_input} `xml:"urn:#{namespace} #{input},omitempty"`
  1049      Res *#{go_output} `xml:"#{tag}#{output},omitempty"`
  1050      Fault_ *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"`
  1051    }
  1052  
  1053    func (b *#{func}Body) Fault() *soap.Fault { return b.Fault_ }
  1054  
  1055  EOS
  1056  
  1057      io.print "func %s(ctx context.Context, r soap.RoundTripper, req *%s) (*%s, error) {\n" % [func, go_input, go_output]
  1058      io.print <<EOS
  1059    var reqBody, resBody #{func}Body
  1060  
  1061    reqBody.Req = req
  1062  
  1063    if err := r.RoundTrip(ctx, &reqBody, &resBody); err != nil {
  1064      return nil, err
  1065    }
  1066  
  1067    return resBody.Res, nil
  1068  EOS
  1069  
  1070      io.print "}\n\n"
  1071    end
  1072  end
  1073  
  1074  class WSDL
  1075    attr_reader :xml
  1076    attr_reader :vijson
  1077  
  1078    PATH = File.expand_path("../sdk", __FILE__)
  1079  
  1080    def self.read(file)
  1081      File.open(File.join(PATH, file))
  1082    end
  1083  
  1084    def initialize(xml, vijson)
  1085      @xml = Nokogiri::XML.parse(xml)
  1086      @vijson = vijson
  1087      $target = @xml.root["targetNamespace"].split(":", 2)[1]
  1088  
  1089      unless $namespaces.include? $target
  1090        $namespaces.push $target
  1091      end
  1092    end
  1093  
  1094    def validate_assumptions!
  1095      schemas.each do |s|
  1096        s.validate_assumptions!
  1097      end
  1098    end
  1099  
  1100    def types(&blk)
  1101      return to_enum(:types) unless block_given?
  1102  
  1103      schemas.each do |s|
  1104        s.types(&blk)
  1105      end
  1106    end
  1107  
  1108    def schemas
  1109      @schemas ||= @xml.xpath('.//xmlns:types/xsd:schema').map do |n|
  1110        Schema.new(n.to_xml, @vijson)
  1111      end
  1112    end
  1113  
  1114    def operations
  1115      @operations ||= @xml.xpath('.//xmlns:portType/xmlns:operation').map do |o|
  1116        Operation.new(self, o)
  1117      end
  1118    end
  1119  
  1120    def message(type)
  1121      @messages ||= begin
  1122                      h = {}
  1123                      @xml.xpath('.//xmlns:message').each do |n|
  1124                        h[n.attr("name")] = n
  1125                      end
  1126                      h
  1127                    end
  1128  
  1129      @messages[type]
  1130    end
  1131  
  1132    def peek
  1133      types.
  1134        sort_by { |x| x.name }.
  1135        uniq { |x| x.name }.
  1136        each { |e| e.peek() }
  1137    end
  1138  
  1139    def self.header(name)
  1140      return <<EOF
  1141  /*
  1142  Copyright (c) #{ENV['COPYRIGHT_DATE_RANGE'] || '2014-2018'} VMware, Inc. All Rights Reserved.
  1143  
  1144  Licensed under the Apache License, Version 2.0 (the "License");
  1145  you may not use this file except in compliance with the License.
  1146  You may obtain a copy of the License at
  1147  
  1148      http://www.apache.org/licenses/LICENSE-2.0
  1149  
  1150  Unless required by applicable law or agreed to in writing, software
  1151  distributed under the License is distributed on an "AS IS" BASIS,
  1152  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1153  See the License for the specific language governing permissions and
  1154  limitations under the License.
  1155  */
  1156  
  1157  package #{name}
  1158  
  1159  EOF
  1160    end
  1161  end