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



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

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



111
112
113
# File 'lib/soaspec/wsdl_generator.rb', line 111

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



105
106
107
# File 'lib/soaspec/wsdl_generator.rb', line 105

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



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

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



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

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



70
71
72
73
74
# File 'lib/soaspec/wsdl_generator.rb', line 70

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



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

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
46
47
# File 'lib/soaspec/wsdl_generator.rb', line 9

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

  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



78
79
80
# File 'lib/soaspec/wsdl_generator.rb', line 78

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

#name_of_wsdlObject

Prompt user for Service name for wsdl



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

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



117
118
119
120
121
122
# File 'lib/soaspec/wsdl_generator.rb', line 117

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



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

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



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

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 ‘:’



85
86
87
# File 'lib/soaspec/wsdl_generator.rb', line 85

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



172
173
174
175
176
# File 'lib/soaspec/wsdl_generator.rb', line 172

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

  list.each { |element| document_type_for element }
end