schemacop 3.0.39 → 3.1.1

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +1 -1
  3. data/.rubocop.yml +4 -1
  4. data/CHANGELOG.md +87 -0
  5. data/Gemfile +1 -1
  6. data/README.md +8 -7
  7. data/README_V3.md +38 -5
  8. data/VERSION +1 -1
  9. data/lib/schemacop/schema3.rb +2 -2
  10. data/lib/schemacop/v2/node.rb +1 -1
  11. data/lib/schemacop/v2/validator/string_validator.rb +1 -1
  12. data/lib/schemacop/v3/any_of_node.rb +13 -0
  13. data/lib/schemacop/v3/array_node.rb +2 -3
  14. data/lib/schemacop/v3/global_context.rb +1 -1
  15. data/lib/schemacop/v3/hash_node.rb +23 -21
  16. data/lib/schemacop/v3/node.rb +5 -0
  17. data/lib/schemacop/v3/numeric_node.rb +2 -2
  18. data/lib/schemacop/v3/object_node.rb +1 -1
  19. data/lib/schemacop/v3/one_of_node.rb +16 -3
  20. data/lib/schemacop/v3/result.rb +14 -1
  21. data/lib/schemacop/v3/string_node.rb +32 -4
  22. data/lib/schemacop/v3.rb +32 -0
  23. data/lib/schemacop.rb +12 -9
  24. data/schemacop.gemspec +3 -3
  25. data/test/lib/test_helper.rb +7 -0
  26. data/test/unit/schemacop/v2/casting_test.rb +1 -1
  27. data/test/unit/schemacop/v2/custom_check_test.rb +2 -2
  28. data/test/unit/schemacop/v2/custom_if_test.rb +2 -2
  29. data/test/unit/schemacop/v3/any_of_node_test.rb +24 -0
  30. data/test/unit/schemacop/v3/array_node_test.rb +60 -1
  31. data/test/unit/schemacop/v3/binary_node_test.rb +8 -0
  32. data/test/unit/schemacop/v3/hash_node_test.rb +143 -1
  33. data/test/unit/schemacop/v3/integer_node_test.rb +15 -1
  34. data/test/unit/schemacop/v3/is_not_node_test.rb +12 -0
  35. data/test/unit/schemacop/v3/number_node_test.rb +1 -1
  36. data/test/unit/schemacop/v3/object_node_test.rb +8 -0
  37. data/test/unit/schemacop/v3/one_of_node_test.rb +61 -0
  38. data/test/unit/schemacop/v3/string_node_test.rb +250 -2
  39. metadata +3 -3
@@ -323,6 +323,10 @@ module Schemacop
323
323
  error '/', 'String does not match format "integer".'
324
324
  end
325
325
 
326
+ assert_validation "32\ntest" do
327
+ error '/', 'String does not match format "integer".'
328
+ end
329
+
326
330
  assert_cast(nil, nil)
327
331
  assert_cast('2234', 2234)
328
332
  assert_cast('-1', -1)
@@ -516,9 +520,11 @@ module Schemacop
516
520
  end
517
521
 
518
522
  def test_format_custom
