scorpio 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,13 +1,12 @@
1
- # retrieved from
1
+ ---
2
2
  kind: discovery#restDescription
3
- etag: '"C5oy1hgQsABtYOYIOXWcR3BgYqU/Pyg0A4J33Dq212hoe9BYpSm0dl4"'
3
+ etag: '"YWOzh2SDasdU84ArJnpYek-OMdg/K3nEDF6hixE8Pks2-9Ysn9j9prQ"'
4
4
  discoveryVersion: v1
5
5
  id: discovery:v1
6
6
  name: discovery
7
7
  version: v1
8
8
  title: APIs Discovery Service
9
- description: Provides information about other Google APIs, such as what APIs are available,
10
- the resource, and method details for each API.
9
+ description: Provides information about other Google APIs, such as what APIs are available, the resource, and method details for each API.
11
10
  ownerDomain: google.com
12
11
  ownerName: Google
13
12
  icons:
@@ -19,7 +18,7 @@ baseUrl: https://www.googleapis.com/discovery/v1/
19
18
  basePath: "/discovery/v1/"
20
19
  rootUrl: https://www.googleapis.com/
21
20
  servicePath: discovery/v1/
22
- batchPath: batch
21
+ batchPath: batch/discovery/v1
23
22
  parameters:
24
23
  alt:
25
24
  type: string
@@ -36,8 +35,7 @@ parameters:
36
35
  location: query
37
36
  key:
38
37
  type: string
39
- description: API key. Your API key identifies your project and provides you with
40
- API access, quota, and reports. Required unless you provide an OAuth 2.0 token.
38
+ description: API key. Your API key identifies your project and provides you with API access, quota, and reports. Required unless you provide an OAuth 2.0 token.
41
39
  location: query
42
40
  oauth_token:
43
41
  type: string
@@ -50,14 +48,11 @@ parameters:
50
48
  location: query
51
49
  quotaUser:
52
50
  type: string
53
- description: Available to use for quota purposes for server-side applications.
54
- Can be any arbitrary string assigned to a user, but should not exceed 40 characters.
55
- Overrides userIp if both are provided.
51
+ description: Available to use for quota purposes for server-side applications. Can be any arbitrary string assigned to a user, but should not exceed 40 characters. Overrides userIp if both are provided.
56
52
  location: query
57
53
  userIp:
58
54
  type: string
59
- description: IP address of the site where the request originates. Use this if
60
- you want to enforce per-user limits.
55
+ description: IP address of the site where the request originates. Use this if you want to enforce per-user limits.
61
56
  location: query
62
57
  schemas:
63
58
  DirectoryList:
@@ -66,8 +61,7 @@ schemas:
66
61
  properties:
67
62
  discoveryVersion:
68
63
  type: string
69
- description: Indicate the version of the Discovery API used to generate this
70
- doc.
64
+ description: Indicate the version of the Discovery API used to generate this doc.
71
65
  default: v1
72
66
  items:
73
67
  type: array
@@ -131,20 +125,17 @@ schemas:
131
125
  properties:
132
126
  "$ref":
133
127
  type: string
134
- description: A reference to another schema. The value of this property is
135
- the "id" of another schema.
128
+ description: A reference to another schema. The value of this property is the "id" of another schema.
136
129
  additionalProperties:
137
130
  "$ref": JsonSchema
138
- description: If this is a schema for an object, this property is the schema
139
- for any additional properties with dynamic keys on this object.
131
+ description: If this is a schema for an object, this property is the schema for any additional properties with dynamic keys on this object.
140
132
  annotations:
141
133
  type: object
142
134
  description: Additional information about this property.
143
135
  properties:
144
136
  required:
145
137
  type: array
146
- description: A list of methods for which this property is required on
147
- requests.
138
+ description: A list of methods for which this property is required on requests.
148
139
  items:
149
140
  type: string
150
141
  default:
@@ -160,25 +151,21 @@ schemas:
160
151
  type: string
161
152
  enumDescriptions:
162
153
  type: array
163
- description: The descriptions for the enums. Each position maps to the corresponding
164
- value in the "enum" array.
154
+ description: The descriptions for the enums. Each position maps to the corresponding value in the "enum" array.
165
155
  items:
166
156
  type: string
167
157
  format:
168
158
  type: string
