diff --git a/CHANGELOG.md b/CHANGELOG.md index 33c54c2..31b9d82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- RENAVAM instance for vehicle registration management. +- Unit tests for RENAVAM validation, formatting, and generation. +- Functions for RENAVAM validation and generation. + ## [2.0.0] - 2025-03-26 ### Added diff --git a/brazilcode.go b/brazilcode.go index b836d35..a4210c2 100644 --- a/brazilcode.go +++ b/brazilcode.go @@ -7,6 +7,7 @@ import ( "github.com/potatowski/brazilcode/v2/src/cnpj" "github.com/potatowski/brazilcode/v2/src/cpf" iface "github.com/potatowski/brazilcode/v2/src/interface" + "github.com/potatowski/brazilcode/v2/src/renavam" "github.com/potatowski/brazilcode/v2/src/voterRegistration" ) @@ -29,6 +30,12 @@ var CNH = cnh.CNH{} // registration information. var VoterRegistration = voterRegistration.VoterRegistration{} +// RENAVAM is an instance of the renavam.RENAVAM struct, which represents +// the Brazilian National Registry of Motor Vehicles (Registro Nacional de +// VeĆ­culos Automotores). It is used to manage and validate vehicle +// registration information in Brazil. +var RENAVAM = renavam.RENAVAM{} + // Documents is a map that associates document types with their corresponding // iface.Document implementations. The keys represent the document type names // (e.g., "CPF", "CNPJ", "CNH", "VoterRegistration"), and the values are the @@ -38,6 +45,7 @@ var Documents = map[string]iface.Document{ "CNPJ": CNPJ, "CNH": CNH, "VoterRegistration": VoterRegistration, + "RENAVAM": RENAVAM, } // IsValid checks if the provided document is valid based on its type. diff --git a/src/renavam/renavam.go b/src/renavam/renavam.go new file mode 100644 index 0000000..d3df2b6 --- /dev/null +++ b/src/renavam/renavam.go @@ -0,0 +1,94 @@ +package renavam + +import ( + "fmt" + + "github.com/potatowski/brazilcode/v2/src/utils" +) + +var ( + ErrRenavamInvalidLength = fmt.Errorf("invalid RENAVAM length") + ErrRenavamInvalid = fmt.Errorf("invalid RENAVAM") +) + +var pesos = []int{3, 2, 9, 8, 7, 6, 5, 4, 3, 2} + +type RENAVAM struct{} + +// IsValid validates a RENAVAM (Brazilian vehicle registration number) document. +// It checks if the provided document has the correct length and verifies its checksum. +// +// Parameters: +// +// doc (string): The RENAVAM document to be validated. +// +// Returns: +// +// error: Returns ErrRenavamInvalidLength if the document does not have 11 characters. +// Returns ErrRenavamInvalid if the checksum validation fails. +// Returns nil if the document is valid. +func (iDoc RENAVAM) IsValid(doc string) error { + doc = utils.RemoveChar(doc) + if len(doc) != 11 { + return ErrRenavamInvalidLength + } + + var sum int + for i := 0; i < len(doc)-1; i++ { + sum += int(doc[i]-'0') * pesos[i] + } + + digit := utils.GetDigit(sum) + if int(doc[len(doc)-1]-'0') != digit { + return ErrRenavamInvalid + } + + return nil +} + +// Generate creates a new RENAVAM (Brazilian vehicle registration number) +// by generating a random base document number, calculating its checksum +// using predefined weights, and appending the resulting digit to the base. +// Returns: +// +// doc (string): The RENAVAM document. +func (iDoc RENAVAM) Generate() (string, error) { + doc := utils.GenerateRandomDoc(10, 9) + var sum int + for i := 0; i < len(doc); i++ { + sum += int(doc[i]-'0') * pesos[i] + } + + digit := utils.GetDigit(sum) + doc = fmt.Sprintf("%s%d", doc, digit) + if err := iDoc.IsValid(doc); err != nil { + return "", err + } + + return doc, nil +} + +// Format formats a RENAVAM document string by removing any non-numeric characters +// and applying a specific pattern. The formatted output separates the first 8 digits +// from the last 3 digits with a hyphen. +// +// Parameters: +// +// doc - The RENAVAM document string to be formatted. +// +// Returns: +// +// A formatted RENAVAM string in the pattern "XXXXXXXXXXX" if the input is valid, +// or an error if the input length is not exactly 11 characters. +// +// Errors: +// +// ErrRenavamInvalidLength - Returned if the input string does not have exactly 11 characters. +func (iDoc RENAVAM) Format(doc string) (string, error) { + err := iDoc.IsValid(doc) + if err != nil { + return "", err + } + + return doc, nil +} diff --git a/src/renavam/renavam_test.go b/src/renavam/renavam_test.go new file mode 100644 index 0000000..60004b7 --- /dev/null +++ b/src/renavam/renavam_test.go @@ -0,0 +1,88 @@ +package renavam + +import ( + "testing" + + iface "github.com/potatowski/brazilcode/v2/src/interface" +) + +var doc iface.Document = RENAVAM{} + +func TestIsValid(t *testing.T) { + testCases := []struct { + name string + doc string + expected error + }{ + { + name: "Valid RENAVAM", + doc: "62959061142", + expected: nil, + }, + { + name: "Invalid RENAVAM - wrong length", + doc: "6295906114", + expected: ErrRenavamInvalidLength, + }, + { + name: "Invalid RENAVAM", + doc: "62959061141", + expected: ErrRenavamInvalid, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := doc.IsValid(tc.doc) + if err != tc.expected { + t.Errorf("Expected error to be '%v' but got '%v'", tc.expected, err) + } + }) + } +} + +func TestFormat(t *testing.T) { + testCases := []struct { + name string + doc string + expected string + expectedError error + }{ + { + name: "Valid RENAVAM", + doc: "62959061142", + expected: "62959061142", + expectedError: nil, + }, + { + name: "Invalid RENAVAM", + doc: "62959061141", + expected: "", + expectedError: ErrRenavamInvalid, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result, err := doc.Format(tc.doc) + if err != nil && err != tc.expectedError { + t.Errorf("Expected error to be '%v' but got '%v'", tc.expectedError, err) + } + + if result != tc.expected { + t.Errorf("Expected result to be '%v' but got '%v'", tc.expected, result) + } + }) + } +} + +func TestGenerate(t *testing.T) { + renavam, err := doc.Generate() + if err != nil { + t.Errorf("[TEST-RENAVAM-generate] unexpected error: %v\n RENAVAM generated: %s", err, renavam) + } + + if len(renavam) != 11 { + t.Errorf("[TEST-RENAVAM-generate] unexpected result: generated RENAVAM has invalid length, got %d", len(renavam)) + } +}