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