encoded_id 1.0.0.rc6 → 1.0.0.rc7

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.
@@ -14,13 +14,13 @@ module EncodedId
14
14
  #
15
15
  # == Security Note:
16
16
  #
17
- # The salt is the 'secret' that makes your HashIDs unique. Without knowing the
17
+ # The salt is the 'secret' that makes your Hashids unique. Without knowing the
18
18
  # salt, it's harder to reverse-engineer the encoding scheme
19
- # or predict hash values BUT HashIDs is not a secure encryption technique. It
19
+ # or predict hash values BUT Hashids is not a secure encryption technique. It
20
20
  # is only to be used to obfuscate values which are not secure (you would just
21
21
  # prefer the average person cannot see them).
22
22
  #
23
- class HashIdSalt
23
+ class HashidSalt
24
24
  # @rbs @salt: String
25
25
  # @rbs @chars: Array[String]
26
26
 
@@ -20,17 +20,14 @@ class MySqids
20
20
  # @rbs @min_length: Integer
21
21
  # @rbs @blocklist: (Array[String] | Set[String])
22
22
 
23
- # @rbs self.@DEFAULT_ALPHABET: String
24
23
  DEFAULT_ALPHABET = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
25
24
 
26
25
  # Default minimum length of 0 means no padding is applied to generated IDs
27
- # @rbs self.@DEFAULT_MIN_LENGTH: Integer
28
26
  DEFAULT_MIN_LENGTH = 0
29
27
  # rubocop:disable Metrics/CollectionLiteralLength, Layout/LineLength
30
28
  # Default blocklist containing words that should not appear in generated IDs
31
29
  # The blocklist prevents offensive or inappropriate words from appearing in IDs by
32
30
  # regenerating IDs that contain these patterns.
