Skip to content

Commit

Permalink
return vat.ErrInvalidID sentinel
Browse files Browse the repository at this point in the history
  • Loading branch information
ungerik committed Feb 5, 2025
1 parent 262eb8d commit d6aeea2
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 25 deletions.
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ require (
github.com/jinzhu/now v1.1.5
github.com/stretchr/testify v1.10.0
github.com/teamwork/tnef v0.0.0-20200108124832-7deabccfdb32
github.com/ungerik/go-fs v0.0.0-20241213130555-c93eabeaac28
github.com/ungerik/go-fs v0.0.0-20250123134246-3ac71b34b8e3
github.com/ungerik/go-reflection v0.0.0-20240905081803-708928fe0862
golang.org/x/net v0.33.0
golang.org/x/text v0.21.0
mvdan.cc/xurls/v2 v2.5.0
golang.org/x/net v0.34.0
golang.org/x/text v0.22.0
mvdan.cc/xurls/v2 v2.6.0
)

require (
Expand All @@ -30,6 +30,6 @@ require (
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/teamwork/test v0.0.0-20200108114543-02621bae84ad // indirect
github.com/teamwork/utils v0.0.0-20220314153103-637fa45fa6cc // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/sys v0.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
20 changes: 10 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,19 @@ github.com/teamwork/tnef v0.0.0-20200108124832-7deabccfdb32 h1:j15wq0XPAY/HR/0+d
github.com/teamwork/tnef v0.0.0-20200108124832-7deabccfdb32/go.mod h1:v7dFaQrF/4+curx7UTH9rqTkHTgXqghfI3thANW150o=
github.com/teamwork/utils v0.0.0-20220314153103-637fa45fa6cc h1:BidxxRk9kopF5IGEyosTRtanaYVYTUbGJh9eULOhv04=
github.com/teamwork/utils v0.0.0-20220314153103-637fa45fa6cc/go.mod h1:3Fn0qxFeRNpvsg/9T1+btOOOKkd1qG2nPYKKcOmNpcs=
github.com/ungerik/go-fs v0.0.0-20241213130555-c93eabeaac28 h1:7ofFy5GIUbWOlFvGbThvFGCEYSsaSNbmvc1w40Z1KfA=
github.com/ungerik/go-fs v0.0.0-20241213130555-c93eabeaac28/go.mod h1:pKXmcBwT1D8y0dEHrOFDYGtYp4NNn1g9nDd/9vLQ6Dg=
github.com/ungerik/go-fs v0.0.0-20250123134246-3ac71b34b8e3 h1:hK4P3BHoJuXbKBZzKtLBchIj5IOVNXE309FW8kA8x0U=
github.com/ungerik/go-fs v0.0.0-20250123134246-3ac71b34b8e3/go.mod h1:pKXmcBwT1D8y0dEHrOFDYGtYp4NNn1g9nDd/9vLQ6Dg=
github.com/ungerik/go-reflection v0.0.0-20240905081803-708928fe0862 h1:rxp/NtuHYkx0HRYL/Y7xh/07ZwI/Pbk3VPkVoq3IUgQ=
github.com/ungerik/go-reflection v0.0.0-20240905081803-708928fe0862/go.mod h1:Ic/uip1MCECqTPItawo5lRHmyaOT6vCM0UuKrczg6LY=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
mvdan.cc/xurls/v2 v2.5.0 h1:lyBNOm8Wo71UknhUs4QTFUNNMyxy2JEIaKKo0RWOh+8=
mvdan.cc/xurls/v2 v2.5.0/go.mod h1:yQgaGQ1rFtJUzkmKiHYSSfuQxqfYmd//X6PxvholpeE=
mvdan.cc/xurls/v2 v2.6.0 h1:3NTZpeTxYVWNSokW3MKeyVkz/j7uYXYiMtXRUfmjbgI=
mvdan.cc/xurls/v2 v2.6.0/go.mod h1:bCvEZ1XvdA6wDnxY7jPPjEmigDtvtvPXAD/Exa9IMSk=
6 changes: 6 additions & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rogpeppe/go-internal v1.13.2-0.20241226121412-a5dc8ff20d0a/go.mod h1:S8kfXMp+yh77OxPD4fdM6YUknrZpQxLhvxzS4gDHENY=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
Expand All @@ -27,17 +28,20 @@ golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.11.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand All @@ -56,9 +60,11 @@ golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
29 changes: 19 additions & 10 deletions vat/id.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package vat

import (
"database/sql/driver"
"errors"
"fmt"
"strings"
"unicode"

"github.com/domonda/go-errs"
"github.com/domonda/go-types/country"
"github.com/domonda/go-types/strutil"
)
Expand All @@ -16,13 +16,17 @@ import (
// https://europa.eu/youreurope/business/taxation/vat/vat-digital-services-moss-scheme/index_en.htm
const MOSSSchemaVATCountryCode = "EU"

const ErrInvalidID errs.Sentinel = "invalid VAT ID"

// ID is a european VAT ID.
// ID implements the database/sql.Scanner and database/sql/driver.Valuer interfaces,
// returning errors when the ID is not valid and can't be normalized.
// Use NullableID to read and write SQL NULL values.
type ID string

// NormalizeVATID returns str as normalized VAT ID or an error.
//
// Returns a wrapped ErrInvalidID error if the VAT ID is not valid.
func NormalizeVATID(str string) (ID, error) {
return ID(str).Normalized()
}
Expand Down Expand Up @@ -51,37 +55,37 @@ func isVATIDTrimRune(r rune) bool {
// }

// Normalized returns the id in normalized form,
// or an error if the VAT ID is not valid.
// or a wrapped ErrInvalidID error if the VAT ID is not valid.
func (id ID) Normalized() (ID, error) {
normalized := ID(strings.ToUpper(strutil.RemoveRunesString(string(id), strutil.IsSpace, unicode.IsPunct)))

// Check length
if len(normalized) < IDMinLength {
return id, fmt.Errorf("VAT ID %q is too short", string(id))
return id, fmt.Errorf("%w: %q is too short", ErrInvalidID, string(id))
}
if len(normalized) > IDMaxLength {
return id, fmt.Errorf("VAT ID %q is too long", string(id))
return id, fmt.Errorf("%w: %q is too long", ErrInvalidID, string(id))
}

// Check country code
countryCode := country.Code(normalized[:2])
if countryCode != MOSSSchemaVATCountryCode && !countryCode.Valid() {
return id, fmt.Errorf("VAT ID %q has an invalid country code: %q", string(id), string(countryCode))
return id, fmt.Errorf("%w: %q has an invalid country code: %q", ErrInvalidID, string(id), string(countryCode))
}

// Check format with country specific regex
regex, ok := idRegex[countryCode]
if !ok {
return id, fmt.Errorf("VAT ID %q has an unsupported country code: %q", string(id), string(countryCode))
return id, fmt.Errorf("%w: %q has an unsupported country code: %q", ErrInvalidID, string(id), string(countryCode))
}
if !regex.MatchString(string(normalized)) {
return id, fmt.Errorf("VAT ID %q has an invalid format", string(id))
return id, fmt.Errorf("%w: %q has an invalid format", ErrInvalidID, string(id))
}

// Test checkFunc-sum if a function is available for the country
checkFunc, ok := checkSumFuncs[countryCode]
if ok && !checkFunc(id, normalized) {
return id, fmt.Errorf("VAT ID %q has an invalid check-sum", string(id))
return id, fmt.Errorf("%w: %q has an invalid check-sum", ErrInvalidID, string(id))
}

return normalized, nil
Expand Down Expand Up @@ -112,12 +116,17 @@ func (id ID) ValidAndNormalized() bool {

// Validate returns an error if id is not a valid VAT ID,
// ignoring normalization.
//
// Returns a wrapped ErrInvalidID error if the VAT ID is not valid.
func (id ID) Validate() error {
_, err := id.Normalized()
return err
}

// ValidateIsNormalized returns an error if id is not a valid and normalized VAT ID.
//
// Will return ErrInvalidID if the id is not valid,
// but another error if the id is valid but not normalized.
func (id ID) ValidateIsNormalized() error {
norm, err := id.Normalized()
if err != nil {
Expand Down Expand Up @@ -213,9 +222,9 @@ func (id *ID) Scan(value any) error {
case []byte:
*id = ID(x)
case nil:
return errors.New("can't scan SQL NULL as vat.ID")
return fmt.Errorf("%w: can't scan SQL NULL as vat.ID", ErrInvalidID)
default:
return fmt.Errorf("can't scan SQL value of type %T as vat.ID", value)
return fmt.Errorf("%w: can't scan SQL value of type %T as vat.ID", ErrInvalidID, value)
}
return nil
}
Expand Down

0 comments on commit d6aeea2

Please sign in to comment.