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.
data/README.md ADDED
@@ -0,0 +1,631 @@
1
+ # JsonGuard
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/json-guard.svg)](https://badge.fury.io/rb/json-guard)
4
+ [![Build Status](https://github.com/zaid-4/json-guard/workflows/CI/badge.svg)](https://github.com/zaid-4/json-guard/actions)
5
+ [![Code Climate](https://codeclimate.com/github/zaid-4/json-guard/badges/gpa.svg)](https://codeclimate.com/github/zaid-4/json-guard)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -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