Module: Soaspec::WsdlGenerator

Includes:
ExeHelpers
Defined in:
lib/soaspec/wsdl_generator.rb

Overview

Produce test content from a WSDL

Instance Method Summary collapse

Methods included from ExeHelpers

#class_content, #create_file, #create_files, #create_files_for, #create_folder, #generated_soap_spec_for, #retrieve_contents, #spec_task

Instance Method Details

#ask_wsdlObject

Prompt user for wsdl



177
178
179
180
181
182
183
184
# File 'lib/soaspec/wsdl_generator.rb', line 177

def ask_wsdl
  prompt = <<-WSDL_LOC
  Enter WSDL:
  WSDL_LOC
  print prompt.chop
  @wsdl = $stdin.gets.strip
  puts
end

#camel_case(underscore_separated) ⇒ String

Returns CamelCased value.

Parameters:

  • underscore_separated (String, Symbol)

    Snakecase value to be converted to camel case

Returns:

  • (String)

    CamelCased value



109
110
111
# File 'lib/soaspec/wsdl_generator.rb', line 109

def camel_case(underscore_separated)
  underscore_separated.to_s.split('_').collect(&:capitalize).join
end

#complex_type?(element) ⇒ Boolean

Returns True if Nokogiri element is a complex type, that is, has a complexType element underneath itself.

Parameters:

  • element (Nokogiri::XML::Element)

    Element to check

Returns:

  • (Boolean)

    True if Nokogiri element is a complex type, that is, has a complexType element underneath itself



103
104
105
# File 'lib/soaspec/wsdl_generator.rb', line 103

def complex_type?(element)
  element.children.any? { |child| name_after_namespace(child) == 'complexType' }
end

#document_type_for(element, depth = 1) ⇒ Object

Adds documentation content for all children of XML element This will be an example yaml for setting SOAP requests

Parameters:

  • element (Nokogiri::XML::Element)

    Type to document for

  • depth (Integer) (defaults to: 1)

    How many times to iterate depth for



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/soaspec/wsdl_generator.rb', line 146

def document_type_for(element, depth = 1)
  # raise "Too far deep for #{element}" unless depth < 6
  return unless depth < 6

  if complex_type? element
    complex_type = element.children.find { |c| name_after_namespace(c) == 'complexType' }
    sequence = complex_type.children.find { |c| name_after_namespace(c) == 'sequence' }
    sequence.children.select { |node| node.class == Nokogiri::XML::Element }.each do |sub_element|
      document_type_for sub_element, depth + 1
    end
  else
    return "No type seen for #{element}, #{element.class}" unless element['type']

    name = element['name']
    type = value_after_namespace(element['type'])
    @use_camel_case = true unless /[[:upper:]]/.match(name[0]).nil?
    spaces = '  ' * depth
    @content += "#{spaces}#{name.snakecase}: #{fill_in_field_from_type(type)} # #{type} \n"
  end
end

#enter_auth_detailsArray

Prompt user to enter basic auth details

Returns:

  • (Array)

    Array with Basic auth username and password entered



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/soaspec/wsdl_generator.rb', line 198

def enter_auth_details
  prompt = <<-AUTH_PROMPT
  User Name:
  AUTH_PROMPT
  print prompt.chop
  auth_name = $stdin.gets.strip
  puts

  prompt = <<-PASSWORD
  User Password:
  PASSWORD
  print prompt.chop
  auth_password = $stdin.gets.strip
  puts
  [auth_name, auth_password]
end

#enumeration?(type) ⇒ Boolean

Returns Whether WSDL type is an enumeration.

Parameters:

  • type (Nokogiri::XML::NodeSet)

    WSDL element type

Returns:

  • (Boolean)

    Whether WSDL type is an enumeration



68
69
70
71
72
# File 'lib/soaspec/wsdl_generator.rb', line 68

def enumeration?(type)
  return false unless type.first

  !type.xpath("*/#{type.first.namespace.prefix}:enumeration").empty?
end

#fill_in_field_from_type(type) ⇒ Object

Based on WSDL type return a valid value

Parameters:

  • type (String)

    Type without the WSDL

Returns:

  • (Object)

    Value representing type to fill in



90
91
92
93
94
95
96
97
98
99
# File 'lib/soaspec/wsdl_generator.rb', line 90

def fill_in_field_from_type(type)
  case type
  when 'string' then options[:string_default] # 'test string'
  when 'int' then 2
  when 'boolean' then true
  when 'double' then '1.5'
  else
    try_enum_for type
  end
end

#generate_from_wsdl(options) ⇒ Object

Generate from WSDL



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/soaspec/wsdl_generator.rb', line 9

