base58_id 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 350af1c45ca377d67a9d8223e069a2fe4d7578ba2a1428f54fa0ed9dfc3935e0
4
+ data.tar.gz: bb0a090804ba8890d5d3c1a851932dd137ff64985b806c01736c1b3c9b63fe0a
5
+ SHA512:
6
+ metadata.gz: 39220e72e36409269d9d6d6dc74bf54a6aef3c40a45096f049190e3e89a0b68df150e6ab843deadb951b99c6e630981a29b05db18a757293845caeaea938a2b6
7
+ data.tar.gz: 5450dc9d8a03b82716ba14c8bb9000654b80918200586b10c1ba1211626962d73fbb9a0f698ec8098e6c6951608373537cdb746790130034e7a816e87be4cca6
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ coverage/
2
+ *.gem
data/.rubocop.yml ADDED
@@ -0,0 +1,49 @@
1
+ require:
2
+ - rubocop-performance
3
+ - rubocop-rspec
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 2.7
7
+ NewCops: enable
8
+
9
+ Metrics/BlockLength:
10
+ Exclude:
11
+ - 'spec/**/*.rb'
12
+ Metrics/MethodLength:
13
+ Exclude:
14
+ - 'spec/**/*.rb'
15
+
16
+ Layout/LineLength:
17
+ Max: 120
18
+ Layout/HashAlignment:
19
+ Enabled: false
20
+ Layout/SpaceInsideArrayPercentLiteral:
21
+ Enabled: false
22
+ Layout/ArrayAlignment:
23
+ Enabled: false
24
+
25
+ Style/Documentation:
26
+ Enabled: false
27
+ Style/WhileUntilModifier:
28
+ Enabled: false
29
+ Style/WordArray:
30
+ Enabled: false
31
+ Style/SymbolArray:
32
+ Enabled: false
33
+ Style/NumericLiterals:
34
+ Enabled: false
35
+ Style/StringLiterals:
36
+ Enabled: false
37
+ Style/TrailingCommaInArrayLiteral:
38
+ Enabled: false
39
+ Style/TrailingCommaInHashLiteral:
40
+ Enabled: false
41
+
42
+ RSpec/ExampleLength:
43
+ Enabled: false
44
+ RSpec/MultipleExpectations:
45
+ Enabled: false
46
+ RSpec/NestedGroups:
47
+ Enabled: false
48
+ RSpec/LetBeforeExamples:
49
+ Enabled: false
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Elias Rodrigues
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # Base58Id
2
+
3
+ Convert an Integer ID or a UUID String to/from Base58 String
4
+
5
+ ## Install
6
+
7
+ Install it from rubygems.org in your terminal:
8
+
9
+ ```sh
10
+ gem install base58_id
11
+ ```
12
+
13
+ Or via `Gemfile` in your project:
14
+
15
+ ```sh
16
+ source 'https://rubygems.org'
17
+
18
+ gem 'base58_id', '~> 1.0'
19
+ ```
20
+
21
+ Or build and install the gem locally:
22
+
23
+ ```sh
24
+ gem build base58_id.gemspec
25
+ gem install base58_id-1.0.0.gem
26
+ ```
27
+
28
+ Require it in your Ruby code and the `Base58Id` class will be available:
29
+
30
+ ```rb
31
+ require 'base58_id'
32
+ ```
33
+
34
+ ## Alphabet
35
+
36
+ It is based on Base64 URL-safe alphabet but with `I`, `O`, `l`, `0`, `-`, `_` removed:
37
+
38
+ ```
39
+ A B C D E F G H J K L M N P Q R S T U V W X Y Z
40
+ a b c d e f g h i j k m n o p q r s t u v w x y z
41
+ 1 2 3 4 5 6 7 8 9
42
+ ```
43
+
44
+ ## Example
45
+
46
+ ```rb
47
+ require 'base58_id'
48
+
49
+ Base58Id.uuid_to_base58('058917af-0d2e-4755-b0bd-25b02b249824')
50
+ # => "qocqGYw9LEfu4WVujMzJv"
51
+
52
+ Base58Id.base58_to_uuid('qocqGYw9LEfu4WVujMzJv')
53
+ # => "058917af-0d2e-4755-b0bd-25b02b249824"
54
+
55
+ Base58Id.uuid_to_integer('058917af-0d2e-4755-b0bd-25b02b249824')
56
+ # => 7357965012972427318415208560898381860
57
+
58
+ Base58Id.integer_to_uuid(7357965012972427318415208560898381860)
59
+ # => "058917af-0d2e-4755-b0bd-25b02b249824"
60
+
61
+ Base58Id.base58_to_integer('qocqGYw9LEfu4WVujMzJv')
62
+ # => 7357965012972427318415208560898381860
63
+
64
+ Base58Id.integer_to_base58(7357965012972427318415208560898381860)
65
+ # => "qocqGYw9LEfu4WVujMzJv"
66
+
67
+ Base58Id.valid_base58?('qocqGYw9LEfu4WVujMzJv')
68
+ # => true
69
+
70
+ Base58Id.valid_base58?('IOl0-_')
71
+ # => false
72
+
73
+ # It accepts UUID String in many formats:
74
+ [
75
+ '058917af0d2e4755b0bd25b02b249824',
76
+ '058917AF0D2E4755B0BD25B02B249824',
77
+ '0x058917af0d2e4755b0bd25b02b249824',
78
+ ].map { |uuid| Base58Id.valid_uuid?(uuid) }
79
+ # => [true, true, true]
80
+
81
+ Base58Id::UUID_PATTERN
82
+ # => /\A(0x)?[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}\z/i
83
+ ```
84
+
85
+ ### Note
86
+
87
+ As it performs an integer conversion, Base58 leading zeros (represented by `A`)
88
+ are ignored/lost:
89
+
90
+ ```rb
91
+ Base58Id.base58_to_integer('A') == Base58Id.base58_to_integer('AAAAAA')
92
+ # => true
93
+
94
+ Base58Id.base58_to_integer('AAAAAAqocqGYw9LEfu4WVujMzJv') ==
95
+ Base58Id.base58_to_integer('qocqGYw9LEfu4WVujMzJv')
96
+ # => true
97
+
98
+ Base58Id.integer_to_base58(Base58Id.base58_to_integer('AAAAAAqocqGYw9LEfu4WVujMzJv'))
99
+ # => "qocqGYw9LEfu4WVujMzJv"
100
+ ```
101
+
102
+ And an empty Base58 String also represents zero:
103
+
104
+ ```rb
105
+ Base58Id.base58_to_integer('')
106
+ # => 0
107
+
108
+ Base58Id.base58_to_uuid('')
109
+ # => "00000000-0000-0000-0000-000000000000"
110
+
111
+ Base58Id.base58_to_integer('') == Base58Id.base58_to_integer('A')
112
+ # => true
113
+ ```
114
+
115
+ ## Tests
116
+
117
+ Run tests with:
118
+
119
+ ```sh
120
+ bundle exec rspec
121
+ ```
122
+
123
+ ## Linter
124
+
125
+ Check your code with:
126
+
127
+ ```sh
128
+ bundle exec rubocop
129
+ ```
data/base58_id.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/base58_id/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'base58_id'
7
+ s.version = Base58Id::VERSION
8
+ s.license = 'MIT'
9
+ s.summary = 'Convert an Integer ID or a UUID String to/from Base58 String'
10
+ s.homepage = 'https://github.com/elias19r/base58_id'
11
+ s.author = 'Elias Rodrigues'
12
+
13
+ s.files = Dir[
14
+ 'lib/**/*',
15
+ 'spec/**/*',
16
+ '.gitignore',
17
+ '.rubocop.yml',
18
+ 'Gemfile',
19
+ 'LICENSE',
20
+ 'README.md',
21
+ 'base58_id.gemspec'
22
+ ]
23
+
24
+ s.required_ruby_version = '>= 2.7'
25
+ s.metadata = {
26
+ 'source_code_uri' => "https://github.com/elias19r/base58_id/tree/v#{Base58Id::VERSION}",
27
+ 'rubygems_mfa_required' => 'true'
28
+ }
29
+
30
+ s.add_development_dependency 'bundler', '~> 2.1'
31
+ s.add_development_dependency 'pry-byebug', '~> 3.9'
32
+ s.add_development_dependency 'rspec', '~> 3.11'
33
+ s.add_development_dependency 'rubocop', '~> 1.31'
34
+ s.add_development_dependency 'rubocop-performance', '~> 1.14'
35
+ s.add_development_dependency 'rubocop-rspec', '~> 2.11'
36
+ s.add_development_dependency 'simplecov', '~> 0.21.0'
37
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Base58Id
4
+ VERSION = '1.0.0'
5
+ end
data/lib/base58_id.rb ADDED
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Base58Id
4
+ # Based on Base64 URL-safe alphabet but with I, O, l, 0, -, _ removed.
5
+ ALPHABET_58 = %w[
6
+ A B C D E F G H J K L M N P Q R S T U V W X Y Z
7
+ a b c d e f g h i j k m n o p q r s t u v w x y z
8
+ 1 2 3 4 5 6 7 8 9
9
+ ].freeze
10
+
11
+ ALPHABET_58_INVERT = ALPHABET_58.each_with_index.to_h
12
+ ALPHABET_58_CHARS = ALPHABET_58.join
13
+ CARET_ALPHABET_58_CHARS = "^#{ALPHABET_58_CHARS}"
14
+
15
+ UUID_PATTERN = /\A(0x)?[0-9a-f]{8}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{4}-?[0-9a-f]{12}\z/i.freeze
16
+ UUID_BYTES_FORMAT = '%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x'
17
+
18
+ def self.integer_to_base58(integer)
19
+ raise ArgumentError, 'argument must be an Integer' unless integer.is_a?(Integer)
20
+ raise ArgumentError, 'argument must be greater than or equal to zero' if integer.negative?
21
+
22
+ integer.digits(58).reduce('') { |str, digit| ALPHABET_58[digit] + str }
23
+ end
24
+
25
+ def self.base58_to_integer(base58)
26
+ raise ArgumentError, 'argument must be a valid Base58 String' unless valid_base58?(base58)
27
+
28
+ base58.chars.reduce(0) { |integer, digit| (integer * 58) + ALPHABET_58_INVERT[digit] }
29
+ end
30
+
31
+ def self.uuid_to_base58(uuid)
32
+ raise ArgumentError, 'argument must be a valid UUID String' unless valid_uuid?(uuid)
33
+
34
+ integer_to_base58(uuid_to_integer(uuid))
35
+ end
36
+
37
+ def self.base58_to_uuid(base58)
38
+ integer_to_uuid(base58_to_integer(base58))
39
+ end
40
+
41
+ def self.uuid_to_integer(uuid)
42
+ raise ArgumentError, 'argument must be a valid UUID String' unless valid_uuid?(uuid)
43
+
44
+ uuid.delete('-').to_i(16)
45
+ end
46
+
47
+ def self.integer_to_uuid(integer)
48
+ raise ArgumentError, 'argument must be an Integer' unless integer.is_a?(Integer)
49
+ raise ArgumentError, 'argument must be greater than or equal to zero' if integer.negative?
50
+
51
+ bytes_array = integer.digits(256)
52
+
53
+ raise ArgumentError, 'argument size must not require more than 16 bytes' if bytes_array.size > 16
54
+
55
+ while bytes_array.size < 16
56
+ bytes_array.push(0)
57
+ end
58
+
59
+ UUID_BYTES_FORMAT % bytes_array.reverse
60
+ end
61
+
62
+ def self.valid_base58?(value)
63
+ raise ArgumentError, 'argument must be a String' unless value.is_a?(String)
64
+
65
+ value.count(CARET_ALPHABET_58_CHARS).zero?
66
+ end
67
+
68
+ def self.valid_uuid?(value)
69
+ raise ArgumentError, 'argument must be a String' unless value.is_a?(String)
70
+
71
+ value.match?(UUID_PATTERN)
72
+ end
73
+ end
@@ -0,0 +1,494 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'base58_id'
5
+
6
+ RSpec.describe Base58Id do
7
+ it 'converts an Integer ID or a UUID String to/from Base58 String' do
8
+ base58 = generate_base58
9
+
10
+ expect(
11
+ described_class.integer_to_base58(
12
+ described_class.base58_to_integer(base58)
13
+ )
14
+ ).to eq(base58)
15
+
16
+ expect(
17
+ described_class.uuid_to_base58(
18
+ described_class.base58_to_uuid(base58)
19
+ )
20
+ ).to eq(base58)
21
+
22
+ base58_with_leading_zeros = "AAAAAAAA#{base58}"
23
+
24
+ expect(
25
+ described_class.integer_to_base58(
26
+ described_class.base58_to_integer(base58_with_leading_zeros)
27
+ )
28
+ ).to eq(base58)
29
+
30
+ expect(
31
+ described_class.uuid_to_base58(
32
+ described_class.base58_to_uuid(base58_with_leading_zeros)
33
+ )
34
+ ).to eq(base58)
35
+
36
+ base58_empty = ''
37
+
38
+ expect(
39
+ described_class.integer_to_base58(
40
+ described_class.base58_to_integer(base58_empty)
41
+ )
42
+ ).to eq('A')
43
+
44
+ expect(
45
+ described_class.uuid_to_base58(
46
+ described_class.base58_to_uuid(base58_empty)
47
+ )
48
+ ).to eq('A')
49
+
50
+ integer = generate_non_negative_integer
51
+
52
+ expect(
53
+ described_class.base58_to_integer(
54
+ described_class.integer_to_base58(integer)
55
+ )
56
+ ).to eq(integer)
57
+
58
+ expect(
59
+ described_class.uuid_to_integer(
60
+ described_class.integer_to_uuid(integer)
61
+ )
62
+ ).to eq(integer)
63
+
64
+ uuid = generate_uuid
65
+
66
+ expect(
67
+ described_class.base58_to_uuid(
68
+ described_class.uuid_to_base58(uuid)
69
+ )
70
+ ).to eq(uuid)
71
+
72
+ expect(
73
+ described_class.integer_to_uuid(
74
+ described_class.uuid_to_integer(uuid)
75
+ )
76
+ ).to eq(uuid)
77
+ end
78
+
79
+ describe '.integer_to_base58' do
80
+ context 'when argument is not an Integer' do
81
+ it 'raises an ArgumentError' do
82
+ non_integer = Object.new
83
+
84
+ expect do
85
+ described_class.integer_to_base58(non_integer)
86
+ end.to raise_error(ArgumentError, 'argument must be an Integer')
87
+ end
88
+ end
89
+
90
+ context 'when argument is an Integer but negative' do
91
+ it 'raises an ArgumentError' do
92
+ negative_integer = generate_negative_integer
93
+
94
+ expect do
95
+ described_class.integer_to_base58(negative_integer)
96
+ end.to raise_error(ArgumentError, 'argument must be greater than or equal to zero')
97
+ end
98
+ end
99
+
100
+ context 'when argument is a non-negative Integer' do
101
+ it 'formats unsigned integer as Base58 String' do
102
+ integer = generate_non_negative_integer
103
+
104
+ expect(described_class.integer_to_base58(integer).count("^#{base58_chars.join}")).to eq(0)
105
+
106
+ some_test_cases.each do |test_case|
107
+ expect(described_class.integer_to_base58(test_case[:integer])).to eq(test_case[:base58])
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '.base58_to_integer' do
114
+ context 'when value is not a valid Base58 String' do
115
+ it 'raises an ArgumentError' do
116
+ non_string = Object.new
117
+
118
+ expect do
119
+ described_class.base58_to_integer(non_string)
120
+ end.to raise_error(ArgumentError, 'argument must be a String')
121
+
122
+ non_base58_chars.each do |non_base58|
123
+ expect do
124
+ described_class.base58_to_integer(non_base58)
125
+ end.to raise_error(ArgumentError, 'argument must be a valid Base58 String')
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'when value is a valid Base58 String' do
131
+ it 'returns the corresponding Integer' do
132
+ big_base58 = generate_big_base58
133
+
134
+ expect(described_class.base58_to_integer(big_base58)).to be_an(Integer)
135
+
136
+ base58_empty = ''
137
+
138
+ expect(described_class.base58_to_integer(base58_empty)).to eq(0)
139
+
140
+ some_test_cases.each do |test_case|
141
+ expect(described_class.base58_to_integer(test_case[:base58])).to eq(test_case[:integer])
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ describe '.uuid_to_base58' do
148
+ context 'when argument is not a valid UUID String' do
149
+ it 'raises an ArgumentError' do
150
+ non_string = Object.new
151
+
152
+ expect do
153
+ described_class.uuid_to_base58(non_string)
154
+ end.to raise_error(ArgumentError, 'argument must be a String')
155
+
156
+ non_uuid = '123'
157
+
158
+ expect do
159
+ described_class.uuid_to_base58(non_uuid)
160
+ end.to raise_error(ArgumentError, 'argument must be a valid UUID String')
161
+ end
162
+ end
163
+
164
+ context 'when argument is a valid UUID String' do
165
+ it 'returns the corresponding Base58' do
166
+ uuid = generate_uuid
167
+
168
+ expect(described_class.uuid_to_base58(uuid).count("^#{base58_chars.join}")).to eq(0)
169
+
170
+ some_test_cases.each do |test_case|
171
+ expect(described_class.uuid_to_base58(test_case[:uuid])).to eq(test_case[:base58])
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ describe '.base58_to_uuid' do
178
+ context 'when value is not a valid Base58 String' do
179
+ it 'raises an ArgumentError' do
180
+ non_string = Object.new
181
+
182
+ expect do
183
+ described_class.base58_to_uuid(non_string)
184
+ end.to raise_error(ArgumentError, 'argument must be a String')
185
+
186
+ non_base58_chars.each do |non_base58|
187
+ expect do
188
+ described_class.base58_to_uuid(non_base58)
189
+ end.to raise_error(ArgumentError, 'argument must be a valid Base58 String')
190
+ end
191
+ end
192
+ end
193
+
194
+ context 'when value is a valid Base58 String but numeric value requires more than 16 bytes' do
195
+ it 'raises an ArgumentError' do
196
+ big_base58 = generate_big_base58
197
+
198
+ expect do
199
+ described_class.base58_to_uuid(big_base58)
200
+ end.to raise_error(ArgumentError, 'argument size must not require more than 16 bytes')
201
+ end
202
+ end
203
+
204
+ context 'when value is a valid Base58 String and numeric value does not require more than 16 bytes' do
205
+ it 'returns the corresponding UUID String' do
206
+ base58 = generate_base58
207
+
208
+ expect(described_class.base58_to_uuid(base58))
209
+ .to match(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/)
210
+
211
+ base58_empty = ''
212
+
213
+ expect(described_class.base58_to_uuid(base58_empty)).to eq('00000000-0000-0000-0000-000000000000')
214
+
215
+ some_test_cases.each do |test_case|
216
+ expect(described_class.base58_to_uuid(test_case[:base58])).to eq(test_case[:uuid])
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ describe '.uuid_to_integer' do
223
+ context 'when argument is not a valid UUID String' do
224
+ it 'raises an ArgumentError' do
225
+ non_string = Object.new
226
+
227
+ expect do
228
+ described_class.uuid_to_integer(non_string)
229
+ end.to raise_error(ArgumentError, 'argument must be a String')
230
+
231
+ non_uuid = '123'
232
+
233
+ expect do
234
+ described_class.uuid_to_integer(non_uuid)
235
+ end.to raise_error(ArgumentError, 'argument must be a valid UUID String')
236
+ end
237
+ end
238
+
239
+ context 'when argument is a valid UUID String' do
240
+ it 'returns the corresponding Integer' do
241
+ uuid = generate_uuid
242
+
243
+ expect(described_class.uuid_to_integer(uuid)).to be_an(Integer)
244
+
245
+ some_test_cases.each do |test_case|
246
+ expect(described_class.uuid_to_integer(test_case[:uuid])).to eq(test_case[:integer])
247
+ end
248
+ end
249
+ end
250
+ end
251
+
252
+ describe '.integer_to_uuid' do
253
+ context 'when argument is not an Integer' do
254
+ it 'raises an ArgumentError' do
255
+ non_integer = Object.new
256
+
257
+ expect do
258
+ described_class.integer_to_uuid(non_integer)
259
+ end.to raise_error(ArgumentError, 'argument must be an Integer')
260
+ end
261
+ end
262
+
263
+ context 'when argument is an Integer but negative' do
264
+ it 'raises an ArgumentError' do
265
+ negative_integer = generate_negative_integer
266
+
267
+ expect do
268
+ described_class.integer_to_uuid(negative_integer)
269
+ end.to raise_error(ArgumentError, 'argument must be greater than or equal to zero')
270
+ end
271
+ end
272
+
273
+ context 'when argument is a non-negative Integer but requires more than 16 bytes' do
274
+ it 'raises an ArgumentError' do
275
+ big_integer = generate_big_integer
276
+
277
+ expect do
278
+ described_class.integer_to_uuid(big_integer)
279
+ end.to raise_error(ArgumentError, 'argument size must not require more than 16 bytes')
280
+ end
281
+ end
282
+
283
+ context 'when argument is a non-negative Integer and does not require more than 16 bytes' do
284
+ it 'formats 128-bit unsigned integer as UUID String' do
285
+ uuid = generate_uuid
286
+ integer = uuid.delete('-').to_i(16)
287
+
288
+ expect(described_class.integer_to_uuid(integer)).to eq(uuid)
289
+
290
+ some_test_cases.each do |test_case|
291
+ expect(described_class.integer_to_uuid(test_case[:integer])).to eq(test_case[:uuid])
292
+ end
293
+ end
294
+ end
295
+ end
296
+
297
+ describe '.valid_base58?' do
298
+ context 'when value is not a String' do
299
+ it 'raises an ArgumentError' do
300
+ value = Object.new
301
+
302
+ expect do
303
+ described_class.valid_base58?(value)
304
+ end.to raise_error(ArgumentError, 'argument must be a String')
305
+ end
306
+ end
307
+
308
+ context 'when value is a String but contains chars not present in the Base58 alphabet' do
309
+ it 'returns false' do
310
+ non_base58_chars.each do |value|
311
+ expect(described_class.valid_base58?(value)).to be(false)
312
+ end
313
+ end
314
+ end
315
+
316
+ context 'when value is a String and contains only chars present in the Base58 alphabet' do
317
+ it 'returns true' do
318
+ base58_chars.each do |value|
319
+ expect(described_class.valid_base58?(value)).to be(true)
320
+ end
321
+
322
+ value = ''
323
+
324
+ expect(described_class.valid_base58?(value)).to be(true)
325
+
326
+ value = generate_big_base58
327
+
328
+ expect(described_class.valid_base58?(value)).to be(true)
329
+ end
330
+ end
331
+ end
332
+
333
+ describe '.valid_uuid?' do
334
+ context 'when value is not a String' do
335
+ it 'raises an ArgumentError' do
336
+ value = Object.new
337
+
338
+ expect do
339
+ described_class.valid_uuid?(value)
340
+ end.to raise_error(ArgumentError, 'argument must be a String')
341
+ end
342
+ end
343
+
344
+ context 'when value is a String but does not match the UUID pattern' do
345
+ it 'returns false' do
346
+ value = '123'
347
+
348
+ expect(described_class.valid_uuid?(value)).to be(false)
349
+ end
350
+ end
351
+
352
+ context 'when value is a String and matches the UUID pattern' do
353
+ it 'returns true' do
354
+ value = generate_uuid
355
+
356
+ expect(described_class.valid_uuid?(value)).to be(true)
357
+ expect(described_class.valid_uuid?(value.delete('-'))).to be(true)
358
+ expect(described_class.valid_uuid?("0x#{value.delete('-')}")).to be(true)
359
+ expect(described_class.valid_uuid?("0X#{value.delete('-')}")).to be(true)
360
+
361
+ expect(described_class.valid_uuid?(value.upcase)).to be(true)
362
+ expect(described_class.valid_uuid?(value.upcase.delete('-'))).to be(true)
363
+ expect(described_class.valid_uuid?("0x#{value.upcase.delete('-')}")).to be(true)
364
+ expect(described_class.valid_uuid?("0X#{value.upcase.delete('-')}")).to be(true)
365
+ end
366
+ end
367
+ end
368
+
369
+ def generate_uuid
370
+ SecureRandom.uuid
371
+ end
372
+
373
+ def generate_negative_integer
374
+ rand((-2**63)..-1)
375
+ end
376
+
377
+ def generate_non_negative_integer
378
+ rand(0..((2**63) - 1))
379
+ end
380
+
381
+ def generate_big_integer
382
+ rand((2**128)..(2**144))
383
+ end
384
+
385
+ def generate_base58
386
+ generate_non_zero_base58_digit + base58_chars.sample(19).join
387
+ end
388
+
389
+ def generate_big_base58
390
+ generate_non_zero_base58_digit + base58_chars.sample(24).join
391
+ end
392
+
393
+ def generate_non_zero_base58_digit
394
+ base58_chars[1..].sample
395
+ end
396
+
397
+ let(:base58_chars) do
398
+ %w[
399
+ A B C D E F G H J K L M N P Q R S T U V W X Y Z
400
+ a b c d e f g h i j k m n o p q r s t u v w x y z
401
+ 1 2 3 4 5 6 7 8 9
402
+ ]
403
+ end
404
+
405
+ let(:non_base58_chars) do
406
+ (' '..'~').to_a - base58_chars
407
+ end
408
+
409
+ let(:some_test_cases) do
410
+ [
411
+ { integer: 0, base58: 'A', uuid: '00000000-0000-0000-0000-000000000000' },
412
+ { integer: 1, base58: 'B', uuid: '00000000-0000-0000-0000-000000000001' },
413
+ { integer: 2, base58: 'C', uuid: '00000000-0000-0000-0000-000000000002' },
414
+ { integer: 3, base58: 'D', uuid: '00000000-0000-0000-0000-000000000003' },
415
+ { integer: 4, base58: 'E', uuid: '00000000-0000-0000-0000-000000000004' },
416
+ { integer: 5, base58: 'F', uuid: '00000000-0000-0000-0000-000000000005' },
417
+ { integer: 6, base58: 'G', uuid: '00000000-0000-0000-0000-000000000006' },
418
+ { integer: 7, base58: 'H', uuid: '00000000-0000-0000-0000-000000000007' },
419
+ { integer: 8, base58: 'J', uuid: '00000000-0000-0000-0000-000000000008' },
420
+ { integer: 9, base58: 'K', uuid: '00000000-0000-0000-0000-000000000009' },
421
+ { integer: 10, base58: 'L', uuid: '00000000-0000-0000-0000-00000000000a' },
422
+ { integer: 11, base58: 'M', uuid: '00000000-0000-0000-0000-00000000000b' },
423
+ { integer: 12, base58: 'N', uuid: '00000000-0000-0000-0000-00000000000c' },
424
+ { integer: 13, base58: 'P', uuid: '00000000-0000-0000-0000-00000000000d' },
425
+ { integer: 14, base58: 'Q', uuid: '00000000-0000-0000-0000-00000000000e' },
426
+ { integer: 15, base58: 'R', uuid: '00000000-0000-0000-0000-00000000000f' },
427
+ { integer: 16, base58: 'S', uuid: '00000000-0000-0000-0000-000000000010' },
428
+ { integer: 17, base58: 'T', uuid: '00000000-0000-0000-0000-000000000011' },
429
+ { integer: 18, base58: 'U', uuid: '00000000-0000-0000-0000-000000000012' },
430
+ { integer: 19, base58: 'V', uuid: '00000000-0000-0000-0000-000000000013' },
431
+ { integer: 20, base58: 'W', uuid: '00000000-0000-0000-0000-000000000014' },
432
+ { integer: 21, base58: 'X', uuid: '00000000-0000-0000-0000-000000000015' },
433
+ { integer: 22, base58: 'Y', uuid: '00000000-0000-0000-0000-000000000016' },
434
+ { integer: 23, base58: 'Z', uuid: '00000000-0000-0000-0000-000000000017' },
435
+ { integer: 24, base58: 'a', uuid: '00000000-0000-0000-0000-000000000018' },
436
+ { integer: 25, base58: 'b', uuid: '00000000-0000-0000-0000-000000000019' },
437
+ { integer: 26, base58: 'c', uuid: '00000000-0000-0000-0000-00000000001a' },
438
+ { integer: 27, base58: 'd', uuid: '00000000-0000-0000-0000-00000000001b' },
439
+ { integer: 28, base58: 'e', uuid: '00000000-0000-0000-0000-00000000001c' },
440
+ { integer: 29, base58: 'f', uuid: '00000000-0000-0000-0000-00000000001d' },
441
+ { integer: 30, base58: 'g', uuid: '00000000-0000-0000-0000-00000000001e' },
442
+ { integer: 31, base58: 'h', uuid: '00000000-0000-0000-0000-00000000001f' },
443
+ { integer: 32, base58: 'i', uuid: '00000000-0000-0000-0000-000000000020' },
444
+ { integer: 33, base58: 'j', uuid: '00000000-0000-0000-0000-000000000021' },
445
+ { integer: 34, base58: 'k', uuid: '00000000-0000-0000-0000-000000000022' },
446
+ { integer: 35, base58: 'm', uuid: '00000000-0000-0000-0000-000000000023' },
447
+ { integer: 36, base58: 'n', uuid: '00000000-0000-0000-0000-000000000024' },
448
+ { integer: 37, base58: 'o', uuid: '00000000-0000-0000-0000-000000000025' },
449
+ { integer: 38, base58: 'p', uuid: '00000000-0000-0000-0000-000000000026' },
450
+ { integer: 39, base58: 'q', uuid: '00000000-0000-0000-0000-000000000027' },
451
+ { integer: 40, base58: 'r', uuid: '00000000-0000-0000-0000-000000000028' },
452
+ { integer: 41, base58: 's', uuid: '00000000-0000-0000-0000-000000000029' },
453
+ { integer: 42, base58: 't', uuid: '00000000-0000-0000-0000-00000000002a' },
454
+ { integer: 43, base58: 'u', uuid: '00000000-0000-0000-0000-00000000002b' },
455
+ { integer: 44, base58: 'v', uuid: '00000000-0000-0000-0000-00000000002c' },
456
+ { integer: 45, base58: 'w', uuid: '00000000-0000-0000-0000-00000000002d' },
457
+ { integer: 46, base58: 'x', uuid: '00000000-0000-0000-0000-00000000002e' },
458
+ { integer: 47, base58: 'y', uuid: '00000000-0000-0000-0000-00000000002f' },
459
+ { integer: 48, base58: 'z', uuid: '00000000-0000-0000-0000-000000000030' },
460
+ { integer: 49, base58: '1', uuid: '00000000-0000-0000-0000-000000000031' },
461
+ { integer: 50, base58: '2', uuid: '00000000-0000-0000-0000-000000000032' },
462
+ { integer: 51, base58: '3', uuid: '00000000-0000-0000-0000-000000000033' },
463
+ { integer: 52, base58: '4', uuid: '00000000-0000-0000-0000-000000000034' },
464
+ { integer: 53, base58: '5', uuid: '00000000-0000-0000-0000-000000000035' },
465
+ { integer: 54, base58: '6', uuid: '00000000-0000-0000-0000-000000000036' },
466
+ { integer: 55, base58: '7', uuid: '00000000-0000-0000-0000-000000000037' },
467
+ { integer: 56, base58: '8', uuid: '00000000-0000-0000-0000-000000000038' },
468
+ { integer: 57, base58: '9', uuid: '00000000-0000-0000-0000-000000000039' },
469
+ { integer: 58, base58: 'BA', uuid: '00000000-0000-0000-0000-00000000003a' },
470
+
471
+ { integer: 58 * 2, base58: 'CA', uuid: '00000000-0000-0000-0000-000000000074' },
472
+ { integer: 58 * 3, base58: 'DA', uuid: '00000000-0000-0000-0000-0000000000ae' },
473
+ { integer: 58 * 10, base58: 'LA', uuid: '00000000-0000-0000-0000-000000000244' },
474
+ { integer: 58 * 20, base58: 'WA', uuid: '00000000-0000-0000-0000-000000000488' },
475
+ { integer: 58 * 58, base58: 'BAA', uuid: '00000000-0000-0000-0000-000000000d24' },
476
+
477
+ { integer: 231, base58: 'D9', uuid: '00000000-0000-0000-0000-0000000000e7' },
478
+ { integer: 232, base58: 'EA', uuid: '00000000-0000-0000-0000-0000000000e8' },
479
+ { integer: 255, base58: 'EZ', uuid: '00000000-0000-0000-0000-0000000000ff' },
480
+ { integer: 256, base58: 'Ea', uuid: '00000000-0000-0000-0000-000000000100' },
481
+ { integer: 280, base58: 'Ez', uuid: '00000000-0000-0000-0000-000000000118' },
482
+ { integer: 281, base58: 'E1', uuid: '00000000-0000-0000-0000-000000000119' },
483
+ { integer: 289, base58: 'E9', uuid: '00000000-0000-0000-0000-000000000121' },
484
+ { integer: 290, base58: 'FA', uuid: '00000000-0000-0000-0000-000000000122' },
485
+
486
+ { integer: (58**10) - 1, base58: '9999999999', uuid: '00000000-0000-0000-05fa-8624c7fba3ff' },
487
+ { integer: 58**10, base58: 'BAAAAAAAAAA', uuid: '00000000-0000-0000-05fa-8624c7fba400' },
488
+ { integer: 58**20, base58: 'BAAAAAAAAAAAAAAAAAAAA', uuid: '0023be67-b5f0-f288-9aaf-505301100000' },
489
+
490
+ { integer: (2**128) - 2, base58: 'hmep7uZkFTa9zuEuQB3XV4', uuid: 'ffffffff-ffff-ffff-ffff-fffffffffffe' },
491
+ { integer: (2**128) - 1, base58: 'hmep7uZkFTa9zuEuQB3XV5', uuid: 'ffffffff-ffff-ffff-ffff-ffffffffffff' },
492
+ ]
493
+ end
494
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'simplecov'
4
+ SimpleCov.start
5
+
6
+ require 'securerandom'
7
+ require 'rspec'
8
+ require 'pry-byebug'
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: base58_id
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Elias Rodrigues
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-06-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry-byebug
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.9'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rubocop
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.31'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.31'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop-performance
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.14'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.14'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop-rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.11'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.11'
97
+ - !ruby/object:Gem::Dependency
98
+ name: simplecov
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.21.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.21.0
111
+ description:
112
+ email:
113
+ executables: []
114
+ extensions: []
115
+ extra_rdoc_files: []
116
+ files:
117
+ - ".gitignore"
118
+ - ".rubocop.yml"
119
+ - Gemfile
120
+ - LICENSE
121
+ - README.md
122
+ - base58_id.gemspec
123
+ - lib/base58_id.rb
124
+ - lib/base58_id/version.rb
125
+ - spec/base58_id_spec.rb
126
+ - spec/spec_helper.rb
127
+ homepage: https://github.com/elias19r/base58_id
128
+ licenses:
129
+ - MIT
130
+ metadata:
131
+ source_code_uri: https://github.com/elias19r/base58_id/tree/v1.0.0
132
+ rubygems_mfa_required: 'true'
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '2.7'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubygems_version: 3.1.6
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Convert an Integer ID or a UUID String to/from Base58 String
152
+ test_files: []