Skip to content

Commit

Permalink
Add documentation for parameters
Browse files Browse the repository at this point in the history
  - adds new annotation method `parameter`
  - adds default `in` option set to `:path` and `:required` for
    parameters specified in the route path
  - adds documentation for parameter description from options:
     parameter name, description: 'parameter description'
  • Loading branch information
vasyl-purchel committed May 26, 2017
1 parent 1c887d3 commit b4001f7
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 18 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class MyApp < Sinatra::Base
notes 'Simple route that gets an ID and returns a string'
produces 'text/plain'
status_codes [200]
parameter :id, description: 'some ID'
get '/:id' do |id|
"Received #{id}"
end
Expand Down
17 changes: 17 additions & 0 deletions etc/css/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,20 @@ article.example
color:#555;
margin:1em;
}

.required_parameter
{
color:#a41e22;
font-size:0.7em;
}

.parameter_type
{
font-size:0.8em;
font-weight:400;
}

.parameter_in
{
color:#555;
}
17 changes: 15 additions & 2 deletions etc/partial.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,25 @@
<thead>
<tr>
<th>Parameter</th>
<th>Description</th>
</tr>
</thead>
<tbody class="operation-params">
<% route[:parameters].each do |param| %>
<% route[:parameters].each do |param_name, param_options| %>
<tr>
<td><%= param %></td>
<td>
<%= param_name %>
<% if param_options[:required] %>
<sup class="required_parameter">*required</sup>
<% end %>
<% if param_options[:type] %>
<div class="parameter_type"><%= param_options[:type] %></div>
<% end %>
<% if param_options[:in] %>
<div class="parameter_in">(<%= param_options[:in] %>)</div>
<% end %>
</td>
<td><%= param_options[:description] %></td>
</tr>
<% end %>
</tbody>
Expand Down
13 changes: 13 additions & 0 deletions lib/doc_my_routes/doc/hash_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Define error classes
module DocMyRoutes
module HashHelpers
def deep_merge(first, second)
merger = proc { |key, f, s| f.is_a?(Hash) && s.is_a?(Hash) ? f.merge(s, &merger) : s }
first.merge(second, &merger)
end

def array_to_hash_keys(arr, default_value = {})
{}.tap { |hash| arr.each { |key| hash[key] = default_value } }
end
end
end
4 changes: 4 additions & 0 deletions lib/doc_my_routes/doc/mixins/annotatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ def notes_ref(value)
route_documentation.notes_ref = value
end

def parameter(value, options = {})
route_documentation.add_parameter(value, options)
end

private

def track_route(resource, verb, route_pattern, conditions)
Expand Down
21 changes: 12 additions & 9 deletions lib/doc_my_routes/doc/route.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# encoding: utf-8

require 'forwardable'
require_relative 'hash_helpers'

module DocMyRoutes
# Simple object representing a route
Expand All @@ -23,11 +24,11 @@ def initialize(resource, verb, route_pattern, conditions, documentation)
end

def to_hash
{
deep_merge({
http_method: verb,
parameters: param_info,
path: path
}.merge(documentation.to_hash)
}, documentation.to_hash)
end

def path
Expand Down Expand Up @@ -60,13 +61,15 @@ def to_s
#
# Try to extract parameters from the route definition otherwise
def param_info
if conditions[:parameters]
conditions[:parameters]
else
route_pattern.split('/').map do |part|
part.start_with?(':') ? part[1..-1].to_sym : nil
end.compact
end
path_parameters_array = route_pattern.split('/').map do |part|
part.start_with?(':') ? part[1..-1].to_sym : nil
end.compact

path_parameters = HashHelpers.array_to_hash_keys(path_parameters_array,
{ in: :path, required: true })
condition_parameters = HashHelpers.array_to_hash_keys(conditions[:parameters] || [])

