Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
cecdcf7
chore(cpf-val): update package metadata
juliolmuller Jul 21, 2026
22550db
feat(cpf-val): implement CPF validation functionality
juliolmuller Jul 21, 2026
f37418d
test(cpf-val): create CPF validation unit tests
juliolmuller Jul 21, 2026
5bfa426
docs(cpf-val): create changelogs file
juliolmuller Jul 21, 2026
5f5b6fe
docs(cpf-val): create README file
juliolmuller Jul 21, 2026
0b24140
docs(cpf-val): create Portuguese version of README file
juliolmuller Jul 21, 2026
38505f6
docs(cpf-val): reinforce error hierarchy
juliolmuller Jul 22, 2026
765f8de
test(cpf-val): update error message for consistency across codebase
juliolmuller Jul 22, 2026
05633bf
refactor(cpf-val): fix CPF input type
juliolmuller Jul 22, 2026
fe4af31
refactor(cnpj-val): fix CPF input type
juliolmuller Jul 22, 2026
e1e5d5b
refactor(cpf-fmt): fix CPF input type
juliolmuller Jul 22, 2026
d395337
refactor(cnpj-fmt): fix CPF input type
juliolmuller Jul 22, 2026
50e47fd
test(cpf-val): add test for `TypeMismatchError` on invalid input
juliolmuller Jul 22, 2026
ef56bb9
docs(cnpj-fmt): correct type predicate formatting
juliolmuller Jul 22, 2026
1f53dfb
docs(cnpj-val): correct type predicate formatting
juliolmuller Jul 22, 2026
deeb4f0
docs(cpf-fmt): correct type predicate formatting
juliolmuller Jul 22, 2026
20cd63b
docs(cpf-val): correct type predicate formatting
juliolmuller Jul 22, 2026
39534fd
docs(cpf-val): update inheritance details for `TypeMismatchError`
juliolmuller Jul 22, 2026
9d98a9c
docs(cpf-val): enhance rescue examples for error handling clarity
juliolmuller Jul 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/cnpj-fmt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ After `require 'cnpj-fmt'`:
- **`CnpjFmt::CnpjFormatterOptions`**: Class holding options; supports merge via constructor, `set`, and keyword arguments.
- **`CnpjFmt::CNPJ_LENGTH`**: `14` (constant).
- **`CnpjFmt::VERSION`**: gem version string.
- **Type predicate**: `CnpjFmt::CnpjInput` — `CnpjFmt::CnpjInput.accept?(value)` / `CnpjFmt::CnpjInput === value` is true only for `String` or `Array<String>`.
- **Errors**: `CnpjFmt::Error`, `CnpjFmt::DomainError`, `CnpjFmt::InvalidArgumentCombinationError`, `CnpjFmt::TypeMismatchError`, `CnpjFmt::InvalidLengthError`, `CnpjFmt::OutOfRangeError`, `CnpjFmt::ValidationError`.

### Other available resources
Expand Down
1 change: 1 addition & 0 deletions packages/cnpj-fmt/README.pt.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ Após `require 'cnpj-fmt'`:
- **`CnpjFmt::CnpjFormatterOptions`**: Classe que armazena opções; suporta mesclagem via construtor, `set` e argumentos nomeados.
- **`CnpjFmt::CNPJ_LENGTH`**: `14` (constante).
- **`CnpjFmt::VERSION`**: string de versão da gem.
- **Predicado de tipo**: `CnpjFmt::CnpjInput` — `CnpjFmt::CnpjInput.accept?(value)` / `CnpjFmt::CnpjInput === value` é verdadeiro apenas para `String` ou `Array<String>`.
- **Erros**: `CnpjFmt::Error`, `CnpjFmt::DomainError`, `CnpjFmt::InvalidArgumentCombinationError`, `CnpjFmt::TypeMismatchError`, `CnpjFmt::InvalidLengthError`, `CnpjFmt::OutOfRangeError`, `CnpjFmt::ValidationError`.

### Outros recursos disponíveis
Expand Down
1 change: 1 addition & 0 deletions packages/cnpj-fmt/src/cnpj-fmt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
# - {CnpjFmt.cnpj_fmt}
# - {CnpjFormatter}, {CnpjFormatterOptions}
# - {CNPJ_LENGTH}, {VERSION}
# - Type predicate: {CnpjFmt::CnpjInput} (+String+ or +Array<String>+)
# - Error marker {CnpjFmt::Error}; domain ancestor {CnpjFmt::DomainError};
# misuse errors {CnpjFmt::TypeMismatchError} and
# {CnpjFmt::InvalidArgumentCombinationError}; domain leaves
Expand Down
35 changes: 29 additions & 6 deletions packages/cnpj-fmt/src/cnpj-fmt/types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,40 @@ module CnpjFmt
hidden hidden_key hidden_start hidden_end dot_key slash_key dash_key escape encode on_fail
].freeze

