explicit 0.1.0 → 0.2.0
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 +4 -4
- data/README.md +281 -58
- data/app/views/explicit/application/_documentation.html.erb +127 -1
- data/app/views/explicit/application/_request.html.erb +37 -0
- data/config/locales/en.yml +3 -0
- data/lib/explicit/configuration.rb +51 -0
- data/lib/explicit/documentation/markdown.rb +23 -0
- data/lib/explicit/documentation/property.rb +19 -0
- data/lib/explicit/documentation.rb +132 -14
- data/lib/explicit/engine.rb +4 -2
- data/lib/explicit/request/example.rb +5 -0
- data/lib/explicit/request/{invalid_params.rb → invalid_params_error.rb} +6 -8
- data/lib/explicit/request/invalid_response_error.rb +19 -5
- data/lib/explicit/request/response.rb +7 -0
- data/lib/explicit/request/route.rb +9 -0
- data/lib/explicit/request.rb +105 -54
- data/lib/explicit/spec/agreement.rb +5 -16
- data/lib/explicit/spec/bigdecimal.rb +27 -0
- data/lib/explicit/spec/boolean.rb +9 -10
- data/lib/explicit/spec/error.rb +3 -0
- data/lib/explicit/spec/one_of.rb +20 -2
- data/lib/explicit/spec/record.rb +2 -0
- data/lib/explicit/spec.rb +8 -0
- data/lib/explicit/test_helper/example_recorder.rb +48 -0
- data/lib/explicit/test_helper.rb +42 -7
- data/lib/explicit/version.rb +1 -1
- data/lib/explicit.rb +13 -6
- metadata +29 -6
@@ -0,0 +1,37 @@
|
|
1
|
+
<h1 id="<%= page.anchor %>"><%= page.title %></h1>
|
2
|
+
|
3
|
+
<div class="page__container">
|
4
|
+
<div class="page__request">
|
5
|
+
<div class="page__request__description markdown">
|
6
|
+
<%= page.description_html %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<div class="params">
|
10
|
+
<div class="params__header">
|
11
|
+
<%= t("explicit.documentation.params_header") %>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<% page.params_properties.each do |property| %>
|
15
|
+
<div class="params__param">
|
16
|
+
<div class="params__param__name">
|
17
|
+
<%= property.name %>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<% if (description_html = property.description_html) %>
|
21
|
+
<div class="params__param__description markdown">
|
22
|
+
<%= description_html %>
|
23
|
+
</div>
|
24
|
+
<% end %>
|
25
|
+
</div>
|
26
|
+
<% end %>
|
27
|
+
</div>
|
28
|
+
</div>
|
29
|
+
|
30
|
+
<div class="page__response">
|
31
|
+
<% page.request.responses.map { |k, v| k }.sort.each do |status| %>
|
32
|
+
<div>
|
33
|
+
<%= status %>
|
34
|
+
</div>
|
35
|
+
<% end %>
|
36
|
+
</div>
|
37
|
+
</div>
|
data/config/locales/en.yml
CHANGED
@@ -3,6 +3,7 @@ en:
|
|
3
3
|
errors:
|
4
4
|
agreement: "must be accepted"
|
5
5
|
array: "invalid item at index(%{index}): %{error}"
|
6
|
+
bigdecimal: "must be a string-encoded decimal number"
|
6
7
|
boolean: "must be a boolean"
|
7
8
|
date_time_iso8601: "must be a valid iso8601 date time"
|
8
9
|
date_time_posix: "must be a valid posix timestamps"
|
@@ -21,3 +22,5 @@ en:
|
|
21
22
|
minlength: "length must be greater than or equal to %{minlength}"
|
22
23
|
maxlength: "length must be smaller than or equal to %{maxlength}"
|
23
24
|
format: "must have format %{regex}"
|
25
|
+
documentation:
|
26
|
+
params_header: Request params
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Explicit
|
4
|
+
extend self
|
5
|
+
|
6
|
+
class Configuration
|
7
|
+
def initialize
|
8
|
+
@rescue_from_invalid_params = true
|
9
|
+
end
|
10
|
+
|
11
|
+
def request_examples_file_path=(path)
|
12
|
+
@request_examples_file_path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
def request_examples_file_path
|
16
|
+
@request_examples_file_path ||= ::Rails.root&.join("public/explicit_request_examples.json")
|
17
|
+
end
|
18
|
+
|
19
|
+
def request_examples_persistance_enabled?
|
20
|
+
ENV["EXPLICIT_PERSIST_EXAMPLES"].in? %w[true 1 on]
|
21
|
+
end
|
22
|
+
|
23
|
+
def rescue_from_invalid_params=(enabled)
|
24
|
+
@rescue_from_invalid_params = enabled
|
25
|
+
end
|
26
|
+
|
27
|
+
def rescue_from_invalid_params?
|
28
|
+
@rescue_from_invalid_params
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_runner=(test_runner)
|
32
|
+
@test_runner = test_runner
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_runner
|
36
|
+
@test_runner ||=
|
37
|
+
if defined?(::RSpec) && ::RSpec.respond_to?(:configure)
|
38
|
+
:rspec
|
39
|
+
else
|
40
|
+
:minitest
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
attr_reader :configuration
|
46
|
+
@configuration = Configuration.new
|
47
|
+
|
48
|
+
def configure(&block)
|
49
|
+
block.call(@configuration)
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Explicit::Documentation::Markdown
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def render(markdown_text)
|
7
|
+
offset = 0
|
8
|
+
markdown_text.each_char do |ch|
|
9
|
+
break if ch != " "
|
10
|
+
|
11
|
+
offset += 1
|
12
|
+
end
|
13
|
+
|
14
|
+
markdown_text = markdown_text.each_line.map do |line|
|
15
|
+
line[offset..-1] || "\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
::Commonmarker.to_html(markdown_text.join, options: {
|
19
|
+
parse: { smart: true },
|
20
|
+
render: { hardbreaks: false }
|
21
|
+
}).html_safe
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Explicit::Documentation::Property
|
4
|
+
attr_reader :name, :spec
|
5
|
+
|
6
|
+
def initialize(name:, spec:)
|
7
|
+
@name = name
|
8
|
+
@spec = spec
|
9
|
+
end
|
10
|
+
|
11
|
+
def description_html
|
12
|
+
case spec
|
13
|
+
in [:description, markdown_text, _subspec]
|
14
|
+
Explicit::Documentation::Markdown.render(markdown_text).html_safe
|
15
|
+
else
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,33 +1,151 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "commonmarker"
|
4
|
+
|
3
5
|
module Explicit::Documentation
|
6
|
+
Section = ::Data.define(:name, :pages)
|
7
|
+
|
8
|
+
module Page
|
9
|
+
class Partial
|
10
|
+
attr_reader :title, :partial
|
11
|
+
|
12
|
+
def initialize(title:, partial:)
|
13
|
+
@title = title
|
14
|
+
@partial = partial
|
15
|
+
end
|
16
|
+
|
17
|
+
def request?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def anchor
|
22
|
+
title.dasherize
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Request
|
27
|
+
attr_reader :request
|
28
|
+
|
29
|
+
def initialize(request:)
|
30
|
+
@request = request
|
31
|
+
end
|
32
|
+
|
33
|
+
def request?
|
34
|
+
true
|
35
|
+
end
|
36
|
+
|
37
|
+
def title
|
38
|
+
@request.get_title
|
39
|
+
end
|
40
|
+
|
41
|
+
def description_html
|
42
|
+
Explicit::Documentation::Markdown.render(@request.get_description).html_safe
|
43
|
+
end
|
44
|
+
|
45
|
+
def anchor
|
46
|
+
title.dasherize
|
47
|
+
end
|
48
|
+
|
49
|
+
def params_properties
|
50
|
+
@request.params.map do |name, spec|
|
51
|
+
Property.new(name:, spec:)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def headers_properties
|
56
|
+
@request.headers.map do |name, spec|
|
57
|
+
Property.new(name:, spec:)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def partial
|
62
|
+
"request"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
4
67
|
class Builder
|
68
|
+
attr_reader :sections
|
69
|
+
|
70
|
+
def initialize
|
71
|
+
@sections = []
|
72
|
+
@current_section = nil
|
73
|
+
end
|
74
|
+
|
5
75
|
def page_title(page_title)
|
6
76
|
@page_title = page_title
|
7
77
|
end
|
8
78
|
|
9
|
-
def
|
10
|
-
@
|
11
|
-
|
79
|
+
def section(name, &block)
|
80
|
+
@current_section = Section.new(name:, pages: [])
|
81
|
+
|
82
|
+
block.call
|
83
|
+
|
84
|
+
@sections << @current_section
|
12
85
|
|
13
|
-
|
86
|
+
@current_section = nil
|
14
87
|
end
|
15
88
|
|
16
|
-
def add(**opts)
|
89
|
+
def add(*requests, **opts)
|
90
|
+
raise ArgumentError(<<-MD) if @current_section.nil?
|
91
|
+
You must define a section before adding a page. For example:
|
92
|
+
|
93
|
+
section "Customers" do
|
94
|
+
add CustomersController::CreateRequest
|
95
|
+
end
|
96
|
+
MD
|
97
|
+
|
98
|
+
if requests.one?
|
99
|
+
@current_section.pages << Page::Request.new(request: requests.first)
|
100
|
+
elsif opts[:partial]
|
101
|
+
@current_section.pages << Page::Partial.new(title: opts[:title], partial: opts[:partial])
|
102
|
+
else
|
103
|
+
raise ArgumentError("expected request or a partial")
|
104
|
+
end
|
17
105
|
end
|
18
106
|
|
19
107
|
def call(request)
|
20
|
-
html
|
21
|
-
partial: "documentation",
|
22
|
-
locals: {
|
23
|
-
page_title: @page_title,
|
24
|
-
primary_color: @primary_color,
|
25
|
-
sections: @sections
|
26
|
-
}
|
27
|
-
)
|
108
|
+
@html ||= render_documentation_page
|
28
109
|
|
29
|
-
[200, {}, [html]]
|
110
|
+
[200, {}, [@html]]
|
30
111
|
end
|
112
|
+
|
113
|
+
private
|
114
|
+
def render_documentation_page
|
115
|
+
merge_request_examples_from_file!
|
116
|
+
|
117
|
+
Explicit::ApplicationController.render(
|
118
|
+
partial: "documentation",
|
119
|
+
locals: {
|
120
|
+
page_title: @page_title,
|
121
|
+
sections: @sections
|
122
|
+
}
|
123
|
+
)
|
124
|
+
end
|
125
|
+
|
126
|
+
def merge_request_examples_from_file!
|
127
|
+
return if !Explicit.configuration.request_examples_file_path
|
128
|
+
|
129
|
+
encoded = ::File.read(Explicit.configuration.request_examples_file_path)
|
130
|
+
examples = ::JSON.parse(encoded)
|
131
|
+
|
132
|
+
@sections.each do |section|
|
133
|
+
section.pages.filter(&:request?).each do |page|
|
134
|
+
if examples.key?(page.request.gid)
|
135
|
+
examples[page.request.gid].each do |example|
|
136
|
+
page.request.add_example(
|
137
|
+
params: example["params"].with_indifferent_access,
|
138
|
+
headers: example["headers"],
|
139
|
+
response: {
|
140
|
+
status: example.dig("response", "status"),
|
141
|
+
data: example.dig("response", "data").with_indifferent_access
|
142
|
+
}
|
143
|
+
)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
31
149
|
end
|
32
150
|
|
33
151
|
def self.build(&block)
|
data/lib/explicit/engine.rb
CHANGED
@@ -2,8 +2,10 @@
|
|
2
2
|
|
3
3
|
class Explicit::Engine < ::Rails::Engine
|
4
4
|
initializer "explicit.rescue_from_invalid_params" do
|
5
|
-
|
6
|
-
|
5
|
+
if Explicit.configuration.rescue_from_invalid_params?
|
6
|
+
ActiveSupport.on_load(:action_controller_api) do
|
7
|
+
include Explicit::Request::InvalidParamsError::Rescuer
|
8
|
+
end
|
7
9
|
end
|
8
10
|
end
|
9
11
|
end
|
@@ -1,19 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
attr_reader :errors
|
3
|
+
class Explicit::Request::InvalidParamsError < ::RuntimeError
|
4
|
+
attr_reader :errors
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
end
|
6
|
+
def initialize(errors)
|
7
|
+
@errors = errors
|
10
8
|
end
|
11
9
|
|
12
|
-
module
|
10
|
+
module Rescuer
|
13
11
|
extend ::ActiveSupport::Concern
|
14
12
|
|
15
13
|
included do
|
16
|
-
rescue_from
|
14
|
+
rescue_from Explicit::Request::InvalidParamsError do |err|
|
17
15
|
params = Explicit::Spec::Error.translate(err.errors)
|
18
16
|
|
19
17
|
render json: { error: "invalid_params", params: }, status: 422
|
@@ -2,16 +2,30 @@
|
|
2
2
|
|
3
3
|
class Explicit::Request::InvalidResponseError < ::RuntimeError
|
4
4
|
def initialize(response, error)
|
5
|
+
error => [:one_of, *errs]
|
6
|
+
error = errs.map { translate_response_error(response, _1) }.join("\n")
|
7
|
+
|
8
|
+
|
5
9
|
super <<-TXT
|
6
|
-
Response did not match expected spec.
|
7
10
|
|
8
|
-
Got:
|
9
11
|
|
10
|
-
|
12
|
+
Got:
|
13
|
+
|
14
|
+
HTTP #{response.status} #{JSON.pretty_generate(response.data)}
|
11
15
|
|
12
|
-
|
16
|
+
This response doesn't match any spec. Here are the errors:
|
17
|
+
|
18
|
+
#{error.presence || " ==> no response specs found for status #{response.status}"}
|
13
19
|
|
14
|
-
#{error.inspect}
|
15
20
|
TXT
|
16
21
|
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def translate_response_error(response, error)
|
25
|
+
error = Explicit::Spec::Error.translate(error)
|
26
|
+
|
27
|
+
<<-TXT
|
28
|
+
HTTP #{response.status} #{JSON.pretty_generate(error)}
|
29
|
+
TXT
|
30
|
+
end
|
17
31
|
end
|
data/lib/explicit/request.rb
CHANGED
@@ -1,73 +1,124 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Explicit::Request
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
4
|
+
attr_reader :routes, :headers, :params, :responses, :examples
|
5
|
+
|
6
|
+
def initialize(&block)
|
7
|
+
@routes = []
|
8
|
+
@headers = {}
|
9
|
+
@params = {}
|
10
|
+
@responses = Hash.new { |hash, key| hash[key] = [] }
|
11
|
+
@examples = Hash.new { |hash, key| hash[key] = [] }
|
12
|
+
|
13
|
+
if Explicit.configuration.rescue_from_invalid_params?
|
14
|
+
@responses[422] << {
|
15
|
+
error: "invalid_params",
|
16
|
+
params: [:hash, :string, :string]
|
17
|
+
}
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
|
20
|
+
instance_eval(&block)
|
21
|
+
end
|
22
|
+
|
23
|
+
def new(&block)
|
24
|
+
subrequest = self.class.new { }
|
25
|
+
|
26
|
+
subrequest.instance_variable_set(:@base_url, @base_url)
|
27
|
+
subrequest.instance_variable_set(:@base_path, @base_path)
|
28
|
+
subrequest.instance_variable_set(:@routes, @routes.dup)
|
29
|
+
subrequest.instance_variable_set(:@headers, @headers.dup)
|
30
|
+
subrequest.instance_variable_set(:@params, @params.dup)
|
31
|
+
subrequest.instance_variable_set(:@responses, @responses.dup)
|
32
|
+
subrequest.instance_variable_set(:@examples, @examples.dup)
|
33
|
+
|
34
|
+
subrequest.tap { _1.instance_eval(&block) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def get(path) = @routes << Route.new(method: :get, path:)
|
38
|
+
def head(path) = @routes << Route.new(method: :head, path:)
|
39
|
+
def post(path) = @routes << Route.new(method: :post, path:)
|
40
|
+
def put(path) = @routes << Route.new(method: :put, path:)
|
41
|
+
def delete(path) = @routes << Route.new(method: :delete, path:)
|
42
|
+
def options(path) = @routes << Route.new(method: :options, path:)
|
43
|
+
def patch(path) = @routes << Route.new(method: :patch, path:)
|
44
|
+
|
45
|
+
def base_url(url) = (@base_url = url)
|
46
|
+
def get_base_url = @base_url
|
22
47
|
|
23
|
-
|
24
|
-
|
48
|
+
def base_path(prefix) = (@base_path = prefix)
|
49
|
+
def get_base_path = @base_path
|
25
50
|
|
26
|
-
|
51
|
+
def title(text) = (@title = text)
|
52
|
+
def get_title = @title || @routes.first.to_s
|
53
|
+
|
54
|
+
def description(markdown) = (@description = markdown)
|
55
|
+
def get_description = @description
|
56
|
+
|
57
|
+
def header(name, spec)
|
58
|
+
raise ArgumentError("duplicated header #{name}") if @headers.key?(name)
|
59
|
+
|
60
|
+
@headers[name] = spec
|
61
|
+
end
|
62
|
+
|
63
|
+
def param(name, spec, **options)
|
64
|
+
raise ArgumentError("duplicated param #{name}") if @params.key?(name)
|
65
|
+
|
66
|
+
if options[:optional]
|
67
|
+
spec = [:nilable, spec]
|
27
68
|
end
|
28
69
|
|
29
|
-
|
30
|
-
|
70
|
+
if (defaultval = options[:default])
|
71
|
+
spec = [:default, defaultval, spec]
|
72
|
+
end
|
31
73
|
|
32
|
-
|
74
|
+
if (description = options[:description])
|
75
|
+
spec = [:description, description, spec]
|
33
76
|
end
|
34
77
|
|
35
|
-
|
36
|
-
|
78
|
+
@params[name] = spec
|
79
|
+
end
|
80
|
+
|
81
|
+
def response(status, spec)
|
82
|
+
@responses[status] << spec
|
83
|
+
end
|
84
|
+
|
85
|
+
def add_example(params:, response:, headers: {})
|
86
|
+
raise ArgumentError.new("missing :status in response") if !response.key?(:status)
|
87
|
+
raise ArgumentError.new("missing :data in response") if !response.key?(:data)
|
88
|
+
|
89
|
+
status, data = response.values_at(:status, :data)
|
90
|
+
|
91
|
+
response = Response.new(status:, data:)
|
92
|
+
|
93
|
+
case responses_validator(status:).call(data)
|
94
|
+
in [:ok, _] then nil
|
95
|
+
in [:error, err] then raise InvalidResponseError.new(response, err)
|
37
96
|
end
|
38
97
|
|
39
|
-
|
40
|
-
|
98
|
+
@examples[response.status] << Example.new(params:, headers:, response:)
|
99
|
+
end
|
41
100
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
101
|
+
def validate!(values)
|
102
|
+
case params_validator.call(values)
|
103
|
+
in [:ok, validated_data] then validated_data
|
104
|
+
in [:error, err] then raise InvalidParamsError.new(err)
|
46
105
|
end
|
106
|
+
end
|
47
107
|
|
48
|
-
|
49
|
-
|
50
|
-
@routes ||= []
|
51
|
-
end
|
52
|
-
|
53
|
-
def headers
|
54
|
-
@headers ||= {}
|
55
|
-
end
|
56
|
-
|
57
|
-
def params
|
58
|
-
@params ||= {}
|
59
|
-
end
|
60
|
-
|
61
|
-
INVALID_PARAMS_SPEC = {
|
62
|
-
status: [:literal, 422],
|
63
|
-
data: {
|
64
|
-
error: "invalid_params",
|
65
|
-
params: [:hash, :string, :string]
|
66
|
-
}
|
67
|
-
}.freeze
|
68
|
-
|
69
|
-
def responses
|
70
|
-
@responses ||= [INVALID_PARAMS_SPEC]
|
71
|
-
end
|
108
|
+
def gid
|
109
|
+
routes.first.to_s
|
72
110
|
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def params_validator
|
114
|
+
@params_validator ||= Explicit::Spec.build(@params)
|
115
|
+
end
|
116
|
+
|
117
|
+
def headers_validator
|
118
|
+
@headers_validator ||= Explicit::Spec.build(@headers)
|
119
|
+
end
|
120
|
+
|
121
|
+
def responses_validator(status:)
|
122
|
+
Explicit::Spec.build([:one_of, *@responses[status]])
|
123
|
+
end
|
73
124
|
end
|
@@ -3,26 +3,15 @@
|
|
3
3
|
module Explicit::Spec::Agreement
|
4
4
|
extend self
|
5
5
|
|
6
|
-
VALUES =
|
7
|
-
|
8
|
-
|
9
|
-
"1" => true
|
10
|
-
}.freeze
|
6
|
+
VALUES = [true, "true", "on", "1", 1].freeze
|
7
|
+
ERROR = [:error, :agreement].freeze
|
8
|
+
OK = [:ok, true].freeze
|
11
9
|
|
12
10
|
def build(options)
|
13
11
|
lambda do |value|
|
14
|
-
value
|
15
|
-
if value.is_a?(TrueClass)
|
16
|
-
value
|
17
|
-
elsif value.is_a?(::String) && options[:parse]
|
18
|
-
VALUES[value]
|
19
|
-
else
|
20
|
-
nil
|
21
|
-
end
|
12
|
+
return ERROR if !VALUES.include?(value)
|
22
13
|
|
23
|
-
|
24
|
-
|
25
|
-
[:ok, value]
|
14
|
+
OK
|
26
15
|
end
|
27
16
|
end
|
28
17
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Explicit::Spec::Bigdecimal
|
4
|
+
extend self
|
5
|
+
|
6
|
+
ERROR = [:error, :bigdecimal].freeze
|
7
|
+
|
8
|
+
def build(options)
|
9
|
+
lambda do |value|
|
10
|
+
return ERROR unless value.is_a?(::String) || value.is_a?(::Integer)
|
11
|
+
|
12
|
+
decimalvalue = BigDecimal(value)
|
13
|
+
|
14
|
+
if (min = options[:min]) && decimalvalue < min
|
15
|
+
return [:error, [:min, min]]
|
16
|
+
end
|
17
|
+
|
18
|
+
if (max = options[:max]) && decimalvalue > max
|
19
|
+
return [:error, [:max, max]]
|
20
|
+
end
|
21
|
+
|
22
|
+
[:ok, decimalvalue]
|
23
|
+
rescue ArgumentError
|
24
|
+
ERROR
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -4,26 +4,25 @@ module Explicit::Spec::Boolean
|
|
4
4
|
extend self
|
5
5
|
|
6
6
|
VALUES = {
|
7
|
+
true => true,
|
7
8
|
"true" => true,
|
8
9
|
"on" => true,
|
9
10
|
"1" => true,
|
11
|
+
1 => true,
|
12
|
+
false => false,
|
10
13
|
"false" => false,
|
11
14
|
"off" => false,
|
12
|
-
"0" => false
|
15
|
+
"0" => false,
|
16
|
+
0 => false
|
13
17
|
}.freeze
|
14
18
|
|
19
|
+
ERROR = [:error, :boolean].freeze
|
20
|
+
|
15
21
|
def build(options)
|
16
22
|
lambda do |value|
|
17
|
-
value =
|
18
|
-
if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
19
|
-
value
|
20
|
-
elsif value.is_a?(::String)
|
21
|
-
VALUES[value]
|
22
|
-
else
|
23
|
-
nil
|
24
|
-
end
|
23
|
+
value = VALUES[value]
|
25
24
|
|
26
|
-
return
|
25
|
+
return ERROR if value.nil?
|
27
26
|
|
28
27
|
[:ok, value]
|
29
28
|
end
|