komoju 0.0.4 → 0.0.7
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +13 -10
- data/bin/generate-client +6 -2
- data/komoju.gemspec +2 -6
- data/lib/komoju.rb +1 -0
- data/lib/komoju/client.rb +541 -111
- data/lib/komoju/version.rb +1 -1
- metadata +8 -74
- data/vendor/heroics/lib/heroics.rb +0 -22
- data/vendor/heroics/lib/heroics/cli.rb +0 -88
- data/vendor/heroics/lib/heroics/client.rb +0 -109
- data/vendor/heroics/lib/heroics/client_generator.rb +0 -99
- data/vendor/heroics/lib/heroics/command.rb +0 -67
- data/vendor/heroics/lib/heroics/errors.rb +0 -6
- data/vendor/heroics/lib/heroics/link.rb +0 -120
- data/vendor/heroics/lib/heroics/naming.rb +0 -19
- data/vendor/heroics/lib/heroics/resource.rb +0 -30
- data/vendor/heroics/lib/heroics/schema.rb +0 -444
- data/vendor/heroics/lib/heroics/version.rb +0 -3
- data/vendor/heroics/test.rb +0 -42
- data/vendor/heroics/test/cli_test.rb +0 -236
- data/vendor/heroics/test/client_generator_test.rb +0 -34
- data/vendor/heroics/test/client_test.rb +0 -215
- data/vendor/heroics/test/command_test.rb +0 -214
- data/vendor/heroics/test/helper.rb +0 -204
- data/vendor/heroics/test/link_test.rb +0 -398
- data/vendor/heroics/test/naming_test.rb +0 -45
- data/vendor/heroics/test/resource_test.rb +0 -35
- data/vendor/heroics/test/schema_test.rb +0 -287
- data/vendor/heroics/test/version_test.rb +0 -9
@@ -1,67 +0,0 @@
|
|
1
|
-
module Heroics
|
2
|
-
class Command
|
3
|
-
# Instantiate a command.
|
4
|
-
#
|
5
|
-
# @param cli_name [String] The name of the CLI.
|
6
|
-
# @param link_schema [LinkSchema] The schema for the underlying link this
|
7
|
-
# command represents.
|
8
|
-
# @param client [Client] The client to use when making requests.
|
9
|
-
# @param output [IO] The stream to write output to.
|
10
|
-
def initialize(cli_name, link_schema, client, output)
|
11
|
-
@cli_name = cli_name
|
12
|
-
@link_schema = link_schema
|
13
|
-
@client = client
|
14
|
-
@output = output
|
15
|
-
end
|
16
|
-
|
17
|
-
# The command name.
|
18
|
-
def name
|
19
|
-
"#{@link_schema.pretty_resource_name}:#{@link_schema.pretty_name}"
|
20
|
-
end
|
21
|
-
|
22
|
-
# The command description.
|
23
|
-
def description
|
24
|
-
@link_schema.description
|
25
|
-
end
|
26
|
-
|
27
|
-
# Write usage information to the output stream.
|
28
|
-
def usage
|
29
|
-
parameters = @link_schema.parameters.map { |parameter| "<#{parameter}>" }
|
30
|
-
parameters = parameters.empty? ? '' : " #{parameters.join(' ')}"
|
31
|
-
example_body = @link_schema.example_body
|
32
|
-
body_parameter = example_body.nil? ? '' : ' <body>'
|
33
|
-
@output.write <<-USAGE
|
34
|
-
Usage: #{@cli_name} #{name}#{parameters}#{body_parameter}
|
35
|
-
|
36
|
-
Description:
|
37
|
-
#{description}
|
38
|
-
USAGE
|
39
|
-
if example_body
|
40
|
-
example_body = MultiJson.dump(example_body, pretty: true)
|
41
|
-
example_body = example_body.lines.map do |line|
|
42
|
-
" #{line}"
|
43
|
-
end.join
|
44
|
-
@output.write <<-USAGE
|
45
|
-
|
46
|
-
Body example:
|
47
|
-
#{example_body}
|
48
|
-
USAGE
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
# Run the command and write the results to the output stream.
|
53
|
-
#
|
54
|
-
# @param parameters [Array] The parameters to pass when making a request
|
55
|
-
# to run the command.
|
56
|
-
def run(*parameters)
|
57
|
-
resource_name = @link_schema.resource_name
|
58
|
-
name = @link_schema.name
|
59
|
-
result = @client.send(resource_name).send(name, *parameters)
|
60
|
-
result = result.to_a if result.instance_of?(Enumerator)
|
61
|
-
if result && !result.instance_of?(String)
|
62
|
-
result = MultiJson.dump(result, pretty: true)
|
63
|
-
end
|
64
|
-
@output.puts(result) unless result.nil?
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
@@ -1,120 +0,0 @@
|
|
1
|
-
module Heroics
|
2
|
-
# A link invokes requests with an HTTP server.
|
3
|
-
class Link
|
4
|
-
# Instantiate a link.
|
5
|
-
#
|
6
|
-
# @param url [String] The URL to use when making requests. Include the
|
7
|
-
# username and password to use with HTTP basic auth.
|
8
|
-
# @param link_schema [LinkSchema] The schema for this link.
|
9
|
-
# @param options [Hash] Configuration for the link. Possible keys
|
10
|
-
# include:
|
11
|
-
# - default_headers: Optionally, a set of headers to include in every
|
12
|
-
# request made by the client. Default is no custom headers.
|
13
|
-
# - cache: Optionally, a Moneta-compatible cache to store ETags.
|
14
|
-
# Default is no caching.
|
15
|
-
def initialize(url, link_schema, options={})
|
16
|
-
@root_url, @path_prefix = unpack_url(url)
|
17
|
-
@link_schema = link_schema
|
18
|
-
@default_headers = options[:default_headers] || {}
|
19
|
-
@cache = options[:cache] || Moneta.new(:Null)
|
20
|
-
end
|
21
|
-
|
22
|
-
# Make a request to the server.
|
23
|
-
#
|
24
|
-
# JSON content received with an ETag is cached. When the server returns a
|
25
|
-
# *304 Not Modified* status code content is loaded and returned from the
|
26
|
-
# cache. The cache considers headers, in addition to the URL path, when
|
27
|
-
# creating keys so that requests to the same path, such as for paginated
|
28
|
-
# results, don't cause cache collisions.
|
29
|
-
#
|
30
|
-
# When the server returns a *206 Partial Content* status code the result
|
31
|
-
# is assumed to be an array and an enumerator is returned. The enumerator
|
32
|
-
# yields results from the response until they've been consumed at which
|
33
|
-
# point, if additional content is available from the server, it blocks and
|
34
|
-
# makes a request to fetch the subsequent page of data. This behaviour
|
35
|
-
# continues until the client stops iterating the enumerator or the dataset
|
36
|
-
# from the server has been entirely consumed.
|
37
|
-
#
|
38
|
-
# @param parameters [Array] The list of parameters to inject into the
|
39
|
-
# path. A request body can be passed as the final parameter and will
|
40
|
-
# always be converted to JSON before being transmitted.
|
41
|
-
# @raise [ArgumentError] Raised if either too many or too few parameters
|
42
|
-
# were provided.
|
43
|
-
# @return [String,Object,Enumerator] A string for text responses, an
|
44
|
-
# object for JSON responses, or an enumerator for list responses.
|
45
|
-
def run(*parameters)
|
46
|
-
path, body = @link_schema.format_path(parameters)
|
47
|
-
path = "#{@path_prefix}#{path}" unless @path_prefix == '/'
|
48
|
-
headers = @default_headers
|
49
|
-
if body
|
50
|
-
headers = headers.merge({'Content-Type' => @link_schema.content_type})
|
51
|
-
body = @link_schema.encode(body)
|
52
|
-
end
|
53
|
-
cache_key = "#{path}:#{headers.hash}"
|
54
|
-
if @link_schema.method == :get
|
55
|
-
etag = @cache["etag:#{cache_key}"]
|
56
|
-
headers = headers.merge({'If-None-Match' => etag}) if etag
|
57
|
-
end
|
58
|
-
|
59
|
-
connection = Excon.new(@root_url, thread_safe_sockets: true)
|
60
|
-
response = connection.request(method: @link_schema.method, path: path,
|
61
|
-
headers: headers, body: body,
|
62
|
-
expects: [200, 201, 202, 204, 206, 304])
|
63
|
-
content_type = response.headers['Content-Type']
|
64
|
-
if response.status == 304
|
65
|
-
MultiJson.load(@cache["data:#{cache_key}"])
|
66
|
-
elsif content_type && content_type =~ /application\/.*json/
|
67
|
-
etag = response.headers['ETag']
|
68
|
-
if etag
|
69
|
-
@cache["etag:#{cache_key}"] = etag
|
70
|
-
@cache["data:#{cache_key}"] = response.body
|
71
|
-
end
|
72
|
-
body = MultiJson.load(response.body)
|
73
|
-
if response.status == 206
|
74
|
-
next_range = response.headers['Next-Range']
|
75
|
-
Enumerator.new do |yielder|
|
76
|
-
while true do
|
77
|
-
# Yield the results we got in the body.
|
78
|
-
body.each do |item|
|
79
|
-
yielder << item
|
80
|
-
end
|
81
|
-
|
82
|
-
# Only make a request to get the next page if we have a valid
|
83
|
-
# next range.
|
84
|
-
break unless next_range
|
85
|
-
headers = headers.merge({'Range' => next_range})
|
86
|
-
response = connection.request(method: @link_schema.method,
|
87
|
-
path: path, headers: headers,
|
88
|
-
expects: [200, 201, 206])
|
89
|
-
body = MultiJson.load(response.body)
|
90
|
-
next_range = response.headers['Next-Range']
|
91
|
-
end
|
92
|
-
end
|
93
|
-
else
|
94
|
-
body
|
95
|
-
end
|
96
|
-
elsif !response.body.empty?
|
97
|
-
response.body
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
private
|
102
|
-
|
103
|
-
# Unpack the URL and split it into a root URL and a path prefix, if one
|
104
|
-
# exists.
|
105
|
-
#
|
106
|
-
# @param url [String] The complete base URL to use when making requests.
|
107
|
-
# @return [String,String] A (root URL, path) prefix pair.
|
108
|
-
def unpack_url(url)
|
109
|
-
root_url = []
|
110
|
-
path_prefix = ''
|
111
|
-
parts = URI.split(url)
|
112
|
-
root_url << "#{parts[0]}://"
|
113
|
-
root_url << "#{parts[1]}@" unless parts[1].nil?
|
114
|
-
root_url << "#{parts[2]}"
|
115
|
-
root_url << ":#{parts[3]}" unless parts[3].nil?
|
116
|
-
path_prefix = parts[5]
|
117
|
-
return root_url.join(''), path_prefix
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module Heroics
|
2
|
-
# Process a name to make it suitable for use as a Ruby method name.
|
3
|
-
#
|
4
|
-
# @param name [String] The name to process.
|
5
|
-
# @return [String] The new name with capitals converted to lowercase, and
|
6
|
-
# dashes and spaces converted to underscores.
|
7
|
-
def self.ruby_name(name)
|
8
|
-
name.downcase.gsub(/[- ]/, '_')
|
9
|
-
end
|
10
|
-
|
11
|
-
# Process a name to make it suitable for use as a pretty command name.
|
12
|
-
#
|
13
|
-
# @param name [String] The name to process.
|
14
|
-
# @return [String] The new name with capitals converted to lowercase, and
|
15
|
-
# underscores and spaces converted to dashes.
|
16
|
-
def self.pretty_name(name)
|
17
|
-
name.downcase.gsub(/[_ ]/, '-')
|
18
|
-
end
|
19
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
module Heroics
|
2
|
-
# A resource with methods mapped to API links.
|
3
|
-
class Resource
|
4
|
-
# Instantiate a resource.
|
5
|
-
#
|
6
|
-
# @param links [Hash<String,Link>] A hash that maps method names to links.
|
7
|
-
def initialize(links)
|
8
|
-
@links = links
|
9
|
-
end
|
10
|
-
|
11
|
-
private
|
12
|
-
|
13
|
-
# Find a link and invoke it.
|
14
|
-
#
|
15
|
-
# @param name [String] The name of the method to invoke.
|
16
|
-
# @param parameters [Array] The arguments to pass to the method. This
|
17
|
-
# should always be a `Hash` mapping parameter names to values.
|
18
|
-
# @raise [NoMethodError] Raised if the name doesn't match a known link.
|
19
|
-
# @return [String,Array,Hash] The response received from the server. JSON
|
20
|
-
# responses are automatically decoded into Ruby objects.
|
21
|
-
def method_missing(name, *parameters)
|
22
|
-
link = @links[name.to_s]
|
23
|
-
if link.nil?
|
24
|
-
address = "<#{self.class.name}:0x00#{(self.object_id << 1).to_s(16)}>"
|
25
|
-
raise NoMethodError.new("undefined method `#{name}' for ##{address}")
|
26
|
-
end
|
27
|
-
link.run(*parameters)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,444 +0,0 @@
|
|
1
|
-
module Heroics
|
2
|
-
# A wrapper around a bare JSON schema to make it easier to use.
|
3
|
-
class Schema
|
4
|
-
attr_reader :schema
|
5
|
-
|
6
|
-
# Instantiate a schema.
|
7
|
-
#
|
8
|
-
# @param schema [Hash] The bare JSON schema to wrap.
|
9
|
-
def initialize(schema)
|
10
|
-
@schema = schema
|
11
|
-
@resources = {}
|
12
|
-
@schema['properties'].each do |key, value|
|
13
|
-
@resources[key] = ResourceSchema.new(@schema, key)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
# A description of the API.
|
18
|
-
def description
|
19
|
-
@schema['description']
|
20
|
-
end
|
21
|
-
|
22
|
-
# Get a schema for a named resource.
|
23
|
-
#
|
24
|
-
# @param name [String] The name of the resource.
|
25
|
-
# @raise [SchemaError] Raised if an unknown resource name is provided.
|
26
|
-
def resource(name)
|
27
|
-
if @schema['definitions'].has_key?(name)
|
28
|
-
ResourceSchema.new(@schema, name)
|
29
|
-
else
|
30
|
-
raise SchemaError.new("Unknown resource '#{name}'.")
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
# The resource schema children that are part of this schema.
|
35
|
-
#
|
36
|
-
# @return [Array<ResourceSchema>] The resource schema children.
|
37
|
-
def resources
|
38
|
-
@resources.values
|
39
|
-
end
|
40
|
-
|
41
|
-
# Get a simple human-readable representation of this client instance.
|
42
|
-
def inspect
|
43
|
-
"#<Heroics::Schema description=\"#{@schema['description']}\">"
|
44
|
-
end
|
45
|
-
alias to_s inspect
|
46
|
-
end
|
47
|
-
|
48
|
-
# A wrapper around a bare resource element in a JSON schema to make it
|
49
|
-
# easier to use.
|
50
|
-
class ResourceSchema
|
51
|
-
attr_reader :name
|
52
|
-
|
53
|
-
# Instantiate a resource schema.
|
54
|
-
#
|
55
|
-
# @param schema [Hash] The bare JSON schema to wrap.
|
56
|
-
# @param name [String] The name of the resource to identify in the schema.
|
57
|
-
def initialize(schema, name)
|
58
|
-
@schema = schema
|
59
|
-
@name = name
|
60
|
-
link_schema = schema['definitions'][name]['links'] || []
|
61
|
-
|
62
|
-
@links = Hash[link_schema.each_with_index.map do |link, link_index|
|
63
|
-
link_name = Heroics.ruby_name(link['title'])
|
64
|
-
[link_name, LinkSchema.new(schema, name, link_index)]
|
65
|
-
end]
|
66
|
-
end
|
67
|
-
|
68
|
-
# A description of the resource.
|
69
|
-
def description
|
70
|
-
@schema['definitions'][name]['description']
|
71
|
-
end
|
72
|
-
|
73
|
-
# Get a schema for a named link.
|
74
|
-
#
|
75
|
-
# @param name [String] The name of the link.
|
76
|
-
# @raise [SchemaError] Raised if an unknown link name is provided.
|
77
|
-
def link(name)
|
78
|
-
schema = @links[name]
|
79
|
-
raise SchemaError.new("Unknown link '#{name}'.") unless schema
|
80
|
-
schema
|
81
|
-
end
|
82
|
-
|
83
|
-
# The link schema children that are part of this resource schema.
|
84
|
-
#
|
85
|
-
# @return [Array<LinkSchema>] The link schema children.
|
86
|
-
def links
|
87
|
-
@links.values
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# A wrapper around a bare link element for a resource in a JSON schema to
|
92
|
-
# make it easier to use.
|
93
|
-
class LinkSchema
|
94
|
-
attr_reader :name, :resource_name, :description
|
95
|
-
|
96
|
-
# Instantiate a link schema.
|
97
|
-
#
|
98
|
-
# @param schema [Hash] The bare JSON schema to wrap.
|
99
|
-
# @param resource_name [String] The name of the resource to identify in
|
100
|
-
# the schema.
|
101
|
-
# @param link_index [Fixnum] The index of the link in the resource schema.
|
102
|
-
def initialize(schema, resource_name, link_index)
|
103
|
-
@schema = schema
|
104
|
-
@resource_name = resource_name
|
105
|
-
@link_index = link_index
|
106
|
-
@name = Heroics.ruby_name(link_schema['title'])
|
107
|
-
@description = link_schema['description']
|
108
|
-
end
|
109
|
-
|
110
|
-
# Get the resource name in pretty form.
|
111
|
-
#
|
112
|
-
# @return [String] The pretty resource name.
|
113
|
-
def pretty_resource_name
|
114
|
-
Heroics.pretty_name(resource_name)
|
115
|
-
end
|
116
|
-
|
117
|
-
# Get the link name in pretty form.
|
118
|
-
#
|
119
|
-
# @return [String] The pretty link name.
|
120
|
-
def pretty_name
|
121
|
-
Heroics.pretty_name(name)
|
122
|
-
end
|
123
|
-
|
124
|
-
# Get the HTTP method for this link.
|
125
|
-
#
|
126
|
-
# @return [Symbol] The HTTP method.
|
127
|
-
def method
|
128
|
-
link_schema['method'].downcase.to_sym
|
129
|
-
end
|
130
|
-
|
131
|
-
# Get the Content-Type for this link.
|
132
|
-
#
|
133
|
-
# @return [String] The Content-Type value
|
134
|
-
def content_type
|
135
|
-
link_schema['encType'] || 'application/json'
|
136
|
-
end
|
137
|
-
|
138
|
-
def encode(body)
|
139
|
-
case content_type
|
140
|
-
when 'application/x-www-form-urlencoded'
|
141
|
-
URI.encode_www_form(body)
|
142
|
-
when /application\/.*json/
|
143
|
-
MultiJson.dump(body)
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
# Get the names of the parameters this link expects.
|
148
|
-
#
|
149
|
-
# @return [Array<String>] The parameters.
|
150
|
-
def parameters
|
151
|
-
parameter_names = link_schema['href'].scan(PARAMETER_REGEX)
|
152
|
-
resolve_parameters(parameter_names)
|
153
|
-
end
|
154
|
-
|
155
|
-
# Get the names and descriptions of the parameters this link expects.
|
156
|
-
#
|
157
|
-
# @return [Hash<String, String>] A list of hashes with `name` and
|
158
|
-
# `description` key/value pairs describing parameters.
|
159
|
-
def parameter_details
|
160
|
-
parameter_names = link_schema['href'].scan(PARAMETER_REGEX)
|
161
|
-
parameters = resolve_parameter_details(parameter_names)
|
162
|
-
parameters << CollectionOptions.new if requires_collection_options?
|
163
|
-
parameters << BodyParameter.new if requires_request_body?
|
164
|
-
parameters
|
165
|
-
end
|
166
|
-
|
167
|
-
# Get an example request body.
|
168
|
-
#
|
169
|
-
# @return [Hash] A sample request body.
|
170
|
-
def example_body
|
171
|
-
if body_schema = link_schema['schema']
|
172
|
-
definitions = @schema['definitions'][@resource_name]['definitions']
|
173
|
-
Hash[body_schema['properties'].keys.map do |property|
|
174
|
-
# FIXME This is wrong! -jkakar
|
175
|
-
if definitions.has_key?(property)
|
176
|
-
example = definitions[property]['example']
|
177
|
-
else
|
178
|
-
example = ''
|
179
|
-
end
|
180
|
-
[property, example]
|
181
|
-
end]
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
# Inject parameters into the link href and return the body, if it exists.
|
186
|
-
#
|
187
|
-
# @param parameters [Array] The list of parameters to inject into the
|
188
|
-
# path.
|
189
|
-
# @raise [ArgumentError] Raised if either too many or too few parameters
|
190
|
-
# were provided.
|
191
|
-
# @return [String,Object] A path and request body pair. The body value is
|
192
|
-
# nil if a payload wasn't included in the list of parameters.
|
193
|
-
def format_path(parameters)
|
194
|
-
path = link_schema['href']
|
195
|
-
parameter_size = path.scan(PARAMETER_REGEX).size
|
196
|
-
too_few_parameters = parameter_size > parameters.size
|
197
|
-
# FIXME We should use the schema to detect when a request body is
|
198
|
-
# permitted and do the calculation correctly here. -jkakar
|
199
|
-
too_many_parameters = parameter_size < (parameters.size - 1)
|
200
|
-
if too_few_parameters || too_many_parameters
|
201
|
-
raise ArgumentError.new("wrong number of arguments " +
|
202
|
-
"(#{parameters.size} for #{parameter_size})")
|
203
|
-
end
|
204
|
-
|
205
|
-
(0..parameter_size).each do |i|
|
206
|
-
path = path.sub(PARAMETER_REGEX, format_parameter(parameters[i]))
|
207
|
-
end
|
208
|
-
body = parameters.slice(parameter_size)
|
209
|
-
return path, body
|
210
|
-
end
|
211
|
-
|
212
|
-
private
|
213
|
-
|
214
|
-
# Match parameters in definition strings.
|
215
|
-
PARAMETER_REGEX = /\{\([%\/a-zA-Z0-9_-]*\)\}/
|
216
|
-
|
217
|
-
# Get the raw link schema.
|
218
|
-
#
|
219
|
-
# @param [Hash] The raw link schema.
|
220
|
-
def link_schema
|
221
|
-
@schema['definitions'][@resource_name]['links'][@link_index]
|
222
|
-
end
|
223
|
-
|
224
|
-
def requires_collection_options?
|
225
|
-
return link_schema['rel'] == 'instances'
|
226
|
-
end
|
227
|
-
|
228
|
-
def requires_request_body?
|
229
|
-
return link_schema.has_key?('schema')
|
230
|
-
end
|
231
|
-
|
232
|
-
# Get the names of the parameters this link expects.
|
233
|
-
#
|
234
|
-
# @param parameters [Array] The names of the parameter definitions to
|
235
|
-
# convert to parameter names.
|
236
|
-
# @return [Array<String>] The parameters.
|
237
|
-
def resolve_parameters(parameters)
|
238
|
-
properties = @schema['definitions'][@resource_name]['properties']
|
239
|
-
return [''] if properties.nil?
|
240
|
-
definitions = Hash[properties.each_pair.map do |key, value|
|
241
|
-
[value['$ref'], key]
|
242
|
-
end]
|
243
|
-
parameters.map do |parameter|
|
244
|
-
definition_name = URI.unescape(parameter[2..-3])
|
245
|
-
if definitions.has_key?(definition_name)
|
246
|
-
definitions[definition_name]
|
247
|
-
else
|
248
|
-
definition_name = definition_name.split('/')[-1]
|
249
|
-
resource_definitions = @schema[
|
250
|
-
'definitions'][@resource_name]['definitions'][definition_name]
|
251
|
-
if resource_definitions.has_key?('anyOf')
|
252
|
-
resource_definitions['anyOf'].map do |property|
|
253
|
-
definitions[property['$ref']]
|
254
|
-
end.join('|')
|
255
|
-
else
|
256
|
-
resource_definitions['oneOf'].map do |property|
|
257
|
-
definitions[property['$ref']]
|
258
|
-
end.join('|')
|
259
|
-
end
|
260
|
-
end
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
# Get the parameters this link expects.
|
265
|
-
#
|
266
|
-
# @param parameters [Array] The names of the parameter definitions to
|
267
|
-
# convert to parameter names.
|
268
|
-
# @return [Array<Parameter|ParameterChoice>] A list of parameter instances
|
269
|
-
# that represent parameters to be injected into the link URL.
|
270
|
-
def resolve_parameter_details(parameters)
|
271
|
-
parameters.map do |parameter|
|
272
|
-
# URI decode parameters and strip the leading '{(' and trailing ')}'.
|
273
|
-
parameter = URI.unescape(parameter[2..-3])
|
274
|
-
|
275
|
-
# Split the path into components and discard the leading '#' that
|
276
|
-
# represents the root of the schema.
|
277
|
-
path = parameter.split('/')[1..-1]
|
278
|
-
info = lookup_parameter(path, @schema)
|
279
|
-
# The reference can be one of several values.
|
280
|
-
resource_name = path[1].gsub('-', '_')
|
281
|
-
if info.has_key?('anyOf')
|
282
|
-
ParameterChoice.new(resource_name,
|
283
|
-
unpack_multiple_parameters(info['anyOf']))
|
284
|
-
elsif info.has_key?('oneOf')
|
285
|
-
ParameterChoice.new(resource_name,
|
286
|
-
unpack_multiple_parameters(info['oneOf']))
|
287
|
-
else
|
288
|
-
name = path[-1]
|
289
|
-
Parameter.new(resource_name, name, info['description'])
|
290
|
-
end
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
# Unpack an 'anyOf' or 'oneOf' multi-parameter blob.
|
295
|
-
#
|
296
|
-
# @param parameters [Array<Hash>] An array of hashes containing '$ref'
|
297
|
-
# keys and definition values.
|
298
|
-
# @return [Array<Parameter>] An array of parameters extracted from the
|
299
|
-
# blob.
|
300
|
-
def unpack_multiple_parameters(parameters)
|
301
|
-
parameters.map do |info|
|
302
|
-
parameter = info['$ref']
|
303
|
-
path = parameter.split('/')[1..-1]
|
304
|
-
info = lookup_parameter(path, @schema)
|
305
|
-
resource_name = path.size > 2 ? path[1].gsub('-', '_') : nil
|
306
|
-
name = path[-1]
|
307
|
-
Parameter.new(resource_name, name, info['description'])
|
308
|
-
end
|
309
|
-
end
|
310
|
-
|
311
|
-
# Recursively walk the object hierarchy in the schema to resolve a given
|
312
|
-
# path. This is used to find property information related to definitions
|
313
|
-
# in link hrefs.
|
314
|
-
#
|
315
|
-
# @param path [Array<String>] An array of paths to walk, such as
|
316
|
-
# ['definitions', 'resource', 'definitions', 'property'].
|
317
|
-
# @param schema [Hash] The schema to walk.
|
318
|
-
def lookup_parameter(path, schema)
|
319
|
-
key = path[0]
|
320
|
-
remaining = path[1..-1]
|
321
|
-
if remaining.empty?
|
322
|
-
return schema[key]
|
323
|
-
else
|
324
|
-
lookup_parameter(remaining, schema[key])
|
325
|
-
end
|
326
|
-
end
|
327
|
-
|
328
|
-
# Convert a path parameter to a format suitable for use in a path.
|
329
|
-
#
|
330
|
-
# @param [Fixnum,String,TrueClass,FalseClass,Time] The parameter to format.
|
331
|
-
# @return [String] The formatted parameter.
|
332
|
-
def format_parameter(parameter)
|
333
|
-
formatted_parameter = parameter.instance_of?(Time) ? iso_format(parameter) : parameter.to_s
|
334
|
-
URI.escape formatted_parameter
|
335
|
-
end
|
336
|
-
|
337
|
-
# Convert a time to an ISO 8601 combined data and time format.
|
338
|
-
#
|
339
|
-
# @param time [Time] The time to convert to ISO 8601 format.
|
340
|
-
# @return [String] An ISO 8601 date in `YYYY-MM-DDTHH:MM:SSZ` format.
|
341
|
-
def iso_format(time)
|
342
|
-
time.getutc.strftime('%Y-%m-%dT%H:%M:%SZ')
|
343
|
-
end
|
344
|
-
end
|
345
|
-
|
346
|
-
# Download a JSON schema from a URL.
|
347
|
-
#
|
348
|
-
# @param url [String] The URL for the schema.
|
349
|
-
# @param options [Hash] Configuration for links. Possible keys include:
|
350
|
-
# - default_headers: Optionally, a set of headers to include in every
|
351
|
-
# request made by the client. Default is no custom headers.
|
352
|
-
# @return [Schema] The downloaded JSON schema.
|
353
|
-
def self.download_schema(url, options={})
|
354
|
-
default_headers = options.fetch(:default_headers, {})
|
355
|
-
response = Excon.get(url, headers: default_headers, expects: [200, 201])
|
356
|
-
Schema.new(MultiJson.load(response.body))
|
357
|
-
end
|
358
|
-
|
359
|
-
# The base parameter class
|
360
|
-
class BaseParameter
|
361
|
-
attr_reader :resource_name, :name, :description
|
362
|
-
|
363
|
-
# This is the used for generating the function signature
|
364
|
-
def signature
|
365
|
-
[@resource_name, @name].compact.join("_")
|
366
|
-
end
|
367
|
-
|
368
|
-
# A pretty representation of a parameter instance
|
369
|
-
def inspect
|
370
|
-
"#{self.class}(name=#{@name}, description=#{@description})"
|
371
|
-
end
|
372
|
-
end
|
373
|
-
|
374
|
-
# A representation of a parameter.
|
375
|
-
class Parameter < BaseParameter
|
376
|
-
def initialize(resource_name, name, description)
|
377
|
-
@resource_name = resource_name
|
378
|
-
@name = name
|
379
|
-
@description = description
|
380
|
-
end
|
381
|
-
|
382
|
-
# The name of the parameter, with the resource included, suitable for use
|
383
|
-
# in a function signature.
|
384
|
-
def name
|
385
|
-
[@resource_name, @name].compact.join("_")
|
386
|
-
end
|
387
|
-
end
|
388
|
-
|
389
|
-
# A representation of a body parameter.
|
390
|
-
class BodyParameter < BaseParameter
|
391
|
-
def initialize
|
392
|
-
@name = 'body'
|
393
|
-
@description = 'the object to pass as the request payload'
|
394
|
-
end
|
395
|
-
end
|
396
|
-
|
397
|
-
# Additional options to pass with a request
|
398
|
-
class CollectionOptions < BaseParameter
|
399
|
-
def initialize
|
400
|
-
@name = 'collection_options'
|
401
|
-
@description = 'additional collection options to pass with the request'
|
402
|
-
end
|
403
|
-
|
404
|
-
def signature
|
405
|
-
"#{@name} = {}"
|
406
|
-
end
|
407
|
-
end
|
408
|
-
|
409
|
-
# A representation of a set of parameters.
|
410
|
-
class ParameterChoice < BaseParameter
|
411
|
-
attr_reader :parameters
|
412
|
-
|
413
|
-
def initialize(resource_name, parameters)
|
414
|
-
@resource_name = resource_name
|
415
|
-
@parameters = parameters
|
416
|
-
end
|
417
|
-
|
418
|
-
# A name created by merging individual parameter descriptions, suitable
|
419
|
-
# for use in a function signature.
|
420
|
-
def name
|
421
|
-
@parameters.map do |parameter|
|
422
|
-
if parameter.resource_name
|
423
|
-
parameter.name
|
424
|
-
else
|
425
|
-
"#{@resource_name}_#{parameter.name}"
|
426
|
-
end
|
427
|
-
end.join('_or_')
|
428
|
-
end
|
429
|
-
|
430
|
-
def signature
|
431
|
-
name
|
432
|
-
end
|
433
|
-
|
434
|
-
# A description created by merging individual parameter descriptions.
|
435
|
-
def description
|
436
|
-
@parameters.map { |parameter| parameter.description }.join(' or ')
|
437
|
-
end
|
438
|
-
|
439
|
-
# A pretty representation of this instance.
|
440
|
-
def inspect
|
441
|
-
"ParameterChoice(parameters=#{@parameters})"
|
442
|
-
end
|
443
|
-
end
|
444
|
-
end
|