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