explicit 0.2.14 → 0.2.16

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +144 -9
  3. data/app/views/explicit/documentation/_page.html.erb +1 -1
  4. data/app/views/explicit/documentation/type/_big_decimal.html.erb +20 -0
  5. data/config/locales/en.yml +1 -0
  6. data/lib/explicit/documentation/builder.rb +1 -1
  7. data/lib/explicit/documentation/output/swagger.rb +21 -2
  8. data/lib/explicit/documentation/output/webpage.rb +4 -3
  9. data/lib/explicit/documentation.rb +1 -1
  10. data/lib/explicit/mcp_server/builder.rb +50 -0
  11. data/lib/explicit/mcp_server/request.rb +42 -0
  12. data/lib/explicit/mcp_server/response.rb +29 -0
  13. data/lib/explicit/mcp_server/router.rb +84 -0
  14. data/lib/explicit/mcp_server/tool.rb +20 -0
  15. data/lib/explicit/mcp_server.rb +39 -0
  16. data/lib/explicit/request.rb +35 -1
  17. data/lib/explicit/type/agreement.rb +7 -9
  18. data/lib/explicit/type/any.rb +4 -4
  19. data/lib/explicit/type/array.rb +6 -8
  20. data/lib/explicit/type/big_decimal.rb +36 -15
  21. data/lib/explicit/type/boolean.rb +4 -6
  22. data/lib/explicit/type/date.rb +9 -11
  23. data/lib/explicit/type/date_range.rb +11 -13
  24. data/lib/explicit/type/date_time_iso8601.rb +9 -11
  25. data/lib/explicit/type/date_time_iso8601_range.rb +10 -12
  26. data/lib/explicit/type/date_time_unix_epoch.rb +9 -11
  27. data/lib/explicit/type/enum.rb +5 -7
  28. data/lib/explicit/type/file.rb +9 -11
  29. data/lib/explicit/type/float.rb +12 -14
  30. data/lib/explicit/type/hash.rb +8 -10
  31. data/lib/explicit/type/integer.rb +12 -14
  32. data/lib/explicit/type/literal.rb +5 -7
  33. data/lib/explicit/type/one_of.rb +3 -5
  34. data/lib/explicit/type/record.rb +12 -25
  35. data/lib/explicit/type/string.rb +11 -13
  36. data/lib/explicit/type.rb +10 -2
  37. data/lib/explicit/version.rb +1 -1
  38. data/lib/explicit.rb +7 -0
  39. metadata +9 -6
@@ -26,14 +26,12 @@ class Explicit::Type::Agreement < Explicit::Type
26
26
  end
27
27
  end
28
28
 
29
- concerning :Swagger do
30
- def swagger_schema
31
- merge_base_swagger_schema({
32
- type: "boolean",
33
- description_topics: [
34
- swagger_i18n("agreement")
35
- ]
36
- })
37
- end
29
+ def json_schema(flavour)
30
+ {
31
+ type: "boolean",
32
+ description_topics: [
33
+ swagger_i18n("agreement")
34
+ ]
35
+ }
38
36
  end
39
37
  end
@@ -19,9 +19,9 @@ class Explicit::Type::Any < Explicit::Type
19
19
  end
20
20
  end
21
21
 
22
- concerning :Swagger do
23
- def swagger_schema
24
- merge_base_swagger_schema({})
25
- end
22
+ def json_schema(flavour)
23
+ return {} if flavour == :swagger
24
+
25
+ { type: "string" }
26
26
  end
27
27
  end
@@ -43,13 +43,11 @@ class Explicit::Type::Array < Explicit::Type
43
43
  end
44
44
  end
45
45
 
46
- concerning :Swagger do
47
- def swagger_schema
48
- merge_base_swagger_schema({
49
- type: "array",
50
- items: item_type.swagger_schema,
51
- minItems: empty ? 0 : 1
52
- })
53
- end
46
+ def json_schema(flavour)
47
+ {
48
+ type: "array",
49
+ items: item_type.mcp_schema,
50
+ minItems: empty ? 0 : 1
51
+ }
54
52
  end
55
53
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class Explicit::Type::BigDecimal < Explicit::Type
4
- attr_reader :min, :max
4
+ attr_reader :min, :max, :negative, :positive
5
5
 
6
- def initialize(min: nil, max: nil)
6
+ def initialize(min: nil, max: nil, negative: nil, positive: nil)
7
7
  @min = min
8
8
  @max = max
9
+ @negative = negative
10
+ @positive = positive
9
11
  end
10
12
 
11
13
  def validate(value)
