genio-parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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