explicit 0.2.4 → 0.2.6

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +65 -13
  3. data/app/views/explicit/documentation/_attribute.html.erb +2 -2
  4. data/app/views/explicit/documentation/_page.html.erb +7 -82
  5. data/app/views/explicit/documentation/_request.html.erb +3 -6
  6. data/app/views/explicit/documentation/type/_array.html.erb +1 -1
  7. data/app/views/explicit/documentation/type/_date.html.erb +1 -0
  8. data/app/views/explicit/documentation/type/_date_range.html.erb +6 -0
  9. data/app/views/explicit/documentation/type/_date_time_iso8601_range.html.erb +8 -0
  10. data/app/views/explicit/documentation/type/_hash.html.erb +2 -2
  11. data/app/views/explicit/documentation/type/_string.html.erb +4 -4
  12. data/config/locales/en.yml +34 -7
  13. data/lib/explicit/documentation/builder.rb +3 -0
  14. data/lib/explicit/documentation/output/swagger.rb +4 -0
  15. data/lib/explicit/documentation/output/webpage.rb +5 -0
  16. data/lib/explicit/request/invalid_response_error.rb +1 -1
  17. data/lib/explicit/request.rb +18 -16
  18. data/lib/explicit/test_helper.rb +15 -0
  19. data/lib/explicit/type/agreement.rb +5 -5
  20. data/lib/explicit/type/array.rb +12 -13
  21. data/lib/explicit/type/big_decimal.rb +14 -14
  22. data/lib/explicit/type/boolean.rb +4 -5
  23. data/lib/explicit/type/date.rb +66 -0
  24. data/lib/explicit/type/date_range.rb +95 -0
  25. data/lib/explicit/type/date_time_iso8601.rb +33 -8
  26. data/lib/explicit/type/date_time_iso8601_range.rb +108 -0
  27. data/lib/explicit/type/date_time_unix_epoch.rb +69 -0
  28. data/lib/explicit/type/enum.rb +4 -5
  29. data/lib/explicit/type/file.rb +7 -7
  30. data/lib/explicit/type/hash.rb +14 -14
  31. data/lib/explicit/type/integer.rb +9 -9
  32. data/lib/explicit/type/literal.rb +4 -5
  33. data/lib/explicit/type/modifiers/default.rb +1 -1
  34. data/lib/explicit/type/modifiers/description.rb +1 -1
  35. data/lib/explicit/type/modifiers/nilable.rb +1 -1
  36. data/lib/explicit/type/one_of.rb +59 -1
  37. data/lib/explicit/type/record.rb +5 -6
  38. data/lib/explicit/type/string.rb +19 -19
  39. data/lib/explicit/type.rb +58 -23
  40. data/lib/explicit/version.rb +1 -1
  41. data/lib/explicit.rb +4 -1
  42. metadata +10 -4
  43. data/lib/explicit/type/date_time_posix.rb +0 -44
  44. /data/app/views/explicit/documentation/type/{_date_time_posix.html.erb → _date_time_unix_epoch.html.erb} +0 -0
@@ -26,22 +26,22 @@ class Explicit::Type::Integer < Explicit::Type
26
26
  nil
27
27
  end
28
28
 
29
- return [:error, error_i18n("integer")] if value.nil?
29
+ return error_i18n("integer") if value.nil?
30
30
 
31
31
  if min && value < min
32
- return [:error, error_i18n("min", min:)]
32
+ return error_i18n("min", min:)
33
33
  end
34
34
 
35
35
  if max && value > max
36
- return [:error, error_i18n("max", max:)]
36
+ return error_i18n("max", max:)
37
37
  end
38
38
 
39
39
  if negative == false && value < 0
40
- return [:error, error_i18n("not_negative")]
40
+ return error_i18n("not_negative")
41
41
  end
42
42
 
43
43
  if positive == false && value > 0
44
- return [:error, error_i18n("not_positive")]
44
+ return error_i18n("not_positive")
45
45
  end
46
46
 
47
47
  [:ok, value]
@@ -63,17 +63,17 @@ class Explicit::Type::Integer < Explicit::Type
63
63
 
64
64
  concerning :Swagger do
65
65
  def swagger_schema