169
- description: 'An additional regular expression or key that helps constrain
170
- the value. For more details see: http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23'
159
+ description: 'An additional regular expression or key that helps constrain the value. For more details see: http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.23'
171
160
  id:
172
161
  type: string
173
162
  description: Unique identifier for this schema.
174
163
  items:
175
164
  "$ref": JsonSchema
176
- description: If this is a schema for an array, this property is the schema
177
- for each element in the array.
165
+ description: If this is a schema for an array, this property is the schema for each element in the array.
178
166
  location:
179
167
  type: string
180
- description: Whether this parameter goes in the query or the path for REST
181
- requests.
168
+ description: Whether this parameter goes in the query or the path for REST requests.
182
169
  maximum:
183
170
  type: string
184
171
  description: The maximum value of this parameter.
@@ -187,21 +174,16 @@ schemas:
187
174
  description: The minimum value of this parameter.
188
175
  pattern:
189
176
  type: string
190
- description: 'The regular expression this parameter must conform to. Uses
191
- Java 6 regex format: http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html'
177
+ description: 'The regular expression this parameter must conform to. Uses Java 6 regex format: http://docs.oracle.com/javase/6/docs/api/java/util/regex/Pattern.html'
192
178
  properties:
193
179
  type: object
194
- description: If this is a schema for an object, list the schema for each property
195
- of this object.
180
+ description: If this is a schema for an object, list the schema for each property of this object.
196
181
  additionalProperties:
197
182
  "$ref": JsonSchema
198
- description: A single property of this object. The value is itself a JSON
199
- Schema object describing this property.
183
+ description: A single property of this object. The value is itself a JSON Schema object describing this property.
200
184
  readOnly:
201
185
  type: boolean
202
- description: The value is read-only, generated by the service. The value cannot
203
- be modified by the client. If the value is included in a POST, PUT, or PATCH
204
- request, it is ignored by the service.
186
+ description: The value is read-only, generated by the service. The value cannot be modified by the client. If the value is included in a POST, PUT, or PATCH request, it is ignored by the service.
205
187
  repeated:
206
188
  type: boolean
207
189
  description: Whether this parameter may appear multiple times.
@@ -210,13 +192,10 @@ schemas:
210
192
  description: Whether the parameter is required.
211
193
  type:
212
194
  type: string
213
- description: 'The value type for this schema. A list of values can be found
214
- here: http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1'
195
+ description: 'The value type for this schema. A list of values can be found here: http://tools.ietf.org/html/draft-zyp-json-schema-03#section-5.1'
215
196
  variant:
216
197
  type: object
217
- description: In a variant data type, the value of one property is used to
218
- determine how to interpret the entire entity. Its value must exist in a
219
- map of descriminant values to schema names.
198
+ description: In a variant data type, the value of one property is used to determine how to interpret the entire entity. Its value must exist in a map of descriminant values to schema names.
220
199
  properties:
221
200
  discriminant:
222
201
  type: string
@@ -262,18 +241,15 @@ schemas:
262
241
  batchPath:
263
242
  type: string
264
243
  description: The path for REST batch requests.
265
- default: batch
266
244
  canonicalName:
267
245
  type: string
268
- description: Indicates how the API name should be capitalized and split into
269
- various parts. Useful for generating pretty class names.
246
+ description: Indicates how the API name should be capitalized and split into various parts. Useful for generating pretty class names.
270
247
  description:
271
248
  type: string
272
249
  description: The description of this API.
273
250
  discoveryVersion:
274
251
  type: string
275
- description: Indicate the version of the Discovery API used to generate this
276
- doc.
252
+ description: Indicate the version of the Discovery API used to generate this doc.
277
253
  default: v1
278
254
  documentationLink:
279
255
  type: string
@@ -284,8 +260,7 @@ schemas:
284
260
  readOnly: true
285
261
  exponentialBackoffDefault:
286
262
  type: boolean
287
- description: Enable exponential backoff for suitable methods in the generated
288
- clients.
263
+ description: Enable exponential backoff for suitable methods in the generated clients.
289
264
  features:
290
265
  type: array
291
266
  description: A list of supported features for this API.
@@ -324,9 +299,7 @@ schemas:
324
299
  description: The name of this API.
325
300
  ownerDomain:
