model-to-schema 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +20 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +12 -0
- data/esquema.gemspec +38 -0
- data/lib/esquema/builder.rb +155 -0
- data/lib/esquema/configuration.rb +34 -0
- data/lib/esquema/keyword_validator.rb +98 -0
- data/lib/esquema/model.rb +31 -0
- data/lib/esquema/property.rb +238 -0
- data/lib/esquema/schema_enhancer.rb +90 -0
- data/lib/esquema/type_caster.rb +53 -0
- data/lib/esquema/version.rb +5 -0
- data/lib/esquema/virtual_column.rb +46 -0
- data/lib/esquema.rb +14 -0
- data/lib/generators/esquema/install/install_generator.rb +16 -0
- data/lib/generators/esquema/install/templates/esquema_initializer.rb +22 -0
- data/sorbet/config +4 -0
- data/sorbet/rbi/annotations/.gitattributes +1 -0
- data/sorbet/rbi/annotations/activemodel.rbi +89 -0
- data/sorbet/rbi/annotations/activerecord.rbi +92 -0
- data/sorbet/rbi/annotations/activesupport.rbi +421 -0
- data/sorbet/rbi/annotations/rainbow.rbi +269 -0
- data/sorbet/rbi/gems/.gitattributes +1 -0
- data/sorbet/rbi/gems/activemodel@7.1.3.rbi +8 -0
- data/sorbet/rbi/gems/activerecord@7.1.3.rbi +8 -0
- data/sorbet/rbi/gems/activesupport@7.1.3.rbi +192 -0
- data/sorbet/rbi/gems/ast@2.4.2.rbi +584 -0
- data/sorbet/rbi/gems/base64@0.2.0.rbi +8 -0
- data/sorbet/rbi/gems/bigdecimal@3.1.6.rbi +8 -0
- data/sorbet/rbi/gems/byebug@11.1.3.rbi +3606 -0
- data/sorbet/rbi/gems/coderay@1.1.3.rbi +3426 -0
- data/sorbet/rbi/gems/concurrent-ruby@1.2.3.rbi +8 -0
- data/sorbet/rbi/gems/connection_pool@2.4.1.rbi +8 -0
- data/sorbet/rbi/gems/diff-lcs@1.5.1.rbi +1130 -0
- data/sorbet/rbi/gems/drb@2.2.0.rbi +1272 -0
- data/sorbet/rbi/gems/erubi@1.12.0.rbi +145 -0
- data/sorbet/rbi/gems/i18n@1.14.1.rbi +8 -0
- data/sorbet/rbi/gems/json@2.7.1.rbi +1553 -0
- data/sorbet/rbi/gems/language_server-protocol@3.17.0.3.rbi +14237 -0
- data/sorbet/rbi/gems/method_source@1.0.0.rbi +272 -0
- data/sorbet/rbi/gems/minitest@5.22.2.rbi +8 -0
- data/sorbet/rbi/gems/mutex_m@0.2.0.rbi +8 -0
- data/sorbet/rbi/gems/netrc@0.11.0.rbi +158 -0
- data/sorbet/rbi/gems/parallel@1.24.0.rbi +280 -0
- data/sorbet/rbi/gems/parser@3.3.0.5.rbi +5472 -0
- data/sorbet/rbi/gems/prettier_print@1.2.1.rbi +951 -0
- data/sorbet/rbi/gems/prism@0.24.0.rbi +31040 -0
- data/sorbet/rbi/gems/pry-byebug@3.10.1.rbi +1150 -0
- data/sorbet/rbi/gems/pry@0.14.2.rbi +10075 -0
- data/sorbet/rbi/gems/racc@1.7.3.rbi +157 -0
- data/sorbet/rbi/gems/rainbow@3.1.1.rbi +402 -0
- data/sorbet/rbi/gems/rake@13.1.0.rbi +3027 -0
- data/sorbet/rbi/gems/rbi@0.1.9.rbi +3006 -0
- data/sorbet/rbi/gems/regexp_parser@2.9.0.rbi +3771 -0
- data/sorbet/rbi/gems/rexml@3.2.6.rbi +4781 -0
- data/sorbet/rbi/gems/rspec-core@3.13.0.rbi +10978 -0
- data/sorbet/rbi/gems/rspec-expectations@3.13.0.rbi +8153 -0
- data/sorbet/rbi/gems/rspec-mocks@3.13.0.rbi +5340 -0
- data/sorbet/rbi/gems/rspec-support@3.13.0.rbi +1629 -0
- data/sorbet/rbi/gems/rspec@3.13.0.rbi +82 -0
- data/sorbet/rbi/gems/rubocop-ast@1.30.0.rbi +7006 -0
- data/sorbet/rbi/gems/rubocop@1.60.2.rbi +57383 -0
- data/sorbet/rbi/gems/ruby-progressbar@1.13.0.rbi +1317 -0
- data/sorbet/rbi/gems/ruby2_keywords@0.0.5.rbi +8 -0
- data/sorbet/rbi/gems/spoom@1.2.4.rbi +3777 -0
- data/sorbet/rbi/gems/sqlite3@1.7.2.rbi +1691 -0
- data/sorbet/rbi/gems/syntax_tree@6.2.0.rbi +23133 -0
- data/sorbet/rbi/gems/tapioca@0.12.0.rbi +3510 -0
- data/sorbet/rbi/gems/thor@1.3.0.rbi +4345 -0
- data/sorbet/rbi/gems/timeout@0.4.1.rbi +142 -0
- data/sorbet/rbi/gems/tzinfo@2.0.6.rbi +8 -0
- data/sorbet/rbi/gems/unicode-display_width@2.5.0.rbi +65 -0
- data/sorbet/rbi/gems/yard-sorbet@0.8.1.rbi +428 -0
- data/sorbet/rbi/gems/yard@0.9.34.rbi +18219 -0
- data/sorbet/rbi/todo.rbi +20 -0
- data/sorbet/tapioca/config.yml +13 -0
- data/sorbet/tapioca/require.rb +4 -0
- metadata +176 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 92f331264f95271e7f464eadf17836ae1bfc3ef7d968b0f4c7e09c0694375200
|
4
|
+
data.tar.gz: ab9a5582eee8f567802129b7248aaf1aface3970363d79112e7ad5e071d8396e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a6253ecb98c34f70ab1c9d8f00613bdf40cc55656a8f29dc37d45f95d211408718e437f7b1f74f81145def1aeb25185bf85270f0a7ed8887b5a4e8b44d50dd30
|
7
|
+
data.tar.gz: 71110299132f10d628f717ee65c90d772da6790f0f8b0711541a714653c50a35f8928ddc6e5dec756a433e9a9de2d2be546f04c29d849f7d823bb03351c1a5fa
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 3.1
|
3
|
+
Exclude:
|
4
|
+
- "spec/support/matchers/**"
|
5
|
+
- "vendor/**/*"
|
6
|
+
|
7
|
+
Style/StringLiterals:
|
8
|
+
Enabled: true
|
9
|
+
EnforcedStyle: double_quotes
|
10
|
+
|
11
|
+
Style/StringLiteralsInInterpolation:
|
12
|
+
Enabled: true
|
13
|
+
EnforcedStyle: double_quotes
|
14
|
+
|
15
|
+
Layout/LineLength:
|
16
|
+
Max: 120
|
17
|
+
|
18
|
+
Metrics/BlockLength:
|
19
|
+
Enabled: false
|
20
|
+
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
3.1.0
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
## [0.1.2] - 2024-02-22
|
2
|
+
- Removed Rails dependency and added ActiveRecord dependency.
|
3
|
+
- Added documentation for `virtual_property` and improved overall README.md
|
4
|
+
- Only supporting Ruby 3.2 and above for now.
|
5
|
+
- Added support for `virtual_property`. This allows you to define JSON schema properties that don't have a corresponding AR attribute.
|
6
|
+
- Performance improvements.
|
7
|
+
- Improved error handling and added more tests.
|
8
|
+
- Improved documentation and added more examples.
|
9
|
+
- Added additional support for schema validation keywords. Example: `minLength`, `maxLength`, `pattern`, `format`, `multipleOf`, `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `enum`, `const`, `items`, `additionalItems`, `contains`, `minItems`, `maxItems`, `uniqueItems`, `propertyNames`, `minProperties`, `maxProperties`, `required`, `dependencies`, `patternProperties`, `allOf`, `anyOf`, `oneOf`, `not`.
|
10
|
+
|
11
|
+
## [0.1.0] - 2024-02-14
|
12
|
+
|
13
|
+
- Initial release
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2024 Sergio Bayona
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Esquema
|
2
|
+
|
3
|
+
Esquema is a Ruby library for JSON Schema generation from ActiveRecord models.
|
4
|
+
|
5
|
+
Esquema was designed with the following assumptions:
|
6
|
+
|
7
|
+
- An ActiveRecord model represents a JSON Schema object.
|
8
|
+
- The JSON object properties are a representation of the model's attributes.
|
9
|
+
- The JSON Schema property types are inferred from the model's attribute types.
|
10
|
+
- The model associations (has_many, belongs_to, etc.) are represented as subschemas or nested schema objects.
|
11
|
+
- You can customize the generated schema by using the configuration file or the `enhance_schema` method.
|
12
|
+
|
13
|
+
Example Use:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class User < ApplicationRecord
|
17
|
+
include Esquema::Model
|
18
|
+
|
19
|
+
# Assuming the User db table has the following columns:
|
20
|
+
# column :name, :string
|
21
|
+
# column :email, :string
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
Calling `User.json_schema` will return the JSON Schema for the User model:
|
28
|
+
|
29
|
+
```json
|
30
|
+
{
|
31
|
+
"title": "User model",
|
32
|
+
"type": "object",
|
33
|
+
"properties": {
|
34
|
+
"id": {
|
35
|
+
"type": "integer"
|
36
|
+
},
|
37
|
+
"name": {
|
38
|
+
"type": "string"
|
39
|
+
},
|
40
|
+
"email": {
|
41
|
+
"type": "string"
|
42
|
+
}
|
43
|
+
},
|
44
|
+
"required:": [
|
45
|
+
"name",
|
46
|
+
"email"
|
47
|
+
]
|
48
|
+
}
|
49
|
+
```
|
50
|
+
|
51
|
+
## Installation
|
52
|
+
|
53
|
+
install the gem by executing:
|
54
|
+
|
55
|
+
$ gem install esquema
|
56
|
+
|
57
|
+
|
58
|
+
Run the following command to install the gem and generate the configuration file:
|
59
|
+
|
60
|
+
```bash
|
61
|
+
rails generate esquema:install
|
62
|
+
```
|
63
|
+
|
64
|
+
This will generate a configuration file at:
|
65
|
+
|
66
|
+
<rails_app>/config/initializer/esquema.rb
|
67
|
+
|
68
|
+
|
69
|
+
## Usage
|
70
|
+
|
71
|
+
Simply include the `Esquema::Model` module in your ActiveRecord model and call the `json_schema` method to generate the JSON Schema for the model.
|
72
|
+
|
73
|
+
There are multiple ways to customize the generated schema:
|
74
|
+
- You can exclude columns, foreign keys, and associations from the schema. See the <rails_project>/config/initializer/esquema.rb configuration for more details.
|
75
|
+
- For more complex customizations, you can use the `enhance_schema` method to modify the schema directly on the AR model. Here is an example:
|
76
|
+
|
77
|
+
```ruby
|
78
|
+
class User < ApplicationRecord
|
79
|
+
include Esquema::Model
|
80
|
+
|
81
|
+
enhance_schema do
|
82
|
+
model_description "A user of the system"
|
83
|
+
property :name, description: "The user's name", title: "Full Name"
|
84
|
+
property :group, enum: [1, 2, 3], default: 1, description: "The user's group"
|
85
|
+
property :email, description: "The user's email", format: "email"
|
86
|
+
virtual_property :age, type: "integer", minimum: 18, maximum: 100, description: "The user's age"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
In the example above, the `enhance_schema` method is used to add a description to the model, change the title of the `name` property and add a description. It adds an enum, default value and a description to the `group` property.
|
92
|
+
|
93
|
+
Use the `property` keyword for the existing model attributes. In other words the symbol passed to the `property` method must be a column in the table that the model represents. Property does not accept a `type` argument, as the type is inferred from the column type.
|
94
|
+
|
95
|
+
Use the `virtual_property` keyword for properties that are not columns in the table that the model represents. Virtual properties require a `type` argument, as the type cannot be inferred.
|
96
|
+
|
97
|
+
|
98
|
+
## Development
|
99
|
+
|
100
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
101
|
+
|
102
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
103
|
+
|
104
|
+
## Contributing
|
105
|
+
|
106
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sergiobayona/esquema.
|
107
|
+
|
108
|
+
## License
|
109
|
+
|
110
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
111
|
+
|
data/Rakefile
ADDED
data/esquema.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/esquema/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "model-to-schema"
|
7
|
+
spec.version = Esquema::VERSION
|
8
|
+
spec.authors = ["Sergio Bayona"]
|
9
|
+
spec.email = ["bayona.sergio@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "Generate json-schema from ActiveRecord models."
|
12
|
+
spec.description = "Generate json-schema from ActiveRecord models."
|
13
|
+
spec.homepage = "https://github.com/sergiobayona/esquema"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.1"
|
16
|
+
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = "https://github.com/sergiobayona/esquema"
|
21
|
+
spec.metadata["changelog_uri"] = "https://github.com/sergiobayona/esquema/blob/main/CHANGELOG.md"
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(File.expand_path(f) == __FILE__) ||
|
28
|
+
f.start_with?(*%w[bin/ spec/ .git .github Gemfile])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
spec.bindir = "exe"
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
33
|
+
spec.require_paths = ["lib"]
|
34
|
+
|
35
|
+
spec.add_dependency "activerecord", "~> 7.0"
|
36
|
+
spec.add_development_dependency "pry-byebug", "~> 3.10", ">= 3.10.1"
|
37
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
38
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "property"
|
4
|
+
require_relative "virtual_column"
|
5
|
+
|
6
|
+
module Esquema
|
7
|
+
# The Builder class is responsible for building a schema for an ActiveRecord model.
|
8
|
+
class Builder
|
9
|
+
attr_reader :model, :required_properties
|
10
|
+
|
11
|
+
def initialize(model)
|
12
|
+
raise ArgumentError, "Class is not an ActiveRecord model" unless model.ancestors.include? ActiveRecord::Base
|
13
|
+
|
14
|
+
@model = model
|
15
|
+
@properties = {}
|
16
|
+
@required_properties = []
|
17
|
+
end
|
18
|
+
|
19
|
+
# Builds the schema for the ActiveRecord model.
|
20
|
+
#
|
21
|
+
# @return [Hash] The built schema.
|
22
|
+
def build_schema
|
23
|
+
@build_schema ||= {
|
24
|
+
title: build_title,
|
25
|
+
description: build_description,
|
26
|
+
type: build_type,
|
27
|
+
properties: build_properties,
|
28
|
+
required: required_properties
|
29
|
+
}.compact
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [Hash] The schema for the ActiveRecord model.
|
33
|
+
def schema
|
34
|
+
build_schema
|
35
|
+
end
|
36
|
+
|
37
|
+
# Builds the properties for the schema.
|
38
|
+
#
|
39
|
+
# @return [Hash] The built properties.
|
40
|
+
def build_properties
|
41
|
+
add_properties_from_columns
|
42
|
+
add_properties_from_associations
|
43
|
+
add_virtual_properties
|
44
|
+
@properties
|
45
|
+
end
|
46
|
+
|
47
|
+
# Builds the type for the schema.
|
48
|
+
#
|
49
|
+
# @return [String] The built type.
|
50
|
+
def build_type
|
51
|
+
model.respond_to?(:type) ? model.type : "object"
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Adds virtual properties to the schema.
|
57
|
+
def add_virtual_properties
|
58
|
+
return unless schema_enhancements[:properties]
|
59
|
+
|
60
|
+
virtual_properties = schema_enhancements[:properties].select { |_k, v| v[:virtual] }
|
61
|
+
required_properties.concat(virtual_properties.keys)
|
62
|
+
|
63
|
+
virtual_properties.each do |property_name, options|
|
64
|
+
virtual_col = VirtualColumn.new(property_name, options)
|
65
|
+
@properties[property_name] = Property.new(virtual_col, options)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Adds properties from columns to the schema.
|
70
|
+
def add_properties_from_columns
|
71
|
+
columns.each do |property|
|
72
|
+
next if property.name.end_with?("_id") && config.exclude_foreign_keys?
|
73
|
+
|
74
|
+
required_properties << property.name
|
75
|
+
options = enhancement_for(property.name)
|
76
|
+
@properties[property.name] ||= Property.new(property, options)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Adds properties from associations to the schema.
|
81
|
+
def add_properties_from_associations
|
82
|
+
associations.each do |association|
|
83
|
+
next if config.exclude_associations?
|
84
|
+
|
85
|
+
@properties[association.name] ||= Property.new(association)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Retrieves the columns of the model.
|
90
|
+
#
|
91
|
+
# @return [Array<ActiveRecord::ConnectionAdapters::Column>] The columns of the model.
|
92
|
+
def columns
|
93
|
+
model.columns.reject { |c| excluded_column?(c.name) }
|
94
|
+
end
|
95
|
+
|
96
|
+
# Retrieves the enhancement options for a property.
|
97
|
+
#
|
98
|
+
# @param property_name [Symbol] The name of the property.
|
99
|
+
# @return [Hash] The enhancement options for the property.
|
100
|
+
def enhancement_for(property_name)
|
101
|
+
schema_enhancements.dig(:properties, property_name.to_sym) || {}
|
102
|
+
end
|
103
|
+
|
104
|
+
# Retrieves the associations of the model.
|
105
|
+
#
|
106
|
+
# @return [Array<ActiveRecord::Reflection::AssociationReflection>] The associations of the model.
|
107
|
+
def associations
|
108
|
+
return [] unless model.respond_to?(:reflect_on_all_associations)
|
109
|
+
|
110
|
+
model.reflect_on_all_associations
|
111
|
+
end
|
112
|
+
|
113
|
+
# Checks if a column is excluded.
|
114
|
+
#
|
115
|
+
# @param column_name [String] The name of the column.
|
116
|
+
# @return [Boolean] True if the column is excluded, false otherwise.
|
117
|
+
def excluded_column?(column_name)
|
118
|
+
raise ArgumentError, "Column name must be a string" unless column_name.is_a? String
|
119
|
+
|
120
|
+
config.excluded_columns.include?(column_name.to_sym)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Builds the title for the schema.
|
124
|
+
#
|
125
|
+
# @return [String] The built title.
|
126
|
+
def build_title
|
127
|
+
schema_enhancements[:model_title].presence || model.name.demodulize.humanize
|
128
|
+
end
|
129
|
+
|
130
|
+
# Builds the description for the schema.
|
131
|
+
#
|
132
|
+
# @return [String] The built description.
|
133
|
+
def build_description
|
134
|
+
schema_enhancements[:model_description].presence
|
135
|
+
end
|
136
|
+
|
137
|
+
# Retrieves the schema enhancements for the model.
|
138
|
+
#
|
139
|
+
# @return [Hash] The schema enhancements.
|
140
|
+
def schema_enhancements
|
141
|
+
if model.respond_to?(:schema_enhancements)
|
142
|
+
model.schema_enhancements
|
143
|
+
else
|
144
|
+
{}
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Retrieves the Esquema configuration.
|
149
|
+
#
|
150
|
+
# @return [Esquema::Configuration] The Esquema configuration.
|
151
|
+
def config
|
152
|
+
Esquema.configuration
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Esquema # rubocop:disable Style/Documentation
|
4
|
+
# The Configuration module provides configuration options for the gem.
|
5
|
+
class Configuration
|
6
|
+
attr_accessor :exclude_associations, :exclude_foreign_keys, :excluded_columns
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
reset
|
10
|
+
end
|
11
|
+
|
12
|
+
def reset
|
13
|
+
@exclude_associations = false
|
14
|
+
@exclude_foreign_keys = true
|
15
|
+
@excluded_columns = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def exclude_foreign_keys?
|
19
|
+
exclude_foreign_keys
|
20
|
+
end
|
21
|
+
|
22
|
+
def exclude_associations?
|
23
|
+
exclude_associations
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.configuration
|
28
|
+
@configuration ||= Configuration.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.configure
|
32
|
+
yield(configuration)
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Esquema
|
4
|
+
# The KeywordValidator module provides functionality for validating schema keyword values.
|
5
|
+
# There are three types of keyword values that must be validated against the type of the
|
6
|
+
# property they are associated with:
|
7
|
+
# - default: The default value for a property.
|
8
|
+
# - enum: The allowed values for a property.
|
9
|
+
# - const: The constant value for a property.
|
10
|
+
module KeywordValidator
|
11
|
+
# The valid options for a property.
|
12
|
+
VALID_OPTIONS = %i[type title description maxLength minLength pattern maxItems minItems
|
13
|
+
maxProperties minProperties properties additionalProperties dependencies
|
14
|
+
enum format multipleOf maximum exclusiveMaximum minimum exclusiveMinimum
|
15
|
+
const allOf anyOf oneOf not default items uniqueItems virtual].freeze
|
16
|
+
|
17
|
+
# Hash containing type validators for different data types.
|
18
|
+
TYPE_VALIDATORS = {
|
19
|
+
string: ->(value) { value.is_a?(String) },
|
20
|
+
integer: ->(value) { value.is_a?(Integer) },
|
21
|
+
number: ->(value) { value.is_a?(Numeric) },
|
22
|
+
boolean: ->(value) { [true, false].include?(value) },
|
23
|
+
array: ->(value) { value.is_a?(Array) },
|
24
|
+
object: ->(value) { value.is_a?(Hash) },
|
25
|
+
null: ->(value) { value.nil? },
|
26
|
+
date: ->(value) { value.is_a?(Date) },
|
27
|
+
datetime: ->(value) { value.is_a?(DateTime) },
|
28
|
+
time: ->(value) { value.is_a?(Time) }
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
# Validates a property based on its type and options.
|
32
|
+
#
|
33
|
+
# @param property_name [Symbol] The name of the property being validated.
|
34
|
+
# @param type [Symbol] The type of the property.
|
35
|
+
# @param options [Hash] The options for the property.
|
36
|
+
# @option options [Object] :default The default value for the property.
|
37
|
+
# @option options [Array] :enum The allowed values for the property.
|
38
|
+
# @option options [Object] :const The constant value for the property.
|
39
|
+
# @raise [ArgumentError] If the options are not in the VALID_OPTIONS constant.
|
40
|
+
# @raise [ArgumentError] If the property name is not a symbol.
|
41
|
+
# @raise [ArgumentError] If the property type is not a symbol.
|
42
|
+
# @raise [ArgumentError] If the type is unknown.
|
43
|
+
def self.validate!(property_name, type, options) # rubocop:disable Metrics/AbcSize
|
44
|
+
options.assert_valid_keys(VALID_OPTIONS)
|
45
|
+
raise ArgumentError, "Property must be a symbol" unless property_name.is_a?(Symbol)
|
46
|
+
raise ArgumentError, "Property type must be a symbol" unless type.is_a?(Symbol)
|
47
|
+
raise ArgumentError, "Unknown type #{type}" unless TYPE_VALIDATORS.key?(type)
|
48
|
+
|
49
|
+
validate_default(property_name, type, options[:default]) if options.key?(:default)
|
50
|
+
validate_enum(property_name, type, options[:enum]) if options.key?(:enum)
|
51
|
+
validate_const(property_name, type, options[:const]) if options.key?(:const)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Validates the default value for a property.
|
55
|
+
#
|
56
|
+
# @param property_name [Symbol] The name of the property being validated.
|
57
|
+
# @param type [Symbol] The type of the property.
|
58
|
+
# @param default [Object] The default value for the property.
|
59
|
+
def self.validate_default(property_name, type, default)
|
60
|
+
validate_value!(property_name, type, default, "default")
|
61
|
+
end
|
62
|
+
|
63
|
+
# Validates the allowed values for a property.
|
64
|
+
#
|
65
|
+
# @param property_name [Symbol] The name of the property being validated.
|
66
|
+
# @param type [Symbol] The type of the property.
|
67
|
+
# @param enum [Array] The allowed values for the property.
|
68
|
+
# @raise [ArgumentError] If the enum is not an array.
|
69
|
+
def self.validate_enum(property_name, type, enum)
|
70
|
+
raise ArgumentError, "Enum for #{property_name} is not an array" unless enum.is_a?(Array)
|
71
|
+
|
72
|
+
enum.each { |value| validate_value!(property_name, type, value, "enum") }
|
73
|
+
end
|
74
|
+
|
75
|
+
# Validates the constant value for a property.
|
76
|
+
#
|
77
|
+
# @param property_name [Symbol] The name of the property being validated.
|
78
|
+
# @param type [Symbol] The type of the property.
|
79
|
+
# @param const [Object] The constant value for the property.
|
80
|
+
def self.validate_const(property_name, type, const)
|
81
|
+
validate_value!(property_name, type, const, "const")
|
82
|
+
end
|
83
|
+
|
84
|
+
# Validates a value based on its type and keyword.
|
85
|
+
#
|
86
|
+
# @param property_name [Symbol] The name of the property being validated.
|
87
|
+
# @param type [Symbol] The type of the property.
|
88
|
+
# @param value [Object] The value to be validated.
|
89
|
+
# @param keyword [String] The keyword being validated (e.g., "default", "enum").
|
90
|
+
# @raise [ArgumentError] If the value does not match the type.
|
91
|
+
def self.validate_value!(property_name, type, value, keyword)
|
92
|
+
validator = TYPE_VALIDATORS[type]
|
93
|
+
return if validator.call(value)
|
94
|
+
|
95
|
+
raise ArgumentError, "#{keyword.capitalize} value for #{property_name} does not match type #{type}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/concern"
|
4
|
+
require_relative "builder"
|
5
|
+
require_relative "schema_enhancer"
|
6
|
+
|
7
|
+
module Esquema
|
8
|
+
# The Esquema module provides functionality for building JSON schemas.
|
9
|
+
module Model
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
included do
|
13
|
+
# Returns the JSON schema for the model.
|
14
|
+
def self.json_schema
|
15
|
+
Esquema::Builder.new(self).build_schema.to_json
|
16
|
+
end
|
17
|
+
|
18
|
+
# Enhances the schema using the provided block.
|
19
|
+
def self.enhance_schema(&block)
|
20
|
+
schema_enhancements
|
21
|
+
enhancer = SchemaEnhancer.new(self, @schema_enhancements)
|
22
|
+
enhancer.instance_eval(&block)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the schema enhancements.
|
26
|
+
def self.schema_enhancements
|
27
|
+
@schema_enhancements ||= {}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|