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