326
301
  type: string
327
- description: The domain of the owner of this API. Together with the ownerName
328
- and a packagePath values, this can be used to generate a library for this
329
- API which would have a unique fully qualified name.
302
+ description: The domain of the owner of this API. Together with the ownerName and a packagePath values, this can be used to generate a library for this API which would have a unique fully qualified name.
330
303
  ownerName:
331
304
  type: string
332
305
  description: The name of the owner of this API. See ownerDomain.
@@ -348,8 +321,7 @@ schemas:
348
321
  description: The resources in this API.
349
322
  additionalProperties:
350
323
  "$ref": RestResource
351
- description: An individual resource description. Contains methods and sub-resources
352
- related to this resource.
324
+ description: An individual resource description. Contains methods and sub-resources related to this resource.
353
325
  revision:
354
326
  type: string
355
327
  description: The version of this API.
@@ -382,15 +354,13 @@ schemas:
382
354
  description: Description of this method.
383
355
  etagRequired:
384
356
  type: boolean
385
- description: Whether this method requires an ETag to be specified. The ETag
386
- is sent as an HTTP If-Match or If-None-Match header.
357
+ description: Whether this method requires an ETag to be specified. The ETag is sent as an HTTP If-Match or If-None-Match header.
387
358
  httpMethod:
388
359
  type: string
389
360
  description: HTTP method used by this method.
390
361
  id:
391
362
  type: string
392
- description: A unique ID for this method. This property can be used to match
393
- methods between different versions of Discovery.
363
+ description: A unique ID for this method. This property can be used to match methods between different versions of Discovery.
394
364
  mediaUpload:
395
365
  type: object
396
366
  description: Media upload parameters.
@@ -413,13 +383,11 @@ schemas:
413
383
  properties:
414
384
  multipart:
415
385
  type: boolean
416
- description: True if this endpoint supports uploading multipart
417
- media.
386
+ description: True if this endpoint supports uploading multipart media.
418
387
  default: 'true'
419
388
  path:
420
389
  type: string
421
- description: The URI path to be used for upload. Should be used
422
- in conjunction with the basePath property at the api-level.
390
+ description: The URI path to be used for upload. Should be used in conjunction with the basePath property at the api-level.
423
391
  simple:
424
392
  type: object
425
393
  description: Supports uploading as a single HTTP request.
@@ -430,13 +398,10 @@ schemas:
430
398
  default: 'true'
431
399
  path:
432
400
  type: string
433
- description: The URI path to be used for upload. Should be used
434
- in conjunction with the basePath property at the api-level.
401
+ description: The URI path to be used for upload. Should be used in conjunction with the basePath property at the api-level.
435
402
  parameterOrder:
436
403
  type: array
437
- description: Ordered list of required parameters, serves as a hint to clients
438
- on how to structure their method signatures. The array is ordered such that
439
- the "most-significant" parameter appears first.
404
+ description: Ordered list of required parameters, serves as a hint to clients on how to structure their method signatures. The array is ordered such that the "most-significant" parameter appears first.
440
405
  items:
441
406
  type: string
442
407
  parameters:
@@ -447,8 +412,7 @@ schemas:
447
412
  description: Details for a single parameter in this method.
448
413
  path:
449
414
  type: string
450
- description: The URI path of this REST method. Should be used in conjunction
451
- with the basePath property at the api-level.
415
+ description: The URI path of this REST method. Should be used in conjunction with the basePath property at the api-level.
452
416
  request:
453
417
  type: object
454
418
  description: The schema for the request.
@@ -482,9 +446,7 @@ schemas:
482
446
  description: Whether this method supports subscriptions.
483
447
  useMediaDownloadService:
484
448
  type: boolean
485
- description: Indicates that downloads from this method should use the download
486
- service URL (i.e. "/download"). Only applies if the method supports media
487
- download.
449
+ description: Indicates that downloads from this method should use the download service URL (i.e. "/download"). Only applies if the method supports media download.
488
450
  RestResource:
489
451
  id: RestResource
490
452
  type: object
@@ -1,6 +1,19 @@
1
1
  require "scorpio/version"
2
+ require "pathname"
3
+ require "pp"
4
+ require "api_hammer/ycomb"
5
+ require "scorpio/json-schema-fragments"
2
6
 