66
- {
66
+ merge_base_swagger_schema({
67
67
  type: "integer",
68
68
  minimum: min,
69
69
  maximum: max,
70
- description: swagger_description([
70
+ description_topics: [
71
71
  positive == false ? swagger_i18n("integer_not_positive") : nil,
72
72
  positive == true ? swagger_i18n("integer_only_positive") : nil,
73
73
  negative == false ? swagger_i18n("integer_not_negative") : nil,
74
74
  negative == true ? swagger_i18n("integer_only_negative") : nil
75
- ])
76
- }.compact_blank
75
+ ]
76
+ })
77
77
  end
78
78
  end
79
79
  end
@@ -15,7 +15,7 @@ class Explicit::Type::Literal < Explicit::Type
15
15
  if value == @value
16
16
  [:ok, value]
17
17
  else
18
- [:error, error_i18n("literal", value: @value.inspect)]
18
+ error_i18n("literal", value: @value.inspect)
19
19
  end
20
20
  end
21
21
 
@@ -35,11 +35,10 @@ class Explicit::Type::Literal < Explicit::Type
35
35
 
36
36
  concerning :Swagger do
37
37
  def swagger_schema
38
- {
38
+ merge_base_swagger_schema({
39
39
  type: @value.is_a?(::String) ? "string" : "integer",
40
- enum: [@value],
41
- description: swagger_description([])
42
- }
40
+ enum: [@value]
41
+ })
43
42
  end
44
43
  end
45
44
  end
@@ -5,7 +5,7 @@ module Explicit::Type::Modifiers::Default
5
5
 
6
6
  def apply(default, type)
7
7
  Explicit::Type.build(type).tap do |type|
8
- type.default = default if type.is_a?(Explicit::Type) # TODO: remove check
8
+ type.default = default
9
9
 
10
10
  original_validate = type.method(:validate)
11
11
 
@@ -5,7 +5,7 @@ module Explicit::Type::Modifiers::Description
5
5
 
6
6
  def apply(description, type)
7
7
  Explicit::Type.build(type).tap do |type|
8
- type.description = description if type.is_a?(Explicit::Type) # TODO: remove check
8
+ type.description = description
9
9
  end
10
10
  end
11
11
  end
@@ -5,7 +5,7 @@ module Explicit::Type::Modifiers::Nilable
5
5
 
6
6
  def apply(type)
7
7
  Explicit::Type.build(type).tap do |type|
8
- type.nilable = true if type.is_a?(Explicit::Type) # TODO: remove check
8
+ type.nilable = true
9
9
 
10
10
  original_validate = type.method(:validate)
11
11
 
@@ -5,6 +5,20 @@ class Explicit::Type::OneOf < Explicit::Type
5
5
 
6
6
  def initialize(subtypes:)
7
7
  @subtypes = subtypes.map { Explicit::Type.build(_1) }
8
+ @subtypes_are_records = @subtypes.all? { _1.is_a?(Explicit::Type::Record) }
9
+ @literal_attribute_name_shared_by_all_subtypes = nil
10
+
11
+ if @subtypes_are_records
12
+ literal_types = @subtypes.map do |subtype|
13
+ subtype.attributes.find { |name, type| type.is_a?(Explicit::Type::Literal) }
14
+ end
15
+
16
+ literal_names = literal_types.map { |name, _| name }.uniq
17
+
18
+ if literal_types.all?(&:present?) && literal_names.one?
19
+ @literal_attribute_name_shared_by_all_subtypes = literal_names.first
20
+ end
21
+ end
8
22
  end
9
23
 
10
24
  def validate(value)
@@ -19,7 +33,51 @@ class Explicit::Type::OneOf < Explicit::Type
19
33
  end
20
34
  end
21
35
 
22
- [:error, errors.join(" OR ")]
36
+ if (err = guess_error_for_intended_subtype_via_matching_literal(value:, errors:))
37
+ return [:error, err]
38
+ end
39
+
40
+ if (err = guess_error_for_intended_subtype_via_matching_keys(value:, errors:))
41
+ return [:error, err]
42
+ end
43
+
44
+ error =
45
+ if @subtypes_are_records
46
+ errors.map { ::JSON.pretty_generate(_1) }.join("\n\n#{ ::I18n.t('explicit.errors.one_of_separator')}\n\n")
47
+ else
48
+ errors.join(" #{I18n.t('explicit.errors.one_of_separator')} ")
49
+ end
50
+
51
+ [:error, error]
52
+ end
53
+
54
+ def guess_error_for_intended_subtype_via_matching_literal(value:, errors:)
55
+ return nil if !@literal_attribute_name_shared_by_all_subtypes
56
+ return nil if !value.is_a?(::Hash)
57
+
58
+ errors.find do |error|
59
+ !error.key?(@literal_attribute_name_shared_by_all_subtypes)
60
+ end
61
+ end
62
+
63
+ def guess_error_for_intended_subtype_via_matching_keys(value:, errors:)
64
+ return nil if !@subtypes_are_records
65
+ return nil if !value.is_a?(::Hash)
66
+
67
+ matches = @subtypes.zip(errors).filter_map do |(subtype, error)|
68
+ s1 = Set.new(subtype.attributes.keys)
69
+ s2 = Set.new(value.keys)
70
+
71
+ if s1.intersection(s2).size.positive?
72
+ { subtype:, error: }
73
+ else
74
+ nil
75
+ end
76
+ end
77
+
78
+ return matches.first[:error] if matches.one?
79
+
80
+ nil
23
81
  end
