sinatra-browse 0.4 → 0.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9e596c2c094f363eec398cd2dc1c74d3d6cccbd9
4
- data.tar.gz: 90b5732c3167897bb1cd6c6dcbed15d02b69e197
3
+ metadata.gz: 1faa38c99f34fe11336ea7e4beb14b9c6c8c5d44
4
+ data.tar.gz: 2d1cc86c5d25a45f8c557202f095f74a9e145325
5
5
  SHA512:
6
- metadata.gz: 33205cac78eadc505f7c947ec7d79a23efdc7b592d6e0feed4a324f62b8374ad202947ce38cad9cf2ca251dd8c210fbfd088b74ddddc8c1f145c22f785b89950
7
- data.tar.gz: 59ff1e796da21200d2b8f163f5a45611dbd2c0fd588b018978a787686fea630c07e56a1e023db7dd39563a4c989cb68d55b38f44a3c6c91973b81e81d972684b
6
+ metadata.gz: 6a68e861b2d04655faa2a459d9246754b526829c1ca4ebfae6ef0c7c2502a7a39583981cdb22b5ec08819aa311f9d460136b96d90f904db964d586d0b0d448b1
7
+ data.tar.gz: 740fbf66e097ea5d34c5998ba248df93a09db40948312b9af0659f5f0b47186ad1f314904419ceee38e4434ebe1a4a8418fe35e8f02a237b4b8a0f2fa168fcdc
data/README.md CHANGED
@@ -94,7 +94,7 @@ param :order, :String, in: ["ascending", "descending"]
94
94
 
95
95
  The following parameter validators can only be used for parameters of type `:String`.
96
96
 
97
- `format` You can pass a regular expression that the string provided must match to.
97
+ `format` The string must match this regular expression.
98
98
 