def generate_from_wsdl(options)
  auth_name, auth_password = enter_auth_details if options[:auth] == 'basic'
  @virtual = false
  savon_options = { wsdl: options[:wsdl] }
  savon_options[:basic_auth] = [auth_name, auth_password] if options[:auth] == 'basic'

  wsdl_doc = Savon.client(**savon_options).wsdl
  @wsdl_schemas = wsdl_doc.parser.schemas
  # Create basic project files
  create_files %w[Rakefile Gemfile README.md spec/spec_helper.rb], ignore_if_present: true
  create_file(filename: '.rspec')
  create_file(filename: '.travis.yml') if options[:ci] == 'travis'
  create_folder 'logs'
  create_file filename: "lib/#{options[:name].snakecase}.rb", content: class_content

  # Files according to WSDL
  wsdl_doc.operations.each do |operation, op_details|
    puts "Creating files for operation: #{operation}"
    @content = "default:\n"
    @use_camel_case = false
    puts 'Message params: ' + op_details.to_s
    # From namespace identifier, find namespace, and for that find schemaLocation xsd and use that to build request
    op_details[:parameters]&.each do |element, details|
      @use_camel_case = true unless /[[:upper:]]/.match(element.to_s[0]).nil?
      @content += "  #{element.to_s.snakecase}: #{fill_in_field_from_type(details[:type])} # #{details[:type]} \n"
      # TODO: If details is a Hash need to loop again
    end
    wsdl_to_yaml_for root_elements_for(op_details)
    params = []
    params << 'convert_request_keys_to: :camelcase' if @use_camel_case
    params_string = params == [] ? '' : ', ' + params.join(', ')
    @class_params = "'#{camel_case(operation)}'#{params_string}"

    create_file(filename: "config/data/#{operation}.yml", content: @content)
    create_file(filename: "spec/#{operation}_spec.rb", content: generated_soap_spec_for(operation))
  end
end

#name_after_namespace(element) ⇒ String

Returns Name of element excluding namespace.

Parameters:

  • element (Nokogiri::XML::Element)

Returns:

  • (String)

    Name of element excluding namespace



76
77
78
# File 'lib/soaspec/wsdl_generator.rb', line 76

def name_after_namespace(element)
  value_after_namespace(element.name)
end

#name_of_wsdlObject

Prompt user for Service name for wsdl



187
188
189
190
191
192
193
194
# File 'lib/soaspec/wsdl_generator.rb', line 187

def name_of_wsdl
  prompt = <<-WSDL_NAME
  Enter what you would like to name WSDL (CamelCase):
  WSDL_NAME
  print prompt.chop
  @name = $stdin.gets.strip
  puts
end

#root_element_for(op_details) ⇒ Object

Element at the root of an operation

Parameters:

  • op_details (Hash)

    Hash with details from WSDL for an operation



115
116
117
118
119
120
# File 'lib/soaspec/wsdl_generator.rb', line 115

def root_element_for(op_details)
  root_element = @wsdl_schemas.at_xpath("//*[@name='#{op_details[:input]}']")
  raise 'Operation has no input defined' if root_element.nil?

  root_element
end

#root_elements_for(op_details) ⇒ Nokogiri::XML::NodeSet

Returns list of elements present at the root of an operation

Parameters:

  • op_details (Hash)

    Hash with details from WSDL for an operation

Returns:

  • (Nokogiri::XML::NodeSet)

    List of the root elements in the SOAP body



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/soaspec/wsdl_generator.rb', line 125

def root_elements_for(op_details)
  raise "'@wsdl_schemas' must be defined" if @wsdl_schemas.nil?

  root_element = root_element_for(op_details)
  schema_namespace = root_element.namespace.prefix
  root_type = root_element['type']
  if root_type
    @wsdl_schemas.xpath("//*[@name='#{value_after_namespace(root_type)}']//#{schema_namespace}:element")
  else
    return [] unless complex_type? root_element # Empty Array if there are no root elements

    complex_type = root_element.children.find { |c| c.name == 'complexType' }
    sequence = complex_type.children.find { |c| c.name == 'sequence' }
    sequence.xpath("#{schema_namespace}:element")
  end
end

#try_enum_for(type) ⇒ Object

Attempt to calculate values of enumeration by looking up type in Schema

Parameters:

  • type (String)

    Try out filling enumeration for type. Return Custom Type if can’t be done



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/soaspec/wsdl_generator.rb', line 49

def try_enum_for(type)
  raise "'@wsdl_schemas' must be defined" if @wsdl_schemas.nil?

  custom_type = @wsdl_schemas.xpath("//*[@name='#{type}']")
  if enumeration? custom_type
    prefix = custom_type.first.namespace.prefix
    enumerations = custom_type.xpath("//#{prefix}:enumeration")
    return 'Custom Type' if enumerations.empty?

    @enums_values = []
    enumerations.each { |enum_value| @enums_values << "'#{enum_value['value']}'" }
    "~randomize [#{@enums_values.join(', ')}]"
  else
    'Custom Type'
  end
end

#value_after_namespace(string) ⇒ String

Return value of string after a namespace

Parameters:

  • string (String)

    String to parse for part after namespace

Returns:

  • (String)

    Part after the namespace, demonstrated by ‘:’



83
84
85
# File 'lib/soaspec/wsdl_generator.rb', line 83

def value_after_namespace(string)
  string.split(':').last
end

#wsdl_to_yaml_for(list) ⇒ Object

TODO:

This should return YAML rather than just set an instance variable

Makes a yaml string in a ‘@content’ instance variable

Parameters:

  • list (Nokogiri::XML::NodeSet)

    List to convert to YAML



170
171
172
173
174
# File 'lib/soaspec/wsdl_generator.rb', line 170

def wsdl_to_yaml_for(list)
  raise "'@content' string must be set" if @content.nil?

  list.each { |element| document_type_for element }
end