schemacop 3.0.34 → 3.0.36
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 +23 -0
- data/README_V2.md +13 -13
- data/README_V3.md +55 -23
- data/VERSION +1 -1
- data/lib/schemacop/railtie.rb +7 -0
- data/lib/schemacop/v3/numeric_node.rb +28 -0
- data/schemacop.gemspec +3 -3
- data/test/unit/schemacop/v3/array_node_test.rb +1 -1
- data/test/unit/schemacop/v3/global_context_test.rb +98 -0
- data/test/unit/schemacop/v3/hash_node_test.rb +1 -1
- data/test/unit/schemacop/v3/number_node_test.rb +111 -0
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9494f9a5a921664fae74b99ecba0830775d160ae91250465b4b88ba1d1fdf970
|
|
4
|
+
data.tar.gz: 2263f93913342b872ac56c52ddd65dd6c34f34711d3624c52c6ffc2d5a9a516a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3712232ba9d867f723ee462d80db875d6e0adbd6cd0579d798908df58f9fd9b5448389e2cc9829fe979bdcd972efa209a4b2ae6a27766d8d7884fa3a7189c4ab
|
|
7
|
+
data.tar.gz: fd5c87120fb0c2d1a1e13122013b08d902d37a32154d1196385ad6f2fb5fec28b54c0170f129533c7ce116ccadd13c7c653832f2102cfc4c63da62257170ee3a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Change log
|
|
2
2
|
|
|
3
|
+
## 3.0.36 (2026-01-05)
|
|
4
|
+
|
|
5
|
+
* Fix `v3_default_options` not being applied when schemas are eager loaded in
|
|
6
|
+
production mode. The Railtie now explicitly loads `config/schemacop.rb`
|
|
7
|
+
during initialization, before eager loading schemas. This ensures that
|
|
8
|
+
default options like `cast_str` are applied to all schemas.
|
|
9
|
+
|
|
10
|
+
For Rails applications, place your Schemacop configuration in
|
|
11
|
+
`config/schemacop.rb` (not in the `initializers/` subdirectory) to benefit
|
|
12
|
+
from this fix. Configuration in `config/initializers/schemacop.rb` will
|
|
13
|
+
continue to work in development mode but will not be applied to eager-loaded
|
|
14
|
+
schemas in production mode (same behavior as in 3.0.35 and earlier).
|
|
15
|
+
|
|
16
|
+
Internal reference: `#144209`.
|
|
17
|
+
|
|
18
|
+
* Fix test compatibility with Ruby 3.2+ by updating JSON parse error regex
|
|
19
|
+
patterns to match the new error message format introduced in Ruby 3.2.
|
|
20
|
+
|
|
21
|
+
## 3.0.35 (2025-07-31)
|
|
22
|
+
|
|
23
|
+
* Add option `max_precision` to `number` nodes for Schemacop V3 schemas to validate
|
|
24
|
+
the maximum number of decimal places for `Float` and `BigDecimal` values
|
|
25
|
+
|
|
3
26
|
## 3.0.34 (2025-05-27)
|
|
4
27
|
|
|
5
28
|
* Adapt string format `number` to cast to `Integer` instead of to a `Float` if
|
data/README_V2.md
CHANGED
|
@@ -61,7 +61,7 @@ gem 'schemacop'
|
|
|
61
61
|
## Basics
|
|
62
62
|
|
|
63
63
|
Since there is no explicit typing in Ruby, it can be hard to make sure that a
|
|
64
|
-
method is
|
|
64
|
+
method is receiving exactly the right kind of data it needs. The idea of this
|
|
65
65
|
gem is to define a schema at boot time that will validate the data being passed
|
|
66
66
|
around at runtime. Those two steps look as follows:
|
|
67
67
|
|
|
@@ -84,7 +84,7 @@ my_schema.validate!(
|
|
|
84
84
|
`validate!` will fail if the data given to it does not match what was specified
|
|
85
85
|
in the schema.
|
|
86
86
|
|
|
87
|
-
### Type
|
|
87
|
+
### Type Lines vs. Field Lines
|
|
88
88
|
|
|
89
89
|
Schemacop uses a DSL (domain-specific language) to let you describe your
|
|
90
90
|
schemas. We distinguish between two kinds of identifiers:
|
|
@@ -309,7 +309,7 @@ end
|
|
|
309
309
|
You might find the notation cumbersome, and you'd be right to say so. Luckily
|
|
310
310
|
there are plenty of short forms available which we will see below.
|
|
311
311
|
|
|
312
|
-
#### Handling
|
|
312
|
+
#### Handling Hashes with Indifferent Access
|
|
313
313
|
|
|
314
314
|
Schemacop has special handling for objects of the class
|
|
315
315
|
`ActiveSupport::HashWithIndifferentAccess`: You may specify the keys as symbols
|
|
@@ -403,12 +403,12 @@ The following types are supported by Schemacop by default:
|
|
|
403
403
|
All types support the options `if` and `check` (see the section about Type Lines
|
|
404
404
|
above).
|
|
405
405
|
|
|
406
|
-
## Short
|
|
406
|
+
## Short Forms
|
|
407
407
|
|
|
408
408
|
For convenience, the following short forms may be used (and combined if
|
|
409
409
|
possible).
|
|
410
410
|
|
|
411
|
-
### Passing a
|
|
411
|
+
### Passing a Type to a Field Line or Schema
|
|
412
412
|
|
|
413
413
|
Instead of adding a Type Line in the block of a Field Line, you can omit `do
|
|
414
414
|
type ... end` and directly write the type after the key of the field.
|
|
@@ -444,7 +444,7 @@ Schema.new(:string, min: 2, max: 5)
|
|
|
444
444
|
This means that the data given to the schema must be a String that is between 2
|
|
445
445
|
and 5 characters long.
|
|
446
446
|
|
|
447
|
-
### Passing
|
|
447
|
+
### Passing Multiple Types at Once
|
|
448
448
|
|
|
449
449
|
You can specify several types at once by putting them in an array.
|
|
450
450
|
|
|
@@ -522,7 +522,7 @@ Note that this does not allow you to specify any options for the hash itself.
|
|
|
522
522
|
You still need to specify `:hash` as a type if you want to pass any options to
|
|
523
523
|
the hash (i.e. a `default`).
|
|
524
524
|
|
|
525
|
-
### Shortform for
|
|
525
|
+
### Shortform for Subtypes
|
|
526
526
|
|
|
527
527
|
In case of nested arrays, you can group all Type Lines to a single one.
|
|
528
528
|
|
|
@@ -638,14 +638,14 @@ Schema.new do
|
|
|
638
638
|
end
|
|
639
639
|
```
|
|
640
640
|
|
|
641
|
-
### Required
|
|
641
|
+
### Required Data Points
|
|
642
642
|
|
|
643
643
|
Note that any *required* validation is done before applying the defaults. If you
|
|
644
644
|
specify a `req` field, it must always be given, no matter if you have specified
|
|
645
645
|
a default or not. Therefore, specifying `req` fields do not make sense in
|
|
646
646
|
conjunction with defaults, as the default is always ignored.
|
|
647
647
|
|
|
648
|
-
## Type
|
|
648
|
+
## Type Casting
|
|
649
649
|
|
|
650
650
|
Starting from version 2.4.0, Schemacop allows you to specify type castings that
|
|
651
651
|
can alter the validated data. Consider the following:
|
|
@@ -663,7 +663,7 @@ Note that Schemacop never modifies the data you pass to it. If you want to
|
|
|
663
663
|
benefit from Schemacop-applied castings, you need to access the cloned, modified
|
|
664
664
|
data returned by `validate` or `validate!`.
|
|
665
665
|
|
|
666
|
-
### Specifying
|
|
666
|
+
### Specifying Type Castings
|
|
667
667
|
|
|
668
668
|
Type castings can be specified using two forms: Either as a hash or as an array.
|
|
669
669
|
While using an array only allows you to specify the supported source types to be
|
|
@@ -689,7 +689,7 @@ Schema.new do
|
|
|
689
689
|
end
|
|
690
690
|
```
|
|
691
691
|
|
|
692
|
-
### Built-in
|
|
692
|
+
### Built-in Casters
|
|
693
693
|
|
|
694
694
|
Schemacop comes with the following casters:
|
|
695
695
|
|
|
@@ -702,7 +702,7 @@ an error if casted to an Integer. When casting float values and strings
|
|
|
702
702
|
containing float values to integers, the decimal places will be discarded
|
|
703
703
|
however.
|
|
704
704
|
|
|
705
|
-
### Execution
|
|
705
|
+
### Execution Order
|
|
706
706
|
|
|
707
707
|
The casting is done *before* the options `if` and `check` are evaluated.
|
|
708
708
|
Example:
|
|
@@ -748,7 +748,7 @@ Schemacop will throw one of the following checked exceptions:
|
|
|
748
748
|
This exception is thrown when the given data does not comply with the given
|
|
749
749
|
schema definition.
|
|
750
750
|
|
|
751
|
-
## Known
|
|
751
|
+
## Known Limitations
|
|
752
752
|
|
|
753
753
|
* Schemacop does not yet allow cyclic structures with infinite depth.
|
|
754
754
|
|
data/README_V3.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Schemacop
|
|
1
|
+
# Schemacop Schema V3
|
|
2
2
|
|
|
3
3
|
## Table of Contents
|
|
4
4
|
|
|
@@ -282,11 +282,11 @@ transformed into various types.
|
|
|
282
282
|
#### Custom Formats
|
|
283
283
|
|
|
284
284
|
You can also implement your custom formats or override the behavior of the
|
|
285
|
-
standard formats.
|
|
286
|
-
|
|
285
|
+
standard formats. For Rails applications, this can be done in
|
|
286
|
+
`config/schemacop.rb`:
|
|
287
287
|
|
|
288
288
|
```ruby
|
|
289
|
-
# config/
|
|
289
|
+
# config/schemacop.rb
|
|
290
290
|
Schemacop.register_string_formatter(
|
|
291
291
|
:character_array, # Formatter name
|
|
292
292
|
pattern: /^[a-zA-Z](,[a-zA-Z])*/, # Regex pattern for validation
|
|
@@ -424,7 +424,7 @@ and subclasses are valid:
|
|
|
424
424
|
* `Rational`
|
|
425
425
|
* `BigDecimal`
|
|
426
426
|
|
|
427
|
-
As some subclasses of `Numeric`, such as `Complex` don't support all required
|
|
427
|
+
As some subclasses of `Numeric`, such as `Complex` don't support all required operations,
|
|
428
428
|
only the above list is supported. If you need support for additional number classes, please
|
|
429
429
|
contact the Gem maintainers.
|
|
430
430
|
|
|
@@ -445,6 +445,10 @@ With the various available options, validations on the value of the number can b
|
|
|
445
445
|
* `multiple_of`
|
|
446
446
|
The received number has to be a multiple of the given number for the validation to
|
|
447
447
|
pass.
|
|
448
|
+
* `max_precision`
|
|
449
|
+
Defines the maximum number of digits after the decimal point for `Float` and
|
|
450
|
+
`BigDecimal` values. Must be a non-negative integer. When `nil` (default), no
|
|
451
|
+
precision validation is performed. Trailing zeros are ignored in the validation.
|
|
448
452
|
* `cast_str`
|
|
449
453
|
When set to `true`, this node also accepts strings that can be casted to a number, e.g.
|
|
450
454
|
the values `'0.1'` or `'3.1415'`. Please note that you can only validate numbers which
|
|
@@ -483,12 +487,27 @@ schema.validate!(nil) # => nil
|
|
|
483
487
|
schema.validate!('') # => nil
|
|
484
488
|
```
|
|
485
489
|
|
|
490
|
+
With `max_precision`:
|
|
491
|
+
|
|
492
|
+
```ruby
|
|
493
|
+
# Validates that the input is a number with maximum 2 decimal places
|
|
494
|
+
schema = Schemacop::Schema3.new(:number, max_precision: 2)
|
|
495
|
+
schema.validate!(42) # => 42
|
|
496
|
+
schema.validate!(42.5) # => 42.5
|
|
497
|
+
schema.validate!(42.52) # => 42.52
|
|
498
|
+
schema.validate!(42.523) # => Schemacop::Exceptions::ValidationError: /: Value must have a maximum precision of 2 digits after the decimal point.
|
|
499
|
+
schema.validate!(BigDecimal('3.14')) # => 0.314e1
|
|
500
|
+
schema.validate!(BigDecimal('3.141')) # => Schemacop::Exceptions::ValidationError: /: Value must have a maximum precision of 2 digits after the decimal point.
|
|
501
|
+
schema.validate!(BigDecimal('3.140')) # => 0.314e1 (trailing zeros are ignored)
|
|
502
|
+
schema.validate!(1r) # => (1/1) (rational numbers are not affected)
|
|
503
|
+
```
|
|
504
|
+
|
|
486
505
|
Please note, that `nil` and blank strings are treated equally when using the `cast_str` option,
|
|
487
506
|
and validating a blank string will return `nil`.
|
|
488
507
|
If you need a value, use the `required` option:
|
|
489
508
|
|
|
490
509
|
```ruby
|
|
491
|
-
schema = Schemacop::Schema3.new(:number, cast_str: true, minimum: 0.0, maximum: (50r), multiple_of: BigDecimal('0.5'),
|
|
510
|
+
schema = Schemacop::Schema3.new(:number, cast_str: true, minimum: 0.0, maximum: (50r), multiple_of: BigDecimal('0.5'), required: true)
|
|
492
511
|
schema.validate!('42.5') # => 42.5
|
|
493
512
|
schema.validate!(nil) # => Schemacop::Exceptions::ValidationError: /: Value must be given.
|
|
494
513
|
schema.validate!('') # => Schemacop::Exceptions::ValidationError: /: Value must be given.
|
|
@@ -617,7 +636,7 @@ It consists of one or multiple values, which can be validated using arbitrary no
|
|
|
617
636
|
must contain to pass the validation.
|
|
618
637
|
|
|
619
638
|
* `unique_items`
|
|
620
|
-
This option specifies
|
|
639
|
+
This option specifies whether the items in the array must all be distinct from
|
|
621
640
|
each other, or if there may be duplicate values. By default, this is false,
|
|
622
641
|
i.e. duplicate values are allowed
|
|
623
642
|
|
|
@@ -685,13 +704,13 @@ schema.validate!([1, 2, 3]) # => Schemacop::Exceptions::ValidationError: /: At l
|
|
|
685
704
|
schema.validate!([1, 3, 5]) # => [1, 3, 5]
|
|
686
705
|
```
|
|
687
706
|
|
|
688
|
-
#### Specifying
|
|
707
|
+
#### Specifying Properties
|
|
689
708
|
|
|
690
709
|
Array nodes support a block in which you can specify the required array contents.
|
|
691
710
|
The array nodes support either list validation, or tuple validation, depending on
|
|
692
711
|
how you specify your array contents.
|
|
693
712
|
|
|
694
|
-
##### List
|
|
713
|
+
##### List Validation
|
|
695
714
|
|
|
696
715
|
List validation validates a sequence of arbitrary length where each item matches
|
|
697
716
|
the same schema. Unless you specify a `min_items` count on the array node, an
|
|
@@ -740,7 +759,7 @@ end
|
|
|
740
759
|
# => Schemacop::Exceptions::InvalidSchemaError: You can only use "list" once.
|
|
741
760
|
```
|
|
742
761
|
|
|
743
|
-
##### Tuple
|
|
762
|
+
##### Tuple Validation
|
|
744
763
|
|
|
745
764
|
On the other hand, tuple validation validates a sequence of fixed length, where
|
|
746
765
|
each item has its own schema that it has to match. Here, the order of the items
|
|
@@ -831,8 +850,8 @@ Using the options `filter` and `reject`, arrays can be filtered. Filtering
|
|
|
831
850
|
happens before validation. Both options behave in the same way, with the only
|
|
832
851
|
difference being that `filter` uses a inclusive approach and `reject` an
|
|
833
852
|
exclusive (see [filter](https://apidock.com/ruby/Array/filter) and
|
|
834
|
-
[reject](https://apidock.com/ruby/Array/reject
|
|
835
|
-
in a similar
|
|
853
|
+
[reject](https://apidock.com/ruby/Array/reject) in the Ruby API, as they behave
|
|
854
|
+
in a similar manner).
|
|
836
855
|
|
|
837
856
|
You can either pass a Symbol which specifies the name of the method that is
|
|
838
857
|
called on each array item:
|
|
@@ -932,11 +951,11 @@ It consists of key-value-pairs that can be validated using arbitrary nodes.
|
|
|
932
951
|
|
|
933
952
|
Defaults to `false`.
|
|
934
953
|
|
|
935
|
-
#### Specifying
|
|
954
|
+
#### Specifying Properties
|
|
936
955
|
|
|
937
956
|
Hash nodes support a block in which you can specify the required hash contents.
|
|
938
957
|
|
|
939
|
-
##### Standard
|
|
958
|
+
##### Standard Properties
|
|
940
959
|
|
|
941
960
|
It supports all type nodes, but requires the suffix `?` or `!` for each
|
|
942
961
|
property, which specifies whether a property is required (`!`) or optional
|
|
@@ -1059,7 +1078,7 @@ schema.validate!({foo: 1}) # => {"foo"=>1}
|
|
|
1059
1078
|
schema.validate!({foo: 'bar'}) # => {"foo"=>"bar"}
|
|
1060
1079
|
```
|
|
1061
1080
|
|
|
1062
|
-
##### Pattern
|
|
1081
|
+
##### Pattern Properties
|
|
1063
1082
|
|
|
1064
1083
|
In addition to symbols, property keys can also be a regular expression. Here,
|
|
1065
1084
|
you may only use the optional `?` suffix for the property. This allows any
|
|
@@ -1079,7 +1098,7 @@ schema.validate!({id_foo: 1, id_bar: 2}) # => {"id_foo"=>1, "id_bar"=>2}
|
|
|
1079
1098
|
schema.validate!({foo: 3}) # => Schemacop::Exceptions::ValidationError: /: Obsolete property "foo".
|
|
1080
1099
|
```
|
|
1081
1100
|
|
|
1082
|
-
##### Additional
|
|
1101
|
+
##### Additional Properties & Property Names
|
|
1083
1102
|
|
|
1084
1103
|
In addition to standard properties, you can allow the hash to contain
|
|
1085
1104
|
additional, unspecified properties. By default, this is turned off if you have
|
|
@@ -1139,7 +1158,7 @@ schema.validate!({foo: :bar}) # => Schemacop::Exceptions::ValidationError:
|
|
|
1139
1158
|
schema.validate!({Foo: :bar}) # => Schemacop::Exceptions::ValidationError: /: Property name :Foo does not match "^[a-z]+$". /Foo: Invalid type, got type "Symbol", expected "array".
|
|
1140
1159
|
```
|
|
1141
1160
|
|
|
1142
|
-
##### Ignoring
|
|
1161
|
+
##### Ignoring Obsolete Properties
|
|
1143
1162
|
|
|
1144
1163
|
By enabling `ignore_obsolete_properties`, you can filter out any unspecified params,
|
|
1145
1164
|
while still passing validation:
|
|
@@ -1540,7 +1559,7 @@ to use other data in the second context than in the first.
|
|
|
1540
1559
|
|
|
1541
1560
|
## External schemas
|
|
1542
1561
|
|
|
1543
|
-
Finally, Schemacop features the possibility to specify schemas in
|
|
1562
|
+
Finally, Schemacop features the possibility to specify schemas in separate
|
|
1544
1563
|
files. This is especially useful is you have schemas in your application which
|
|
1545
1564
|
are used multiple times throughout the application.
|
|
1546
1565
|
|
|
@@ -1563,7 +1582,7 @@ Where:
|
|
|
1563
1582
|
* context schemas: Defined in the current context using `context.schema`
|
|
1564
1583
|
* global schemas: Defined in a ruby file in the load path
|
|
1565
1584
|
|
|
1566
|
-
### External
|
|
1585
|
+
### External Schemas in Rails Applications
|
|
1567
1586
|
|
|
1568
1587
|
In Rails applications, your schemas are automatically eager-loaded from the load
|
|
1569
1588
|
path `'app/schemas'` when your application is started, unless your application
|
|
@@ -1614,7 +1633,7 @@ schema.validate!({usr: {first_name: 'Joe', last_name: 'Doe', groups: [{name: 'fo
|
|
|
1614
1633
|
# => {"usr"=>{"first_name"=>"Joe", "last_name"=>"Doe", "groups"=>[{"name"=>"foo"}, {"name"=>"bar"}]}}
|
|
1615
1634
|
```
|
|
1616
1635
|
|
|
1617
|
-
### External
|
|
1636
|
+
### External Schemas in Non-Rails Applications
|
|
1618
1637
|
|
|
1619
1638
|
Usage in non-Rails applications is the same as with usage in Rails applications,
|
|
1620
1639
|
however you might need to eager load the schemas yourself:
|
|
@@ -1632,11 +1651,24 @@ to eager-load them on start of your application / script.
|
|
|
1632
1651
|
Using the setting `Schemacop.v3_default_options`, you can specify a hash
|
|
1633
1652
|
containing default options that will be used for every schemacop node (options
|
|
1634
1653
|
not supported by a particular node are automatically ignored). Options passed
|
|
1635
|
-
directly to a node still take precedence.
|
|
1636
|
-
|
|
1654
|
+
directly to a node still take precedence.
|
|
1655
|
+
|
|
1656
|
+
### Rails applications
|
|
1657
|
+
|
|
1658
|
+
For Rails applications, configure default options in `config/schemacop.rb`
|
|
1659
|
+
(note: not in the `initializers/` subdirectory). This ensures that options are
|
|
1660
|
+
applied before schemas are eager loaded in production mode:
|
|
1661
|
+
|
|
1662
|
+
```ruby
|
|
1663
|
+
# config/schemacop.rb
|
|
1664
|
+
Schemacop.v3_default_options = { cast_str: true }.freeze
|
|
1665
|
+
```
|
|
1666
|
+
|
|
1667
|
+
### Non-Rails applications
|
|
1668
|
+
|
|
1669
|
+
For non-Rails applications, set the options before loading any schemas:
|
|
1637
1670
|
|
|
1638
1671
|
```ruby
|
|
1639
|
-
# config/initializers/schemacop.rb
|
|
1640
1672
|
Schemacop.v3_default_options = { cast_str: true }.freeze
|
|
1641
1673
|
|
|
1642
1674
|
# Example schema: As cast_str is enabled in the default options, strings will
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.0.
|
|
1
|
+
3.0.36
|
data/lib/schemacop/railtie.rb
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
module Schemacop
|
|
2
2
|
class Railtie < Rails::Railtie
|
|
3
3
|
initializer 'schemacop' do
|
|
4
|
+
# Load configuration
|
|
5
|
+
config_path = Rails.root.join('config', 'schemacop.rb')
|
|
6
|
+
|
|
7
|
+
if config_path.exist?
|
|
8
|
+
load config_path
|
|
9
|
+
end
|
|
10
|
+
|
|
4
11
|
# Load global schemas
|
|
5
12
|
unless Rails.env.development?
|
|
6
13
|
V3::GlobalContext.eager_load!
|
|
@@ -8,6 +8,7 @@ module Schemacop
|
|
|
8
8
|
multiple_of
|
|
9
9
|
exclusive_minimum
|
|
10
10
|
exclusive_maximum
|
|
11
|
+
max_precision
|
|
11
12
|
].freeze
|
|
12
13
|
|
|
13
14
|
def self.allowed_options
|
|
@@ -58,12 +59,39 @@ module Schemacop
|
|
|
58
59
|
if options[:multiple_of] && !compare_float((super_data % options[:multiple_of]), 0.0)
|
|
59
60
|
result.error "Value must be a multiple of #{options[:multiple_of]}."
|
|
60
61
|
end
|
|
62
|
+
|
|
63
|
+
# Validate max precision #
|
|
64
|
+
if options[:max_precision] && (super_data.is_a?(Float) || super_data.is_a?(BigDecimal))
|
|
65
|
+
# Convert to string and check decimal places
|
|
66
|
+
str = super_data.to_s
|
|
67
|
+
# For BigDecimal, remove the trailing '0' from the scientific notation if present
|
|
68
|
+
str = str.sub(/\.0e[+-]?\d+\z/, '.0') if super_data.is_a?(BigDecimal)
|
|
69
|
+
|
|
70
|
+
if (match = str.match(/\.(\d+)$/))
|
|
71
|
+
# Remove trailing zeros for more accurate precision check
|
|
72
|
+
decimal_part = match[1].sub(/0+\z/, '')
|
|
73
|
+
decimal_places = decimal_part.empty? ? 0 : decimal_part.length
|
|
74
|
+
if decimal_places > options[:max_precision]
|
|
75
|
+
result.error "Value must have a maximum precision of #{options[:max_precision]} digits after the decimal point."
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
61
79
|
end
|
|
62
80
|
|
|
63
81
|
def validate_self
|
|
64
82
|
# Check that the options have the correct type
|
|
65
83
|
ATTRIBUTES.each do |attribute|
|
|
66
84
|
next if options[attribute].nil?
|
|
85
|
+
|
|
86
|
+
# Special handling for max_precision - must be an integer
|
|
87
|
+
if attribute == :max_precision
|
|
88
|
+
unless options[attribute].is_a?(Integer) && options[attribute] >= 0
|
|
89
|
+
fail 'Option "max_precision" must be a non-negative integer.'
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
next
|
|
93
|
+
end
|
|
94
|
+
|
|
67
95
|
next unless allowed_types.keys.none? { |c| options[attribute].send(type_assertion_method, c) }
|
|
68
96
|
|
|
69
97
|
collection = allowed_types.values.map { |t| "\"#{t}\"" }.uniq.sort.join(' or ')
|
data/schemacop.gemspec
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# stub: schemacop 3.0.
|
|
2
|
+
# stub: schemacop 3.0.36 ruby lib
|
|
3
3
|
|
|
4
4
|
Gem::Specification.new do |s|
|
|
5
5
|
s.name = "schemacop".freeze
|
|
6
|
-
s.version = "3.0.
|
|
6
|
+
s.version = "3.0.36".freeze
|
|
7
7
|
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
|
9
9
|
s.require_paths = ["lib".freeze]
|
|
10
10
|
s.authors = ["Sitrox".freeze]
|
|
11
|
-
s.date = "
|
|
11
|
+
s.date = "2026-01-05"
|
|
12
12
|
s.files = [".github/workflows/ruby.yml".freeze, ".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, ".yardopts".freeze, "CHANGELOG.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "README_V2.md".freeze, "README_V3.md".freeze, "RUBY_VERSION".freeze, "Rakefile".freeze, "VERSION".freeze, "lib/schemacop.rb".freeze, "lib/schemacop/base_schema.rb".freeze, "lib/schemacop/exceptions.rb".freeze, "lib/schemacop/railtie.rb".freeze, "lib/schemacop/schema.rb".freeze, "lib/schemacop/schema2.rb".freeze, "lib/schemacop/schema3.rb".freeze, "lib/schemacop/scoped_env.rb".freeze, "lib/schemacop/v2.rb".freeze, "lib/schemacop/v2/caster.rb".freeze, "lib/schemacop/v2/collector.rb".freeze, "lib/schemacop/v2/dupper.rb".freeze, "lib/schemacop/v2/field_node.rb".freeze, "lib/schemacop/v2/node.rb".freeze, "lib/schemacop/v2/node_resolver.rb".freeze, "lib/schemacop/v2/node_supporting_field.rb".freeze, "lib/schemacop/v2/node_supporting_type.rb".freeze, "lib/schemacop/v2/node_with_block.rb".freeze, "lib/schemacop/v2/validator/array_validator.rb".freeze, "lib/schemacop/v2/validator/boolean_validator.rb".freeze, "lib/schemacop/v2/validator/float_validator.rb".freeze, "lib/schemacop/v2/validator/hash_validator.rb".freeze, "lib/schemacop/v2/validator/integer_validator.rb".freeze, "lib/schemacop/v2/validator/nil_validator.rb".freeze, "lib/schemacop/v2/validator/number_validator.rb".freeze, "lib/schemacop/v2/validator/object_validator.rb".freeze, "lib/schemacop/v2/validator/string_validator.rb".freeze, "lib/schemacop/v2/validator/symbol_validator.rb".freeze, "lib/schemacop/v3.rb".freeze, "lib/schemacop/v3/all_of_node.rb".freeze, "lib/schemacop/v3/any_of_node.rb".freeze, "lib/schemacop/v3/array_node.rb".freeze, "lib/schemacop/v3/boolean_node.rb".freeze, "lib/schemacop/v3/combination_node.rb".freeze, "lib/schemacop/v3/context.rb".freeze, "lib/schemacop/v3/dsl_scope.rb".freeze, "lib/schemacop/v3/global_context.rb".freeze, "lib/schemacop/v3/hash_node.rb".freeze, "lib/schemacop/v3/integer_node.rb".freeze, "lib/schemacop/v3/is_not_node.rb".freeze, "lib/schemacop/v3/node.rb".freeze, "lib/schemacop/v3/node_registry.rb".freeze, "lib/schemacop/v3/number_node.rb".freeze, "lib/schemacop/v3/numeric_node.rb".freeze, "lib/schemacop/v3/object_node.rb".freeze, "lib/schemacop/v3/one_of_node.rb".freeze, "lib/schemacop/v3/reference_node.rb".freeze, "lib/schemacop/v3/result.rb".freeze, "lib/schemacop/v3/string_node.rb".freeze, "lib/schemacop/v3/symbol_node.rb".freeze, "schemacop.gemspec".freeze, "test/lib/test_helper.rb".freeze, "test/schemas/nested/group.rb".freeze, "test/schemas/user.rb".freeze, "test/unit/schemacop/v2/casting_test.rb".freeze, "test/unit/schemacop/v2/collector_test.rb".freeze, "test/unit/schemacop/v2/custom_check_test.rb".freeze, "test/unit/schemacop/v2/custom_if_test.rb".freeze, "test/unit/schemacop/v2/defaults_test.rb".freeze, "test/unit/schemacop/v2/empty_test.rb".freeze, "test/unit/schemacop/v2/nil_dis_allow_test.rb".freeze, "test/unit/schemacop/v2/node_resolver_test.rb".freeze, "test/unit/schemacop/v2/short_forms_test.rb".freeze, "test/unit/schemacop/v2/types_test.rb".freeze, "test/unit/schemacop/v2/validator_array_test.rb".freeze, "test/unit/schemacop/v2/validator_boolean_test.rb".freeze, "test/unit/schemacop/v2/validator_float_test.rb".freeze, "test/unit/schemacop/v2/validator_hash_test.rb".freeze, "test/unit/schemacop/v2/validator_integer_test.rb".freeze, "test/unit/schemacop/v2/validator_nil_test.rb".freeze, "test/unit/schemacop/v2/validator_number_test.rb".freeze, "test/unit/schemacop/v2/validator_object_test.rb".freeze, "test/unit/schemacop/v2/validator_string_test.rb".freeze, "test/unit/schemacop/v2/validator_symbol_test.rb".freeze, "test/unit/schemacop/v3/all_of_node_test.rb".freeze, "test/unit/schemacop/v3/any_of_node_test.rb".freeze, "test/unit/schemacop/v3/array_node_test.rb".freeze, "test/unit/schemacop/v3/boolean_node_test.rb".freeze, "test/unit/schemacop/v3/global_context_test.rb".freeze, "test/unit/schemacop/v3/hash_node_test.rb".freeze, "test/unit/schemacop/v3/integer_node_test.rb".freeze, "test/unit/schemacop/v3/is_not_node_test.rb".freeze, "test/unit/schemacop/v3/node_test.rb".freeze, "test/unit/schemacop/v3/number_node_test.rb".freeze, "test/unit/schemacop/v3/object_node_test.rb".freeze, "test/unit/schemacop/v3/one_of_node_test.rb".freeze, "test/unit/schemacop/v3/reference_node_test.rb".freeze, "test/unit/schemacop/v3/string_node_test.rb".freeze, "test/unit/schemacop/v3/symbol_node_test.rb".freeze]
|
|
13
13
|
s.homepage = "https://github.com/sitrox/schemacop".freeze
|
|
14
14
|
s.licenses = ["MIT".freeze]
|
|
@@ -988,7 +988,7 @@ module Schemacop
|
|
|
988
988
|
end
|
|
989
989
|
|
|
990
990
|
assert_validation('{42]') do
|
|
991
|
-
error '/', /JSON parse error: "(\d+: )?unexpected token at '{42]'"\./
|
|
991
|
+
error '/', /JSON parse error: "((\d+: )?unexpected token at '{42]'|expected object key, got '42]' at line \d+ column \d+)"\./
|
|
992
992
|
end
|
|
993
993
|
|
|
994
994
|
assert_validation('"foo"') do
|
|
@@ -161,6 +161,104 @@ module Schemacop
|
|
|
161
161
|
GlobalContext.instance.schema_for('user')
|
|
162
162
|
end
|
|
163
163
|
end
|
|
164
|
+
|
|
165
|
+
def test_v3_default_options_with_lazy_load
|
|
166
|
+
dir = Dir.mktmpdir
|
|
167
|
+
Schemacop.load_paths << dir
|
|
168
|
+
File.write(File.join(dir, 'test_schema.rb'), %(schema :integer))
|
|
169
|
+
|
|
170
|
+
Schemacop.v3_default_options = { cast_str: true }.freeze
|
|
171
|
+
|
|
172
|
+
schema = GlobalContext.instance.schema_for('test_schema')
|
|
173
|
+
result = schema.validate('42')
|
|
174
|
+
|
|
175
|
+
assert result.valid?, "Expected string '42' to be cast to integer 42, but got errors: #{result.errors}"
|
|
176
|
+
assert_equal 42, result.data
|
|
177
|
+
ensure
|
|
178
|
+
Schemacop.v3_default_options = {}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def test_v3_default_options_with_eager_load
|
|
182
|
+
dir = Dir.mktmpdir
|
|
183
|
+
Schemacop.load_paths << dir
|
|
184
|
+
File.write(File.join(dir, 'test_schema.rb'), %(schema :integer))
|
|
185
|
+
|
|
186
|
+
Schemacop.v3_default_options = { cast_str: true }.freeze
|
|
187
|
+
|
|
188
|
+
GlobalContext.instance.eager_load!
|
|
189
|
+
schema = GlobalContext.instance.schema_for('test_schema')
|
|
190
|
+
result = schema.validate('42')
|
|
191
|
+
|
|
192
|
+
assert result.valid?, "Expected string '42' to be cast to integer 42, but got errors: #{result.errors}"
|
|
193
|
+
assert_equal 42, result.data
|
|
194
|
+
ensure
|
|
195
|
+
Schemacop.v3_default_options = {}
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def test_v3_default_options_with_nested_schema
|
|
199
|
+
dir = Dir.mktmpdir
|
|
200
|
+
Schemacop.load_paths << dir
|
|
201
|
+
File.write(File.join(dir, 'form_schema.rb'), <<~SCHEMA)
|
|
202
|
+
schema do
|
|
203
|
+
int! :age
|
|
204
|
+
str! :name
|
|
205
|
+
end
|
|
206
|
+
SCHEMA
|
|
207
|
+
|
|
208
|
+
Schemacop.v3_default_options = { cast_str: true }.freeze
|
|
209
|
+
|
|
210
|
+
schema = GlobalContext.instance.schema_for('form_schema')
|
|
211
|
+
result = schema.validate({ age: '25', name: 'John' })
|
|
212
|
+
|
|
213
|
+
assert result.valid?, "Expected string '25' to be cast to integer 25, but got errors: #{result.errors}"
|
|
214
|
+
assert_equal 25, result.data[:age]
|
|
215
|
+
assert_equal 'John', result.data[:name]
|
|
216
|
+
ensure
|
|
217
|
+
Schemacop.v3_default_options = {}
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def test_v3_default_options_set_before_eager_load
|
|
221
|
+
dir = Dir.mktmpdir
|
|
222
|
+
Schemacop.load_paths << dir
|
|
223
|
+
File.write(File.join(dir, 'test_schema.rb'), %(schema :integer))
|
|
224
|
+
|
|
225
|
+
# Set default options BEFORE eager loading (simulates production mode)
|
|
226
|
+
Schemacop.v3_default_options = { cast_str: true }.freeze
|
|
227
|
+
|
|
228
|
+
GlobalContext.instance.eager_load!
|
|
229
|
+
schema = GlobalContext.instance.schema_for('test_schema')
|
|
230
|
+
result = schema.validate('42')
|
|
231
|
+
|
|
232
|
+
assert result.valid?, "Expected string '42' to be cast to integer 42, but got errors: #{result.errors}"
|
|
233
|
+
assert_equal 42, result.data
|
|
234
|
+
ensure
|
|
235
|
+
Schemacop.v3_default_options = {}
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def test_v3_default_options_set_after_eager_load
|
|
239
|
+
dir = Dir.mktmpdir
|
|
240
|
+
Schemacop.load_paths << dir
|
|
241
|
+
File.write(File.join(dir, 'test_schema.rb'), %(schema :integer))
|
|
242
|
+
|
|
243
|
+
# Eager load schemas BEFORE setting default options
|
|
244
|
+
GlobalContext.instance.eager_load!
|
|
245
|
+
|
|
246
|
+
Schemacop.v3_default_options = { cast_str: true }.freeze
|
|
247
|
+
|
|
248
|
+
schema = GlobalContext.instance.schema_for('test_schema')
|
|
249
|
+
result = schema.validate('42')
|
|
250
|
+
|
|
251
|
+
# NOTE: Setting v3_default_options AFTER eager loading does not work
|
|
252
|
+
# because schemas are already cached. In Rails applications, the
|
|
253
|
+
# Railtie explicitly loads 'config/schemacop.rb' before eager loading,
|
|
254
|
+
# ensuring options are set before schemas are loaded.
|
|
255
|
+
#
|
|
256
|
+
# This test verifies that the options don't retroactively apply.
|
|
257
|
+
refute result.valid?, 'Options set after eager_load! should not affect already-loaded schemas'
|
|
258
|
+
assert_equal({ [] => ['Invalid type, got type "String", expected "integer".'] }, result.errors)
|
|
259
|
+
ensure
|
|
260
|
+
Schemacop.v3_default_options = {}
|
|
261
|
+
end
|
|
164
262
|
end
|
|
165
263
|
end
|
|
166
264
|
end
|
|
@@ -32,7 +32,7 @@ module Schemacop
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
assert_validation('{42]') do
|
|
35
|
-
error '/', /JSON parse error: "(\d+: )?unexpected token at '{42]'"\./
|
|
35
|
+
error '/', /JSON parse error: "((\d+: )?unexpected token at '{42]'|expected object key, got '42]' at line \d+ column \d+)"\./
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
assert_validation('"foo"') do
|
|
@@ -193,6 +193,75 @@ module Schemacop
|
|
|
193
193
|
end
|
|
194
194
|
end
|
|
195
195
|
|
|
196
|
+
def test_max_precision
|
|
197
|
+
schema :number, max_precision: 2
|
|
198
|
+
|
|
199
|
+
assert_json(
|
|
200
|
+
type: :number,
|
|
201
|
+
maxPrecision: 2
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
# Valid cases
|
|
205
|
+
assert_validation(42) # Integer
|
|
206
|
+
assert_validation(3.14) # 2 decimal places
|
|
207
|
+
assert_validation(3.1) # 1 decimal place
|
|
208
|
+
assert_validation(3.0) # 0 decimal places
|
|
209
|
+
assert_validation(0.12) # 2 decimal places
|
|
210
|
+
assert_validation(1r) # Rational should not be affected
|
|
211
|
+
|
|
212
|
+
# BigDecimal valid cases
|
|
213
|
+
assert_validation(BigDecimal('3.14')) # 2 decimal places
|
|
214
|
+
assert_validation(BigDecimal('3.1')) # 1 decimal place
|
|
215
|
+
assert_validation(BigDecimal('3.0')) # 0 decimal places (with trailing zero)
|
|
216
|
+
assert_validation(BigDecimal('3')) # 0 decimal places
|
|
217
|
+
assert_validation(BigDecimal('3.00')) # trailing zeros should be ignored
|
|
218
|
+
|
|
219
|
+
# Invalid cases - Float
|
|
220
|
+
assert_validation(3.141) do
|
|
221
|
+
error '/', 'Value must have a maximum precision of 2 digits after the decimal point.'
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
assert_validation(0.123) do
|
|
225
|
+
error '/', 'Value must have a maximum precision of 2 digits after the decimal point.'
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
assert_validation(123.456789) do
|
|
229
|
+
error '/', 'Value must have a maximum precision of 2 digits after the decimal point.'
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Invalid cases - BigDecimal
|
|
233
|
+
assert_validation(BigDecimal('3.141')) do
|
|
234
|
+
error '/', 'Value must have a maximum precision of 2 digits after the decimal point.'
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
assert_validation(BigDecimal('0.123')) do
|
|
238
|
+
error '/', 'Value must have a maximum precision of 2 digits after the decimal point.'
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Test with max_precision: 0
|
|
242
|
+
schema :number, max_precision: 0
|
|
243
|
+
|
|
244
|
+
assert_json(
|
|
245
|
+
type: :number,
|
|
246
|
+
maxPrecision: 0
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
assert_validation(42)
|
|
250
|
+
assert_validation(3.0)
|
|
251
|
+
assert_validation(0)
|
|
252
|
+
assert_validation(BigDecimal('3'))
|
|
253
|
+
assert_validation(BigDecimal('3.0'))
|
|
254
|
+
assert_validation(BigDecimal('3.00'))
|
|
255
|
+
|
|
256
|
+
assert_validation(3.1) do
|
|
257
|
+
error '/', 'Value must have a maximum precision of 0 digits after the decimal point.'
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
assert_validation(BigDecimal('3.1')) do
|
|
261
|
+
error '/', 'Value must have a maximum precision of 0 digits after the decimal point.'
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
196
265
|
# Helper function that checks for all the options if the option is
|
|
197
266
|
# an allowed class or something else, in which case it needs to raise
|
|
198
267
|
def validate_self_should_error(value_to_check)
|
|
@@ -239,6 +308,21 @@ module Schemacop
|
|
|
239
308
|
schema :number, multiple_of: 0
|
|
240
309
|
end
|
|
241
310
|
|
|
311
|
+
assert_raises_with_message Exceptions::InvalidSchemaError,
|
|
312
|
+
'Option "max_precision" must be a non-negative integer.' do
|
|
313
|
+
schema :number, max_precision: -1
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
assert_raises_with_message Exceptions::InvalidSchemaError,
|
|
317
|
+
'Option "max_precision" must be a non-negative integer.' do
|
|
318
|
+
schema :number, max_precision: 2.5
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
assert_raises_with_message Exceptions::InvalidSchemaError,
|
|
322
|
+
'Option "max_precision" must be a non-negative integer.' do
|
|
323
|
+
schema :number, max_precision: '2'
|
|
324
|
+
end
|
|
325
|
+
|
|
242
326
|
validate_self_should_error(1 + 0i) # Complex
|
|
243
327
|
validate_self_should_error(Object.new)
|
|
244
328
|
validate_self_should_error(true)
|
|
@@ -310,6 +394,33 @@ module Schemacop
|
|
|
310
394
|
})
|
|
311
395
|
end
|
|
312
396
|
|
|
397
|
+
def test_combined_validations_with_max_precision
|
|
398
|
+
schema :number, minimum: 0, maximum: 100, max_precision: 1
|
|
399
|
+
|
|
400
|
+
assert_json(
|
|
401
|
+
type: :number,
|
|
402
|
+
minimum: 0,
|
|
403
|
+
maximum: 100,
|
|
404
|
+
maxPrecision: 1
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
assert_validation(50.5)
|
|
408
|
+
assert_validation(0.1)
|
|
409
|
+
assert_validation(100.0)
|
|
410
|
+
|
|
411
|
+
assert_validation(50.55) do
|
|
412
|
+
error '/', 'Value must have a maximum precision of 1 digits after the decimal point.'
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
assert_validation(-0.1) do
|
|
416
|
+
error '/', 'Value must have a minimum of 0.'
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
assert_validation(100.1) do
|
|
420
|
+
error '/', 'Value must have a maximum of 100.'
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
313
424
|
def test_cast_str
|
|
314
425
|
schema :number, cast_str: true, minimum: 0.0, maximum: 50r, multiple_of: BigDecimal('0.5')
|
|
315
426
|
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: schemacop
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.0.
|
|
4
|
+
version: 3.0.36
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sitrox
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-01-05 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: activesupport
|
|
@@ -162,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
162
162
|
- !ruby/object:Gem::Version
|
|
163
163
|
version: '0'
|
|
164
164
|
requirements: []
|
|
165
|
-
rubygems_version:
|
|
165
|
+
rubygems_version: 4.0.2
|
|
166
166
|
specification_version: 4
|
|
167
167
|
summary: Schemacop validates ruby structures consisting of nested hashes and arrays
|
|
168
168
|
against simple schema definitions.
|