33
- # @rbs self.@DEFAULT_BLOCKLIST: Array[String]
34
31
  DEFAULT_BLOCKLIST = %w[0rgasm 1d10t 1d1ot 1di0t 1diot 1eccacu10 1eccacu1o 1eccacul0
35
32
  1eccaculo 1mbec11e 1mbec1le 1mbeci1e 1mbecile a11upat0 a11upato a1lupat0 a1lupato aand ah01e ah0le aho1e ahole al1upat0 al1upato allupat0 allupato ana1 ana1e anal anale anus arrapat0 arrapato arsch arse ass b00b b00be b01ata b0ceta b0iata b0ob b0obe b0sta b1tch b1te b1tte ba1atkar balatkar bastard0 bastardo batt0na battona bitch bite bitte bo0b bo0be bo1ata boceta boiata boob boobe bosta bran1age bran1er bran1ette bran1eur bran1euse branlage branler branlette branleur branleuse c0ck c0g110ne c0g11one c0g1i0ne c0g1ione c0gl10ne c0gl1one c0gli0ne c0glione c0na c0nnard c0nnasse c0nne c0u111es c0u11les c0u1l1es c0u1lles c0ui11es c0ui1les c0uil1es c0uilles c11t c11t0 c11to c1it c1it0 c1ito cabr0n cabra0 cabrao cabron caca cacca cacete cagante cagar cagare cagna cara1h0 cara1ho caracu10 caracu1o caracul0 caraculo caralh0 caralho cazz0 cazz1mma cazzata cazzimma cazzo ch00t1a ch00t1ya ch00tia ch00tiya ch0d ch0ot1a ch0ot1ya ch0otia ch0otiya ch1asse ch1avata ch1er ch1ng0 ch1ngadaz0s ch1ngadazos ch1ngader1ta ch1ngaderita ch1ngar ch1ngo ch1ngues ch1nk chatte chiasse chiavata chier ching0 chingadaz0s chingadazos chingader1ta chingaderita chingar chingo chingues chink cho0t1a cho0t1ya cho0tia cho0tiya chod choot1a choot1ya chootia chootiya cl1t cl1t0 cl1to clit clit0 clito cock cog110ne cog11one cog1i0ne cog1ione cogl10ne cogl1one cogli0ne coglione cona connard connasse conne cou111es cou11les cou1l1es cou1lles coui11es coui1les couil1es couilles cracker crap cu10 cu1att0ne cu1attone cu1er0 cu1ero cu1o cul0 culatt0ne culattone culer0 culero culo cum cunt d11d0 d11do d1ck d1ld0 d1ldo damn de1ch deich depp di1d0 di1do dick dild0 dildo dyke encu1e encule enema enf01re enf0ire enfo1re enfoire estup1d0 estup1do estupid0 estupido etr0n etron f0da f0der f0ttere f0tters1 f0ttersi f0tze f0utre f1ca f1cker f1ga fag fica ficker figa foda foder fottere fotters1 fottersi fotze foutre fr0c10 fr0c1o fr0ci0 fr0cio fr0sc10 fr0sc1o fr0sci0 fr0scio froc10 froc1o froci0 frocio frosc10 frosc1o frosci0 froscio fuck g00 g0o g0u1ne g0uine gandu go0 goo gou1ne gouine gr0gnasse grognasse haram1 harami haramzade hund1n hundin id10t id1ot idi0t idiot imbec11e imbec1le imbeci1e imbecile j1zz jerk jizz k1ke kam1ne kamine kike leccacu10 leccacu1o leccacul0 leccaculo m1erda m1gn0tta m1gnotta m1nch1a m1nchia m1st mam0n mamahuev0 mamahuevo mamon masturbat10n masturbat1on masturbate masturbati0n masturbation merd0s0 merd0so merda merde merdos0 merdoso mierda mign0tta mignotta minch1a minchia mist musch1 muschi n1gger neger negr0 negre negro nerch1a nerchia nigger orgasm p00p p011a p01la p0l1a p0lla p0mp1n0 p0mp1no p0mpin0 p0mpino p0op p0rca p0rn p0rra p0uff1asse p0uffiasse p1p1 p1pi p1r1a p1rla p1sc10 p1sc1o p1sci0 p1scio p1sser pa11e pa1le pal1e palle pane1e1r0 pane1e1ro pane1eir0 pane1eiro panele1r0 panele1ro paneleir0 paneleiro patakha pec0r1na pec0rina pecor1na pecorina pen1s pendej0 pendejo penis pip1 pipi pir1a pirla pisc10 pisc1o pisci0 piscio pisser po0p po11a po1la pol1a polla pomp1n0 pomp1no pompin0 pompino poop porca porn porra pouff1asse pouffiasse pr1ck prick pussy put1za puta puta1n putain pute putiza puttana queca r0mp1ba11e r0mp1ba1le r0mp1bal1e r0mp1balle r0mpiba11e r0mpiba1le r0mpibal1e r0mpiballe rand1 randi rape recch10ne recch1one recchi0ne recchione retard romp1ba11e romp1ba1le romp1bal1e romp1balle rompiba11e rompiba1le rompibal1e rompiballe ruff1an0 ruff1ano ruffian0 ruffiano s1ut sa10pe sa1aud sa1ope sacanagem sal0pe salaud salope saugnapf sb0rr0ne sb0rra sb0rrone sbattere sbatters1 sbattersi sborr0ne sborra sborrone sc0pare sc0pata sch1ampe sche1se sche1sse scheise scheisse schlampe schwachs1nn1g schwachs1nnig schwachsinn1g schwachsinnig schwanz scopare scopata sexy sh1t shit slut sp0mp1nare sp0mpinare spomp1nare spompinare str0nz0 str0nza str0nzo stronz0 stronza stronzo stup1d stupid succh1am1 succh1ami succhiam1 succhiami sucker t0pa tapette test1c1e test1cle testic1e testicle tette topa tr01a tr0ia tr0mbare tr1ng1er tr1ngler tring1er tringler tro1a troia trombare turd twat vaffancu10 vaffancu1o vaffancul0 vaffanculo vag1na vagina verdammt verga w1chsen wank wichsen x0ch0ta x0chota xana xoch0ta xochota z0cc01a z0cc0la z0cco1a z0ccola z1z1 z1zi ziz1 zizi zocc01a zocc0la zocco1a zoccola].freeze
36
33
  # rubocop:enable Metrics/CollectionLiteralLength, Layout/LineLength
@@ -83,23 +80,15 @@ class MySqids
83
80
  "Minimum length has to be between 0 and #{min_length_limit}"
84
81
  end
85
82
 
86
- # Filter the blocklist to only include words that:
87
- # 1. Are at least 3 characters long
88
- # 2. Only contain characters that exist in the alphabet (case-insensitive)
89
- # This ensures we don't try to block words that could never appear in generated IDs
90
83
  filtered_blocklist = if options[:blocklist].nil? && options[:alphabet].nil?
91
- # If using default blocklist and alphabet, skip filtering since we know it's valid
92
84
  blocklist
93
85
  else
94
86
  downcased_alphabet = alphabet.map(&:downcase)
95
- # Only keep words that can be formed from the alphabet
96
87
  blocklist.select do |word|
