Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ULID.valid_as_variants? with ULID.valid? deprecation #205

Merged
merged 1 commit into from
Jul 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"ruby.format": "rubocop",
"cSpell.words": [
"kwargs",
"Undocumentable",
"unshareable"
] // use rubocop for formatting
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
28 changes: 23 additions & 5 deletions lib/ulid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
40 changes: 33 additions & 7 deletions sig/ulid.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
#
Expand Down
14 changes: 13 additions & 1 deletion steep_expectations.yml
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +11 to +13
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope ruby/rbs#1056 removes this 🙏

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

70 changes: 56 additions & 14 deletions test/core/test_ulid_class.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ def test_exposed_methods
:range,
:at,
:normalized?,
:parse
:parse,
:valid_as_variants?
].sort,
exposed_methods.sort
)
Expand Down Expand Up @@ -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?
Expand Down Expand Up @@ -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)))
Expand Down Expand Up @@ -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
Expand Down
26 changes: 23 additions & 3 deletions test/helper.rb
Original file line number Diff line number Diff line change
@@ -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')
Expand All @@ -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
2 changes: 1 addition & 1 deletion test/many_data/test_fixed_many_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down