sinatra-browse 0.4 → 0.5

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.
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