explicit 0.2.6 → 0.2.8

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
  SHA256:
3
- metadata.gz: 46f92f599374b50a6ff4dcf044fadac51b9bf12d8f7c18b3f9bd1b39124be855
4
- data.tar.gz: a9ec574ef2a8a6f3b0892994037fb7c19522af6afcaf4583933b8774b3ea2321
3
+ metadata.gz: 6d45270c6ac48802f9d370fb8afe70ec879f761499f589b5a875671161fbebaf
4
+ data.tar.gz: 2f2f57d5147f9f2a676c98727c63de82f7ed61477ad4d3b7bd0399ba4ea27c67
5
5
  SHA512:
6
- metadata.gz: dafc5889a7c3a33a772f9c3eac434e24ce7424e1cdd0e9c8f24f859fa6a3f5143fd631546f40d7705e69a14b68b75cc7314c1420bdadda791ca57eb6f132dc5d
7
- data.tar.gz: 9a03258778b8b0b7fd0b928c7414c8599f2d6e324dcd45b8284c8f6dc89cf4dce027942201f9c03237c24c96df3e7a124e798e8a30de38f9520b82cd11b0a87d
6
+ metadata.gz: e91a474e286da7b34fa36de63fa9885e1c89b765a4cf3aa2a0d9de8b936841247014f2ae6b326bc093cecb94fb05ae2cfc3824fe61f1be159554d98ac7df7d36
7
+ data.tar.gz: 40ab134356ea61d933c81379aa84977c70cb3d00cebfc3b62d1e3e26e6b698ba8f19993a22900ed6be07ec8e356272812de5a84bc87184014a29a226d055367b
data/README.md CHANGED
@@ -28,6 +28,7 @@ documented types at runtime.
28
28
  - [Description](#description)
29
29
  - [Enum](#enum)
30
30
  - [File](#file)
31
+ - [Float](#float)
31
32
  - [Hash](#hash)
32
33
  - [Integer](#integer)
33
34
  - [Literal](#literal)
@@ -141,6 +142,9 @@ request instead of `Explicit::Request`. For example:
141
142
 
142
143
  ```ruby
143
144
  AuthenticatedRequest = Explicit::Request.new do
145
+ base_url "https://my-app.com"
146
+ base_path "/api/v1"
147
+
144
148
  header "Authorization", [:string, format: /Bearer [a-zA-Z0-9]{20}/]
145
149
 
146
150
  response 403, { error: "unauthorized" }
@@ -255,6 +259,8 @@ documentation for your API. The following methods are available:
255
259
 
256
260
  - `page_title(text)` - Sets the web page title.
257
261
  - `company_logo_url(url)` - Shows the company logo in the navigation menu.
262
+ The url can also be a lambda that returns the url, useful for referencing
263
+ assets at runtime.
258
264
  - `favicon_url(url)` - Adds a favicon to the web page.
259
265
  - `version(semver)` - Sets the version of the API. Default: "1.0"
260
266
  - `section(name, &block)` - Adds a section to the navigation menu.
@@ -548,6 +554,18 @@ match value_type. If you are expecting a hash with a specific set of keys use a
548
554
 
549
555
  Value must be an uploaded file using "multipart/form-data" encoding.
550
556
 
557
+ ### Float
558
+
559
+ ```ruby
560
+ :float
561
+ [:float, negative: false]
562
+ [:float, positive: false]
563
+ [:float, min: 0] # inclusive
564
+ [:float, max: 10] # inclusive
565
+ ```
566
+
567
+ Float encoded string alues such as "0.5" or "500.01" are automatically converted to `Float`.
568
+
551
569
  ### Integer
552
570
 
553
571
  ```ruby
@@ -559,7 +577,7 @@ Value must be an uploaded file using "multipart/form-data" encoding.
559
577
  ```
560
578
 
561
579
  Integer encoded string values such as "10" or "-2" are automatically converted
562
- to integer.
580
+ to `Integer`.
563
581
 
564
582
  ### Literal
565
583
 
@@ -0,0 +1,25 @@
1
+ <%= type_constraints do %>
2
+ <% if type.min.present? %>
3
+ <%= type_constraint "min", type.min %>
4
+ <% end %>
5
+
6
+ <% if type.max.present? %>
7
+ <%= type_constraint "max", type.max %>
8
+ <% end %>
9
+
10
+ <% if type.negative == false %>
11
+ <%= type_constraint "not", "negative" %>
12
+ <% end %>
13
+
14
+ <% if type.negative == true %>
15
+ <%= type_constraint "only", "negative" %>
16
+ <% end %>
17
+
18
+ <% if type.positive == false %>
19
+ <%= type_constraint "not", "positive" %>
20
+ <% end %>
21
+
22
+ <% if type.positive == true %>
23
+ <%= type_constraint "only", "positive" %>
24
+ <% end %>
25
+ <% end %>
@@ -34,11 +34,14 @@ en:
34
34
  file: "must be a file"
35
35
  file_max_size: "must be smaller than %{max_size}"
36
36
  file_content_type: "file content type must be one of: %{allowed_content_types}"
37
+ float: "must be a number"
37
38
  integer: "must be an integer"
38
39
  min: "must be at least %{min}"
39
40
  max: "must be at most %{max}"
40
41
  not_negative: "must not be negative"
42
+ only_negative: "must be negative"
41
43
  not_positive: "must not be positive"
44
+ only_positive: "must be positive"
42
45
  literal: "must be %{value}"
43
46
  one_of_separator: "OR"
44
47
  string: "must be a string"
@@ -61,9 +64,9 @@ en:
61
64
  file_max_size: "* Max size: %{max_size}"
62
65
  file_content_types: "* Content types: %{content_types}"
63
66
  hash_not_empty: "* Must have at least one value"
64
- integer_not_positive: "* Must not be positive"
65
- integer_only_positive: "* Must be positive"
66
- integer_not_negative: "* Must not be negative"
67
- integer_only_negative: "* Must be negative"
67
+ number_not_positive: "* Must not be positive"
68
+ number_only_positive: "* Must be positive"
69
+ number_not_negative: "* Must not be negative"
70
+ number_only_negative: "* Must be negative"
68
71
  string_not_empty: "* Must not be empty"
69
72
  string_downcase: "* Case insensitive"
@@ -59,6 +59,7 @@ module Explicit::Documentation
59
59
 
60
60
  def merge_request_examples_from_file!
61
61
  return if !Explicit.configuration.request_examples_file_path
62
+ return if !::File.exist?(Explicit.configuration.request_examples_file_path)
62
63
 
63
64
  encoded = ::File.read(Explicit.configuration.request_examples_file_path)
64
65
 
@@ -31,7 +31,7 @@ module Explicit::Documentation::Output
31
31
  def call(request)
32
32
  @swagger_document ||= swagger_document
33
33
 
34
- [200, {"Content-Type" => "application/json"}, [@swagger_document.to_json]]
34
+ [ 200, { "Content-Type" => "application/json" }, [ @swagger_document.to_json ] ]
35
35
  end
36
36
 
37
37
  def inspect
@@ -43,7 +43,7 @@ module Explicit::Documentation::Output
43
43
  base_urls = builder.requests.map(&:get_base_url).uniq
44
44
  base_paths = builder.requests.map(&:get_base_path).uniq
45
45
 
46
- if !base_urls.one?
46
+ if !base_urls.compact_blank.empty? && !base_urls.one?
47
47
  raise InconsistentBaseURLError.new <<~TXT
48
48
  There are requests with different base URLs in the same documentation,
49
49
  which is not supported by Swagger.
@@ -56,7 +56,7 @@ module Explicit::Documentation::Output
56
56
  TXT
57
57
  end
58
58
 
59
- if !base_paths.one?
59
+ if !base_paths.compact_blank.empty? && !base_paths.one?
60
60
  raise InconsistentBasePathError.new <<~TXT
61
61
  There are requests with different base paths in the same documentation,
62
62
  which is not supported by Swagger.
@@ -69,7 +69,7 @@ module Explicit::Documentation::Output
69
69
  TXT
70
70
  end
71
71
 
72
- base_urls.first + base_paths.first
72
+ (base_urls.first || "") + (base_paths.first || "")
73
73
  end
74
74
 
75
75
  def build_tags_from_sections
@@ -87,7 +87,7 @@ module Explicit::Documentation::Output
87
87
  route = request.routes.first
88
88
 
89
89
  paths[route.path_with_curly_syntax][route.method.to_s] = {
90
- tags: [section.name],
90
+ tags: [ section.name ],
91
91
  summary: request.get_title,
92
92
  description: request.get_description,
93
93
  parameters: build_parameters(request),
@@ -132,7 +132,7 @@ module Explicit::Documentation::Output
132
132
 
133
133
  return nil if body_params_type.attributes.empty?
134
134
 
135
- examples =
135
+ examples =
136
136
  request.examples
137
137
  .flat_map { |_, examples| examples }
138
138
  .filter_map do |example|
@@ -141,7 +141,7 @@ module Explicit::Documentation::Output
141
141
  in [:error, _] then nil
142
142
  end
143
143
  end
144
- .map.with_index { |example, index| [index, { value: example }] }
144
+ .map.with_index { |example, index| [ index, { value: example } ] }
145
145
  .to_h
146
146
 
147
147
  {
@@ -160,7 +160,7 @@ module Explicit::Documentation::Output
160
160
 
161
161
  request.responses.each do |status, _|
162
162
  examples = request.examples[status].map.with_index do |example, index|
163
- [index.to_s, { value: example.response.data }]
163
+ [ index.to_s, { value: example.response.data } ]
164
164
  end.to_h
165
165
 
166
166
  responses[status] = {
@@ -19,15 +19,17 @@ module Explicit::Documentation::Output
19
19
  end
20
20
 
21
21
  private
22
+ Eval = ->(value) { value.respond_to?(:call) ? value.call : value }
23
+
22
24
  def render_documentation_page
23
25
  Explicit::ApplicationController.render(
24
26
  partial: "explicit/documentation/page",
25
27
  locals: {
26
28
  url_helpers: @builder.rails_engine.routes.url_helpers,
27
- page_title: builder.get_page_title,
28
- company_logo_url: builder.get_company_logo_url,
29
- favicon_url: builder.get_favicon_url,
30
- version: builder.get_version,
29
+ page_title: Eval[builder.get_page_title],
30
+ company_logo_url: Eval[builder.get_company_logo_url],
31
+ favicon_url: Eval[builder.get_favicon_url],
32
+ version: Eval[builder.get_version],
31
33
  sections: builder.sections,
32
34
  }
33
35
  )
@@ -50,9 +50,13 @@ module Explicit::TestHelper
50
50
  end
51
51
 
52
52
  method = route.method
53
+ path = (request.get_base_path || "") + route.replace_path_params(params)
53
54
 
54
- path = (request.get_base_path || "") + route.path
55
- path = path.gsub(/:(\w+)/) { params.delete($1.to_sym).to_s }
55
+ if missing_path_param?(route, params)
56
+ raise <<~DESC
57
+ The request #{route} needs the following path parameters: #{route.params.join(", ")}
58
+ DESC
59
+ end
56
60
 
57
61
  process(method, path, params:, headers:)
58
62
 
@@ -90,6 +94,10 @@ module Explicit::TestHelper
90
94
  response
91
95
  end
92
96
 
97
+ def missing_path_param?(route, params)
98
+ route.params.any? { params.fetch(_1, nil).nil? }
99
+ end
100
+
93
101
  def ensure_response_matches_documented_type!(request, response)
94
102
  responses_type = request.responses_type(status: response.status)
95
103
 
@@ -11,7 +11,7 @@ class Explicit::Type::Date < Explicit::Type
11
11
  end
12
12
 
13
13
  def validate(value)
14
- return [:ok, value] if value.is_a?(::Date)
14
+ return [ :ok, value ] if value.is_a?(::Date)
15
15
  return error_i18n("string") if !value.is_a?(::String)
16
16
 
17
17
  date = ::Date.parse(value, false)
@@ -32,7 +32,7 @@ class Explicit::Type::Date < Explicit::Type
32
32
  end
33
33
  end
34
34
 
35
- [:ok, date]
35
+ [ :ok, date ]
36
36
  rescue ::Date::Error
37
37
  error_i18n("date_format")
38
38
  end
@@ -53,14 +53,14 @@ class Explicit::Type::Date < Explicit::Type
53
53
 
54
54
  concerning :Swagger do
55
55
  def swagger_schema
56
- {
56
+ merge_base_swagger_schema({
57
57
  type: "string",
58
58
  pattern: /\d{4}-\d{2}-\d{2}/.inspect[1..-2],
59
59
  format: "date",
60
60
  description_topics: [
61
61
  swagger_i18n("date_format")
62
62
  ]
63
- }
63
+ })
64
64
  end
65
65
  end
66
66
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Explicit::Type::Float < Explicit::Type
4
+ attr_reader :min, :max, :negative, :positive
5
+
6
+ def initialize(min: nil, max: nil, negative: nil, positive: nil)
7
+ @min = min
8
+ @max = max
9
+ @negative = negative
10
+ @positive = positive
11
+ end
12
+
13
+ ParseFromString = ->(value) do
14
+ Float(value)
15
+ rescue ::ArgumentError
16
+ nil
17
+ end
18
+
19
+ def validate(value)
20
+ value =
21
+ if value.is_a?(::Integer) || value.is_a?(::Float) || value.is_a?(::BigDecimal)
22
+ value
23
+ elsif value.is_a?(::String)
24
+ ParseFromString[value]
25
+ else
26
+ nil
27
+ end
28
+
29
+ return error_i18n("float") if value.nil?
30
+
31
+ if min && value < min
32
+ return error_i18n("min", min:)
33
+ end
34
+
35
+ if max && value > max
36
+ return error_i18n("max", max:)
37
+ end
38
+
39
+ if negative == false && value < 0
40
+ return error_i18n("not_negative")
41
+ end
42
+
43
+ if negative == true && value >= 0
44
+ return error_i18n("only_negative")
45
+ end
46
+
47
+ if positive == false && value > 0
48
+ return error_i18n("not_positive")
49
+ end
50
+
51
+ if positive == true && value <= 0
52
+ return error_i18n("only_positive")
53
+ end
54
+
55
+ [ :ok, value ]
56
+ end
57
+
58
+ concerning :Webpage do
59
+ def summary
60
+ "float"
61
+ end
62
+
63
+ def partial
64
+ "explicit/documentation/type/float"
65
+ end
66
+
67
+ def has_details?
68
+ min.present? || max.present? || !negative.nil? || !positive.nil?
69
+ end
70
+ end
71
+
72
+ concerning :Swagger do
73
+ def swagger_schema
74
+ merge_base_swagger_schema({
75
+ type: "number",
76
+ minimum: min,
77
+ maximum: max,
78
+ description_topics: [
79
+ positive == false ? swagger_i18n("number_not_positive") : nil,
80
+ positive == true ? swagger_i18n("number_only_positive") : nil,
81
+ negative == false ? swagger_i18n("number_not_negative") : nil,
82
+ negative == true ? swagger_i18n("number_only_negative") : nil
83
+ ]
84
+ })
85
+ end
86
+ end
87
+ end
@@ -40,11 +40,19 @@ class Explicit::Type::Integer < Explicit::Type
40
40
  return error_i18n("not_negative")
41
41
  end
42
42
 
43
+ if negative == true && value >= 0
44
+ return error_i18n("only_negative")
45
+ end
46
+
43
47
  if positive == false && value > 0
44
48
  return error_i18n("not_positive")
45
49
  end
46
50
 
47
- [:ok, value]
51
+ if positive == true && value <= 0
52
+ return error_i18n("only_positive")
53
+ end
54
+
55
+ [ :ok, value ]
48
56
  end
49
57
 
50
58
  concerning :Webpage do
@@ -68,10 +76,10 @@ class Explicit::Type::Integer < Explicit::Type
68
76
  minimum: min,
69
77
  maximum: max,
70
78
  description_topics: [
71
- positive == false ? swagger_i18n("integer_not_positive") : nil,
72
- positive == true ? swagger_i18n("integer_only_positive") : nil,
73
- negative == false ? swagger_i18n("integer_not_negative") : nil,
74
- negative == true ? swagger_i18n("integer_only_negative") : nil
79
+ positive == false ? swagger_i18n("number_not_positive") : nil,
80
+ positive == true ? swagger_i18n("number_only_positive") : nil,
81
+ negative == false ? swagger_i18n("number_not_negative") : nil,
82
+ negative == true ? swagger_i18n("number_only_negative") : nil
75
83
  ]
76
84
  })
77
85
  end
@@ -27,28 +27,35 @@ class Explicit::Type::OneOf < Explicit::Type
27
27
  @subtypes.each do |subtype|
28
28
  case subtype.validate(value)
29
29
  in [:ok, validated_value]
30
- return [:ok, validated_value]
30
+ return [ :ok, validated_value ]
31
31
  in [:error, err]
32
32
  errors << err
33
33
  end
34
34
  end
35
35
 
36
36
  if (err = guess_error_for_intended_subtype_via_matching_literal(value:, errors:))
37
- return [:error, err]
37
+ return [ :error, err ]
38
38
  end
39
39
 
40
40
  if (err = guess_error_for_intended_subtype_via_matching_keys(value:, errors:))
41
- return [:error, err]
41
+ return [ :error, err ]
42
42
  end
43
43
 
44
+ separator =
45
+ if ::I18n.exists?("explicit.errors.one_of_separator")
46
+ ::I18n.t("explicit.errors.one_of_separator")
47
+ else
48
+ ::I18n.t("explicit.errors.one_of_separator", locale: :en)
49
+ end
50
+
44
51
  error =
45
52
  if @subtypes_are_records
46
- errors.map { ::JSON.pretty_generate(_1) }.join("\n\n#{ ::I18n.t('explicit.errors.one_of_separator')}\n\n")
53
+ errors.map { ::JSON.pretty_generate(_1) }.join("\n\n#{separator}\n\n")
47
54
  else
48
- errors.join(" #{I18n.t('explicit.errors.one_of_separator')} ")
55
+ errors.join(" #{separator} ")
49
56
  end
50
57
 
51
- [:error, error]
58
+ [ :error, error ]
52
59
  end
53
60
 
54
61
  def guess_error_for_intended_subtype_via_matching_literal(value:, errors:)
@@ -101,4 +108,4 @@ class Explicit::Type::OneOf < Explicit::Type
101
108
  { oneOf: subtypes.map(&:swagger_schema) }
102
109
  end
103
110
  end
104
- end
111
+ end
data/lib/explicit/type.rb CHANGED
@@ -59,6 +59,11 @@ class Explicit::Type
59
59
  in [:file, options]
60
60
  Explicit::Type::File.new(**options)
61
61
 
62
+ in :float
63
+ Explicit::Type::Float.new
64
+ in [:float, options]
65
+ Explicit::Type::Float.new(**options)
66
+
62
67
  in :integer
63
68
  Explicit::Type::Integer.new
64
69
  in [:integer, options]
@@ -109,13 +114,24 @@ class Explicit::Type
109
114
  def error_i18n(name, context = {})
110
115
  key = "explicit.errors.#{name}"
111
116
 
112
- [:error, ::I18n.t(key, **context)]
117
+ translation =
118
+ if ::I18n.exists?(key)
119
+ ::I18n.t(key, **context)
120
+ else
121
+ ::I18n.t(key, **context.merge(locale: :en))
122
+ end
123
+
124
+ [ :error, translation ]
113
125
  end
114
126
 
115
127
  def swagger_i18n(name, context = {})
116
128
  key = "explicit.swagger.#{name}"
117
129
 
118
- ::I18n.t(key, **context)
130
+ if ::I18n.exists?(key)
131
+ ::I18n.t(key, **context)
132
+ else
133
+ ::I18n.t(key, **context.merge(locale: :en))
134
+ end
119
135
  end
120
136
 
121
137
  def merge_base_swagger_schema(attributes)
@@ -144,4 +160,4 @@ class Explicit::Type
144
160
 
145
161
  base_attributes.merge(attributes).compact_blank
146
162
  end
147
- end
163
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Explicit
4
- VERSION = "0.2.6"
4
+ VERSION = "0.2.8"
5
5
  end
data/lib/explicit.rb CHANGED
@@ -35,6 +35,7 @@ require "explicit/type/date_time_unix_epoch"
35
35
  require "explicit/type/date"
36
36
  require "explicit/type/enum"
37
37
  require "explicit/type/file"
38
+ require "explicit/type/float"
38
39
  require "explicit/type/hash"
39
40
  require "explicit/type/integer"
40
41
  require "explicit/type/literal"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: explicit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luiz Vasconcellos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-31 00:00:00.000000000 Z
11
+ date: 2025-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -66,6 +66,7 @@ files:
66
66
  - app/views/explicit/documentation/type/_date_time_unix_epoch.html.erb
67
67
  - app/views/explicit/documentation/type/_enum.html.erb
68
68
  - app/views/explicit/documentation/type/_file.html.erb
69
+ - app/views/explicit/documentation/type/_float.html.erb
69
70
  - app/views/explicit/documentation/type/_hash.html.erb
70
71
  - app/views/explicit/documentation/type/_integer.html.erb
71
72
  - app/views/explicit/documentation/type/_one_of.html.erb
@@ -104,6 +105,7 @@ files:
104
105
  - lib/explicit/type/date_time_unix_epoch.rb
105
106
  - lib/explicit/type/enum.rb
106
107
  - lib/explicit/type/file.rb
108
+ - lib/explicit/type/float.rb
107
109
  - lib/explicit/type/hash.rb
108
110
  - lib/explicit/type/integer.rb
109
111
  - lib/explicit/type/literal.rb