# Represents valid input types for CNPJ formatting.
# Case-equality predicate for CNPJ input: +String+ or +Array<String>+.
#
# A CNPJ can be provided as:
# Matches the runtime contract of {CnpjFormatter#format} and {CnpjFmt.cnpj_fmt}.
# Use {CnpjInput.accept?} or +CnpjInput === value+ (in +case+/+when+) to test
# candidacy without raising.
#
# - A string containing alphanumeric characters (with or without formatting)
# - An array of strings, where each string represents an alphanumeric
# character or group of alphanumeric characters
# @example
# CnpjFmt::CnpjInput.accept?('91415732000793') # => true
# CnpjFmt::CnpjInput.accept?(%w[9 1 4 1]) # => true
# CnpjFmt::CnpjInput.accept?(123) # => false
# CnpjFmt::CnpjInput.accept?([1, 2, 3]) # => false
#
# @see CnpjFormatter#format
# @see CnpjFmt.cnpj_fmt
CnpjInput = Object
module CnpjInput
class << self
# @param value [Object] candidate input
# @return [Boolean] whether +value+ is a +String+ or an +Array+ of +String+
def accept?(value)
return true if value.is_a?(String)
return false unless value.is_a?(Array)

value.all?(String)
end

# Case-equality entry point for +case+/+when+ and +===+ checks.
#
# @param value [Object] candidate input
# @return [Boolean]
def ===(value)
accept?(value)
end
end
end

# Callback function type for handling formatting failures.
#
Expand Down
12 changes: 3 additions & 9 deletions packages/cnpj-fmt/src/cnpj-fmt/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,11 @@ def apply_post_processing(formatted_cnpj, options)
# @return [String] joined string input
# @raise [TypeMismatchError] if the input is not a +String+ or +Array<String>+
def to_string_input(cnpj_input)
return cnpj_input if cnpj_input.is_a?(String)

if cnpj_input.is_a?(Array)
cnpj_input.each do |item|
raise TypeMismatchError.new(cnpj_input, 'string or string[]') unless item.is_a?(String)
end
raise TypeMismatchError.new(cnpj_input, 'string or string[]') unless CnpjInput.accept?(cnpj_input)

return cnpj_input.join
end
return cnpj_input if cnpj_input.is_a?(String)

raise TypeMismatchError.new(cnpj_input, 'string or string[]')
cnpj_input.join
end

# Invokes the +on_fail+ callback and validates its return type.
Expand Down
16 changes: 16 additions & 0 deletions packages/cnpj-fmt/tests/cnpj_fmt.spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,20 @@
end
end
end

context 'when inspecting public types' do
it 'exposes CnpjInput as a String or Array<String> predicate' do
aggregate_failures do
expect(described_class::CnpjInput.accept?('91415732000793')).to be(true)
expect(described_class::CnpjInput.accept?(%w[9 1 4])).to be(true)
expect(described_class::CnpjInput.accept?(123)).to be(false)
expect(described_class::CnpjInput.accept?([1, 2, 3])).to be(false)
expect(described_class::CnpjInput.accept?(['9', 1])).to be(false)
# rubocop:disable Style/CaseEquality -- public case-equality protocol
expect(described_class::CnpjInput === 123).to be(false)
expect(described_class::CnpjInput === '91415732000793').to be(true)
# rubocop:enable Style/CaseEquality
end
end
end
end
3 changes: 2 additions & 1 deletion packages/cnpj-val/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,8 @@ After `require 'cnpj-val'`:
- **`CnpjVal::CnpjValidatorOptions`**: Class holding options; supports merge via constructor, `set`, and keyword arguments.
- **`CnpjVal::CNPJ_LENGTH`**: `14` (constant).
- **`CnpjVal::VERSION`**: gem version string.
- **Type markers**: `CnpjVal::CnpjInput`, `CnpjVal::CnpjType`, `CnpjVal::CnpjValidatorOptionsInput`.
- **Type predicate**: `CnpjVal::CnpjInput` — `CnpjVal::CnpjInput.accept?(value)` / `CnpjVal::CnpjInput === value` is true only for `String` or `Array<String>`.
- **Type markers**: `CnpjVal::CnpjType`, `CnpjVal::CnpjValidatorOptionsInput`.
- **Errors**: `CnpjVal::Error`, `CnpjVal::DomainError`, `CnpjVal::InvalidArgumentCombinationError`, `CnpjVal::TypeMismatchError`, `CnpjVal::ValidationError`.

