cpf-utilities 0.0.0 β†’ 1.0.0

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.
data/README.md ADDED
@@ -0,0 +1,657 @@
1
+ ![cpf-utilities for Ruby](https://br-utils.vercel.app/img/cover_cpf-utils.jpg)
2
+
3
+ [![Gem Version](https://img.shields.io/gem/v/cpf-utilities)](https://rubygems.org/gems/cpf-utilities)
4
+ [![Gem Downloads](https://img.shields.io/gem/dt/cpf-utilities)](https://rubygems.org/gems/cpf-utilities)
5
+ [![Ruby Version](https://img.shields.io/gem/rv/cpf-utilities)](https://www.ruby-lang.org/)
6
+ [![Test Status](https://img.shields.io/github/actions/workflow/status/LacusSolutions/br-utils-ruby/ci.yml?label=ci/cd)](https://github.com/LacusSolutions/br-utils-ruby/actions)
7
+ [![Last Update Date](https://img.shields.io/github/last-commit/LacusSolutions/br-utils-ruby)](https://github.com/LacusSolutions/br-utils-ruby)
8
+ [![Project License](https://img.shields.io/github/license/LacusSolutions/br-utils-ruby)](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE)
9
+
10
+ > 🌎 [Acessar documentaΓ§Γ£o em portuguΓͺs](./README.pt.md)
11
+
12
+ A Ruby toolkit to format, generate, and validate CPF (Brazilian Individual's Taxpayer ID). It wraps [`cpf-fmt`](https://rubygems.org/gems/cpf-fmt), [`cpf-gen`](https://rubygems.org/gems/cpf-gen), and [`cpf-val`](https://rubygems.org/gems/cpf-val) in a single faΓ§ade class (`CpfUtils`).
13
+
14
+ ## Ruby Support
15
+
16
+ | ![Ruby 3.1](https://img.shields.io/badge/Ruby-3.1-CC342D?logo=ruby&logoColor=white) | ![Ruby 3.2](https://img.shields.io/badge/Ruby-3.2-CC342D?logo=ruby&logoColor=white) | ![Ruby 3.3](https://img.shields.io/badge/Ruby-3.3-CC342D?logo=ruby&logoColor=white) | ![Ruby 3.4](https://img.shields.io/badge/Ruby-3.4-CC342D?logo=ruby&logoColor=white) | ![Ruby 4.0](https://img.shields.io/badge/Ruby-4.0-CC342D?logo=ruby&logoColor=white) |
17
+ | --- | --- | --- | --- | --- |
18
+ | Passing βœ” | Passing βœ” | Passing βœ” | Passing βœ” | Passing βœ” |
19
+
20
+ Requires Ruby **β‰₯ 3.1** (see `required_ruby_version` in the gemspec).
21
+
22
+ ## Features
23
+
24
+ - βœ… **Unified API**: Class helpers `CpfUtils.format` / `.generate` / `.is_valid`
25
+ - βœ… **Two-tier access**: Prefer `CpfUtils::CpfFormatter` / `CpfGenerator` / `CpfValidator` for the main classes; Options, helpers, and errors live under `CpfUtils::CpfFmt` / `CpfGen` / `CpfVal` (root siblings `CpfFmt` / `CpfGen` / `CpfVal` still work)
26
+ - βœ… **Numeric CPF**: Format, generate, and validate 11-digit numeric CPF (`XXX.XXX.XXX-XX`)
27
+ - βœ… **Reusable instance**: `CpfUtils` class with optional default settings (formatter/generator options or instances; validator instance)
28
+ - βœ… **Flexible input**: `#format` and `#is_valid` accept a `String` or an `Array` of strings (elements concatenated in order)
29
+ - βœ… **Per-call overrides**: Instance defaults plus a per-call options `Hash`/`*Options` instance **or** keyword overrides on `#format` / `#generate` (not both); `#is_valid` takes input only
30
+ - βœ… **Error handling**: Component errors propagate unchanged; this gem defines `CpfUtils::TypeMismatchError` and `CpfUtils::InvalidArgumentCombinationError` for API misuse
31
+
32
+ ## Installation
33
+
34
+ Install the gem directly:
35
+
36
+ ```bash
37
+ gem install cpf-utilities
38
+ ```
39
+
40
+ Or add it to your `Gemfile` and run `bundle install`:
41
+
42
+ ```ruby
43
+ gem 'cpf-utilities'
44
+ ```
45
+
46
+ This installs **`cpf-utilities`** together with [`cpf-fmt`](https://rubygems.org/gems/cpf-fmt), [`cpf-gen`](https://rubygems.org/gems/cpf-gen), and [`cpf-val`](https://rubygems.org/gems/cpf-val). You do **not** need separate `gem install` / `gem` lines for the component packages when using **`cpf-utilities`**.
47
+
48
+ ## Require
49
+
50
+ ```ruby
51
+ require 'cpf-utilities'
52
+ ```
53
+
54
+ ## Quick Start
55
+
56
+ Basic usage with class helpers (aliases of `CpfUtils::DEFAULT`):
57
+
58
+ ```ruby
59
+ require 'cpf-utilities'
60
+
61
+ cpf = '12345678909'
62
+
63
+ CpfUtils.format(cpf) # => "123.456.789-09"
64
+ CpfUtils.format(cpf, hidden: true) # => "123.***.***-**"
65
+ CpfUtils.format( # => "123456789_09"
66
+ cpf,
67
+ dot_key: '',
68
+ dash_key: '_'
69
+ )
70
+
71
+ CpfUtils.generate # => e.g. "47844241055" (11-digit numeric)
72
+ CpfUtils.generate(format: true) # => e.g. "478.442.410-55"
73
+ CpfUtils.generate(prefix: '528250911') # => e.g. "52825091138"
74
+
75
+ CpfUtils.is_valid('12345678909') # => true
76
+ CpfUtils.is_valid('123.456.789-09') # => true
77
+ CpfUtils.is_valid('12345678900') # => false
78
+ ```
79
+
80
+ ## Usage
81
+
82
+ You can work in these equivalent ways:
83
+
84
+ 1. **`CpfUtils.format` / `.generate` / `.is_valid`** β€” class helpers for quick one-off calls (forward to `DEFAULT`).
85
+ 2. **`CpfUtils::DEFAULT`** β€” mutable shared singleton (same object the class helpers use; process-wide / not thread-isolated).
86
+ 3. **`CpfUtils.new`** β€” configurable instance with shared defaults across format, generate, and validate.
87
+ 4. **Main classes under `CpfUtils`** β€” `CpfUtils::CpfFormatter`, `CpfUtils::CpfGenerator`, `CpfUtils::CpfValidator`.
88
+ 5. **Nested package modules** β€” Options, helpers, errors, and types via `CpfUtils::CpfFmt` / `CpfGen` / `CpfVal` (e.g. `CpfUtils::CpfFmt::CpfFormatterOptions`, `CpfUtils::CpfFmt.cpf_fmt`).
89
+ 6. **Root sibling modules** (still supported) β€” `CpfFmt`, `CpfGen`, `CpfVal` unchanged.
90
+
91
+ All approaches expose the same options and behavior. For exhaustive option tables and component-specific details, see the README of each [bundled package](#bundled-packages).
92
+
93
+ ### Formatter options
94
+
95
+ When calling `#format(cpf_input, options = nil, **keywords)`, all options are optional:
96
+
97
+ | Option | Type | Default | Description |
98
+ |--------|------|---------|-------------|
99
+ | `hidden` | `Boolean` | `false` | When `true`, mask digits in `hidden_start`–`hidden_end` with `hidden_key` |
100
+ | `hidden_key` | `String` | `'*'` | Character(s) used to replace masked digits |
101
+ | `hidden_start` | `Integer` | `3` | Start index (0–10, inclusive) of the range to hide |
102
+ | `hidden_end` | `Integer` | `10` | End index (0–10, inclusive) of the range to hide |
103
+ | `dot_key` | `String` | `'.'` | Dot delimiter (e.g. in `123.456.789`) |
104
+ | `dash_key` | `String` | `'-'` | Dash delimiter (e.g. before check digits `…-09`) |
105
+ | `escape` | `Boolean` | `false` | When `true`, escape HTML special characters in the result |
106
+ | `encode` | `Boolean` | `false` | When `true`, URL-encode the result (similar to JavaScript `encodeURIComponent`) |
107
+ | `on_fail` | `Proc` / callable | returns `''` | Callback when sanitized input length β‰  11; return value is used as result |
108
+
109
+ ### Generator options
110
+
111
+ When calling `#generate(options = nil, **keywords)`, all options are optional:
112
+
113
+ | Option | Type | Default | Description |
114
+ |--------|------|---------|-------------|
115
+ | `format` | `Boolean` | `false` | When `true`, return the generated CPF in standard format (`000.000.000-00`) |
116
+ | `prefix` | `String` | `''` | Partial start string (0–9 digits). Non-digits are stripped; missing characters are generated and check digits computed. Prefixes longer than 9 digits are truncated silently. |
117
+
118
+ Prefix rules: the base (first 9 digits) cannot be all zeros; 9 repeated digits (e.g. `999999999`) are not allowed.
119
+
120
+ ### Class helpers (`CpfUtils.format` / `.generate` / `.is_valid`)
121
+
122
+ These class methods are aliases of the same methods on `CpfUtils::DEFAULT`. Prefer them for one-off calls:
123
+
124
+ ```ruby
125
+ CpfUtils.format('12345678909')
126
+ CpfUtils.generate(format: true)
127
+ CpfUtils.is_valid('12345678909')
128
+ ```
129
+
130
+ ### `CpfUtils::DEFAULT` (default instance)
131
+
132
+ `CpfUtils::DEFAULT` is the pre-built, **mutable** singleton behind the class helpers (parity with the JS default export / Python `cpf_utils`). Its configuration is **process-wide and shared across threads**: mutating it (e.g. `DEFAULT.formatter = …`) affects subsequent `CpfUtils.format` / `.generate` / `.is_valid` calls for every caller in the process. Prefer `CpfUtils.new` or per-call options for concurrent or isolated work; custom instances stay independent of `DEFAULT`:
133
+
134
+ ```ruby
135
+ CpfUtils::DEFAULT.formatter = { dash_key: '|' }
136
+ CpfUtils.format('12345678909') # => "123.456.789|09"
137
+
138
+ custom = CpfUtils.new
139
+ custom.format('12345678909') # => "123.456.789-09" (unaffected)
140
+ ```
141
+
142
+ Instance methods on `DEFAULT` (and any `CpfUtils` instance):
143
+
144
+ - **`#format(cpf_input, options = nil, **keywords)`**: Formats a CPF string or array of strings. Delegates to the internal formatter. Input must be 11 digits (after sanitization); otherwise `on_fail` is used.
145
+ - **`#generate(options = nil, **keywords)`**: Generates a valid CPF. Delegates to the internal generator.
146
+ - **`#is_valid(cpf_input)`**: Returns `true` if the CPF is valid. Delegates to the internal validator. No per-call options β€” the CPF validator has none.
147
+
148
+ ### `CpfUtils` (class)
149
+
150
+ For custom default formatter, generator, or validator, create your own instance:
151
+
152
+ ```ruby
153
+ require 'cpf-utilities'
154
+
155
+ utils = CpfUtils.new(
156
+ formatter: { hidden: true, hidden_key: '#' },
157
+ generator: { format: true, prefix: '123' }
158
+ )
159
+
160
+ utils.format('47844241055') # => "478.###.###-##"
161
+ utils.generate # => e.g. "123.456.789-09"
162
+ utils.is_valid('123.456.789-09') # => true
163
+
164
+ # Access or replace internal instances
165
+ utils.formatter # => CpfFmt::CpfFormatter
166
+ utils.generator # => CpfGen::CpfGenerator
167
+ utils.validator # => CpfVal::CpfValidator
168
+ ```
169
+
170
+ - **`CpfUtils.new(settings = nil, **keywords)`**: Optional settings. Pass either a settings `Hash` with `:formatter`, `:generator`, and/or `:validator` keys, **or** the same keys as keyword arguments β€” not both (passing both raises `CpfUtils::InvalidArgumentCombinationError`). For `:formatter` / `:generator`, each value may be a component instance, a `*Options` instance (stored by reference β€” mutating it later affects subsequent calls with no per-call override), a plain options `Hash`, or omitted/`nil` for defaults. For `:validator`, pass a `CpfVal::CpfValidator` instance, `nil`, or a duck-typed object β€” **not** an options `Hash` (there is no `CpfValidatorOptions`).
171
+ - **`#format(cpf_input, options = nil, **keywords)`**: Same as the default instance; per-call options override the formatter’s defaults for that call only. Pass either an options `Hash`/`CpfFmt::CpfFormatterOptions` **or** keyword overrides β€” not both.
172
+ - **`#generate(options = nil, **keywords)`**: Same as the default instance; per-call options override the generator’s defaults. Pass either an options `Hash`/`CpfGen::CpfGeneratorOptions` **or** keyword overrides β€” not both.
173
+ - **`#is_valid(cpf_input)`**: Same as the default instance. No per-call options.
174
+ - **`#formatter`**, **`#generator`**, **`#validator`**: Accessors (getters and setters) for the internal components. Setters accept the same shapes as the constructor. To change a single formatter/generator option without replacing the instance, mutate the component’s options (e.g. `utils.formatter.options.hidden = true`).
175
+
176
+ Instance defaults and per-call overrides:
177
+
178
+ ```ruby
179
+ require 'cpf-utilities'
180
+
181
+ utils = CpfUtils.new(
182
+ formatter: { hidden: true, hidden_key: '#' },
183
+ generator: { format: true }
184
+ )
185
+
186
+ cpf = '12345678909'
187
+
188
+ utils.format(cpf) # masked (instance formatter defaults)
189
+ utils.format(cpf, hidden: false) # this call only: unmasked
190
+ utils.generate(format: false) # this call only: compact output
191
+ utils.is_valid(cpf) # => true
192
+ ```
193
+
194
+ Options can also be passed as a `Hash` (or options instance) on `#format` / `#generate` β€” without keyword overrides:
195
+
196
+ ```ruby
197
+ utils.format(cpf, { dash_key: '|' })
198
+ utils.generate({ prefix: '12345', format: true })
199
+ ```
200
+
201
+ ### Using component classes and nested modules
202
+
203
+ Preferred paths after `require 'cpf-utilities'`:
204
+
205
+ ```ruby
206
+ require 'cpf-utilities'
207
+
208
+ # Main classes at the faΓ§ade root
209
+ formatter = CpfUtils::CpfFormatter.new(hidden: true)
210
+ generator = CpfUtils::CpfGenerator.new(format: true)
211
+ validator = CpfUtils::CpfValidator.new
212
+
213
+ formatter.format('47844241055') # => "478.***.***-**"
214
+
215
+ # Options, helpers, and errors under nested package modules
216
+ options = CpfUtils::CpfFmt::CpfFormatterOptions.new(dash_key: '|')
217
+ CpfUtils::CpfFmt.cpf_fmt('12345678909') # => "123.456.789-09"
218
+
219
+ begin
220
+ CpfUtils::CpfFmt.cpf_fmt(12_345)
221
+ rescue CpfUtils::CpfFmt::TypeMismatchError
222
+ # wrong input type
223
+ end
224
+ ```
225
+
226
+ Root siblings remain supported (same objects as the nests):
227
+
228
+ ```ruby
229
+ CpfFmt.cpf_fmt('12345678909', dash_key: '|') # => "123.456.789|09"
230
+ CpfGen.cpf_gen(format: true) # => e.g. "478.442.410-55"
231
+ CpfVal.cpf_val('12345678909') # => true
232
+ CpfFmt::CpfFormatter.new(hidden: true)
233
+ ```
234
+
235
+ See [`cpf-fmt`](../cpf-fmt/README.md), [`cpf-gen`](../cpf-gen/README.md), and [`cpf-val`](../cpf-val/README.md) for full option and error details.
236
+
237
+ ## API
238
+
239
+ ### Exports
240
+
241
+ After `require 'cpf-utilities'`:
242
+
243
+ - **`CpfUtils`**: FaΓ§ade class to create a utils instance with optional default formatter, generator, and validator settings.
244
+ - **`CpfUtils.format` / `.generate` / `.is_valid`**: Class helpers that forward to `CpfUtils::DEFAULT`.
245
+ - **`CpfUtils::DEFAULT`**: Mutable pre-built `CpfUtils` instance (same object the class helpers use). Process-wide / shared across threads β€” prefer `CpfUtils.new` or per-call options under concurrency.
246
+ - **`CpfUtils::VERSION`**: Gem version string.
247
+ - **Main-class shortcuts**: `CpfUtils::CpfFormatter`, `CpfUtils::CpfGenerator`, `CpfUtils::CpfValidator` (same objects as the sibling classes).
248
+ - **Nested package modules**: `CpfUtils::CpfFmt`, `CpfUtils::CpfGen`, `CpfUtils::CpfVal` β€” full sibling surface (Options, helpers, errors, types). Options/helpers/errors are **not** aliased at the `CpfUtils` root.
249
+ - **Root sibling modules** (still supported): `CpfFmt`, `CpfGen`, `CpfVal` β€” same objects as the nests.
250
+
251
+ ### Errors & Exceptions
252
+
253
+ `CpfUtils` defines only API-misuse errors for this gem’s argument rules. Component errors are raised by the bundled packages and propagate unchanged.
254
+
255
+ #### Defined by `cpf-utilities`
256
+
257
+ Errors defined by this gem are **API misuse** only (wrong type or invalid argument combination). Every custom error includes the `CpfUtils::Error` marker module. This gem defines **no** `CpfUtils::DomainError` and no domain leaves β€” domain failures come only from the [bundled packages](#propagated-from-bundled-packages) and keep those packages’ namespaces (`CpfFmt::…`, `CpfGen::…`, `CpfVal::…`).
258
+
259
+ `rescue CpfUtils::Error` catches **only** errors this gem raises. It does **not** catch component errors that propagate unchanged.
260
+
261
+ ##### Summary
262
+
263
+ | Class | Inherits from | Category | Trigger condition |
264
+ |-------|---------------|----------|-------------------|
265
+ | `CpfUtils::InvalidArgumentCombinationError` | `CpfUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (+ `include CpfUtils::Error`) | API misuse | Constructor: non-`nil` settings `Hash` with any non-`nil` keyword; or `#format`/`#generate`/class helpers: non-`nil` options `Hash`/`*Options` with any non-`nil` keyword |
266
+ | `CpfUtils::TypeMismatchError` | `CpfUtils::TypeMismatchError < TypeError < StandardError` (+ `include CpfUtils::Error`) | API misuse | Non-`nil` `settings` argument to `CpfUtils.new` is not a `Hash` |
267
+
268
+ ##### `CpfUtils::Error` (marker module)
269
+
270
+ - **Inheritance:** module marker mixed into every custom error this gem raises via `include` (not a class).
271
+ - **Category:** N/A (rescue target only) β€” not a failure mode by itself.
272
+ - **When it is raised:** Never raised directly; included by every custom error this gem raises.
273
+ - **Example:** N/A
274
+ - **How to rescue it:**
275
+
276
+ ```ruby
277
+ rescue CpfUtils::Error
278
+ # TypeMismatchError, InvalidArgumentCombinationError from this gem only
279
+ # (not CpfFmt::*, CpfGen::*, or CpfVal::* errors)
280
+ ```
281
+
282
+ ##### `CpfUtils::TypeMismatchError`
283
+
284
+ - **Inheritance:** `CpfUtils::TypeMismatchError < TypeError < StandardError` (includes `CpfUtils::Error`)
285
+ - **Category:** API misuse β€” the caller passed a value of the wrong type.
286
+ - **When it is raised:** Raised when `CpfUtils.new` receives a non-`nil` `settings` argument that is not a `Hash`.
287
+ - **Example:**
288
+
289
+ ```ruby
290
+ CpfUtils.new('not-a-hash') # raises CpfUtils::TypeMismatchError
291
+ CpfUtils.new(false) # raises CpfUtils::TypeMismatchError (false is non-nil)
292
+ ```
293
+
294
+ - **How to rescue it:**
295
+
296
+ ```ruby
297
+ rescue CpfUtils::TypeMismatchError
298
+ # this gem's type-contract violation
299
+
300
+ rescue TypeError
301
+ # native type errors, including this gem's TypeMismatchError
302
+ ```
303
+
304
+ ##### `CpfUtils::InvalidArgumentCombinationError`
305
+
306
+ - **Inheritance:** `CpfUtils::InvalidArgumentCombinationError < ArgumentError < StandardError` (includes `CpfUtils::Error`)
307
+ - **Category:** API misuse β€” the caller mixed mutually exclusive argument patterns.
308
+ - **When it is raised:** Raised when `CpfUtils.new` receives both a non-`nil` settings `Hash` and any non-`nil` keyword argument (`formatter:`, `generator:`, `validator:`); or when `#format`, `#generate`, or the class helpers receive both a non-`nil` options `Hash`/`*Options` instance and any non-`nil` keyword argument at the same time. `#is_valid` has no options path and does not raise this error.
309
+ - **Example:**
310
+
311
+ ```ruby
312
+ CpfUtils.new({ formatter: { hidden: true } }, generator: { format: true })
313
+ # raises CpfUtils::InvalidArgumentCombinationError
314
+
315
+ CpfUtils.format('12345678909', { hidden: true }, dash_key: '|')
316
+ # raises CpfUtils::InvalidArgumentCombinationError
317
+ ```
318
+
319
+ - **How to rescue it:**
320
+
321
+ ```ruby
322
+ rescue CpfUtils::InvalidArgumentCombinationError
323
+ # this gem's invalid signature combination
324
+
325
+ rescue ArgumentError
326
+ # native argument errors, including this gem's InvalidArgumentCombinationError
327
+ ```
328
+
329
+ ##### Rescue granularity
330
+
331
+ Each level is shown as its own standalone example (do not merge them into one `rescue` ladder β€” a broad native handler would make narrower clauses unreachable).
332
+
333
+ ```ruby
334
+ require 'cpf-utilities'
335
+
336
+ # 1) Single native class β€” catches misuse errors of that kind,
337
+ # including non-library ones already handled elsewhere in the consumer's code.
338
+ begin
339
+ CpfUtils.new('not-a-hash')
340
+ rescue TypeError
341
+ # CpfUtils::TypeMismatchError and any other TypeError (library or not)
342
+ end
343
+
344
+ begin
345
+ CpfUtils.new({ formatter: { hidden: true } }, generator: { format: true })
346
+ rescue ArgumentError
347
+ # CpfUtils::InvalidArgumentCombinationError and any other ArgumentError (library or not)
348
+ end
349
+ ```
350
+
351
+ ```ruby
352
+ require 'cpf-utilities'
353
+
354
+ # 2) Bundled DomainError β€” this gem defines no DomainError; domain failures
355
+ # come from component packages and keep those namespaces (e.g. CpfFmt).
356
+ begin
357
+ CpfUtils.new.format('12345678909', hidden_start: -1)
358
+ rescue CpfFmt::DomainError
359
+ # CpfFmt::OutOfRangeError, CpfFmt::ValidationError, and other DomainError subclasses
360
+ end
361
+ ```
362
+
363
+ ```ruby
364
+ require 'cpf-utilities'
365
+
366
+ # 3) CpfUtils::Error β€” catches everything this gem raises, regardless of native ancestry.
367
+ # Does not catch CpfFmt::*, CpfGen::*, or CpfVal::* errors.
368
+ begin
369
+ CpfUtils.new('not-a-hash')
370
+ rescue CpfUtils::Error
371
+ # every custom error that includes CpfUtils::Error
372
+ end
373
+ ```
374
+
375
+ ```ruby
376
+ require 'cpf-utilities'
377
+
378
+ # 4) Specific leaf class β€” catches only that exact failure mode.
379
+ begin
380
+ CpfUtils.new('not-a-hash')
381
+ rescue CpfUtils::TypeMismatchError
382
+ # only CpfUtils::TypeMismatchError
383
+ end
384
+ ```
385
+
386
+ #### Propagated from bundled packages
387
+
388
+ Component errors keep their package namespaces and propagate unchanged through the faΓ§ade (and via nested / root sibling APIs). Each package also exposes an `*::Error` marker module for library-wide rescue. Invalid CPF **data** on `#is_valid` returns `false` (no domain raise). Formatting length failure is **not** raised by `#format` β€” it is delivered to **`on_fail`** as `CpfFmt::InvalidLengthError` (default `on_fail` returns `''`).
389
+
390
+ ##### Summary
391
+
392
+ | Class | Inherits from | Category | Trigger condition |
393
+ |-------|---------------|----------|-------------------|
394
+ | `CpfFmt::InvalidArgumentCombinationError` | `CpfFmt::InvalidArgumentCombinationError < ArgumentError < StandardError` (+ `include CpfFmt::Error`) | API misuse | Both an `options` instance/`Hash` and any non-`nil` keyword on `CpfFormatter` / `cpf_fmt` |
395
+ | `CpfFmt::TypeMismatchError` | `CpfFmt::TypeMismatchError < TypeError < StandardError` (+ `include CpfFmt::Error`) | API misuse | CPF input or formatter option has the wrong type (or `on_fail` return is not a `String`) |
396
+ | `CpfGen::InvalidArgumentCombinationError` | `CpfGen::InvalidArgumentCombinationError < ArgumentError < StandardError` (+ `include CpfGen::Error`) | API misuse | Both an `options` instance/`Hash` and any non-`nil` keyword on `CpfGenerator` / `cpf_gen` |
397
+ | `CpfGen::TypeMismatchError` | `CpfGen::TypeMismatchError < TypeError < StandardError` (+ `include CpfGen::Error`) | API misuse | Generator option (`format` / `prefix`) has the wrong type |
398
+ | `CpfVal::TypeMismatchError` | `CpfVal::TypeMismatchError < TypeError < StandardError` (+ `include CpfVal::Error`) | API misuse | CPF input is not a `String` or `Array` of strings |
399
+ | `CpfFmt::InvalidLengthError` | `CpfFmt::InvalidLengthError < CpfFmt::DomainError < RangeError < StandardError` (+ `include CpfFmt::Error`) | Domain error | Sanitized length β‰  11 β€” **passed to `on_fail`**, not raised by `#format` |
400
+ | `CpfFmt::OutOfRangeError` | `CpfFmt::OutOfRangeError < CpfFmt::DomainError < RangeError < StandardError` (+ `include CpfFmt::Error`) | Domain error | `hidden_start` / `hidden_end` outside `0`–`10` |
401
+ | `CpfFmt::ValidationError` | `CpfFmt::ValidationError < CpfFmt::DomainError < RangeError < StandardError` (+ `include CpfFmt::Error`) | Domain error | `hidden_key` / `dot_key` / `dash_key` contains a disallowed character |
402
+ | `CpfGen::ValidationError` | `CpfGen::ValidationError < CpfGen::DomainError < RangeError < StandardError` (+ `include CpfGen::Error`) | Domain error | `prefix` is ineligible (zeroed base or 9 repeated digits) |
403
+
404
+ ##### `CpfFmt::DomainError`
405
+
406
+ - **Inheritance:** `CpfFmt::DomainError < RangeError < StandardError` (includes `CpfFmt::Error`)
407
+ - **Category:** Domain error β€” ancestor for formatter domain leaves.
408
+ - **When it is raised:** Not raised directly; rescue target for `OutOfRangeError`, `ValidationError`, and re-raised `InvalidLengthError`.
409
+ - **Example:** Prefer rescuing a leaf, or `CpfFmt::DomainError` for all formatter domain failures.
410
+ - **How to rescue it:**
411
+
412
+ ```ruby
413
+ rescue CpfFmt::DomainError
414
+ # OutOfRangeError, ValidationError, InvalidLengthError (if re-raised from on_fail)
415
+ ```
416
+
417
+ ##### `CpfFmt::TypeMismatchError`
418
+
419
+ - **Inheritance:** `CpfFmt::TypeMismatchError < TypeError < StandardError` (includes `CpfFmt::Error`)
420
+ - **Category:** API misuse β€” wrong type for CPF input or a formatter option.
421
+ - **When it is raised:** Raised when `#format` / `cpf_fmt` receives a non-`String` / non-`Array<String>` input, an option has the wrong type, or `on_fail` does not return a `String`.
422
+ - **Example:**
423
+
424
+ ```ruby
425
+ CpfUtils.new.format(12_345) # raises CpfFmt::TypeMismatchError
426
+ ```
427
+
428
+ - **How to rescue it:**
429
+
430
+ ```ruby
431
+ rescue CpfFmt::TypeMismatchError
432
+ # formatter type-contract violation
433
+
434
+ rescue TypeError
435
+ # native type errors, including CpfFmt::TypeMismatchError
436
+ ```
437
+
438
+ ##### `CpfFmt::InvalidArgumentCombinationError`
439
+
440
+ - **Inheritance:** `CpfFmt::InvalidArgumentCombinationError < ArgumentError < StandardError` (includes `CpfFmt::Error`)
441
+ - **Category:** API misuse β€” mixed `options` and keywords on the formatter API.
442
+ - **When it is raised:** Raised by `CpfFmt::CpfFormatter` / `CpfFmt.cpf_fmt` when both an `options` instance/`Hash` and any non-`nil` keyword are passed. (The faΓ§ade raises `CpfUtils::InvalidArgumentCombinationError` for the same pattern on `CpfUtils#format`.)
443
+ - **Example:**
444
+
445
+ ```ruby
446
+ CpfFmt::CpfFormatter.new({ dash_key: '_' }, hidden: true)
447
+ # raises CpfFmt::InvalidArgumentCombinationError
448
+ ```
449
+
450
+ - **How to rescue it:**
451
+
452
+ ```ruby
453
+ rescue CpfFmt::InvalidArgumentCombinationError
454
+ # formatter invalid signature combination
455
+
456
+ rescue ArgumentError
457
+ # native argument errors, including this one
458
+ ```
459
+
460
+ ##### `CpfFmt::InvalidLengthError` (callback-delivered)
461
+
462
+ - **Inheritance:** `CpfFmt::InvalidLengthError < CpfFmt::DomainError < RangeError < StandardError` (includes `CpfFmt::Error`)
463
+ - **Category:** Domain error β€” sanitized CPF length is not exactly 11.
464
+ - **When it is raised:** **Not raised** by `#format` / `cpf_fmt`; constructed and passed as the second argument to `on_fail`.
465
+ - **Example:**
466
+
467
+ ```ruby
468
+ custom_fail = ->(value, error) {
469
+ error # => #<CpfFmt::InvalidLengthError ...>
470
+ "Invalid CPF: #{value}"
471
+ }
472
+
473
+ CpfUtils.new.format('123', on_fail: custom_fail) # => "Invalid CPF: 123"
474
+ CpfUtils.new.format('123') # => "" (default on_fail)
475
+ ```
476
+
477
+ - **How to rescue it:** Handle inside `on_fail` (typical), or rescue if you re-raise:
478
+
479
+ ```ruby
480
+ rescue CpfFmt::InvalidLengthError
481
+ # this exact length violation
482
+
483
+ rescue CpfFmt::DomainError
484
+ # RangeError-rooted domain failures from cpf-fmt
485
+ ```
486
+
487
+ ##### `CpfFmt::OutOfRangeError`
488
+
489
+ - **Inheritance:** `CpfFmt::OutOfRangeError < CpfFmt::DomainError < RangeError < StandardError` (includes `CpfFmt::Error`)
490
+ - **Category:** Domain error β€” `hidden_start` / `hidden_end` outside `0`–`10`.
491
+ - **When it is raised:** Raised when building or applying formatter options with an out-of-range hide index.
492
+ - **Example:**
493
+
494
+ ```ruby
495
+ CpfUtils.new.format('12345678909', hidden_start: -1) # raises CpfFmt::OutOfRangeError
496
+ ```
497
+
498
+ - **How to rescue it:**
499
+
500
+ ```ruby
501
+ rescue CpfFmt::OutOfRangeError
502
+ # this exact range violation
503
+
504
+ rescue CpfFmt::DomainError
505
+ # RangeError-rooted domain failures from cpf-fmt
506
+ ```
507
+
508
+ ##### `CpfFmt::ValidationError`
509
+
510
+ - **Inheritance:** `CpfFmt::ValidationError < CpfFmt::DomainError < RangeError < StandardError` (includes `CpfFmt::Error`)
511
+ - **Category:** Domain error β€” a key option contains a disallowed character.
512
+ - **When it is raised:** Raised when `hidden_key`, `dot_key`, or `dash_key` contains a forbidden character.
513
+ - **Example:**
514
+
515
+ ```ruby
516
+ CpfUtils.new(formatter: { dot_key: 'Γ₯' }) # raises CpfFmt::ValidationError
517
+ ```
518
+
519
+ - **How to rescue it:**
520
+
521
+ ```ruby
522
+ rescue CpfFmt::ValidationError
523
+ # this exact domain validation failure
524
+
525
+ rescue CpfFmt::DomainError
526
+ # RangeError-rooted domain failures from cpf-fmt
527
+ ```
528
+
529
+ ##### `CpfGen::DomainError`
530
+
531
+ - **Inheritance:** `CpfGen::DomainError < RangeError < StandardError` (includes `CpfGen::Error`)
532
+ - **Category:** Domain error β€” ancestor for generator domain leaves.
533
+ - **When it is raised:** Not raised directly; rescue target for `CpfGen::ValidationError`.
534
+ - **Example:** Prefer `rescue CpfGen::ValidationError` or `CpfGen::DomainError`.
535
+ - **How to rescue it:**
536
+
537
+ ```ruby
538
+ rescue CpfGen::DomainError
539
+ # ValidationError and other DomainError subclasses from cpf-gen
540
+ ```
541
+
542
+ ##### `CpfGen::TypeMismatchError`
543
+
544
+ - **Inheritance:** `CpfGen::TypeMismatchError < TypeError < StandardError` (includes `CpfGen::Error`)
545
+ - **Category:** API misuse β€” wrong type for a generator option.
546
+ - **When it is raised:** Raised when `format` or `prefix` has the wrong runtime type.
547
+ - **Example:**
548
+
549
+ ```ruby
550
+ CpfUtils.new.generate(prefix: 123) # raises CpfGen::TypeMismatchError
551
+ ```
552
+
553
+ - **How to rescue it:**
554
+
555
+ ```ruby
556
+ rescue CpfGen::TypeMismatchError
557
+ # generator type-contract violation
558
+
559
+ rescue TypeError
560
+ # native type errors, including CpfGen::TypeMismatchError
561
+ ```
562
+
563
+ ##### `CpfGen::InvalidArgumentCombinationError`
564
+
565
+ - **Inheritance:** `CpfGen::InvalidArgumentCombinationError < ArgumentError < StandardError` (includes `CpfGen::Error`)
566
+ - **Category:** API misuse β€” mixed `options` and keywords on the generator API.
567
+ - **When it is raised:** Raised by `CpfGen::CpfGenerator` / `CpfGen.cpf_gen` when both an `options` instance/`Hash` and any non-`nil` keyword are passed. (The faΓ§ade raises `CpfUtils::InvalidArgumentCombinationError` for the same pattern on `CpfUtils#generate`.)
568
+ - **Example:**
569
+
570
+ ```ruby
571
+ CpfGen::CpfGenerator.new({ format: true }, prefix: '123')
572
+ # raises CpfGen::InvalidArgumentCombinationError
573
+ ```
574
+
575
+ - **How to rescue it:**
576
+
577
+ ```ruby
578
+ rescue CpfGen::InvalidArgumentCombinationError
579
+ # generator invalid signature combination
580
+
581
+ rescue ArgumentError
582
+ # native argument errors, including this one
583
+ ```
584
+
585
+ ##### `CpfGen::ValidationError`
586
+
587
+ - **Inheritance:** `CpfGen::ValidationError < CpfGen::DomainError < RangeError < StandardError` (includes `CpfGen::Error`)
588
+ - **Category:** Domain error β€” ineligible `prefix`.
589
+ - **When it is raised:** Raised when `prefix` is a zeroed base (`'000000000'`) or 9 repeated digits (e.g. `'999999999'`).
590
+ - **Example:**
591
+
592
+ ```ruby
593
+ CpfUtils.new.generate(prefix: '000000000') # raises CpfGen::ValidationError
594
+ ```
595
+
596
+ - **How to rescue it:**
597
+
598
+ ```ruby
599
+ rescue CpfGen::ValidationError
600
+ # this exact domain validation failure
601
+
602
+ rescue CpfGen::DomainError
603
+ # RangeError-rooted domain failures from cpf-gen
604
+ ```
605
+
606
+ ##### `CpfVal::TypeMismatchError`
607
+
608
+ - **Inheritance:** `CpfVal::TypeMismatchError < TypeError < StandardError` (includes `CpfVal::Error`)
609
+ - **Category:** API misuse β€” wrong type for CPF input.
610
+ - **When it is raised:** Raised when `#is_valid` / `cpf_val` receives a value that is not a `String` or an `Array` of strings (including a non-string array element). Invalid CPF **data** returns `false` and does not raise.
611
+ - **Example:**
612
+
613
+ ```ruby
614
+ CpfUtils.new.is_valid(12_345_678_909) # raises CpfVal::TypeMismatchError
615
+ CpfUtils.new.is_valid('12345678900') # => false (invalid data, no raise)
616
+ ```
617
+
618
+ - **How to rescue it:**
619
+
620
+ ```ruby
621
+ rescue CpfVal::TypeMismatchError
622
+ # validator type-contract violation
623
+
624
+ rescue TypeError
625
+ # native type errors, including CpfVal::TypeMismatchError
626
+ ```
627
+
628
+ ### Bundled packages
629
+
630
+ | Package | Main resources | README |
631
+ |---------|----------------|--------|
632
+ | [`cpf-fmt`](https://rubygems.org/gems/cpf-fmt) | `CpfFmt::CpfFormatter`, `CpfFmt::CpfFormatterOptions`, `CpfFmt.cpf_fmt` | [docs](../cpf-fmt/README.md) |
633
+ | [`cpf-gen`](https://rubygems.org/gems/cpf-gen) | `CpfGen::CpfGenerator`, `CpfGen::CpfGeneratorOptions`, `CpfGen.cpf_gen` | [docs](../cpf-gen/README.md) |
634
+ | [`cpf-val`](https://rubygems.org/gems/cpf-val) | `CpfVal::CpfValidator`, `CpfVal.cpf_val` | [docs](../cpf-val/README.md) |
635
+
636
+ All of the above are pulled in as dependencies of **`cpf-utilities`**. For exhaustive option tables, exception lists, and edge-case behavior, see each package README.
637
+
638
+ ## Contribution & Support
639
+
640
+ We welcome contributions! Please see our [Contributing Guidelines](https://github.com/LacusSolutions/br-utils-ruby/blob/main/CONTRIBUTING.md) for details. If you find this project helpful, please consider:
641
+
642
+ - ⭐ Starring the repository
643
+ - 🀝 Contributing to the codebase
644
+ - πŸ’‘ [Suggesting new features](https://github.com/LacusSolutions/br-utils-ruby/issues)
645
+ - πŸ› [Reporting bugs](https://github.com/LacusSolutions/br-utils-ruby/issues)
646
+
647
+ ## License
648
+
649
+ This project is licensed under the MIT License β€” see the [LICENSE](https://github.com/LacusSolutions/br-utils-ruby/blob/main/LICENSE) file for details.
650
+
651
+ ## Changelog
652
+
653
+ See [CHANGELOG](./CHANGELOG.md) for a list of changes and version history.
654
+
655
+ ---
656
+
657
+ Made with ❀️ by [Lacus Solutions](https://github.com/LacusSolutions)