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.
- checksums.yaml +7 -0
- data/.rubocop.yml +36 -0
- data/CHANGELOG.md +127 -0
- data/CONSTRAINTS.md +70 -0
- data/LICENSE.txt +21 -0
- data/README.md +1060 -0
- data/Rakefile +11 -0
- data/docs/.gitignore +5 -0
- data/docs/404.html +25 -0
- data/docs/Gemfile +38 -0
- data/docs/_config.yml +53 -0
- data/docs/_posts/2024-05-07-welcome-to-jekyll.markdown +29 -0
- data/docs/about.markdown +18 -0
- data/docs/index.markdown +7 -0
- data/lib/easy_talk/active_record_schema_builder.rb +299 -0
- data/lib/easy_talk/builders/base_builder.rb +65 -0
- data/lib/easy_talk/builders/boolean_builder.rb +23 -0
- data/lib/easy_talk/builders/collection_helpers.rb +12 -0
- data/lib/easy_talk/builders/composition_builder.rb +77 -0
- data/lib/easy_talk/builders/integer_builder.rb +28 -0
- data/lib/easy_talk/builders/null_builder.rb +16 -0
- data/lib/easy_talk/builders/number_builder.rb +27 -0
- data/lib/easy_talk/builders/object_builder.rb +180 -0
- data/lib/easy_talk/builders/string_builder.rb +27 -0
- data/lib/easy_talk/builders/temporal_builder.rb +49 -0
- data/lib/easy_talk/builders/typed_array_builder.rb +56 -0
- data/lib/easy_talk/builders/union_builder.rb +37 -0
- data/lib/easy_talk/configuration.rb +29 -0
- data/lib/easy_talk/errors.rb +8 -0
- data/lib/easy_talk/errors_helper.rb +147 -0
- data/lib/easy_talk/keywords.rb +37 -0
- data/lib/easy_talk/model.rb +197 -0
- data/lib/easy_talk/property.rb +130 -0
- data/lib/easy_talk/schema_definition.rb +78 -0
- data/lib/easy_talk/sorbet_extension.rb +15 -0
- data/lib/easy_talk/tools/function_builder.rb +40 -0
- data/lib/easy_talk/types/base_composer.rb +23 -0
- data/lib/easy_talk/types/composer.rb +88 -0
- data/lib/easy_talk/version.rb +5 -0
- data/lib/easy_talk.rb +29 -0
- metadata +265 -0
data/Rakefile
ADDED
data/docs/.gitignore
ADDED
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/
|
data/docs/about.markdown
ADDED
@@ -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
|
data/docs/index.markdown
ADDED
@@ -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,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
|