### Other available resources
Expand Down
3 changes: 2 additions & 1 deletion packages/cnpj-val/README.pt.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,8 @@ Após `require 'cnpj-val'`:
- **`CnpjVal::CnpjValidatorOptions`**: Classe que armazena opções; suporta mesclagem via construtor, `set` e argumentos nomeados.
- **`CnpjVal::CNPJ_LENGTH`**: `14` (constante).
- **`CnpjVal::VERSION`**: string de versão da gem.
- **Marcadores de tipo**: `CnpjVal::CnpjInput`, `CnpjVal::CnpjType`, `CnpjVal::CnpjValidatorOptionsInput`.
- **Predicado de tipo**: `CnpjVal::CnpjInput` — `CnpjVal::CnpjInput.accept?(value)` / `CnpjVal::CnpjInput === value` é verdadeiro apenas para `String` ou `Array<String>`.
- **Marcadores de tipo**: `CnpjVal::CnpjType`, `CnpjVal::CnpjValidatorOptionsInput`.
- **Erros**: `CnpjVal::Error`, `CnpjVal::DomainError`, `CnpjVal::InvalidArgumentCombinationError`, `CnpjVal::TypeMismatchError`, `CnpjVal::ValidationError`.

### Outros recursos disponíveis
Expand Down
4 changes: 2 additions & 2 deletions packages/cnpj-val/src/cnpj-val.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
# - {CnpjVal.cnpj_val}
# - {CnpjVal::CnpjValidator}, {CnpjVal::CnpjValidatorOptions}
# - {CnpjVal::CNPJ_LENGTH}, {CnpjVal::VERSION}
# - Type markers: {CnpjVal::CnpjInput}, {CnpjVal::CnpjType},
# {CnpjVal::CnpjValidatorOptionsInput}
# - Type predicate: {CnpjVal::CnpjInput} (+String+ or +Array<String>+);
# type markers: {CnpjVal::CnpjType}, {CnpjVal::CnpjValidatorOptionsInput}
# - Error marker {CnpjVal::Error}; domain ancestor {CnpjVal::DomainError};
# misuse errors {CnpjVal::TypeMismatchError} and
# {CnpjVal::InvalidArgumentCombinationError}; domain leaf
Expand Down
13 changes: 4 additions & 9 deletions packages/cnpj-val/src/cnpj-val/cnpj_validator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

require_relative 'cnpj_validator_options'
require_relative 'errors'
require_relative 'types'

module CnpjVal
# Validator for CNPJ (Cadastro Nacional da Pessoa Jurídica) identifiers.
Expand Down Expand Up @@ -133,17 +134,11 @@ def sanitize_cnpj_input(cnpj_input, actual_options)
end

def to_string_input(cnpj_input)
return cnpj_input if cnpj_input.is_a?(String)

if cnpj_input.is_a?(Array)
cnpj_input.each do |item|
raise TypeMismatchError.new(cnpj_input, 'string or string[]') unless item.is_a?(String)
end
raise TypeMismatchError.new(cnpj_input, 'string or string[]') unless CnpjInput.accept?(cnpj_input)

return cnpj_input.join
end
return cnpj_input if cnpj_input.is_a?(String)

raise TypeMismatchError.new(cnpj_input, 'string or string[]')
cnpj_input.join
end

def sanitize(value, cnpj_type)
Expand Down
34 changes: 29 additions & 5 deletions packages/cnpj-val/src/cnpj-val/types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,40 @@ module CnpjVal
# Allowed values for the +type+ option.
CNPJ_TYPE_OPTIONS = %w[alphanumeric numeric].freeze

