explicit 0.2.0 → 0.2.2
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 +76 -68
- data/app/helpers/explicit/application_helper.rb +32 -0
- data/app/views/explicit/documentation/_attribute.html.erb +38 -0
- data/app/views/explicit/documentation/_page.html.erb +166 -0
- data/app/views/explicit/documentation/_request.html.erb +87 -0
- data/app/views/explicit/documentation/request/_examples.html.erb +50 -0
- data/app/views/explicit/documentation/type/_agreement.html.erb +7 -0
- data/app/views/explicit/documentation/type/_array.html.erb +3 -0
- data/app/views/explicit/documentation/type/_big_decimal.html.erb +4 -0
- data/app/views/explicit/documentation/type/_boolean.html.erb +7 -0
- data/app/views/explicit/documentation/type/_date_time_iso8601.html.erb +3 -0
- data/app/views/explicit/documentation/type/_date_time_posix.html.erb +3 -0
- data/app/views/explicit/documentation/type/_enum.html.erb +7 -0
- data/app/views/explicit/documentation/type/_file.html.erb +9 -0
- data/app/views/explicit/documentation/type/_hash.html.erb +4 -0
- data/app/views/explicit/documentation/type/_integer.html.erb +25 -0
- data/app/views/explicit/documentation/type/_one_of.html.erb +11 -0
- data/app/views/explicit/documentation/type/_record.html.erb +9 -0
- data/app/views/explicit/documentation/type/_string.html.erb +21 -0
- data/config/locales/en.yml +27 -11
- data/lib/explicit/configuration.rb +1 -1
- data/lib/explicit/documentation/builder.rb +80 -0
- data/lib/explicit/documentation/markdown.rb +2 -13
- data/lib/explicit/documentation/output/swagger.rb +176 -0
- data/lib/explicit/documentation/output/webpage.rb +31 -0
- data/lib/explicit/documentation/page/partial.rb +20 -0
- data/lib/explicit/documentation/page/request.rb +27 -0
- data/lib/explicit/documentation/section.rb +9 -0
- data/lib/explicit/documentation.rb +12 -145
- data/lib/explicit/request/example.rb +50 -1
- data/lib/explicit/request/invalid_params_error.rb +1 -3
- data/lib/explicit/request/invalid_response_error.rb +2 -15
- data/lib/explicit/request/route.rb +18 -0
- data/lib/explicit/request.rb +43 -24
- data/lib/explicit/test_helper/example_recorder.rb +7 -2
- data/lib/explicit/test_helper.rb +25 -7
- data/lib/explicit/type/agreement.rb +39 -0
- data/lib/explicit/type/array.rb +56 -0
- data/lib/explicit/type/big_decimal.rb +58 -0
- data/lib/explicit/type/boolean.rb +47 -0
- data/lib/explicit/type/date_time_iso8601.rb +41 -0
- data/lib/explicit/type/date_time_posix.rb +44 -0
- data/lib/explicit/type/enum.rb +41 -0
- data/lib/explicit/type/file.rb +60 -0
- data/lib/explicit/type/hash.rb +57 -0
- data/lib/explicit/type/integer.rb +79 -0
- data/lib/explicit/type/literal.rb +45 -0
- data/lib/explicit/type/modifiers/default.rb +24 -0
- data/lib/explicit/type/modifiers/description.rb +11 -0
- data/lib/explicit/type/modifiers/nilable.rb +19 -0
- data/lib/explicit/type/modifiers/param_location.rb +11 -0
- data/lib/explicit/type/one_of.rb +46 -0
- data/lib/explicit/type/record.rb +96 -0
- data/lib/explicit/type/string.rb +68 -0
- data/lib/explicit/type.rb +112 -0
- data/lib/explicit/version.rb +1 -1
- data/lib/explicit.rb +28 -18
- metadata +47 -25
- data/app/views/explicit/application/_documentation.html.erb +0 -136
- data/app/views/explicit/application/_request.html.erb +0 -37
- data/lib/explicit/documentation/property.rb +0 -19
- data/lib/explicit/spec/agreement.rb +0 -17
- data/lib/explicit/spec/array.rb +0 -28
- data/lib/explicit/spec/bigdecimal.rb +0 -27
- data/lib/explicit/spec/boolean.rb +0 -30
- data/lib/explicit/spec/date_time_iso8601.rb +0 -17
- data/lib/explicit/spec/date_time_posix.rb +0 -21
- data/lib/explicit/spec/default.rb +0 -20
- data/lib/explicit/spec/error.rb +0 -63
- data/lib/explicit/spec/hash.rb +0 -30
- data/lib/explicit/spec/inclusion.rb +0 -15
- data/lib/explicit/spec/integer.rb +0 -53
- data/lib/explicit/spec/literal.rb +0 -15
- data/lib/explicit/spec/nilable.rb +0 -15
- data/lib/explicit/spec/one_of.rb +0 -40
- data/lib/explicit/spec/record.rb +0 -33
- data/lib/explicit/spec/string.rb +0 -50
- data/lib/explicit/spec.rb +0 -72
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Explicit::Documentation::Page
|
4
|
+
class Request
|
5
|
+
attr_reader :request
|
6
|
+
|
7
|
+
def initialize(request:)
|
8
|
+
@request = request
|
9
|
+
end
|
10
|
+
|
11
|
+
def request?
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def title
|
16
|
+
@request.get_title
|
17
|
+
end
|
18
|
+
|
19
|
+
def anchor
|
20
|
+
title.gsub(" ", "-").downcase
|
21
|
+
end
|
22
|
+
|
23
|
+
def partial
|
24
|
+
"explicit/documentation/request"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -3,156 +3,23 @@
|
|
3
3
|
require "commonmarker"
|
4
4
|
|
5
5
|
module Explicit::Documentation
|
6
|
-
|
6
|
+
extend self
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
attr_reader :title, :partial
|
8
|
+
def new(&block)
|
9
|
+
engine = ::Class.new(::Rails::Engine)
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
67
|
-
class Builder
|
68
|
-
attr_reader :sections
|
69
|
-
|
70
|
-
def initialize
|
71
|
-
@sections = []
|
72
|
-
@current_section = nil
|
73
|
-
end
|
74
|
-
|
75
|
-
def page_title(page_title)
|
76
|
-
@page_title = page_title
|
11
|
+
builder = Builder.new(engine).tap do |builder|
|
12
|
+
builder.instance_eval &block
|
13
|
+
builder.merge_request_examples_from_file!
|
77
14
|
end
|
78
15
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
@sections << @current_section
|
85
|
-
|
86
|
-
@current_section = nil
|
87
|
-
end
|
88
|
-
|
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
|
105
|
-
end
|
106
|
-
|
107
|
-
def call(request)
|
108
|
-
@html ||= render_documentation_page
|
109
|
-
|
110
|
-
[200, {}, [@html]]
|
16
|
+
engine.define_singleton_method(:documentation_builder) { builder }
|
17
|
+
|
18
|
+
engine.routes.draw do
|
19
|
+
get "/", to: builder.webpage, as: :explicit_documentation_webpage
|
20
|
+
get "/swagger", to: builder.swagger, as: :explicit_documentation_swagger
|
111
21
|
end
|
112
22
|
|
113
|
-
|
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
|
149
|
-
end
|
150
|
-
|
151
|
-
def self.build(&block)
|
152
|
-
builder = Builder.new.tap { _1.instance_eval &block }
|
153
|
-
|
154
|
-
::Class.new(::Rails::Engine).tap do |engine|
|
155
|
-
engine.routes.draw { root to: builder }
|
156
|
-
end
|
23
|
+
engine
|
157
24
|
end
|
158
25
|
end
|
@@ -1,5 +1,54 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Explicit::Request
|
4
|
-
Example = ::Data.define(:params, :headers, :response)
|
4
|
+
Example = ::Data.define(:request, :params, :headers, :response) do
|
5
|
+
def to_curl_lines
|
6
|
+
route = request.routes.first
|
7
|
+
method = route.method.to_s.upcase
|
8
|
+
path = route.replace_path_params(params)
|
9
|
+
url = "#{request.get_base_url}#{request.get_base_path}#{path}"
|
10
|
+
curl_request = "curl -X#{method} \"#{url}\""
|
11
|
+
|
12
|
+
body_params = params.except(*route.params)
|
13
|
+
|
14
|
+
curl_headers =
|
15
|
+
if body_params.empty?
|
16
|
+
[]
|
17
|
+
elsif request.accepts_file_upload?
|
18
|
+
['-H "Content-Type: multipart/form-data"']
|
19
|
+
else
|
20
|
+
['-H "Content-Type: application/json"']
|
21
|
+
end
|
22
|
+
|
23
|
+
headers.each do |key, value|
|
24
|
+
curl_headers << "-H \"#{key}: #{value}\""
|
25
|
+
end
|
26
|
+
|
27
|
+
curl_body =
|
28
|
+
if body_params.empty?
|
29
|
+
[]
|
30
|
+
elsif request.accepts_file_upload?
|
31
|
+
file_params = request.params_type.attributes.filter do |name, type|
|
32
|
+
type.is_a?(Explicit::Type::File)
|
33
|
+
end.to_h
|
34
|
+
|
35
|
+
non_file_params = body_params.except(*file_params.keys)
|
36
|
+
|
37
|
+
curl_non_file_params = non_file_params.to_query.split("&").map do |key_value|
|
38
|
+
"-F \"#{CGI.unescape(key_value).gsub('"', '\"')}\""
|
39
|
+
end
|
40
|
+
|
41
|
+
curl_file_params = file_params.map do |name, _|
|
42
|
+
"-F #{name}=\"#{body_params[name]}\""
|
43
|
+
end
|
44
|
+
|
45
|
+
curl_non_file_params.concat(curl_file_params)
|
46
|
+
else
|
47
|
+
# https://stackoverflow.com/questions/34847981/curl-with-multiline-of-json
|
48
|
+
["-d @- << EOF\n#{JSON.pretty_generate(body_params)}\nEOF"]
|
49
|
+
end
|
50
|
+
|
51
|
+
[curl_request].concat(curl_headers).concat(curl_body)
|
52
|
+
end
|
53
|
+
end
|
5
54
|
end
|
@@ -12,9 +12,7 @@ class Explicit::Request::InvalidParamsError < ::RuntimeError
|
|
12
12
|
|
13
13
|
included do
|
14
14
|
rescue_from Explicit::Request::InvalidParamsError do |err|
|
15
|
-
params
|
16
|
-
|
17
|
-
render json: { error: "invalid_params", params: }, status: 422
|
15
|
+
render json: { error: "invalid_params", params: err.errors }, status: 422
|
18
16
|
end
|
19
17
|
end
|
20
18
|
end
|
@@ -2,10 +2,6 @@
|
|
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
|
-
|
9
5
|
super <<-TXT
|
10
6
|
|
11
7
|
|
@@ -13,19 +9,10 @@ Got:
|
|
13
9
|
|
14
10
|
HTTP #{response.status} #{JSON.pretty_generate(response.data)}
|
15
11
|
|
16
|
-
This response doesn't match any
|
12
|
+
This response doesn't match any type. Here are the errors:
|
17
13
|
|
18
|
-
#{error.presence || " ==> no response
|
14
|
+
#{error.presence || " ==> no response types found for status #{response.status}"}
|
19
15
|
|
20
16
|
TXT
|
21
17
|
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
|
31
18
|
end
|
@@ -5,5 +5,23 @@ class Explicit::Request
|
|
5
5
|
def to_s
|
6
6
|
"#{method.to_s.upcase} #{path}"
|
7
7
|
end
|
8
|
+
|
9
|
+
def params
|
10
|
+
path.split("/").filter_map do |part|
|
11
|
+
part[1..-1].to_sym if part.start_with?(":")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def replace_path_params(values)
|
16
|
+
values.reduce(path) do |acc_path, (key, value)|
|
17
|
+
acc_path.gsub(":#{key}", value.to_s)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def path_with_curly_syntax
|
22
|
+
params.reduce(path) do |acc_path, param|
|
23
|
+
acc_path.gsub(":#{param}", "{#{param}}")
|
24
|
+
end
|
25
|
+
end
|
8
26
|
end
|
9
27
|
end
|
data/lib/explicit/request.rb
CHANGED
@@ -13,7 +13,11 @@ class Explicit::Request
|
|
13
13
|
if Explicit.configuration.rescue_from_invalid_params?
|
14
14
|
@responses[422] << {
|
15
15
|
error: "invalid_params",
|
16
|
-
params: [
|
16
|
+
params: [
|
17
|
+
:description,
|
18
|
+
"An object containing error messages for all invalid params",
|
19
|
+
[:hash, :string, :string]
|
20
|
+
]
|
17
21
|
}
|
18
22
|
end
|
19
23
|
|
@@ -28,8 +32,14 @@ class Explicit::Request
|
|
28
32
|
subrequest.instance_variable_set(:@routes, @routes.dup)
|
29
33
|
subrequest.instance_variable_set(:@headers, @headers.dup)
|
30
34
|
subrequest.instance_variable_set(:@params, @params.dup)
|
31
|
-
|
32
|
-
|
35
|
+
|
36
|
+
@responses.each do |status, types|
|
37
|
+
subrequest.responses[status] = types.dup
|
38
|
+
end
|
39
|
+
|
40
|
+
@examples.each do |status, examples|
|
41
|
+
subrequest.examples[status] = examples.dup
|
42
|
+
end
|
33
43
|
|
34
44
|
subrequest.tap { _1.instance_eval(&block) }
|
35
45
|
end
|
@@ -54,32 +64,36 @@ class Explicit::Request
|
|
54
64
|
def description(markdown) = (@description = markdown)
|
55
65
|
def get_description = @description
|
56
66
|
|
57
|
-
def header(name,
|
67
|
+
def header(name, type)
|
58
68
|
raise ArgumentError("duplicated header #{name}") if @headers.key?(name)
|
59
69
|
|
60
|
-
@headers[name] =
|
70
|
+
@headers[name] = type
|
61
71
|
end
|
62
72
|
|
63
|
-
def param(name,
|
73
|
+
def param(name, type, **options)
|
64
74
|
raise ArgumentError("duplicated param #{name}") if @params.key?(name)
|
65
75
|
|
66
76
|
if options[:optional]
|
67
|
-
|
77
|
+
type = [:nilable, type]
|
68
78
|
end
|
69
79
|
|
70
80
|
if (defaultval = options[:default])
|
71
|
-
|
81
|
+
type = [:default, defaultval, type]
|
72
82
|
end
|
73
83
|
|
74
84
|
if (description = options[:description])
|
75
|
-
|
85
|
+
type = [:description, description, type]
|
76
86
|
end
|
77
87
|
|
78
|
-
@params
|
88
|
+
if @routes.first&.params&.include?(name)
|
89
|
+
type = [:_param_location, :path, type]
|
90
|
+
end
|
91
|
+
|
92
|
+
@params[name] = type
|
79
93
|
end
|
80
94
|
|
81
|
-
def response(status,
|
82
|
-
@responses[status] <<
|
95
|
+
def response(status, typespec)
|
96
|
+
@responses[status] << typespec
|
83
97
|
end
|
84
98
|
|
85
99
|
def add_example(params:, response:, headers: {})
|
@@ -90,16 +104,16 @@ class Explicit::Request
|
|
90
104
|
|
91
105
|
response = Response.new(status:, data:)
|
92
106
|
|
93
|
-
case
|
107
|
+
case responses_type(status:).validate(data)
|
94
108
|
in [:ok, _] then nil
|
95
109
|
in [:error, err] then raise InvalidResponseError.new(response, err)
|
96
110
|
end
|
97
111
|
|
98
|
-
@examples[response.status] << Example.new(params:, headers:, response:)
|
112
|
+
@examples[response.status] << Example.new(request: self, params:, headers:, response:)
|
99
113
|
end
|
100
114
|
|
101
115
|
def validate!(values)
|
102
|
-
case
|
116
|
+
case params_type.validate(values)
|
103
117
|
in [:ok, validated_data] then validated_data
|
104
118
|
in [:error, err] then raise InvalidParamsError.new(err)
|
105
119
|
end
|
@@ -109,16 +123,21 @@ class Explicit::Request
|
|
109
123
|
routes.first.to_s
|
110
124
|
end
|
111
125
|
|
112
|
-
|
113
|
-
|
114
|
-
|
126
|
+
def accepts_file_upload?
|
127
|
+
params_type.attributes.any? do |name, type|
|
128
|
+
type.is_a?(Explicit::Type::File)
|
115
129
|
end
|
130
|
+
end
|
116
131
|
|
117
|
-
|
118
|
-
|
119
|
-
|
132
|
+
def headers_type
|
133
|
+
@headers_type ||= Explicit::Type.build(@headers)
|
134
|
+
end
|
120
135
|
|
121
|
-
|
122
|
-
|
123
|
-
|
136
|
+
def params_type
|
137
|
+
@params_type ||= Explicit::Type.build(@params)
|
138
|
+
end
|
139
|
+
|
140
|
+
def responses_type(status:)
|
141
|
+
Explicit::Type.build([:one_of, *@responses[status]])
|
142
|
+
end
|
124
143
|
end
|
@@ -25,6 +25,7 @@ class Explicit::TestHelper::ExampleRecorder
|
|
25
25
|
|
26
26
|
def add(request_gid:, params:, headers:, response:)
|
27
27
|
@examples[request_gid] << Explicit::Request::Example.new(
|
28
|
+
request: nil,
|
28
29
|
params:,
|
29
30
|
headers:,
|
30
31
|
response:
|
@@ -32,7 +33,11 @@ class Explicit::TestHelper::ExampleRecorder
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def save!
|
35
|
-
|
36
|
+
examples_hash = @examples.keys.map do |key|
|
37
|
+
[key, @examples[key]]
|
38
|
+
end.to_h
|
39
|
+
|
40
|
+
total_examples_count = examples_hash.sum { _2.size }
|
36
41
|
file_path = Explicit.configuration.request_examples_file_path
|
37
42
|
|
38
43
|
puts "" if Explicit.configuration.test_runner == :rspec
|
@@ -43,6 +48,6 @@ class Explicit::TestHelper::ExampleRecorder
|
|
43
48
|
puts " [Explicit] ========="
|
44
49
|
puts "" if Explicit.configuration.test_runner == :minitest
|
45
50
|
|
46
|
-
::File.write(file_path,
|
51
|
+
::File.write(file_path, examples_hash.to_json, mode: "w")
|
47
52
|
end
|
48
53
|
end
|
data/lib/explicit/test_helper.rb
CHANGED
@@ -19,8 +19,9 @@ module Explicit::TestHelper
|
|
19
19
|
|
20
20
|
included do
|
21
21
|
if Explicit.configuration.test_runner == :minitest
|
22
|
-
|
23
|
-
|
22
|
+
running_in_parallel = Minitest.parallel_executor.is_a?(
|
23
|
+
ActiveSupport::Testing::ParallelizeExecutor
|
24
|
+
)
|
24
25
|
|
25
26
|
if running_in_parallel
|
26
27
|
uri = Explicit::TestHelper::ExampleRecorder.start_service
|
@@ -48,16 +49,21 @@ module Explicit::TestHelper
|
|
48
49
|
DESC
|
49
50
|
end
|
50
51
|
|
51
|
-
|
52
|
+
method = route.method
|
53
|
+
path = (request.get_base_path || "") + route.path
|
54
|
+
|
55
|
+
process(method, path, params:, headers:)
|
52
56
|
|
53
57
|
response = Explicit::Request::Response.new(
|
54
58
|
status: @response.status,
|
55
59
|
data: @response.parsed_body.deep_symbolize_keys
|
56
60
|
)
|
57
61
|
|
58
|
-
|
62
|
+
ensure_response_matches_documented_type!(request, response)
|
59
63
|
|
60
64
|
if opts[:save_as_example]
|
65
|
+
params = replace_uploaded_files_with_filenames(params)
|
66
|
+
|
61
67
|
ExampleRecorder.instance.add(
|
62
68
|
request_gid: request.gid,
|
63
69
|
params:,
|
@@ -69,12 +75,24 @@ module Explicit::TestHelper
|
|
69
75
|
response
|
70
76
|
end
|
71
77
|
|
72
|
-
def
|
73
|
-
|
78
|
+
def ensure_response_matches_documented_type!(request, response)
|
79
|
+
responses_type = request.responses_type(status: response.status)
|
74
80
|
|
75
|
-
case
|
81
|
+
case responses_type.validate(response.data.with_indifferent_access)
|
76
82
|
in [:ok, _] then :all_good
|
77
83
|
in [:error, err] then raise Explicit::Request::InvalidResponseError.new(response, err)
|
78
84
|
end
|
79
85
|
end
|
86
|
+
|
87
|
+
def replace_uploaded_files_with_filenames(hash)
|
88
|
+
hash.to_h do |key, value|
|
89
|
+
if Explicit::Type::File::FILE_CLASSES.any? { value.is_a?(_1) }
|
90
|
+
[key, "@#{value.original_filename}"]
|
91
|
+
elsif value.is_a?(::Hash)
|
92
|
+
[key, replace_uploaded_files_with_filenames(value)]
|
93
|
+
else
|
94
|
+
[key, value]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
80
98
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Explicit::Type::Agreement < Explicit::Type
|
4
|
+
VALUES = [true, "true", "on", "1", 1].freeze
|
5
|
+
OK = [:ok, true].freeze
|
6
|
+
|
7
|
+
def validate(value)
|
8
|
+
if VALUES.include?(value)
|
9
|
+
OK
|
10
|
+
else
|
11
|
+
[:error, error_i18n("agreement")]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
concerning :Webpage do
|
16
|
+
def summary
|
17
|
+
"boolean"
|
18
|
+
end
|
19
|
+
|
20
|
+
def partial
|
21
|
+
"explicit/documentation/type/agreement"
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_details?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
concerning :Swagger do
|
30
|
+
def swagger_schema
|
31
|
+
{
|
32
|
+
type: "boolean",
|
33
|
+
description: swagger_description([
|
34
|
+
swagger_i18n("agreement")
|
35
|
+
])
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Explicit::Type::Array < Explicit::Type
|
4
|
+
attr_reader :itemtype, :empty
|
5
|
+
|
6
|
+
def initialize(itemtype:, empty: true)
|
7
|
+
@itemtype = Explicit::Type.build(itemtype)
|
8
|
+
@empty = empty
|
9
|
+
end
|
10
|
+
|
11
|
+
def validate(values)
|
12
|
+
return [:error, :array] if !values.is_a?(::Array)
|
13
|
+
|
14
|
+
if values.empty? && !empty
|
15
|
+
return [:error, :empty]
|
16
|
+
end
|
17
|
+
|
18
|
+
validated = []
|
19
|
+
|
20
|
+
values.each.with_index do |value, index|
|
21
|
+
case itemtype.validate(value)
|
22
|
+
in [:ok, value]
|
23
|
+
validated << value
|
24
|
+
in [:error, error]
|
25
|
+
return [:error, error_i18n("array", index:, error:)]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
[:ok, validated]
|
30
|
+
end
|
31
|
+
|
32
|
+
concerning :Webpage do
|
33
|
+
def summary
|
34
|
+
"array of #{itemtype.summary}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def partial
|
38
|
+
"explicit/documentation/type/array"
|
39
|
+
end
|
40
|
+
|
41
|
+
def has_details?
|
42
|
+
true
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
concerning :Swagger do
|
47
|
+
def swagger_schema
|
48
|
+
{
|
49
|
+
type: "array",
|
50
|
+
items: itemtype.swagger_schema,
|
51
|
+
minItems: empty ? 0 : 1,
|
52
|
+
description: swagger_description([])
|
53
|
+
}.compact_blank
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|