diff --git a/.vscode/settings.json b/.vscode/settings.json index 6d9e368f..4ef8e679 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,7 @@ }, "ruby.format": "rubocop", "cSpell.words": [ + "kwargs", "Undocumentable", "unshareable" ] // use rubocop for formatting diff --git a/README.md b/README.md index 44727b6d..07e11a21 100644 --- a/README.md +++ b/README.md @@ -346,7 +346,7 @@ The original `Crockford's base32` maps `I`, `L` to `1`, `O` to `0`. And accepts freestyle inserting `Hyphens (-)`. To consider this patterns or not is different in each implementations. -Current parser/validator/matcher aims to cover `subset of Crockford's base32`. +Current parser/validator/matcher basically aims to cover `subset of Crockford's base32`. I have suggested it would be clarified in [ulid/spec#57](https://github.com/ulid/spec/pull/57). >Case insensitive @@ -356,12 +356,13 @@ But it is a controversial point, discussing in [ulid/spec#3](https://github.com/ Be that as it may, this gem provides API for handling the nasty possibilities. -`ULID.normalize` and `ULID.normalized?` +`ULID.normalize`, `ULID.normalized?`, `ULID.valid_as_variants?` ```ruby -ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV" -ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false -ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true +ULID.normalize('01g70y0y7g-z1xwdarexergsddd') #=> "01G70Y0Y7GZ1XWDAREXERGSDDD" +ULID.normalized?('01g70y0y7g-z1xwdarexergsddd') #=> false +ULID.normalized?('01G70Y0Y7GZ1XWDAREXERGSDDD') #=> true +ULID.valid_as_variants?('01g70y0y7g-z1xwdarexergsddd') #=> true ``` #### UUIDv4 converter (experimental) diff --git a/lib/ulid.rb b/lib/ulid.rb index 592a3198..195dea5a 100644 --- a/lib/ulid.rb +++ b/lib/ulid.rb @@ -280,18 +280,36 @@ def self.normalize(string) parse(normalized_in_crockford).to_s end + # @param [String, #to_str] string # @return [Boolean] - def self.normalized?(object) - normalized = normalize(object) + def self.normalized?(string) + normalized = normalize(string) rescue Exception false else - normalized == object + normalized == string end + # @param [String, #to_str] string # @return [Boolean] - def self.valid?(object) - string = String.try_convert(object) + def self.valid_as_variants?(string) + normalize(string) + rescue Exception + false + else + true + end + + # @deprecated Use [.valid_as_variants?] or [.normalized?] instead + # + # Returns `true` if it is normalized string. + # Basically the difference of normalized? is to accept downcase or not. This returns true for downcased ULIDs. + # + # @return [Boolean] + def self.valid?(string) + warn_kwargs = (RUBY_VERSION >= '3.0') ? { category: :deprecated } : {} + Warning.warn('ULID.valid? is deprecated. Use ULID.valid_as_variants? or ULID.normalized? instead.', **warn_kwargs) + string = String.try_convert(string) string ? STRICT_PATTERN_WITH_CROCKFORD_BASE32_SUBSET.match?(string) : false end diff --git a/sig/ulid.rbs b/sig/ulid.rbs index b0656a4d..72e0257d 100644 --- a/sig/ulid.rbs +++ b/sig/ulid.rbs @@ -309,14 +309,13 @@ class ULID < Object # ``` def self.sample: (?period: period) -> ULID | (Integer number, ?period: period?) -> Array[ULID] - def self.valid?: (untyped) -> bool # Returns normalized string # # ```ruby + # ULID.normalize('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> "01G70Y0Y7GZ1XWDAREXERGSDDD" # ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV" - # ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false - # ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true + # ULID.normalize('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> ULID::ParserError # ``` # # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3) @@ -325,13 +324,40 @@ class ULID < Object # Returns `true` if it is normalized string # # ```ruby - # ULID.normalize('-olarz3-noekisv4rrff-q6ig5fav--') #=> "01ARZ3N0EK1SV4RRFFQ61G5FAV" - # ULID.normalized?('-olarz3-noekisv4rrff-q6ig5fav--') #=> false - # ULID.normalized?('01ARZ3N0EK1SV4RRFFQ61G5FAV') #=> true + # ULID.normalized?('01G70Y0Y7GZ1XWDAREXERGSDDD') #=> true + # ULID.normalized?('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> false + # ULID.normalized?(ULID.generate.to_s.downcase) #=> false + # ULID.normalized?('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> false (Not raising ULID::ParserError) + # ``` + # + # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3) + def self.normalized?: (_ToStr string) -> bool + | (untyped) -> false + + # Returns `true` if it is valid in ULID format variants + # + # ```ruby + # ULID.valid_as_variants?(ULID.generate.to_s.downcase) #=> true + # ULID.valid_as_variants?('01G70Y0Y7G-Z1XWDAREXERGSDDD') #=> true + # ULID.valid_as_variants?('01G70Y0Y7G_Z1XWDAREXERGSDDD') #=> false + # ``` + # + # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3) + def self.valid_as_variants?: (_ToStr string) -> bool + | (untyped) -> false + + # DEPRECATED Use valid_as_variants? instead + # + # Returns `true` if it is normalized string. + # Basically the difference of normalized? is to accept downcase or not. This returns true for downcased ULIDs. + # + # ```ruby + # ULID.valid?(ULID.generate.to_s.downcase) #=> true # ``` # # See also [ulid/spec#57](https://github.com/ulid/spec/pull/57) and [ulid/spec#3](https://github.com/ulid/spec/issues/3) - def self.normalized?: (untyped) -> bool + def self.valid?: (_ToStr string) -> bool + | (untyped) -> false # Returns parsed ULIDs from given String for rough operations. # diff --git a/steep_expectations.yml b/steep_expectations.yml index dcd024e9..6b946e6f 100644 --- a/steep_expectations.yml +++ b/steep_expectations.yml @@ -1 +1,13 @@ ---- [] +--- +- file: lib/ulid.rb + diagnostics: + - range: + start: + line: 311 + character: 12 + end: + line: 311 + character: 16 + severity: ERROR + message: Type `singleton(::Warning)` does not have method `warn` + code: Ruby::NoMethod diff --git a/test/core/test_ulid_class.rb b/test/core/test_ulid_class.rb index dc7876c3..b7508d88 100644 --- a/test/core/test_ulid_class.rb +++ b/test/core/test_ulid_class.rb @@ -36,7 +36,8 @@ def test_exposed_methods :range, :at, :normalized?, - :parse + :parse, + :valid_as_variants? ].sort, exposed_methods.sort ) @@ -136,19 +137,23 @@ def test_from_milliseconds_and_entropy end def test_valid? - assert_equal(false, ULID.valid?(nil)) - assert_equal(false, ULID.valid?('')) - assert_equal(false, ULID.valid?(BasicObject.new)) - assert_equal(false, ULID.valid?(Object.new)) - assert_equal(false, ULID.valid?(42)) - assert_equal(false, ULID.valid?(:'01ARZ3NDEKTSV4RRFFQ69G5FAV')) - assert_equal(false, ULID.valid?(ULID.sample)) - assert_equal(false, ULID.valid?("01ARZ3NDEKTSV4RRFFQ69G5FAV\n")) - assert_equal(false, ULID.valid?('01ARZ3NDEKTSV4RRFFQ69G5FAU')) - assert_equal(true, ULID.valid?('01ARZ3NDEKTSV4RRFFQ69G5FAV')) - assert_equal(true, ULID.valid?('01ARZ3NDEKTSV4RRFFQ69G5FAV'.downcase)) - assert_equal(true, ULID.valid?('7ZZZZZZZZZZZZZZZZZZZZZZZZZ')) - assert_equal(false, ULID.valid?('80000000000000000000000000')) + assert_warning('ULID.valid? is deprecated. Use ULID.valid_as_variants? or ULID.normalized? instead.') do + assert_equal(false, ULID.valid?(nil)) + assert_equal(false, ULID.valid?('')) + assert_equal(false, ULID.valid?(BasicObject.new)) + assert_equal(false, ULID.valid?(Object.new)) + assert_equal(false, ULID.valid?(42)) + assert_equal(false, ULID.valid?(:'01ARZ3NDEKTSV4RRFFQ69G5FAV')) + assert_equal(false, ULID.valid?(ULID.sample)) + assert_equal(false, ULID.valid?("01ARZ3NDEKTSV4RRFFQ69G5FAV\n")) + assert_equal(false, ULID.valid?('01ARZ3NDEKTSV4RRFFQ69G5FAU')) + assert_equal(true, ULID.valid?('01ARZ3NDEKTSV4RRFFQ69G5FAV')) + assert_equal(true, ULID.valid?('01ARZ3NDEKTSV4RRFFQ69G5FAV'.downcase)) + assert_equal(true, ULID.valid?('7ZZZZZZZZZZZZZZZZZZZZZZZZZ')) + assert_equal(false, ULID.valid?('80000000000000000000000000')) + + assert_false(ULID.valid?('01G70Y0Y7G-Z1XWDAREXERGSDDD')) + end assert_raises(ArgumentError) do ULID.valid? @@ -215,6 +220,8 @@ def test_normalize end def test_normalized? + assert_false(ULID.normalized?('01G70Y0Y7G-Z1XWDAREXERGSDDD')) + nasty = '-olarz3-noekisv4rrff-q6ig5fav--' assert_equal(false, ULID.normalized?(nasty)) assert_equal(true, ULID.normalized?(ULID.normalize(nasty))) @@ -247,6 +254,41 @@ def test_normalized? end end + def test_valid_as_variants? + assert_true(ULID.valid_as_variants?('01G70Y0Y7G-Z1XWDAREXERGSDDD')) + + nasty = '-olarz3-noekisv4rrff-q6ig5fav--' + assert_true(ULID.valid_as_variants?(nasty)) + assert_true(ULID.valid_as_variants?(ULID.normalize(nasty))) + + normalized = '01ARZ3NDEKTSV4RRFFQ69G5FAV' + assert_true(ULID.valid_as_variants?(normalized)) + assert_true(ULID.valid_as_variants?(normalized.downcase)) + + [ + '', + "01ARZ3NDEKTSV4RRFFQ69G5FAV\n", + '01ARZ3NDEKTSV4RRFFQ69G5FAU', + '01ARZ3NDEKTSV4RRFFQ69G5FA', + '80000000000000000000000000' + ].each do |invalid| + assert_false(ULID.valid_as_variants?(invalid)) + end + + ULID.sample(1000).each do |sample| + assert_true(ULID.valid_as_variants?(sample.to_s)) + assert_true(ULID.valid_as_variants?(sample.to_s.downcase)) + end + + assert_raises(ArgumentError) do + ULID.valid_as_variants? + end + + [nil, 42, normalized.to_sym, BasicObject.new, Object.new, ULID.parse(normalized)].each do |evil| + assert_false(ULID.valid_as_variants?(evil)) + end + end + def test_range time_has_more_value_than_milliseconds1 = Time.at(946684800, Rational('123456.789')) # 2000-01-01 00:00:00.123456789 UTC time_has_more_value_than_milliseconds2 = Time.at(1620045632, Rational('123456.789')) # 2021-05-03 12:40:32.123456789 UTC diff --git a/test/helper.rb b/test/helper.rb index 385cc21c..37927791 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -1,20 +1,27 @@ # coding: us-ascii # frozen_string_literal: true +# How to use: https://github.com/jeremyevans/ruby-warning require('warning') -# How to use => https://test-unit.github.io/test-unit/en/ +# How to use: https://test-unit.github.io/test-unit/en/ require('test/unit') require('irb') require('power_assert/colorize') require('irb/power_assert') +require 'stringio' + Warning[:deprecated] = true Warning[:experimental] = true -Warning.process do |_warning| - :raise +Warning.process do |warning_message| + if /ULID.valid\? is deprecated/.match?(warning_message) + :default + else + :raise + end end require_relative('../lib/ulid') @@ -37,4 +44,17 @@ def assert_acceptable_randomized_string(ulids) assert_in_epsilon(awesome_randomized_ulids.size, ulids.size, (5/100r).to_f) end end + + def assert_warning(pattern, &block) + org_stderr = $stderr + $stderr = fake_io = StringIO.new(+'', 'r+') + + begin + block.call + fake_io.rewind + assert_match(pattern, fake_io.read) + ensure + $stderr = org_stderr + end + end end diff --git a/test/many_data/test_fixed_many_data.rb b/test/many_data/test_fixed_many_data.rb index 15bb4d4d..5b977bb4 100644 --- a/test/many_data/test_fixed_many_data.rb +++ b/test/many_data/test_fixed_many_data.rb @@ -25,7 +25,7 @@ def assert_example(ulid, example) assert_equal(example.octets, ulid.octets) assert do - ULID.valid?(example.string) + ULID.normalized?(example.string) end end