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

     1  # Copyright (c) 2014-2024 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? && ["CoresPerNumaNode", "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          if slice?
   325            prefix = ""
   326            t = "#{pkg}ByteSlice"
   327          end
   328        when "double"
   329          t = "float64"
   330        when "float"
   331          t = "float32"
   332        when "short"
   333          t = "int16"
   334        when "base64Binary"
   335          t = "[]byte"
   336        when "anyURI"
   337          t = "string"
   338        else
   339          raise "unknown type: %s" % t
   340        end
   341      else
   342        pkg = ""
   343        if $target != self.ns
   344          pkg = "types."
   345        end
   346  
   347        t = vim_type
   348  
   349        if base_type?
   350          prefix += "#{pkg}Base"
   351        else
   352          t = pkg + t
   353          prefix += "*" if !slice? && !enum_type? && optional?
   354        end
   355      end
   356  
   357      prefix + t
   358    end
   359  
   360    def slice?
   361      test_attr("maxOccurs", "unbounded")
   362    end
   363  
   364    def optional?
   365      test_attr("minOccurs", "0")
   366    end
   367  
   368    def need_omitempty=(v)
   369      @need_omitempty = v
   370    end
   371  
   372    def json_omitempty=(v)
   373      @json_omitempty = v
   374    end
   375  
   376    def need_omitempty?
   377      var_type # HACK: trigger setting need_omitempty if necessary
   378      if @need_omitempty.nil?
   379        @need_omitempty = optional?
   380      else
   381        @need_omitempty
   382      end
   383    end
   384  
   385    def json_omitempty?
   386      var_type # HACK: trigger setting json_omitempty if necessary
   387      if @json_omitempty.nil?
   388        @json_omitempty = need_omitempty?
   389      else
   390        @json_omitempty
   391      end
   392    end
   393  
   394    def need_typeattr?
   395      base_type? || any_type?
   396    end
   397  
   398    protected
   399  
   400    def test_attr(attr, expected)
   401      actual = @node.attr(attr)
   402      if actual != nil
   403        case actual
   404        when expected
   405          true
   406        else
   407          raise "%s=%s" % [value, type.attr(value)]
   408        end
   409      else
   410        false
   411      end
   412    end
   413  end
   414  
   415  class Element < Simple
   416    def initialize(node, vijson)
   417      super(node, vijson)
   418    end
   419  
   420    def has_type?
   421      !@node["type"].nil?
   422    end
   423  
   424    def child
   425      cs = @node.element_children
   426      assert_equal 1, cs.length
   427      assert_equal "complexType", cs.first.name
   428  
   429      t = ComplexType.new(cs.first, @vijson)
   430      t.name = self.name
   431      t
   432    end
   433  
   434    def dump(io)
   435      if has_type?
   436        ucfirstName = ucfirst(name)
   437        if @vijson != nil
   438          if @vijson.has_key?(ucfirstName)
   439            if @vijson[ucfirstName].has_key?("description")
   440              @vijson[ucfirstName]["description"].each_line do |line|
   441                io.print "// #{sanitize_line(line)}"
   442              end
   443            end
   444          end
   445        end
   446        io.print "type %s %s\n\n" % [ucfirstName, var_type]
   447      else
   448        child.dump(io)
   449      end
   450    end
   451  
   452    def dump_init(io)
   453      if has_type?
   454        init_type io, name, name
   455      end
   456    end
   457  
   458    def dump_field(io, json_tag="", vijson_props=nil)
   459      xmlTag = name
   460      xmlTag += ",omitempty" if need_omitempty?
   461      xmlTag += ",typeattr" if need_typeattr?
   462      tag = "%s %s `xml:\"%s\"" % [var_name, var_type, xmlTag]
   463  
   464      jsonTag = ""
   465      if json_tag != ""
   466        jsonTag = json_tag # Caller-provided JSON tag
   467      elsif var_name == "This" && var_type == "ManagedObjectReference"
   468        jsonTag = "-"      # For marshal/unmarshal operations using a type
   469                           # discriminator
   470      else
   471        jsonTag = name
   472        jsonTag += ",omitempty" if json_omitempty?
   473      end
   474      tag += " json:\"%s\"" % [jsonTag]
   475  
   476      # Print the field's comments as well as determining whether or not the field
   477      # has a comment with a line that matches the following regex with a
   478      # capturing group to parse the API version:
   479      #
   480      #   ***Since:*** vSphere API (.+)$
   481      #
   482      # If the comments do contain this line, it will not be printed, instead the
   483      # captured version is added to the field's Go tags to persist the minimum
   484      # API version for the field.
   485      if vijson_props != nil
   486        if vijson_props.has_key?(name)
   487          if vijson_props[name].has_key?("description")
   488            comments = []
   489            vijson_props[name]["description"].each_line do |line|
   490              m = line.match(SINCE_API_FORMAT)
   491              if m == nil
   492                comments.append("// #{sanitize_line(line)}")
   493              else
   494                tag += " vim:\"%s\"" % [m[1]]
   495                comments.pop(1)
   496              end
   497            end
   498            io.print comments.join()
   499          end
   500        end
   501      end
   502  
   503      io.print "%s`\n" % [tag]
   504    end
   505  
   506    def peek(type=nil)
   507      if has_type?
   508        return if self.type =~ /^xsd:/
   509  
   510        Peek.ref(vim_type)
   511      else
   512        child.peek()
   513      end
   514    end
   515  end
   516  
   517  class Attribute < Simple
   518    def dump_field(io)
   519      xmlTag = name
   520      xmlTag += ",omitempty" if need_omitempty?
   521      xmlTag += ",attr"
   522      xmlTag += ",typeattr" if need_typeattr?
   523      tag = "%s %s `xml:\"%s\"" % [var_name, var_type, xmlTag]
   524  
   525      jsonTag = name
   526      jsonTag += ",omitempty" if json_omitempty?
   527      tag += " json:\"%s\"" % [jsonTag]
   528  
   529      io.print "%s`\n" % [tag]
   530    end
   531  end
   532  
   533  class SimpleType < Simple
   534    def is_enum?
   535      true
   536    end
   537  
   538    def dump(io)
   539      ucfirstName = ucfirst(name)
   540      posValCmnts = {}
   541      if @vijson != nil
   542        ucfirstNameEnum = ucfirstName + "_enum"
   543        if @vijson.has_key?(ucfirstNameEnum)
   544          if @vijson[ucfirstNameEnum].has_key?("description")
   545            comments = []
   546            posValCur = nil
   547            posValSectionActive = false
   548            @vijson[ucfirstNameEnum]["description"].each_line do |line|
   549              if line.match?(SINCE_API_FORMAT)
   550                comments.pop(1)
   551                if posValCur != nil
   552                  posValCmnts[posValCur].pop(1)
   553                end
   554              elsif line.start_with?("Possible values:")
   555                comments.pop(1)
   556                posValSectionActive = true
   557              elsif posValSectionActive
   558                if line == ""
   559                  comments.pop(1)
   560                  posValSectionActive = false
   561                else
   562                  m = line.match(POSSIBLE_VALUE_FORMAT)
   563                  if m != nil
   564                    posValCur = m[1]
   565                    if m[2] == nil
   566                      posValCmnts[posValCur] = []
   567                    elsif !line.match?(SINCE_API_FORMAT)
   568                      posValCmnts[posValCur] = ["// #{sanitize_line(m[2])}\n"]
   569                    end
   570                  else
   571                    line.sub!(/^\s{2}/, '')
   572                    if line.match?(SINCE_API_FORMAT)
   573                      posValCmnts[posValCur].pop(1)
   574                    else
   575                      posValCmnts[posValCur].append("// #{sanitize_line(line)}")
   576                    end
   577                  end
   578                end
   579              else
   580                comments.append("// #{sanitize_line(line)}")
   581              end
   582            end
   583            io.print comments.join()
   584          end
   585        end
   586      end
   587      io.print "type %s string\n\n" % ucfirstName
   588  
   589      enums = @node.xpath(".//xsd:enumeration").map do |n|
   590        comments = nil
   591        if posValCmnts.has_key?(n["value"])
   592          comments = posValCmnts[n["value"]].join()
   593        end
   594        EnumValue.new(self, n["value"], comments)
   595      end
   596  
   597      io.print "const (\n"
   598      enums.each { |e| e.dump(io) }
   599      io.print ")\n\n"
   600  
   601      io.print "func(e %1$s) Values() []%1$s {\n\treturn []%1$s{\n" % ucfirstName
   602      enums.each { |e| io.print("\t\t%s,\n" % e.var_name()) }
   603      io.print "\t}\n}\n\n"
   604  
   605      if $target == "vim25"
   606        io.print "func(e %1$s) Strings() []string {\n\treturn EnumValuesAsStrings(e.Values())\n}\n\n" % ucfirstName
   607      else
   608        io.print "func(e %1$s) Strings() []string {\n\treturn types.EnumValuesAsStrings(e.Values())\n}\n\n" % ucfirstName
   609      end
   610  
   611    end
   612  
   613    def dump_init(io)
   614      ucfirstName = ucfirst(name)
   615      minApiVersion = nil
   616      minApiVersionsForValues = {}
   617      if @vijson != nil
   618        ucfirstNameEnum = ucfirstName + "_enum"
   619        if @vijson.has_key?(ucfirstNameEnum)
   620          if @vijson[ucfirstNameEnum].has_key?("description")
   621            posValCur = nil
   622            posValSectionActive = false
   623            @vijson[ucfirstNameEnum]["description"].each_line do |line|
   624              m = line.match(SINCE_API_FORMAT)
   625              if m != nil
   626                minApiVersion = m[1]
   627              elsif line.start_with?("Possible values:")
   628                posValSectionActive = true
   629              elsif posValSectionActive
   630                if line == ""
   631                  posValSectionActive = false
   632                else
   633                  m = line.match(POSSIBLE_VALUE_FORMAT)
   634                  if m != nil
   635                    posValCur = m[1]
   636                    if m[2] != nil
   637                      m = m[2].match(SINCE_API_FORMAT)
   638                      if m != nil
   639                        minApiVersionsForValues[posValCur] = m[1]
   640                      end
   641                    end
   642                  else
   643                    line.sub!(/^\s{2}/, '')
   644                    m = line.match(SINCE_API_FORMAT)
   645                    if m != nil
   646                      minApiVersionsForValues[posValCur] = m[1]
   647                    end
   648                  end
   649                end
   650              end
   651            end
   652          end
   653        end
   654      end
   655  
   656      if minApiVersionsForValues.size() == 0
   657        minApiVersionsForValues = nil
   658      end
   659  
   660      init_type io, name, name, minApiVersion, minApiVersionsForValues
   661    end
   662  
   663    def peek
   664      Peek.enum(name)
   665    end
   666  end
   667  
   668  class ComplexType < Simple
   669    class SimpleContent < Simple
   670      def dump(io)
   671        attr = Attribute.new(@node.at_xpath(".//xsd:attribute"), @vijson)
   672        attr.dump_field(io)
   673  
   674        # HACK DELUXE(PN)
   675        extension = @node.at_xpath(".//xsd:extension")
   676        type = extension["base"].split(":", 2)[1]
   677        io.print "Value %s `xml:\",chardata\" json:\"value\"`\n" % type
   678        io.print "ServerGUID %s `xml:\"serverGuid,attr,omitempty\" json:\"serverGuid,omitempty\"`\n" % type
   679      end
   680  
   681      def peek
   682      end
   683    end
   684  
   685    class ComplexContent < Simple
   686      def base
   687        extension = @node.at_xpath(".//xsd:extension")
   688        assert_not_nil extension
   689  
   690        base = extension["base"]
   691        assert_not_nil base
   692  
   693        base
   694      end
   695  
   696      def dump(io)
   697        Sequence.new(@node, @vijson).dump(io, base)
   698      end
   699  
   700      def dump_interface(io, name)
   701        Sequence.new(@node, @vijson).dump_interface(io, name)
   702      end
   703  
   704      def peek
   705        Sequence.new(@node, @vijson).peek(vim_type(base))
   706      end
   707    end
   708  
   709    class Sequence < Simple
   710      attr_accessor :array_of
   711  
   712      def initialize(node, vijson, array_of=false)
   713        super(node, vijson)
   714        self.array_of = array_of
   715      end
   716  
   717      def sequence
   718        sequence = @node.at_xpath(".//xsd:sequence")
   719        if sequence != nil
   720          sequence.element_children.map do |n|
   721            Element.new(n, @vijson)
   722          end
   723        else
   724          nil
   725        end
   726      end
   727  
   728      def dump(io, base = nil)
   729        return unless elements = sequence
   730        if base != nil
   731          kind = vim_type(base)
   732  
   733          pkg = ""
   734          if $target != ns(base)
   735            pkg = "types."
   736          end
   737          io.print "#{pkg}#{kind}\n\n"
   738        end
   739  
   740        elements.each do |e|
   741          e.dump_field(io, json_tag=self.array_of ? "_value" : "", vijson_props=@vijson_props)
   742        end
   743      end
   744  
   745      def dump_interface(io, name)
   746        method = "Get%s() *%s" % [name, name]
   747        io.print "func (b *%s) %s { return b }\n" % [name, method]
   748        io.print "type Base%s interface {\n" % name
   749        io.print "%s\n" % method
   750        io.print "}\n\n"
   751        init_type io, "Base#{name}", name
   752      end
   753  
   754      def peek(base = nil)
   755        return unless elements = sequence
   756        name = @node.attr("name")
   757        return unless name
   758  
   759        elements.each do |e|
   760          e.peek(name)
   761        end
   762  
   763        c = Peek.register(name)
   764        if base
   765          c.parent = base
   766          Peek.register(c.parent).children << name
   767        end
   768      end
   769    end
   770  
   771    def klass
   772      @klass ||= begin
   773                   cs = @node.element_children
   774                   if !cs.empty?
   775                     assert_equal 1, cs.length
   776  
   777                     case cs.first.name
   778                     when "simpleContent"
   779                       SimpleContent.new(@node, @vijson)
   780                     when "complexContent"
   781                       ComplexContent.new(@node, @vijson)
   782                     when "sequence"
   783                       Sequence.new(@node, @vijson, self.name.start_with?("ArrayOf"))
   784                     else
   785                       raise "don't know what to do for element: %s..." % cs.first.name
   786                     end
   787                   end
   788                 end
   789    end
   790  
   791    def dump_init(io)
   792      minApiVersion = nil
   793      ucfirstName = ucfirst(name)
   794      if @vijson != nil
   795        if @vijson.has_key?(ucfirstName)
   796          if @vijson[ucfirstName].has_key?("description")
   797            @vijson[ucfirstName]["description"].each_line do |line|
   798              m = line.match(SINCE_API_FORMAT)
   799              if m != nil
   800                minApiVersion = m[1]
   801                break
   802              end
   803            end
   804          end
   805        end
   806      end
   807      init_type io, name, name, minApiVersion
   808    end
   809  
   810    def dump(io)
   811      ucfirstName = ucfirst(name)
   812      if @vijson != nil
   813        if @vijson.has_key?(ucfirstName)
   814          if @vijson[ucfirstName].has_key?("description")
   815            comments = []
   816            @vijson[ucfirstName]["description"].each_line do |line|
   817              if line.match?(SINCE_API_FORMAT)
   818                comments.pop(1)
   819              else
   820                comments.append("// #{sanitize_line(line)}")
   821              end
   822            end
   823            io.print comments.join()
   824          end
   825        end
   826      end
   827      io.print "type %s struct {\n" % ucfirstName
   828      klass.dump(io) if klass
   829      io.print "}\n\n"
   830    end
   831  
   832    def peek
   833      Peek.register(name).klass = klass
   834      klass.peek if klass
   835    end
   836  end
   837  
   838  class Schema
   839    include Test::Unit::Assertions
   840  
   841    attr_accessor :namespace
   842    attr_reader :vijson
   843  
   844    def initialize(xml, vijson)
   845      @xml = Nokogiri::XML.parse(xml)
   846      @vijson = vijson
   847      @namespace = @xml.root.attr("targetNamespace").split(":", 2)[1]
   848      @xml
   849    end
   850  
   851    # We have some assumptions about structure, make sure they hold.
   852    def validate_assumptions!
   853      # Every enumeration is part of a restriction
   854      @xml.xpath(".//xsd:enumeration").each do |n|
   855        assert_equal "restriction", n.parent.name
   856      end
   857  
   858      # See type == enum
   859      @xml.xpath(".//xsd:restriction").each do |n|
   860        # Every restriction has type xsd:string (it's an enum)
   861        assert_equal "xsd:string", n["base"]
   862  
   863        # Every restriction is part of a simpleType
   864        assert_equal "simpleType", n.parent.name
   865  
   866        # Every restriction is alone
   867        assert_equal 1, n.parent.element_children.size
   868      end
   869  
   870      # See type == complex_content
   871      @xml.xpath(".//xsd:complexContent").each do |n|
   872        # complexContent is child of complexType
   873        assert_equal "complexType", n.parent.name
   874  
   875      end
   876  
   877      # See type == complex_type
   878      @xml.xpath(".//xsd:complexType").each do |n|
   879        cc = n.element_children
   880  
   881        # OK to have an empty complexType
   882        next if cc.size == 0
   883  
   884        # Require 1 element otherwise
   885        assert_equal 1, cc.size
   886  
   887        case cc.first.name
   888        when "complexContent"
   889          # complexContent has 1 "extension" element
   890          cc = cc.first.element_children
   891          assert_equal 1, cc.size
   892          assert_equal "extension", cc.first.name
   893  
   894          # extension has 1 "sequence" element
   895          ec = cc.first.element_children
   896          assert_equal 1, ec.size
   897          assert_equal "sequence", ec.first.name
   898  
   899          # sequence has N "element" elements
   900          sc = ec.first.element_children
   901          assert sc.all? { |e| e.name == "element" }
   902        when "simpleContent"
   903          # simpleContent has 1 "extension" element
   904          cc = cc.first.element_children
   905          assert_equal 1, cc.size
   906          assert_equal "extension", cc.first.name
   907  
   908          # extension has 1 or more "attribute" elements
   909          ec = cc.first.element_children
   910          assert_not_equal 0, ec.size
   911          assert_equal "attribute", ec.first.name
   912        when "sequence"
   913          # sequence has N "element" elements
   914          sc = cc.first.element_children
   915          assert sc.all? { |e| e.name == "element" }
   916        else
   917          raise "unknown element: %s" % cc.first.name
   918        end
   919      end
   920  
   921      imports.each do |i|
   922        i.validate_assumptions!
   923      end
   924  
   925      includes.each do |i|
   926        i.validate_assumptions!
   927      end
   928    end
   929  
   930    def types
   931      return to_enum(:types) unless block_given?
   932  
   933      if $target != self.namespace
   934        return
   935      end
   936  
   937      imports.each do |i|
   938        i.types do |t|
   939          yield t
   940        end
   941      end
   942  
   943      includes.each do |i|
   944        i.types do |t|
   945          yield t
   946        end
   947      end
   948  
   949      @xml.root.children.each do |n|
   950        case n.class.to_s
   951        when "Nokogiri::XML::Text"
   952          next
   953        when "Nokogiri::XML::Element"
   954          case n.name
   955          when "include", "import"
   956            next
   957          when "element"
   958            e = Element.new(n, @vijson)
   959            if e.has_type? && e.vim_type?
   960              if e.ns == $target
   961                yield e
   962              end
   963            else
   964              yield e
   965            end
   966          when "simpleType"
   967            yield SimpleType.new(n, @vijson)
   968          when "complexType"
   969            yield ComplexType.new(n, @vijson)
   970          else
   971            raise "unknown child: %s" % n.name
   972          end
   973        else
   974          raise "unknown type: %s" % n.class
   975        end
   976      end
   977    end
   978  
   979    def imports
   980      @imports ||= @xml.root.xpath(".//xmlns:import").map do |n|
   981        Schema.new(WSDL.read(n["schemaLocation"]), @vijson)
   982      end
   983    end
   984  
   985    def includes
   986      @includes ||= @xml.root.xpath(".//xmlns:include").map do |n|
   987        Schema.new(WSDL.read(n["schemaLocation"]), @vijson)
   988      end
   989    end
   990  end
   991  
   992  
   993  class Operation
   994    include Test::Unit::Assertions
   995  
   996    def initialize(wsdl, operation_node)
   997      @wsdl = wsdl
   998      @operation_node = operation_node
   999    end
  1000  
  1001    def name
  1002      @operation_node["name"]
  1003    end
  1004  
  1005    def namespace
  1006      type = @operation_node.at_xpath("./xmlns:input").attr("message")
  1007      keep_ns(type)
  1008    end
  1009  
  1010    def remove_ns(x)
  1011      ns, x = x.split(":", 2)
  1012      if ! valid_ns? ns
  1013          raise
  1014      end
  1015      x
  1016    end
  1017  
  1018    def keep_ns(x)
  1019      ns, x = x.split(":", 2)
  1020      if ! valid_ns? ns
  1021          raise
  1022      end
  1023      ns
  1024    end
  1025  
  1026    def find_type_for(type)
  1027      type = remove_ns(type)
  1028  
  1029      message = @wsdl.message(type)
  1030      assert_not_nil message
  1031  
  1032      part = message.at_xpath("./xmlns:part")
  1033      assert_not_nil message
  1034  
  1035      remove_ns(part["element"])
  1036    end
  1037  
  1038    def input
  1039      type = @operation_node.at_xpath("./xmlns:input").attr("message")
  1040      find_type_for(type)
  1041    end
  1042  
  1043    def go_input
  1044      "types." + ucfirst(input)
  1045    end
  1046  
  1047    def output
  1048      type = @operation_node.at_xpath("./xmlns:output").attr("message")
  1049      find_type_for(type)
  1050    end
  1051  
  1052    def go_output
  1053      "types." + ucfirst(output)
  1054    end
  1055  
  1056    def dump(io)
  1057      func = ucfirst(name)
  1058      if namespace != "vim25"
  1059        tag = "urn:#{namespace} "
  1060      end
  1061      io.print <<EOS
  1062    type #{func}Body struct{
  1063      Req *#{go_input} `xml:"urn:#{namespace} #{input},omitempty"`
  1064      Res *#{go_output} `xml:"#{tag}#{output},omitempty"`
  1065      Fault_ *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"`
  1066    }
  1067  
  1068    func (b *#{func}Body) Fault() *soap.Fault { return b.Fault_ }
  1069  
  1070  EOS
  1071  
  1072      io.print "func %s(ctx context.Context, r soap.RoundTripper, req *%s) (*%s, error) {\n" % [func, go_input, go_output]
  1073      io.print <<EOS
  1074    var reqBody, resBody #{func}Body
  1075  
  1076    reqBody.Req = req
  1077  
  1078    if err := r.RoundTrip(ctx, &reqBody, &resBody); err != nil {
  1079      return nil, err
  1080    }
  1081  
  1082    return resBody.Res, nil
  1083  EOS
  1084  
  1085      io.print "}\n\n"
  1086    end
  1087  end
  1088  
  1089  class WSDL
  1090    attr_reader :xml
  1091    attr_reader :vijson
  1092  
  1093    PATH = File.expand_path("../sdk", __FILE__)
  1094  
  1095    def self.read(file)
  1096      File.open(File.join(PATH, file))
  1097    end
  1098  
  1099    def initialize(xml, vijson)
  1100      @xml = Nokogiri::XML.parse(xml)
  1101      @vijson = vijson
  1102      $target = @xml.root["targetNamespace"].split(":", 2)[1]
  1103  
  1104      unless $namespaces.include? $target
  1105        $namespaces.push $target
  1106      end
  1107    end
  1108  
  1109    def validate_assumptions!
  1110      schemas.each do |s|
  1111        s.validate_assumptions!
  1112      end
  1113    end
  1114  
  1115    def types(&blk)
  1116      return to_enum(:types) unless block_given?
  1117  
  1118      schemas.each do |s|
  1119        s.types(&blk)
  1120      end
  1121    end
  1122  
  1123    def schemas
  1124      @schemas ||= @xml.xpath('.//xmlns:types/xsd:schema').map do |n|
  1125        Schema.new(n.to_xml, @vijson)
  1126      end
  1127    end
  1128  
  1129    def operations
  1130      @operations ||= @xml.xpath('.//xmlns:portType/xmlns:operation').map do |o|
  1131        Operation.new(self, o)
  1132      end
  1133    end
  1134  
  1135    def message(type)
  1136      @messages ||= begin
  1137                      h = {}
  1138                      @xml.xpath('.//xmlns:message').each do |n|
  1139                        h[n.attr("name")] = n
  1140                      end
  1141                      h
  1142                    end
  1143  
  1144      @messages[type]
  1145    end
  1146  
  1147    def peek
  1148      types.
  1149        sort_by { |x| x.name }.
  1150        uniq { |x| x.name }.
  1151        each { |e| e.peek() }
  1152    end
  1153  
  1154    def self.header(name)
  1155      return <<EOF
  1156  /*
  1157  Copyright (c) #{ENV['COPYRIGHT_DATE_RANGE'] || '2014-2018'} VMware, Inc. All Rights Reserved.
  1158  
  1159  Licensed under the Apache License, Version 2.0 (the "License");
  1160  you may not use this file except in compliance with the License.
  1161  You may obtain a copy of the License at
  1162  
  1163      http://www.apache.org/licenses/LICENSE-2.0
  1164  
  1165  Unless required by applicable law or agreed to in writing, software
  1166  distributed under the License is distributed on an "AS IS" BASIS,
  1167  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  1168  See the License for the specific language governing permissions and
  1169  limitations under the License.
  1170  */
  1171  
  1172  package #{name}
  1173  
  1174  EOF
  1175    end
  1176  end