3
7
  module Scorpio
8
+ def self.root
9
+ @root ||= Pathname.new(__FILE__).dirname.parent.expand_path
10
+ end
11
+
12
+ # generally put in code paths that are not expected to be valid control flow paths.
13
+ # rather a NotImplementedCorrectlyError. but that's too long.
14
+ class Bug < NotImplementedError
15
+ end
16
+
4
17
  proc { |v| define_singleton_method(:error_classes_by_status) { v } }.call({})
5
18
  class Error < StandardError; end
6
19
  class HTTPError < Error
@@ -53,13 +66,29 @@ module Scorpio
53
66
  error_classes_by_status.freeze
54
67
 
55
68
  autoload :Model, 'scorpio/model'
69
+ autoload :OpenAPI, 'scorpio/openapi'
70
+ autoload :Google, 'scorpio/google_api_document'
71
+ autoload :JSON, 'scorpio/json'
72
+ autoload :Schema, 'scorpio/schema'
56
73
 
57
74
  class << self
58
75
  def stringify_symbol_keys(hash)
59
76
  unless hash.is_a?(Hash)
60
- raise ArgumentError, "expected argument to be a Hash; got #{hash.class}: #{hash.inspect}"
77
+ raise ArgumentError, "expected argument to be a Hash; got #{hash.class}: #{hash.pretty_inspect}"
61
78
  end
62
79
  hash.map { |k,v| {k.is_a?(Symbol) ? k.to_s : k => v} }.inject({}, &:update)
63
80
  end
64
81
  end
82
+
83
+ module FingerprintHash
84
+ def ==(other)
85
+ other.respond_to?(:fingerprint) && other.fingerprint == self.fingerprint
86
+ end
87
+
88
+ alias eql? ==
89
+
90
+ def hash
91
+ fingerprint.hash
92
+ end
93
+ end
65
94
  end