99
99
  ```ruby
100
100
  param :alphanumeric, :String, format: /^[0-9A-Za-z]*$/
@@ -3,18 +3,36 @@
3
3
  require 'sinatra/base'
4
4
 
5
5
  Dir["#{File.dirname(__FILE__)}/browse/*.rb"].each {|f| require f }
6
+ Dir["#{File.dirname(__FILE__)}/browse/parameter_types/*.rb"].each do |f|
7
+ require f
8
+ end
6
9
 
7
10
  module Sinatra::Browse
11
+ #
12
+ # Main DSL methods
13
+ #
8
14
  def param(name, type, options = {})
9
15
  temp_browse_params[name] = options.merge({ type: type })
10
16
  end
11
17
 
12
18
  def parameter_options(parameter, options)
13
- #TODO: Raise error when the parameter overridden doesn't exist
19
+ if temp_browse_params[parameter].nil?
20
+ msg = "Tried to override undeclared parameter #{parameter}"
21
+ raise Errors::UnknownParameterError, msg
22
+ end
23
+
14
24
  temp_browse_params[parameter].merge! options
15
25
  end
16
26
  alias :param_options :parameter_options
17
27
 
28
+ def describe(description)
29
+ @_browse_description = description
30
+ end
31
+ alias :desc :describe
32
+
33
+ #
34
+ # Internal stuff
35
+ #
18
36
  def temp_browse_params
19
37
  @_temp_browse_params ||= reset_temp_params
20
38
  end
@@ -31,18 +49,20 @@ module Sinatra::Browse
31
49
  @_browse_description ||= ""
32
50
  end
33
51
 
34
- def describe(description)
35
- @_browse_description = description
36
- end
37
- alias :desc :describe
38
-
39
52
  def browse_routes_for(request_method, path_info)
40
53
  browse_routes.values.find { |v| v.matches?(request_method, path_info) }
41
54
  end
42
55
 
43
- #TODO: Rename method... It doesn't sound like we'd be creating a new route object here
44
- def set_browse_routes_for(request_method, path_info, description = browse_description, new_params = temp_browse_params)
45
- new_route = Route.new(request_method, path_info, browse_description, new_params)
56
+ def create_browse_route(request_method,
57
+ path_info,
58
+ description = browse_description,
59
+ new_params = temp_browse_params)
60
+
61
+ new_route = Route.new(request_method,
62
+ path_info,
63
+ browse_description,
64
+ new_params)
65
+
46
66
  browse_routes[new_route.name] = new_route
47
67
  end
48
68
 
@@ -76,29 +96,26 @@ module Sinatra::Browse
76
96
  browse_route = app.browse_routes_for(request.request_method, request.path_info)
77
97
 
78
98
  if browse_route
79
- #TODO: Optionally throw error for undefined params
80
-
81
99
  if settings.remove_undefined_parameters
82
100
  browse_route.delete_undefined(params, settings.allowed_undefined_parameters)
83
101
  end
84
102
 
85
- browse_route.coerce_type(params)
86
- browse_route.set_defaults(params)
87
- validation_result = browse_route.validate(params)
88
- unless validation_result[:success]
89
- if validation_result[:on_error].respond_to?(:to_proc)
90
- error_proc = validation_result.delete(:on_error).to_proc
91
- instance_exec validation_result, &error_proc
103
+ validation_successful, error_hash = browse_route.process(params)
104
+
105
+ unless validation_successful
106
+ if error_hash[:on_error].respond_to?(:to_proc)
107
+ error_proc = error_hash.delete(:on_error).to_proc
108
+ instance_exec error_hash, &error_proc
92
109
  else
93
- instance_exec validation_result, &app.default_on_error
110
+ instance_exec error_hash, &app.default_on_error
94
111
  end
95
112
  end
96
- browse_route.transform(params)
113
+
97
114
  end
98
115
  end
99
116
 
100
- # Create the (future) browsable api
101
- app.param :format, :String, in: ["kusohtml", "json", "yaml"], default: "kusohtml"
117
+ app.describe "Displays this browsable API."
118
+ app.param :format, :String, in: ["html", "json", "yaml", "yml"], default: "html"
102
119
  app.get '/browse' do
103
120
  Sinatra::Browse.format(params["format"], app.browse_routes).generate
104
121
  end
@@ -106,7 +123,7 @@ module Sinatra::Browse
106
123
 
107
124
  def self.route_added(verb, path, block)
108
125
  return if verb == "HEAD" && !@app.settings.show_head_routes
109
- @app.set_browse_routes_for(verb, path)
126
+ @app.create_browse_route(verb, path)
110
127
  @app.reset_temp_params
111
128
  @app.desc ""
112
129
  end
@@ -0,0 +1,35 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <body>
4
+ <% @browse_routes.each do |name, route| %>
5
+ <h1><%= name %></h1>
6
+
7
+ <p><%= route.description %></p>
8
+
9
+ <% unless route.param_declarations.empty? %>
10
+ <h2>Parameters</h2>
11
+
12
+ <dl>
13
+ <% route.param_declarations.each do |name, param| %>
14
+ <dt><%= param.name %></dt>
15
+
16
+ <% if param.required? %>
17
+ <dd>Required</dd>
18
+ <% end %>
19
+
20
+ <dd>Type: <%= param.type %></dd>
21
+
22
+ <% if param.default %>
23
+ <dd>Default: <%= param.default %></dd>
24
+ <% end %>
25
+
26
+ <% param.validators.each do |v| %>
27
+ <dd><%= v.name %>: <%= h v.criteria %></dd>
28
+ <% end %>
29
+
30
+ <% end %>
31
+ </dl>
32
+ <% end %>
33
+ <% end %>
34
+ </body>
35
+ </html>
@@ -0,0 +1,7 @@
1
+ <% @browse_routes.each do |name, route| %>
2
+ # <%= name %>
3
+
4
+ <% route.param_declarations.each do |name, param| %>
5
+ * <%= param.name %>
6
+ <% end %>
7
+ <% end %>
@@ -0,0 +1,7 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Sinatra::Browse
4
+ module Errors
5
+ class UnknownParameterError < Exception; end
6
+ end
7
+ end
@@ -1,16 +1,19 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  require 'yaml'
4
+ require 'erb'
4
5
 
5
6
  module Sinatra::Browse
6
7
  def self.format(f, browse_routes)
7
8
  case f
8
- when "kusohtml"
9
- KusoHtml.new(browse_routes)
9
+ when "html"
10
+ ErbTemplate.new(browse_routes, "html.erb")
10
11
  when "json"
11
12
  JSON.new(browse_routes)
12
- when "yaml"
13
+ when "yaml", "yml"
13
14
  YAML.new(browse_routes)
15
+ when "markdown"
16
+ ErbTemplate.new(browse_routes, "markdown.erb")
14
17
  end
15
18
  end
16
19
 
@@ -20,30 +23,28 @@ module Sinatra::Browse
20
23
  end
21
24
  end
22
25
 
23
- class KusoHtml < BrowseFormat
26
+ class ErbTemplate < BrowseFormat
27
+ include ERB::Util
28
+
29
+ def initialize(browse_routes, filename)
30
+ super(browse_routes)
31
+ @template = File.read(File.dirname(__FILE__) + "/erb_templates/" + filename)
32
+ end
33
+
24
34
  def generate
25
- output = ""
26
- @browse_routes.each { |name, route|
27
- output += "<h3>#{name}</h3>"
28
- output += "<p>#{route.description}</p><ul>"
29
- route.parameters.each { |param_key, param_value|
30
- output += "<li>#{param_key} #{param_value.to_s}</li>"
31
- }
32
- output += "</ul>"
33
- }
34
- output
35
+ ERB.new(@template).result(binding)
35
36
  end
36
37
  end
37
38
 
38
39
  class JSON < BrowseFormat
39
40
  def generate
40
- @browse_routes.values.map { |br| br.to_hash }.to_json
41
+ @browse_routes.values.map { |br| br.to_hash(noprocs: true) }.to_json
41
42
  end
42
43
  end
43
44
 
44
45
  class YAML < BrowseFormat
45
46
  def generate
46
- @browse_routes.values.map { |br| br.to_hash }.to_yaml
47
+ @browse_routes.values.map { |br| br.to_hash(noprocs: true) }.to_yaml
47
48
  end
48
49
  end
49
50
  end
@@ -0,0 +1,117 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Sinatra::Browse
4
+ def self.parameter_type(name, &blk)
5
+ const_set "#{name}Type", Class.new(ParameterType, &blk)
6
+ end
7
+
8
+ class ParameterType
9
+ attr_reader :name
10
+ attr_reader :default
11
+ attr_reader :validators
12
+
13
+ def initialize(name, map)
14
+ @name = name
15
+ @default = map.delete(:default)
16
+
17
+ @transform = map.delete(:transform)
18
+ @transform = @transform.to_proc if @transform
19
+
20
+ @required = !! map[:required]
21
+ @on_error = map.delete(:on_error)
22
+
23
+ @validators = []
24
+ map.each do |key, value|
25
+ if val_blk = @@validator_declarations[key]
26
+ @validators << Validator.new(
27
+ name: key,
28
+ criteria: map[key],
29
+ validation_blk: val_blk
30
+ )
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ def default
37
+ @default.is_a?(Proc) ? @default.call : @default
38
+ end
39
+
40
+ def required?
41
+ @required
42
+ end
43
+
44
+ def validate(params)
45
+ @validators.each do |v|
46
+ return false, build_error_hash(v.name, v.value) unless v.validate(self.name, params)
47
+ end
48
+
49
+ true
50
+ end
51
+
52
+ def transform(value)
53
+ @transform ? @transform.call(value) : value
54
+ end
55
+
56
+ def coerce(value)
57
+ raise NotImplementedError
58
+ end
59
+
60
+ def build_error_hash(reason, value)
61
+ {
62
+ reason: reason,
63
+ parameter: self.name,
64
+ value: value,
65
+ on_error: @on_error
66
+ }
67
+ end
68
+
69
+ def type
70
+ type_string = self.class.to_s.split("::").last
71
+ type_string[0, type_string.size - 4].to_sym
72
+ end
73
+
74
+ def to_hash(options = {})
75
+ h = {
76
+ name: @name,
77
+ type: type,
78
+ required: required?,
79
+ }
80
+
81
+ if @default
82
+ h[:default] = if @default.is_a?(Proc) && options[:noprocs]
83
+ "dynamically generated"
84
+ else
85
+ @default
86
+ end
87
+ end
88
+
89
+ @validators.each { |v| h[v.name.to_sym] = v.criteria }
90
+
91
+ h
92
+ end
93
+
94
+ #
95
+ # DSL
96
+ #
97
+
98
+ def self.coerce(&blk)
99
+ define_method(:coerce) { |value| blk.call(value) }
100
+ end
101
+
102
+ def self.validator(name, &blk)
103
+ @@validator_declarations ||= {}
104
+
105
+ @@validator_declarations[name] = blk
106
+ end
107
+
108
+ #
109
+ # Validators
110
+ #
111
+
112
+ # We need a to_s here because the user should be allowed to define dependencies
113
+ # using symbols while the actual keys of the params hash are strings
114
+ validator(:depends_on) { |dep| @params.has_key?(dep.to_s) }
115
+ validator(:in) { |possible_values| possible_values.member?(@value) }
116
+ end
117
+ end
@@ -0,0 +1,18 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Sinatra::Browse
4
+ parameter_type(:Boolean) do
5
+ coerce do |value|
6
+ #TODO: Raise error if it's something else
7
+ # true and false are included here because they can be set as default
8
+ # values even though only strings will come through http requests
9
+ case value
10
+ when "y", "yes", "t", "true", "1", true
11
+ true
12
+ when "n", "no", "f", "false", "0", false
13
+ false
14
+ end
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,7 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Sinatra::Browse
4
+ parameter_type(:Float) do
5
+ coerce { |value| Float(value) }
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Sinatra::Browse
4
+ parameter_type(:Integer) do
5
+ coerce { |value| Integer(value) }
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Sinatra::Browse
4
+
5
+ parameter_type(:String) do
6
+ coerce { |value| String(value) }
7
+
8
+ validator(:format) { |regex| !! (@value =~ regex) }
9
+ validator(:min_length) { |min_len| @value.length >= min_len }
10
+ validator(:max_length) { |max_len| @value.length <= max_len }
11
+ end
12
+
13
+ end
@@ -1,100 +1,69 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
- module Sinatra::Browse
3
+ module Sinatra::Browse
4
4
  class Route
5
- attr_reader :parameters
5
+ attr_reader :param_declarations
6
6
  attr_reader :name
7
7
  attr_reader :match
8
8
  attr_reader :description
9
9
 
10
- class ValidationError < Exception; end
11
-
12
10
  # This is here because we're using the name as the keys for the
13
11
  # _browse_routes hash. We want to build it outside of this class for that.
14
12
  def self.build_name(request_method, path_info)
15
13
  "#{request_method} #{path_info}"
16
14
  end
17
15
 
18
- def initialize(request_method, path_info, description, parameters = nil)
16
+ def initialize(request_method, path_info, description, declaration_maps = nil)
19
17
  @name = build_name(request_method, path_info)
20
18
  @match = build_match(request_method, path_info)
21
19
  @description = description
22
- @parameters = parameters || {}
20
+ build_declarations(declaration_maps || {})
23
21
  end
24
22
 
25
- def to_hash
26
- {name: @name, description: @description}.merge @parameters
23
+ def to_hash(options = {})
24
+ {
25
+ route: @name,
26
+ description: @description,
27
+ parameters: @param_declarations.map { |name, pd| pd.to_hash(options) }
28
+ }
27
29
  end
28
30
 
29
31
  def matches?(request_method, path_info)
30
32
  !! (build_name(request_method,path_info) =~ @match)
31
33
  end
32
34
 
33
- def has_parameter?(parameter)
34
- @parameters.has_key?(parameter.to_sym)
35
+ def has_parameter?(name)
36
+ @param_declarations.has_key?(name.to_sym)
35
37
  end
36
38
 
37
- def coerce_type(params)
38
- @parameters.each { |k,v|
39
- params[k] &&= case v[:type]
40
- when :Boolean
41
- cast_to_boolean(params[k])
42
- else
43
- send(v[:type], params[k])
44
- end
45
- }
46
- end
39
+ def process(params)
40
+ @param_declarations.each do |name, pd|
41
+ name = name.to_s # The params hash uses strings but declarations use symbols
47
42
 
48
- def set_defaults(params)
49
- @parameters.each { |k,v|
50
- unless params[k] || v[:default].nil?
51
- params[k] = v[:default].is_a?(Proc) ? v[:default].call(params[k]) : v[:default]
52
- end
53
- }
54
- end
55
-
56
- def delete_undefined(params, allowed)
57
- params.delete_if { |i| !(self.has_parameter?(i) || allowed.member?(i)) }
58
- end
43
+ params[name] ||= pd.default
59
44
 
60
- def validate(params)
61
- @parameters.each { |k,v|
62
- return fail_validation k, params[k], v, :required if !params[k] && v[:required]
63
- if params[k]
64
- return fail_validation k, params[k], v, :depends_on if v[:depends_on] && !params[v[:depends_on]]
65
- return fail_validation k, params[k], v, :in if v[:in] && !v[:in].member?(params[k])
66
-
67
- if v[:type] == :String
68
- return fail_validation k, params[k], v, :format if v[:format] && !(params[k] =~ v[:format])
69
- return fail_validation k, params[k], v, :min_length if v[:min_length] && params[k].length < v[:min_length]
70
- return fail_validation k, params[k], v, :max_length if v[:max_length] && params[k].length > v[:max_length]
71
- end
45
+ # We specifically check for nil here since a boolean's default can be false
46
+ if params[name].nil?
47
+ return false, pd.build_error_hash(:required, nil) if pd.required?
48
+ next
72
49
  end
73
- }
74
50
 
75
- {success: true}
76
- end
51
+ params[name] = pd.coerce(params[name])
77
52
 
78
- def transform(params)
79
- @parameters.each { |k,v|
80
- params[k] = v[:transform].to_proc.call(params[k]) if params[k] && v[:transform]
81
- }
82
- end
53
+ success, error_hash = pd.validate(params)
54
+ return false, error_hash unless success
83
55
 
84
- private
85
- def cast_to_boolean(param)
86
- case param
87
- when "y", "yes", "t", "true", "1"
88
- true
89
- when "n", "no", "f", "false", "0"
90
- false
56
+ params[name] = pd.transform(params[name])
91
57
  end
58
+
59
+ true
92
60
  end
93
61
 
94
- def fail_validation(parameter, value, options, reason)
95
- {success: false, reason: reason , parameter: parameter, value: value}.merge(options)
62
+ def delete_undefined(params, allowed)
63
+ params.delete_if { |i| !(self.has_parameter?(i) || allowed.member?(i)) }
96
64
  end
97
65
 
66
+ private
98
67
  def build_name(request_method, path_info)
99
68
  self.class.build_name(request_method, path_info)
100
69
  end
@@ -102,5 +71,15 @@
102
71
  def build_match(request_method, path_info)
103
72
  /^#{request_method}\s\s#{path_info.gsub(/:[^\/]*/, '[^\/]*')}$/
104
73
  end
74
+
75
+ def build_declarations(declaration_maps)
76
+ @param_declarations = {}
77
+
78
+ declaration_maps.each do |name, map|
79
+ type = map.delete(:type)
80
+
81
+ @param_declarations[name] = Sinatra::Browse.const_get("#{type}Type").new(name, map)
82
+ end
83
+ end
105
84
  end
106
85
  end
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Sinatra::Browse
4
+ class Validator
5
+ attr_reader :criteria
6
+ attr_reader :value
7
+ attr_reader :name
8
+
9
+ def initialize(map)
10
+ @name = map[:name]
11
+ @criteria = map[:criteria]
12
+ @validation_blk = map[:validation_blk]
13
+ end
14
+
15
+ def validate(param_name, params)
16
+ @value = params[param_name]
17
+ @params = params
18
+
19
+ instance_exec @criteria, &@validation_blk
20
+ end
21
+ end
22
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-browse
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.4'
4
+ version: '0.5'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Axsh Co. LTD
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-14 00:00:00.000000000 Z
11
+ date: 2014-07-28 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Parameter declaration framework and browsable API for Sinatra
14
14
  email: dev@axsh.net
@@ -17,8 +17,17 @@ extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
19
  - lib/sinatra/browse.rb
20
- - lib/sinatra/browse/format.rb
21
20
  - lib/sinatra/browse/route.rb
21
+ - lib/sinatra/browse/errors.rb
22
+ - lib/sinatra/browse/erb_templates/html.erb
23
+ - lib/sinatra/browse/erb_templates/markdown.erb
24
+ - lib/sinatra/browse/parameter_type.rb
25
+ - lib/sinatra/browse/parameter_types/boolean.rb
26
+ - lib/sinatra/browse/parameter_types/float.rb
27
+ - lib/sinatra/browse/parameter_types/string.rb
28
+ - lib/sinatra/browse/parameter_types/integer.rb
29
+ - lib/sinatra/browse/format.rb
30
+ - lib/sinatra/browse/validator.rb
22
31
  - LICENSE
23
32
  - README.md
24
33
  homepage: https://github.com/axsh/sinatra-browse