model-to-schema 0.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/.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
|