@@ -23,6 +25,22 @@ class Explicit::Type::BigDecimal < Explicit::Type
23
25
  return error_i18n("max", max:)
24
26
  end
25
27
 
28
+ if negative == false && decimal_value < 0
29
+ return error_i18n("not_negative")
30
+ end
31
+
32
+ if negative == true && decimal_value >= 0
33
+ return error_i18n("only_negative")
34
+ end
35
+
36
+ if positive == false && decimal_value > 0
37
+ return error_i18n("not_positive")
38
+ end
39
+
40
+ if positive == true && decimal_value <= 0
41
+ return error_i18n("only_positive")
42
+ end
43
+
26
44
  [:ok, decimal_value]
27
45
  rescue ArgumentError
28
46
  return error_i18n("big_decimal")
@@ -30,7 +48,7 @@ class Explicit::Type::BigDecimal < Explicit::Type
30
48
 
31
49
  concerning :Webpage do
32
50
  def summary
33
- "string"
51
+ "decimal string"
34
52
  end
35
53
 
36
54
  def partial
@@ -42,17 +60,20 @@ class Explicit::Type::BigDecimal < Explicit::Type
42
60
  end
43
61
  end
44
62
 
45
- concerning :Swagger do
46
- def swagger_schema
47
- merge_base_swagger_schema({
48
- type: "string",
49
- pattern: /^\d*\.?\d*$/.inspect[1..-2],
50
- format: "decimal number",
51
- description_topics: [
52
- min&.then { swagger_i18n("big_decimal_min", min: _1) },
53
- max&.then { swagger_i18n("big_decimal_max", max: _1) }
54
- ]
55
- })
56
- end
63
+ def json_schema(flavour)
64
+ {
65
+ type: "string",
66
+ pattern: /^\d*\.?\d*$/.inspect[1..-2],
67
+ format: "decimal number",
68
+ description_topics: [
69
+ swagger_i18n("big_decimal_format"),
70
+ min&.then { swagger_i18n("big_decimal_min", min: _1) },
71
+ max&.then { swagger_i18n("big_decimal_max", max: _1) },
72
+ positive == false ? swagger_i18n("number_not_positive") : nil,
73
+ positive == true ? swagger_i18n("number_only_positive") : nil,
74
+ negative == false ? swagger_i18n("number_not_negative") : nil,
75
+ negative == true ? swagger_i18n("number_only_negative") : nil
76
+ ].compact
77
+ }
57
78
  end
58
79
  end
@@ -36,11 +36,9 @@ class Explicit::Type::Boolean < Explicit::Type
36
36
  end
37
37
  end
38
38
 
39
- concerning :Swagger do
40
- def swagger_schema
41
- merge_base_swagger_schema({
42
- type: "boolean"
43
- })
44
- end
39
+ def json_schema(flavour)
40
+ {
41
+ type: "boolean"
42
+ }
45
43
  end
46
44
  end
@@ -51,16 +51,14 @@ class Explicit::Type::Date < Explicit::Type
51
51
  end
52
52
  end
53
53
 
54
- concerning :Swagger do
55
- def swagger_schema
56
- merge_base_swagger_schema({
57
- type: "string",
58
- pattern: /\d{4}-\d{2}-\d{2}/.inspect[1..-2],
59
- format: "date",
60
- description_topics: [
61
- swagger_i18n("date_format")
62
- ]
63
- })
64
- end
54
+ def json_schema(flavour)
55
+ {
56
+ type: "string",
57
+ pattern: /\d{4}-\d{2}-\d{2}/.inspect[1..-2],
58
+ format: "date",
59
+ description_topics: [
60
+ swagger_i18n("date_format")
61
+ ]
62
+ }
65
63
  end
66
64
  end
@@ -78,18 +78,16 @@ class Explicit::Type::DateRange < Explicit::Type
78
78
  end
79
79
  end
80
80
 
81
- concerning :Swagger do
82
- def swagger_schema
83
- merge_base_swagger_schema({
84
- type: "string",
85
- pattern: FORMAT.inspect[1..-2],
86
- format: "date range",
87
- description_topics: [
88
- swagger_i18n("date_range"),
89
- min_range&.then { swagger_i18n("date_range_min_range", min_range: _1.inspect) },
90
- max_range&.then { swagger_i18n("date_range_max_range", max_range: _1.inspect) },
91
- ]
92
- })
93
- end
81
+ def json_schema(flavour)
82
+ {
83
+ type: "string",
84
+ pattern: FORMAT.inspect[1..-2],
85
+ format: "date range",
86
+ description_topics: [
87
+ swagger_i18n("date_range"),
88
+ min_range&.then { swagger_i18n("date_range_min_range", min_range: _1.inspect) },
89
+ max_range&.then { swagger_i18n("date_range_max_range", max_range: _1.inspect) },
90
+ ]
91
+ }
94
92
  end