HashHelpers.deep_merge(condition_parameters, path_parameters)
end
end
end
8 changes: 7 additions & 1 deletion lib/doc_my_routes/doc/route_documentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ module DocMyRoutes
class RouteDocumentation
attr_accessor :summary, :notes, :status_codes, :examples_regex, :hidden,
:produces, :notes_ref
attr_reader :examples
attr_reader :examples, :parameters

def initialize
@status_codes = { 200 => DocMyRoutes::StatusCodeInfo::STATUS_CODES[200] }
@hidden = false
@produces = []
@parameters = {}
end

# A route documentation object MUST have a summary, otherwise is not
Expand All @@ -25,6 +26,7 @@ def to_hash
examples_regex: examples_regex,
produces: produces,
examples: examples,
parameters: parameters,
hidden: hidden?
}
end
Expand All @@ -33,6 +35,10 @@ def produces=(values)
@produces = values.flatten.compact
end

def add_parameter(name, options)
@parameters[name] = options
end

def status_codes=(route_status_codes)
@status_codes = Hash[route_status_codes.map do |code|
[code, DocMyRoutes::StatusCodeInfo::STATUS_CODES[code]]
Expand Down
2 changes: 1 addition & 1 deletion lib/doc_my_routes/version.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# DocMyRoutes version
module DocMyRoutes
VERSION = '0.11.1'
VERSION = '0.12.0'
end
14 changes: 9 additions & 5 deletions spec/unit/doc_helpers_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
DEFAULT_NOTES = 'Example notes'
DEFAULT_NOTES_FILE = 'etc/example_notes.txt'
DEFAULT_STATUS_CODES = [200, 401]
DEFAULT_PARAMETER_OPTIONS = { type: :integer, description: 'example parameter' }

def mock_notes_file(content, path = 'notes.txt')
mock_notes_path = File.expand_path(path)
Expand All @@ -25,6 +26,7 @@ def mock_notes_file(content, path = 'notes.txt')
doc.summary = DEFAULT_SUMMARY
doc.notes = DEFAULT_NOTES
doc.status_codes = DEFAULT_STATUS_CODES
doc.add_parameter(:example_id, DEFAULT_PARAMETER_OPTIONS)
doc
end

Expand All @@ -34,9 +36,7 @@ def mock_notes_file(content, path = 'notes.txt')
"Actual object #{actual} doesn't have documentation"

actual_docs = actual.documentation
actual_docs.summary == expected.summary && \
actual_docs.notes == expected.notes && \
actual_docs.status_codes == expected.status_codes
actual_docs.to_hash == expected.to_hash
end
description { "The route #{actual} doesn't match #{expected}" }
end
Expand All @@ -63,7 +63,7 @@ def mock_notes_file(content, path = 'notes.txt')
DocMyRoutes::RouteCollection.routes.clear
end

context 'with summary, notes and status codes for a GET verb' do
context 'with summary, notes, parameter and status codes for a GET verb' do
subject do
key = DocMyRoutes::RouteCollection.routes.keys[0]
DocMyRoutes::RouteCollection.routes[key]
Expand All @@ -75,20 +75,24 @@ def doc_route
summary DEFAULT_SUMMARY
notes DEFAULT_NOTES
status_codes DEFAULT_STATUS_CODES
parameter :example_id, DEFAULT_PARAMETER_OPTIONS
get '/api/example' do
end
}
end

it_behaves_like 'a correctly tracked route'
end

context 'with a summary, notes in an external file and status codes' do
context 'with a summary, notes in an external file, parameter and status codes' do
def doc_route
mock_notes_file(DEFAULT_NOTES, DEFAULT_NOTES_FILE)
# Test class with only two routes GET and HEAD
self.class.const_set :DocRoute, Class.new(DocMyRoutes::Test::MyApp) {
summary DEFAULT_SUMMARY
notes_ref DEFAULT_NOTES_FILE
status_codes DEFAULT_STATUS_CODES
parameter :example_id, DEFAULT_PARAMETER_OPTIONS
post '/api/example_notes_file' do
end
}
Expand Down

0 comments on commit b4001f7

Please sign in to comment.