97
88
  word.length >= 3 && (word.downcase.chars - downcased_alphabet).empty?
98
89
  end.to_set(&:downcase)
99
90
  end
100
91
 
101
- # Store the alphabet as an array of integer codepoints after shuffling
102
- # Shuffling ensures the alphabet order is unique to this instance
103
92
  @alphabet = shuffle(alphabet.map(&:ord))
104
93
  @min_length = min_length
105
94
  @blocklist = filtered_blocklist
@@ -128,7 +117,10 @@ class MySqids
128
117
  return "" if numbers.empty?
129
118
 
130
119
  # Validate that all numbers are within the acceptable range
131
- in_range_numbers = numbers.filter_map { |n| i = n.to_i; i if i.between?(0, MAX_INT) }
120
+ in_range_numbers = numbers.filter_map { |n|
121
+ i = n.to_i
122
+ i if i.between?(0, MAX_INT)
123
+ }
132
124
  unless in_range_numbers.length == numbers.length
133
125
  raise ArgumentError,
134
126
  "Encoding supports numbers between 0 and #{MAX_INT}"
@@ -161,8 +153,7 @@ class MySqids
161
153
 
162
154
  return ret if id.empty?
163
155
 
164
- # Convert string to array of character codepoints for processing
165
- id = id.chars.map(&:ord)
156
+ id = id.codepoints
166
157
 
167
158
  # Validate that all characters in the ID exist in our alphabet
168
159
  # If any character is invalid, return empty array
@@ -291,7 +282,6 @@ class MySqids
291
282
  offset = (offset + increment) % alphabet_length
292
283
 
293
284
  prefix = @alphabet[offset]
294
- # Now working with modified alphabet
295
285
  alphabet = rotate_and_reverse_alphabet(@alphabet, offset)
296
286
  id = [prefix]
297
287
 
@@ -352,7 +342,6 @@ class MySqids
352
342
  while true # rubocop:disable Style/InfiniteLoop
353
343
  new_char_index = (result % alphabet_length) + 1
354
344
  new_char = alphabet[new_char_index]
355
- # id is an array, we want to insert the new char at the start_index position.
356
345
  id.insert(start_index, new_char)
357
346
  result /= alphabet_length
358
347
  break if result <= 0
@@ -4,27 +4,44 @@
4
4
 
5
5
  module EncodedId
6
6
  module Encoders
7
- class Sqids < Base
7
+ # Encoder implementation using the Sqids algorithm for encoding/decoding IDs.
8
+ class Sqids
8
9
  # @rbs @sqids: untyped
10
+ # @rbs @min_hash_length: Integer
11
+ # @rbs @alphabet: Alphabet
12
+ # @rbs @blocklist: Blocklist
13
+ # @rbs @blocklist_mode: Symbol
14
+ # @rbs @blocklist_max_length: Integer
9
15
 
