sinatra-chiro 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NzYwZmYwNTJjMDNmMzhkMDAxNzVmYzkxYTE0MTM4ZjYyMDcxZDFjZQ==
5
+ data.tar.gz: !binary |-
6
+ MDdiNzJhMzQyMDAxNTkyZmY1MzUzNDJmZWI3ZmE0YjFkN2I2MGJlOA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ MDFlNDliMTIzOGE3MjYxMTQzYjRjYzZiODY5NzZmZGVlNTY1YjJmMTg3YzU5
10
+ YmQ2ZTVmZGI1MTczYjhmYTc5OTlkOGU4ZGRhZDA1MWFjMjE0ZWY0M2I3MjI0
11
+ MDE2YjA5MDZlYmE2YTAwMTRjZDI1OTc1NGQ5OTQ4ZmU5MWViNjA=
12
+ data.tar.gz: !binary |-
13
+ MTE4OTI4YmQ2ZmZiYzY3MDczZTBhNDM0NjI0ODUxOTgxMzI0NDAwNmM5ZmMx
14
+ ZGQ2YjVkOWIzNzY3Mjg1MGI1MWI1NGUyNTk1MDIxZWExMDQ1ZmNmZTUyNWNm
15
+ OGEwYTQyM2MwZWU4YjYzMzZiYTdkZmRjYmU4NzgyY2MwZmMwNDk=
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ # A sample Gemfile
2
+ source "https://rubygems.org"
3
+
4
+ # Specify your gem's dependencies in chiro.gemspec
5
+ gemspec
6
+
7
+ group :development do
8
+ gem "guard", "~> 1.6.2"
9
+ gem 'rb-inotify', '~> 0.9'
10
+ gem "guard-rspec", "~> 2.5.0"
11
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2013 Richard Nienaber
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all
12
+ copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
20
+ OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,119 @@
1
+ chiro
2
+ =============
3
+
4
+ Chiro is a DSL for your sinatra application which generates readable documentation and validates the parameters used in your API.
5
+
6
+ The generated documentation includes details of all required parameters for a given route, and can optionally include an example response and possible errors which may occur.
7
+
8
+ This information can then be viewed for a specific route by including "help" as a query string parameter in the relevent URL, or the documentation for all routes can be viewed by using the path /routes.
9
+
10
+
11
+ ## Implementation
12
+
13
+ In your server file, every request must be contained within an `endpoint` block. This block must also contain any parameters used by the request in order for them to be documented and validated. Below is a simple example demonstrating the declaration of an endpoint.
14
+
15
+ ```ruby
16
+ app_description "Greeter Application"
17
+
18
+ endpoint 'Greeter', 'greets the user by name' do
19
+ named_param(:name, 'name of the user', :type => String)
20
+ query_param(:greeting, 'how the user wants to be greeted', :type => String, :optional => false)
21
+ get '/greet/:name' do
22
+ "#{params[:greeting]}, #{params[:name]}"
23
+ end
24
+ end
25
+ ```
26
+
27
+ The application must first be named using `app_description`, as shown above, then any endpoints must be declared in the format:
28
+
29
+ endpoint 'Name of feature', 'description of feature'
30
+
31
+
32
+ ## Declaring Parameters
33
+
34
+ If a parameter received via a request has not been declared, or has been declared improperly, a validation error will arise. There are three types of parameters which can be declared in the endpoint:
35
+
36
+ Query Parameters are found within the query string (eg. /greeter?greeting=hello)
37
+
38
+ query_param(:greeting, 'how the user wants to be greeted', :type => String, :optional => true)
39
+
40
+ Named Parameters are found between forward slashes in the path (eg. /person/:height)
41
+
42
+ named_param(:height, 'the height of the person', :type => Float)
43
+
44
+ Form parameters, which are obtained using a post request.
45
+
46
+ form(:birthday, 'the date of birth of the user', :type => Date, :optional => false)
47
+
48
+ The first two arguments should be the name of the parameter, and a brief description.
49
+
50
+ The `:type` argument can be one of `String`, `Fixnum`, `Float`, `Date`, `Time`, `DateTime`, `:boolean`, `Array` or the regular expression for matching. If a value for `:type` is not given, it will default to `String` and will be validated as such.
51
+
52
+ If `:optional` is false, a validation error will be raised when the parameter has not been given. The value of `:optional` defaults to false for named parameters, and true for query and form parameters.
53
+
54
+ If your application assigns a default value to parameters, the optional argument `:default` can be used to tell this to Chiro. For example:
55
+
56
+ ```ruby
57
+ query_param(:greeting, 'how the user wants to be greeted', :type => String, :optional => true, :default => 'hello')
58
+ get '/greet/:name' do
59
+ greeting = params[:greeting] || 'Hello'
60
+ "#{greeting}"
61
+ end
62
+ ```
63
+
64
+
65
+ ## Regular Expressions
66
+
67
+ To customise the validation process to validate a parameter according to a regular expression, you substitute the regular expression as the value of the `:type` argument. It is also useful to add to the declaration a `:comment` argument to explain the expression, and `:type_description`, which updates the type in the documentation.
68
+
69
+ For example, you might want a `:gender` parameter which only accepts "male" or "female", and would want it to be validated accordingly.
70
+
71
+ ```
72
+ query_param(:gender, 'gender parameter of regexp type', :type => /^male$|^female$/, :type_description => "male|female", :comment => 'Must be "male" or "female"')
73
+ ```
74
+
75
+
76
+ ## Possible Errors
77
+
78
+ It is also possible for your generated documentation to include details of possible errors which may occur. These can be declared within the endpoint in a similar way to parameters, but with a description and a status code. For example:
79
+
80
+ ```ruby
81
+ possible_error('invalid_request_error', 400, 'Invalid request errors arise when your request has invalid parameters')
82
+ ```
83
+
84
+
85
+ ## Responses
86
+
87
+ Example responses can also be included this way by including `response` within the endpoint with a hash of all given parameters as the argument.
88
+
89
+ ```
90
+ response({:string => 'Richard', :date => '1981-01-01', :time => '12:00:00', :fixnum => 24, :float => 1.2, :array => [1,2,3,4,5]})
91
+ ```
92
+
93
+ This would return the following example response to the documentation:
94
+
95
+ ```
96
+ {
97
+ string: Richard
98
+ date: 1981-01-01
99
+ time: 12:00:00
100
+ fixnum: 24
101
+ float: 1.2
102
+ array: [1, 2, 3, 4, 5]
103
+ }
104
+
105
+ ```
106
+
107
+
108
+ ## Validation
109
+
110
+ If any validation errors occur when your application is run, a response status code 403 will be returned along with a JSON object containing all errors, for example:
111
+
112
+ ```json
113
+ {
114
+ "validation_errors":[
115
+ "name parameter must be a string of only letters",
116
+ "date parameter must be a string in the format: yyyy-mm-dd"
117
+ ]
118
+ }
119
+ ```
@@ -0,0 +1,25 @@
1
+ module Sinatra
2
+ module Chiro
3
+ class Documentation
4
+
5
+ attr_reader :endpoints
6
+
7
+ def initialize(endpoints)
8
+ @endpoints = endpoints
9
+ end
10
+
11
+ def document(env)
12
+ _, path = env['sinatra.route'].split
13
+ endpoint = endpoints.select { |d| d.path == path}.flatten.first
14
+ raise "Path #{path} doesn't have any docs" unless endpoint
15
+ [[endpoints[0].appname, [endpoint]]]
16
+ end
17
+
18
+ def routes
19
+ [endpoints[0].appname, endpoints]
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+
@@ -0,0 +1,39 @@
1
+ class Endpoint
2
+ attr_reader :appname, :description, :verb, :path, :named_params, :query_params, :forms, :possible_errors, :response, :title
3
+
4
+ def initialize(opts)
5
+ @appname = opts[:appname]
6
+ @description = opts[:description]
7
+ @title = opts[:title]
8
+ @verb = opts[:verb]
9
+ @path = opts[:path]
10
+ @named_params = opts[:named_params]
11
+ @query_params = opts[:query_params]
12
+ @perform_validation = opts[:perform_validation]
13
+ @response = opts[:response]
14
+ @forms = opts[:forms]
15
+ @possible_errors = opts[:possible_errors]
16
+ end
17
+
18
+
19
+ def route
20
+ "#{verb}: #{path}"
21
+ end
22
+
23
+ def validate?
24
+ @perform_validation
25
+ end
26
+
27
+ def to_json(*a)
28
+ {:title => title,
29
+ :description => description,
30
+ :verb => verb,
31
+ :path => path,
32
+ :named_params => named_params,
33
+ :query_params => query_params,
34
+ :forms => forms,
35
+ :possible_errors => possible_errors,
36
+ :response => response,
37
+ }.to_json
38
+ end
39
+ end
@@ -0,0 +1,33 @@
1
+ module Sinatra
2
+ class Base
3
+ #we monkey patch here because at this point we know the name of the route
4
+ alias_method :old_route_eval, :route_eval
5
+ def route_eval
6
+ show_help if params.has_key? "help"
7
+ validate_parameters if self.class.respond_to?(:validator)
8
+
9
+ old_route_eval { yield }
10
+ end
11
+
12
+ def validate_parameters
13
+ error = self.class.validator.validate(params, env)
14
+ if error == "not found"
15
+ status 404
16
+ throw :halt, "Path not found"
17
+ elsif error!= nil
18
+ status 403
19
+ throw :halt, "#{error}"
20
+ end
21
+ end
22
+
23
+ def show_help
24
+ if self.class.respond_to?(:validator)
25
+ status 200
26
+ erb_file = settings.erb_file
27
+ views = settings.views_location
28
+ throw :halt, "#{erb(erb_file, {:views => views}, :endpoint => self.class.documentation.document(env)) }"
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + '/base'
2
+
3
+ module Sinatra
4
+ module Chiro
5
+ module Parameters
6
+ class ArrayValidator < Base
7
+ def validate(given)
8
+ "#{name} parameter must be an Array of Strings" if !given[name].is_a? Array
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,44 @@
1
+ module Sinatra
2
+ module Chiro
3
+ module Parameters
4
+ class Base
5
+ attr_reader :options
6
+ def initialize(opts={})
7
+ @options = opts
8
+ end
9
+
10
+ def name
11
+ @options[:name]
12
+ end
13
+
14
+ def name_display
15
+ @options[:name].to_s
16
+ end
17
+
18
+ def description
19
+ @options[:description]
20
+ end
21
+
22
+ def comment
23
+ nil
24
+ end
25
+
26
+ def type_description
27
+ @options[:type]
28
+ end
29
+
30
+ def type
31
+ @options[:type]
32
+ end
33
+
34
+ def default
35
+ @options[:default]
36
+ end
37
+
38
+ def optional
39
+ @options[:optional]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,18 @@
1
+ module Sinatra
2
+ module Chiro
3
+ module Parameters
4
+ class BooleanValidator < Base
5
+ def validate(given)
6
+ if given[name] != "true" && given[name] != "false"
7
+ "#{name_display} parameter must be true or false"
8
+ end
9
+ end
10
+
11
+ def comment
12
+ "Must be true or false"
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,22 @@
1
+ module Sinatra
2
+ module Chiro
3
+ module Parameters
4
+ class DateValidator < Base
5
+ def validate(given)
6
+ Date.parse(given.to_s)
7
+
8
+ if given[name] !~ /^\d{4}-\d{2}-\d{2}$/
9
+ "#{name_display} parameter must be a string in the format: yyyy-mm-dd"
10
+ end
11
+ rescue ArgumentError
12
+ "#{name_display} parameter invalid"
13
+ end
14
+
15
+ def comment
16
+ 'Must be expressed according to ISO 8601 (ie. YYYY-MM-DD)'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,21 @@
1
+ module Sinatra
2
+ module Chiro
3
+ module Parameters
4
+ class DateTimeValidator < Base
5
+ def validate(given)
6
+ DateTime.parse(given.to_s)
7
+
8
+ if given[name] !~ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/
9
+ "#{name_display} parameter must be a string in the format: yyyy-mm-ddThh:mm:ss"
10
+ end
11
+ rescue ArgumentError
12
+ "#{name_display} parameter invalid"
13
+ end
14
+ def comment
15
+ 'Must be expressed according to ISO 8601 (ie. YYYY-MM-DDThh:mm:ss)'
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
@@ -0,0 +1,11 @@
1
+ module Sinatra
2
+ module Chiro
3
+ module Parameters
4
+ class FixnumValidator < Base
5
+ def validate(given)
6
+ "#{name_display} parameter must be an integer" if given[name] !~/^\s*\d+\s*$/
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Sinatra
2
+ module Chiro
3
+ module Parameters
4
+ class FloatValidator < Base
5
+ def validate(given)
6
+ "#{name_display} parameter must be a Float" if given[name]!~/^\s*[+-]?((\d+_?)*\d+(\.(\d+_?)*\d+)?|\.(\d+_?)*\d+)(\s*|([eE][+-]?(\d+_?)*\d+)\s*)$/
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ module Sinatra
2
+ module Chiro
3
+ module Parameters
4
+ class ParameterFactory
5
+ def self.validator_from_type(options)
6
+ validator = case options[:type].to_s
7
+ when 'String' then StringValidator
8
+ when 'Fixnum' then FixnumValidator
9
+ when 'Float' then FloatValidator
10
+ when 'Date' then DateValidator
11
+ when 'DateTime' then DateTimeValidator
12
+ when 'Time' then TimeValidator
13
+ when 'boolean' then BooleanValidator
14
+ when '[String]' then ArrayValidator
15
+ else
16
+ if options[:type].is_a? Regexp
17
+ RegexpValidator
18
+ end
19
+ end
20
+ validator.new(options)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
@@ -0,0 +1,22 @@
1
+ module Sinatra
2
+ module Chiro
3
+ module Parameters
4
+ class RegexpValidator < Base
5
+
6
+ def validate(given)
7
+ "#{name_display} parameter should match regexp: #{options[:type]}" if given[name] !~ options[:type]
8
+ end
9
+
10
+ def type_description
11
+ super || "Regexp"
12
+ end
13
+
14
+ def comment
15
+ "#{options[:comment]}"
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,16 @@
1
+ module Sinatra
2
+ module Chiro
3
+ module Parameters
4
+ class StringValidator < Base
5
+ def validate(given)
6
+ if given[name_display] !~/^[a-zA-Z]*$/
7
+ "#{name_display} parameter must be a string of only letters"
8
+ elsif given[name_display].empty?
9
+ "#{name_display} parameter must not be empty"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,22 @@
1
+ module Sinatra
2
+ module Chiro
3
+ module Parameters
4
+ class TimeValidator < Base
5
+ def validate(given)
6
+ Time.parse(given.to_s)
7
+
8
+ if given[name] !~ /^\d{2}:\d{2}:\d{2}$/
9
+ "#{name_display} parameter must be a string in the format: hh:mm:ss"
10
+ end
11
+ rescue ArgumentError
12
+ "#{name_display} parameter invalid"
13
+ end
14
+
15
+ def comment
16
+ 'Must be expressed according to ISO 8601 (ie. hh:mm:ss)'
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,47 @@
1
+ require 'time'
2
+
3
+ module Sinatra
4
+ module Chiro
5
+ class Validation
6
+ attr_reader :endpoints
7
+
8
+ def initialize(endpoints)
9
+ @endpoints = endpoints
10
+ end
11
+
12
+ def validate(params, env)
13
+ _, path = env['sinatra.route'].split
14
+
15
+ endpoint = endpoints.select { |d| d.path == path }.flatten.first
16
+ return if endpoint.nil?
17
+
18
+ all_given = params.dup
19
+ all_given.delete('captures')
20
+ all_given.delete('splat')
21
+
22
+ all_params = endpoint.named_params + endpoint.query_params + endpoint.forms
23
+
24
+ allowed_params = []
25
+ errors = []
26
+
27
+ all_params.each do |parameter|
28
+ unless all_given[parameter.name_display].nil?
29
+ errors << parameter.validate(all_given)
30
+ end
31
+ if !parameter.optional
32
+ errors << "must include a #{parameter.name_display} parameter" if all_given[parameter.name_display].nil?
33
+ end
34
+ allowed_params << parameter.name_display
35
+ end
36
+
37
+ all_given.map { |k, _| k.to_s}.each do |param|
38
+ errors << "#{param} is not a valid parameter" if !allowed_params.include?(param)
39
+ end
40
+
41
+ if !errors.compact.empty? then
42
+ JSON.dump ({:validation_errors => errors.compact})
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,118 @@
1
+ require 'sinatra/chiro/endpoint'
2
+ require 'sinatra/chiro/document'
3
+ require 'sinatra/chiro/validate'
4
+ require 'sinatra/chiro/monkey_patch'
5
+ Dir[File.dirname(__FILE__) + '/chiro/parameters/*.rb'].sort.each { |f| require f}
6
+
7
+ CHIRO_APPS = []
8
+ module Sinatra
9
+ module Chiro
10
+ def endpoints
11
+ @endpoints ||= []
12
+ end
13
+
14
+ def validator
15
+ @validator ||= Validation.new(endpoints)
16
+ end
17
+
18
+ def documentation
19
+ @documentation ||= Documentation.new(endpoints)
20
+ end
21
+
22
+ def app_description(description)
23
+ @app_description = description
24
+ end
25
+
26
+ def endpoint(title=nil, description=nil, opts={})
27
+ opts[:title] ||= title
28
+ opts[:description] ||= description
29
+ opts[:perform_validation] ||= true
30
+ @named_params = []
31
+ @query_params = []
32
+ @forms = []
33
+ @possible_errors = []
34
+ @response = nil
35
+ @appname = @app_description || self.name
36
+
37
+ yield
38
+
39
+ opts[:verb] = @verb || :GET
40
+ opts[:named_params] = @named_params
41
+ opts[:query_params] = @query_params
42
+ opts[:forms] = @forms
43
+ opts[:possible_errors] = @possible_errors
44
+ opts[:response] = @response
45
+ opts[:path] = @path
46
+ opts[:appname] = @appname
47
+ endpoints << Endpoint.new(opts)
48
+ end
49
+
50
+ def named_param(name, description, opts={})
51
+ opts.merge!(:name => name, :description => description, :optional => false)
52
+ Chiro.remove_unknown_param_keys(opts)
53
+ Chiro.set_param_defaults(opts)
54
+ @named_params << Parameters::ParameterFactory.validator_from_type(opts)
55
+ end
56
+
57
+ def form(name, description, opts={})
58
+ opts.merge!(:name => name, :description => description)
59
+ Chiro.remove_unknown_param_keys(opts)
60
+ Chiro.set_param_defaults(opts)
61
+ @forms << Parameters::ParameterFactory.validator_from_type(opts)
62
+ end
63
+
64
+ def query_param(name, description, opts={})
65
+ opts.merge!(:name => name, :description => description)
66
+ Chiro.set_param_defaults(opts)
67
+ @query_params << Parameters::ParameterFactory.validator_from_type(opts)
68
+ end
69
+
70
+ def possible_error(name, code, description)
71
+ @possible_errors << {:name => name, :code => code, :description => description}
72
+ end
73
+
74
+ def get(path, opts = {}, &block)
75
+ @path = path
76
+ @verb = :GET
77
+ super
78
+ end
79
+
80
+ def post(path, opts = {}, &block)
81
+ @path = path
82
+ @verb = :POST
83
+ super
84
+ end
85
+
86
+ def response(result)
87
+ @response = result
88
+ end
89
+
90
+ def self.registered(app)
91
+ CHIRO_APPS << app
92
+
93
+ app.set :erb_file, :help
94
+ app.set :views_location, File.join(File.dirname(__FILE__), '..', 'views')
95
+
96
+ app.get '/routes' do
97
+ routes = CHIRO_APPS.select { |a| a.respond_to?(:documentation) }.map { |a| a.documentation.routes }
98
+ erb_file = settings.erb_file
99
+ views = settings.views_location
100
+ erb(erb_file, {:views => views}, :endpoint => routes)
101
+ end
102
+ end
103
+
104
+ private
105
+ def self.remove_unknown_param_keys(opts)
106
+ known_options = [:name, :description, :default, :type, :optional, :validator, :comment, :type_description]
107
+ opts.delete_if { |k| !k.is_a?(Symbol) || !known_options.include?(k)}
108
+ end
109
+
110
+ def self.set_param_defaults(opts)
111
+ opts[:type] ||= String
112
+ opts[:optional] ||= true if opts[:optional].nil?
113
+ end
114
+
115
+ end
116
+
117
+ register Sinatra::Chiro
118
+ end
@@ -0,0 +1,5 @@
1
+ module Sinatra
2
+ module Chiro
3
+ VERSION = '0.0.2'
4
+ end
5
+ end
@@ -0,0 +1,259 @@
1
+
2
+
3
+ <!DOCTYPE html>
4
+ <!--[if lt IE 7]>
5
+ <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
6
+ <!--[if IE 7]>
7
+ <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
8
+ <!--[if IE 8]>
9
+ <html class="no-js lt-ie9"> <![endif]-->
10
+ <!--[if gt IE 8]><!-->
11
+ <html class="no-js"> <!--<![endif]-->
12
+ <head>
13
+ <meta charset="utf-8">
14
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
15
+ <title>Documentation</title>
16
+ <meta name="description" content="">
17
+ <meta name="viewport" content="width=device-width, initial-scale=1">
18
+ <link href="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/css/bootstrap.min.css" rel="stylesheet">
19
+ <style>
20
+ .scrollspy-example {
21
+ overflow: auto;
22
+ position: relative;
23
+ height: auto;
24
+ }
25
+
26
+ thead {
27
+ background-color: #D3EDFB;
28
+ }
29
+
30
+ .container {
31
+ padding-top: 0;
32
+ margin-left: 15px;
33
+ height: 100%
34
+ }
35
+
36
+ body {
37
+ color: #54606c;
38
+ }
39
+
40
+ .hero-unit {
41
+ padding: 30px;
42
+ border-bottom: solid thin #DDDDDD;
43
+ }
44
+
45
+ p {
46
+ color: #73C3ED;
47
+ font-size: 16px;
48
+ padding-left: 15px;
49
+ }
50
+
51
+ h1 {
52
+ padding-top: 30px;
53
+ }
54
+
55
+ h2 {
56
+ padding-left: 15px;
57
+ }
58
+
59
+ .table {
60
+ font-size: 14px;
61
+ }
62
+
63
+ pre {
64
+ height: auto;
65
+ max-height: 200px;
66
+ line-height: 10px;
67
+ overflow: auto;
68
+ background-color: #F9F9F9
69
+ }
70
+
71
+ .sidebar {
72
+ border-left: solid thin #DDDDDD;
73
+ padding: 50px 0 0 0;
74
+ }
75
+
76
+ .nav-pills {
77
+ overflow: auto;
78
+ height: 100%;
79
+ }
80
+
81
+ li {
82
+ color: #54606c;
83
+ padding: 0 20px 0 20px;
84
+ width: 100%;
85
+ text-align: left
86
+ }
87
+
88
+ a {
89
+ color: #54606c;
90
+ }
91
+
92
+ .nav-pills>li>a:hover {
93
+ color: white;
94
+ background-color: #D3EDFB;
95
+ }
96
+
97
+ .nav-pills>li.active>a:hover {
98
+ color: #54606c;
99
+ background-color: #D3EDFB;
100
+ }
101
+
102
+ .nav-pills>li.active>a {
103
+ color: #54606c;
104
+ background-color: #D3EDFB;
105
+ }
106
+ </style>
107
+ </head>
108
+
109
+
110
+ <body data-spy="scroll" data-target="#navbarExample" data-offset="50" class="scrollspy-example">
111
+
112
+ <div class="container">
113
+ <div class="col-lg-9">
114
+ <% endpoint.each do |endpoints| %>
115
+ <h1 id="<%= endpoints[0].downcase.tr(" ", "_") %>"><%= endpoints[0] %></h1>
116
+ <% endpoints[1].each do |endpoint| %>
117
+ <div class="hero-unit" id="<%= endpoint.title.downcase.tr(" ", "_") %>">
118
+
119
+ <h2><%= endpoint.title %></h2>
120
+ <p><%= endpoint.description %></p>
121
+ <h3><%= endpoint.route %></h3>
122
+
123
+ <% if !endpoint.query_params.empty? %>
124
+ <h4>Query String Parameters:</h4>
125
+ <table class='table table-striped table-bordered table-condensed'>
126
+ <thead>
127
+ <tr>
128
+ <th>Name</th>
129
+ <th>Description</th>
130
+ <th>Type</th>
131
+ <th>Optional</th>
132
+ <th>Comment</th>
133
+ </tr>
134
+ </thead>
135
+ <% endpoint.query_params.each do |p| %>
136
+ <tbody>
137
+ <td><%= p.name %></td>
138
+ <td><%= p.description %></td>
139
+ <td><%= p.type_description %></td>
140
+ <td><%= p.optional %></td>
141
+ <td><%= p.comment %></td>
142
+ </tbody>
143
+ <% end %>
144
+ </table>
145
+ <% end %>
146
+
147
+ <% if !endpoint.named_params.empty? %>
148
+ <h4>Named Parameters:</h4>
149
+ <table class='table table-striped table-bordered table-condensed'>
150
+ <thead>
151
+ <tr>
152
+ <th>Name</th>
153
+ <th>Description</th>
154
+ <th>Type</th>
155
+ <th>Optional</th>
156
+ <th>Comment</th>
157
+ </tr>
158
+ </thead>
159
+ <% endpoint.named_params.each do |p| %>
160
+ <tbody>
161
+ <td><%= p.name %></td>
162
+ <td><%= p.description %></td>
163
+ <td><%= p.type_description %></td>
164
+ <td><%= p.optional %></td>
165
+ <td><%= p.comment %></td>
166
+ </tbody>
167
+ <% end %>
168
+ </table>
169
+ <% end %>
170
+
171
+ <% if !endpoint.forms.empty? %>
172
+ <h4>Form Parameters:</h4>
173
+ <table class='table table-striped table-bordered table-condensed'>
174
+ <thead>
175
+ <tr>
176
+ <th>Name</th>
177
+ <th>Description</th>
178
+ <th>Type</th>
179
+ <th>Optional</th>
180
+ <th>Comment</th>
181
+ </tr>
182
+ </thead>
183
+ <% endpoint.forms.each do |p| %>
184
+ <tbody>
185
+ <td><%= p.name %></td>
186
+ <td><%= p.description %></td>
187
+ <td><%= p.type_description %></td>
188
+ <td><%= p.optional %></td>
189
+ <td><%= p.comment %></td>
190
+ </tbody>
191
+ <% end %>
192
+ </table>
193
+ <% end %>
194
+
195
+ <% if !endpoint.possible_errors.empty? %>
196
+ <h4>Possible Errors:</h4>
197
+ <table class='table table-striped table-bordered table-condensed'>
198
+ <thead>
199
+ <tr>
200
+ <th>Name</th>
201
+ <th>Code</th>
202
+ <th>Description</th>
203
+ </tr>
204
+ </thead>
205
+ <% endpoint.possible_errors.each do |p| %>
206
+ <tbody>
207
+ <td><%= p[:name] %></td>
208
+ <td><%= p[:code] %></td>
209
+ <td><%= p[:description] %></td>
210
+ </tbody>
211
+ <% end %>
212
+ </table>
213
+ <% end %>
214
+
215
+ <% if !endpoint.response.nil? %>
216
+ <h4>Response:</h4>
217
+ <pre>
218
+ {
219
+ <% endpoint.response.each do |k, v| %>
220
+ <%= "#{k}: #{v}" %>
221
+ <% end %>
222
+ }
223
+ </pre>
224
+ <% end %>
225
+ </div>
226
+ <% end %>
227
+ <% end %>
228
+ </div>
229
+
230
+ <div class="row">
231
+ <div class="col-lg-3">
232
+ <div id="navbarExample">
233
+ <div class="col-lg-3">
234
+ <ul type=none class="nav nav-pills sidebar affix">
235
+ <% endpoint.each do |endpoints| %>
236
+ <li class="">
237
+ <a href='#<%= endpoints[0].downcase.tr(" ", "_")%>'><%=endpoints[0]%></a>
238
+ <ul class = "nav nav-pills">
239
+ <% endpoints[1].each do |endpoint| %>
240
+ <li class="">
241
+ <a href='#<%= endpoint.title.downcase.tr(" ", "_") %>'><%= endpoint.title %></a>
242
+ </li>
243
+ <% end %>
244
+ </ul>
245
+ </li>
246
+ <% end %>
247
+ </ul>
248
+ </div>
249
+ </div>
250
+ </div>
251
+ </div>
252
+ </div>
253
+
254
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
255
+ <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.0-rc1/js/bootstrap.min.js"></script>
256
+ </body>
257
+ </html>
258
+
259
+
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-chiro
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Richard Nienaber
8
+ - Scott Adams
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-15 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: sinatra
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: 1.4.3
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: 1.4.3
28
+ - !ruby/object:Gem::Dependency
29
+ name: json
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ~>
33
+ - !ruby/object:Gem::Version
34
+ version: 1.8.0
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ~>
40
+ - !ruby/object:Gem::Version
41
+ version: 1.8.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: sinatra-contrib
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ~>
47
+ - !ruby/object:Gem::Version
48
+ version: 1.4.0
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ~>
54
+ - !ruby/object:Gem::Version
55
+ version: 1.4.0
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ version: 2.13.0
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 2.13.0
70
+ - !ruby/object:Gem::Dependency
71
+ name: rake
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ description: Documents and validates sinatra api requests
85
+ email:
86
+ - rjnienaber@gmail.com
87
+ executables: []
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - Gemfile
92
+ - LICENSE
93
+ - README.md
94
+ - lib/sinatra/chiro.rb
95
+ - lib/sinatra/chiro/document.rb
96
+ - lib/sinatra/chiro/endpoint.rb
97
+ - lib/sinatra/chiro/monkey_patch.rb
98
+ - lib/sinatra/chiro/parameters/array.rb
99
+ - lib/sinatra/chiro/parameters/base.rb
100
+ - lib/sinatra/chiro/parameters/boolean.rb
101
+ - lib/sinatra/chiro/parameters/date.rb
102
+ - lib/sinatra/chiro/parameters/datetime.rb
103
+ - lib/sinatra/chiro/parameters/fixnum.rb
104
+ - lib/sinatra/chiro/parameters/float.rb
105
+ - lib/sinatra/chiro/parameters/parameter_factory.rb
106
+ - lib/sinatra/chiro/parameters/regexp.rb
107
+ - lib/sinatra/chiro/parameters/string.rb
108
+ - lib/sinatra/chiro/parameters/time.rb
109
+ - lib/sinatra/chiro/validate.rb
110
+ - lib/sinatra/chiro_version.rb
111
+ - lib/views/help.erb
112
+ homepage: https://github.com/rjnienaber/chiro
113
+ licenses:
114
+ - MIT
115
+ metadata: {}
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ! '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ required_rubygems_version: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ requirements: []
131
+ rubyforge_project:
132
+ rubygems_version: 2.0.6
133
+ signing_key:
134
+ specification_version: 4
135
+ summary: An easy way to produce self-documenting sinatra apis
136
+ test_files: []