24
82
 
25
83
  concerning :Webpage do
@@ -4,7 +4,7 @@ class Explicit::Type::Record < Explicit::Type
4
4
  attr_reader :attributes
5
5
 
6
6
  def initialize(attributes:)
7
- @attributes = attributes.map do |attribute_name, type|
7
+ @attributes = attributes.to_h do |attribute_name, type|
8
8
  type = Explicit::Type.build(type) if !type.is_a?(Explicit::Type)
9
9
 
10
10
  [attribute_name, type]
@@ -12,7 +12,7 @@ class Explicit::Type::Record < Explicit::Type
12
12
  end
13
13
 
14
14
  def validate(data)
15
- return [:error, error_i18n("hash")] if !data.respond_to?(:[])
15
+ return error_i18n("hash") if !data.respond_to?(:[])
16
16
 
17
17
  validated_data = {}
18
18
  errors = {}
@@ -85,12 +85,11 @@ class Explicit::Type::Record < Explicit::Type
85
85
  type.required? ? name.to_s : nil
86
86
  end
87
87
 
88
- {
88
+ merge_base_swagger_schema({
89
89
  type: "object",
90
90
  properties:,
91
- required:,
92
- description: swagger_description([])
93
- }.compact_blank
91
+ required:
92
+ })
94
93
  end
95
94
  end
96
95
  end
@@ -1,37 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Explicit::Type::String < Explicit::Type
4
- attr_reader :empty, :strip, :format, :minlength, :maxlength, :downcase
4
+ attr_reader :empty, :strip, :format, :min_length, :max_length, :downcase
5
5
 
6
- def initialize(empty: nil, strip: nil, format: nil, minlength: nil, maxlength: nil, downcase: false)
6
+ def initialize(empty: nil, strip: nil, format: nil, min_length: nil, max_length: nil, downcase: false)
7
7
  @empty = empty
8
8
  @strip = strip
9
9
  @format = format
10
- @minlength = minlength
11
- @maxlength = maxlength
10
+ @min_length = min_length
11
+ @max_length = max_length
12
12
  @downcase = downcase
13
13
  end
14
14
 
15
15
  def validate(value)
16
- return [:error, error_i18n("string")] if !value.is_a?(::String)
16
+ return error_i18n("string") if !value.is_a?(::String)
17
17
 
18
18
  value = value.strip if strip
19
19
  value = value.downcase if downcase
20
20
 
21
21
  if empty == false && value.empty?
22
- return [:error, error_i18n("empty")]
22
+ return error_i18n("empty")
23
23
  end
24
24
 
25
- if minlength && value.length < minlength
26
- return [:error, error_i18n("minlength", minlength:)]
25
+ if min_length && value.length < min_length
26
+ return error_i18n("min_length", min_length:)
27
27
  end
28
28
 
29
- if maxlength && value.length > maxlength
30
- return [:error, error_i18n("maxlength", maxlength:)]
29
+ if max_length && value.length > max_length
30
+ return error_i18n("max_length", max_length:)
31
31
  end
32
32
 
33
33
  if format && !format.match?(value)
34
- return [:error, error_i18n("format", regex: format.inspect)]
34
+ return error_i18n("format", regex: format.inspect)
35
35
  end
36
36
 
37
37
  [:ok, value]
@@ -47,22 +47,22 @@ class Explicit::Type::String < Explicit::Type
47
47
  end
48
48
 
49
49
  def has_details?
50
- !empty.nil? || format.present? || minlength.present? || maxlength.present?
50
+ !empty.nil? || format.present? || min_length.present? || max_length.present?
51
51
  end
