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

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