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