52
52
  end
53
53
 
54
54
  concerning :Swagger do
55
55
  def swagger_schema
56
- {
56
+ merge_base_swagger_schema({
57
57
  type: "string",
58
- pattern: format&.inspect,
59
- minLength: minlength || (empty == false ? 1 : nil),
60
- maxLength: maxlength,
61
- description: swagger_description([
58
+ pattern: format&.inspect&.then { _1[1..-2] },
59
+ minLength: min_length || (empty == false ? 1 : nil),
60
+ maxLength: max_length,
61
+ description_topics: [
62
62
  empty == false ? swagger_i18n("string_not_empty") : nil,
63
63
  downcase == true ? swagger_i18n("string_downcase") : nil
64
- ])
65
- }.compact_blank
64
+ ]
65
+ })
66
66
  end
67
67
  end
68
68
  end
data/lib/explicit/type.rb CHANGED
@@ -8,28 +8,48 @@ class Explicit::Type
8
8
  in :agreement
9
9
  Explicit::Type::Agreement.new
10
10
 
11
- in [:array, itemtype]
12
- Explicit::Type::Array.new(itemtype:)
13
- in [:array, itemtype, options]
14
- Explicit::Type::Array.new(itemtype:, **options)
11
+ in [:array, item_type]
12
+ Explicit::Type::Array.new(item_type:)
13
+ in [:array, item_type, options]
14
+ Explicit::Type::Array.new(item_type:, **options)
15
15
 
16
- in :bigdecimal
16
+ in :big_decimal
17
17
  Explicit::Type::BigDecimal.new
18
- in [:bigdecimal, options]
18
+ in [:big_decimal, options]
19
19
  Explicit::Type::BigDecimal.new(**options)
20
20
 
21
21
  in :boolean
22
22
  Explicit::Type::Boolean.new
23
23
 
24
+ in :date_range
25
+ Explicit::Type::DateRange.new
26
+ in [:date_range, options]
27
+ Explicit::Type::DateRange.new(**options)
28
+
29
+ in :date_time_iso8601_range
30
+ Explicit::Type::DateTimeISO8601Range.new()
31
+ in [:date_time_iso8601_range, options]
32
+ Explicit::Type::DateTimeISO8601Range.new(**options)
33
+
24
34
  in :date_time_iso8601
25
35
  Explicit::Type::DateTimeISO8601.new
26
- in :date_time_posix
27
- Explicit::Type::DateTimePosix.new
36
+ in [:date_time_iso8601, options]
37
+ Explicit::Type::DateTimeISO8601.new(**options)
38
+
39
+ in :date_time_unix_epoch
40
+ Explicit::Type::DateTimeUnixEpoch.new
41
+ in [:date_time_unix_epoch, options]
42
+ Explicit::Type::DateTimeUnixEpoch.new(**options)
43
+
44
+ in :date
45
+ Explicit::Type::Date.new
46
+ in [:date, options]
47
+ Explicit::Type::Date.new(**options)
28
48
 
29
- in [:hash, keytype, valuetype]
30
- Explicit::Type::Hash.new(keytype:, valuetype:)
31
- in [:hash, keytype, valuetype, options]
32
- Explicit::Type::Hash.new(keytype:, valuetype:, **options)
49
+ in [:hash, key_type, value_type]
50
+ Explicit::Type::Hash.new(key_type:, value_type:)
51
+ in [:hash, key_type, value_type, options]
52
+ Explicit::Type::Hash.new(key_type:, value_type:, **options)
33
53
 
34
54
  in [:enum, allowed_values]
35
55
  Explicit::Type::Enum.new(allowed_values)
@@ -89,7 +109,7 @@ class Explicit::Type
89
109
  def error_i18n(name, context = {})
90
110
  key = "explicit.errors.#{name}"
91
111
 
92
- ::I18n.t(key, **context)
112
+ [:error, ::I18n.t(key, **context)]
93
113
  end
94
114
 
95
115
  def swagger_i18n(name, context = {})
@@ -98,15 +118,30 @@ class Explicit::Type
98
118
  ::I18n.t(key, **context)
99
119
  end
100
120
 