523
+ formatters_was = Schemacop.string_formatters.dup
524
+
519
525
  Schemacop.register_string_formatter(
520
526
  :integer_tuple_list,
521
- pattern: /^(-?[0-9]+):(-?[0-9]+)(,(-?[0-9]+):(-?[0-9]+))*$/,
527
+ pattern: /\A(-?[0-9]+):(-?[0-9]+)(,(-?[0-9]+):(-?[0-9]+))*\z/,
522
528
  handler: proc do |value|
523
529
  value.split(',').map { |t| t.split(':').map(&:to_i) }
524
530
  end
@@ -539,8 +545,55 @@ module Schemacop
539
545
  error '/', 'String does not match format "integer-tuple-list".'
540
546
  end
541
547
 
548
+ assert_validation "1:5\nsd sfdij soidf" do
549
+ error '/', 'String does not match format "integer-tuple-list".'
550
+ end
551
+
542
552
  assert_cast nil, nil
543
553
  assert_cast '1:2,3:4,5:-6', [[1, 2], [3, 4], [5, -6]]
554
+ ensure
555
+ Schemacop.string_formatters = formatters_was
556
+ end
557
+
558
+ # A format pattern must be anchored with \A and \z: ^ and $ match the
559
+ # start and the end of any line, so a multi-line string would pass the
560
+ # validation and be handed to the cast handler.
561
+ def test_format_multiline
562
+ values = {
563
+ date: '2020-01-13',
564
+ date_time: '2018-11-13T20:20:39Z',
565
+ time: '20:30:39Z',
566
+ email: 'support@example.com',
567
+ mailbox: '<support@example.com>',
568
+ boolean: 'true',
569
+ integer: '23425',
570
+ number: '2.34',
571
+ integer_list: '1,2,3',
572
+ ipv4: '192.168.0.1',
573
+ ipv4_cidr: '192.168.0.1/24',
574
+ ipv6: '2001:db8::1'
575
+ }
576
+
577
+ assert_equal Schemacop.string_formatters.reject { |_, f| f[:pattern].nil? }.keys.sort,
578
+ values.keys.map { |format| format.to_s.dasherize.to_sym }.sort,
579
+ 'Every format with a pattern must be covered here'
580
+
581
+ values.each do |format, value|
582
+ name = format.to_s.dasherize.to_sym
583
+
584
+ schema :string, format: format
585
+
586
+ assert_validation value
587
+
588
+ ["#{value}\nfoo", "foo\n#{value}", "#{value}\n"].each do |multiline|
589
+ refute Schemacop.string_formatters[name][:pattern].match?(multiline),
590
+ "Format #{name} matches the multi-line value #{multiline.inspect}"
591
+
592
+ assert_validation multiline do
593
+ error '/', "String does not match format #{name.to_s.inspect}."
594
+ end
595
+ end
596
+ end
544
597
  end
545
598
 
546
599
  def test_enum
@@ -653,7 +706,7 @@ module Schemacop
653
706
  end
654
707
 
655
708
  assert_raises_with_message Exceptions::InvalidSchemaError,
656
- 'Option "pattern" can\'t be parsed: end pattern '\
709
+ 'Option "pattern" can\'t be parsed: end pattern ' \
657
710
  'with unmatched parenthesis: /(abcde/.' do
658
711
  schema :string, pattern: '(abcde'
659
712
  end
@@ -750,6 +803,201 @@ module Schemacop
750
803
  assert_cast("\t", "\t")
751
804
  end
752
805
 
806
+ def test_encoding_single
807
+ schema :string, encoding: 'UTF-8'
808
+
809
+ assert_validation 'Hello World'
810
+ assert_validation ''
811
+
812
+ assert_validation 'Hello World'.encode('ASCII') do
813
+ error '/', 'String has encoding "US-ASCII" but must be "UTF-8".'
814
+ end
815
+ end
816
+
817
+ def test_encoding_multiple
818
+ schema :string, encoding: %w[UTF-8 US-ASCII]
819
+
820
+ assert_validation 'Hello World'
821
+ assert_validation 'Hello World'.encode('ASCII')
822
+
823
+ assert_validation 'Hello World'.encode('ISO-8859-1') do
824
+ error '/', 'String has encoding "ISO-8859-1" but must be "UTF-8" or "US-ASCII".'
825
+ end
826
+ end
827
+
828
+ def test_encoding_with_nil
829
+ schema :string, encoding: 'UTF-8'
830
+
831
+ assert_validation nil
832
+ end
833
+
834
+ def test_encoding_invalid_bytes
835
+ schema :string, encoding: 'UTF-8'
836
+
837
+ assert_validation invalid_string do
838
+ error '/', 'String has invalid "UTF-8" encoding.'
839
+ end
840
+ end
841
+
842
+ def test_encoding_invalid_bytes_without_specific_encoding
843
+ schema :string
844
+
845
+ assert_validation invalid_string do
846
+ error '/', 'String has invalid "UTF-8" encoding.'
847
+ end
848
+ end
849
+
850
+ def test_encoding_invalid_bytes_us_ascii
851
+ schema :string
852
+
853
+ assert_validation invalid_string('US-ASCII') do
854
+ error '/', 'String has invalid "US-ASCII" encoding.'
855
+ end
856
+ end
857
+
858
+ def test_encoding_invalid_bytes_and_encoding_mismatch
859
+ schema :string, encoding: 'UTF-8'
860
+
861
+ assert_validation invalid_string('US-ASCII') do
862
+ error '/', 'String has encoding "US-ASCII" but must be "UTF-8".'
863
+ error '/', 'String has invalid "US-ASCII" encoding.'
864
+ end
865
+ end
866
+
867
+ def test_encoding_invalid_bytes_with_format
868
+ schema :string, format: :integer
869
+
870
+ assert_validation invalid_string do
871
+ error '/', 'String has invalid "UTF-8" encoding.'
872
+ end
873
+ end
874
+
875
+ def test_encoding_invalid_bytes_with_format_options
876
+ schema :string, format: :integer, format_options: { minimum: 5 }
877
+
878
+ assert_validation invalid_string do
879
+ error '/', 'String has invalid "UTF-8" encoding.'
880
+ end
881
+ end
882
+
883
+ def test_encoding_invalid_bytes_with_format_without_pattern
884
+ schema :string, format: :symbol
885
+
886
+ assert_validation :foo.to_s
887
+
888
+ # The symbol formatter has no pattern to fail on, and its handler calls
889
+ # "to_sym", which raises on an invalid byte sequence.
890
+ assert_validation invalid_string do
891
+ error '/', 'String has invalid "UTF-8" encoding.'
892
+ end
893
+ end
894
+
895
+ def test_encoding_incompatible_with_pattern
896
+ schema :string, pattern: /^ü+$/
897
+
898
+ assert_validation 'üü'
899
+ assert_validation 'üü'.encode('ISO-8859-1') do
900
+ error '/', 'String does not match pattern "^ü+$".'
901
+ end
902
+ end
903
+
904
+ def test_encoding_incompatible_with_format
905
+ schema :string, format: :integer
906
+
907
+ assert_validation '42'
908
+
909
+ # UTF-16 is a dummy encoding and cannot be matched against a pattern in
910
+ # any other encoding, so the string does not match, valid as it looks.
911
+ assert_validation '42'.encode('UTF-16') do
912
+ error '/', 'String does not match format "integer".'
913
+ end
914
+ end
915
+
916
+ def test_encoding_invalid_bytes_with_pattern
917
+ schema :string, pattern: /\Aabc.*\z/
918
+
919
+ assert_validation invalid_string do
920
+ error '/', 'String has invalid "UTF-8" encoding.'
921
+ end
922
+ end
923
+
924
+ def test_encoding_invalid_bytes_with_allow_blank_false
925
+ schema :string, allow_blank: false
926
+
927
+ assert_validation invalid_string do
928
+ error '/', 'String has invalid "UTF-8" encoding.'
929
+ end
930
+ end
931
+
932
+ def test_encoding_incompatible_with_allow_blank_false
933
+ schema :string, allow_blank: false
934
+
935
+ assert_validation '42'.encode('UTF-16')
936
+ assert_validation ''.encode('UTF-16') do
937
+ error '/', 'String is blank but must not be blank!'
938
+ end
939
+ end
940
+
941
+ def test_encoding_binary_has_no_invalid_bytes
942
+ schema :string
943
+
944
+ # Every byte sequence is valid in ASCII-8BIT.
945
+ assert_validation invalid_string('ASCII-8BIT')
946
+ end
947
+
948
+ def test_encoding_invalid_bytes_with_length
949
+ schema :string, min_length: 20, max_length: 30
950
+
951
+ # The length checks do not decode the string, so they are still
952
+ # reported.
953
+ assert_validation invalid_string do
954
+ error '/', 'String is 7 characters long but must be at least 20.'
955
+ error '/', 'String has invalid "UTF-8" encoding.'
956
+ end
957
+ end
958
+
959
+ def test_encoding_incompatible_with_length
960
+ schema :string, max_length: 2
961
+
962
+ assert_validation '42'
963
+
964
+ # A dummy encoding has no characters, so "size" counts bytes.
965
+ assert_validation '42'.encode('UTF-16') do
966
+ error '/', 'String is 6 characters long but must be at most 2.'
967
+ end
968
+ end
969
+
970
+ def test_encoding_invalid_bytes_with_enum
971
+ schema :string, enum: ['foo']
972
+
973
+ assert_validation invalid_string do
974
+ error '/', 'Value not included in enum ["foo"].'
975
+ error '/', 'String has invalid "UTF-8" encoding.'
976
+ end
977
+ end
978
+
979
+ def test_encoding_validate_self
980
+ assert_raises_with_message Exceptions::InvalidSchemaError,
981
+ 'Option "encoding" must be a string or an array of strings.' do
982
+ schema :string, encoding: 123
983
+ end
984
+
985
+ assert_raises_with_message Exceptions::InvalidSchemaError,
986
+ 'Option "encoding" must be a string or an array of strings.' do
987
+ schema :string, encoding: [123]
988
+ end
989
+
990
+ assert_raises_with_message Exceptions::InvalidSchemaError,
991
+ 'Option "encoding" contains unknown encoding "UNKNOWN-FOO".' do
992
+ schema :string, encoding: 'UNKNOWN-FOO'
993
+ end
994
+
995
+ assert_raises_with_message Exceptions::InvalidSchemaError,
996
+ 'Option "encoding" contains unknown encoding "UNKNOWN-FOO".' do
997
+ schema :string, encoding: %w[UTF-8 UNKNOWN-FOO]
998
+ end
999
+ end
1000
+
753
1001
  def test_empty_or_whitespace_string_blank_not_allowed
754
1002
  schema :string, allow_blank: false
755
1003
 
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.39
4
+ version: 3.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sitrox
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-03-26 00:00:00.000000000 Z
10
+ date: 2026-09-08 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: activesupport
@@ -164,7 +164,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
164
  - !ruby/object:Gem::Version
165
165
  version: '0'
166
166
  requirements: []
167
- rubygems_version: 4.0.2
167
+ rubygems_version: 4.0.8
168
168
  specification_version: 4
169
169
  summary: Schemacop validates ruby structures consisting of nested hashes and arrays
170
170
  against simple schema definitions.