# Represents valid input types for CNPJ validation.
# Case-equality predicate for CNPJ input: +String+ or +Array<String>+.
#
# A CNPJ may be given as:
# Matches the runtime contract of {CnpjValidator#is_valid} and {CnpjVal.cnpj_val}.
# Use {CnpjInput.accept?} or +CnpjInput === value+ (in +case+/+when+) to test
# candidacy without raising.
#
# - A string of alphanumeric characters (with or without formatting).
# - An array of strings, each representing one or more alphanumeric characters.
# @example
# CnpjVal::CnpjInput.accept?('91415732000793') # => true
# CnpjVal::CnpjInput.accept?(%w[9 1 4 1]) # => true
# CnpjVal::CnpjInput.accept?(123) # => false
# CnpjVal::CnpjInput.accept?([1, 2, 3]) # => false
#
# @see CnpjValidator#is_valid
# @see CnpjVal.cnpj_val
CnpjInput = Object
module CnpjInput
class << self
# @param value [Object] candidate input
# @return [Boolean] whether +value+ is a +String+ or an +Array+ of +String+
def accept?(value)
return true if value.is_a?(String)
return false unless value.is_a?(Array)

value.all?(String)
end

# Case-equality entry point for +case+/+when+ and +===+ checks.
#
# @param value [Object] candidate input
# @return [Boolean]
def ===(value)
accept?(value)
end
end
end

# Character set for CNPJ values (generation or validation).
#
Expand Down
16 changes: 16 additions & 0 deletions packages/cnpj-val/tests/cnpj_val.spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,20 @@
end
end
end

context 'when inspecting public types' do
it 'exposes CnpjInput as a String or Array<String> predicate' do
aggregate_failures do
expect(described_class::CnpjInput.accept?('91415732000793')).to be(true)
expect(described_class::CnpjInput.accept?(%w[9 1 4])).to be(true)
expect(described_class::CnpjInput.accept?(123)).to be(false)
expect(described_class::CnpjInput.accept?([1, 2, 3])).to be(false)
expect(described_class::CnpjInput.accept?(['9', 1])).to be(false)
# rubocop:disable Style/CaseEquality -- public case-equality protocol
expect(described_class::CnpjInput === 123).to be(false)
expect(described_class::CnpjInput === '91415732000793').to be(true)
# rubocop:enable Style/CaseEquality
end
end
end
end
1 change: 1 addition & 0 deletions packages/cpf-fmt/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,7 @@ After `require 'cpf-fmt'`:
- **`CpfFmt::CpfFormatterOptions`**: Class holding options; supports merge via constructor, `set`, and keyword arguments.
- **`CpfFmt::CPF_LENGTH`**: `11` (constant).
- **`CpfFmt::VERSION`**: gem version string.
- **Type predicate**: `CpfFmt::CpfInput` — `CpfFmt::CpfInput.accept?(value)` / `CpfFmt::CpfInput === value` is true only for `String` or `Array<String>`.
- **Errors**: `CpfFmt::Error`, `CpfFmt::DomainError`, `CpfFmt::InvalidArgumentCombinationError`, `CpfFmt::TypeMismatchError`, `CpfFmt::InvalidLengthError`, `CpfFmt::OutOfRangeError`, `CpfFmt::ValidationError`.

### Other available resources
Expand Down
1 change: 1 addition & 0 deletions packages/cpf-fmt/README.pt.md
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ Após `require 'cpf-fmt'`:
- **`CpfFmt::CpfFormatterOptions`**: Classe que armazena opções; suporta mesclagem via construtor, `set` e argumentos nomeados.
- **`CpfFmt::CPF_LENGTH`**: `11` (constante).
- **`CpfFmt::VERSION`**: string de versão da gem.
- **Predicado de tipo**: `CpfFmt::CpfInput` — `CpfFmt::CpfInput.accept?(value)` / `CpfFmt::CpfInput === value` é verdadeiro apenas para `String` ou `Array<String>`.
- **Erros**: `CpfFmt::Error`, `CpfFmt::DomainError`, `CpfFmt::InvalidArgumentCombinationError`, `CpfFmt::TypeMismatchError`, `CpfFmt::InvalidLengthError`, `CpfFmt::OutOfRangeError`, `CpfFmt::ValidationError`.

### Outros recursos disponíveis
Expand Down
1 change: 1 addition & 0 deletions packages/cpf-fmt/src/cpf-fmt.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
# - {CpfFmt.cpf_fmt}
# - {CpfFormatter}, {CpfFormatterOptions}
# - {CPF_LENGTH}, {VERSION}
# - Type predicate: {CpfFmt::CpfInput} (+String+ or +Array<String>+)
# - Error marker {CpfFmt::Error}; domain ancestor {CpfFmt::DomainError};
# misuse errors {CpfFmt::TypeMismatchError} and
# {CpfFmt::InvalidArgumentCombinationError}; domain leaves
Expand Down
34 changes: 29 additions & 5 deletions packages/cpf-fmt/src/cpf-fmt/types.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,40 @@ module CpfFmt
hidden hidden_key hidden_start hidden_end dot_key dash_key escape encode on_fail
].freeze