95
93
  end
@@ -52,15 +52,13 @@ class Explicit::Type::DateTimeISO8601 < Explicit::Type
52
52
  end
53
53
  end
54
54
 
55
- concerning :Swagger do
56
- def swagger_schema
57
- merge_base_swagger_schema({
58
- type: "string",
59
- format: "date-time",
60
- description_topics: [
61
- swagger_i18n("date_time_iso8601")
62
- ]
63
- })
64
- end
55
+ def json_schema(flavour)
56
+ {
57
+ type: "string",
58
+ format: "date-time",
59
+ description_topics: [
60
+ swagger_i18n("date_time_iso8601")
61
+ ]
62
+ }
65
63
  end
66
- end
64
+ end
@@ -92,17 +92,15 @@ class Explicit::Type::DateTimeISO8601Range < Explicit::Type
92
92
  end
93
93
  end
94
94
 
95
- concerning :Swagger do
96
- def swagger_schema
97
- merge_base_swagger_schema({
98
- type: "string",
99
- format: "date time range",
100
- description_topics: [
101
- swagger_i18n("date_time_iso8601_range"),
102
- min_range&.then { swagger_i18n("date_time_iso8601_range_min_range", min_range: _1.inspect) },
103
- max_range&.then { swagger_i18n("date_time_iso8601_range_max_range", max_range: _1.inspect) },
104
- ]
105
- })
106
- end
95
+ def json_schema(flavour)
96
+ {
97
+ type: "string",
98
+ format: "date time range",
99
+ description_topics: [
100
+ swagger_i18n("date_time_iso8601_range"),
101
+ min_range&.then { swagger_i18n("date_time_iso8601_range_min_range", min_range: _1.inspect) },
102
+ max_range&.then { swagger_i18n("date_time_iso8601_range_max_range", max_range: _1.inspect) },
103
+ ]
104
+ }
107
105
  end
108
106
  end
@@ -54,16 +54,14 @@ class Explicit::Type::DateTimeUnixEpoch < Explicit::Type
54
54
  end
55
55
  end
56
56
 
57
- concerning :Swagger do
58
- def swagger_schema
59
- merge_base_swagger_schema({
60
- type: "integer",
61
- minimum: 1,
62
- format: "POSIX time",
63
- description_topics: [
64
- swagger_i18n("date_time_unix_epoch")
65
- ]
66
- })
67
- end
57
+ def json_schema(flavour)
58
+ {
59
+ type: "integer",
60
+ minimum: 1,
61
+ format: "POSIX time",
62
+ description_topics: [
63
+ swagger_i18n("date_time_unix_epoch")
64
+ ]
65
+ }
68
66
  end
69
67
  end
@@ -29,12 +29,10 @@ class Explicit::Type::Enum < Explicit::Type
29
29
  end
30
30
  end
31
31
 
32
- concerning :Swagger do
33
- def swagger_schema
34
- merge_base_swagger_schema({
35
- type: "string",
36
- enum: allowed_values
37
- })
38
- end
32
+ def json_schema(flavour)
33
+ {
34
+ type: "string",
35
+ enum: allowed_values
36
+ }
39
37
  end
40
38
  end
@@ -45,16 +45,14 @@ class Explicit::Type::File < Explicit::Type
45
45
  end
46
46
  end
47
47
 
48
- concerning :Swagger do
49
- def swagger_schema
50
- merge_base_swagger_schema({
51
- type: "string",
52
- format: "binary",
53
- description_topics: [
54
- max_size&.then { swagger_i18n("file_max_size", max_size: number_to_human_size(_1)) },
55
- content_types.any? ? swagger_i18n("file_content_types", content_types: content_types.join(', ')) : nil
56
- ]
57
- })
58
- end
48
+ def json_schema(flavour)
49
+ {
50
+ type: "string",
51
+ format: "binary",
52
+ description_topics: [
53
+ max_size&.then { swagger_i18n("file_max_size", max_size: number_to_human_size(_1)) },
54
+ content_types.any? ? swagger_i18n("file_content_types", content_types: content_types.join(', ')) : nil
55
+ ]
56
+ }
59
57
  end
60
58
  end
@@ -69,19 +69,17 @@ class Explicit::Type::Float < Explicit::Type
69
69
  end
70
70
  end
