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 CHANGED
@@ -51,7 +51,9 @@ name: user_projects
51
51
  route: /users/:user_id/projects
52
52
  method: GET
53
53
  definitions:
54
- - versions: ["1.0"]
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 = 12
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.definitions.find { |d| d.version == version }
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.reverse.each do |definition| %>
7
- <div class="versioned-endpoint-definition">
8
- <h3>Version <%= definition.version %></h3>
9
- <hr>
10
-
11
- <%= Interpol::Documentation.html_for_schema(definition.schema) %>
12
- </div>
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-->
@@ -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(&:version)
47
+ definitions.map { |d| d.first.version }
43
48
  end
44
49
 
45
50
  def definitions
46
- @definitions.values.sort_by(&:version)
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
- definitions[version] = EndpointDefinition.new(name, version, definition)
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 = endpoint_name
97
- @version = version
98
- @schema = fetch_from(definition, 'schema')
99
- @examples = fetch_from(definition, 'examples').map { |e| EndpointExample.new(e, self) }
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
-
@@ -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.find_definition(request_method, path) do |endpoint|
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
@@ -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
- example = example_for(endpoint, version)
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
@@ -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 |definition|
9
- definition.examples.each_with_index do |example, index|
10
- yield endpoint, definition, example, index
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
@@ -1,3 +1,3 @@
1
1
  module Interpol
2
- VERSION = "0.0.8"
2
+ VERSION = "0.1.0"
3
3
  end
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.8
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-05-29 00:00:00.000000000 Z
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: -3251393236495982186
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: -3251393236495982186
230
+ hash: -430941373413258822
231
231
  requirements: []
232
232
  rubyforge_project:
233
233
  rubygems_version: 1.8.24