zod_rails 0.3.0 → 0.3.2
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 +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +25 -4
- data/lib/tasks/zod_rails.rake +110 -0
- data/lib/zod_rails/generation/schema_builder.rb +19 -7
- data/lib/zod_rails/introspection/column_info.rb +8 -5
- data/lib/zod_rails/mapping/enum_mapper.rb +17 -3
- data/lib/zod_rails/mapping/type_mapper.rb +7 -3
- data/lib/zod_rails/mapping/validation_mapper.rb +39 -28
- data/lib/zod_rails/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1163af9def212c75de8a25cc2e35fda2bbef62b372b239cd7bface8a3ae56098
|
|
4
|
+
data.tar.gz: 9d397ad7adcd549b35d059157b209a2214c2a558310ff9e860f663cad1849c47
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d1bd6a4631656247e5a0a15faf4b2928cbc1b3e9d982086b661757b3ec8eed4eecaa0fa421561165238ba997afa9fe3f8788b616d407169da281a1b6cb5821e4
|
|
7
|
+
data.tar.gz: 10edec80377e583661f8741bac4bff3647992fbc35d5dd16117564eb1d802e7f955b731259d861458d2a8137fb3ea60d8f2837f3fdc1faf1b4530e24fad1c92b
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,37 @@ All notable changes to ZodRails are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.3.2 - 2026-08-03
|
|
8
|
+
|
|
9
|
+
### Upgrade Notes
|
|
10
|
+
|
|
11
|
+
- Upgrade from `0.3.0` or `0.3.1` immediately. Those releases omitted the Railtie's Rake task file from the packaged
|
|
12
|
+
gem, causing `Rails.application.load_tasks` and unrelated Rails tasks such as `db:migrate` and `assets:precompile`
|
|
13
|
+
to fail with `LoadError`.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Include `lib/tasks/zod_rails.rake` in the published gem.
|
|
18
|
+
- Verify the runtime manifest and task loading in the test suite.
|
|
19
|
+
|
|
20
|
+
## 0.3.1 - 2026-08-03
|
|
21
|
+
|
|
22
|
+
### Upgrade Notes
|
|
23
|
+
|
|
24
|
+
- Regenerate committed schemas after upgrading if your models contain PostgreSQL arrays, adapter-reported `:bigint`
|
|
25
|
+
columns, or `time` columns. Their generated wire schemas now match Rails payload shapes more closely.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Generate `z.array(...)` schemas for PostgreSQL array columns, with inclusion constraints on elements and
|
|
30
|
+
presence, length, default, and nullability constraints on the outer array.
|
|
31
|
+
- Map adapter-reported `:bigint` columns to the JSON number shape emitted by Rails.
|
|
32
|
+
- Validate Rails `time` payloads as ISO datetimes with timezone-offset support.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Run a generated-schema runtime contract against the pinned Zod version in CI.
|
|
37
|
+
|
|
7
38
|
## 0.3.0 - 2026-08-03
|
|
8
39
|
|
|
9
40
|
### Upgrade Notes
|
data/README.md
CHANGED
|
@@ -45,6 +45,9 @@ Add to your Gemfile:
|
|
|
45
45
|
gem "zod_rails"
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
Use `0.3.2` or newer. Versions `0.3.0` and `0.3.1` omitted the Railtie's Rake task file from the published gem and can
|
|
49
|
+
break Rails task loading.
|
|
50
|
+
|
|
48
51
|
Then run:
|
|
49
52
|
|
|
50
53
|
```bash
|
|
@@ -112,19 +115,24 @@ end
|
|
|
112
115
|
| Rails/DB Type | Zod Type |
|
|
113
116
|
|---------------|----------|
|
|
114
117
|
| `string`, `text` | `z.string()` |
|
|
115
|
-
| `integer` | `z.int()` |
|
|
118
|
+
| `integer`, `bigint` | `z.int()` |
|
|
116
119
|
| `float` | `z.number()` |
|
|
117
|
-
| `bigint` | `z.string()` (avoids JS `Number` overflow) |
|
|
118
120
|
| `decimal` | `z.string()` (preserves `BigDecimal` precision) |
|
|
119
121
|
| `boolean` | `z.boolean()` |
|
|
120
122
|
| `date` | `z.iso.date()` |
|
|
121
123
|
| `datetime`, `timestamp` | `z.iso.datetime({ offset: true })` |
|
|
122
124
|
| `json`, `jsonb` | `z.json()` |
|
|
123
125
|
| `uuid` | `z.uuid()` |
|
|
124
|
-
| `time` | `z.
|
|
126
|
+
| `time` | `z.iso.datetime({ offset: true })` |
|
|
125
127
|
| `binary` | `z.string()` |
|
|
126
128
|
| `enum` | `z.enum([...])` |
|
|
127
129
|
|
|
130
|
+
PostgreSQL array columns wrap the element mapping with `z.array(...)`. Nullability and input optionality apply to the
|
|
131
|
+
array itself, so a nullable `string[]` becomes `z.array(z.string()).nullable()`, while a non-null array with a database
|
|
132
|
+
default becomes `z.array(z.string())` in the response schema and `z.array(z.string()).optional()` in the input schema.
|
|
133
|
+
Rails inclusion validators on array attributes constrain each element, so `inclusion: { in: %w[a b] }` produces an
|
|
134
|
+
element enum inside the array.
|
|
135
|
+
|
|
128
136
|
## Validation Mappings
|
|
129
137
|
|
|
130
138
|
ZodRails introspects your model validations and maps them to Zod constraints:
|
|
@@ -346,7 +354,15 @@ For custom types not in the mapping table, ZodRails falls back to `z.unknown()`.
|
|
|
346
354
|
|
|
347
355
|
ZodRails generates a useful static approximation of database columns and unconditional model validations. It cannot reproduce validations that require database access, another attribute, or runtime model state. Conditional and context-specific validations, dynamic numericality values, uniqueness, and unsupported Ruby regular-expression modes are skipped.
|
|
348
356
|
|
|
349
|
-
The generated wire types must match your serializers.
|
|
357
|
+
The generated wire types must match your serializers. Rails emits integer and bigint attributes as JSON numbers, so
|
|
358
|
+
ZodRails maps both to `z.int()`. Zod intentionally rejects integers outside JavaScript's safe integer range; serialize
|
|
359
|
+
large identifiers as strings and provide an application-owned schema when values can exceed that range. Decimal values
|
|
360
|
+
map to strings to preserve `BigDecimal` precision. Review generated schemas when using custom serializers,
|
|
361
|
+
adapter-specific types, or custom ActiveRecord types.
|
|
362
|
+
|
|
363
|
+
PostgreSQL permits null elements and multidimensional values without exposing either constraint through ordinary
|
|
364
|
+
column metadata. ZodRails currently generates one-dimensional arrays with non-null elements. Use an application-owned
|
|
365
|
+
schema when a column intentionally stores null elements or nested arrays.
|
|
350
366
|
|
|
351
367
|
### Misconfigured model names
|
|
352
368
|
|
|
@@ -380,8 +396,13 @@ After checking out the repo:
|
|
|
380
396
|
```bash
|
|
381
397
|
bundle install
|
|
382
398
|
bundle exec rspec
|
|
399
|
+
bun install --frozen-lockfile
|
|
400
|
+
bun run test:contracts
|
|
383
401
|
```
|
|
384
402
|
|
|
403
|
+
The Bun contract test generates a TypeScript schema and parses representative Rails payload values with the pinned Zod
|
|
404
|
+
version. This complements the Ruby unit and golden tests by exercising the generated code at runtime.
|
|
405
|
+
|
|
385
406
|
## Contributing
|
|
386
407
|
|
|
387
408
|
1. Fork it
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :zod_rails do
|
|
4
|
+
desc "Generate Zod schemas for configured models (DRY_RUN=1 to preview only)"
|
|
5
|
+
task generate: :environment do
|
|
6
|
+
config = ZodRails.configuration
|
|
7
|
+
generator = ZodRails::Generator.new(output_dir: config.output_dir)
|
|
8
|
+
|
|
9
|
+
if config.models.empty?
|
|
10
|
+
puts "No models configured. Add models to ZodRails.configure { |c| c.models = ['User', 'Article'] }"
|
|
11
|
+
exit 1
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
resolution = ZodRails::ModelResolver.resolve(config.models)
|
|
15
|
+
|
|
16
|
+
unless resolution[:missing].empty?
|
|
17
|
+
puts "ZodRails: #{resolution[:missing].size} model(s) in config.models could not be loaded:"
|
|
18
|
+
resolution[:missing].each { |m| puts " - #{m}" }
|
|
19
|
+
puts ""
|
|
20
|
+
puts "Check the model names in config/initializers/zod_rails.rb."
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
unless resolution[:invalid].empty?
|
|
25
|
+
puts "ZodRails: configured constants must be concrete ActiveRecord models:"
|
|
26
|
+
resolution[:invalid].each { |model| puts " - #{model}" }
|
|
27
|
+
exit 1
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
models = resolution[:resolved]
|
|
31
|
+
|
|
32
|
+
if ENV["DRY_RUN"] == "1"
|
|
33
|
+
drift = generator.check(models)
|
|
34
|
+
if drift.empty?
|
|
35
|
+
puts "ZodRails (dry run): no changes."
|
|
36
|
+
else
|
|
37
|
+
puts "ZodRails (dry run): would update #{drift.size} file(s):"
|
|
38
|
+
drift.each { |d| puts " - #{d[:filename]} (#{d[:status]})" }
|
|
39
|
+
end
|
|
40
|
+
else
|
|
41
|
+
generated = generator.generate_all(models)
|
|
42
|
+
puts "Generated #{generated.size} schema file(s):"
|
|
43
|
+
generated.each { |f| puts " - #{f}" }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
desc "Check whether generated schemas match the current models (exits nonzero on drift)"
|
|
48
|
+
task check: :environment do
|
|
49
|
+
config = ZodRails.configuration
|
|
50
|
+
generator = ZodRails::Generator.new(output_dir: config.output_dir)
|
|
51
|
+
|
|
52
|
+
if config.models.empty?
|
|
53
|
+
puts "No models configured. Nothing to check."
|
|
54
|
+
exit 0
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
resolution = ZodRails::ModelResolver.resolve(config.models)
|
|
58
|
+
|
|
59
|
+
unless resolution[:missing].empty?
|
|
60
|
+
puts "ZodRails: #{resolution[:missing].size} model(s) in config.models could not be loaded:"
|
|
61
|
+
resolution[:missing].each { |m| puts " - #{m}" }
|
|
62
|
+
exit 1
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
unless resolution[:invalid].empty?
|
|
66
|
+
puts "ZodRails: configured constants must be concrete ActiveRecord models:"
|
|
67
|
+
resolution[:invalid].each { |model| puts " - #{model}" }
|
|
68
|
+
exit 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
drift = generator.check(resolution[:resolved])
|
|
72
|
+
|
|
73
|
+
if drift.empty?
|
|
74
|
+
puts "ZodRails: generated schemas are up to date."
|
|
75
|
+
exit 0
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
puts "ZodRails: #{drift.size} file(s) out of date:"
|
|
79
|
+
drift.each { |d| puts " - #{d[:filename]} (#{d[:status]})" }
|
|
80
|
+
puts ""
|
|
81
|
+
puts "Run `bin/rails zod_rails:generate` to update."
|
|
82
|
+
exit 1
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
desc "Generate Zod schema for a specific model"
|
|
86
|
+
task :generate_model, [:model_name] => :environment do |_t, args|
|
|
87
|
+
model_name = args[:model_name]
|
|
88
|
+
unless model_name
|
|
89
|
+
puts "Usage: rails zod_rails:generate_model[ModelName]"
|
|
90
|
+
exit 1
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
config = ZodRails.configuration
|
|
94
|
+
generator = ZodRails::Generator.new(output_dir: config.output_dir)
|
|
95
|
+
|
|
96
|
+
resolution = ZodRails::ModelResolver.resolve([model_name])
|
|
97
|
+
if resolution[:missing].any?
|
|
98
|
+
puts "ZodRails: model '#{model_name}' could not be loaded. Check the spelling."
|
|
99
|
+
exit 1
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
if resolution[:invalid].any?
|
|
103
|
+
puts "ZodRails: '#{model_name}' is not a concrete ActiveRecord model."
|
|
104
|
+
exit 1
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
filename = generator.generate_all(resolution[:resolved]).first
|
|
108
|
+
puts "Generated: #{filename}"
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -59,7 +59,7 @@ module ZodRails
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def string_array_inclusion(column, validations)
|
|
62
|
-
return nil
|
|
62
|
+
return nil if column.array || !STRING_TYPES.include?(column.type)
|
|
63
63
|
|
|
64
64
|
validations.find { |validation| string_array_inclusion?(validation) }
|
|
65
65
|
end
|
|
@@ -75,10 +75,11 @@ module ZodRails
|
|
|
75
75
|
remaining = validations.reject { |validation| validation.equal?(inclusion) }
|
|
76
76
|
Mapping::EnumMapper.call(
|
|
77
77
|
inclusion.options[:in],
|
|
78
|
-
validation_chain: Mapping::ValidationMapper.call_all(remaining, base_type: :string),
|
|
78
|
+
validation_chain: Mapping::ValidationMapper.call_all(remaining, base_type: :string, array: column.array),
|
|
79
79
|
nullable: nullable?(column, validations),
|
|
80
80
|
input_schema: input_schema,
|
|
81
|
-
has_default: column.has_default
|
|
81
|
+
has_default: column.has_default,
|
|
82
|
+
array: column.array
|
|
82
83
|
)
|
|
83
84
|
end
|
|
84
85
|
|
|
@@ -86,10 +87,12 @@ module ZodRails
|
|
|
86
87
|
values = inspector.enums[column.name]
|
|
87
88
|
Mapping::EnumMapper.call(
|
|
88
89
|
values,
|
|
89
|
-
validation_chain: Mapping::ValidationMapper.call_all(validations, base_type: :string),
|
|
90
|
+
validation_chain: Mapping::ValidationMapper.call_all(validations, base_type: :string, array: column.array),
|
|
90
91
|
nullable: nullable?(column, validations),
|
|
91
92
|
input_schema: input_schema,
|
|
92
|
-
has_default: column.has_default
|
|
93
|
+
has_default: column.has_default,
|
|
94
|
+
array: column.array,
|
|
95
|
+
element_validation_chain: array_element_validation_chain(column, validations)
|
|
93
96
|
)
|
|
94
97
|
end
|
|
95
98
|
|
|
@@ -98,10 +101,12 @@ module ZodRails
|
|
|
98
101
|
column.type,
|
|
99
102
|
nullable: nullable?(column, validations),
|
|
100
103
|
input_schema: input_schema,
|
|
101
|
-
has_default: column.has_default
|
|
104
|
+
has_default: column.has_default,
|
|
105
|
+
array: column.array,
|
|
106
|
+
element_validation_chain: array_element_validation_chain(column, validations)
|
|
102
107
|
)
|
|
103
108
|
|
|
104
|
-
validation_chain = Mapping::ValidationMapper.call_all(validations, base_type: column.type)
|
|
109
|
+
validation_chain = Mapping::ValidationMapper.call_all(validations, base_type: column.type, array: column.array)
|
|
105
110
|
insert_validation_chain(base_type, validation_chain)
|
|
106
111
|
end
|
|
107
112
|
|
|
@@ -126,6 +131,13 @@ module ZodRails
|
|
|
126
131
|
end
|
|
127
132
|
end
|
|
128
133
|
|
|
134
|
+
def array_element_validation_chain(column, validations)
|
|
135
|
+
return "" unless column.array
|
|
136
|
+
|
|
137
|
+
element_validations = validations.select { |validation| validation.kind == :inclusion }
|
|
138
|
+
Mapping::ValidationMapper.call_all(element_validations, base_type: column.type)
|
|
139
|
+
end
|
|
140
|
+
|
|
129
141
|
def filtered_columns
|
|
130
142
|
inspector.columns.reject { |col| excluded_columns.include?(col.name) }
|
|
131
143
|
end
|
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
module ZodRails
|
|
4
4
|
module Introspection
|
|
5
5
|
class ColumnInfo
|
|
6
|
-
attr_reader :name, :type, :nullable, :has_default
|
|
6
|
+
attr_reader :name, :type, :nullable, :has_default, :array
|
|
7
7
|
|
|
8
|
-
def initialize(name:, type:, nullable:, has_default:)
|
|
8
|
+
def initialize(name:, type:, nullable:, has_default:, array: false)
|
|
9
9
|
@name = name
|
|
10
10
|
@type = type
|
|
11
11
|
@nullable = nullable
|
|
12
12
|
@has_default = has_default
|
|
13
|
+
@array = array
|
|
13
14
|
freeze
|
|
14
15
|
end
|
|
15
16
|
|
|
@@ -19,7 +20,8 @@ module ZodRails
|
|
|
19
20
|
type: column.type,
|
|
20
21
|
nullable: column.null,
|
|
21
22
|
has_default: !column.default.nil? ||
|
|
22
|
-
(column.respond_to?(:default_function) && !column.default_function.nil?)
|
|
23
|
+
(column.respond_to?(:default_function) && !column.default_function.nil?),
|
|
24
|
+
array: column.respond_to?(:array?) && column.array?
|
|
23
25
|
)
|
|
24
26
|
end
|
|
25
27
|
|
|
@@ -28,12 +30,13 @@ module ZodRails
|
|
|
28
30
|
name == other.name &&
|
|
29
31
|
type == other.type &&
|
|
30
32
|
nullable == other.nullable &&
|
|
31
|
-
has_default == other.has_default
|
|
33
|
+
has_default == other.has_default &&
|
|
34
|
+
array == other.array
|
|
32
35
|
end
|
|
33
36
|
alias eql? ==
|
|
34
37
|
|
|
35
38
|
def hash
|
|
36
|
-
[self.class, name, type, nullable, has_default].hash
|
|
39
|
+
[self.class, name, type, nullable, has_default, array].hash
|
|
37
40
|
end
|
|
38
41
|
end
|
|
39
42
|
end
|
|
@@ -3,16 +3,30 @@
|
|
|
3
3
|
module ZodRails
|
|
4
4
|
module Mapping
|
|
5
5
|
class EnumMapper
|
|
6
|
-
def self.call(values, nullable: false, input_schema: false,
|
|
6
|
+
def self.call(values, nullable: false, input_schema: false, array: false, **options)
|
|
7
|
+
has_default = options.fetch(:has_default, false)
|
|
8
|
+
validation_chain = options.fetch(:validation_chain, "")
|
|
9
|
+
element_validation_chain = options.fetch(:element_validation_chain, "")
|
|
7
10
|
names = values.is_a?(Hash) ? values.keys : values
|
|
8
11
|
quoted = names.map { |name| JSON.generate(name.to_s) }
|
|
9
12
|
enum = "z.enum([#{quoted.join(", ")}])"
|
|
10
|
-
base =
|
|
13
|
+
base = if array
|
|
14
|
+
array_schema(enum, element_validation_chain, validation_chain)
|
|
15
|
+
elsif validation_chain.empty?
|
|
16
|
+
enum
|
|
17
|
+
else
|
|
18
|
+
"z.string()#{validation_chain}.pipe(#{enum})"
|
|
19
|
+
end
|
|
11
20
|
|
|
12
21
|
suffix = determine_suffix(nullable: nullable, input_schema: input_schema, has_default: has_default)
|
|
13
22
|
"#{base}#{suffix}"
|
|
14
23
|
end
|
|
15
24
|
|
|
25
|
+
def self.array_schema(enum, element_validation_chain, array_validation_chain)
|
|
26
|
+
element = element_validation_chain.empty? ? enum : "z.string()#{element_validation_chain}.pipe(#{enum})"
|
|
27
|
+
"z.array(#{element})#{array_validation_chain}"
|
|
28
|
+
end
|
|
29
|
+
|
|
16
30
|
def self.determine_suffix(nullable:, input_schema:, has_default:)
|
|
17
31
|
return "" unless nullable || has_default
|
|
18
32
|
|
|
@@ -23,7 +37,7 @@ module ZodRails
|
|
|
23
37
|
end
|
|
24
38
|
end
|
|
25
39
|
|
|
26
|
-
private_class_method :determine_suffix
|
|
40
|
+
private_class_method :array_schema, :determine_suffix
|
|
27
41
|
end
|
|
28
42
|
end
|
|
29
43
|
end
|
|
@@ -7,25 +7,29 @@ module ZodRails
|
|
|
7
7
|
string: "z.string()",
|
|
8
8
|
text: "z.string()",
|
|
9
9
|
integer: "z.int()",
|
|
10
|
-
bigint: "z.
|
|
10
|
+
bigint: "z.int()",
|
|
11
11
|
float: "z.number()",
|
|
12
12
|
decimal: "z.string()",
|
|
13
13
|
boolean: "z.boolean()",
|
|
14
14
|
date: "z.iso.date()",
|
|
15
15
|
datetime: "z.iso.datetime({ offset: true })",
|
|
16
16
|
timestamp: "z.iso.datetime({ offset: true })",
|
|
17
|
-
time: "z.
|
|
17
|
+
time: "z.iso.datetime({ offset: true })",
|
|
18
18
|
json: "z.json()",
|
|
19
19
|
jsonb: "z.json()",
|
|
20
20
|
uuid: "z.uuid()",
|
|
21
21
|
binary: "z.string()"
|
|
22
22
|
}.freeze
|
|
23
23
|
|
|
24
|
-
def self.call(type, nullable: false, input_schema: false,
|
|
24
|
+
def self.call(type, nullable: false, input_schema: false, array: false, **options)
|
|
25
|
+
has_default = options.fetch(:has_default, false)
|
|
26
|
+
element_validation_chain = options.fetch(:element_validation_chain, "")
|
|
25
27
|
base = TYPE_MAP.fetch(type.to_sym) do
|
|
26
28
|
ZodRails.logger.warn("ZodRails: Unknown type '#{type}', falling back to z.unknown()")
|
|
27
29
|
"z.unknown()"
|
|
28
30
|
end
|
|
31
|
+
base = "#{base}#{element_validation_chain}"
|
|
32
|
+
base = "z.array(#{base})" if array
|
|
29
33
|
|
|
30
34
|
suffix = determine_suffix(nullable: nullable, input_schema: input_schema, has_default: has_default)
|
|
31
35
|
"#{base}#{suffix}"
|
|
@@ -13,35 +13,37 @@ module ZodRails
|
|
|
13
13
|
NUMERIC_ZOD_TYPES = %i[integer float].freeze
|
|
14
14
|
STRING_ZOD_TYPES = %i[string text].freeze
|
|
15
15
|
|
|
16
|
-
def self.call(validation, base_type:)
|
|
16
|
+
def self.call(validation, base_type:, array: false)
|
|
17
17
|
return "" if validation.conditional? || no_op_presence?(validation)
|
|
18
18
|
|
|
19
19
|
case validation.kind
|
|
20
|
-
when :presence then map_presence(validation, base_type)
|
|
21
|
-
when :length then map_length(validation, base_type)
|
|
22
|
-
when :numericality then map_numericality(validation, base_type)
|
|
23
|
-
when :format then map_format(validation, base_type)
|
|
24
|
-
when :inclusion then map_inclusion(validation, base_type)
|
|
20
|
+
when :presence then map_presence(validation, base_type, array)
|
|
21
|
+
when :length then map_length(validation, base_type, array)
|
|
22
|
+
when :numericality then map_numericality(validation, base_type, array)
|
|
23
|
+
when :format then map_format(validation, base_type, array)
|
|
24
|
+
when :inclusion then map_inclusion(validation, base_type, array)
|
|
25
25
|
else ""
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def self.call_all(validations, base_type:)
|
|
29
|
+
def self.call_all(validations, base_type:, array: false)
|
|
30
30
|
constraints = { min: nil, max: nil, length: nil, others: [] }
|
|
31
31
|
|
|
32
32
|
validations.each do |v|
|
|
33
|
-
collect_constraints(v, base_type, constraints)
|
|
33
|
+
collect_constraints(v, base_type, constraints, array)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
build_chain(constraints)
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def self.map_presence(_validation, base_type)
|
|
39
|
+
def self.map_presence(_validation, base_type, array)
|
|
40
|
+
return ".min(1)" if array
|
|
41
|
+
|
|
40
42
|
STRING_ZOD_TYPES.include?(base_type) ? ".min(1)#{presence_suffix}" : ""
|
|
41
43
|
end
|
|
42
44
|
|
|
43
|
-
def self.map_length(validation, base_type)
|
|
44
|
-
return "" unless STRING_ZOD_TYPES.include?(base_type)
|
|
45
|
+
def self.map_length(validation, base_type, array)
|
|
46
|
+
return "" unless array || STRING_ZOD_TYPES.include?(base_type)
|
|
45
47
|
|
|
46
48
|
parts = []
|
|
47
49
|
opts = validation.options
|
|
@@ -56,8 +58,8 @@ module ZodRails
|
|
|
56
58
|
parts.join
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
def self.map_numericality(validation, base_type)
|
|
60
|
-
return ""
|
|
61
|
+
def self.map_numericality(validation, base_type, array)
|
|
62
|
+
return "" if array || !NUMERIC_ZOD_TYPES.include?(base_type)
|
|
61
63
|
|
|
62
64
|
validation.options.filter_map do |key, value|
|
|
63
65
|
method = NUMERICALITY_MAP[key]
|
|
@@ -65,8 +67,8 @@ module ZodRails
|
|
|
65
67
|
end.join
|
|
66
68
|
end
|
|
67
69
|
|
|
68
|
-
def self.map_format(validation, base_type)
|
|
69
|
-
return ""
|
|
70
|
+
def self.map_format(validation, base_type, array)
|
|
71
|
+
return "" if array || !STRING_ZOD_TYPES.include?(base_type)
|
|
70
72
|
|
|
71
73
|
regex = validation.options[:with]
|
|
72
74
|
return "" unless regex
|
|
@@ -74,7 +76,9 @@ module ZodRails
|
|
|
74
76
|
RegexpMapper.call(regex)&.then { |expression| ".regex(#{expression})" } || ""
|
|
75
77
|
end
|
|
76
78
|
|
|
77
|
-
def self.map_inclusion(validation, base_type)
|
|
79
|
+
def self.map_inclusion(validation, base_type, array)
|
|
80
|
+
return "" if array
|
|
81
|
+
|
|
78
82
|
values = validation.options[:in] || validation.options[:within]
|
|
79
83
|
return "" unless values.is_a?(Array)
|
|
80
84
|
|
|
@@ -112,27 +116,35 @@ module ZodRails
|
|
|
112
116
|
".pipe(z.union([#{literals}]))"
|
|
113
117
|
end
|
|
114
118
|
|
|
115
|
-
def self.collect_constraints(validation, base_type, constraints)
|
|
119
|
+
def self.collect_constraints(validation, base_type, constraints, array)
|
|
116
120
|
return if validation.conditional? || no_op_presence?(validation)
|
|
121
|
+
return collect_array_constraints(validation, base_type, constraints) if array
|
|
117
122
|
|
|
118
123
|
case validation.kind
|
|
119
|
-
when :presence then handle_presence_constraint(base_type, constraints)
|
|
120
|
-
when :length then handle_length_constraint(validation, base_type, constraints)
|
|
124
|
+
when :presence then handle_presence_constraint(base_type, constraints, false)
|
|
125
|
+
when :length then handle_length_constraint(validation, base_type, constraints, false)
|
|
121
126
|
when :numericality then handle_numericality_constraint(validation, base_type, constraints)
|
|
122
127
|
when :format then handle_format_constraint(validation, base_type, constraints)
|
|
123
128
|
when :inclusion then handle_inclusion_constraint(validation, base_type, constraints)
|
|
124
129
|
end
|
|
125
130
|
end
|
|
126
131
|
|
|
127
|
-
def self.
|
|
128
|
-
|
|
132
|
+
def self.collect_array_constraints(validation, base_type, constraints)
|
|
133
|
+
case validation.kind
|
|
134
|
+
when :presence then handle_presence_constraint(base_type, constraints, true)
|
|
135
|
+
when :length then handle_length_constraint(validation, base_type, constraints, true)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def self.handle_presence_constraint(base_type, constraints, array)
|
|
140
|
+
return unless array || STRING_ZOD_TYPES.include?(base_type)
|
|
129
141
|
|
|
130
142
|
constraints[:min] = [constraints[:min] || 0, 1].max
|
|
131
|
-
constraints[:others] << presence_suffix
|
|
143
|
+
constraints[:others] << presence_suffix unless array
|
|
132
144
|
end
|
|
133
145
|
|
|
134
|
-
def self.handle_length_constraint(validation, base_type, constraints)
|
|
135
|
-
return unless STRING_ZOD_TYPES.include?(base_type)
|
|
146
|
+
def self.handle_length_constraint(validation, base_type, constraints, array)
|
|
147
|
+
return unless array || STRING_ZOD_TYPES.include?(base_type)
|
|
136
148
|
|
|
137
149
|
opts = validation.options
|
|
138
150
|
constraints[:length] = opts[:is] if opts[:is]
|
|
@@ -213,14 +225,13 @@ module ZodRails
|
|
|
213
225
|
value.is_a?(Numeric) && (!value.respond_to?(:finite?) || value.finite?)
|
|
214
226
|
end
|
|
215
227
|
|
|
216
|
-
def self.no_op_presence?(validation)
|
|
217
|
-
validation.kind == :presence && validation.options[:allow_blank]
|
|
218
|
-
end
|
|
228
|
+
def self.no_op_presence?(validation) = validation.kind == :presence && validation.options[:allow_blank]
|
|
219
229
|
|
|
220
230
|
private_class_method :map_presence, :map_length, :map_numericality, :map_format, :map_inclusion,
|
|
221
231
|
:build_array_inclusion_suffix, :string_array_for_string_type?,
|
|
222
232
|
:numeric_array_for_numeric_type?, :build_string_enum_suffix,
|
|
223
|
-
:build_numeric_literal_suffix, :collect_constraints, :
|
|
233
|
+
:build_numeric_literal_suffix, :collect_constraints, :collect_array_constraints,
|
|
234
|
+
:build_chain,
|
|
224
235
|
:handle_presence_constraint, :handle_length_constraint, :handle_format_constraint,
|
|
225
236
|
:handle_inclusion_constraint, :apply_range_inclusion, :apply_array_inclusion,
|
|
226
237
|
:handle_numericality_constraint, :apply_numeric_range, :presence_suffix, :static_number?,
|
data/lib/zod_rails/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: zod_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Matt Kelly
|
|
@@ -75,6 +75,7 @@ files:
|
|
|
75
75
|
- CHANGELOG.md
|
|
76
76
|
- LICENSE
|
|
77
77
|
- README.md
|
|
78
|
+
- lib/tasks/zod_rails.rake
|
|
78
79
|
- lib/zod_rails.rb
|
|
79
80
|
- lib/zod_rails/configuration.rb
|
|
80
81
|
- lib/zod_rails/generation/file_writer.rb
|