101
- def swagger_description(extras)
102
- extras = extras.compact_blank
103
-
104
- if description.present? && extras.empty?
105
- description
106
- elsif description.present? && extras.any?
107
- description + "\n\n" + extras.compact_blank.join("\n")
108
- else
109
- extras.compact_blank.join("\n")
110
- end
121
+ def merge_base_swagger_schema(attributes)
122
+ topics = attributes.delete(:description_topics)&.compact_blank || []
123
+
124
+ formatted_description =
125
+ if description.present? && topics.empty?
126
+ description
127
+ elsif description.present? && topics.any?
128
+ description + "\n\n" + topics.join("\n")
129
+ else
130
+ topics.join("\n")
131
+ end
132
+
133
+ default_value =
134
+ if default&.respond_to?(:call)
135
+ nil
136
+ else
137
+ default
138
+ end
139
+
140
+ base_attributes = {
141
+ default: default_value,
142
+ description: formatted_description
143
+ }
144
+
145
+ base_attributes.merge(attributes).compact_blank
111
146
  end
112
147
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Explicit
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.6"
5
5
  end
data/lib/explicit.rb CHANGED
@@ -28,8 +28,11 @@ require "explicit/type/agreement"
28
28
  require "explicit/type/array"
29
29
  require "explicit/type/big_decimal"
30
30
  require "explicit/type/boolean"
31
+ require "explicit/type/date_range"
32
+ require "explicit/type/date_time_iso8601_range"
31
33
  require "explicit/type/date_time_iso8601"
32
- require "explicit/type/date_time_posix"
34
+ require "explicit/type/date_time_unix_epoch"
35
+ require "explicit/type/date"
33
36
  require "explicit/type/enum"
34
37
  require "explicit/type/file"
35
38
  require "explicit/type/hash"
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.4
4
+ version: 0.2.6
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-06 00:00:00.000000000 Z
11
+ date: 2025-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -59,8 +59,11 @@ files:
59
59
  - app/views/explicit/documentation/type/_array.html.erb
60
60
  - app/views/explicit/documentation/type/_big_decimal.html.erb
61
61
  - app/views/explicit/documentation/type/_boolean.html.erb
62
+ - app/views/explicit/documentation/type/_date.html.erb
63
+ - app/views/explicit/documentation/type/_date_range.html.erb
62
64
  - app/views/explicit/documentation/type/_date_time_iso8601.html.erb
63
- - app/views/explicit/documentation/type/_date_time_posix.html.erb
65
+ - app/views/explicit/documentation/type/_date_time_iso8601_range.html.erb
66
+ - app/views/explicit/documentation/type/_date_time_unix_epoch.html.erb
64
67
  - app/views/explicit/documentation/type/_enum.html.erb
65
68
  - app/views/explicit/documentation/type/_file.html.erb
66
69
  - app/views/explicit/documentation/type/_hash.html.erb
@@ -94,8 +97,11 @@ files:
94
97
  - lib/explicit/type/array.rb
95
98
  - lib/explicit/type/big_decimal.rb
96
99
  - lib/explicit/type/boolean.rb
100
+ - lib/explicit/type/date.rb
101
+ - lib/explicit/type/date_range.rb
97
102
  - lib/explicit/type/date_time_iso8601.rb
98
- - lib/explicit/type/date_time_posix.rb
103
+ - lib/explicit/type/date_time_iso8601_range.rb
104
+ - lib/explicit/type/date_time_unix_epoch.rb
99
105
  - lib/explicit/type/enum.rb
100
106
  - lib/explicit/type/file.rb
101
107
  - lib/explicit/type/hash.rb
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "time"
4
-
5
- class Explicit::Type::DateTimePosix < Explicit::Type
6
- def validate(value)
7
- if !value.is_a?(::Integer) && !value.is_a?(::String)
8
- return [:error, error_i18n("date_time_posix")]
9
- end
10
-
11
- datetimeval = DateTime.strptime(value.to_s, "%s")
12
-
13
- [:ok, datetimeval]
14
- rescue Date::Error
15
- return [:error, error_i18n("date_time_posix")]
16
- end
17
-
18
- concerning :Webpage do
19
- def summary
20
- "integer"
21
- end
22
-
23
- def partial
24
- "explicit/documentation/type/date_time_posix"
25
- end
26
-
27
- def has_details?
28
- true
29
- end
30
- end
31
-
32
- concerning :Swagger do
33
- def swagger_schema
34
- {
35
- type: "integer",
36
- minimum: 1,
37
- format: "POSIX time",
38
- description: swagger_description([
39
- swagger_i18n("date_time_posix")
40
- ])
41
- }
42
- end
43
- end
44
- end