71
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
- }.compact_blank)
85
- end
72
+ def json_schema(flavour)
73
+ {
74
+ type: "number",
75
+ minimum: min,
76
+ maximum: max,
77
+ description_topics: [
78
+ positive == false ? swagger_i18n("number_not_positive") : nil,
79
+ positive == true ? swagger_i18n("number_only_positive") : nil,
80
+ negative == false ? swagger_i18n("number_not_negative") : nil,
81
+ negative == true ? swagger_i18n("number_only_negative") : nil
82
+ ].compact_blank
83
+ }.compact_blank
86
84
  end
87
85
  end
@@ -46,15 +46,13 @@ class Explicit::Type::Hash < Explicit::Type
46
46
  end
47
47
  end
48
48
 
49
- concerning :Swagger do
50
- def swagger_schema
51
- merge_base_swagger_schema({
52
- type: "object",
53
- additionalProperties: value_type.swagger_schema,
54
- description_topics: [
55
- empty == false ? swagger_i18n("hash_not_empty") : nil
56
- ]
57
- })
58
- end
49
+ def json_schema(flavour)
50
+ {
51
+ type: "object",
52
+ additionalProperties: value_type.mcp_schema,
53
+ description_topics: [
54
+ empty == false ? swagger_i18n("hash_not_empty") : nil
55
+ ]
56
+ }
59
57
  end
60
58
  end
@@ -69,19 +69,17 @@ class Explicit::Type::Integer < Explicit::Type
69
69
  end
70
70
  end
71
71
 
72
- concerning :Swagger do
73
- def swagger_schema
74
- merge_base_swagger_schema({
75
- type: "integer",
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
- }.compact_blank)
85
- end
72
+ def json_schema(flavour)
73
+ {
74
+ type: "integer",
75
+ minimum: min,
76
+ maximum: max,
77
+ description_topics: [
78
+ positive == false ? swagger_i18n("number_not_positive") : nil,
79
+ positive == true ? swagger_i18n("number_only_positive") : nil,
80
+ negative == false ? swagger_i18n("number_not_negative") : nil,
81
+ negative == true ? swagger_i18n("number_only_negative") : nil
82
+ ].compact_blank
83
+ }.compact_blank
86
84
  end
87
85
  end
@@ -33,12 +33,10 @@ class Explicit::Type::Literal < Explicit::Type
33
33
  end
34
34
  end
35
35
 
36
- concerning :Swagger do
37
- def swagger_schema
38
- merge_base_swagger_schema({
39
- type: @value.is_a?(::String) ? "string" : "integer",
40
- enum: [@value]
41
- })
42
- end
36
+ def json_schema(flavour)
37
+ {
38
+ type: @value.is_a?(::String) ? "string" : "integer",
39
+ enum: [@value]
40
+ }
43
41
  end
44
42
  end
@@ -101,11 +101,9 @@ class Explicit::Type::OneOf < Explicit::Type
101
101
  end
102
102
  end
103
103
 
104
- concerning :Swagger do
105
- def swagger_schema
106
- return subtypes.first.swagger_schema if subtypes.one?
104
+ def json_schema(flavour)
105
+ raise ::NotImplementedError if flavour == :mcp
107
106
 
108
- { oneOf: subtypes.map(&:swagger_schema) }
109
- end
107
+ { oneOf: subtypes.map(&:swagger_schema) }
110
108
  end
111
109
  end
@@ -73,33 +73,20 @@ class Explicit::Type::Record < Explicit::Type
73
73
  end
74
74
  end
75
75
 
76
- concerning :Swagger do
77
- def swagger_parameters
78
- attributes.map do |name, type|
79
- {
80
- name:,
81
- in: type.param_location_path? ? "path" : "body",
82
- description: type.description,
83
- required: !type.nilable,
84
- schema: type.swagger_schema
85
- }
86
- end
76
+ def json_schema(flavour)
77
+ properties = attributes.to_h do |name, type|
78
+ [ name, flavour == :mcp ? type.mcp_schema : type.swagger_schema ]
87
79
  end
88
80
 
89
- def swagger_schema
90
- properties = attributes.to_h do |name, type|
91
- [ name, type.swagger_schema ]
92
- end
93
-
94
- required = attributes.filter_map do |name, type|
95
- type.required? ? name.to_s : nil
96
- end
97
-
98
- merge_base_swagger_schema({
99
- type: "object",
100
- properties:,
101
- required:
102
- }.compact_blank)
81
+ required = attributes.filter_map do |name, type|
82
+ type.required? ? name.to_s : nil
103
83
  end
