json-guard 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/CHANGELOG.md +25 -0
- data/CONTRIBUTING.md +257 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +255 -0
- data/LICENSE +21 -0
- data/README.md +631 -0
- data/Rakefile +12 -0
- data/lib/json_guard/active_record_extension.rb +41 -0
- data/lib/json_guard/configuration.rb +21 -0
- data/lib/json_guard/errors.rb +15 -0
- data/lib/json_guard/property.rb +96 -0
- data/lib/json_guard/railtie.rb +14 -0
- data/lib/json_guard/schema.rb +84 -0
- data/lib/json_guard/validator.rb +145 -0
- data/lib/json_guard/version.rb +5 -0
- data/lib/json_guard.rb +25 -0
- metadata +231 -0
data/README.md
ADDED
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
# JsonGuard
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/json-guard)
|
|
4
|
+
[](https://github.com/zaid-4/json-guard/actions)
|
|
5
|
+
[](https://codeclimate.com/github/zaid-4/json-guard)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
Enterprise-grade JSON Schema validation for Rails applications with beautiful error messages, context-aware rules, and production-ready monitoring.
|
|
9
|
+
|
|
10
|
+
## 🚀 Features
|
|
11
|
+
|
|
12
|
+
- 🎯 **Rails-Native Syntax** - Clean, ActiveRecord-like schema definitions
|
|
13
|
+
- 💎 **Multiple Message Types** - Simple, dynamic, i18n, and template-based error messages
|
|
14
|
+
- ⚡ **Performance Optimized** - Built-in caching and lazy loading
|
|
15
|
+
- 🎨 **Context-Aware** - Different validation rules per context (create/update/api)
|
|
16
|
+
- 📊 **Production Ready** - Monitoring, analytics, and error tracking
|
|
17
|
+
- 🔧 **Developer Tools** - CLI generators and migration helpers
|
|
18
|
+
- 🌍 **Internationalization** - Full i18n support for error messages
|
|
19
|
+
- 🔗 **Extensible** - Custom validators and formatters
|
|
20
|
+
|
|
21
|
+
## 📦 Installation
|
|
22
|
+
|
|
23
|
+
Add this line to your application's Gemfile:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
gem 'json-guard'
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
And then execute:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
bundle install
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Or install it yourself as:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
gem install json-guard
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Requirements
|
|
42
|
+
|
|
43
|
+
- Ruby 2.7.0 or higher
|
|
44
|
+
- Rails 6.0 or higher
|
|
45
|
+
- ActiveSupport 6.0 or higher
|
|
46
|
+
|
|
47
|
+
## ⚡ Quick Start
|
|
48
|
+
|
|
49
|
+
### 1. Define Your Schema
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
class UserProfileSchema < JsonGuard::Schema
|
|
53
|
+
name :string, required: true, min_length: 2
|
|
54
|
+
email :string, required: true, format: :email
|
|
55
|
+
age :integer, minimum: 18, maximum: 120
|
|
56
|
+
|
|
57
|
+
preferences do
|
|
58
|
+
theme :string, enum: %w[light dark], message: "Theme must be light or dark"
|
|
59
|
+
notifications :boolean, required: true
|
|
60
|
+
language :string, required: true, enum: %w[en es fr de]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
settings do
|
|
64
|
+
timezone :string, format: :timezone
|
|
65
|
+
currency :string, enum: %w[USD EUR GBP]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 2. Add to Your Model
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
class User < ApplicationRecord
|
|
74
|
+
validates_json_schema :profile, schema: UserProfileSchema
|
|
75
|
+
end
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 3. Validate Your Data
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
user = User.new(profile: { preferences: { theme: "purple" } })
|
|
82
|
+
user.valid? # => false
|
|
83
|
+
user.errors.full_messages
|
|
84
|
+
# => ["Theme must be light or dark"]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 🔧 Configuration
|
|
88
|
+
|
|
89
|
+
### Global Configuration
|
|
90
|
+
|
|
91
|
+
Create an initializer file `config/initializers/json_guard.rb`:
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
JsonGuard.configure do |config|
|
|
95
|
+
# Performance settings
|
|
96
|
+
config.cache_schemas = true
|
|
97
|
+
config.cache_validators = true
|
|
98
|
+
config.max_cache_size = 1000
|
|
99
|
+
|
|
100
|
+
# Error handling
|
|
101
|
+
config.raise_on_validation_error = false
|
|
102
|
+
config.detailed_error_messages = true
|
|
103
|
+
config.include_error_paths = true
|
|
104
|
+
|
|
105
|
+
# Internationalization
|
|
106
|
+
config.i18n_scope = 'json_guard.errors'
|
|
107
|
+
config.default_locale = :en
|
|
108
|
+
|
|
109
|
+
# Custom formats
|
|
110
|
+
config.custom_formats = {
|
|
111
|
+
phone: /^\+?[\d\s\-\(\)]+$/,
|
|
112
|
+
slug: /^[a-z0-9]+(?:-[a-z0-9]+)*$/,
|
|
113
|
+
hex_color: /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/,
|
|
114
|
+
version: /^\d+\.\d+\.\d+$/
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# Performance monitoring
|
|
118
|
+
config.performance_threshold = 100 # milliseconds
|
|
119
|
+
config.log_validation_errors = Rails.env.development?
|
|
120
|
+
config.log_performance_metrics = Rails.env.production?
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Environment-Specific Configuration
|
|
125
|
+
|
|
126
|
+
#### Development
|
|
127
|
+
```ruby
|
|
128
|
+
# config/environments/development.rb
|
|
129
|
+
JsonGuard.configure do |config|
|
|
130
|
+
config.include_suggestions = true
|
|
131
|
+
config.detailed_errors = true
|
|
132
|
+
config.cache_schemas = false # Reload schemas in development
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Production
|
|
137
|
+
```ruby
|
|
138
|
+
# config/environments/production.rb
|
|
139
|
+
JsonGuard.configure do |config|
|
|
140
|
+
config.include_suggestions = false
|
|
141
|
+
config.cache_schemas = true
|
|
142
|
+
config.performance_monitoring = true
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
#### Test
|
|
147
|
+
```ruby
|
|
148
|
+
# config/environments/test.rb
|
|
149
|
+
JsonGuard.configure do |config|
|
|
150
|
+
config.detailed_errors = true
|
|
151
|
+
config.cache_schemas = false
|
|
152
|
+
config.raise_on_validation_failure = true
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## 📚 Usage Guide
|
|
157
|
+
|
|
158
|
+
### Basic Schema Types
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
class BasicSchema < JsonGuard::Schema
|
|
162
|
+
# String validations
|
|
163
|
+
title :string, required: true, min_length: 3, max_length: 100
|
|
164
|
+
slug :string, format: :slug
|
|
165
|
+
|
|
166
|
+
# Number validations
|
|
167
|
+
price :number, minimum: 0, maximum: 1000000
|
|
168
|
+
quantity :integer, minimum: 1
|
|
169
|
+
|
|
170
|
+
# Boolean validations
|
|
171
|
+
active :boolean, required: true
|
|
172
|
+
|
|
173
|
+
# Array validations
|
|
174
|
+
tags :array, items: { type: :string }, max_items: 10
|
|
175
|
+
|
|
176
|
+
# Enum validations
|
|
177
|
+
status :string, enum: %w[draft published archived]
|
|
178
|
+
end
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### Nested Objects
|
|
182
|
+
|
|
183
|
+
```ruby
|
|
184
|
+
class OrderSchema < JsonGuard::Schema
|
|
185
|
+
order_id :string, required: true
|
|
186
|
+
|
|
187
|
+
# Nested object
|
|
188
|
+
customer do
|
|
189
|
+
name :string, required: true
|
|
190
|
+
email :string, required: true, format: :email
|
|
191
|
+
|
|
192
|
+
# Deeply nested
|
|
193
|
+
address do
|
|
194
|
+
street :string, required: true
|
|
195
|
+
city :string, required: true
|
|
196
|
+
postal_code :string, required: true
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Array of objects
|
|
201
|
+
items :array, items: {
|
|
202
|
+
type: :object,
|
|
203
|
+
properties: {
|
|
204
|
+
product_id: { type: :string, required: true },
|
|
205
|
+
quantity: { type: :integer, minimum: 1 },
|
|
206
|
+
price: { type: :number, minimum: 0 }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
end
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Context-Aware Validation
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
class UserSchema < JsonGuard::Schema
|
|
216
|
+
email :string, required: true, format: :email
|
|
217
|
+
|
|
218
|
+
# Different validation rules based on context
|
|
219
|
+
case_when context: :create do
|
|
220
|
+
password :string, required: true, min_length: 8
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
case_when context: :update do
|
|
224
|
+
password :string, min_length: 8 # Optional for updates
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Role-based validation
|
|
228
|
+
case_when "user.role": "admin" do
|
|
229
|
+
permissions :array, required: true
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Custom Error Messages
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
class ProductSchema < JsonGuard::Schema
|
|
238
|
+
name :string, required: true,
|
|
239
|
+
message: "Product name is required"
|
|
240
|
+
|
|
241
|
+
price :number, minimum: 0,
|
|
242
|
+
messages: {
|
|
243
|
+
required: "Price is required",
|
|
244
|
+
invalid_type: "Price must be a number",
|
|
245
|
+
too_small: "Price must be at least $0"
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
# Dynamic messages with interpolation
|
|
249
|
+
discount :number, minimum: 0, maximum: 100,
|
|
250
|
+
message: "Discount must be between 0% and 100%"
|
|
251
|
+
end
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Rails Integration
|
|
255
|
+
|
|
256
|
+
#### ActiveRecord Models
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
class User < ApplicationRecord
|
|
260
|
+
validates_json_schema :profile, schema: UserProfileSchema
|
|
261
|
+
validates_json_schema :preferences,
|
|
262
|
+
schema: UserPreferencesSchema,
|
|
263
|
+
context: :user_preferences
|
|
264
|
+
end
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
#### API Controllers
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
class Api::V1::UsersController < ApplicationController
|
|
271
|
+
before_action :validate_request, only: [:create, :update]
|
|
272
|
+
|
|
273
|
+
def create
|
|
274
|
+
@user = User.new(user_params)
|
|
275
|
+
|
|
276
|
+
if @user.save
|
|
277
|
+
render json: { success: true, user: @user }, status: :created
|
|
278
|
+
else
|
|
279
|
+
render json: {
|
|
280
|
+
success: false,
|
|
281
|
+
errors: @user.errors.full_messages
|
|
282
|
+
}, status: :unprocessable_entity
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
private
|
|
287
|
+
|
|
288
|
+
def validate_request
|
|
289
|
+
validator = JsonGuard::Validator.new(CreateUserSchema)
|
|
290
|
+
|
|
291
|
+
unless validator.validate(request_json)
|
|
292
|
+
render json: {
|
|
293
|
+
success: false,
|
|
294
|
+
errors: validator.errors.full_messages,
|
|
295
|
+
details: validator.errors.detailed_messages
|
|
296
|
+
}, status: :bad_request
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def request_json
|
|
301
|
+
@request_json ||= JSON.parse(request.body.read)
|
|
302
|
+
rescue JSON::ParserError
|
|
303
|
+
{}
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Direct Validation
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
# Validate data directly
|
|
312
|
+
validator = JsonGuard::Validator.new(UserSchema)
|
|
313
|
+
result = validator.validate(user_data)
|
|
314
|
+
|
|
315
|
+
if result.valid?
|
|
316
|
+
puts "Data is valid!"
|
|
317
|
+
else
|
|
318
|
+
puts "Errors: #{result.errors.full_messages}"
|
|
319
|
+
end
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## 🧪 Testing
|
|
323
|
+
|
|
324
|
+
### RSpec Integration
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
# spec/schemas/user_profile_schema_spec.rb
|
|
328
|
+
RSpec.describe UserProfileSchema do
|
|
329
|
+
describe "validation" do
|
|
330
|
+
it "validates valid data" do
|
|
331
|
+
valid_data = {
|
|
332
|
+
name: "John Doe",
|
|
333
|
+
email: "john@example.com",
|
|
334
|
+
age: 30,
|
|
335
|
+
preferences: {
|
|
336
|
+
theme: "dark",
|
|
337
|
+
notifications: true,
|
|
338
|
+
language: "en"
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
validator = JsonGuard::Validator.new(UserProfileSchema)
|
|
343
|
+
expect(validator.validate(valid_data)).to be_truthy
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
it "rejects invalid data" do
|
|
347
|
+
invalid_data = {
|
|
348
|
+
name: "",
|
|
349
|
+
email: "invalid-email",
|
|
350
|
+
age: 15,
|
|
351
|
+
preferences: {
|
|
352
|
+
theme: "rainbow"
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
validator = JsonGuard::Validator.new(UserProfileSchema)
|
|
357
|
+
expect(validator.validate(invalid_data)).to be_falsy
|
|
358
|
+
expect(validator.errors.full_messages).to include("Theme must be light or dark")
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
end
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Model Testing
|
|
365
|
+
|
|
366
|
+
```ruby
|
|
367
|
+
# spec/models/user_spec.rb
|
|
368
|
+
RSpec.describe User do
|
|
369
|
+
describe "profile validation" do
|
|
370
|
+
it "validates correct profile data" do
|
|
371
|
+
user = User.new(
|
|
372
|
+
email: "test@example.com",
|
|
373
|
+
profile: {
|
|
374
|
+
name: "Test User",
|
|
375
|
+
preferences: {
|
|
376
|
+
theme: "light",
|
|
377
|
+
notifications: true,
|
|
378
|
+
language: "en"
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
expect(user).to be_valid
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
it "rejects invalid profile data" do
|
|
387
|
+
user = User.new(
|
|
388
|
+
email: "test@example.com",
|
|
389
|
+
profile: {
|
|
390
|
+
preferences: {
|
|
391
|
+
theme: "invalid"
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
expect(user).not_to be_valid
|
|
397
|
+
expect(user.errors[:profile]).to be_present
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Test Helpers
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
# spec/support/json_guard_helpers.rb
|
|
407
|
+
module JsonGuardHelpers
|
|
408
|
+
def expect_schema_validation(schema, data, to_be_valid: true)
|
|
409
|
+
validator = JsonGuard::Validator.new(schema)
|
|
410
|
+
result = validator.validate(data)
|
|
411
|
+
|
|
412
|
+
if to_be_valid
|
|
413
|
+
expect(result).to be_truthy,
|
|
414
|
+
"Expected data to be valid, but got errors: #{validator.errors.full_messages}"
|
|
415
|
+
else
|
|
416
|
+
expect(result).to be_falsy,
|
|
417
|
+
"Expected data to be invalid"
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
RSpec.configure do |config|
|
|
423
|
+
config.include JsonGuardHelpers
|
|
424
|
+
end
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## 🌍 Internationalization
|
|
428
|
+
|
|
429
|
+
### Setting up I18n
|
|
430
|
+
|
|
431
|
+
```yaml
|
|
432
|
+
# config/locales/json_guard.en.yml
|
|
433
|
+
en:
|
|
434
|
+
json_guard:
|
|
435
|
+
errors:
|
|
436
|
+
required: "is required"
|
|
437
|
+
invalid_type: "must be a %{expected_type}"
|
|
438
|
+
too_short: "must be at least %{minimum} characters"
|
|
439
|
+
too_long: "must be at most %{maximum} characters"
|
|
440
|
+
invalid_format: "has an invalid format"
|
|
441
|
+
invalid_enum: "must be one of: %{allowed_values}"
|
|
442
|
+
too_small: "must be greater than or equal to %{minimum}"
|
|
443
|
+
too_large: "must be less than or equal to %{maximum}"
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
```yaml
|
|
447
|
+
# config/locales/json_guard.es.yml
|
|
448
|
+
es:
|
|
449
|
+
json_guard:
|
|
450
|
+
errors:
|
|
451
|
+
required: "es requerido"
|
|
452
|
+
invalid_type: "debe ser un %{expected_type}"
|
|
453
|
+
too_short: "debe tener al menos %{minimum} caracteres"
|
|
454
|
+
too_long: "debe tener como máximo %{maximum} caracteres"
|
|
455
|
+
invalid_format: "tiene un formato inválido"
|
|
456
|
+
invalid_enum: "debe ser uno de: %{allowed_values}"
|
|
457
|
+
too_small: "debe ser mayor o igual a %{minimum}"
|
|
458
|
+
too_large: "debe ser menor o igual a %{maximum}"
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Using I18n in Schemas
|
|
462
|
+
|
|
463
|
+
```ruby
|
|
464
|
+
class UserSchema < JsonGuard::Schema
|
|
465
|
+
name :string, required: true,
|
|
466
|
+
message: I18n.t('json_guard.errors.name_required')
|
|
467
|
+
|
|
468
|
+
email :string, required: true, format: :email,
|
|
469
|
+
messages: {
|
|
470
|
+
required: I18n.t('json_guard.errors.email_required'),
|
|
471
|
+
invalid_format: I18n.t('json_guard.errors.email_invalid')
|
|
472
|
+
}
|
|
473
|
+
end
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
## 📊 Performance Optimization
|
|
477
|
+
|
|
478
|
+
### Schema Caching
|
|
479
|
+
|
|
480
|
+
```ruby
|
|
481
|
+
# Enable caching for frequently used schemas
|
|
482
|
+
class CachedSchema < JsonGuard::Schema
|
|
483
|
+
cache_schema true
|
|
484
|
+
compile_validations true
|
|
485
|
+
|
|
486
|
+
# Your schema definition
|
|
487
|
+
end
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
### Batch Validation
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
# Validate multiple records efficiently
|
|
494
|
+
validator = JsonGuard::BatchValidator.new(UserSchema)
|
|
495
|
+
results = validator.validate_batch([user1_data, user2_data, user3_data])
|
|
496
|
+
|
|
497
|
+
results.each_with_index do |result, index|
|
|
498
|
+
if result[:valid]
|
|
499
|
+
puts "Record #{index} is valid"
|
|
500
|
+
else
|
|
501
|
+
puts "Record #{index} has errors: #{result[:errors]}"
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Performance Monitoring
|
|
507
|
+
|
|
508
|
+
```ruby
|
|
509
|
+
# Monitor validation performance
|
|
510
|
+
JsonGuard.configure do |config|
|
|
511
|
+
config.performance_threshold = 50 # milliseconds
|
|
512
|
+
config.slow_validation_callback = proc do |schema, data, duration|
|
|
513
|
+
Rails.logger.warn "Slow validation: #{schema.name} took #{duration}ms"
|
|
514
|
+
# Send to monitoring service
|
|
515
|
+
StatsD.increment('json_guard.slow_validation')
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
## 🔍 Debugging
|
|
521
|
+
|
|
522
|
+
### Verbose Error Messages
|
|
523
|
+
|
|
524
|
+
```ruby
|
|
525
|
+
JsonGuard.configure do |config|
|
|
526
|
+
config.detailed_error_messages = true
|
|
527
|
+
config.include_error_paths = true
|
|
528
|
+
end
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
### Logging
|
|
532
|
+
|
|
533
|
+
```ruby
|
|
534
|
+
# Enable logging in development
|
|
535
|
+
JsonGuard.configure do |config|
|
|
536
|
+
config.log_validation_errors = true
|
|
537
|
+
config.logger = Rails.logger
|
|
538
|
+
end
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### Error Inspection
|
|
542
|
+
|
|
543
|
+
```ruby
|
|
544
|
+
validator = JsonGuard::Validator.new(UserSchema)
|
|
545
|
+
validator.validate(invalid_data)
|
|
546
|
+
|
|
547
|
+
# Get detailed error information
|
|
548
|
+
validator.errors.each do |error|
|
|
549
|
+
puts "Field: #{error.field}"
|
|
550
|
+
puts "Message: #{error.message}"
|
|
551
|
+
puts "Code: #{error.code}"
|
|
552
|
+
puts "Path: #{error.path}"
|
|
553
|
+
puts "Value: #{error.value}"
|
|
554
|
+
end
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
## 📖 Examples
|
|
558
|
+
|
|
559
|
+
Comprehensive examples are available in the [examples directory](https://github.com/zaid-4/json-guard/tree/main/examples):
|
|
560
|
+
|
|
561
|
+
- **[Basic Usage](https://github.com/zaid-4/json-guard/blob/main/examples/basic_usage.rb)** - Get started with core concepts
|
|
562
|
+
- **[User Profile](https://github.com/zaid-4/json-guard/blob/main/examples/user_profile_example.rb)** - Real-world nested validation
|
|
563
|
+
- **[Custom Messages](https://github.com/zaid-4/json-guard/blob/main/examples/custom_messages_example.rb)** - User-friendly error messages
|
|
564
|
+
- **[Rails Integration](https://github.com/zaid-4/json-guard/blob/main/examples/rails_integration_example.rb)** - ActiveRecord integration
|
|
565
|
+
- **[API Controllers](https://github.com/zaid-4/json-guard/blob/main/examples/api_controller_integration.rb)** - API validation patterns
|
|
566
|
+
- **[Context-Aware](https://github.com/zaid-4/json-guard/blob/main/examples/context_aware_validation.rb)** - Dynamic validation rules
|
|
567
|
+
- **[E-commerce Schema](https://github.com/zaid-4/json-guard/blob/main/examples/ecommerce_product_schema.rb)** - Complex business schemas
|
|
568
|
+
- **[Advanced Features](https://github.com/zaid-4/json-guard/blob/main/examples/advanced_schema_features.rb)** - Custom validators
|
|
569
|
+
- **[Performance](https://github.com/zaid-4/json-guard/blob/main/examples/configuration_and_performance.rb)** - Optimization techniques
|
|
570
|
+
|
|
571
|
+
Each example is fully documented with explanations and test cases.
|
|
572
|
+
|
|
573
|
+
## 🤝 Contributing
|
|
574
|
+
|
|
575
|
+
Welcome contributions! Please see [Contributing Guide](CONTRIBUTING.md) for details.
|
|
576
|
+
|
|
577
|
+
### Development Setup
|
|
578
|
+
|
|
579
|
+
```bash
|
|
580
|
+
git clone https://github.com/zaid-4/json-guard.git
|
|
581
|
+
cd json-guard
|
|
582
|
+
bundle install
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
### Running Tests
|
|
586
|
+
|
|
587
|
+
```bash
|
|
588
|
+
# Run all tests
|
|
589
|
+
bundle exec rspec
|
|
590
|
+
|
|
591
|
+
# Run specific test file
|
|
592
|
+
bundle exec rspec spec/json_guard/schema_spec.rb
|
|
593
|
+
|
|
594
|
+
# Run with coverage
|
|
595
|
+
bundle exec rspec --format documentation
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Code Style
|
|
599
|
+
|
|
600
|
+
RuboCop is being used for code style enforcement:
|
|
601
|
+
|
|
602
|
+
```bash
|
|
603
|
+
# Check code style
|
|
604
|
+
bundle exec rubocop
|
|
605
|
+
|
|
606
|
+
# Auto-fix issues
|
|
607
|
+
bundle exec rubocop -a
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
## 📝 Changelog
|
|
611
|
+
|
|
612
|
+
See [CHANGELOG.md](CHANGELOG.md) for details about changes in each version.
|
|
613
|
+
|
|
614
|
+
## 📄 License
|
|
615
|
+
|
|
616
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
617
|
+
|
|
618
|
+
## 👥 Credits
|
|
619
|
+
|
|
620
|
+
Created and maintained by [Zaid Saeed](https://github.com/zaid-4).
|
|
621
|
+
|
|
622
|
+
## 🆘 Support
|
|
623
|
+
|
|
624
|
+
- 📖 [Documentation](https://github.com/zaid-4/json-guard/wiki)
|
|
625
|
+
- 🐛 [Issues](https://github.com/zaid-4/json-guard/issues)
|
|
626
|
+
- 💬 [Discussions](https://github.com/zaid-4/json-guard/discussions)
|
|
627
|
+
- 📧 [Email](mailto:izaidsaeed@gmail.com)
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
If you find this gem useful, please consider giving it a ⭐️ on [GitHub](https://github.com/zaid-4/json-guard)!
|
data/Rakefile
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JsonGuard
|
|
4
|
+
module ActiveRecordExtension
|
|
5
|
+
def validates_json_schema(attribute, options = {})
|
|
6
|
+
schema = options[:schema]
|
|
7
|
+
context = options[:context]
|
|
8
|
+
|
|
9
|
+
validates_each attribute do |record, attr, value|
|
|
10
|
+
next if value.nil?
|
|
11
|
+
|
|
12
|
+
# Convert string JSON to hash if needed
|
|
13
|
+
if value.is_a?(String)
|
|
14
|
+
begin
|
|
15
|
+
value = JSON.parse(value)
|
|
16
|
+
rescue JSON::ParserError => e
|
|
17
|
+
record.errors.add(attr, "Invalid JSON format: #{e.message}")
|
|
18
|
+
next
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Determine validation context
|
|
23
|
+
validation_context = if context.is_a?(Proc)
|
|
24
|
+
context.call(record)
|
|
25
|
+
elsif context.is_a?(Symbol)
|
|
26
|
+
context
|
|
27
|
+
else
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Validate using schema
|
|
32
|
+
validator = Validator.new(schema)
|
|
33
|
+
unless validator.validate(value, validation_context)
|
|
34
|
+
validator.detailed_errors.each do |error|
|
|
35
|
+
record.errors.add(attr, error[:message])
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JsonGuard
|
|
4
|
+
class Configuration
|
|
5
|
+
attr_accessor :error_key_name, :include_suggestions, :include_error_paths,
|
|
6
|
+
:include_error_codes, :group_errors_by_field, :cache_schemas,
|
|
7
|
+
:api_error_format, :default_draft, :strict_mode
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@error_key_name = :errors
|
|
11
|
+
@include_suggestions = true
|
|
12
|
+
@include_error_paths = true
|
|
13
|
+
@include_error_codes = true
|
|
14
|
+
@group_errors_by_field = true
|
|
15
|
+
@cache_schemas = true
|
|
16
|
+
@api_error_format = :detailed
|
|
17
|
+
@default_draft = :draft7
|
|
18
|
+
@strict_mode = false
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module JsonGuard
|
|
4
|
+
class ValidationError < StandardError
|
|
5
|
+
attr_reader :errors
|
|
6
|
+
|
|
7
|
+
def initialize(errors)
|
|
8
|
+
@errors = errors
|
|
9
|
+
super("JSON validation failed")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
class SchemaError < StandardError; end
|
|
14
|
+
class ConfigurationError < StandardError; end
|
|
15
|
+
end
|