genio-parser 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ require 'active_support/all'
2
+ require 'uri'
3
+ require 'open-uri'
4
+
5
+ module Genio
6
+ module Parser
7
+ module Format
8
+ class Base
9
+ include Logging
10
+
11
+ attr_accessor :files, :services, :data_types, :enum_types, :options, :endpoint
12
+
13
+ def initialize(options = {})
14
+ @options = options
15
+
16
+ @files = Types::Base.new( "#" => "self" )
17
+ @services = Types::Base.new
18
+ @data_types = Types::Base.new
19
+ @enum_types = Types::Base.new
20
+ end
21
+
22
+ def to_iodocs
23
+ IODocs.to_iodocs(self)
24
+ end
25
+
26
+ def open(file, options = {})
27
+ options[:ssl_verify_mode] ||= 0
28
+ super(file, options)
29
+ end
30
+
31
+ def load_files
32
+ @load_files ||= []
33
+ end
34
+
35
+ def expand_path(file)
36
+ if load_files.any? and file !~ /^(\/|https?:\/\/)/
37
+ parent_file = load_files.last
38
+ if parent_file =~ /^https?:/
39
+ file = URI.join(parent_file, file).to_s
40
+ else
41
+ file = File.expand_path(file, File.dirname(parent_file))
42
+ end
43
+ end
44
+ file
45
+ end
46
+
47
+ def read_file(file, &block)
48
+ file = expand_path(file)
49
+ load_files.push(file)
50
+ logger.info("GET #{file}")
51
+ block.call(open(file).read)
52
+ ensure
53
+ load_files.pop
54
+ end
55
+
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,97 @@
1
+ module Genio
2
+ module Parser
3
+ module Format
4
+ class IODocs < Base
5
+
6
+ class << self
7
+
8
+ def to_iodocs(schema)
9
+ { "endpoints" => schema.services.map{|name, service| service_to_iodocs(name, service, schema) } }
10
+ end
11
+
12
+ def service_to_iodocs(name, service, schema)
13
+ { "name" => name,
14
+ "methods" => service.operations.map{|name, operation| operation_to_iodocs(name, operation, schema) } }
15
+ end
16
+
17
+ URIPropertyName = /{([^}]+)}/
18
+ def operation_to_iodocs(name, operation, schema)
19
+ data = {
20
+ "Name" => name,
21
+ "HTTPMethod" => operation.type,
22
+ "URI" => operation.path.gsub(URIPropertyName,':\1') ,
23
+ "Required" => "Y",
24
+ "Type" => "complex",
25
+ "Parameters" => [],
26
+ "Description" => operation.description || "" }
27
+ if operation.parameters
28
+ data["Parameters"] =
29
+ operation.parameters.map do |name, property|
30
+ property_to_iodocs(name, property.merge( :required => (property.location == "path") ), schema)
31
+ end
32
+ end
33
+ operation.path.scan(URIPropertyName) do |name|
34
+ if operation.parameters.nil? or operation.parameters[$1].nil?
35
+ property = Types::Property.new(:type => "string", :required => true)
36
+ data["Parameters"] << property_to_iodocs($1, property, schema)
37
+ end
38
+ end
39
+ if operation.request_property
40
+ property = operation.request_property.merge( :required => true )
41
+ parameter = property_to_iodocs(property.type, property, schema)
42
+ data["Parameters"] += [ parameter ]
43
+ data["RequestContentType"] = "application/json"
44
+ end
45
+ data
46
+ end
47
+
48
+ def members_loaded
49
+ @members ||= {}
50
+ end
51
+
52
+ def members_for_data_type(data_type, schema)
53
+ return [] if members_loaded[data_type]
54
+ members_loaded[data_type] = true
55
+ members = []
56
+ if data_type.extends and schema.data_types[data_type.extends]
57
+ members += members_for_data_type(schema.data_types[data_type.extends], schema)
58
+ end
59
+ data_type.properties.each{|name, property|
60
+ unless property.readonly
61
+ members.push(property_to_iodocs(name, property, schema))
62
+ end
63
+ }
64
+ members_loaded.delete(data_type)
65
+ members
66
+ end
67
+
68
+ def property_to_iodocs(name, property, schema)
69
+ if property.attribute and schema.options[:attribute]
70
+ name = "@#{name}"
71
+ elsif property.package and schema.options[:namespace]
72
+ name = "#{property.package}:#{name}"
73
+ end
74
+ data = {
75
+ "Name" => name,
76
+ "Type" => property.type,
77
+ "ValidatedClass" => property.type,
78
+ }
79
+ data["Description"] = property.description if property.description
80
+ data["Required"] = "Y" if property.required == true
81
+ data["Default"] = property.default unless property.default.nil?
82
+ if property.enum
83
+ data["Type"] = "enumerated"
84
+ data["EnumeratedList"] = property.enum
85
+ elsif schema.data_types[property.type]
86
+ data["Type"] = "complex"
87
+ data["Members"] = members_for_data_type(schema.data_types[property.type], schema)
88
+ data["Members"] = [ data["Members"] ] if property.array
89
+ end
90
+ data
91
+ end
92
+ end
93
+
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,207 @@
1
+ require 'json'
2
+
3
+ module Genio
4
+ module Parser
5
+ module Format
6
+ class JsonSchema < Base
7
+ include Logging
8
+
9
+ # Load schema
10
+ # == Example
11
+ # schema.load("path/to/json_schema.json")
12
+ # schema.load("http://example.com/json_schema.json")
13
+ def load(filename, force = false)
14
+ if data_types[filename] || (!force and data_types[class_name(filename)])
15
+ class_name(filename)
16
+ elsif files[filename]
17
+ files[filename]
18
+ else
19
+ files[filename] = class_name(filename)
20
+ parse_file(filename)
21
+ end
22
+ end
23
+
24
+ # Parse Given schema file and return class name
25
+ def parse_file(filename)
26
+ klass = class_name(filename)
27
+ read_file(filename) do |data|
28
+ data = JSON.parse(data, :object_class => Types::Base, :max_nesting => 100)
29
+ if data.type == "object" or data.properties or data.type.is_a? Array # Check the type is object or not.
30
+ data_types[klass] = {}
31
+ data_types[klass] = parse_object(data)
32
+ elsif data.resources # Checkout the schema file contains the services or not
33
+ parse_resource(data)
34
+ end
35
+ end
36
+ klass
37
+ end
38
+
39
+ # Parse object schema
40
+ def parse_object(data)
41
+ if data["$ref"]
42
+ return self.data_types[self.load(data["$ref"], true)]
43
+ end
44
+
45
+ properties = Types::Base.new
46
+
47
+ # Parse each properties
48
+ if data.properties
49
+ data.properties.each do |name, options|
50
+ properties[name] = parse_property(name, options)
51
+ end
52
+ elsif data.type.is_a?(Array)
53
+ data.type.each do |object|
54
+ properties.merge!(parse_object(object).properties)
55
+ end
56
+ end
57
+
58
+ # Load extends class.
59
+ if data.extends.is_a? String
60
+ data.extends = self.load(data.extends)
61
+ else
62
+ data.extends = nil
63
+ end
64
+
65
+ # Parse array type
66
+ if data.items
67
+ array_type = parse_object(data.items)
68
+ properties.merge!(array_type.properties)
69
+ data.extends ||= array_type.extends
70
+ data.array = true
71
+ end
72
+
73
+ data.properties = properties
74
+ Types::DataType.new(data)
75
+ end
76
+
77
+ # Parse property.
78
+ def parse_property(name, data)
79
+ data.array = true if data.type == "array"
80
+ data.type =
81
+ if data["$ref"] # Check the type is refer to another schema or not
82
+ self.load(data["$ref"])
83
+ elsif data.additionalProperties and data.additionalProperties["$ref"]
84
+ self.load(data.additionalProperties["$ref"])
85
+ elsif data.properties # Check the type has object definition or not
86
+ klass_name = class_name(name)
87
+ data_types[klass_name] = parse_object(data)
88
+ klass_name
89
+ elsif data.type.is_a? Array
90
+ data.union_types = data.type.map do |type|
91
+ parse_object(type)
92
+ end
93
+ "object"
94
+ elsif data.items # Parse array value type
95
+ array_property = parse_property(name, data.items)
96
+ array_property.type
97
+ else
98
+ data.type # Simple type
99
+ end
100
+ Types::Property.new(data)
101
+ rescue => error
102
+ logger.error error.message
103
+ Types::Property.new
104
+ end
105
+
106
+ # Parse resource schema
107
+ def parse_resource(data)
108
+
109
+ self.endpoint ||= data.rootUrl
110
+
111
+ if data.schemas
112
+ data.schemas.each do |name, options|
113
+ data_types[class_name(name)] = true
114
+ end
115
+ data.schemas.each do |name, options|
116
+ data_types[class_name(name)] = parse_object(options)
117
+ end
118
+ end
119
+
120
+ parse_services(data.resources, data)
121
+ end
122
+
123
+ def parse_services(resources, data)
124
+ # Parse Resources
125
+ resources.each do |name, options|
126
+ service = parse_service(options, data)
127
+ service.path = File.join(data.servicePath, name)
128
+ if services[class_name(name)]
129
+ service.operations.merge!(services[class_name(name)].operations)
130
+ end
131
+ services[class_name(name)] = service
132
+ end
133
+ end
134
+
135
+ # Parse each operation in service
136
+ def parse_service(data, service)
137
+ data["methods"] ||= {}
138
+
139
+ data["methods"].each do |name, options|
140
+ options.relative_path = options.path
141
+ options.path = File.join(service.servicePath, options.path)
142
+ options.type = options.httpMethod
143
+ if options.request
144
+ options.request_property = parse_property("#{name}_request", options.request)
145
+ options.request = options.request_property.type
146
+ end
147
+ if options.response
148
+ options.response_property = parse_property("#{name}_response", options.response)
149
+ options.response = options.response_property.type
150
+ end
151
+ # Load service parameters
152
+ if options.parameters.nil? and options.type == "GET"
153
+ options.parameters = service.parameters
154
+ end
155
+ end
156
+
157
+ data.operations = data["methods"]
158
+
159
+ parse_services(data.resources, service) if data.resources
160
+
161
+ Types::Service.new(data)
162
+ end
163
+
164
+ # Update configured ref links
165
+ def update_ref_paths(data, paths = {})
166
+ paths.each do |path, replace_path|
167
+ replace_path = replace_path.sub(/\/?$/, "/")
168
+ data = data.gsub(/("\$ref"\s*:\s*")#{Regexp.escape(path)}\/?/, "\\1#{replace_path}")
169
+ end
170
+ data
171
+ end
172
+
173
+ # Format class name
174
+ # === Example
175
+ # class_name("credit-card") # return "CreditCard"
176
+ # class_name("/path/to/payment.json") # return "Payment"
177
+ def class_name(name)
178
+ File.basename(name.to_s.gsub(/\#$/, "").gsub(/-/, "_"), ".json").camelcase
179
+ end
180
+
181
+ # Fix file name format
182
+ def file_name(name)
183
+ name.to_s.gsub(/-/, "_").underscore
184
+ end
185
+
186
+ # Map operations based on the Request or Response types.
187
+ def fix_unknown_service
188
+ new_services = Types::Base.new
189
+ services.each do |service_name, service|
190
+ unless data_types[service_name]
191
+ service.operations.each do |operation_name, operation|
192
+ if data_types[operation.request]
193
+ new_services[operation.request] ||= Types::Base.new( :operations => {} )
194
+ new_services[operation.request].operations[operation_name] = operation
195
+ elsif data_types[operation.response]
196
+ new_services[operation.response] ||= Types::Base.new( :operations => {} )
197
+ new_services[operation.response].operations[operation_name] = operation
198
+ end
199
+ end
200
+ end
201
+ end
202
+ services.merge!(new_services)
203
+ end
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,283 @@
1
+ require "nokogiri"
2
+
3
+ module Genio
4
+ module Parser
5
+ module Format
6
+ class Wadl < Base
7
+
8
+ attr_accessor :current_schema, :namespaces, :attributes, :element_form_defaults
9
+
10
+ def load(file)
11
+ return if self.files[file]
12
+ self.files[file] = file
13
+
14
+ doc = load_doc(file)
15
+ schema = xslt.transform(doc)
16
+ schema.remove_namespaces!
17
+
18
+ parse_schema(schema)
19
+ end
20
+
21
+ def load_doc(file)
22
+ logger.info "GET #{file}"
23
+ document = Nokogiri::XML(open(file).read)
24
+
25
+ import_document = document.dup
26
+ import_document.remove_namespaces!
27
+
28
+ import_document.css("schema import").each do |import|
29
+ if import.attr("schemaLocation")
30
+ import_file = File.join(File.dirname(file), import.attr("schemaLocation"))
31
+ logger.info "GET #{import_file}"
32
+ parent = document.css("definitions types").first
33
+ if parent and parent.children.first
34
+ xml = Nokogiri::XML(open(import_file).read)
35
+ xml.children.each{|element| parent.children.first.before(element) }
36
+ end
37
+ end
38
+ end
39
+
40
+ document
41
+ end
42
+
43
+ def xslt
44
+ @xslt ||= Nokogiri::XSLT(File.read(File.expand_path("../../../../../data/wadl2meta.xsl", __FILE__)))
45
+ end
46
+
47
+ def parse_schema(schema)
48
+
49
+ self.current_schema = schema
50
+
51
+ self.namespaces ||= Types::Base.new
52
+ schema.css("properties namespace").map{|namespace|
53
+ self.namespaces[namespace.text] = namespace.attr("name")
54
+ }
55
+
56
+ self.attributes ||= Types::Base.new
57
+ schema.css("properties attribute").map{|attribute|
58
+ self.attributes[attribute.attr("name")] = attribute.text
59
+ }
60
+
61
+ self.element_form_defaults ||= Types::Base.new
62
+ schema.css("properties elementFormDefault").map{|element|
63
+ self.element_form_defaults[element.attr("namespace")] = element.text
64
+ }
65
+
66
+ schema.css("elements enum").each do |enum|
67
+ name = valid_class(enum.attr("name"))
68
+ type = Types::EnumType.new(enum.attributes.map{|k,v| [k, v.value] })
69
+ type.values = enum.css("value").map{|element| element.text }
70
+ self.enum_types[name] = type
71
+ end
72
+
73
+ # Load data_types
74
+ schema.css("classes class").each do |kls|
75
+ name = valid_class(kls.attr("name"))
76
+ value = parse_data_type(kls, self.namespaces[kls.attr("package")])
77
+ self.data_types[name] = value
78
+ end
79
+
80
+ # Validate property.type in each data_type
81
+ self.data_types.values.each do |data_type|
82
+ data_type.properties.values.each do |property|
83
+ property.type = valid_type(property.type)
84
+ end
85
+ end
86
+
87
+ # Set endpoint
88
+ resources = schema.css("resources").first
89
+ self.endpoint ||= resources.attr("base") if resources
90
+
91
+ # Load service and operations
92
+ schema.css("resources resource").each do |resource|
93
+ parse_resource(resource)
94
+ end
95
+ end
96
+
97
+ def parse_data_type(kls, default_package = nil)
98
+ attrs = Hash[kls.attributes.map{|key, value| [ key, value.value ] }]
99
+ data_type = Types::DataType.new(attrs)
100
+
101
+ if data_type.package.blank?
102
+ element = self.current_schema.css("elements element[name=#{data_type.name}]")[0]
103
+ data_type.package = element.attr("package") if element
104
+ end
105
+
106
+ if kls.css("extends").any?
107
+ data_type.extends = kls.css("extends").first.attr("name")
108
+ end
109
+
110
+ data_type.properties = {}
111
+ kls.css("properties property").each do |property|
112
+ name = property.attr("name")
113
+ value = Hash[property.attributes.map{|key, value| [ key, value.value ] }]
114
+ value["package"] = default_package if default_package and name != "value"
115
+ data_type.properties[name] = parse_property(value)
116
+ end
117
+
118
+ data_type
119
+ end
120
+
121
+ def get_element(name, level = 0)
122
+ if name.present?
123
+ element = self.current_schema.css("elements element[name=#{name}]").first
124
+ if element and element.attr("name") != element.attr("type") and level > 0
125
+ get_element(element.attr("type"), level - 1 ) || element
126
+ else
127
+ element
128
+ end
129
+ end
130
+ end
131
+
132
+ def parse_property(property)
133
+ property = Types::Property.new(property)
134
+
135
+ # Check for complex type
136
+ if property.ref
137
+ property.type = valid_type(property.ref)
138
+ end
139
+
140
+ unless property.type
141
+ element = get_element(property.name)
142
+ if element
143
+ property.type = element.attr("type")
144
+ element_package = self.namespaces[element.attr("package")]
145
+ property.package = element_package if element_package
146
+ end
147
+ end
148
+
149
+ if property.type.present? and !current_schema.css("classes class[name=#{property.type}]").first
150
+ element = get_element(property.type)
151
+ property.type = element.attr("type") if element and element.attr("type").present?
152
+ end
153
+
154
+ # Array type
155
+ if property.max == "unbounded" or property.max.to_i > 1
156
+ property.array = true
157
+ end
158
+
159
+ # Required
160
+ if property.min and property.min.to_i > 0
161
+ property.required = true
162
+ end
163
+
164
+ # Description
165
+ if property.documentation
166
+ property.description = property.documentation.gsub(/\s+/, " ").strip
167
+ end
168
+
169
+ # Enum
170
+ if enum_types[property.type]
171
+ property.enum = enum_types[property.type].values
172
+ end
173
+
174
+ property.attribute = true if property.attrib
175
+
176
+ property
177
+ end
178
+
179
+ def parse_resource(resource)
180
+ resource.css("method").each do |method_object|
181
+ operation = Types::Operation.new(
182
+ :id => method_object.attr("id"),
183
+ :type => method_object.attr("name"),
184
+ :parameters => {},
185
+ :path => resource.attr("path") )
186
+
187
+ resource.css("> param").each do |param|
188
+ operation.parameters[param.attr("name")] =
189
+ Types::Property.new( :type => valid_type(param.attr("type")), :location => "path" )
190
+ end
191
+
192
+ method_object.css("param").each do |param|
193
+ operation.parameters[param.attr("name")] =
194
+ Types::Property.new( :type => valid_type(param.attr("type")) )
195
+ end
196
+
197
+ request = method_object.css("request representation").first
198
+ if request
199
+ operation.request_property = operation_property(request)
200
+ operation.request = operation.request_property.type
201
+ end
202
+
203
+ response = method_object.css("response representation").first
204
+ if response
205
+ operation.response_property = operation_property(response)
206
+ operation.response = operation.response_property.type
207
+ end
208
+
209
+ add_operation(operation)
210
+ operation
211
+ end
212
+ end
213
+
214
+ def operation_property(type)
215
+ property = Types::Property.new(type.attributes.map{|k,v| [k, v.value] })
216
+ property.type = valid_type(property.element) || "string"
217
+ element = get_element(property.type)
218
+ property.type = valid_type(element.attr(:type)) if element and element.attr(:type).present?
219
+ property
220
+ end
221
+
222
+ def add_operation(operation)
223
+ path = operation.path.split("/")
224
+ type =
225
+ if path[-1] =~ /^{/ and path[-2].present? and self.data_types[valid_class(path[-2])]
226
+ valid_class(path[-2])
227
+ elsif path[-2] =~ /^{/ and path[-3].present? and self.data_types[valid_class(path[-3])]
228
+ valid_class(path[-3])
229
+ elsif path[-1].present? and path[-1] !~ /^{/ and self.data_types[valid_class(path[-1])]
230
+ valid_class(path[-1])
231
+ elsif operation.response and self.data_types[operation.response]
232
+ operation.response
233
+ elsif operation.request and self.data_types[operation.request]
234
+ operation.request
235
+ else
236
+ "Service"
237
+ end
238
+ if type
239
+ self.data_types[type] ||= Types::DataType.new( :properties => {} )
240
+ self.services[type] ||= Types::Service.new( :operations => {} )
241
+ name = operation_name(operation)
242
+ self.services[type].operations[name] = operation
243
+ end
244
+ end
245
+
246
+ private
247
+
248
+ def valid_class(name)
249
+ name.gsub(/-/, "_").camelcase
250
+ end
251
+
252
+ def valid_type(name)
253
+ if name
254
+ name = name.gsub(/^.*:/, "")
255
+ type = valid_class(name)
256
+ ( data_types[type] || enum_types[type] ) ? type : name
257
+ end
258
+ end
259
+
260
+ def operation_name(operation)
261
+ if operation.id
262
+ operation.id
263
+ elsif operation.type == "GET"
264
+ if operation.path =~ /}$/
265
+ "get"
266
+ else
267
+ "list"
268
+ end
269
+ elsif operation.type == "DELETE"
270
+ "delete"
271
+ elsif operation.path =~ /}\/([^{}]*)$/
272
+ $1.dup
273
+ elsif operation.type == "POST"
274
+ "create"
275
+ else
276
+ "update"
277
+ end
278
+ end
279
+
280
+ end
281
+ end
282
+ end
283
+ end