scorpio 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +2 -0
- data/README.md +108 -25
- data/Rakefile +2 -2
- data/documents/openapis.org/v3/schema.json +1239 -0
- data/documents/openapis.org/v3/schema.yml +794 -0
- data/lib/scorpio.rb +68 -68
- data/lib/scorpio/google_api_document.rb +18 -24
- data/lib/scorpio/json-schema-fragments.rb +1 -1
- data/lib/scorpio/json/node.rb +106 -74
- data/lib/scorpio/openapi.rb +146 -68
- data/lib/scorpio/pickle_adapter.rb +3 -3
- data/lib/scorpio/{model.rb → resource_base.rb} +187 -145
- data/lib/scorpio/schema.rb +188 -76
- data/lib/scorpio/schema_instance_base.rb +309 -0
- data/lib/scorpio/schema_instance_base/to_rb.rb +127 -0
- data/lib/scorpio/typelike_modules.rb +120 -4
- data/lib/scorpio/util.rb +83 -0
- data/lib/scorpio/util/faraday/response_media_type.rb +15 -0
- data/lib/scorpio/version.rb +1 -1
- data/scorpio.gemspec +0 -1
- metadata +10 -19
- data/lib/scorpio/schema_object_base.rb +0 -227
data/lib/scorpio/openapi.rb
CHANGED
@@ -1,79 +1,157 @@
|
|
1
|
-
require 'scorpio/
|
1
|
+
require 'scorpio/schema_instance_base'
|
2
2
|
|
3
3
|
module Scorpio
|
4
4
|
module OpenAPI
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
module V3
|
6
|
+
openapi_schema = Scorpio::Schema.new(::JSON.parse(Scorpio.root.join('documents/openapis.org/v3/schema.json').read))
|
7
|
+
openapi_class = proc do |*key|
|
8
|
+
Scorpio.class_for_schema(key.inject(openapi_schema, &:[]))
|
9
|
+
end
|
10
|
+
|
11
|
+
Document = openapi_class.call()
|
12
|
+
|
13
|
+
# naming these is not strictly necessary, but is nice to have.
|
14
|
+
# generated: puts openapi_schema_doc['definitions'].select { |k,v| ['object', nil].include?(v['type']) }.keys.map { |k| "#{k[0].upcase}#{k[1..-1]} = openapi_class.call('definitions', '#{k}')" }
|
15
|
+
Info = openapi_class.call('definitions', 'info')
|
16
|
+
Contact = openapi_class.call('definitions', 'contact')
|
17
|
+
License = openapi_class.call('definitions', 'license')
|
18
|
+
Server = openapi_class.call('definitions', 'server')
|
19
|
+
ServerVariable = openapi_class.call('definitions', 'serverVariable')
|
20
|
+
Components = openapi_class.call('definitions', 'components')
|
21
|
+
Paths = openapi_class.call('definitions', 'paths')
|
22
|
+
PathItem = openapi_class.call('definitions', 'pathItem')
|
23
|
+
Operation = openapi_class.call('definitions', 'operation')
|
24
|
+
ExternalDocs = openapi_class.call('definitions', 'externalDocs')
|
25
|
+
Parameter = openapi_class.call('definitions', 'parameter')
|
26
|
+
RequestBody = openapi_class.call('definitions', 'requestBody')
|
27
|
+
MediaType = openapi_class.call('definitions', 'mediaType')
|
28
|
+
Encoding = openapi_class.call('definitions', 'encoding')
|
29
|
+
Responses = openapi_class.call('definitions', 'responses')
|
30
|
+
Response = openapi_class.call('definitions', 'response')
|
31
|
+
Callback = openapi_class.call('definitions', 'callback')
|
32
|
+
Example = openapi_class.call('definitions', 'example')
|
33
|
+
Link = openapi_class.call('definitions', 'link')
|
34
|
+
Header = openapi_class.call('definitions', 'header')
|
35
|
+
Tag = openapi_class.call('definitions', 'tag')
|
36
|
+
Examples = openapi_class.call('definitions', 'examples')
|
37
|
+
Reference = openapi_class.call('definitions', 'reference')
|
38
|
+
Schema = openapi_class.call('definitions', 'schema')
|
39
|
+
Discriminator = openapi_class.call('definitions', 'discriminator')
|
40
|
+
Xml = openapi_class.call('definitions', 'xml')
|
41
|
+
SecurityScheme = openapi_class.call('definitions', 'securityScheme')
|
42
|
+
OauthFlows = openapi_class.call('definitions', 'oauthFlows')
|
43
|
+
OauthFlow = openapi_class.call('definitions', 'oauthFlow')
|
44
|
+
SecurityRequirement = openapi_class.call('definitions', 'securityRequirement')
|
45
|
+
AnyOrExpression = openapi_class.call('definitions', 'anyOrExpression')
|
46
|
+
CallbackOrReference = openapi_class.call('definitions', 'callbackOrReference')
|
47
|
+
ExampleOrReference = openapi_class.call('definitions', 'exampleOrReference')
|
48
|
+
HeaderOrReference = openapi_class.call('definitions', 'headerOrReference')
|
49
|
+
LinkOrReference = openapi_class.call('definitions', 'linkOrReference')
|
50
|
+
ParameterOrReference = openapi_class.call('definitions', 'parameterOrReference')
|
51
|
+
RequestBodyOrReference = openapi_class.call('definitions', 'requestBodyOrReference')
|
52
|
+
ResponseOrReference = openapi_class.call('definitions', 'responseOrReference')
|
53
|
+
SchemaOrReference = openapi_class.call('definitions', 'schemaOrReference')
|
54
|
+
SecuritySchemeOrReference = openapi_class.call('definitions', 'securitySchemeOrReference')
|
55
|
+
AnysOrExpressions = openapi_class.call('definitions', 'anysOrExpressions')
|
56
|
+
CallbacksOrReferences = openapi_class.call('definitions', 'callbacksOrReferences')
|
57
|
+
Encodings = openapi_class.call('definitions', 'encodings')
|
58
|
+
ExamplesOrReferences = openapi_class.call('definitions', 'examplesOrReferences')
|
59
|
+
HeadersOrReferences = openapi_class.call('definitions', 'headersOrReferences')
|
60
|
+
LinksOrReferences = openapi_class.call('definitions', 'linksOrReferences')
|
61
|
+
MediaTypes = openapi_class.call('definitions', 'mediaTypes')
|
62
|
+
ParametersOrReferences = openapi_class.call('definitions', 'parametersOrReferences')
|
63
|
+
RequestBodiesOrReferences = openapi_class.call('definitions', 'requestBodiesOrReferences')
|
64
|
+
ResponsesOrReferences = openapi_class.call('definitions', 'responsesOrReferences')
|
65
|
+
SchemasOrReferences = openapi_class.call('definitions', 'schemasOrReferences')
|
66
|
+
SecuritySchemesOrReferences = openapi_class.call('definitions', 'securitySchemesOrReferences')
|
67
|
+
ServerVariables = openapi_class.call('definitions', 'serverVariables')
|
68
|
+
Strings = openapi_class.call('definitions', 'strings')
|
69
|
+
Object = openapi_class.call('definitions', 'object')
|
70
|
+
Any = openapi_class.call('definitions', 'any')
|
71
|
+
Expression = openapi_class.call('definitions', 'expression')
|
72
|
+
SpecificationExtension = openapi_class.call('definitions', 'specificationExtension')
|
73
|
+
DefaultType = openapi_class.call('definitions', 'defaultType')
|
8
74
|
end
|
75
|
+
module V2
|
76
|
+
openapi_schema = Scorpio::Schema.new(::JSON.parse(Scorpio.root.join('documents/swagger.io/v2/schema.json').read))
|
77
|
+
openapi_class = proc do |*key|
|
78
|
+
Scorpio.class_for_schema(key.inject(openapi_schema, &:[]))
|
79
|
+
end
|
80
|
+
|
81
|
+
Document = openapi_class.call()
|
9
82
|
|
10
|
-
|
83
|
+
# naming these is not strictly necessary, but is nice to have.
|
84
|
+
# generated: puts Scorpio::OpenAPI::V2::Document.schema_document['definitions'].select { |k,v| ['object', nil].include?(v['type']) }.keys.map { |k| "#{k[0].upcase}#{k[1..-1]} = openapi_class.call('definitions', '#{k}')" }
|
85
|
+
Info = openapi_class.call('definitions', 'info')
|
86
|
+
Contact = openapi_class.call('definitions', 'contact')
|
87
|
+
License = openapi_class.call('definitions', 'license')
|
88
|
+
Paths = openapi_class.call('definitions', 'paths')
|
89
|
+
Definitions = openapi_class.call('definitions', 'definitions')
|
90
|
+
ParameterDefinitions = openapi_class.call('definitions', 'parameterDefinitions')
|
91
|
+
ResponseDefinitions = openapi_class.call('definitions', 'responseDefinitions')
|
92
|
+
ExternalDocs = openapi_class.call('definitions', 'externalDocs')
|
93
|
+
Examples = openapi_class.call('definitions', 'examples')
|
94
|
+
Operation = openapi_class.call('definitions', 'operation')
|
95
|
+
PathItem = openapi_class.call('definitions', 'pathItem')
|
96
|
+
Responses = openapi_class.call('definitions', 'responses')
|
97
|
+
ResponseValue = openapi_class.call('definitions', 'responseValue')
|
98
|
+
Response = openapi_class.call('definitions', 'response')
|
99
|
+
Headers = openapi_class.call('definitions', 'headers')
|
100
|
+
Header = openapi_class.call('definitions', 'header')
|
101
|
+
VendorExtension = openapi_class.call('definitions', 'vendorExtension')
|
102
|
+
BodyParameter = openapi_class.call('definitions', 'bodyParameter')
|
103
|
+
HeaderParameterSubSchema = openapi_class.call('definitions', 'headerParameterSubSchema')
|
104
|
+
QueryParameterSubSchema = openapi_class.call('definitions', 'queryParameterSubSchema')
|
105
|
+
FormDataParameterSubSchema = openapi_class.call('definitions', 'formDataParameterSubSchema')
|
106
|
+
PathParameterSubSchema = openapi_class.call('definitions', 'pathParameterSubSchema')
|
107
|
+
NonBodyParameter = openapi_class.call('definitions', 'nonBodyParameter')
|
108
|
+
Parameter = openapi_class.call('definitions', 'parameter')
|
109
|
+
Schema = openapi_class.call('definitions', 'schema')
|
110
|
+
FileSchema = openapi_class.call('definitions', 'fileSchema')
|
111
|
+
PrimitivesItems = openapi_class.call('definitions', 'primitivesItems')
|
112
|
+
SecurityRequirement = openapi_class.call('definitions', 'securityRequirement')
|
113
|
+
Xml = openapi_class.call('definitions', 'xml')
|
114
|
+
Tag = openapi_class.call('definitions', 'tag')
|
115
|
+
SecurityDefinitions = openapi_class.call('definitions', 'securityDefinitions')
|
116
|
+
BasicAuthenticationSecurity = openapi_class.call('definitions', 'basicAuthenticationSecurity')
|
117
|
+
ApiKeySecurity = openapi_class.call('definitions', 'apiKeySecurity')
|
118
|
+
Oauth2ImplicitSecurity = openapi_class.call('definitions', 'oauth2ImplicitSecurity')
|
119
|
+
Oauth2PasswordSecurity = openapi_class.call('definitions', 'oauth2PasswordSecurity')
|
120
|
+
Oauth2ApplicationSecurity = openapi_class.call('definitions', 'oauth2ApplicationSecurity')
|
121
|
+
Oauth2AccessCodeSecurity = openapi_class.call('definitions', 'oauth2AccessCodeSecurity')
|
122
|
+
Oauth2Scopes = openapi_class.call('definitions', 'oauth2Scopes')
|
123
|
+
Title = openapi_class.call('definitions', 'title')
|
124
|
+
Description = openapi_class.call('definitions', 'description')
|
125
|
+
Default = openapi_class.call('definitions', 'default')
|
126
|
+
MultipleOf = openapi_class.call('definitions', 'multipleOf')
|
127
|
+
Maximum = openapi_class.call('definitions', 'maximum')
|
128
|
+
ExclusiveMaximum = openapi_class.call('definitions', 'exclusiveMaximum')
|
129
|
+
Minimum = openapi_class.call('definitions', 'minimum')
|
130
|
+
ExclusiveMinimum = openapi_class.call('definitions', 'exclusiveMinimum')
|
131
|
+
MaxLength = openapi_class.call('definitions', 'maxLength')
|
132
|
+
MinLength = openapi_class.call('definitions', 'minLength')
|
133
|
+
Pattern = openapi_class.call('definitions', 'pattern')
|
134
|
+
MaxItems = openapi_class.call('definitions', 'maxItems')
|
135
|
+
MinItems = openapi_class.call('definitions', 'minItems')
|
136
|
+
UniqueItems = openapi_class.call('definitions', 'uniqueItems')
|
137
|
+
Enum = openapi_class.call('definitions', 'enum')
|
138
|
+
JsonReference = openapi_class.call('definitions', 'jsonReference')
|
11
139
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
Contact = openapi_class.call('definitions', 'contact')
|
16
|
-
License = openapi_class.call('definitions', 'license')
|
17
|
-
Paths = openapi_class.call('definitions', 'paths')
|
18
|
-
Definitions = openapi_class.call('definitions', 'definitions')
|
19
|
-
ParameterDefinitions = openapi_class.call('definitions', 'parameterDefinitions')
|
20
|
-
ResponseDefinitions = openapi_class.call('definitions', 'responseDefinitions')
|
21
|
-
ExternalDocs = openapi_class.call('definitions', 'externalDocs')
|
22
|
-
Examples = openapi_class.call('definitions', 'examples')
|
23
|
-
Operation = openapi_class.call('definitions', 'operation')
|
24
|
-
PathItem = openapi_class.call('definitions', 'pathItem')
|
25
|
-
Responses = openapi_class.call('definitions', 'responses')
|
26
|
-
ResponseValue = openapi_class.call('definitions', 'responseValue')
|
27
|
-
Response = openapi_class.call('definitions', 'response')
|
28
|
-
Headers = openapi_class.call('definitions', 'headers')
|
29
|
-
Header = openapi_class.call('definitions', 'header')
|
30
|
-
VendorExtension = openapi_class.call('definitions', 'vendorExtension')
|
31
|
-
BodyParameter = openapi_class.call('definitions', 'bodyParameter')
|
32
|
-
HeaderParameterSubSchema = openapi_class.call('definitions', 'headerParameterSubSchema')
|
33
|
-
QueryParameterSubSchema = openapi_class.call('definitions', 'queryParameterSubSchema')
|
34
|
-
FormDataParameterSubSchema = openapi_class.call('definitions', 'formDataParameterSubSchema')
|
35
|
-
PathParameterSubSchema = openapi_class.call('definitions', 'pathParameterSubSchema')
|
36
|
-
NonBodyParameter = openapi_class.call('definitions', 'nonBodyParameter')
|
37
|
-
Parameter = openapi_class.call('definitions', 'parameter')
|
38
|
-
Schema = openapi_class.call('definitions', 'schema')
|
39
|
-
FileSchema = openapi_class.call('definitions', 'fileSchema')
|
40
|
-
PrimitivesItems = openapi_class.call('definitions', 'primitivesItems')
|
41
|
-
SecurityRequirement = openapi_class.call('definitions', 'securityRequirement')
|
42
|
-
Xml = openapi_class.call('definitions', 'xml')
|
43
|
-
Tag = openapi_class.call('definitions', 'tag')
|
44
|
-
SecurityDefinitions = openapi_class.call('definitions', 'securityDefinitions')
|
45
|
-
BasicAuthenticationSecurity = openapi_class.call('definitions', 'basicAuthenticationSecurity')
|
46
|
-
ApiKeySecurity = openapi_class.call('definitions', 'apiKeySecurity')
|
47
|
-
Oauth2ImplicitSecurity = openapi_class.call('definitions', 'oauth2ImplicitSecurity')
|
48
|
-
Oauth2PasswordSecurity = openapi_class.call('definitions', 'oauth2PasswordSecurity')
|
49
|
-
Oauth2ApplicationSecurity = openapi_class.call('definitions', 'oauth2ApplicationSecurity')
|
50
|
-
Oauth2AccessCodeSecurity = openapi_class.call('definitions', 'oauth2AccessCodeSecurity')
|
51
|
-
Oauth2Scopes = openapi_class.call('definitions', 'oauth2Scopes')
|
52
|
-
Title = openapi_class.call('definitions', 'title')
|
53
|
-
Description = openapi_class.call('definitions', 'description')
|
54
|
-
Default = openapi_class.call('definitions', 'default')
|
55
|
-
MultipleOf = openapi_class.call('definitions', 'multipleOf')
|
56
|
-
Maximum = openapi_class.call('definitions', 'maximum')
|
57
|
-
ExclusiveMaximum = openapi_class.call('definitions', 'exclusiveMaximum')
|
58
|
-
Minimum = openapi_class.call('definitions', 'minimum')
|
59
|
-
ExclusiveMinimum = openapi_class.call('definitions', 'exclusiveMinimum')
|
60
|
-
MaxLength = openapi_class.call('definitions', 'maxLength')
|
61
|
-
MinLength = openapi_class.call('definitions', 'minLength')
|
62
|
-
Pattern = openapi_class.call('definitions', 'pattern')
|
63
|
-
MaxItems = openapi_class.call('definitions', 'maxItems')
|
64
|
-
MinItems = openapi_class.call('definitions', 'minItems')
|
65
|
-
UniqueItems = openapi_class.call('definitions', 'uniqueItems')
|
66
|
-
Enum = openapi_class.call('definitions', 'enum')
|
67
|
-
JsonReference = openapi_class.call('definitions', 'jsonReference')
|
140
|
+
class Operation
|
141
|
+
attr_accessor :path
|
142
|
+
attr_accessor :http_method
|
68
143
|
|
69
|
-
|
70
|
-
|
71
|
-
|
144
|
+
# there should only be one body parameter; this returns it
|
145
|
+
def body_parameter
|
146
|
+
(parameters || []).detect do |parameter|
|
147
|
+
parameter['in'] == 'body'
|
148
|
+
end
|
149
|
+
end
|
72
150
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
151
|
+
def request_schema
|
152
|
+
if body_parameter && body_parameter['schema']
|
153
|
+
Scorpio::Schema.new(body_parameter['schema'])
|
154
|
+
end
|
77
155
|
end
|
78
156
|
end
|
79
157
|
end
|
@@ -2,7 +2,7 @@ require 'scorpio'
|
|
2
2
|
require 'pickle'
|
3
3
|
|
4
4
|
module Scorpio
|
5
|
-
class
|
5
|
+
class ResourceBase
|
6
6
|
module PickleAdapter
|
7
7
|
include ::Pickle::Adapter::Base
|
8
8
|
|
@@ -10,12 +10,12 @@ module Scorpio
|
|
10
10
|
#
|
11
11
|
# all of the Scorpio models MUST be loaded before this gets called.
|
12
12
|
def self.model_classes
|
13
|
-
ObjectSpace.each_object(Class).select { |klass| klass < ::Scorpio::
|
13
|
+
ObjectSpace.each_object(Class).select { |klass| klass < ::Scorpio::ResourceBase }
|
14
14
|
end
|
15
15
|
|
16
16
|
# get a list of column names for a given class
|
17
17
|
def self.column_names(klass)
|
18
|
-
klass.all_schema_properties
|
18
|
+
klass.all_schema_properties.to_a
|
19
19
|
end
|
20
20
|
|
21
21
|
# Get an instance by id of the model
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'addressable/template'
|
2
|
-
require '
|
2
|
+
require 'faraday'
|
3
|
+
require 'scorpio/util/faraday/response_media_type'
|
3
4
|
|
4
5
|
module Scorpio
|
5
6
|
# see also Faraday::Env::MethodsWithBodies
|
@@ -7,7 +8,7 @@ module Scorpio
|
|
7
8
|
class RequestSchemaFailure < Error
|
8
9
|
end
|
9
10
|
|
10
|
-
class
|
11
|
+
class ResourceBase
|
11
12
|
class << self
|
12
13
|
def define_inheritable_accessor(accessor, options = {})
|
13
14
|
if options[:default_getter]
|
@@ -36,9 +37,6 @@ module Scorpio
|
|
36
37
|
klass.instance_exec(&options[:on_set])
|
37
38
|
end
|
38
39
|
end
|
39
|
-
if options[:update_methods]
|
40
|
-
update_dynamic_methods
|
41
|
-
end
|
42
40
|
end
|
43
41
|
end
|
44
42
|
end
|
@@ -46,35 +44,88 @@ module Scorpio
|
|
46
44
|
# (except in the unlikely event it is overwritten by a subclass)
|
47
45
|
define_inheritable_accessor(:openapi_document_class)
|
48
46
|
# the openapi document
|
49
|
-
define_inheritable_accessor(:
|
50
|
-
define_inheritable_accessor(:
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
47
|
+
define_inheritable_accessor(:tag_name, on_set: -> { update_dynamic_methods })
|
48
|
+
define_inheritable_accessor(:represented_schemas, default_value: [], on_set: proc do
|
49
|
+
unless represented_schemas.respond_to?(:to_ary)
|
50
|
+
raise(TypeError, "represented_schemas must be an array. received: #{represented_schemas.pretty_inspect.chomp}")
|
51
|
+
end
|
52
|
+
if represented_schemas.all? { |s| s.is_a?(Scorpio::Schema) }
|
53
|
+
represented_schemas.each do |schema|
|
54
|
+
openapi_document_class.models_by_schema = openapi_document_class.models_by_schema.merge(schema => self)
|
55
|
+
end
|
56
|
+
update_dynamic_methods
|
57
|
+
else
|
58
|
+
self.represented_schemas = self.represented_schemas.map do |schema|
|
59
|
+
unless schema.is_a?(Scorpio::Schema)
|
60
|
+
schema = Scorpio::Schema.new(schema)
|
61
|
+
end
|
62
|
+
unless schema['type'].nil? || schema.describes_hash?
|
63
|
+
raise(TypeError, "given schema for #{self.inspect} not of type object - type must be object for Scorpio ResourceBase to represent this schema. schema is: #{schema.pretty_inspect.chomp}")
|
64
|
+
end
|
65
|
+
schema
|
66
|
+
end
|
58
67
|
end
|
59
68
|
end)
|
60
|
-
define_inheritable_accessor(:schemas_by_key, default_value: {})
|
61
|
-
define_inheritable_accessor(:schemas_by_path)
|
62
|
-
define_inheritable_accessor(:schemas_by_id, default_value: {})
|
63
69
|
define_inheritable_accessor(:models_by_schema, default_value: {})
|
64
|
-
|
70
|
+
# the base url to which paths are appended.
|
71
|
+
# by default this looks at the openapi document's schemes, picking https or http first.
|
72
|
+
# it looks at the openapi_document's host and basePath.
|
73
|
+
# a model overriding this MUST include the openapi document's basePath if defined, e.g.
|
74
|
+
# class MyModel
|
75
|
+
# self.base_url = File.join('https://example.com/', openapi_document.basePath)
|
76
|
+
# end
|
77
|
+
define_inheritable_accessor(:base_url, default_getter: -> {
|
78
|
+
if openapi_document.schemes.nil?
|
79
|
+
scheme = 'https'
|
80
|
+
elsif openapi_document.schemes.respond_to?(:to_ary)
|
81
|
+
# prefer https, then http, then anything else since we probably don't support.
|
82
|
+
scheme = openapi_document.schemes.sort_by { |s| ['https', 'http'].index(s) || (1.0 / 0) }.first
|
83
|
+
end
|
84
|
+
if openapi_document.host && scheme
|
85
|
+
Addressable::URI.new(
|
86
|
+
scheme: scheme,
|
87
|
+
host: openapi_document.host,
|
88
|
+
path: openapi_document.basePath,
|
89
|
+
).to_s
|
90
|
+
end
|
91
|
+
})
|
65
92
|
|
93
|
+
define_inheritable_accessor(:user_agent, default_getter: -> {
|
94
|
+
"Scorpio/#{Scorpio::VERSION} (https://github.com/notEthan/scorpio) Faraday/#{Faraday::VERSION} Ruby/#{RUBY_VERSION}"
|
95
|
+
})
|
66
96
|
define_inheritable_accessor(:faraday_request_middleware, default_value: [])
|
67
97
|
define_inheritable_accessor(:faraday_adapter, default_getter: proc { Faraday.default_adapter })
|
68
98
|
define_inheritable_accessor(:faraday_response_middleware, default_value: [])
|
69
99
|
class << self
|
70
|
-
def
|
100
|
+
def openapi_document
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
104
|
+
def openapi_document=(openapi_document)
|
105
|
+
self.openapi_document_class = self
|
106
|
+
|
71
107
|
if openapi_document.is_a?(Hash)
|
72
|
-
openapi_document = OpenAPI::Document.new(openapi_document)
|
108
|
+
openapi_document = OpenAPI::V2::Document.new(openapi_document)
|
109
|
+
end
|
110
|
+
|
111
|
+
begin
|
112
|
+
singleton_class.instance_exec { remove_method(:openapi_document) }
|
113
|
+
rescue NameError
|
73
114
|
end
|
115
|
+
define_singleton_method(:openapi_document) { openapi_document }
|
116
|
+
define_singleton_method(:openapi_document=) do
|
117
|
+
if self == openapi_document_class
|
118
|
+
raise(ArgumentError, "openapi_document may only be set once on #{self.inspect}")
|
119
|
+
else
|
120
|
+
raise(ArgumentError, "openapi_document may not be overridden on subclass #{self.inspect} after it was set on #{openapi_document_class.inspect}")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
update_dynamic_methods
|
124
|
+
|
74
125
|
openapi_document.paths.each do |path, path_item|
|
75
126
|
path_item.each do |http_method, operation|
|
76
127
|
next if http_method == 'parameters' # parameters is not an operation. TOOD maybe just select the keys that are http methods?
|
77
|
-
unless operation.is_a?(Scorpio::OpenAPI::Operation)
|
128
|
+
unless operation.is_a?(Scorpio::OpenAPI::V2::Operation)
|
78
129
|
raise("bad operation at #{operation.fragment}: #{operation.pretty_inspect}")
|
79
130
|
end
|
80
131
|
operation.path = path
|
@@ -83,18 +134,7 @@ module Scorpio
|
|
83
134
|
end
|
84
135
|
|
85
136
|
openapi_document.validate!
|
86
|
-
|
87
|
-
self.schemas_by_key = {}
|
88
|
-
self.schemas_by_id = {}
|
89
|
-
self.openapi_document = openapi_document
|
90
|
-
(openapi_document.definitions || {}).each do |schema_key, schema|
|
91
|
-
if schema['id']
|
92
|
-
# this isn't actually allowed by openapi's definition. whatever.
|
93
|
-
self.schemas_by_id = self.schemas_by_id.merge(schema['id'] => schema)
|
94
|
-
end
|
95
|
-
self.schemas_by_path = self.schemas_by_path.merge(schema.object.fragment => schema)
|
96
|
-
self.schemas_by_key = self.schemas_by_key.merge(schema_key => schema)
|
97
|
-
end
|
137
|
+
|
98
138
|
update_dynamic_methods
|
99
139
|
end
|
100
140
|
|
@@ -104,12 +144,7 @@ module Scorpio
|
|
104
144
|
end
|
105
145
|
|
106
146
|
def all_schema_properties
|
107
|
-
|
108
|
-
unless schema['type'] == 'object'
|
109
|
-
raise "definition key #{schema_key} for #{self} is not of type object - type must be object for Scorpio Model to represent this schema" # TODO class
|
110
|
-
end
|
111
|
-
schema['properties'].keys
|
112
|
-
end.inject([], &:|)
|
147
|
+
represented_schemas.map(&:described_hash_property_names).inject(Set.new, &:|)
|
113
148
|
end
|
114
149
|
|
115
150
|
def update_instance_accessors
|
@@ -128,14 +163,11 @@ module Scorpio
|
|
128
163
|
end
|
129
164
|
|
130
165
|
def operation_for_resource_class?(operation)
|
131
|
-
return false unless
|
166
|
+
return false unless tag_name
|
132
167
|
|
133
|
-
return true if operation
|
168
|
+
return true if operation.tags.respond_to?(:to_ary) && operation.tags.include?(tag_name)
|
134
169
|
|
135
|
-
|
136
|
-
|
137
|
-
request_schema = operation.body_parameter['schema'] if operation.body_parameter
|
138
|
-
if request_schema && schemas_by_key.any? { |key, as| as == request_schema && definition_keys.include?(key) }
|
170
|
+
if operation.request_schema && represented_schemas.include?(operation.request_schema)
|
139
171
|
return true
|
140
172
|
end
|
141
173
|
|
@@ -145,11 +177,8 @@ module Scorpio
|
|
145
177
|
def operation_for_resource_instance?(operation)
|
146
178
|
return false unless operation_for_resource_class?(operation)
|
147
179
|
|
148
|
-
request_schema = operation.body_parameter['schema'] if operation.body_parameter
|
149
|
-
|
150
180
|
# define an instance method if the request schema is for this model
|
151
|
-
request_resource_is_self = request_schema &&
|
152
|
-
schemas_by_key.any? { |key, as| as == request_schema && definition_keys.include?(key) }
|
181
|
+
request_resource_is_self = operation.request_schema && represented_schemas.include?(operation.request_schema)
|
153
182
|
|
154
183
|
# also define an instance method depending on certain attributes the request description
|
155
184
|
# might have in common with the model's schema attributes
|
@@ -165,27 +194,21 @@ module Scorpio
|
|
165
194
|
# should we define an instance method?
|
166
195
|
#request_attributes |= method_desc['parameters'] ? method_desc['parameters'].keys : []
|
167
196
|
|
168
|
-
schema_attributes =
|
169
|
-
schema = schemas_by_key[schema_key]
|
170
|
-
schema['type'] == 'object' && schema['properties'] ? schema['properties'].keys : []
|
171
|
-
end.inject([], &:|)
|
197
|
+
schema_attributes = represented_schemas.map(&:described_hash_property_names).inject(Set.new, &:|)
|
172
198
|
|
173
|
-
return request_resource_is_self || (request_attributes & schema_attributes).any?
|
199
|
+
return request_resource_is_self || (request_attributes & schema_attributes.to_a).any?
|
174
200
|
end
|
175
201
|
|
176
202
|
def method_names_by_operation
|
177
203
|
@method_names_by_operation ||= Hash.new do |h, operation|
|
178
204
|
h[operation] = begin
|
179
|
-
raise(ArgumentError, operation.pretty_inspect) unless operation.is_a?(Scorpio::OpenAPI::Operation)
|
180
|
-
|
181
|
-
|
182
|
-
elsif resource_name && operation.operationId =~ /\A#{Regexp.escape(resource_name)}\.(\w+)\z/
|
205
|
+
raise(ArgumentError, operation.pretty_inspect) unless operation.is_a?(Scorpio::OpenAPI::V2::Operation)
|
206
|
+
|
207
|
+
if operation.tags.respond_to?(:to_ary) && operation.tags.include?(tag_name) && operation.operationId =~ /\A#{Regexp.escape(tag_name)}\.(\w+)\z/
|
183
208
|
method_name = $1
|
184
209
|
else
|
185
|
-
method_name = operation.operationId
|
210
|
+
method_name = operation.operationId
|
186
211
|
end
|
187
|
-
method_name = '_' + method_name unless method_name[/\A[a-zA-Z_]/]
|
188
|
-
method_name.gsub(/[^\w]/, '_')
|
189
212
|
end
|
190
213
|
end
|
191
214
|
end
|
@@ -214,36 +237,14 @@ module Scorpio
|
|
214
237
|
end
|
215
238
|
end
|
216
239
|
|
217
|
-
def deref_schema(schema)
|
218
|
-
schema = schema.object if schema.is_a?(Scorpio::SchemaObjectBase)
|
219
|
-
schema = schema.deref if schema.is_a?(Scorpio::JSON::Node)
|
220
|
-
schema && schemas_by_id[schema['$ref']] || schema
|
221
|
-
end
|
222
|
-
|
223
|
-
MODULES_FOR_JSON_SCHEMA_TYPES = {
|
224
|
-
'object' => [Hash],
|
225
|
-
'array' => [Array, Set],
|
226
|
-
'string' => [String],
|
227
|
-
'integer' => [Integer],
|
228
|
-
'number' => [Numeric],
|
229
|
-
'boolean' => [TrueClass, FalseClass],
|
230
|
-
'null' => [NilClass],
|
231
|
-
}
|
232
|
-
|
233
240
|
def connection
|
234
|
-
Faraday.new do |c|
|
235
|
-
unless faraday_request_middleware.any? { |m| [*m].first == :json }
|
236
|
-
c.request :json
|
237
|
-
end
|
241
|
+
Faraday.new(:headers => {'User-Agent' => user_agent}) do |c|
|
238
242
|
faraday_request_middleware.each do |m|
|
239
243
|
c.request(*m)
|
240
244
|
end
|
241
245
|
faraday_response_middleware.each do |m|
|
242
246
|
c.response(*m)
|
243
247
|
end
|
244
|
-
unless faraday_response_middleware.any? { |m| [*m].first == :json }
|
245
|
-
c.response :json, :content_type => /\bjson$/, :preserve_raw => true
|
246
|
-
end
|
247
248
|
c.adapter(*faraday_adapter)
|
248
249
|
end
|
249
250
|
end
|
@@ -266,7 +267,10 @@ module Scorpio
|
|
266
267
|
"which were empty: #{empty_variables.inspect}")
|
267
268
|
end
|
268
269
|
path = path_template.expand(template_params)
|
269
|
-
|
270
|
+
# we do not use Addressable::URI#join as the paths should just be concatenated, not resolved.
|
271
|
+
# we use File.join just to deal with consecutive slashes.
|
272
|
+
url = File.join(base_url, path)
|
273
|
+
url = Addressable::URI.parse(url)
|
270
274
|
# assume that call_params must be included somewhere. model_attributes are a source of required things
|
271
275
|
# but not required to be here.
|
272
276
|
other_params = call_params
|
@@ -274,14 +278,13 @@ module Scorpio
|
|
274
278
|
other_params.reject! { |k, _| path_template.variables.include?(k) }
|
275
279
|
end
|
276
280
|
|
277
|
-
|
278
|
-
if request_schema
|
281
|
+
if operation.request_schema
|
279
282
|
# TODO deal with model_attributes / call_params better in nested whatever
|
280
283
|
if call_params.nil?
|
281
|
-
body = request_body_for_schema(model_attributes, request_schema)
|
284
|
+
body = request_body_for_schema(model_attributes, operation.request_schema)
|
282
285
|
elsif call_params.is_a?(Hash)
|
283
|
-
body = request_body_for_schema(model_attributes.merge(call_params), request_schema)
|
284
|
-
body.
|
286
|
+
body = request_body_for_schema(model_attributes.merge(call_params), operation.request_schema)
|
287
|
+
body = body.merge(call_params) # TODO
|
285
288
|
else
|
286
289
|
body = call_params
|
287
290
|
end
|
@@ -300,7 +303,59 @@ module Scorpio
|
|
300
303
|
end
|
301
304
|
end
|
302
305
|
|
303
|
-
|
306
|
+
request_headers = {}
|
307
|
+
|
308
|
+
if METHODS_WITH_BODIES.any? { |m| m.to_s == http_method.downcase.to_s } && body != nil
|
309
|
+
consumes = operation.consumes || openapi_document.consumes || []
|
310
|
+
if consumes.include?("application/json") || (!body.respond_to?(:to_str) && consumes.empty?)
|
311
|
+
# if we have a body that's not a string and no indication of how to serialize it, we guess json.
|
312
|
+
request_headers['Content-Type'] = "application/json"
|
313
|
+
unless body.respond_to?(:to_str)
|
314
|
+
body = ::JSON.pretty_generate(Typelike.as_json(body))
|
315
|
+
end
|
316
|
+
elsif consumes.include?("application/x-www-form-urlencoded")
|
317
|
+
request_headers['Content-Type'] = "application/x-www-form-urlencoded"
|
318
|
+
unless body.respond_to?(:to_str)
|
319
|
+
body = URI.encode_www_form(body)
|
320
|
+
end
|
321
|
+
elsif body.is_a?(String)
|
322
|
+
if consumes.size == 1
|
323
|
+
request_headers['Content-Type'] = consumes.first
|
324
|
+
end
|
325
|
+
else
|
326
|
+
raise("do not know how to serialize for #{consumes.inspect}: #{body.pretty_inspect.chomp}")
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
response = connection.run_request(http_method, url, body, request_headers)
|
331
|
+
|
332
|
+
if response.media_type == 'application/json'
|
333
|
+
if response.body.empty?
|
334
|
+
response_object = nil
|
335
|
+
else
|
336
|
+
begin
|
337
|
+
response_object = ::JSON.parse(response.body)
|
338
|
+
rescue ::JSON::ParserError
|
339
|
+
# TODO warn
|
340
|
+
response_object = response.body
|
341
|
+
end
|
342
|
+
end
|
343
|
+
else
|
344
|
+
response_object = response.body
|
345
|
+
end
|
346
|
+
|
347
|
+
if operation.responses
|
348
|
+
_, operation_response = operation.responses.detect { |k, v| k.to_s == response.status.to_s }
|
349
|
+
operation_response ||= operation.responses['default']
|
350
|
+
response_schema = operation_response['schema'] if operation_response
|
351
|
+
end
|
352
|
+
if response_schema
|
353
|
+
# not too sure about this, but I don't think it makes sense to instantiate things that are
|
354
|
+
# not hash or array as a SchemaInstanceBase
|
355
|
+
if response_object.respond_to?(:to_hash) || response_object.respond_to?(:to_ary)
|
356
|
+
response_object = Scorpio.class_for_schema(response_schema).new(response_object)
|
357
|
+
end
|
358
|
+
end
|
304
359
|
|
305
360
|
error_class = Scorpio.error_classes_by_status[response.status]
|
306
361
|
error_class ||= if (400..499).include?(response.status)
|
@@ -312,27 +367,28 @@ module Scorpio
|
|
312
367
|
end
|
313
368
|
if error_class
|
314
369
|
message = "Error calling operation #{operation.operationId} on #{self}:\n" + (response.env[:raw_body] || response.env.body)
|
315
|
-
raise
|
370
|
+
raise(error_class.new(message).tap do |e|
|
371
|
+
e.faraday_response = response
|
372
|
+
e.response_object = response_object
|
373
|
+
end)
|
316
374
|
end
|
317
375
|
|
318
|
-
if operation.responses
|
319
|
-
_, operation_response = operation.responses.detect { |k, v| k.to_s == response.status.to_s }
|
320
|
-
operation_response ||= operation.responses['default']
|
321
|
-
response_schema = operation_response.schema if operation_response
|
322
|
-
end
|
323
376
|
initialize_options = {
|
324
377
|
'persisted' => true,
|
325
378
|
'source' => {'operationId' => operation.operationId, 'call_params' => call_params, 'url' => url.to_s},
|
326
379
|
'response' => response,
|
327
380
|
}
|
328
|
-
response_object_to_instances(
|
381
|
+
response_object_to_instances(response_object, initialize_options)
|
329
382
|
end
|
330
383
|
|
331
384
|
def request_body_for_schema(object, schema)
|
332
|
-
|
333
|
-
if object.is_a?(Scorpio::Model)
|
385
|
+
if object.is_a?(Scorpio::ResourceBase)
|
334
386
|
# TODO request_schema_fail unless schema is for given model type
|
335
|
-
request_body_for_schema(object.
|
387
|
+
request_body_for_schema(object.attributes, schema)
|
388
|
+
elsif object.is_a?(Scorpio::SchemaInstanceBase)
|
389
|
+
request_body_for_schema(object.instance, schema)
|
390
|
+
elsif object.is_a?(Scorpio::JSON::Node)
|
391
|
+
request_body_for_schema(object.content, schema)
|
336
392
|
else
|
337
393
|
if object.is_a?(Hash)
|
338
394
|
object.map do |key, value|
|
@@ -391,7 +447,7 @@ module Scorpio
|
|
391
447
|
request_body_for_schema(el, subschema)
|
392
448
|
end
|
393
449
|
else
|
394
|
-
# TODO maybe raise on anything not
|
450
|
+
# TODO maybe raise on anything not serializable
|
395
451
|
# TODO check conformance to schema, request_schema_fail if not
|
396
452
|
object
|
397
453
|
end
|
@@ -402,36 +458,27 @@ module Scorpio
|
|
402
458
|
raise(RequestSchemaFailure, "object does not conform to schema.\nobject = #{object.pretty_inspect}\nschema = #{::JSON.pretty_generate(schema, quirks_mode: true)}")
|
403
459
|
end
|
404
460
|
|
405
|
-
def response_object_to_instances(object,
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
model = models_by_schema[schema_as_key]
|
424
|
-
if model
|
425
|
-
model.new(out, initialize_options)
|
426
|
-
else
|
427
|
-
out
|
428
|
-
end
|
429
|
-
elsif schema['type'] == 'array' && MODULES_FOR_JSON_SCHEMA_TYPES['array'].any? { |m| object.is_a?(m) }
|
461
|
+
def response_object_to_instances(object, initialize_options = {})
|
462
|
+
if object.is_a?(SchemaInstanceBase)
|
463
|
+
model = models_by_schema[object.schema]
|
464
|
+
end
|
465
|
+
|
466
|
+
if object.respond_to?(:to_hash)
|
467
|
+
out = Typelike.modified_copy(object) do
|
468
|
+
object.map do |key, value|
|
469
|
+
{key => response_object_to_instances(value, initialize_options)}
|
470
|
+
end.inject({}, &:update)
|
471
|
+
end
|
472
|
+
if model
|
473
|
+
model.new(out, initialize_options)
|
474
|
+
else
|
475
|
+
out
|
476
|
+
end
|
477
|
+
elsif object.respond_to?(:to_ary)
|
478
|
+
Typelike.modified_copy(object) do
|
430
479
|
object.map do |element|
|
431
|
-
response_object_to_instances(element,
|
480
|
+
response_object_to_instances(element, initialize_options)
|
432
481
|
end
|
433
|
-
else
|
434
|
-
object
|
435
482
|
end
|
436
483
|
else
|
437
484
|
object
|
@@ -470,17 +517,13 @@ module Scorpio
|
|
470
517
|
|
471
518
|
# if we're making a POST or PUT and the request schema is this resource, we'll assume that
|
472
519
|
# the request is persisting this resource
|
473
|
-
|
474
|
-
request_resource_is_self = request_schema &&
|
475
|
-
request_schema['id'] &&
|
476
|
-
self.class.schemas_by_key.any? { |key, as| (as['id'] ? as['id'] == request_schema['id'] : as == request_schema) && self.class.definition_keys.include?(key) }
|
520
|
+
request_resource_is_self = operation.request_schema && self.class.represented_schemas.include?(operation.request_schema)
|
477
521
|
if @options['response'] && @options['response'].status && operation.responses
|
478
|
-
_,
|
522
|
+
_, response_schema_node = operation.responses.detect { |k, v| k.to_s == @options['response'].status.to_s }
|
479
523
|
end
|
480
|
-
response_schema =
|
481
|
-
response_resource_is_self = response_schema &&
|
482
|
-
|
483
|
-
if request_resource_is_self && %w(PUT POST).include?(api_method['httpMethod'])
|
524
|
+
response_schema = Scorpio::Schema.new(response_schema_node) if response_schema_node
|
525
|
+
response_resource_is_self = response_schema && self.class.represented_schemas.include?(response_schema)
|
526
|
+
if request_resource_is_self && %w(put post).include?(operation.http_method.to_s.downcase)
|
484
527
|
@persisted = true
|
485
528
|
|
486
529
|
if response_resource_is_self
|
@@ -491,17 +534,16 @@ module Scorpio
|
|
491
534
|
response
|
492
535
|
end
|
493
536
|
|
494
|
-
|
495
|
-
|
496
|
-
@attributes
|
537
|
+
def as_json
|
538
|
+
Typelike.as_json(@attributes)
|
497
539
|
end
|
498
540
|
|
499
541
|
def inspect
|
500
|
-
"\#<#{self.class.
|
542
|
+
"\#<#{self.class.inspect} #{attributes.inspect}>"
|
501
543
|
end
|
502
544
|
def pretty_print(q)
|
503
545
|
q.instance_exec(self) do |obj|
|
504
|
-
text "\#<#{obj.class.
|
546
|
+
text "\#<#{obj.class.inspect}"
|
505
547
|
group_sub {
|
506
548
|
nest(2) {
|
507
549
|
breakable ' '
|
@@ -514,7 +556,7 @@ module Scorpio
|
|
514
556
|
end
|
515
557
|
|
516
558
|
def fingerprint
|
517
|
-
{class: self.class, attributes: @attributes}
|
559
|
+
{class: self.class, attributes: Typelike.as_json(@attributes)}
|
518
560
|
end
|
519
561
|
include FingerprintHash
|
520
562
|
end
|