sqids 0.1.2 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +14 -9
- data/lib/sqids.rb +50 -59
- data/spec/alphabet_spec.rb +9 -3
- data/spec/blocklist_spec.rb +37 -22
- data/spec/encoding_spec.rb +32 -37
- data/spec/minlength_spec.rb +39 -13
- data/sqids.gemspec +1 -1
- metadata +2 -3
- data/spec/uniques_spec.rb +0 -62
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8738a24714961747ba8fb004ffba899f34889de1e65ba3932a59a50b85558aef
|
4
|
+
data.tar.gz: 6b26a781bd07bc2b805af36d29bd0cc838351569a3a44988e171b6083e75a3e4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4dbacb914b13fc4a8125f3b313359db82afe1251d5f9ef7d5eaa724fb8b474521e102ec00ef938d621259c52541bf9f6b63647ef1d24243e3bd72a1ab07f6926
|
7
|
+
data.tar.gz: 33660c785f18c7a2692e3e184c275c104c73dd72ac5aa6bca940f1da7dda1a5b3f66c30f55c4010d488bec694bafd0329979bcce3f3e0db20bfb87bdf01a6f4b
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
**v0.2.1:**
|
4
|
+
- Improvement: speeding up Sqids.new: [[PR #7](https://github.com/sqids/sqids-ruby/pull/7)] (thanks to [@lawrencegripper](https://github.com/lawrencegripper))
|
5
|
+
|
6
|
+
**v0.2.0:** **⚠️ BREAKING CHANGE**
|
7
|
+
- **Breaking change**: IDs change. Algorithm has been fine-tuned for better performance [[Issue #11](https://github.com/sqids/sqids-spec/issues/11)]
|
8
|
+
- `alphabet` cannot contain multibyte characters
|
9
|
+
- `min_length` upper limit has increased from alphabet length to `255`
|
10
|
+
- Max blocklist re-encoding attempts has been capped at the length of the alphabet - 1
|
11
|
+
- Minimum alphabet length has changed from 5 to 3
|
12
|
+
- `min_value()` and `max_value()` functions have been removed
|
13
|
+
|
3
14
|
**v0.1.2:**
|
4
15
|
- Bug fix: spec update (PR #7): blocklist filtering in uppercase-only alphabet [[PR #7](https://github.com/sqids/sqids-spec/pull/7)]
|
5
16
|
- Lower uniques test from 1_000_000 to 10_000
|
data/README.md
CHANGED
@@ -55,37 +55,42 @@ Simple encode & decode:
|
|
55
55
|
|
56
56
|
```ruby
|
57
57
|
sqids = Sqids.new
|
58
|
-
id = sqids.encode([1, 2, 3]) # '
|
58
|
+
id = sqids.encode([1, 2, 3]) # '86Rf07'
|
59
59
|
numbers = sqids.decode(id) # [1, 2, 3]
|
60
60
|
```
|
61
61
|
|
62
62
|
> **Note**
|
63
63
|
> 🚧 Because of the algorithm's design, **multiple IDs can decode back into the same sequence of numbers**. If it's important to your design that IDs are canonical, you have to manually re-encode decoded numbers and check that the generated ID matches.
|
64
64
|
|
65
|
-
|
65
|
+
Enforce a *minimum* length for IDs:
|
66
66
|
|
67
67
|
```ruby
|
68
|
-
sqids = Sqids.new(
|
69
|
-
id = sqids.encode([1, 2, 3]) # '
|
68
|
+
sqids = Sqids.new(min_length: 10)
|
69
|
+
id = sqids.encode([1, 2, 3]) # '86Rf07xd4z'
|
70
70
|
numbers = sqids.decode(id) # [1, 2, 3]
|
71
71
|
```
|
72
72
|
|
73
|
-
|
73
|
+
Randomize IDs by providing a custom alphabet:
|
74
74
|
|
75
75
|
```ruby
|
76
|
-
sqids = Sqids.new(
|
77
|
-
id = sqids.encode([1, 2, 3]) # '
|
76
|
+
sqids = Sqids.new(alphabet: 'FxnXM1kBN6cuhsAvjW3Co7l2RePyY8DwaU04Tzt9fHQrqSVKdpimLGIJOgb5ZE')
|
77
|
+
id = sqids.encode([1, 2, 3]) # 'B4aajs'
|
78
78
|
numbers = sqids.decode(id) # [1, 2, 3]
|
79
79
|
```
|
80
80
|
|
81
81
|
Prevent specific words from appearing anywhere in the auto-generated IDs:
|
82
82
|
|
83
83
|
```ruby
|
84
|
-
sqids = Sqids.new(blocklist: Set.new(%w[
|
85
|
-
id = sqids.encode([1, 2, 3]) # '
|
84
|
+
sqids = Sqids.new(blocklist: Set.new(%w[86Rf07]))
|
85
|
+
id = sqids.encode([1, 2, 3]) # 'se8ojk'
|
86
86
|
numbers = sqids.decode(id) # [1, 2, 3]
|
87
87
|
```
|
88
88
|
|
89
|
+
> [!WARNING]
|
90
|
+
> If you provide a large custom blocklist and/or custom alphabet, calls to `Sqid.new` can take
|
91
|
+
> ~1ms. You should create a singleton instance of `Sqid` at service start and reusing that rather than
|
92
|
+
> repeatedly calling `Squid.new`
|
93
|
+
|
89
94
|
## 📝 License
|
90
95
|
|
91
96
|
[MIT](LICENSE)
|
data/lib/sqids.rb
CHANGED
@@ -15,21 +15,33 @@ class Sqids
|
|
15
15
|
min_length = options[:min_length] || DEFAULT_MIN_LENGTH
|
16
16
|
blocklist = options[:blocklist] || DEFAULT_BLOCKLIST
|
17
17
|
|
18
|
-
raise ArgumentError, 'Alphabet
|
18
|
+
raise ArgumentError, 'Alphabet cannot contain multibyte characters' if contains_multibyte_chars(alphabet)
|
19
|
+
raise ArgumentError, 'Alphabet length must be at least 3' if alphabet.length < 3
|
19
20
|
|
20
21
|
if alphabet.chars.uniq.size != alphabet.length
|
21
22
|
raise ArgumentError,
|
22
23
|
'Alphabet must contain unique characters'
|
23
24
|
end
|
24
25
|
|
25
|
-
|
26
|
+
min_length_limit = 255
|
27
|
+
unless min_length.is_a?(Integer) && min_length >= 0 && min_length <= min_length_limit
|
26
28
|
raise TypeError,
|
27
|
-
"Minimum length has to be between
|
29
|
+
"Minimum length has to be between 0 and #{min_length_limit}"
|
28
30
|
end
|
29
31
|
|
30
|
-
filtered_blocklist = blocklist
|
31
|
-
|
32
|
-
|
32
|
+
filtered_blocklist = if blocklist == DEFAULT_BLOCKLIST && alphabet == DEFAULT_ALPHABET
|
33
|
+
# If the blocklist is the default one, we don't need to filter it
|
34
|
+
# we already know it's valid (lowercase and words longer than 3 chars)
|
35
|
+
blocklist
|
36
|
+
else
|
37
|
+
# Downcase the alphabet once, rather than in the loop, to save significant time
|
38
|
+
# with large blocklists
|
39
|
+
downcased_alphabet = alphabet.downcase.chars
|
40
|
+
# Filter the blocklist
|
41
|
+
blocklist.select do |word|
|
42
|
+
word.length >= 3 && (word.downcase.chars - downcased_alphabet).empty?
|
43
|
+
end.to_set(&:downcase)
|
44
|
+
end
|
33
45
|
|
34
46
|
@alphabet = shuffle(alphabet)
|
35
47
|
@min_length = min_length
|
@@ -39,13 +51,13 @@ class Sqids
|
|
39
51
|
def encode(numbers)
|
40
52
|
return '' if numbers.empty?
|
41
53
|
|
42
|
-
in_range_numbers = numbers.select { |n| n >=
|
54
|
+
in_range_numbers = numbers.select { |n| n >= 0 && n <= Sqids.max_value }
|
43
55
|
unless in_range_numbers.length == numbers.length
|
44
56
|
raise ArgumentError,
|
45
|
-
"Encoding supports numbers between
|
57
|
+
"Encoding supports numbers between 0 and #{Sqids.max_value}"
|
46
58
|
end
|
47
59
|
|
48
|
-
encode_numbers(in_range_numbers
|
60
|
+
encode_numbers(in_range_numbers)
|
49
61
|
end
|
50
62
|
|
51
63
|
def decode(id)
|
@@ -61,26 +73,18 @@ class Sqids
|
|
61
73
|
prefix = id[0]
|
62
74
|
offset = @alphabet.index(prefix)
|
63
75
|
alphabet = @alphabet.slice(offset, @alphabet.length) + @alphabet.slice(0, offset)
|
64
|
-
|
65
|
-
alphabet = alphabet.slice(2, alphabet.length)
|
76
|
+
alphabet = alphabet.reverse
|
66
77
|
|
67
78
|
id = id[1, id.length]
|
68
79
|
|
69
|
-
partition_index = id.index(partition)
|
70
|
-
if !partition_index.nil? && partition_index.positive? && partition_index < id.length - 1
|
71
|
-
id = id[partition_index + 1, id.length]
|
72
|
-
alphabet = shuffle(alphabet)
|
73
|
-
end
|
74
|
-
|
75
80
|
while id.length.positive?
|
76
|
-
separator = alphabet[
|
77
|
-
chunks = id.split(separator, 2)
|
81
|
+
separator = alphabet[0]
|
78
82
|
|
83
|
+
chunks = id.split(separator, 2)
|
79
84
|
if chunks.any?
|
80
|
-
|
81
|
-
return [] unless chunks[0].chars.all? { |c| alphabet_without_separator.include?(c) }
|
85
|
+
return ret if chunks[0] == ''
|
82
86
|
|
83
|
-
ret.push(to_number(chunks[0],
|
87
|
+
ret.push(to_number(chunks[0], alphabet.slice(1, alphabet.length - 1)))
|
84
88
|
alphabet = shuffle(alphabet) if chunks.length > 1
|
85
89
|
end
|
86
90
|
|
@@ -90,14 +94,6 @@ class Sqids
|
|
90
94
|
ret
|
91
95
|
end
|
92
96
|
|
93
|
-
def self.min_value
|
94
|
-
0
|
95
|
-
end
|
96
|
-
|
97
|
-
def self.max_value
|
98
|
-
defined?(Integer::MAX) ? Integer::MAX : ((2**((0.size * 8) - 2)) - 1)
|
99
|
-
end
|
100
|
-
|
101
97
|
private
|
102
98
|
|
103
99
|
def shuffle(alphabet)
|
@@ -115,59 +111,42 @@ class Sqids
|
|
115
111
|
chars.join
|
116
112
|
end
|
117
113
|
|
118
|
-
def encode_numbers(numbers,
|
114
|
+
def encode_numbers(numbers, increment: 0)
|
115
|
+
raise ArgumentError, 'Reached max attempts to re-generate the ID' if increment > @alphabet.length
|
116
|
+
|
119
117
|
offset = numbers.length
|
120
118
|
numbers.each_with_index do |v, i|
|
121
119
|
offset += @alphabet[v % @alphabet.length].ord + i
|
122
120
|
end
|
123
121
|
offset = offset % @alphabet.length
|
122
|
+
offset = (offset + increment) % @alphabet.length
|
124
123
|
|
125
124
|
alphabet = @alphabet.slice(offset, @alphabet.length) + @alphabet.slice(0, offset)
|
126
125
|
prefix = alphabet[0]
|
127
|
-
|
128
|
-
alphabet = alphabet.slice(2, alphabet.length)
|
126
|
+
alphabet = alphabet.reverse
|
129
127
|
ret = [prefix]
|
130
128
|
|
131
129
|
numbers.each_with_index do |num, i|
|
132
|
-
|
133
|
-
ret.push(to_id(num, alphabet_without_separator))
|
130
|
+
ret.push(to_id(num, alphabet.slice(1, alphabet.length - 1)))
|
134
131
|
|
135
132
|
next unless i < numbers.length - 1
|
136
133
|
|
137
|
-
|
138
|
-
if partitioned && i.zero?
|
139
|
-
ret.push(partition)
|
140
|
-
else
|
141
|
-
ret.push(separator)
|
142
|
-
end
|
143
|
-
|
134
|
+
ret.push(alphabet[0])
|
144
135
|
alphabet = shuffle(alphabet)
|
145
136
|
end
|
146
137
|
|
147
138
|
id = ret.join
|
148
139
|
|
149
140
|
if @min_length > id.length
|
150
|
-
|
151
|
-
numbers = [0] + numbers
|
152
|
-
id = encode_numbers(numbers, partitioned: true)
|
153
|
-
end
|
141
|
+
id += alphabet[0]
|
154
142
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
id.length - 1]
|
143
|
+
while (@min_length - id.length).positive?
|
144
|
+
alphabet = shuffle(alphabet)
|
145
|
+
id += alphabet.slice(0, [@min_length - id.length, alphabet.length].min)
|
159
146
|
end
|
160
147
|
end
|
161
148
|
|
162
|
-
if blocked_id?(id)
|
163
|
-
if partitioned
|
164
|
-
numbers[0] += 1
|
165
|
-
else
|
166
|
-
numbers = [0] + numbers
|
167
|
-
end
|
168
|
-
|
169
|
-
id = encode_numbers(numbers, partitioned: true)
|
170
|
-
end
|
149
|
+
id = encode_numbers(numbers, increment: increment + 1) if blocked_id?(id)
|
171
150
|
|
172
151
|
id
|
173
152
|
end
|
@@ -206,4 +185,16 @@ class Sqids
|
|
206
185
|
end
|
207
186
|
end
|
208
187
|
end
|
188
|
+
|
189
|
+
def contains_multibyte_chars(input_str)
|
190
|
+
input_str.each_char do |char|
|
191
|
+
return true if char.bytesize > 1
|
192
|
+
end
|
193
|
+
|
194
|
+
false
|
195
|
+
end
|
196
|
+
|
197
|
+
def self.max_value
|
198
|
+
defined?(Integer::MAX) ? Integer::MAX : ((2**((0.size * 8) - 2)) - 1)
|
199
|
+
end
|
209
200
|
end
|
data/spec/alphabet_spec.rb
CHANGED
@@ -8,14 +8,14 @@ describe Sqids do
|
|
8
8
|
sqids = Sqids.new(alphabet: '0123456789abcdef')
|
9
9
|
|
10
10
|
numbers = [1, 2, 3]
|
11
|
-
id = '
|
11
|
+
id = '489158'
|
12
12
|
|
13
13
|
expect(sqids.encode(numbers)).to eq(id)
|
14
14
|
expect(sqids.decode(id)).to eq(numbers)
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'decodes after encoding with a short alphabet' do
|
18
|
-
sqids = Sqids.new(alphabet: '
|
18
|
+
sqids = Sqids.new(alphabet: 'abc')
|
19
19
|
|
20
20
|
numbers = [1, 2, 3]
|
21
21
|
encoded = sqids.encode(numbers)
|
@@ -33,6 +33,12 @@ describe Sqids do
|
|
33
33
|
expect(sqids.decode(encoded)).to eq(numbers)
|
34
34
|
end
|
35
35
|
|
36
|
+
it 'fails when alphabet has multibyte characters' do
|
37
|
+
expect do
|
38
|
+
Sqids.new(alphabet: 'ë1092')
|
39
|
+
end.to raise_error(ArgumentError)
|
40
|
+
end
|
41
|
+
|
36
42
|
it 'fails when alphabet characters are repeated' do
|
37
43
|
expect do
|
38
44
|
Sqids.new(alphabet: 'aabcdefg')
|
@@ -41,7 +47,7 @@ describe Sqids do
|
|
41
47
|
|
42
48
|
it 'fails when alphabet is too short' do
|
43
49
|
expect do
|
44
|
-
Sqids.new(alphabet: '
|
50
|
+
Sqids.new(alphabet: 'ab')
|
45
51
|
end.to raise_error(ArgumentError)
|
46
52
|
end
|
47
53
|
end
|
data/spec/blocklist_spec.rb
CHANGED
@@ -8,59 +8,74 @@ describe Sqids do
|
|
8
8
|
it 'uses default blocklist if no custom blocklist is provided' do
|
9
9
|
sqids = Sqids.new
|
10
10
|
|
11
|
-
expect(sqids.decode('
|
12
|
-
expect(sqids.encode([
|
11
|
+
expect(sqids.decode('aho1e')).to eq([4_572_721])
|
12
|
+
expect(sqids.encode([4_572_721])).to eq('JExTR')
|
13
13
|
end
|
14
14
|
|
15
15
|
it 'does not use any blocklist if an empty blocklist is provided' do
|
16
16
|
sqids = Sqids.new(blocklist: Set.new([]))
|
17
17
|
|
18
|
-
expect(sqids.decode('
|
19
|
-
expect(sqids.encode([
|
18
|
+
expect(sqids.decode('aho1e')).to eq([4_572_721])
|
19
|
+
expect(sqids.encode([4_572_721])).to eq('aho1e')
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'uses provided blocklist if non-empty blocklist is provided' do
|
23
|
-
sqids = Sqids.new(blocklist: Set.new(['
|
23
|
+
sqids = Sqids.new(blocklist: Set.new(['ArUO']))
|
24
24
|
|
25
|
-
expect(sqids.decode('
|
26
|
-
expect(sqids.encode([
|
25
|
+
expect(sqids.decode('aho1e')).to eq([4_572_721])
|
26
|
+
expect(sqids.encode([4_572_721])).to eq('aho1e')
|
27
27
|
|
28
|
-
expect(sqids.decode('
|
29
|
-
expect(sqids.encode([100_000])).to eq('
|
30
|
-
expect(sqids.decode('
|
28
|
+
expect(sqids.decode('ArUO')).to eq([100_000])
|
29
|
+
expect(sqids.encode([100_000])).to eq('QyG4')
|
30
|
+
expect(sqids.decode('QyG4')).to eq([100_000])
|
31
31
|
end
|
32
32
|
|
33
33
|
it 'uses blocklist to prevent certain encodings' do
|
34
|
-
sqids = Sqids.new(blocklist: Set.new(%w[
|
34
|
+
sqids = Sqids.new(blocklist: Set.new(%w[JSwXFaosAN OCjV9JK64o rBHf 79SM 7tE6]))
|
35
35
|
|
36
|
-
expect(sqids.encode([
|
37
|
-
expect(sqids.decode('
|
36
|
+
expect(sqids.encode([1_000_000, 2_000_000])).to eq('1aYeB7bRUt')
|
37
|
+
expect(sqids.decode('1aYeB7bRUt')).to eq([1_000_000, 2_000_000])
|
38
38
|
end
|
39
39
|
|
40
40
|
it 'can decode blocklist words' do
|
41
|
-
sqids = Sqids.new(blocklist: Set.new(%w[
|
41
|
+
sqids = Sqids.new(blocklist: Set.new(%w[86Rf07 se8ojk ARsz1p Q8AI49 5sQRZO]))
|
42
42
|
|
43
|
-
expect(sqids.decode('
|
44
|
-
expect(sqids.decode('
|
45
|
-
expect(sqids.decode('
|
46
|
-
expect(sqids.decode('
|
47
|
-
expect(sqids.decode('
|
43
|
+
expect(sqids.decode('86Rf07')).to eq([1, 2, 3])
|
44
|
+
expect(sqids.decode('se8ojk')).to eq([1, 2, 3])
|
45
|
+
expect(sqids.decode('ARsz1p')).to eq([1, 2, 3])
|
46
|
+
expect(sqids.decode('Q8AI49')).to eq([1, 2, 3])
|
47
|
+
expect(sqids.decode('5sQRZO')).to eq([1, 2, 3])
|
48
48
|
end
|
49
49
|
|
50
50
|
it 'matches against a short blocklist word' do
|
51
|
-
sqids = Sqids.new(blocklist: Set.new(['
|
51
|
+
sqids = Sqids.new(blocklist: Set.new(['pnd']))
|
52
52
|
|
53
53
|
expect(sqids.decode(sqids.encode([1_000]))).to eq([1_000])
|
54
54
|
end
|
55
55
|
|
56
56
|
it 'blocklist filtering in constructor' do
|
57
57
|
# lowercase blocklist in only-uppercase alphabet
|
58
|
-
sqids = Sqids.new(alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', blocklist: Set.new(['
|
58
|
+
sqids = Sqids.new(alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', blocklist: Set.new(['sxnzkl']))
|
59
59
|
|
60
60
|
id = sqids.encode([1, 2, 3])
|
61
61
|
numbers = sqids.decode(id)
|
62
62
|
|
63
|
-
expect(id).to eq('
|
63
|
+
expect(id).to eq('IBSHOZ') # without blocklist, would've been "SXNZKL"
|
64
64
|
expect(numbers).to eq([1, 2, 3])
|
65
65
|
end
|
66
|
+
|
67
|
+
it 'max encoding attempts' do
|
68
|
+
alphabet = 'abc'
|
69
|
+
min_length = 3
|
70
|
+
blocklist = Set.new(%w[cab abc bca])
|
71
|
+
|
72
|
+
sqids = Sqids.new(alphabet: alphabet, min_length: min_length, blocklist: blocklist)
|
73
|
+
|
74
|
+
expect(min_length).to eq(alphabet.length)
|
75
|
+
expect(min_length).to eq(blocklist.size)
|
76
|
+
|
77
|
+
expect do
|
78
|
+
sqids.encode([0])
|
79
|
+
end.to raise_error(ArgumentError)
|
80
|
+
end
|
66
81
|
end
|
data/spec/encoding_spec.rb
CHANGED
@@ -8,7 +8,7 @@ describe 'Sqids' do
|
|
8
8
|
sqids = Sqids.new
|
9
9
|
|
10
10
|
numbers = [1, 2, 3]
|
11
|
-
id = '
|
11
|
+
id = '86Rf07'
|
12
12
|
|
13
13
|
expect(sqids.encode(numbers)).to eq(id)
|
14
14
|
expect(sqids.decode(id)).to eq(numbers)
|
@@ -25,16 +25,16 @@ describe 'Sqids' do
|
|
25
25
|
sqids = Sqids.new
|
26
26
|
|
27
27
|
ids = {
|
28
|
-
'
|
29
|
-
'
|
30
|
-
'
|
31
|
-
'
|
32
|
-
'
|
33
|
-
'
|
34
|
-
'
|
35
|
-
'
|
36
|
-
'
|
37
|
-
'
|
28
|
+
'bM' => [0],
|
29
|
+
'Uk' => [1],
|
30
|
+
'gb' => [2],
|
31
|
+
'Ef' => [3],
|
32
|
+
'Vq' => [4],
|
33
|
+
'uw' => [5],
|
34
|
+
'OI' => [6],
|
35
|
+
'AX' => [7],
|
36
|
+
'p6' => [8],
|
37
|
+
'nJ' => [9]
|
38
38
|
}
|
39
39
|
|
40
40
|
ids.each do |id, numbers|
|
@@ -47,16 +47,16 @@ describe 'Sqids' do
|
|
47
47
|
sqids = Sqids.new
|
48
48
|
|
49
49
|
ids = {
|
50
|
-
'
|
51
|
-
'
|
52
|
-
'
|
53
|
-
'
|
54
|
-
'
|
55
|
-
'
|
56
|
-
'
|
57
|
-
'
|
58
|
-
'
|
59
|
-
'
|
50
|
+
'SvIz' => [0, 0],
|
51
|
+
'n3qa' => [0, 1],
|
52
|
+
'tryF' => [0, 2],
|
53
|
+
'eg6q' => [0, 3],
|
54
|
+
'rSCF' => [0, 4],
|
55
|
+
'sR8x' => [0, 5],
|
56
|
+
'uY2M' => [0, 6],
|
57
|
+
'74dI' => [0, 7],
|
58
|
+
'30WX' => [0, 8],
|
59
|
+
'moxr' => [0, 9]
|
60
60
|
}
|
61
61
|
|
62
62
|
ids.each do |id, numbers|
|
@@ -69,16 +69,16 @@ describe 'Sqids' do
|
|
69
69
|
sqids = Sqids.new
|
70
70
|
|
71
71
|
ids = {
|
72
|
-
'
|
73
|
-
'
|
74
|
-
'
|
75
|
-
'
|
76
|
-
'
|
77
|
-
'
|
78
|
-
'
|
79
|
-
'
|
80
|
-
'
|
81
|
-
'
|
72
|
+
'SvIz' => [0, 0],
|
73
|
+
'nWqP' => [1, 0],
|
74
|
+
'tSyw' => [2, 0],
|
75
|
+
'eX68' => [3, 0],
|
76
|
+
'rxCY' => [4, 0],
|
77
|
+
'sV8a' => [5, 0],
|
78
|
+
'uf2K' => [6, 0],
|
79
|
+
'7Cdk' => [7, 0],
|
80
|
+
'3aWP' => [8, 0],
|
81
|
+
'm2xn' => [9, 0]
|
82
82
|
}
|
83
83
|
|
84
84
|
ids.each do |id, numbers|
|
@@ -110,14 +110,9 @@ describe 'Sqids' do
|
|
110
110
|
expect(sqids.decode('*')).to eq([])
|
111
111
|
end
|
112
112
|
|
113
|
-
it 'decoding an invalid ID with a repeating reserved character' do
|
114
|
-
sqids = Sqids.new
|
115
|
-
expect(sqids.decode('fff')).to eq([])
|
116
|
-
end
|
117
|
-
|
118
113
|
it 'encode out-of-range numbers' do
|
119
114
|
sqids = Sqids.new
|
120
|
-
expect { sqids.encode([
|
115
|
+
expect { sqids.encode([-1]) }.to raise_error(ArgumentError)
|
121
116
|
expect { sqids.encode([Sqids.max_value + 1]) }.to raise_error(ArgumentError)
|
122
117
|
end
|
123
118
|
end
|
data/spec/minlength_spec.rb
CHANGED
@@ -10,26 +10,52 @@ describe Sqids do
|
|
10
10
|
sqids = Sqids.new(min_length: default_alphabet.length)
|
11
11
|
|
12
12
|
numbers = [1, 2, 3]
|
13
|
-
id = '
|
13
|
+
id = '86Rf07xd4zBmiJXQG6otHEbew02c3PWsUOLZxADhCpKj7aVFv9I8RquYrNlSTM'
|
14
14
|
|
15
15
|
expect(sqids.encode(numbers)).to eq(id)
|
16
16
|
expect(sqids.decode(id)).to eq(numbers)
|
17
17
|
end
|
18
18
|
|
19
|
+
it 'encodes and decodes incremental' do
|
20
|
+
numbers = [1, 2, 3]
|
21
|
+
map = {
|
22
|
+
6 => '86Rf07',
|
23
|
+
7 => '86Rf07x',
|
24
|
+
8 => '86Rf07xd',
|
25
|
+
9 => '86Rf07xd4',
|
26
|
+
10 => '86Rf07xd4z',
|
27
|
+
11 => '86Rf07xd4zB',
|
28
|
+
12 => '86Rf07xd4zBm',
|
29
|
+
13 => '86Rf07xd4zBmi'
|
30
|
+
}
|
31
|
+
map[default_alphabet.length + 0] = '86Rf07xd4zBmiJXQG6otHEbew02c3PWsUOLZxADhCpKj7aVFv9I8RquYrNlSTM'
|
32
|
+
map[default_alphabet.length + 1] = '86Rf07xd4zBmiJXQG6otHEbew02c3PWsUOLZxADhCpKj7aVFv9I8RquYrNlSTMy'
|
33
|
+
map[default_alphabet.length + 2] = '86Rf07xd4zBmiJXQG6otHEbew02c3PWsUOLZxADhCpKj7aVFv9I8RquYrNlSTMyf'
|
34
|
+
map[default_alphabet.length + 3] = '86Rf07xd4zBmiJXQG6otHEbew02c3PWsUOLZxADhCpKj7aVFv9I8RquYrNlSTMyf1'
|
35
|
+
|
36
|
+
map.each do |min_length, id|
|
37
|
+
sqids = Sqids.new(min_length: min_length)
|
38
|
+
|
39
|
+
expect(sqids.encode(numbers)).to eq(id)
|
40
|
+
expect(sqids.encode(numbers).length).to eq(min_length)
|
41
|
+
expect(sqids.decode(id)).to eq(numbers)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
19
45
|
it 'encodes and decodes incremental numbers' do
|
20
46
|
sqids = Sqids.new(min_length: default_alphabet.length)
|
21
47
|
|
22
48
|
ids = {
|
23
|
-
'
|
24
|
-
'
|
25
|
-
'
|
26
|
-
'
|
27
|
-
'
|
28
|
-
'
|
29
|
-
'
|
30
|
-
'
|
31
|
-
'
|
32
|
-
'
|
49
|
+
'SvIzsqYMyQwI3GWgJAe17URxX8V924Co0DaTZLtFjHriEn5bPhcSkfmvOslpBu' => [0, 0],
|
50
|
+
'n3qafPOLKdfHpuNw3M61r95svbeJGk7aAEgYn4WlSjXURmF8IDqZBy0CT2VxQc' => [0, 1],
|
51
|
+
'tryFJbWcFMiYPg8sASm51uIV93GXTnvRzyfLleh06CpodJD42B7OraKtkQNxUZ' => [0, 2],
|
52
|
+
'eg6ql0A3XmvPoCzMlB6DraNGcWSIy5VR8iYup2Qk4tjZFKe1hbwfgHdUTsnLqE' => [0, 3],
|
53
|
+
'rSCFlp0rB2inEljaRdxKt7FkIbODSf8wYgTsZM1HL9JzN35cyoqueUvVWCm4hX' => [0, 4],
|
54
|
+
'sR8xjC8WQkOwo74PnglH1YFdTI0eaf56RGVSitzbjuZ3shNUXBrqLxEJyAmKv2' => [0, 5],
|
55
|
+
'uY2MYFqCLpgx5XQcjdtZK286AwWV7IBGEfuS9yTmbJvkzoUPeYRHr4iDs3naN0' => [0, 6],
|
56
|
+
'74dID7X28VLQhBlnGmjZrec5wTA1fqpWtK4YkaoEIM9SRNiC3gUJH0OFvsPDdy' => [0, 7],
|
57
|
+
'30WXpesPhgKiEI5RHTY7xbB1GnytJvXOl2p0AcUjdF6waZDo9Qk8VLzMuWrqCS' => [0, 8],
|
58
|
+
'moxr3HqLAK0GsTND6jowfZz3SUx7cQ8aC54Pl1RbIvFXmEJuBMYVeW9yrdOtin' => [0, 9]
|
33
59
|
}
|
34
60
|
|
35
61
|
ids.each do |id, numbers|
|
@@ -41,7 +67,7 @@ describe Sqids do
|
|
41
67
|
it 'encodes with different min lengths' do
|
42
68
|
[0, 1, 5, 10, default_alphabet.length].each do |min_length|
|
43
69
|
[
|
44
|
-
[
|
70
|
+
[0],
|
45
71
|
[0, 0, 0, 0, 0],
|
46
72
|
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
47
73
|
[100, 200, 300],
|
@@ -60,6 +86,6 @@ describe Sqids do
|
|
60
86
|
|
61
87
|
it 'raises error for out-of-range invalid min lengths' do
|
62
88
|
expect { Sqids.new(min_length: -1) }.to raise_error(TypeError)
|
63
|
-
expect { Sqids.new(min_length:
|
89
|
+
expect { Sqids.new(min_length: 256) }.to raise_error(TypeError)
|
64
90
|
end
|
65
91
|
end
|
data/sqids.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqids
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sqids Maintainers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-07-25 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description:
|
14
14
|
email:
|
@@ -27,7 +27,6 @@ files:
|
|
27
27
|
- spec/blocklist_spec.rb
|
28
28
|
- spec/encoding_spec.rb
|
29
29
|
- spec/minlength_spec.rb
|
30
|
-
- spec/uniques_spec.rb
|
31
30
|
- sqids.gemspec
|
32
31
|
homepage: https://sqids.org/ruby
|
33
32
|
licenses:
|
data/spec/uniques_spec.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'rspec'
|
4
|
-
require_relative '../lib/sqids'
|
5
|
-
|
6
|
-
upper = 10_000
|
7
|
-
|
8
|
-
describe Sqids do
|
9
|
-
let(:sqids) { Sqids.new(min_length: Sqids::DEFAULT_ALPHABET.length) }
|
10
|
-
|
11
|
-
it 'uniques, with padding' do
|
12
|
-
set = Set.new
|
13
|
-
|
14
|
-
(0...upper).each do |i|
|
15
|
-
numbers = [i]
|
16
|
-
id = sqids.encode(numbers)
|
17
|
-
set.add(id)
|
18
|
-
expect(sqids.decode(id)).to eq(numbers)
|
19
|
-
end
|
20
|
-
|
21
|
-
expect(set.size).to eq(upper)
|
22
|
-
end
|
23
|
-
|
24
|
-
it 'uniques, low ranges' do
|
25
|
-
set = Set.new
|
26
|
-
|
27
|
-
(0...upper).each do |i|
|
28
|
-
numbers = [i]
|
29
|
-
id = sqids.encode(numbers)
|
30
|
-
set.add(id)
|
31
|
-
expect(sqids.decode(id)).to eq(numbers)
|
32
|
-
end
|
33
|
-
|
34
|
-
expect(set.size).to eq(upper)
|
35
|
-
end
|
36
|
-
|
37
|
-
it 'uniques, high ranges' do
|
38
|
-
set = Set.new
|
39
|
-
|
40
|
-
(100_000_000...(100_000_000 + upper)).each do |i|
|
41
|
-
numbers = [i]
|
42
|
-
id = sqids.encode(numbers)
|
43
|
-
set.add(id)
|
44
|
-
expect(sqids.decode(id)).to eq(numbers)
|
45
|
-
end
|
46
|
-
|
47
|
-
expect(set.size).to eq(upper)
|
48
|
-
end
|
49
|
-
|
50
|
-
it 'uniques, multi' do
|
51
|
-
set = Set.new
|
52
|
-
|
53
|
-
(0...upper).each do |i|
|
54
|
-
numbers = [i, i, i, i, i]
|
55
|
-
id = sqids.encode(numbers)
|
56
|
-
set.add(id)
|
57
|
-
expect(sqids.decode(id)).to eq(numbers)
|
58
|
-
end
|
59
|
-
|
60
|
-
expect(set.size).to eq(upper)
|
61
|
-
end
|
62
|
-
end
|