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