validrb 0.5.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/CHANGELOG.md +99 -0
- data/CLAUDE.md +434 -0
- data/LICENSE +21 -0
- data/README.md +654 -0
- data/Rakefile +10 -0
- data/lib/validrb/constraints/base.rb +59 -0
- data/lib/validrb/constraints/enum.rb +33 -0
- data/lib/validrb/constraints/format.rb +63 -0
- data/lib/validrb/constraints/length.rb +72 -0
- data/lib/validrb/constraints/max.rb +43 -0
- data/lib/validrb/constraints/min.rb +43 -0
- data/lib/validrb/context.rb +41 -0
- data/lib/validrb/custom_type.rb +95 -0
- data/lib/validrb/errors.rb +122 -0
- data/lib/validrb/field.rb +346 -0
- data/lib/validrb/i18n.rb +88 -0
- data/lib/validrb/introspection.rb +206 -0
- data/lib/validrb/openapi.rb +642 -0
- data/lib/validrb/result.rb +89 -0
- data/lib/validrb/schema.rb +303 -0
- data/lib/validrb/serializer.rb +113 -0
- data/lib/validrb/types/array.rb +91 -0
- data/lib/validrb/types/base.rb +90 -0
- data/lib/validrb/types/boolean.rb +37 -0
- data/lib/validrb/types/date.rb +70 -0
- data/lib/validrb/types/datetime.rb +71 -0
- data/lib/validrb/types/decimal.rb +57 -0
- data/lib/validrb/types/discriminated_union.rb +74 -0
- data/lib/validrb/types/float.rb +46 -0
- data/lib/validrb/types/integer.rb +53 -0
- data/lib/validrb/types/literal.rb +43 -0
- data/lib/validrb/types/object.rb +52 -0
- data/lib/validrb/types/string.rb +29 -0
- data/lib/validrb/types/time.rb +69 -0
- data/lib/validrb/types/union.rb +75 -0
- data/lib/validrb/version.rb +5 -0
- data/lib/validrb.rb +55 -0
- data/validrb.gemspec +43 -0
- metadata +91 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: fb818549e6bca37a89242aee2e72b9f74c32cbafa3db4bb6cbaad060b93fc426
|
|
4
|
+
data.tar.gz: 0d7debd6603cbf1afb0bf664e1a3252de6c270bd2844ea8f2cd1406b198e4008
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 33da9e6eefcc7c7b4ab78d8785c7b12b0216f8f0677b4d7120ebec5056ba1b6a42030afa3c13618c03d4e22e47538da085bc0783db7973698dcec6d2b44fea20
|
|
7
|
+
data.tar.gz: 2845fbd1a754e91509692d671d5ff050e738cc8799625d34028acc77cf47a041861f80556b23cefb05206c58b56350c62643d540b2ad6e51d5cc2644fea1ce16
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.5.0] - 2024-01-15
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **OpenAPI 3.0 Generation** - Generate complete OpenAPI specs from schemas
|
|
12
|
+
- **OpenAPI Import** - Create Validrb schemas from OpenAPI/JSON Schema definitions
|
|
13
|
+
- `Validrb::OpenAPI::Generator` for building OpenAPI documents
|
|
14
|
+
- `Validrb::OpenAPI::PathBuilder` for defining API endpoints
|
|
15
|
+
- `Validrb::OpenAPI::Importer` for importing external schemas
|
|
16
|
+
- `Schema#to_openapi` method for single schema export
|
|
17
|
+
- Support for servers, paths, and component schemas in OpenAPI output
|
|
18
|
+
- JSON and YAML export formats
|
|
19
|
+
|
|
20
|
+
### Production Readiness
|
|
21
|
+
- Comprehensive README documentation
|
|
22
|
+
- CHANGELOG following Keep a Changelog format
|
|
23
|
+
- GitHub Actions CI workflow for Ruby 3.0-3.3
|
|
24
|
+
- Updated gemspec with full metadata
|
|
25
|
+
|
|
26
|
+
## [0.4.0] - 2024-01-15
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- **Literal types** - Exact value matching with `literal:` option
|
|
30
|
+
- **Refinements** - Custom validation predicates with `refine:` option
|
|
31
|
+
- **Validation context** - Pass request-level data through validation pipeline
|
|
32
|
+
- **Schema introspection** - `field_names`, `required_fields`, `optional_fields`, `to_schema_hash`
|
|
33
|
+
- **JSON Schema generation** - `to_json_schema` method for JSON Schema Draft-07 output
|
|
34
|
+
- **Custom type API** - `Validrb.define_type` DSL for creating custom types
|
|
35
|
+
- **Discriminated unions** - Schema selection based on discriminator field
|
|
36
|
+
- **Serialization** - `dump` and `to_json` methods for converting data to primitives
|
|
37
|
+
- Context support in transforms, preprocessors, refinements, and validators
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
- Field validation now accepts optional `context:` parameter
|
|
41
|
+
- Schema `safe_parse` and `parse` now accept optional `context:` parameter
|
|
42
|
+
- Validators can now receive context as second argument
|
|
43
|
+
|
|
44
|
+
## [0.3.0] - 2024-01-14
|
|
45
|
+
|
|
46
|
+
### Added
|
|
47
|
+
- **Preprocessing** - `preprocess:` option for transforming input before validation
|
|
48
|
+
- **Conditional validation** - `when:` and `unless:` options for conditional field validation
|
|
49
|
+
- **Union types** - Accept multiple types with `union:` option
|
|
50
|
+
- **Coercion modes** - Disable coercion per-field with `coerce: false`
|
|
51
|
+
- **I18n support** - Internationalized error messages with locale switching
|
|
52
|
+
- `Validrb::I18n` module with `add_translations`, `locale`, and `reset!`
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
- Validation pipeline now: preprocess -> coerce -> constraints -> transform
|
|
56
|
+
- Conditional fields are skipped when condition is false (treated as optional)
|
|
57
|
+
|
|
58
|
+
## [0.2.0] - 2024-01-13
|
|
59
|
+
|
|
60
|
+
### Added
|
|
61
|
+
- **Custom validators** - Cross-field validation with `validate` blocks
|
|
62
|
+
- **Custom error messages** - `message:` option for fields
|
|
63
|
+
- **Date/DateTime/Time types** - Full temporal type support with coercion
|
|
64
|
+
- **Decimal type** - BigDecimal support for precise numeric values
|
|
65
|
+
- **Schema composition** - `extend`, `merge`, `pick`, `omit`, `partial` methods
|
|
66
|
+
- **Unknown key handling** - `strict:` and `passthrough:` schema options
|
|
67
|
+
- **Transforms** - `transform:` option for post-validation transformation
|
|
68
|
+
- **Nullable fields** - `nullable:` option (distinct from `optional:`)
|
|
69
|
+
- ValidatorContext class for custom validators with `error` and `base_error` methods
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
- Schema now supports composition methods for building derived schemas
|
|
73
|
+
- Error paths now support nested structures correctly
|
|
74
|
+
|
|
75
|
+
## [0.1.0] - 2024-01-12
|
|
76
|
+
|
|
77
|
+
### Added
|
|
78
|
+
- Initial release
|
|
79
|
+
- **Core types** - string, integer, float, boolean, array, object
|
|
80
|
+
- **Type coercion** - Automatic conversion between compatible types
|
|
81
|
+
- **Constraints** - min, max, length, format, enum
|
|
82
|
+
- **Schema DSL** - `Validrb.schema` with `field` declarations
|
|
83
|
+
- **Parsing methods** - `parse` (raises) and `safe_parse` (returns Result)
|
|
84
|
+
- **Result types** - `Success` and `Failure` with data/errors
|
|
85
|
+
- **Error tracking** - Path-tracked errors with ErrorCollection
|
|
86
|
+
- **Named formats** - email, url, uuid, phone, alphanumeric, alpha, numeric, hex, slug
|
|
87
|
+
- **Nested validation** - Objects and arrays with item/schema validation
|
|
88
|
+
- **Field options** - optional, default, nullable
|
|
89
|
+
- Zero runtime dependencies
|
|
90
|
+
|
|
91
|
+
### Notes
|
|
92
|
+
- Requires Ruby >= 3.0
|
|
93
|
+
- Inspired by Pydantic (Python) and Zod (TypeScript)
|
|
94
|
+
|
|
95
|
+
[0.5.0]: https://github.com/validrb/validrb/compare/v0.4.0...v0.5.0
|
|
96
|
+
[0.4.0]: https://github.com/validrb/validrb/compare/v0.3.0...v0.4.0
|
|
97
|
+
[0.3.0]: https://github.com/validrb/validrb/compare/v0.2.0...v0.3.0
|
|
98
|
+
[0.2.0]: https://github.com/validrb/validrb/compare/v0.1.0...v0.2.0
|
|
99
|
+
[0.1.0]: https://github.com/validrb/validrb/releases/tag/v0.1.0
|
data/CLAUDE.md
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
# Validrb - Development Context
|
|
2
|
+
|
|
3
|
+
## Project Overview
|
|
4
|
+
|
|
5
|
+
Validrb is a Ruby schema validation library with type coercion, inspired by Pydantic (Python) and Zod (TypeScript). It provides a clean DSL for defining data schemas with automatic type coercion, constraint validation, and detailed error reporting.
|
|
6
|
+
|
|
7
|
+
## Current Status: Phase 5 Complete
|
|
8
|
+
|
|
9
|
+
All core functionality implemented and tested (719 tests passing).
|
|
10
|
+
|
|
11
|
+
## Architecture
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Validrb.schema { ... }
|
|
15
|
+
│
|
|
16
|
+
▼
|
|
17
|
+
Schema (DSL, parse/safe_parse, composition, introspection)
|
|
18
|
+
│
|
|
19
|
+
├── Validators (custom cross-field validation + context)
|
|
20
|
+
├── Serialization (dump/to_json)
|
|
21
|
+
│
|
|
22
|
+
▼
|
|
23
|
+
Field (preprocess → type → constraints → refinements → transform)
|
|
24
|
+
│
|
|
25
|
+
├── Conditional (when:/unless: with context)
|
|
26
|
+
├── Coercion modes (coerce: true/false)
|
|
27
|
+
├── Literal types (exact value matching)
|
|
28
|
+
├── Refinements (custom predicates)
|
|
29
|
+
│
|
|
30
|
+
├──────────────┬──────────────┐
|
|
31
|
+
▼ ▼ ▼
|
|
32
|
+
Types Constraints Context
|
|
33
|
+
(coerce→validate) (min/max/enum/format) (request context)
|
|
34
|
+
│
|
|
35
|
+
├── Union types
|
|
36
|
+
├── Discriminated unions
|
|
37
|
+
├── Custom types
|
|
38
|
+
│
|
|
39
|
+
▼
|
|
40
|
+
Result (Success/Failure + serialization)
|
|
41
|
+
Errors (path-tracked, I18n)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## File Structure
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
lib/
|
|
48
|
+
├── validrb.rb # Main entry point
|
|
49
|
+
└── validrb/
|
|
50
|
+
├── version.rb # VERSION = "0.5.0"
|
|
51
|
+
├── i18n.rb # I18n support for error messages
|
|
52
|
+
├── errors.rb # Error, ErrorCollection, ValidationError
|
|
53
|
+
├── result.rb # Success, Failure result types
|
|
54
|
+
├── context.rb # Validation context
|
|
55
|
+
├── field.rb # Field definition with all options
|
|
56
|
+
├── schema.rb # Schema class with DSL Builder + composition
|
|
57
|
+
├── custom_type.rb # Custom type DSL
|
|
58
|
+
├── introspection.rb # Schema/field introspection
|
|
59
|
+
├── serializer.rb # Serialization to primitives/JSON
|
|
60
|
+
├── openapi.rb # OpenAPI 3.0 generation and import
|
|
61
|
+
├── types/
|
|
62
|
+
│ ├── base.rb # Base type class + registry
|
|
63
|
+
│ ├── string.rb # String type
|
|
64
|
+
│ ├── integer.rb # Integer type
|
|
65
|
+
│ ├── float.rb # Float type
|
|
66
|
+
│ ├── boolean.rb # Boolean type
|
|
67
|
+
│ ├── array.rb # Array type with item validation
|
|
68
|
+
│ ├── object.rb # Object type for nested schemas
|
|
69
|
+
│ ├── date.rb # Date type
|
|
70
|
+
│ ├── datetime.rb # DateTime type
|
|
71
|
+
│ ├── time.rb # Time type
|
|
72
|
+
│ ├── decimal.rb # Decimal type (BigDecimal)
|
|
73
|
+
│ ├── union.rb # Union type (multiple types)
|
|
74
|
+
│ ├── literal.rb # Literal type (exact values)
|
|
75
|
+
│ └── discriminated_union.rb # Discriminated union type
|
|
76
|
+
└── constraints/
|
|
77
|
+
├── base.rb # Base constraint class + registry
|
|
78
|
+
├── min.rb # Minimum value/length
|
|
79
|
+
├── max.rb # Maximum value/length
|
|
80
|
+
├── length.rb # Exact/range/min/max length
|
|
81
|
+
├── format.rb # Regex or named formats
|
|
82
|
+
└── enum.rb # Value in allowed list
|
|
83
|
+
|
|
84
|
+
spec/
|
|
85
|
+
├── spec_helper.rb
|
|
86
|
+
├── validrb_spec.rb
|
|
87
|
+
├── validrb/
|
|
88
|
+
│ ├── errors_spec.rb
|
|
89
|
+
│ ├── result_spec.rb
|
|
90
|
+
│ ├── field_spec.rb
|
|
91
|
+
│ ├── schema_spec.rb
|
|
92
|
+
│ ├── i18n_spec.rb
|
|
93
|
+
│ ├── context_spec.rb
|
|
94
|
+
│ ├── custom_type_spec.rb
|
|
95
|
+
│ ├── serializer_spec.rb
|
|
96
|
+
│ ├── introspection_spec.rb
|
|
97
|
+
│ ├── openapi_spec.rb
|
|
98
|
+
│ ├── types/*.rb
|
|
99
|
+
│ └── constraints/*.rb
|
|
100
|
+
└── integration/
|
|
101
|
+
├── basic_schema_spec.rb
|
|
102
|
+
├── nested_schema_spec.rb
|
|
103
|
+
├── phase2_features_spec.rb
|
|
104
|
+
├── phase3_features_spec.rb
|
|
105
|
+
├── phase4_features_spec.rb
|
|
106
|
+
└── phase4_edge_cases_spec.rb
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## API Reference
|
|
110
|
+
|
|
111
|
+
### Schema Definition
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
schema = Validrb.schema do
|
|
115
|
+
# Basic fields
|
|
116
|
+
field :name, :string
|
|
117
|
+
field :age, :integer, optional: true
|
|
118
|
+
field :role, :string, default: "user"
|
|
119
|
+
|
|
120
|
+
# Constraints
|
|
121
|
+
field :email, :string, format: :email
|
|
122
|
+
field :bio, :string, min: 10, max: 500
|
|
123
|
+
field :status, :string, enum: %w[active inactive]
|
|
124
|
+
|
|
125
|
+
# Preprocessing (runs BEFORE validation)
|
|
126
|
+
field :username, :string, preprocess: ->(v) { v.strip.downcase }
|
|
127
|
+
|
|
128
|
+
# Transform (runs AFTER validation)
|
|
129
|
+
field :tags, :string, transform: ->(v) { v.split(",") }
|
|
130
|
+
|
|
131
|
+
# Nullable (accepts nil)
|
|
132
|
+
field :deleted_at, :datetime, nullable: true
|
|
133
|
+
|
|
134
|
+
# Union types (accepts multiple types)
|
|
135
|
+
field :id, :string, union: [:integer, :string]
|
|
136
|
+
|
|
137
|
+
# Literal types (exact values only)
|
|
138
|
+
field :priority, :integer, literal: [1, 2, 3]
|
|
139
|
+
|
|
140
|
+
# Refinements (custom predicates)
|
|
141
|
+
field :password, :string,
|
|
142
|
+
refine: [
|
|
143
|
+
{ check: ->(v) { v.length >= 8 }, message: "must be 8+ chars" },
|
|
144
|
+
{ check: ->(v) { v.match?(/\d/) }, message: "must contain digit" }
|
|
145
|
+
]
|
|
146
|
+
|
|
147
|
+
# Context-aware refinement
|
|
148
|
+
field :amount, :decimal,
|
|
149
|
+
refine: ->(v, ctx) { ctx.nil? || v <= ctx[:max_amount] }
|
|
150
|
+
|
|
151
|
+
# Disable coercion
|
|
152
|
+
field :count, :integer, coerce: false
|
|
153
|
+
|
|
154
|
+
# Conditional validation (supports context)
|
|
155
|
+
field :company, :string, when: ->(d, ctx) { d[:account_type] == "business" }
|
|
156
|
+
field :personal_id, :string, unless: :is_company
|
|
157
|
+
|
|
158
|
+
# Custom error message
|
|
159
|
+
field :note, :string, min: 8, message: "must be at least 8 characters"
|
|
160
|
+
|
|
161
|
+
# Nested schema
|
|
162
|
+
field :address, :object, schema: AddressSchema
|
|
163
|
+
|
|
164
|
+
# Typed arrays
|
|
165
|
+
field :scores, :array, of: :integer
|
|
166
|
+
|
|
167
|
+
# Discriminated union
|
|
168
|
+
field :payment, :discriminated_union,
|
|
169
|
+
discriminator: :method,
|
|
170
|
+
mapping: { "card" => CardSchema, "paypal" => PaypalSchema }
|
|
171
|
+
|
|
172
|
+
# Custom validators (support context)
|
|
173
|
+
validate do |data, ctx|
|
|
174
|
+
if ctx && ctx[:restricted] && data[:amount] > 100
|
|
175
|
+
error(:amount, "exceeds limit in restricted mode")
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Schema Options
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
# Strict mode - reject unknown keys
|
|
185
|
+
Validrb.schema(strict: true) { ... }
|
|
186
|
+
|
|
187
|
+
# Passthrough mode - keep unknown keys
|
|
188
|
+
Validrb.schema(passthrough: true) { ... }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Field Options Reference
|
|
192
|
+
|
|
193
|
+
| Option | Type | Description |
|
|
194
|
+
|--------|------|-------------|
|
|
195
|
+
| `optional` | Boolean | Field can be missing (default: false) |
|
|
196
|
+
| `nullable` | Boolean | Field accepts nil value (default: false) |
|
|
197
|
+
| `default` | Any/Proc | Default value when missing |
|
|
198
|
+
| `message` | String | Custom error message |
|
|
199
|
+
| `preprocess` | Proc | Transform input BEFORE validation |
|
|
200
|
+
| `transform` | Proc | Transform value AFTER validation |
|
|
201
|
+
| `coerce` | Boolean | Enable type coercion (default: true) |
|
|
202
|
+
| `when` | Proc/Symbol | Only validate if condition is true |
|
|
203
|
+
| `unless` | Proc/Symbol | Only validate if condition is false |
|
|
204
|
+
| `union` | Array | Accept any of these types |
|
|
205
|
+
| `literal` | Array | Accept only these exact values |
|
|
206
|
+
| `refine` | Proc/Array | Custom validation predicates |
|
|
207
|
+
| `min` | Numeric | Minimum value/length |
|
|
208
|
+
| `max` | Numeric | Maximum value/length |
|
|
209
|
+
| `length` | Int/Range/Hash | Length constraint |
|
|
210
|
+
| `format` | Symbol/Regexp | Format validation |
|
|
211
|
+
| `enum` | Array | Allowed values |
|
|
212
|
+
| `of` | Symbol | Item type for arrays |
|
|
213
|
+
| `schema` | Schema | Nested schema for objects |
|
|
214
|
+
| `discriminator` | Symbol | Field for discriminated union |
|
|
215
|
+
| `mapping` | Hash | Schemas for discriminated union |
|
|
216
|
+
|
|
217
|
+
### Type Coercion Rules
|
|
218
|
+
|
|
219
|
+
| Type | Accepts | Coerces From |
|
|
220
|
+
|------|---------|--------------|
|
|
221
|
+
| `:string` | String | Symbol, Numeric |
|
|
222
|
+
| `:integer` | Integer | String, Float (whole) |
|
|
223
|
+
| `:float` | Float | String, Integer |
|
|
224
|
+
| `:boolean` | true/false | "true"/"false", "yes"/"no", 1/0, etc. |
|
|
225
|
+
| `:array` | Array | (validates items with `of:`) |
|
|
226
|
+
| `:object` | Hash | (validates with `schema:`) |
|
|
227
|
+
| `:date` | Date | ISO8601 String, Time, DateTime, timestamp |
|
|
228
|
+
| `:datetime` | DateTime | ISO8601 String, Time, Date, timestamp |
|
|
229
|
+
| `:time` | Time | ISO8601 String, DateTime, Date, timestamp |
|
|
230
|
+
| `:decimal` | BigDecimal | String, Integer, Float, Rational |
|
|
231
|
+
| `:union` | Any matching | Tries each type in order |
|
|
232
|
+
| `:literal` | Exact value | No coercion, exact match only |
|
|
233
|
+
| `:discriminated_union` | Object | Selects schema by discriminator field |
|
|
234
|
+
|
|
235
|
+
### Named Formats
|
|
236
|
+
|
|
237
|
+
`:email`, `:url`, `:uuid`, `:phone`, `:alphanumeric`, `:alpha`, `:numeric`, `:hex`, `:slug`
|
|
238
|
+
|
|
239
|
+
### Schema Composition
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
# Extend with additional fields
|
|
243
|
+
UserSchema = BaseSchema.extend { field :name, :string }
|
|
244
|
+
|
|
245
|
+
# Pick specific fields
|
|
246
|
+
PublicSchema = FullSchema.pick(:id, :name)
|
|
247
|
+
|
|
248
|
+
# Omit fields
|
|
249
|
+
SafeSchema = FullSchema.omit(:password)
|
|
250
|
+
|
|
251
|
+
# Merge schemas
|
|
252
|
+
MergedSchema = Schema1.merge(Schema2)
|
|
253
|
+
|
|
254
|
+
# Make all fields optional
|
|
255
|
+
UpdateSchema = CreateSchema.partial
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### I18n Configuration
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
# Add custom translations
|
|
262
|
+
Validrb::I18n.add_translations(:en, required: "cannot be blank")
|
|
263
|
+
|
|
264
|
+
# Change locale
|
|
265
|
+
Validrb::I18n.locale = :es
|
|
266
|
+
|
|
267
|
+
# Add translations for other locales
|
|
268
|
+
Validrb::I18n.add_translations(:es, required: "es requerido")
|
|
269
|
+
|
|
270
|
+
# Use Rails I18n backend
|
|
271
|
+
Validrb::I18n.backend = :rails
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Validation Context
|
|
275
|
+
|
|
276
|
+
```ruby
|
|
277
|
+
# Create a context
|
|
278
|
+
ctx = Validrb.context(user_id: 123, is_admin: true, max_amount: 1000)
|
|
279
|
+
|
|
280
|
+
# Pass context to parse
|
|
281
|
+
result = schema.safe_parse(data, context: ctx)
|
|
282
|
+
|
|
283
|
+
# Context is available in refinements, conditions, transforms, and validators
|
|
284
|
+
field :amount, :decimal, refine: ->(v, ctx) { v <= ctx[:max_amount] }
|
|
285
|
+
field :admin_only, :string, when: ->(data, ctx) { ctx[:is_admin] }
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Custom Types
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
# Define a custom type
|
|
292
|
+
Validrb.define_type(:money) do
|
|
293
|
+
coerce { |v| BigDecimal(v.to_s.gsub(/[$,]/, "")) }
|
|
294
|
+
validate { |v| v >= 0 }
|
|
295
|
+
error_message { "must be a valid money amount" }
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Use in schemas
|
|
299
|
+
field :price, :money
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Schema Introspection
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
# Field inspection
|
|
306
|
+
schema.field_names # => [:id, :name, :email]
|
|
307
|
+
schema.required_fields # => [:id, :name]
|
|
308
|
+
schema.optional_fields # => [:age]
|
|
309
|
+
schema.fields_with_defaults # => [:role]
|
|
310
|
+
schema.conditional_fields # => [:company]
|
|
311
|
+
|
|
312
|
+
# Field details
|
|
313
|
+
field = schema.field(:name)
|
|
314
|
+
field.constraint_values # => { min: 1, max: 100 }
|
|
315
|
+
field.has_constraint?(Validrb::Constraints::Min) # => true
|
|
316
|
+
|
|
317
|
+
# Generate JSON Schema
|
|
318
|
+
schema.to_json_schema # => { "$schema": "...", "type": "object", ... }
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### Serialization
|
|
322
|
+
|
|
323
|
+
```ruby
|
|
324
|
+
# Parse and serialize to hash with primitives
|
|
325
|
+
result = schema.safe_parse(data)
|
|
326
|
+
result.dump # => { "name" => "John", "date" => "2024-01-15" }
|
|
327
|
+
result.dump(format: :json) # => '{"name":"John","date":"2024-01-15"}'
|
|
328
|
+
result.to_json # Same as dump(format: :json)
|
|
329
|
+
|
|
330
|
+
# Schema-level serialization
|
|
331
|
+
schema.dump(data) # Parse + serialize (raises on error)
|
|
332
|
+
schema.safe_dump(data) # Parse + serialize (returns Result)
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Validation Flow
|
|
336
|
+
|
|
337
|
+
```
|
|
338
|
+
Input Value
|
|
339
|
+
│
|
|
340
|
+
▼
|
|
341
|
+
┌─────────────────┐
|
|
342
|
+
│ Conditional? │──No──┐
|
|
343
|
+
│ (when:/unless:) │ │
|
|
344
|
+
└────────┬────────┘ │
|
|
345
|
+
│Yes │
|
|
346
|
+
▼ │
|
|
347
|
+
┌─────────────────┐ │
|
|
348
|
+
│ Should validate?│──No──┼──► Skip (return nil)
|
|
349
|
+
└────────┬────────┘ │
|
|
350
|
+
│Yes │
|
|
351
|
+
▼ │
|
|
352
|
+
┌─────────────────┐ │
|
|
353
|
+
│ Preprocess │◄─────┘
|
|
354
|
+
│ (before coerce) │
|
|
355
|
+
└────────┬────────┘
|
|
356
|
+
│
|
|
357
|
+
▼
|
|
358
|
+
┌─────────────────┐
|
|
359
|
+
│ Type Coercion │──► coerce: false? → Type Check Only
|
|
360
|
+
│ (if enabled) │
|
|
361
|
+
└────────┬────────┘
|
|
362
|
+
│
|
|
363
|
+
▼
|
|
364
|
+
┌─────────────────┐
|
|
365
|
+
│ Constraints │
|
|
366
|
+
│ (min/max/etc) │
|
|
367
|
+
└────────┬────────┘
|
|
368
|
+
│
|
|
369
|
+
▼
|
|
370
|
+
┌─────────────────┐
|
|
371
|
+
│ Transform │
|
|
372
|
+
│ (after valid) │
|
|
373
|
+
└────────┬────────┘
|
|
374
|
+
│
|
|
375
|
+
▼
|
|
376
|
+
Output Value
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Running Tests
|
|
380
|
+
|
|
381
|
+
```bash
|
|
382
|
+
bundle install
|
|
383
|
+
bundle exec rspec # Run all tests
|
|
384
|
+
bundle exec rspec --format doc # Verbose output
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Version History
|
|
388
|
+
|
|
389
|
+
### Phase 1 (v0.1.0)
|
|
390
|
+
- Basic types (string, integer, float, boolean, array, object)
|
|
391
|
+
- Constraints (min, max, length, format, enum)
|
|
392
|
+
- Schema DSL, type coercion, nested validation
|
|
393
|
+
- Error path tracking, parse/safe_parse
|
|
394
|
+
|
|
395
|
+
### Phase 2 (v0.2.0)
|
|
396
|
+
- Custom validators (cross-field validation)
|
|
397
|
+
- Custom error messages
|
|
398
|
+
- Date/DateTime/Time types
|
|
399
|
+
- Decimal type (BigDecimal)
|
|
400
|
+
- Schema composition (extend, merge, pick, omit, partial)
|
|
401
|
+
- Unknown keys handling (strict/passthrough)
|
|
402
|
+
- Transforms (post-validation)
|
|
403
|
+
- Nullable fields
|
|
404
|
+
|
|
405
|
+
### Phase 3 (v0.3.0)
|
|
406
|
+
- Preprocessing (pre-validation transformation)
|
|
407
|
+
- Conditional validation (when:/unless:)
|
|
408
|
+
- Union types (multiple type acceptance)
|
|
409
|
+
- Coercion modes (disable per-field)
|
|
410
|
+
- I18n support (internationalized errors)
|
|
411
|
+
|
|
412
|
+
### Phase 4 (v0.4.0)
|
|
413
|
+
- Literal types (exact value matching)
|
|
414
|
+
- Refinements (custom validation predicates)
|
|
415
|
+
- Validation context (request-level data)
|
|
416
|
+
- Schema introspection (field inspection, JSON Schema generation)
|
|
417
|
+
- Custom type API (define_type DSL)
|
|
418
|
+
- Discriminated unions (type selection by discriminator)
|
|
419
|
+
- Serialization (dump to primitives/JSON)
|
|
420
|
+
|
|
421
|
+
## Future Enhancements
|
|
422
|
+
|
|
423
|
+
- [ ] Async validators (database checks)
|
|
424
|
+
- [ ] Rails integration (ActiveModel compatibility)
|
|
425
|
+
- [ ] OpenAPI schema generation
|
|
426
|
+
- [ ] Dependent field validation DSL
|
|
427
|
+
|
|
428
|
+
## Code Conventions
|
|
429
|
+
|
|
430
|
+
- All files use `# frozen_string_literal: true`
|
|
431
|
+
- Zero runtime dependencies (bigdecimal is stdlib)
|
|
432
|
+
- Ruby >= 3.0 required
|
|
433
|
+
- RSpec for testing
|
|
434
|
+
- Objects are frozen/immutable after creation
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Validrb Contributors
|
|
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 all
|
|
13
|
+
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 THE
|
|
21
|
+
SOFTWARE.
|