10
- # @rbs (String salt, ?Integer min_hash_length, ?Alphabet alphabet, ?Blocklist blocklist) -> void
11
- def initialize(salt, min_hash_length = 0, alphabet = Alphabet.alphanum, blocklist = Blocklist.empty)
12
- super
13
- @sqids = ::MySqids.new(
16
+ # @rbs (?Integer min_hash_length, ?Alphabet alphabet, ?Blocklist blocklist, ?Symbol blocklist_mode, ?Integer blocklist_max_length) -> void
17
+ def initialize(min_hash_length = 0, alphabet = Alphabet.alphanum, blocklist = Blocklist.empty, blocklist_mode = :length_threshold, blocklist_max_length = 32)
18
+ @min_hash_length = min_hash_length
19
+ @alphabet = alphabet
20
+ @blocklist = blocklist
21
+ @blocklist_mode = blocklist_mode
22
+ @blocklist_max_length = blocklist_max_length
23
+
24
+ @sqids = ::SqidsWithBlocklistMode.new(
14
25
  {
15
26
  min_length: min_hash_length,
16
27
  alphabet: alphabet.characters,
17
- blocklist: blocklist
28
+ blocklist: blocklist,
29
+ blocklist_mode: blocklist_mode,
30
+ blocklist_max_length: blocklist_max_length
18
31
  }
19
32
  )
20
- rescue TypeError, ArgumentError => e
21
- raise InvalidInputError, "unable to create sqids instance: #{e.message}"
33
+ rescue TypeError, ArgumentError => error
34
+ raise InvalidInputError, "unable to create sqids instance: #{error.message}"
22
35
  end
23
36
 
37
+ attr_reader :min_hash_length #: Integer
38
+ attr_reader :alphabet #: Alphabet
39
+ attr_reader :blocklist #: Blocklist
40
+
24
41
  # @rbs (Array[Integer] numbers) -> String
25
42
  def encode(numbers)
26
- numbers.all? { |n| Integer(n) } # raises if conversion fails
27
- return "" if numbers.empty? || numbers.any? { |n| n < 0 }
43
+ numbers.all? { Integer(_1) } # raises if conversion fails
44
+ return "" if numbers.empty? || numbers.any?(&:negative?)
28
45
 
29
46
  @sqids.encode(numbers)
30
47
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ module EncodedId
6
+ module Encoders
7
+ # Configuration for Sqids encoder
8
+ # Sqids does not use a salt - it shuffles the alphabet deterministically
9
+ class SqidsConfiguration < BaseConfiguration
10
+ # @rbs () -> Symbol
11
+ def encoder_type
12
+ :sqids
13
+ end
14
+
15
+ # Create the Sqids encoder instance
16
+ # @rbs () -> Sqids
17
+ def create_encoder
18
+ Sqids.new(min_length, alphabet, blocklist, blocklist_mode, blocklist_max_length)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rbs_inline: enabled
4
+
5
+ # Extension of MySqids (vendored Sqids) that adds blocklist mode support.
6
+ # This subclass overrides blocklist checking to support different modes
7
+ # without modifying the vendored library.
8
+ # In the future, the base class can be changed from MySqids to ::Sqids::Sqids
9
+ # once we use the official gem.
10
+ class SqidsWithBlocklistMode < MySqids
11
+ # @rbs @blocklist_mode: Symbol
12
+ # @rbs @blocklist_max_length: Integer
13
+
14
+ # @rbs (?Hash[Symbol, untyped] options) -> void
15
+ def initialize(options = {})
16
+ @blocklist_mode = options[:blocklist_mode] || :length_threshold
17
+ @blocklist_max_length = options[:blocklist_max_length] || 32
18
+
19
+ # Remove our custom options before passing to parent
20
+ parent_options = options.dup
21
+ parent_options.delete(:blocklist_mode)
22
+ parent_options.delete(:blocklist_max_length)
23
+
24
+ super(parent_options)
25
+ end
26
+
27
+ private
28
+
29
+ # Override blocked_id? to implement blocklist mode logic
30
+ # @rbs (String id) -> bool
31
+ def blocked_id?(id)
32
+ return false unless check_blocklist?(id)
33
+
34
+ super
35
+ end
36
+
37
+ # Determines if blocklist checking should be performed based on mode and ID length
38
+ # @rbs (String id) -> bool
39
+ def check_blocklist?(id)
40
+ return false if @blocklist.empty?
41
+
42
+ case @blocklist_mode
43
+ when :always
44
+ true
45
+ when :length_threshold
46
+ id.length <= @blocklist_max_length
47
+ when :raise_if_likely
48
+ # This mode raises at configuration time, so if we get here, we check
49
+ true
50
+ else
51
+ true
52
+ end
53
+ end
54
+ end
@@ -37,7 +37,6 @@ module EncodedId
37
37
  hex_digit_encoding_group_size
38
38
  end
39
39
 
40
- # Convert hex strings to integer representations
41
40
  # @rbs (encodeableHexValue hexs) -> Array[Integer]
42
41
  def integer_representation(hexs)
43
42
  inputs = Array(hexs).map(&:to_s)
@@ -48,12 +47,10 @@ module EncodedId
48
47
  digits_to_encode << hex_string_separator
49
48
  end
50
49
 
51
- # Remove the last marker
52
50
  digits_to_encode.pop unless digits_to_encode.empty?
53
51
  digits_to_encode
54
52
  end
55
53
 
56
- # Convert integer representations to hex strings
57
54
  # @rbs (Array[Integer] integers) -> Array[String]
58
55
  def integers_to_hex_strings(integers)
59
56
  hex_strings = [] #: Array[String]
@@ -66,12 +63,12 @@ module EncodedId
66
63
  hex_string = []
67
64
  add_leading = false
68
65
  else
66
+ # Add leading zeros to maintain group size for all groups except the first
69
67
  hex_string << (add_leading ? "%.#{@hex_digit_encoding_group_size}x" % integer : integer.to_s(16))
70
68
  add_leading = true
71
69
  end
72
70
  end
73
71
 
74
- # Add the last hex string
75
72
  hex_strings << hex_string.join unless hex_string.empty?
76
73
  hex_strings.reverse
77
74
  end
@@ -96,12 +93,12 @@ module EncodedId
96
93
  # @rbs (String hex_string_cleaned) -> Array[Integer]
97
94
  def convert_to_integer_groups(hex_string_cleaned)
98
95
  groups = [] #: Array[Array[String]]
99
- hex_string_cleaned.chars.reverse.each_with_index do |char, i|
100
- group_id = i / @hex_digit_encoding_group_size
101
- groups[group_id] ||= []
102
- groups[group_id].unshift(char)
96
+ hex_string_cleaned.chars.reverse.each_with_index do |char, index|
97
+ group_id = index / @hex_digit_encoding_group_size
98
+ group = (groups[group_id] ||= [])
99
+ group.unshift(char)
103
100
  end
104
- groups.map { |c| c.join.to_i(16) }
101
+ groups.map { _1.join.to_i(16) }
105
102
  end
106
103
  end
107
104
  end
@@ -10,53 +10,77 @@ module EncodedId
10
10
  # type encodeableValue = Array[String | Integer] | String | Integer
11
11
 
12
12
  class ReversibleId
13
- # @rbs VALID_ENCODERS: Array[Symbol]
14
- VALID_ENCODERS = [:hashids, :sqids].freeze
15
- # @rbs DEFAULT_ENCODER: Symbol
16
- DEFAULT_ENCODER = :hashids
17
-
18
- # @rbs @alphabet: Alphabet
19
- # @rbs @salt: String
20
- # @rbs @length: Integer
21
- # @rbs @split_at: Integer?
22
- # @rbs @split_with: String?
13
+ # @rbs @config: Encoders::BaseConfiguration
23
14
  # @rbs @hex_represention_encoder: HexRepresentation
24
- # @rbs @max_length: Integer?
25
- # @rbs @max_inputs_per_id: Integer
26
- # @rbs @blocklist: Blocklist
27
- # @rbs @encoder: Encoders::Base
28
-
29
- # @rbs (salt: String, ?length: Integer, ?split_at: Integer?, ?split_with: String?, ?alphabet: Alphabet, ?hex_digit_encoding_group_size: Integer, ?max_length: Integer?, ?max_inputs_per_id: Integer, ?encoder: Symbol | Encoders::Base, ?blocklist: Blocklist | Array[String] | Set[String] | nil) -> void
30
- def initialize(salt:, length: 8, split_at: 4, split_with: "-", alphabet: Alphabet.modified_crockford, hex_digit_encoding_group_size: 4, max_length: 128, max_inputs_per_id: 32, encoder: DEFAULT_ENCODER, blocklist: Blocklist.empty)
31
- @alphabet = validate_alphabet(alphabet)
32
- @salt = validate_salt(salt)
33
- @length = validate_length(length)
34
- @split_at = validate_split_at(split_at)
35
- @split_with = validate_split_with(split_with, alphabet)
36
- @hex_represention_encoder = HexRepresentation.new(hex_digit_encoding_group_size)
37
- @max_length = validate_max_length(max_length)
38
- @max_inputs_per_id = validate_max_input(max_inputs_per_id)
39
- @blocklist = validate_blocklist(blocklist)
40
- @encoder = create_encoder(validate_encoder(encoder))
41
- end
42
-
43
- # Accessors for introspection
44
- attr_reader :salt #: String
45
- attr_reader :length #: Integer
46
- attr_reader :alphabet #: Alphabet
47
- attr_reader :split_at #: Integer?
48
- attr_reader :split_with #: String?
49
- attr_reader :hex_represention_encoder #: HexRepresentation
50
- attr_reader :max_length #: Integer?
51
- attr_reader :blocklist #: Blocklist
52
- attr_reader :encoder #: Encoders::Base
15
+ # @rbs @encoder: untyped
16
+
17
+ # Factory method to create a Hashid-based reversible ID
18
+ # @rbs (salt: String, **untyped options) -> ReversibleId
19
+ def self.hashid(salt:, **options)
20
+ new(Encoders::HashidConfiguration.new(salt: salt, **options))
21
+ end
22
+
23
+ # Factory method to create a Sqids-based reversible ID (default)
24
+ # @rbs (**untyped options) -> ReversibleId
25
+ def self.sqids(**options)
26
+ new(Encoders::SqidsConfiguration.new(**options))
27
+ end
28
+
29
+ # Initialize with a configuration object
30
+ # Defaults to Sqids configuration if called with no arguments
31
+ # @rbs (?Encoders::BaseConfiguration? config) -> void
32
+ def initialize(config = nil)
33
+ @config = config || Encoders::SqidsConfiguration.new
34
+
35
+ unless @config.is_a?(Encoders::BaseConfiguration)
36
+ raise InvalidConfigurationError, "config must be an instance of Encoders::BaseConfiguration (or nil for default Sqids)"
37
+ end
38
+
39
+ @hex_represention_encoder = HexRepresentation.new(@config.hex_digit_encoding_group_size)
40
+ @encoder = create_encoder
41
+ end
42
+
43
+ # @rbs () -> String?
44
+ def salt
45
+ config = @config
46
+ return config.salt if config.is_a?(Encoders::HashidConfiguration)
47
+ nil
48
+ end
49
+
50
+ def min_length
51
+ @config.min_length
52
+ end
53
+
54
+ def alphabet
55
+ @config.alphabet
56
+ end
57
+
58
+ def split_at
59
+ @config.split_at
60
+ end
61
+
62
+ def split_with
63
+ @config.split_with
64
+ end
65
+
66
+ attr_reader :hex_represention_encoder
67
+
68
+ def max_length
69
+ @config.max_length
70
+ end
71
+
72
+ def blocklist
73
+ @config.blocklist
74
+ end
75
+
76
+ attr_reader :encoder #: untyped
53
77
 
54
78
  # Encode the input values into a hash
55
79
  # @rbs (encodeableValue values) -> String
56
80
  def encode(values)
57
81
  inputs = prepare_input(values)
58
82
  encoded_id = encoder.encode(inputs)
59
- encoded_id = humanize_length(encoded_id) if split_with && split_at
83
+ encoded_id = humanize_length(encoded_id) if @config.split_with && @config.split_at
60
84
 
61
85
  raise EncodedIdLengthError if max_length_exceeded?(encoded_id)
62
86
 
@@ -66,155 +90,78 @@ module EncodedId
66
90
  # Encode hex strings into a hash
67
91
  # @rbs (encodeableHexValue hexs) -> String
68
92
  def encode_hex(hexs)
69
- encode(hex_represention_encoder.hex_as_integers(hexs))
93
+ encode(@hex_represention_encoder.hex_as_integers(hexs))
70
94
  end
71
95
 
72
96
  # Decode the hash to original array
73
97
  # @rbs (String str, ?downcase: bool) -> Array[Integer]
74
- def decode(str, downcase: true)
98
+ def decode(str, downcase: false)
75
99
  raise EncodedIdFormatError, "Max length of input exceeded" if max_length_exceeded?(str)
76
100
 
77
101
  encoder.decode(convert_to_hash(str, downcase))
78
- rescue InvalidInputError => e
79
- raise EncodedIdFormatError, e.message
102
+ rescue InvalidInputError => error
103
+ raise EncodedIdFormatError, error.message
80
104
  end
81
105
 
82
106
  # Decode hex strings from a hash
83
107
  # @rbs (String str, ?downcase: bool) -> Array[String]
84
- def decode_hex(str, downcase: true)
108
+ def decode_hex(str, downcase: false)
85
109
  integers = encoder.decode(convert_to_hash(str, downcase))
86
- hex_represention_encoder.integers_as_hex(integers)
110
+ @hex_represention_encoder.integers_as_hex(integers)
87
111
  end
88
112
 
89
113
  private
90
114
 
91
- # @rbs (Alphabet alphabet) -> Alphabet
92
- def validate_alphabet(alphabet)
93
- return alphabet if alphabet.is_a?(Alphabet)
94
- raise InvalidAlphabetError, "alphabet must be an instance of Alphabet"
95
- end
96
-
97
- # @rbs (String salt) -> String
98
- def validate_salt(salt)
99
- return salt if salt.is_a?(String) && salt.size > 3
100
- raise InvalidConfigurationError, "Salt must be a string and longer than 3 characters"
101
- end
102
-
103
- # Target length of the encoded string (the minimum but not maximum length)
104
- # @rbs (Integer length) -> Integer
105
- def validate_length(length)
106
- return length if valid_integer_option?(length)
107
- raise InvalidConfigurationError, "Length must be an integer greater than 0"
108
- end
109
-
110
- # @rbs (Integer? max_length) -> Integer?
111
- def validate_max_length(max_length)
112
- return max_length if valid_integer_option?(max_length) || max_length.nil?
113
- raise InvalidConfigurationError, "Max length must be an integer greater than 0"
114
- end
115
-
116
- # @rbs (Integer max_inputs_per_id) -> Integer
117
- def validate_max_input(max_inputs_per_id)
118
- return max_inputs_per_id if valid_integer_option?(max_inputs_per_id)
119
- raise InvalidConfigurationError, "Max inputs per ID must be an integer greater than 0"
120
- end
121
-
122
- # Split the encoded string into groups of this size
123
- # @rbs (Integer? split_at) -> Integer?
124
- def validate_split_at(split_at)
125
- return split_at if valid_integer_option?(split_at) || split_at.nil?
126
- raise InvalidConfigurationError, "Split at must be an integer greater than 0 or nil"
127
- end
128
-
129
- # @rbs (String? split_with, Alphabet alphabet) -> String?
130
- def validate_split_with(split_with, alphabet)
131
- return split_with if split_with.nil? || (split_with.is_a?(String) && !alphabet.characters.include?(split_with))
132
- raise InvalidConfigurationError, "Split with must be a string and not part of the alphabet or nil"
133
- end
134
-
135
- # @rbs (Integer? value) -> bool
136
- def valid_integer_option?(value)
137
- value.is_a?(Integer) && value > 0
138
- end
139
-
140
115
  # @rbs (encodeableValue value) -> Array[Integer]
141
116
  def prepare_input(value)
142
117
  inputs = value.is_a?(Array) ? value.map(&:to_i) : [value.to_i]
143
118
  raise ::EncodedId::InvalidInputError, "Cannot encode an empty array" if inputs.empty?
144
119
  raise ::EncodedId::InvalidInputError, "Integer IDs to be encoded can only be positive" if inputs.any?(&:negative?)
145
-
146
- raise ::EncodedId::InvalidInputError, "%d integer IDs provided, maximum amount of IDs is %d" % [inputs.length, @max_inputs_per_id] if inputs.length > @max_inputs_per_id
120
+ raise ::EncodedId::InvalidInputError, "%d integer IDs provided, maximum amount of IDs is %d" % [inputs.length, @config.max_inputs_per_id] if inputs.length > @config.max_inputs_per_id
147
121
 
148
122
  inputs
149
123
  end
150
124
 
151
- # @rbs (Symbol | Encoders::Base encoder) -> Encoders::Base
152
- def create_encoder(encoder)
153
- # If an encoder instance was provided, return it directly
154
- return @encoder if defined?(@encoder) && @encoder.is_a?(Encoders::Base)
155
- return encoder if encoder.is_a?(Encoders::Base)
156
-
157
- case encoder
158
- when :sqids
159
- if defined?(Encoders::Sqids)
160
- Encoders::Sqids.new(salt, length, alphabet, @blocklist)
161
- else
162
- raise InvalidConfigurationError, "Sqids encoder requested but the sqids gem is not available. Please add 'gem \"sqids\"' to your Gemfile."
163
- end
164
- when :hashids
165
- Encoders::HashId.new(salt, length, alphabet, @blocklist)
166
- else
167
- raise InvalidConfigurationError, "The encoder name is not supported '#{encoder}'"
168
- end
169
- end
170
-
171
- # @rbs (Symbol | Encoders::Base encoder) -> (Symbol | Encoders::Base)
172
- def validate_encoder(encoder)
173
- # Accept either a valid symbol or an Encoders::Base instance
174
- return encoder if VALID_ENCODERS.include?(encoder) || encoder.is_a?(Encoders::Base)
175
- raise InvalidConfigurationError, "Encoder must be one of: #{VALID_ENCODERS.join(", ")} or an instance of EncodedId::Encoders::Base"
176
- end
177
-
178
- # @rbs (Blocklist | Array[String] | Set[String] | nil blocklist) -> Blocklist
179
- def validate_blocklist(blocklist)
180
- return blocklist if blocklist.is_a?(Blocklist)
181
- return Blocklist.empty if blocklist.nil?
182
-
183
- return Blocklist.new(blocklist) if blocklist.is_a?(Array) || blocklist.is_a?(Set)
184
-
185
- raise InvalidConfigurationError, "Blocklist must be an instance of Blocklist, a Set, or an Array of strings"
125
+ # @rbs () -> untyped
126
+ def create_encoder
127
+ @config.create_encoder
186
128
  end
187
129
 
130
+ # Splits long encoded strings into groups for readability
131
+ # e.g., "ABCDEFGH" with split_at=4, split_with="-" becomes "ABCD-EFGH"
188
132
  # @rbs (String hash) -> String
189
133
  def humanize_length(hash)
190
134
  len = hash.length
191
- at = split_at #: Integer
192
- with = split_with #: String
135
+ at = @config.split_at #: Integer
136
+ with = @config.split_with #: String
193
137
  return hash if len <= at
194
138
 
195
139
  separator_count = (len - 1) / at
196
140
  result = hash.dup
197
141
  insert_offset = 0
198
- (1..separator_count).each do |i|
199
- insert_pos = i * at + insert_offset
142
+ (1..separator_count).each do |separator_index|
143
+ insert_pos = separator_index * at + insert_offset
200
144
  result.insert(insert_pos, with)
201
145
  insert_offset += with.length
202
146
  end
203
147
  result
204
148
  end
205
149
 
150
+ # Reverses humanize_length transformation: removes separators and optionally downcases
206
151
  # @rbs (String str, bool downcase) -> String
207
152
  def convert_to_hash(str, downcase)
208
- str = str.gsub(split_with, "") if split_with
153
+ str = str.gsub(@config.split_with, "") if @config.split_with
209
154
  str = str.downcase if downcase
210
155
  map_equivalent_characters(str)
211
156
  end
212
157
 
158
+ # Maps equivalent characters based on alphabet configuration (e.g., 'O' -> '0', 'I' -> '1')
213
159
  # @rbs (String str) -> String
214
160
  def map_equivalent_characters(str)
215
- return str unless alphabet.equivalences
161
+ equivalences = @config.alphabet.equivalences
162
+ return str unless equivalences
216
163
 
217
- alphabet.equivalences.reduce(str) do |cleaned, ceq|
164
+ equivalences.reduce(str) do |cleaned, ceq|
218
165
  from, to = ceq
219
166
  cleaned.tr(from, to)
220
167
  end
@@ -222,9 +169,9 @@ module EncodedId
222
169
 
223
170
  # @rbs (String str) -> bool
224
171
  def max_length_exceeded?(str)
225
- return false if max_length.nil?
172
+ return false if @config.max_length.nil?
226
173
 
227
- str.length > max_length
174
+ str.length > @config.max_length
228
175
  end
229
176
  end
230
177
  end
@@ -3,6 +3,5 @@
3
3
  # rbs_inline: enabled
4
4
 
5
5
  module EncodedId
6
- # @rbs VERSION: String
7
- VERSION = "1.0.0.rc6"
6
+ VERSION = "1.0.0.rc7"
8
7
  end
data/lib/encoded_id.rb CHANGED
@@ -8,29 +8,26 @@ require_relative "encoded_id/hex_representation"
8
8
  require_relative "encoded_id/blocklist"
9
9
 
10
10
  # Load the encoder framework
11
- require_relative "encoded_id/encoders/base"
12
- require_relative "encoded_id/encoders/hash_id_salt"
13
- require_relative "encoded_id/encoders/hash_id_consistent_shuffle"
14
- require_relative "encoded_id/encoders/hash_id_ordinal_alphabet_separator_guards"
15
- require_relative "encoded_id/encoders/hash_id"
16
-
17
- # Only load Sqids encoder if the gem is available
18
- begin
19
- require "sqids"
20
- require_relative "encoded_id/encoders/my_sqids"
21
- require_relative "encoded_id/encoders/sqids"
22
- rescue LoadError
23
- # Sqids gem not available, encoder will not be loaded
24
- end
11
+ require_relative "encoded_id/encoders/hashid_salt"
12
+ require_relative "encoded_id/encoders/hashid_consistent_shuffle"
13
+ require_relative "encoded_id/encoders/hashid_ordinal_alphabet_separator_guards"
14
+ require_relative "encoded_id/encoders/hashid"
15
+
16
+ require "sqids"
17
+ # TODO: move back to only using gem once upstreamed our changes
18
+ require_relative "encoded_id/encoders/my_sqids"
19
+ require_relative "encoded_id/encoders/sqids_with_blocklist_mode"
20
+
21
+ require_relative "encoded_id/encoders/sqids"
22
+
23
+ # Load configuration classes
24
+ require_relative "encoded_id/encoders/base_configuration"
25
+ require_relative "encoded_id/encoders/hashid_configuration"
26
+ require_relative "encoded_id/encoders/sqids_configuration"
25
27
 
26
28
  require_relative "encoded_id/reversible_id"
27
29
 
28
30
  # @rbs!
29
- # class Integer
30
- # MAX: Integer
31
- # end
32
- #
33
- # # Optional Sqids gem support
34
31
  # module Sqids
35
32
  # DEFAULT_BLOCKLIST: Array[String]
36
33
  # end