@@ -0,0 +1,232 @@
1
+ require 'api_hammer/ycomb'
2
+ require 'scorpio/schema_object_base'
3
+
4
+ module Scorpio
5
+ module Google
6
+ apidoc_schema_doc = ::JSON.parse(Scorpio.root.join('documents/www.googleapis.com/discovery/v1/apis/discovery/v1/rest').read)
7
+ api_document_class = proc do |*key|
8
+ Scorpio.class_for_schema(Scorpio::JSON::Node.new_by_type(apidoc_schema_doc, ['schemas', *key]))
9
+ end
10
+
11
+ # naming these is not strictly necessary, but is nice to have.
12
+ # generated: puts Scorpio::Google::ApiDocument.document['schemas'].select { |k,v| v['type'] == 'object' }.keys.map { |k| "#{k[0].upcase}#{k[1..-1]} = api_document_class.call('#{k}')" }
13
+ DirectoryList = api_document_class.call('DirectoryList')
14
+ JsonSchema = api_document_class.call('JsonSchema')
15
+ RestDescription = api_document_class.call('RestDescription')
16
+ RestMethod = api_document_class.call('RestMethod')
17
+ RestResource = api_document_class.call('RestResource')
18
+
19
+ # not generated
20
+ RestMethodRequest = api_document_class.call('RestMethod', 'properties', 'request')
21
+ RestMethodResponse = api_document_class.call('RestMethod', 'properties', 'response')
22
+
23
+ # google does a weird thing where it defines a schema with a $ref property where a json-schema is to be used in the document (method request and response fields), instead of just setting the schema to be the json-schema schema. we'll share a module across those schema classes that really represent schemas. is this confusingly meta enough?
24
+ module SchemaLike
25
+ def to_openapi
26
+ dup_doc = ::JSON.parse(::JSON.generate(object.content))
27
+ # openapi does not want an id field on schemas
28
+ dup_doc.delete('id')
29
+ if dup_doc['properties'].is_a?(Hash)
30
+ required_properties = dup_doc['properties'].select do |key, value|
31
+ value.is_a?(Hash) ? value.delete('required') : nil
32
+ end.keys
33
+ # put required before properties
34
+ unless required_properties.empty?
35
+ dup_doc = dup_doc.map do |k, v|
36
+ base = k == 'properties' ? {'required' => required_properties } : {}
37
+ base.merge({k => v})
38
+ end.inject({}, &:update)
39
+ end
40
+ end
41
+ dup_doc
42
+ end
43
+ end
44
+ [JsonSchema, RestMethodRequest, RestMethodResponse].each { |klass| klass.send(:include, SchemaLike) }
45
+
46
+ class RestDescription
47
+ def to_openapi_document(options = {})
48
+ Scorpio::OpenAPI::Document.new(to_openapi_hash(options))
49
+ end
50
+
51
+ def to_openapi_hash(options = {})
52
+ # we will be modifying the api document (RestDescription). clone self and modify that one.
53
+ ad = self.class.new(::JSON.parse(::JSON.generate(object.document)))
54
+ ad_methods = []
55
+ if ad['methods']
56
+ ad_methods += ad['methods'].map do |mn, m|
57
+ m.tap do
58
+ m.send(:define_singleton_method, :resource_name) { }
59
+ m.send(:define_singleton_method, :method_name) { mn }
60
+ end
61
+ end
62
+ end
63
+ ad_methods += ad.resources.map do |rn, r|
64
+ (r['methods'] || {}).map do |mn, m|
65
+ m.tap do
66
+ m.send(:define_singleton_method, :resource_name) { rn }
67
+ m.send(:define_singleton_method, :method_name) { mn }
68
+ end
69
+ end
70
+ end.inject([], &:+)
71
+
72
+ paths = ad_methods.group_by { |m| m['path'] }.map do |path, path_methods|
73
+ unless path =~ %r(\A/)
74
+ path = '/' + path
75
+ end
76
+ operations = path_methods.group_by { |m| m['httpMethod'] }.map do |http_method, http_method_methods|
77
+ if http_method_methods.size > 1
78
+ #raise("http method #{http_method} at path #{path} not unique: #{http_method_methods.pretty_inspect}")
79
+ end
80
+ method = http_method_methods.first
81
+ unused_path_params = Addressable::Template.new(path).variables
82
+ {http_method.downcase => {}.tap do |operation|
83
+ #operation['tags'] = []
84
+ #operation['summary'] =
85
+ operation['description'] = method['description'] if method['description']
86
+ if method.resource_name && options[:x]
87
+ operation['x-resource'] = method.resource_name
88
+ operation['x-resource-method'] = method.method_name
89
+ end
90
+ #operation['externalDocs'] =
91
+ operation['operationId'] = method['id'] || (method.resource_name ? "#{method.resource_name}.#{method.method_name}" : method.method_name)
92
+ #operation['produces'] =
93
+ #operation['consumes'] =
94
+ if method['parameters']
95
+ operation['parameters'] = method['parameters'].map do |name, parameter|
96
+ {}.tap do |op_param|
97
+ op_param['description'] = parameter.description if parameter.description
98
+ op_param['name'] = name
99
+ op_param['in'] = if parameter.location
100
+ parameter.location
101
+ elsif unused_path_params.include?(name)
102
+ 'path'
103
+ else
104
+ 'query'
105
+ # unused: header, formdata, body
106
+ end
107
+ unused_path_params.delete(name) if op_param['in'] == 'path'
108
+ op_param['required'] = parameter.key?('required') ? parameter['required'] : op_param['in'] == 'path' ? true : false
109
+ op_param['type'] = parameter.type || 'string'
110
+ op_param['format'] = parameter.format if parameter.format
111
+ end
112
+ end
113
+ end
114
+ if unused_path_params.any?
115
+ operation['parameters'] ||= []
116
+ operation['parameters'] += unused_path_params.map do |param_name|
117
+ {
118
+ 'name' => param_name,
119
+ 'in' => 'path',
120
+ 'required' => true,
121
+ 'type' => 'string',
122
+ }
123
+ end
124
+ end
125
+ if method['request']
126
+ operation['parameters'] ||= []
127
+ operation['parameters'] << {
128
+ 'name' => 'body',
129
+ 'in' => 'body',
130
+ 'required' => true,
131
+ 'schema' => method['request'],
132
+ }
133
+ end
134
+ if method['response']
135
+ operation['responses'] = {
136
+ 'default' => {
137
+ 'description' => 'default response',
138
+ 'schema' => method['response'],
139
+ },
140
+ }
141
+ end
142
+ end}
143
+ end.inject({}, &:update)
144
+
145
+ {path => operations}
146
+ end.inject({}, &:update)
147
+
148
+ openapi = {
149
+ 'swagger' => '2.0',
150
+ 'info' => { #/definitions/info
151
+ 'title' => ad.title || ad.name,
152
+ 'description' => ad.description,
153
+ 'version' => ad.version || '',
154
+ #'termsOfService' => '',
155
+ 'contact' => {
156
+ 'name' => ad.ownerName,
157
+ #'url' =>
158
+ #'email' => '',
159
+ }.reject { |_, v| v.nil? },
160
+ #'license' => {
161
+ #'name' => '',
162
+ #'url' => '',
163
+ #},
164
+ },
165
+ 'host' => ad.rootUrl ? Addressable::URI.parse(ad.rootUrl).host : ad.baseUrl ? Addressable::URI.parse(ad.rootUrl).host : ad.name, # uhh ... got nothin' better
166
+ 'basePath' => begin
167
+ path = ad.servicePath || ad.basePath || (ad.baseUrl ? Addressable::URI.parse(ad.baseUrl).path : '/')
168
+ path =~ %r(\A/) ? path : "/" + path
169
+ end,
170
+ 'schemes' => ad.rootUrl ? [Addressable::URI.parse(ad.rootUrl).scheme] : ad.baseUrl ? [Addressable::URI.parse(ad.rootUrl).scheme] : [], #/definitions/schemesList
171
+ 'consumes' => ['application/json'], # we'll just make this assumption
172
+ 'produces' => ['application/json'],
173
+ 'paths' => paths, #/definitions/paths
174
+ }
175
+ if ad.schemas
176
+ openapi['definitions'] = ad.schemas
177
+ ad.schemas.each do |name, schema|
178
+ openapi = ycomb do |rec|
179
+ proc do |object|
180
+ if object.respond_to?(:to_hash)
181
+ object.merge(object.map do |k, v|
182
+ if k == '$ref' && (v == schema['id'] || v == "#/schemas/#{name}" || v == name)
183
+ {k => "#/definitions/#{name}"}
184
+ else
185
+ ycomb do |toopenapirec|
186
+ proc do |toopenapiobject|
187
+ toopenapiobject = toopenapiobject.to_openapi if toopenapiobject.respond_to?(:to_openapi)
188
+ if toopenapiobject.respond_to?(:to_hash)
189
+ toopenapiobject.map { |k, v| {toopenapirec.call(k) => toopenapirec.call(v)} }.inject({}, &:update)
190
+ elsif toopenapiobject.respond_to?(:to_ary)
191
+ toopenapiobject.map(&toopenapirec)
192
+ elsif toopenapiobject.is_a?(Symbol)
193
+ toopenapiobject.to_s
194
+ elsif [String, TrueClass, FalseClass, NilClass, Numeric].any? { |c| toopenapiobject.is_a?(c) }
195
+ toopenapiobject
196
+ else
197
+ raise(TypeError, "bad (not jsonifiable) object: #{toopenapiobject.pretty_inspect}")
198
+ end
199
+ end
200
+ end.call({k => rec.call(v)})
201
+ end
202
+ end.inject({}, &:merge))
203
+ elsif object.respond_to?(:to_ary)
204
+ object.map(&rec)
205
+ else
206
+ object
207
+ end
208
+ end
209
+ end.call(openapi)
210
+ end
211
+ end
212
+ # check we haven't got anything that shouldn't go in a openapi document
213
+ openapi = ycomb do |rec|
214
+ proc do |object|
215
+ object = object.to_openapi if object.respond_to?(:to_openapi)
216
+ if object.respond_to?(:to_hash)
217
+ object.map { |k, v| {rec.call(k) => rec.call(v)} }.inject({}, &:update)
218
+ elsif object.respond_to?(:to_ary)
219
+ object.map(&rec)
220
+ elsif object.is_a?(Symbol)
221
+ object.to_s
222
+ elsif [String, TrueClass, FalseClass, NilClass, Numeric].any? { |c| object.is_a?(c) }
223
+ object
224
+ else
225
+ raise(TypeError, "bad (not jsonifiable) object: #{object.pretty_inspect}")
226
+ end
227
+ end
228
+ end.call(openapi)
229
+ end
230
+ end
231
+ end
232
+ end