# Represents valid input types for CPF formatting.
# Case-equality predicate for CPF input: +String+ or +Array<String>+.
#
# A CPF can be provided as:
# Matches the runtime contract of {CpfFormatter#format} and {CpfFmt.cpf_fmt}.
# Use {CpfInput.accept?} or +CpfInput === value+ (in +case+/+when+) to test
# candidacy without raising.
#
# - A string containing digits (with or without formatting)
# - An array of strings, where each string represents a digit or group of digits
# @example
# CpfFmt::CpfInput.accept?('82911017366') # => true
# CpfFmt::CpfInput.accept?(%w[8 2 9 1 1]) # => true
# CpfFmt::CpfInput.accept?(123) # => false
# CpfFmt::CpfInput.accept?([1, 2, 3]) # => false
#
# @see CpfFormatter#format
# @see CpfFmt.cpf_fmt
CpfInput = Object
module CpfInput
class << self
# @param value [Object] candidate input
# @return [Boolean] whether +value+ is a +String+ or an +Array+ of +String+
def accept?(value)
return true if value.is_a?(String)
return false unless value.is_a?(Array)

value.all?(String)
end

# Case-equality entry point for +case+/+when+ and +===+ checks.
#
# @param value [Object] candidate input
# @return [Boolean]
def ===(value)
accept?(value)
end
end
end

# Callback function type for handling formatting failures.
#
Expand Down
12 changes: 3 additions & 9 deletions packages/cpf-fmt/src/cpf-fmt/utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,17 +107,11 @@ def apply_post_processing(formatted_cpf, options)
# @return [String] joined string input
# @raise [TypeMismatchError] if the input is not a +String+ or +Array<String>+
def to_string_input(cpf_input)
return cpf_input if cpf_input.is_a?(String)

if cpf_input.is_a?(Array)
cpf_input.each do |item|
raise TypeMismatchError.new(cpf_input, 'string or string[]') unless item.is_a?(String)
end
raise TypeMismatchError.new(cpf_input, 'string or string[]') unless CpfInput.accept?(cpf_input)

return cpf_input.join
end
return cpf_input if cpf_input.is_a?(String)

raise TypeMismatchError.new(cpf_input, 'string or string[]')
cpf_input.join
end

# Invokes the +on_fail+ callback and validates its return type.
Expand Down
16 changes: 16 additions & 0 deletions packages/cpf-fmt/tests/cpf_fmt.spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,20 @@
end
end
end

context 'when inspecting public types' do
it 'exposes CpfInput as a String or Array<String> predicate' do
aggregate_failures do
expect(described_class::CpfInput.accept?('82911017366')).to be(true)
expect(described_class::CpfInput.accept?(%w[8 2 9])).to be(true)
expect(described_class::CpfInput.accept?(123)).to be(false)
expect(described_class::CpfInput.accept?([1, 2, 3])).to be(false)
expect(described_class::CpfInput.accept?(['8', 2])).to be(false)
# rubocop:disable Style/CaseEquality -- public case-equality protocol
expect(described_class::CpfInput === 123).to be(false)
expect(described_class::CpfInput === '82911017366').to be(true)
# rubocop:enable Style/CaseEquality
end
end
end
end
16 changes: 16 additions & 0 deletions packages/cpf-val/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
# cpf-val

## 1.0.0

### 🚀 Stable Version Released!

Utility module to validate CPF (Brazilian personal ID) strings. Main features:

- **Multiple interfaces**: supports `CpfVal.cpf_val` and `CpfVal::CpfValidator#is_valid` with no options or keyword arguments.
- **Flexible input**: accepts a `String` or `Array<String>` (formatted or raw); non-digit characters are stripped before validation.
- **Strict validation**: requires exactly `CpfVal::CPF_LENGTH` (`11`) digits and matching check digits; repeated-digit bases return `false`.
- **Structured errors**: `CpfVal::Error` marker with misuse leaf `TypeMismatchError`; invalid CPF data returns `false` without raising.
- **Check-digit delegation**: validation uses `CpfDV::CpfCheckDigits` from `cpf-dv` for eligibility rules and verifier digits.
- **Constants**: `CpfVal::CPF_LENGTH` (`11`) for the required digit count after sanitization.
- **Dependencies**: runtime gems `cpf-dv` and `lacus-utils` (Ruby `>= 3.1`).

For detailed usage and API reference, see the [README](./README.md).
Loading