84
+
85
+ {
86
+ type: "object",
87
+ properties:,
88
+ required:,
89
+ additionalProperties: false
90
+ }.compact
104
91
  end
105
92
  end
@@ -51,18 +51,16 @@ class Explicit::Type::String < Explicit::Type
51
51
  end
52
52
  end
53
53
 
54
- concerning :Swagger do
55
- def swagger_schema
56
- merge_base_swagger_schema({
57
- type: "string",
58
- pattern: format&.inspect&.then { _1[1..-2] },
59
- minLength: min_length || (empty == false ? 1 : nil),
60
- maxLength: max_length,
61
- description_topics: [
62
- empty == false ? swagger_i18n("string_not_empty") : nil,
63
- downcase == true ? swagger_i18n("string_downcase") : nil
64
- ]
65
- }.compact_blank)
66
- end
54
+ def json_schema(flavour)
55
+ {
56
+ type: "string",
57
+ pattern: format&.inspect&.then { _1[1..-2] },
58
+ minLength: min_length || (empty == false ? 1 : nil),
59
+ maxLength: max_length,
60
+ description_topics: [
61
+ empty == false ? swagger_i18n("string_not_empty") : nil,
62
+ downcase == true ? swagger_i18n("string_downcase") : nil
63
+ ].compact_blank
64
+ }.compact_blank
67
65
  end
68
66
  end
data/lib/explicit/type.rb CHANGED
@@ -130,7 +130,7 @@ class Explicit::Type
130
130
  end
131
131
 
132
132
  def required?
133
- !nilable
133
+ !nilable && default.blank?
134
134
  end
135
135
 
136
136
  def error_i18n(name, context = {})
@@ -156,7 +156,15 @@ class Explicit::Type
156
156
  end
157
157
  end
158
158
 
159
- def merge_base_swagger_schema(attributes)
159
+ def swagger_schema
160
+ merge_base_json_schema(json_schema(:swagger))
161
+ end
162
+
163
+ def mcp_schema
164
+ merge_base_json_schema(json_schema(:mcp))
165
+ end
166
+
167
+ def merge_base_json_schema(attributes)
160
168
  topics = attributes.delete(:description_topics)&.compact_blank || []
161
169
 
162
170
  formatted_description =
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Explicit
4
- VERSION = "0.2.14"
4
+ VERSION = "0.2.16"
5
5
  end
data/lib/explicit.rb CHANGED
@@ -4,6 +4,13 @@ require "explicit/configuration"
4
4
  require "explicit/engine"
5
5
  require "explicit/version"
6
6
 
7
+ require "explicit/mcp_server"
8
+ require "explicit/mcp_server/builder"
9
+ require "explicit/mcp_server/request"
10
+ require "explicit/mcp_server/router"
11
+ require "explicit/mcp_server/response"
12
+ require "explicit/mcp_server/tool"
13
+
7
14
  require "explicit/documentation"
8
15
  require "explicit/documentation/builder"
9
16
  require "explicit/documentation/markdown"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: explicit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.14
4
+ version: 0.2.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luiz Vasconcellos
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-04-06 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rails
@@ -86,6 +85,12 @@ files:
86
85
  - lib/explicit/documentation/page/request.rb
87
86
  - lib/explicit/documentation/section.rb
88
87
  - lib/explicit/engine.rb
88
+ - lib/explicit/mcp_server.rb
89
+ - lib/explicit/mcp_server/builder.rb
90
+ - lib/explicit/mcp_server/request.rb
91
+ - lib/explicit/mcp_server/response.rb
92
+ - lib/explicit/mcp_server/router.rb
93
+ - lib/explicit/mcp_server/tool.rb
89
94
  - lib/explicit/request.rb
90
95
  - lib/explicit/request/example.rb
91
96
  - lib/explicit/request/invalid_params_error.rb
@@ -128,7 +133,6 @@ metadata:
128
133
  homepage_uri: https://github.com/luizpvas/explicit
129
134
  source_code_uri: https://github.com/luizpvas/explicit
130
135
  changelog_uri: https://github.com/luizpvas/explicit
131
- post_install_message:
132
136
  rdoc_options: []
133
137
  require_paths:
134
138
  - lib
@@ -143,8 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
143
147
  - !ruby/object:Gem::Version
144
148
  version: '0'
145
149
  requirements: []
146
- rubygems_version: 3.4.22
147
- signing_key:
150
+ rubygems_version: 3.6.7
148
151
  specification_version: 4
149
152
  summary: Explicit is a validation and documentation library for REST APIs
150
153
  test_files: []