interpol 0.0.8 → 0.1.0
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.
- data/README.md +12 -1
- data/Rakefile +1 -1
- data/lib/interpol/configuration.rb +9 -2
- data/lib/interpol/documentation_app/views/endpoint.erb +9 -7
- data/lib/interpol/endpoint.rb +90 -18
- data/lib/interpol/errors.rb +3 -0
- data/lib/interpol/response_schema_validator.rb +2 -1
- data/lib/interpol/stub_app.rb +5 -3
- data/lib/interpol/test_helper.rb +5 -3
- data/lib/interpol/version.rb +1 -1
- metadata +4 -4
data/README.md
CHANGED
@@ -51,7 +51,9 @@ name: user_projects
|
|
51
51
|
route: /users/:user_id/projects
|
52
52
|
method: GET
|
53
53
|
definitions:
|
54
|
-
-
|
54
|
+
- message_type: response
|
55
|
+
versions: ["1.0"]
|
56
|
+
status_codes: ["2xx", "404"]
|
55
57
|
schema:
|
56
58
|
description: Returns a list of projects for the given user.
|
57
59
|
type: object
|
@@ -94,8 +96,17 @@ Let's look at this YAML file, point-by-point:
|
|
94
96
|
* The `definitions` array contains a list of versioned schema definitions, with
|
95
97
|
corresponding examples. Everytime you modify your schema and change the version,
|
96
98
|
you should add a new entry here.
|
99
|
+
* The `message_type` describes whether the following schema is for requests or responses.
|
100
|
+
It is an optional attribute that when omitted defaults to response. The only valid values
|
101
|
+
are `request` and `response`.
|
97
102
|
* The `versions` array lists the endpoint versions that should be associated with a
|
98
103
|
particular schema definition.
|
104
|
+
* The `status_codes` is an optional array of status code strings describing for which
|
105
|
+
status code or codes this schema applies to. `status_codes` is ignored if used with the
|
106
|
+
`request` `message_type`. When used with the `response` `message_type` it is an optional
|
107
|
+
attribute that defaults to all status codes. Valid formats for a status code are 3
|
108
|
+
characters. Each character must be a digit (0-9) or 'x' (wildcard). The following strings
|
109
|
+
are all valid: "200", "4xx", "x0x".
|
99
110
|
* The `schema` contains a [JSON schema](http://tools.ietf.org/html/draft-zyp-json-schema-03)
|
100
111
|
description of the contents of the endpoint. This schema definition is used by the
|
101
112
|
`SchemaValidation` middleware to ensure that your implementation of the endpoint
|
data/Rakefile
CHANGED
@@ -12,7 +12,7 @@ if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' # MRI only
|
|
12
12
|
|
13
13
|
desc "Run cane to check quality metrics"
|
14
14
|
Cane::RakeTask.new(:quality) do |cane|
|
15
|
-
cane.abc_max =
|
15
|
+
cane.abc_max = 16
|
16
16
|
cane.add_threshold 'coverage/coverage_percent.txt', :==, 100
|
17
17
|
cane.style_measure = 100
|
18
18
|
end
|
@@ -8,14 +8,21 @@ module Interpol
|
|
8
8
|
include HashFetcher
|
9
9
|
NoDefinitionFound = Class.new
|
10
10
|
|
11
|
-
def find_definition(method, path)
|
11
|
+
def find_definition(method, path, message_type, status_code = nil)
|
12
12
|
with_endpoint_matching(method, path) do |endpoint|
|
13
13
|
version = yield endpoint
|
14
|
-
endpoint.
|
14
|
+
find_definitions_for(endpoint, version, message_type).find do |definition|
|
15
|
+
definition.matches_status_code?(status_code)
|
16
|
+
end
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
18
20
|
private
|
21
|
+
def find_definitions_for(endpoint, version, message_type)
|
22
|
+
endpoint.definitions.find do |d|
|
23
|
+
d.first.version == version && d.first.message_type == message_type
|
24
|
+
end || []
|
25
|
+
end
|
19
26
|
|
20
27
|
def with_endpoint_matching(method, path)
|
21
28
|
method = method.downcase.to_sym
|
@@ -3,13 +3,15 @@
|
|
3
3
|
<h1><%= endpoint.name %></h1>
|
4
4
|
<h2><%= endpoint.method.to_s.upcase %> <%= endpoint.route %></h2>
|
5
5
|
|
6
|
-
<% endpoint.definitions.
|
7
|
-
|
8
|
-
<
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
6
|
+
<% endpoint.definitions.each do |definitions| %>
|
7
|
+
<% definitions.each do |definition| %>
|
8
|
+
<div class="versioned-endpoint-definition">
|
9
|
+
<h3><%= definition.message_type.capitalize %> - Version <%= definition.version %> - <%= definition.status_codes %></h3>
|
10
|
+
<br/>
|
11
|
+
<%= Interpol::Documentation.html_for_schema(definition.schema) %>
|
12
|
+
</div>
|
13
|
+
<hr/>
|
14
|
+
<% end %>
|
13
15
|
<% end %>
|
14
16
|
</div>
|
15
17
|
</div><!--/span-->
|
data/lib/interpol/endpoint.rb
CHANGED
@@ -27,23 +27,36 @@ module Interpol
|
|
27
27
|
validate_name!
|
28
28
|
end
|
29
29
|
|
30
|
-
def find_definition!(version)
|
31
|
-
@definitions.fetch(version) do
|
30
|
+
def find_definition!(version, message_type)
|
31
|
+
@definitions.fetch([message_type, version]) do
|
32
32
|
message = "No definition found for #{name} endpoint for version #{version}"
|
33
|
+
message << " and message_type #{message_type}"
|
33
34
|
raise ArgumentError.new(message)
|
34
35
|
end
|
35
36
|
end
|
36
37
|
|
37
|
-
def find_example_for!(version)
|
38
|
-
find_definition!(version).examples.first
|
38
|
+
def find_example_for!(version, message_type)
|
39
|
+
find_definition!(version, message_type).first.examples.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_example_status_code_for!(version)
|
43
|
+
find_definition!(version, 'response').first.example_status_code
|
39
44
|
end
|
40
45
|
|
41
46
|
def available_versions
|
42
|
-
definitions.map
|
47
|
+
definitions.map { |d| d.first.version }
|
43
48
|
end
|
44
49
|
|
45
50
|
def definitions
|
46
|
-
|
51
|
+
# sort all requests before all responses
|
52
|
+
# sort higher version numbers before lower version numbers
|
53
|
+
@definitions.values.sort do |x,y|
|
54
|
+
if x.first.message_type == y.first.message_type
|
55
|
+
y.first.version <=> x.first.version
|
56
|
+
else
|
57
|
+
x.first.message_type <=> y.first.message_type
|
58
|
+
end
|
59
|
+
end
|
47
60
|
end
|
48
61
|
|
49
62
|
def route_matches?(path)
|
@@ -66,12 +79,16 @@ module Interpol
|
|
66
79
|
end
|
67
80
|
end
|
68
81
|
|
82
|
+
DEFAULT_MESSAGE_TYPE = 'response'
|
83
|
+
|
69
84
|
def extract_definitions_from(endpoint_hash)
|
70
|
-
definitions = {}
|
85
|
+
definitions = Hash.new { |h, k| h[k] = [] }
|
71
86
|
|
72
87
|
fetch_from(endpoint_hash, 'definitions').each do |definition|
|
73
88
|
fetch_from(definition, 'versions').each do |version|
|
74
|
-
|
89
|
+
message_type = definition.fetch('message_type', DEFAULT_MESSAGE_TYPE)
|
90
|
+
key = [message_type, version]
|
91
|
+
definitions[key] << EndpointDefinition.new(name, version, message_type, definition)
|
75
92
|
end
|
76
93
|
end
|
77
94
|
|
@@ -90,13 +107,15 @@ module Interpol
|
|
90
107
|
# Provides the means to validate data against that version of the schema.
|
91
108
|
class EndpointDefinition
|
92
109
|
include HashFetcher
|
93
|
-
attr_reader :endpoint_name, :version, :schema, :examples
|
94
|
-
|
95
|
-
def initialize(endpoint_name, version, definition)
|
96
|
-
@endpoint_name
|
97
|
-
@
|
98
|
-
@
|
99
|
-
@
|
110
|
+
attr_reader :endpoint_name, :message_type, :version, :schema, :examples
|
111
|
+
|
112
|
+
def initialize(endpoint_name, version, message_type, definition)
|
113
|
+
@endpoint_name = endpoint_name
|
114
|
+
@message_type = message_type
|
115
|
+
@status_codes = StatusCodeMatcher.new(definition['status_codes'])
|
116
|
+
@version = version
|
117
|
+
@schema = fetch_from(definition, 'schema')
|
118
|
+
@examples = fetch_from(definition, 'examples').map { |e| EndpointExample.new(e, self) }
|
100
119
|
make_schema_strict!(@schema)
|
101
120
|
end
|
102
121
|
|
@@ -108,7 +127,19 @@ module Interpol
|
|
108
127
|
end
|
109
128
|
|
110
129
|
def description
|
111
|
-
"#{endpoint_name} (v. #{version})"
|
130
|
+
"#{endpoint_name} (v. #{version}, mt. #{message_type}, sc. #{status_codes})"
|
131
|
+
end
|
132
|
+
|
133
|
+
def status_codes
|
134
|
+
@status_codes.code_strings.join(',')
|
135
|
+
end
|
136
|
+
|
137
|
+
def matches_status_code?(status_code)
|
138
|
+
status_code.nil? || @status_codes.matches?(status_code)
|
139
|
+
end
|
140
|
+
|
141
|
+
def example_status_code
|
142
|
+
@example_status_code ||= @status_codes.example_status_code
|
112
143
|
end
|
113
144
|
|
114
145
|
private
|
@@ -127,6 +158,49 @@ module Interpol
|
|
127
158
|
end
|
128
159
|
end
|
129
160
|
|
161
|
+
# Holds the acceptable status codes for an enpoint entry
|
162
|
+
# Acceptable status code are either exact status codes (200, 404, etc)
|
163
|
+
# or partial status codes (2xx, 3xx, 4xx, etc). Currently, partial status
|
164
|
+
# codes can only be a digit followed by two lower-case x's.
|
165
|
+
class StatusCodeMatcher
|
166
|
+
attr_reader :code_strings
|
167
|
+
|
168
|
+
def initialize(codes)
|
169
|
+
codes = ["xxx"] if Array(codes).empty?
|
170
|
+
@code_strings = codes
|
171
|
+
validate!
|
172
|
+
end
|
173
|
+
|
174
|
+
def matches?(status_code)
|
175
|
+
code_regexes.any? { |re| re =~ status_code.to_s }
|
176
|
+
end
|
177
|
+
|
178
|
+
def example_status_code
|
179
|
+
example_status_code = "200"
|
180
|
+
code_strings.first.chars.each_with_index do |char, index|
|
181
|
+
example_status_code[index] = char if char != 'x'
|
182
|
+
end
|
183
|
+
example_status_code
|
184
|
+
end
|
185
|
+
|
186
|
+
private
|
187
|
+
def code_regexes
|
188
|
+
@code_regexes ||= code_strings.map do |string|
|
189
|
+
/\A#{string.gsub('x', '\d')}\z/
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def validate!
|
194
|
+
code_strings.each do |code|
|
195
|
+
# ensure code is 3 characters and all chars are a number or 'x'
|
196
|
+
# http://rubular.com/r/4sl68Bb4XO
|
197
|
+
unless code =~ /\A[\dx]{3}\Z/
|
198
|
+
raise StatusCodeMatcherArgumentError, "#{code} is not a valid format"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
130
204
|
# Wraps an example for a particular endpoint entry.
|
131
205
|
class EndpointExample
|
132
206
|
attr_reader :data, :definition
|
@@ -140,5 +214,3 @@ module Interpol
|
|
140
214
|
end
|
141
215
|
end
|
142
216
|
end
|
143
|
-
|
144
|
-
|
data/lib/interpol/errors.rb
CHANGED
@@ -31,5 +31,8 @@ module Interpol
|
|
31
31
|
# Error raised when the schema validator cannot find a matching
|
32
32
|
# endpoint definition for the request.
|
33
33
|
class NoEndpointDefinitionFoundError < Error; end
|
34
|
+
|
35
|
+
# Raised when an invalid status code is found during validate_codes!
|
36
|
+
class StatusCodeMatcherArgumentError < ArgumentError; end
|
34
37
|
end
|
35
38
|
|
@@ -67,7 +67,8 @@ module Interpol
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def validator
|
70
|
-
@validator ||= @config.endpoints.
|
70
|
+
@validator ||= @config.endpoints.
|
71
|
+
find_definition(request_method, path, 'response', status) do |endpoint|
|
71
72
|
@config.api_version_for(env, endpoint)
|
72
73
|
end
|
73
74
|
end
|
data/lib/interpol/stub_app.rb
CHANGED
@@ -18,8 +18,8 @@ module Interpol
|
|
18
18
|
self.class.interpol_config
|
19
19
|
end
|
20
20
|
|
21
|
-
def example_for(endpoint, version)
|
22
|
-
endpoint.find_example_for!(version)
|
21
|
+
def example_for(endpoint, version, message_type)
|
22
|
+
endpoint.find_example_for!(version, message_type)
|
23
23
|
rescue ArgumentError
|
24
24
|
interpol_config.request_version_unavailable(self, version, endpoint.available_versions)
|
25
25
|
end
|
@@ -49,8 +49,10 @@ module Interpol
|
|
49
49
|
def endpoint_definition(endpoint)
|
50
50
|
lambda do
|
51
51
|
version = interpol_config.api_version_for(request.env, endpoint)
|
52
|
-
|
52
|
+
message_type = 'response'
|
53
|
+
example = example_for(endpoint, version, message_type)
|
53
54
|
example.validate!
|
55
|
+
status endpoint.find_example_status_code_for!(version)
|
54
56
|
JSON.dump(example.data)
|
55
57
|
end
|
56
58
|
end
|
data/lib/interpol/test_helper.rb
CHANGED
@@ -5,9 +5,11 @@ module Interpol
|
|
5
5
|
module Common
|
6
6
|
def each_example_from(endpoints)
|
7
7
|
endpoints.each do |endpoint|
|
8
|
-
endpoint.definitions.each do |
|
9
|
-
|
10
|
-
|
8
|
+
endpoint.definitions.each do |definitions|
|
9
|
+
definitions.each do |definition|
|
10
|
+
definition.examples.each_with_index do |example, index|
|
11
|
+
yield endpoint, definition, example, index
|
12
|
+
end
|
11
13
|
end
|
12
14
|
end
|
13
15
|
end
|
data/lib/interpol/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: interpol
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json-schema
|
@@ -218,7 +218,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
218
218
|
version: '0'
|
219
219
|
segments:
|
220
220
|
- 0
|
221
|
-
hash: -
|
221
|
+
hash: -430941373413258822
|
222
222
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
223
223
|
none: false
|
224
224
|
requirements:
|
@@ -227,7 +227,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
227
227
|
version: '0'
|
228
228
|
segments:
|
229
229
|
- 0
|
230
|
-
hash: -
|
230
|
+
hash: -430941373413258822
|
231
231
|
requirements: []
|
232
232
|
rubyforge_project:
|
233
233
|
rubygems_version: 1.8.24
|