diff --git a/README.md b/README.md index d57ea27..c9eb069 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/etc/css/base.css b/etc/css/base.css index 546069a..15610a8 100644 --- a/etc/css/base.css +++ b/etc/css/base.css @@ -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; +} \ No newline at end of file diff --git a/etc/partial.html.erb b/etc/partial.html.erb index 9d98fcd..bd0bed7 100644 --- a/etc/partial.html.erb +++ b/etc/partial.html.erb @@ -48,12 +48,25 @@ Parameter + Description - <% route[:parameters].each do |param| %> + <% route[:parameters].each do |param_name, param_options| %> - <%= param %> + + <%= param_name %> + <% if param_options[:required] %> + *required + <% end %> + <% if param_options[:type] %> +
<%= param_options[:type] %>
+ <% end %> + <% if param_options[:in] %> +
(<%= param_options[:in] %>)
+ <% end %> + + <%= param_options[:description] %> <% end %> diff --git a/lib/doc_my_routes/doc/hash_helpers.rb b/lib/doc_my_routes/doc/hash_helpers.rb new file mode 100644 index 0000000..8163701 --- /dev/null +++ b/lib/doc_my_routes/doc/hash_helpers.rb @@ -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 diff --git a/lib/doc_my_routes/doc/mixins/annotatable.rb b/lib/doc_my_routes/doc/mixins/annotatable.rb index 299e02a..e77dc75 100644 --- a/lib/doc_my_routes/doc/mixins/annotatable.rb +++ b/lib/doc_my_routes/doc/mixins/annotatable.rb @@ -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) diff --git a/lib/doc_my_routes/doc/route.rb b/lib/doc_my_routes/doc/route.rb index c2c48df..ac996b4 100644 --- a/lib/doc_my_routes/doc/route.rb +++ b/lib/doc_my_routes/doc/route.rb @@ -1,6 +1,7 @@ # encoding: utf-8 require 'forwardable' +require_relative 'hash_helpers' module DocMyRoutes # Simple object representing a route @@ -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 @@ -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 diff --git a/lib/doc_my_routes/doc/route_documentation.rb b/lib/doc_my_routes/doc/route_documentation.rb index efc084c..f5aadb5 100644 --- a/lib/doc_my_routes/doc/route_documentation.rb +++ b/lib/doc_my_routes/doc/route_documentation.rb @@ -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 @@ -25,6 +26,7 @@ def to_hash examples_regex: examples_regex, produces: produces, examples: examples, + parameters: parameters, hidden: hidden? } end @@ -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]] diff --git a/lib/doc_my_routes/version.rb b/lib/doc_my_routes/version.rb index 261c05b..f283fbe 100644 --- a/lib/doc_my_routes/version.rb +++ b/lib/doc_my_routes/version.rb @@ -1,4 +1,4 @@ # DocMyRoutes version module DocMyRoutes - VERSION = '0.11.1' + VERSION = '0.12.0' end diff --git a/spec/unit/doc_helpers_spec.rb b/spec/unit/doc_helpers_spec.rb index 0c664e5..f00d1b2 100644 --- a/spec/unit/doc_helpers_spec.rb +++ b/spec/unit/doc_helpers_spec.rb @@ -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) @@ -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 @@ -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 @@ -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] @@ -75,13 +75,16 @@ 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 @@ -89,6 +92,7 @@ def doc_route 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 }