easy_talk_two 1.1.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.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +36 -0
  3. data/CHANGELOG.md +127 -0
  4. data/CONSTRAINTS.md +70 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +1060 -0
  7. data/Rakefile +11 -0
  8. data/docs/.gitignore +5 -0
  9. data/docs/404.html +25 -0
  10. data/docs/Gemfile +38 -0
  11. data/docs/_config.yml +53 -0
  12. data/docs/_posts/2024-05-07-welcome-to-jekyll.markdown +29 -0
  13. data/docs/about.markdown +18 -0
  14. data/docs/index.markdown +7 -0
  15. data/lib/easy_talk/active_record_schema_builder.rb +299 -0
  16. data/lib/easy_talk/builders/base_builder.rb +65 -0
  17. data/lib/easy_talk/builders/boolean_builder.rb +23 -0
  18. data/lib/easy_talk/builders/collection_helpers.rb +12 -0
  19. data/lib/easy_talk/builders/composition_builder.rb +77 -0
  20. data/lib/easy_talk/builders/integer_builder.rb +28 -0
  21. data/lib/easy_talk/builders/null_builder.rb +16 -0
  22. data/lib/easy_talk/builders/number_builder.rb +27 -0
  23. data/lib/easy_talk/builders/object_builder.rb +180 -0
  24. data/lib/easy_talk/builders/string_builder.rb +27 -0
  25. data/lib/easy_talk/builders/temporal_builder.rb +49 -0
  26. data/lib/easy_talk/builders/typed_array_builder.rb +56 -0
  27. data/lib/easy_talk/builders/union_builder.rb +37 -0
  28. data/lib/easy_talk/configuration.rb +29 -0
  29. data/lib/easy_talk/errors.rb +8 -0
  30. data/lib/easy_talk/errors_helper.rb +147 -0
  31. data/lib/easy_talk/keywords.rb +37 -0
  32. data/lib/easy_talk/model.rb +197 -0
  33. data/lib/easy_talk/property.rb +130 -0
  34. data/lib/easy_talk/schema_definition.rb +78 -0
  35. data/lib/easy_talk/sorbet_extension.rb +15 -0
  36. data/lib/easy_talk/tools/function_builder.rb +40 -0
  37. data/lib/easy_talk/types/base_composer.rb +23 -0
  38. data/lib/easy_talk/types/composer.rb +88 -0
  39. data/lib/easy_talk/version.rb +5 -0
  40. data/lib/easy_talk.rb +29 -0
  41. metadata +265 -0
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+ require 'rubocop/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ RuboCop::RakeTask.new
10
+
11
+ task default: %i[spec rubocop]
data/docs/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ _site
2
+ .sass-cache
3
+ .jekyll-cache
4
+ .jekyll-metadata
5
+ vendor
data/docs/404.html ADDED
@@ -0,0 +1,25 @@
1
+ ---
2
+ permalink: /404.html
3
+ layout: default
4
+ ---
5
+
6
+ <style type="text/css" media="screen">
7
+ .container {
8
+ margin: 10px auto;
9
+ max-width: 600px;
10
+ text-align: center;
11
+ }
12
+ h1 {
13
+ margin: 30px 0;
14
+ font-size: 4em;
15
+ line-height: 1;
16
+ letter-spacing: -1px;
17
+ }
18
+ </style>
19
+
20
+ <div class="container">
21
+ <h1>404</h1>
22
+
23
+ <p><strong>Page not found :(</strong></p>
24
+ <p>The requested page could not be found.</p>
25
+ </div>
data/docs/Gemfile ADDED
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ # Hello! This is where you manage which Jekyll version is used to run.
5
+ # When you want to use a different version, change it below, save the
6
+ # file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
7
+ #
8
+ # bundle exec jekyll serve
9
+ #
10
+ # This will help ensure the proper Jekyll version is running.
11
+ # Happy Jekylling!
12
+ # gem "jekyll", "~> 4.3.3"
13
+ gem 'github-pages', group: :jekyll_plugins
14
+
15
+ gem 'webrick'
16
+ # This is the default theme for new Jekyll sites. You may change this to anything you like.
17
+ gem 'minima', '~> 2.5'
18
+ # If you want to use GitHub Pages, remove the "gem "jekyll"" above and
19
+ # uncomment the line below. To upgrade, run `bundle update github-pages`.
20
+ # gem "github-pages", group: :jekyll_plugins
21
+ # If you have any plugins, put them here!
22
+ group :jekyll_plugins do
23
+ gem 'jekyll-feed', '~> 0.12'
24
+ end
25
+
26
+ # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem
27
+ # and associated library.
28
+ platforms :mingw, :x64_mingw, :mswin, :jruby do
29
+ gem 'tzinfo', '>= 1', '< 3'
30
+ gem 'tzinfo-data'
31
+ end
32
+
33
+ # Performance-booster for watching directories on Windows
34
+ gem 'wdm', '~> 0.1.1', platforms: %i[mingw x64_mingw mswin]
35
+
36
+ # Lock `http_parser.rb` gem to `v0.6.x` on JRuby builds since newer versions of the gem
37
+ # do not have a Java counterpart.
38
+ gem 'http_parser.rb', '~> 0.6.0', platforms: [:jruby]
data/docs/_config.yml ADDED
@@ -0,0 +1,53 @@
1
+ # Welcome to Jekyll!
2
+ #
3
+ # This config file is meant for settings that affect your whole blog, values
4
+ # which you are expected to set up once and rarely edit after that. If you find
5
+ # yourself editing this file very often, consider using Jekyll's data files
6
+ # feature for the data you need to update frequently.
7
+ #
8
+ # For technical reasons, this file is *NOT* reloaded automatically when you use
9
+ # 'bundle exec jekyll serve'. If you change this file, please restart the server process.
10
+ #
11
+ # If you need help with YAML syntax, here are some quick references for you:
12
+ # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml
13
+ # https://learnxinyminutes.com/docs/yaml/
14
+ #
15
+ # Site settings
16
+ # These are used to personalize your new site. If you look in the HTML files,
17
+ # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
18
+ # You can create any custom variable you would like, and they will be accessible
19
+ # in the templates via {{ site.myvariable }}.
20
+
21
+ title: EasyTalk
22
+ email: bayona.sergio@gmail.com
23
+ description: >- # this means to ignore newlines until "baseurl:"
24
+ EasyTalk: define and generate JSON Schema documents.
25
+ baseurl: "/easy_talk" # the subpath of your site, e.g. /blog
26
+ url: "https://sergiobayona.github.io" # the base hostname & protocol for your site, e.g. http://example.com
27
+ twitter_username: sergiobayona
28
+ github_username: sergiobayona
29
+
30
+ # Build settings
31
+ theme: minima
32
+ plugins:
33
+ - jekyll-feed
34
+
35
+ # Exclude from processing.
36
+ # The following items will not be processed, by default.
37
+ # Any item listed under the `exclude:` key here will be automatically added to
38
+ # the internal "default list".
39
+ #
40
+ # Excluded items can be processed by explicitly listing the directories or
41
+ # their entries' file path in the `include:` list.
42
+ #
43
+ # exclude:
44
+ # - .sass-cache/
45
+ # - .jekyll-cache/
46
+ # - gemfiles/
47
+ # - Gemfile
48
+ # - Gemfile.lock
49
+ # - node_modules/
50
+ # - vendor/bundle/
51
+ # - vendor/cache/
52
+ # - vendor/gems/
53
+ # - vendor/ruby/
@@ -0,0 +1,29 @@
1
+ ---
2
+ layout: post
3
+ title: "Welcome to Jekyll!"
4
+ date: 2024-05-07 18:39:19 -0500
5
+ categories: jekyll update
6
+ ---
7
+ You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated.
8
+
9
+ Jekyll requires blog post files to be named according to the following format:
10
+
11
+ `YEAR-MONTH-DAY-title.MARKUP`
12
+
13
+ Where `YEAR` is a four-digit number, `MONTH` and `DAY` are both two-digit numbers, and `MARKUP` is the file extension representing the format used in the file. After that, include the necessary front matter. Take a look at the source for this post to get an idea about how it works.
14
+
15
+ Jekyll also offers powerful support for code snippets:
16
+
17
+ {% highlight ruby %}
18
+ def print_hi(name)
19
+ puts "Hi, #{name}"
20
+ end
21
+ print_hi('Tom')
22
+ #=> prints 'Hi, Tom' to STDOUT.
23
+ {% endhighlight %}
24
+
25
+ Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll Talk][jekyll-talk].
26
+
27
+ [jekyll-docs]: https://jekyllrb.com/docs/home
28
+ [jekyll-gh]: https://github.com/jekyll/jekyll
29
+ [jekyll-talk]: https://talk.jekyllrb.com/
@@ -0,0 +1,18 @@
1
+ ---
2
+ layout: page
3
+ title: About
4
+ permalink: /about/
5
+ ---
6
+
7
+ This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](https://jekyllrb.com/)
8
+
9
+ You can find the source code for Minima at GitHub:
10
+ [jekyll][jekyll-organization] /
11
+ [minima](https://github.com/jekyll/minima)
12
+
13
+ You can find the source code for Jekyll at GitHub:
14
+ [jekyll][jekyll-organization] /
15
+ [jekyll](https://github.com/jekyll/jekyll)
16
+
17
+
18
+ [jekyll-organization]: https://github.com/jekyll
@@ -0,0 +1,7 @@
1
+ ---
2
+ # Feel free to add content and custom Front Matter to this file.
3
+ # To modify the layout, see https://jekyllrb.com/docs/themes/#overriding-theme-defaults
4
+
5
+ layout: home
6
+ ---
7
+ EasyTalk is a Ruby library that simplifies defining and generating JSON Schema documents, and validates that JSON data conforms to these schemas.
@@ -0,0 +1,299 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyTalk
4
+ # This class is responsible for building a SchemaDefinition from an ActiveRecord model
5
+ # It analyzes the database schema and creates a SchemaDefinition that can be
6
+ # passed to ObjectBuilder for final schema generation
7
+ class ActiveRecordSchemaBuilder
8
+ # Mapping of ActiveRecord column types to Ruby classes
9
+ COLUMN_TYPE_MAP = {
10
+ string: String,
11
+ text: String,
12
+ integer: Integer,
13
+ bigint: Integer,
14
+ float: Float,
15
+ decimal: Float,
16
+ boolean: T::Boolean,
17
+ date: Date,
18
+ datetime: DateTime,
19
+ timestamp: DateTime,
20
+ time: Time,
21
+ json: Hash,
22
+ jsonb: Hash
23
+ }.freeze
24
+
25
+ # Mapping for format constraints based on column type
26
+ FORMAT_MAP = {
27
+ date: 'date',
28
+ datetime: 'date-time',
29
+ timestamp: 'date-time',
30
+ time: 'time'
31
+ }.freeze
32
+
33
+ attr_reader :model
34
+
35
+ # Initialize the builder with an ActiveRecord model
36
+ #
37
+ # @param model [Class] An ActiveRecord model class
38
+ # @raise [ArgumentError] If the provided class is not an ActiveRecord model
39
+ def initialize(model)
40
+ raise ArgumentError, 'Class must be an ActiveRecord model' unless model.ancestors.include?(ActiveRecord::Base)
41
+
42
+ @model = model
43
+ end
44
+
45
+ # Build a SchemaDefinition object from the ActiveRecord model
46
+ #
47
+ # @return [EasyTalk::SchemaDefinition] A schema definition built from the database structure
48
+ def build_schema_definition
49
+ schema_def = SchemaDefinition.new(model.name)
50
+
51
+ # Apply basic schema metadata
52
+ apply_schema_metadata(schema_def)
53
+
54
+ # Add all database columns as properties
55
+ add_column_properties(schema_def)
56
+
57
+ # Add model associations as properties
58
+ add_association_properties(schema_def) unless EasyTalk.configuration.exclude_associations
59
+
60
+ # Add virtual properties defined in schema_enhancements
61
+ add_virtual_properties(schema_def)
62
+
63
+ schema_def
64
+ end
65
+
66
+ private
67
+
68
+ # Set top-level schema metadata like title, description, and additionalProperties
69
+ #
70
+ # @param schema_def [EasyTalk::SchemaDefinition] The schema definition to modify
71
+ def apply_schema_metadata(schema_def)
72
+ # Set title (from enhancements or derive from model name)
73
+ title = schema_enhancements['title'] || model.name.demodulize.humanize
74
+ schema_def.title(title)
75
+
76
+ # Set description if provided
77
+ if (description = schema_enhancements['description'])
78
+ schema_def.description(description)
79
+ end
80
+
81
+ # Set additionalProperties (from enhancements or configuration default)
82
+ additional_props = if schema_enhancements.key?('additionalProperties')
83
+ schema_enhancements['additionalProperties']
84
+ else
85
+ EasyTalk.configuration.default_additional_properties
86
+ end
87
+ schema_def.additional_properties(additional_props)
88
+ end
89
+
90
+ # Add properties based on database columns
91
+ #
92
+ # @param schema_def [EasyTalk::SchemaDefinition] The schema definition to modify
93
+ def add_column_properties(schema_def)
94
+ filtered_columns.each do |column|
95
+ # Get column enhancement info if it exists
96
+ column_enhancements = schema_enhancements.dig('properties', column.name.to_s) || {}
97
+
98
+ # Map the database type to Ruby type
99
+ ruby_type = COLUMN_TYPE_MAP.fetch(column.type, String)
100
+
101
+ # If the column is nullable, wrap the type in a Union with NilClass
102
+ ruby_type = T::Types::Union.new([ruby_type, NilClass]) if column.null
103
+
104
+ # Build constraints hash for this column
105
+ constraints = build_column_constraints(column, column_enhancements)
106
+
107
+ # Add the property to schema definition
108
+ schema_def.property(column.name.to_sym, ruby_type, constraints)
109
+ end
110
+ end
111
+
112
+ # Build constraints hash for a database column
113
+ #
114
+ # @param column [ActiveRecord::ConnectionAdapters::Column] The database column
115
+ # @param enhancements [Hash] Any schema enhancements for this column
116
+ # @return [Hash] The constraints hash
117
+ def build_column_constraints(column, enhancements)
118
+ constraints = {
119
+ optional: enhancements['optional'],
120
+ description: enhancements['description'],
121
+ title: enhancements['title']
122
+ }
123
+
124
+ # Add format constraint for date/time columns
125
+ if (format = FORMAT_MAP[column.type])
126
+ constraints[:format] = format
127
+ end
128
+
129
+ # Add max_length for string columns with limits
130
+ constraints[:max_length] = column.limit if column.type == :string && column.limit
131
+
132
+ # Add precision/scale for numeric columns
133
+ if column.type == :decimal && column.precision
134
+ constraints[:precision] = column.precision
135
+ constraints[:scale] = column.scale if column.scale
136
+ end
137
+
138
+ # Add default value if present and not a proc
139
+ if column.default && !column.default.is_a?(Proc) && column.type == :boolean
140
+ constraints[:default] = ActiveModel::Type::Boolean.new.cast(column.default)
141
+ elsif column.default && !column.default.is_a?(Proc)
142
+ constraints[:default] = column.default
143
+ end
144
+
145
+ # Remove nil values
146
+ constraints.compact
147
+ end
148
+
149
+ # Add properties based on ActiveRecord associations
150
+ #
151
+ # @param schema_def [EasyTalk::SchemaDefinition] The schema definition to modify
152
+ def add_association_properties(schema_def)
153
+ model.reflect_on_all_associations.each do |association|
154
+ # Skip if we can't determine the class or it's in the association exclusion list
155
+ next if association_excluded?(association)
156
+
157
+ # Get association enhancement info if it exists
158
+ assoc_enhancements = schema_enhancements.dig('properties', association.name.to_s) || {}
159
+
160
+ case association.macro
161
+ when :belongs_to, :has_one
162
+ schema_def.property(
163
+ association.name,
164
+ association.klass,
165
+ { optional: assoc_enhancements['optional'], description: assoc_enhancements['description'] }.compact
166
+ )
167
+ when :has_many, :has_and_belongs_to_many
168
+ schema_def.property(
169
+ association.name,
170
+ T::Array[association.klass],
171
+ { optional: assoc_enhancements['optional'], description: assoc_enhancements['description'] }.compact
172
+ )
173
+ end
174
+ end
175
+ end
176
+
177
+ # Add virtual properties defined in schema_enhancements
178
+ #
179
+ # @param schema_def [EasyTalk::SchemaDefinition] The schema definition to modify
180
+ def add_virtual_properties(schema_def)
181
+ return unless schema_enhancements['properties']
182
+
183
+ schema_enhancements['properties'].each do |name, options|
184
+ next unless options['virtual']
185
+
186
+ # Map string type name to Ruby class
187
+ ruby_type = map_type_string_to_ruby_class(options['type'] || 'string')
188
+
189
+ # Build constraints for virtual property
190
+ constraints = {
191
+ description: options['description'],
192
+ title: options['title'],
193
+ optional: options['optional'],
194
+ format: options['format'],
195
+ default: options['default'],
196
+ min_length: options['minLength'],
197
+ max_length: options['maxLength'],
198
+ enum: options['enum']
199
+ }.compact
200
+
201
+ # Add the virtual property
202
+ schema_def.property(name.to_sym, ruby_type, constraints)
203
+ end
204
+ end
205
+
206
+ # Map a type string to a Ruby class
207
+ #
208
+ # @param type_str [String] The type string (e.g., 'string', 'integer')
209
+ # @return [Class] The corresponding Ruby class
210
+ def map_type_string_to_ruby_class(type_str)
211
+ case type_str.to_s.downcase
212
+ when 'string' then String
213
+ when 'integer' then Integer
214
+ when 'number' then Float
215
+ when 'boolean' then T::Boolean
216
+ when 'object' then Hash
217
+ when 'array' then Array
218
+ when 'date' then Date
219
+ when 'datetime' then DateTime
220
+ when 'time' then Time
221
+ else String # Default fallback
222
+ end
223
+ end
224
+
225
+ # Get all columns that should be included in the schema
226
+ #
227
+ # @return [Array<ActiveRecord::ConnectionAdapters::Column>] Filtered columns
228
+ def filtered_columns
229
+ model.columns.reject do |column|
230
+ config = EasyTalk.configuration
231
+ excluded_columns.include?(column.name.to_sym) ||
232
+ (config.exclude_primary_key && column.name == model.primary_key) ||
233
+ (config.exclude_timestamps && timestamp_column?(column.name)) ||
234
+ (config.exclude_foreign_keys && foreign_key_column?(column.name))
235
+ end
236
+ end
237
+
238
+ # Check if a column is a timestamp column
239
+ #
240
+ # @param column_name [String] The column name
241
+ # @return [Boolean] True if the column is a timestamp column
242
+ def timestamp_column?(column_name)
243
+ %w[created_at updated_at].include?(column_name)
244
+ end
245
+
246
+ # Check if a column is a foreign key column
247
+ #
248
+ # @param column_name [String] The column name
249
+ # @return [Boolean] True if the column is a foreign key column
250
+ def foreign_key_column?(column_name)
251
+ column_name.end_with?('_id')
252
+ end
253
+
254
+ # Check if an association should be excluded
255
+ #
256
+ # @param association [ActiveRecord::Reflection::AssociationReflection] The association
257
+ # @return [Boolean] True if the association should be excluded
258
+ def association_excluded?(association)
259
+ !association.klass ||
260
+ excluded_associations.include?(association.name.to_sym) ||
261
+ association.options[:polymorphic] # Skip polymorphic associations (complex to model)
262
+ end
263
+
264
+ # Get schema enhancements
265
+ #
266
+ # @return [Hash] Schema enhancements
267
+ def schema_enhancements
268
+ @schema_enhancements ||= if model.respond_to?(:schema_enhancements)
269
+ model.schema_enhancements.deep_transform_keys(&:to_s)
270
+ else
271
+ {}
272
+ end
273
+ end
274
+
275
+ # Get all excluded columns
276
+ #
277
+ # @return [Array<Symbol>] Excluded column names
278
+ def excluded_columns
279
+ @excluded_columns ||= begin
280
+ config = EasyTalk.configuration
281
+ global_exclusions = config.excluded_columns || []
282
+ model_exclusions = schema_enhancements['ignore'] || []
283
+
284
+ # Combine and convert to symbols for consistent comparison
285
+ (global_exclusions + model_exclusions).map(&:to_sym)
286
+ end
287
+ end
288
+
289
+ # Get all excluded associations
290
+ #
291
+ # @return [Array<Symbol>] Excluded association names
292
+ def excluded_associations
293
+ @excluded_associations ||= begin
294
+ model_exclusions = schema_enhancements['ignore_associations'] || []
295
+ model_exclusions.map(&:to_sym)
296
+ end
297
+ end
298
+ end
299
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+
4
+ module EasyTalk
5
+ module Builders
6
+ # BaseBuilder is a class that provides a common structure for building schema properties
7
+ class BaseBuilder
8
+ extend T::Sig
9
+
10
+ # BaseBuilder is a class that provides a common structure for building objects
11
+ # representing schema properties.
12
+ COMMON_OPTIONS = {
13
+ title: { type: T.nilable(String), key: :title },
14
+ description: { type: T.nilable(String), key: :description },
15
+ optional: { type: T.nilable(T::Boolean), key: :optional }
16
+ }.freeze
17
+
18
+ attr_reader :property_name, :schema, :options
19
+
20
+ sig do
21
+ params(
22
+ property_name: Symbol,
23
+ schema: T::Hash[Symbol, T.untyped],
24
+ options: T::Hash[Symbol, String],
25
+ valid_options: T::Hash[Symbol, T.untyped]
26
+ ).void
27
+ end
28
+ # Initializes a new instance of the BaseBuilder class.
29
+ #
30
+ # @param property_name [Symbol] The name of the property.
31
+ # @param schema [Hash] A hash representing a json schema object.
32
+ # @param options [Hash] The options for the builder (default: {}).
33
+ # @param valid_options [Hash] The acceptable options for the given property type (default: {}).
34
+ def initialize(property_name, schema, options = {}, valid_options = {})
35
+ @valid_options = COMMON_OPTIONS.merge(valid_options)
36
+ EasyTalk.assert_valid_property_options(property_name, options, @valid_options.keys)
37
+ @property_name = property_name
38
+ @schema = schema
39
+ @options = options
40
+ end
41
+
42
+ # Builds the schema object based on the provided options.
43
+ sig { returns(T::Hash[Symbol, T.untyped]) }
44
+ def build
45
+ @valid_options.each_with_object(schema) do |(constraint_name, value), obj|
46
+ next if @options[constraint_name].nil?
47
+
48
+ # Use our centralized validation
49
+ ErrorHelper.validate_constraint_value(
50
+ property_name: property_name,
51
+ constraint_name: constraint_name,
52
+ value_type: value[:type],
53
+ value: @options[constraint_name]
54
+ )
55
+
56
+ obj[value[:key]] = @options[constraint_name]
57
+ end
58
+ end
59
+
60
+ def self.collection_type?
61
+ false
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_builder'
4
+
5
+ module EasyTalk
6
+ module Builders
7
+ # Builder class for boolean properties.
8
+ class BooleanBuilder < BaseBuilder
9
+ extend T::Sig
10
+
11
+ # VALID_OPTIONS defines the valid options for a boolean property.
12
+ VALID_OPTIONS = {
13
+ enum: { type: T::Array[T::Boolean], key: :enum },
14
+ default: { type: T::Boolean, key: :default }
15
+ }.freeze
16
+
17
+ sig { params(name: Symbol, constraints: Hash).void }
18
+ def initialize(name, constraints = {})
19
+ super(name, { type: 'boolean' }, constraints, VALID_OPTIONS)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EasyTalk
4
+ module Builders
5
+ # Base builder class for array-type properties.
6
+ module CollectionHelpers
7
+ def collection_type?
8
+ true
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'collection_helpers'
4
+
5
+ module EasyTalk
6
+ module Builders
7
+ # This class represents a builder for composing JSON schemas using the "allOf", "anyOf", or "oneOf" keywords.
8
+ class CompositionBuilder
9
+ extend CollectionHelpers
10
+ extend T::Sig
11
+
12
+ COMPOSER_TO_KEYWORD = {
13
+ 'AllOfBuilder' => 'allOf',
14
+ 'AnyOfBuilder' => 'anyOf',
15
+ 'OneOfBuilder' => 'oneOf'
16
+ }.freeze
17
+
18
+ sig { params(name: Symbol, type: T.untyped, _constraints: Hash).void }
19
+ # Initializes a new instance of the CompositionBuilder class.
20
+ #
21
+ # @param name [Symbol] The name of the composition.
22
+ # @param type [Class] The type of the composition.
23
+ # @param _constraints [Hash] The constraints for the composition (not used in this method).
24
+ def initialize(name, type, _constraints)
25
+ @composer_type = self.class.name.split('::').last
26
+ @name = name
27
+ @type = type
28
+ @context = {}
29
+ end
30
+
31
+ # Builds the composed JSON schema.
32
+ #
33
+ # @return [void]
34
+ def build
35
+ @context[@name.to_sym] = {
36
+ type: 'object',
37
+ composer_keyword => schemas
38
+ }
39
+ end
40
+
41
+ # Returns the composer keyword based on the composer type.
42
+ #
43
+ # @return [String] The composer keyword.
44
+ def composer_keyword
45
+ COMPOSER_TO_KEYWORD[@composer_type]
46
+ end
47
+
48
+ # Returns an array of schemas for the composed JSON schema.
49
+ #
50
+ # @return [Array<Hash>] The array of schemas.
51
+ def schemas
52
+ items.map do |type|
53
+ type.respond_to?(:schema) ? type.schema : { type: type.to_s.downcase }
54
+ end
55
+ end
56
+
57
+ # Returns the items of the type.
58
+ #
59
+ # @return [T.untyped] The items of the type.
60
+ def items
61
+ @type.items
62
+ end
63
+
64
+ # Builder class for AllOf composition.
65
+ class AllOfBuilder < CompositionBuilder
66
+ end
67
+
68
+ # Builder class for AnyOf composition.
69
+ class AnyOfBuilder < CompositionBuilder
70
+ end
71
+
72
+ # Builder class for OneOf composition.
73
+ class OneOfBuilder < CompositionBuilder
74
+ end
75
+ end
76
+ end
77
+ end