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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 818a611a08d135cb5fb9c691a101472133c2313e5536675b62af795b01a56fbb
4
- data.tar.gz: bea474ef2808294b83597bab07b076103b5ddddbe0d4aa2164bbed779f2cc130
3
+ metadata.gz: 9494f9a5a921664fae74b99ecba0830775d160ae91250465b4b88ba1d1fdf970
4
+ data.tar.gz: 2263f93913342b872ac56c52ddd65dd6c34f34711d3624c52c6ffc2d5a9a516a
5
5
  SHA512:
6
- metadata.gz: f815986a0a382961c35445d710217a7af8bf9937fe6e137852e4c54c95bc9550b6796aa610f29e2b06aacd277e99eb2e19039bde2d8e1b6c0c5d27578db6b2a0
7
- data.tar.gz: 0db3deb0605ae6a1315e91a58ca1beebfd74aabe7be9488df320736d1077c54ba993c4705b4a8f4b63c8adda3daf57c5a3e6b46639127daf9435a8e1b45544a1
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 recieving exactly the right kind of data it needs. The idea of this
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 lines vs. Field lines
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 hashes with indifferent access
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 forms
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 type to a Field Line or schema
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 multiple types at once
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 subtypes
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 data points
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 casting
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 type castings
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 casters
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 order
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 limitations
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 schema V3
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. This can be done in the initializer configuration (in case of
286
- a Rails appliation):
285
+ standard formats. For Rails applications, this can be done in
286
+ `config/schemacop.rb`:
287
287
 
288
288
  ```ruby
289
- # config/initializers/schemacop.rb
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 oeprations,
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'), require: true)
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 wether the items in the array must all be distinct from
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 properties
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 validation
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 validation
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] in the Ruby API, as they behave
835
- in a similar manor).
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 properties
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 properties
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 properties
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 properties & property names
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 obsolete properties
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 seperate
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 schemas in Rails applications
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 schemas in Non-Rails applications
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. The setting can be set in an
1636
- initializer:
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.34
1
+ 3.0.36
@@ -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.34 ruby lib
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.34".freeze
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 = "2025-05-27"
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.34
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: 2025-05-27 00:00:00.000000000 Z
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: 3.6.8
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.