1# Copyright (c) 2014-2018 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 15require "nokogiri" 16require "test/unit" 17 18$namespaces = %w(vim25) 19 20def valid_ns?(t) 21 $namespaces.include?(t) 22end 23 24def ucfirst(v) 25 x = "ArrayOf" 26 if v.start_with?(x) 27 # example: ArrayOfvslmInfrastructureObjectPolicy -> ArrayOfVslm... 28 return x + ucfirst(v[x.length..-1]) 29 end 30 31 # example: vslmInfrastructureObjectPolicy -. VslmInfrastructureObjectPolicy 32 v[0].capitalize + v[1..-1] 33end 34 35def init_type(io, name, kind) 36 t = "reflect.TypeOf((*#{ucfirst kind})(nil)).Elem()" 37 38 io.print "func init() {\n" 39 40 if $target == "vim25" 41 io.print "t[\"#{name}\"] = #{t}\n" 42 else 43 unless name.start_with? "Base" 44 name = "#{$target}:#{name}" 45 end 46 io.print "types.Add(\"#{name}\", #{t})\n" 47 end 48 49 io.print "}\n\n" 50end 51 52class Peek 53 class Type 54 attr_accessor :parent, :children, :klass 55 56 def initialize(name) 57 @name = name 58 @children = [] 59 end 60 61 def base? 62 # VrpResourceAllocationInfo is removed in 6.7, so base will no longer generated 63 return false if @name == "ResourceAllocationInfo" 64 65 return !children.empty? 66 end 67 end 68 69 @@types = {} 70 @@refs = {} 71 @@enums = {} 72 73 def self.types 74 return @@types 75 end 76 77 def self.refs 78 return @@refs 79 end 80 81 def self.enums 82 return @@enums 83 end 84 85 def self.ref(type) 86 refs[type] = true 87 end 88 89 def self.enum(type) 90 enums[type] = true 91 end 92 93 def self.enum?(type) 94 enums[type] 95 end 96 97 def self.register(name) 98 raise unless name 99 types[name] ||= Type.new(name) 100 end 101 102 def self.base?(name) 103 return unless c = types[name] 104 c.base? 105 end 106 107 def self.dump_interfaces(io) 108 types.keys.sort.each do |name| 109 next unless base?(name) 110 klass = types[name].klass 111 klass.dump_interface(io, name) if klass 112 end 113 end 114end 115 116class EnumValue 117 def initialize(type, value) 118 @type = type 119 @value = value 120 end 121 122 def type_name 123 ucfirst(@type.name) 124 end 125 126 def var_name 127 n = ucfirst(@type.name) 128 v = var_value 129 if v == "" 130 n += "Null" 131 else 132 n += ucfirst(v) 133 end 134 135 return n 136 end 137 138 def var_value 139 @value 140 end 141 142 def dump(io) 143 io.print "%s = %s(\"%s\")\n" % [var_name, type_name, var_value] 144 end 145end 146 147class Simple 148 include Test::Unit::Assertions 149 150 attr_accessor :name, :type 151 152 def initialize(node) 153 @node = node 154 end 155 156 def name 157 @name || @node["name"] 158 end 159 160 def type 161 @type || @node["type"] 162 end 163 164 def is_enum? 165 false 166 end 167 168 def dump_init(io) 169 # noop 170 end 171 172 def var_name 173 n = self.name 174 n = n[1..-1] if n[0] == "_" # Strip leading _ 175 n = ucfirst(n) 176 return n 177 end 178 179 def ns(t = self.type) 180 t.split(":", 2)[0] 181 end 182 183 def vim_type? 184 valid_ns? ns 185 end 186 187 def vim_type(t = self.type) 188 ns, kind = t.split(":", 2) 189 if ! valid_ns? ns 190 raise 191 end 192 ucfirst(kind) 193 end 194 195 def base_type? 196 vim_type? && Peek.base?(vim_type) 197 end 198 199 def enum_type? 200 vim_type? && Peek.enum?(vim_type) 201 end 202 203 def any_type? 204 self.type == "xsd:anyType" 205 end 206 207 def pointer_type? 208 ["UnitNumber"].include?(var_name) or 209 optional? && ["OwnerId", "GroupId", "MaxWaitSeconds", "Reservation", "Limit", "OverheadLimit"].include?(var_name) 210 end 211 212 def var_type 213 t = self.type 214 prefix = "" 215 216 if slice? 217 prefix += "[]" 218 if ["AffinitySet"].include?(var_name) 219 self.need_omitempty = false 220 end 221 end 222 223 if t =~ /^xsd:(.*)$/ 224 t = $1 225 case t 226 when "string" 227 when "int" 228 if pointer_type? 229 prefix += "*" 230 self.need_omitempty = false 231 end 232 t = "int32" 233 when "boolean" 234 t = "bool" 235 if !slice? && optional? 236 prefix += "*" 237 self.need_omitempty = false 238 end 239 when "long" 240 if pointer_type? 241 prefix += "*" 242 self.need_omitempty = false 243 end 244 t = "int64" 245 when "dateTime" 246 t = "time.Time" 247 if !slice? && optional? 248 prefix += "*" 249 self.need_omitempty = false 250 end 251 when "anyType" 252 pkg = "" 253 if $target != "vim25" 254 pkg = "types." 255 end 256 t = "#{pkg}AnyType" 257 if ["Value", "Val"].include?(var_name) 258 self.need_omitempty = false 259 end 260 when "byte" 261 when "double" 262 t = "float64" 263 when "float" 264 t = "float32" 265 when "short" 266 t = "int16" 267 when "base64Binary" 268 t = "[]byte" 269 when "anyURI" 270 t = "string" 271 else 272 raise "unknown type: %s" % t 273 end 274 else 275 pkg = "" 276 if $target != self.ns 277 pkg = "types." 278 end 279 280 t = vim_type 281 282 if base_type? 283 prefix += "#{pkg}Base" 284 else 285 t = pkg + t 286 prefix += "*" if !slice? && !enum_type? && optional? 287 end 288 end 289 290 prefix + t 291 end 292 293 def slice? 294 test_attr("maxOccurs", "unbounded") 295 end 296 297 def optional? 298 test_attr("minOccurs", "0") 299 end 300 301 def need_omitempty=(v) 302 @need_omitempty = v 303 end 304 305 def need_omitempty? 306 var_type # HACK: trigger setting need_omitempty if necessary 307 if @need_omitempty.nil? 308 @need_omitempty = optional? 309 else 310 @need_omitempty 311 end 312 end 313 314 def need_typeattr? 315 base_type? || any_type? 316 end 317 318 protected 319 320 def test_attr(attr, expected) 321 actual = @node.attr(attr) 322 if actual != nil 323 case actual 324 when expected 325 true 326 else 327 raise "%s=%s" % [value, type.attr(value)] 328 end 329 else 330 false 331 end 332 end 333end 334 335class Element < Simple 336 def initialize(node) 337 super(node) 338 end 339 340 def has_type? 341 !@node["type"].nil? 342 end 343 344 def child 345 cs = @node.element_children 346 assert_equal 1, cs.length 347 assert_equal "complexType", cs.first.name 348 349 t = ComplexType.new(cs.first) 350 t.name = self.name 351 t 352 end 353 354 def dump(io) 355 if has_type? 356 io.print "type %s %s\n\n" % [ucfirst(name), var_type] 357 else 358 child.dump(io) 359 end 360 end 361 362 def dump_init(io) 363 if has_type? 364 init_type io, name, name 365 end 366 end 367 368 def dump_field(io) 369 tag = name 370 tag += ",omitempty" if need_omitempty? 371 tag += ",typeattr" if need_typeattr? 372 io.print "%s %s `xml:\"%s\"`\n" % [var_name, var_type, tag] 373 end 374 375 def peek(type=nil) 376 if has_type? 377 return if self.type =~ /^xsd:/ 378 379 Peek.ref(vim_type) 380 else 381 child.peek() 382 end 383 end 384end 385 386class Attribute < Simple 387 def dump_field(io) 388 tag = name 389 tag += ",omitempty" if need_omitempty? 390 tag += ",attr" 391 io.print "%s %s `xml:\"%s\"`\n" % [var_name, var_type, tag] 392 end 393end 394 395class SimpleType < Simple 396 def is_enum? 397 true 398 end 399 400 def dump(io) 401 enums = @node.xpath(".//xsd:enumeration").map do |n| 402 EnumValue.new(self, n["value"]) 403 end 404 405 io.print "type %s string\n\n" % ucfirst(name) 406 io.print "const (\n" 407 enums.each { |e| e.dump(io) } 408 io.print ")\n\n" 409 end 410 411 def dump_init(io) 412 init_type io, name, name 413 end 414 415 def peek 416 Peek.enum(name) 417 end 418end 419 420class ComplexType < Simple 421 class SimpleContent < Simple 422 def dump(io) 423 attr = Attribute.new(@node.at_xpath(".//xsd:attribute")) 424 attr.dump_field(io) 425 426 # HACK DELUXE(PN) 427 extension = @node.at_xpath(".//xsd:extension") 428 type = extension["base"].split(":", 2)[1] 429 io.print "Value %s `xml:\",chardata\"`\n" % type 430 end 431 432 def peek 433 end 434 end 435 436 class ComplexContent < Simple 437 def base 438 extension = @node.at_xpath(".//xsd:extension") 439 assert_not_nil extension 440 441 base = extension["base"] 442 assert_not_nil base 443 444 base 445 end 446 447 def dump(io) 448 Sequence.new(@node).dump(io, base) 449 end 450 451 def dump_interface(io, name) 452 Sequence.new(@node).dump_interface(io, name) 453 end 454 455 def peek 456 Sequence.new(@node).peek(vim_type(base)) 457 end 458 end 459 460 class Sequence < Simple 461 def sequence 462 sequence = @node.at_xpath(".//xsd:sequence") 463 if sequence != nil 464 sequence.element_children.map do |n| 465 Element.new(n) 466 end 467 else 468 nil 469 end 470 end 471 472 def dump(io, base = nil) 473 return unless elements = sequence 474 if base != nil 475 kind = vim_type(base) 476 477 pkg = "" 478 if $target != ns(base) 479 pkg = "types." 480 end 481 io.print "#{pkg}#{kind}\n\n" 482 end 483 484 elements.each do |e| 485 e.dump_field(io) 486 end 487 end 488 489 def dump_interface(io, name) 490 method = "Get%s() *%s" % [name, name] 491 io.print "func (b *%s) %s { return b }\n" % [name, method] 492 io.print "type Base%s interface {\n" % name 493 io.print "%s\n" % method 494 io.print "}\n\n" 495 init_type io, "Base#{name}", name 496 end 497 498 def peek(base = nil) 499 return unless elements = sequence 500 name = @node.attr("name") 501 return unless name 502 503 elements.each do |e| 504 e.peek(name) 505 end 506 507 c = Peek.register(name) 508 if base 509 c.parent = base 510 Peek.register(c.parent).children << name 511 end 512 end 513 end 514 515 def klass 516 @klass ||= begin 517 cs = @node.element_children 518 if !cs.empty? 519 assert_equal 1, cs.length 520 521 case cs.first.name 522 when "simpleContent" 523 SimpleContent.new(@node) 524 when "complexContent" 525 ComplexContent.new(@node) 526 when "sequence" 527 Sequence.new(@node) 528 else 529 raise "don't know what to do for element: %s..." % cs.first.name 530 end 531 end 532 end 533 end 534 535 def dump_init(io) 536 init_type io, name, name 537 end 538 539 def dump(io) 540 io.print "type %s struct {\n" % ucfirst(name) 541 klass.dump(io) if klass 542 io.print "}\n\n" 543 end 544 545 def peek 546 Peek.register(name).klass = klass 547 klass.peek if klass 548 end 549end 550 551class Schema 552 include Test::Unit::Assertions 553 554 attr_accessor :namespace 555 556 def initialize(xml) 557 @xml = Nokogiri::XML.parse(xml) 558 @namespace = @xml.root.attr("targetNamespace").split(":", 2)[1] 559 @xml 560 end 561 562 # We have some assumptions about structure, make sure they hold. 563 def validate_assumptions! 564 # Every enumeration is part of a restriction 565 @xml.xpath(".//xsd:enumeration").each do |n| 566 assert_equal "restriction", n.parent.name 567 end 568 569 # See type == enum 570 @xml.xpath(".//xsd:restriction").each do |n| 571 # Every restriction has type xsd:string (it's an enum) 572 assert_equal "xsd:string", n["base"] 573 574 # Every restriction is part of a simpleType 575 assert_equal "simpleType", n.parent.name 576 577 # Every restriction is alone 578 assert_equal 1, n.parent.element_children.size 579 end 580 581 # See type == complex_content 582 @xml.xpath(".//xsd:complexContent").each do |n| 583 # complexContent is child of complexType 584 assert_equal "complexType", n.parent.name 585 586 end 587 588 # See type == complex_type 589 @xml.xpath(".//xsd:complexType").each do |n| 590 cc = n.element_children 591 592 # OK to have an empty complexType 593 next if cc.size == 0 594 595 # Require 1 element otherwise 596 assert_equal 1, cc.size 597 598 case cc.first.name 599 when "complexContent" 600 # complexContent has 1 "extension" element 601 cc = cc.first.element_children 602 assert_equal 1, cc.size 603 assert_equal "extension", cc.first.name 604 605 # extension has 1 "sequence" element 606 ec = cc.first.element_children 607 assert_equal 1, ec.size 608 assert_equal "sequence", ec.first.name 609 610 # sequence has N "element" elements 611 sc = ec.first.element_children 612 assert sc.all? { |e| e.name == "element" } 613 when "simpleContent" 614 # simpleContent has 1 "extension" element 615 cc = cc.first.element_children 616 assert_equal 1, cc.size 617 assert_equal "extension", cc.first.name 618 619 # extension has 1 or more "attribute" elements 620 ec = cc.first.element_children 621 assert_not_equal 0, ec.size 622 assert_equal "attribute", ec.first.name 623 when "sequence" 624 # sequence has N "element" elements 625 sc = cc.first.element_children 626 assert sc.all? { |e| e.name == "element" } 627 else 628 raise "unknown element: %s" % cc.first.name 629 end 630 end 631 632 imports.each do |i| 633 i.validate_assumptions! 634 end 635 636 includes.each do |i| 637 i.validate_assumptions! 638 end 639 end 640 641 def types 642 return to_enum(:types) unless block_given? 643 644 if $target != self.namespace 645 return 646 end 647 648 imports.each do |i| 649 i.types do |t| 650 yield t 651 end 652 end 653 654 includes.each do |i| 655 i.types do |t| 656 yield t 657 end 658 end 659 660 @xml.root.children.each do |n| 661 case n.class.to_s 662 when "Nokogiri::XML::Text" 663 next 664 when "Nokogiri::XML::Element" 665 case n.name 666 when "include", "import" 667 next 668 when "element" 669 e = Element.new(n) 670 if e.has_type? && e.vim_type? 671 if e.ns == $target 672 yield e 673 end 674 else 675 yield e 676 end 677 when "simpleType" 678 yield SimpleType.new(n) 679 when "complexType" 680 yield ComplexType.new(n) 681 else 682 raise "unknown child: %s" % n.name 683 end 684 else 685 raise "unknown type: %s" % n.class 686 end 687 end 688 end 689 690 def imports 691 @imports ||= @xml.root.xpath(".//xmlns:import").map do |n| 692 Schema.new(WSDL.read n["schemaLocation"]) 693 end 694 end 695 696 def includes 697 @includes ||= @xml.root.xpath(".//xmlns:include").map do |n| 698 Schema.new(WSDL.read n["schemaLocation"]) 699 end 700 end 701end 702 703 704class Operation 705 include Test::Unit::Assertions 706 707 def initialize(wsdl, operation_node) 708 @wsdl = wsdl 709 @operation_node = operation_node 710 end 711 712 def name 713 @operation_node["name"] 714 end 715 716 def namespace 717 type = @operation_node.at_xpath("./xmlns:input").attr("message") 718 keep_ns(type) 719 end 720 721 def remove_ns(x) 722 ns, x = x.split(":", 2) 723 if ! valid_ns? ns 724 raise 725 end 726 x 727 end 728 729 def keep_ns(x) 730 ns, x = x.split(":", 2) 731 if ! valid_ns? ns 732 raise 733 end 734 ns 735 end 736 737 def find_type_for(type) 738 type = remove_ns(type) 739 740 message = @wsdl.message(type) 741 assert_not_nil message 742 743 part = message.at_xpath("./xmlns:part") 744 assert_not_nil message 745 746 remove_ns(part["element"]) 747 end 748 749 def input 750 type = @operation_node.at_xpath("./xmlns:input").attr("message") 751 find_type_for(type) 752 end 753 754 def go_input 755 "types." + ucfirst(input) 756 end 757 758 def output 759 type = @operation_node.at_xpath("./xmlns:output").attr("message") 760 find_type_for(type) 761 end 762 763 def go_output 764 "types." + ucfirst(output) 765 end 766 767 def dump(io) 768 func = ucfirst(name) 769 io.print <<EOS 770 type #{func}Body struct{ 771 Req *#{go_input} `xml:"urn:#{namespace} #{input},omitempty"` 772 Res *#{go_output} `xml:"urn:#{namespace} #{output},omitempty"` 773 Fault_ *soap.Fault `xml:"http://schemas.xmlsoap.org/soap/envelope/ Fault,omitempty"` 774 } 775 776 func (b *#{func}Body) Fault() *soap.Fault { return b.Fault_ } 777 778EOS 779 780 io.print "func %s(ctx context.Context, r soap.RoundTripper, req *%s) (*%s, error) {\n" % [func, go_input, go_output] 781 io.print <<EOS 782 var reqBody, resBody #{func}Body 783 784 reqBody.Req = req 785 786 if err := r.RoundTrip(ctx, &reqBody, &resBody); err != nil { 787 return nil, err 788 } 789 790 return resBody.Res, nil 791EOS 792 793 io.print "}\n\n" 794 end 795end 796 797class WSDL 798 attr_reader :xml 799 800 PATH = File.expand_path("../sdk", __FILE__) 801 802 def self.read(file) 803 File.open(File.join(PATH, file)) 804 end 805 806 def initialize(xml) 807 @xml = Nokogiri::XML.parse(xml) 808 $target = @xml.root["targetNamespace"].split(":", 2)[1] 809 810 unless $namespaces.include? $target 811 $namespaces.push $target 812 end 813 end 814 815 def validate_assumptions! 816 schemas.each do |s| 817 s.validate_assumptions! 818 end 819 end 820 821 def types(&blk) 822 return to_enum(:types) unless block_given? 823 824 schemas.each do |s| 825 s.types(&blk) 826 end 827 end 828 829 def schemas 830 @schemas ||= @xml.xpath('.//xmlns:types/xsd:schema').map do |n| 831 Schema.new(n.to_xml) 832 end 833 end 834 835 def operations 836 @operations ||= @xml.xpath('.//xmlns:portType/xmlns:operation').map do |o| 837 Operation.new(self, o) 838 end 839 end 840 841 def message(type) 842 @messages ||= begin 843 h = {} 844 @xml.xpath('.//xmlns:message').each do |n| 845 h[n.attr("name")] = n 846 end 847 h 848 end 849 850 @messages[type] 851 end 852 853 def peek 854 types. 855 sort_by { |x| x.name }. 856 uniq { |x| x.name }. 857 each { |e| e.peek() } 858 end 859 860 def self.header(name) 861 return <<EOF 862/* 863Copyright (c) 2014-2018 VMware, Inc. All Rights Reserved. 864 865Licensed under the Apache License, Version 2.0 (the "License"); 866you may not use this file except in compliance with the License. 867You may obtain a copy of the License at 868 869 http://www.apache.org/licenses/LICENSE-2.0 870 871Unless required by applicable law or agreed to in writing, software 872distributed under the License is distributed on an "AS IS" BASIS, 873WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 874See the License for the specific language governing permissions and 875limitations under the License. 876*/ 877 878package #{name} 879 880EOF 881 end 882end 883