Skip to content

Commit

Permalink
Updated parser to return similar named errors as in
Browse files Browse the repository at this point in the history
  • Loading branch information
johnerikhalse committed Jun 7, 2023
1 parent ef47de1 commit 83994ff
Show file tree
Hide file tree
Showing 8 changed files with 327 additions and 206 deletions.
4 changes: 2 additions & 2 deletions canonicalizer/canonicalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ type profile struct {
func (p *profile) Parse(rawUrl string) (*url.Url, error) {
u, err := p.Parser.Parse(rawUrl)
if err != nil {
if errors.Code(err) == errors.FailRelativeUrlWithNoBase && p.defaultScheme != "" {
if errors.Type(err) == errors.MissingSchemeNonRelativeURL && p.defaultScheme != "" {
rawUrl = p.defaultScheme + "://" + rawUrl
u, err = p.Parser.Parse(rawUrl)
}
Expand All @@ -64,7 +64,7 @@ func (p *profile) Parse(rawUrl string) (*url.Url, error) {
func (p *profile) ParseRef(rawUrl, ref string) (*url.Url, error) {
b, err := p.Parser.Parse(rawUrl)
if err != nil {
if errors.Code(err) == errors.FailRelativeUrlWithNoBase && p.defaultScheme != "" {
if errors.Type(err) == errors.MissingSchemeNonRelativeURL && p.defaultScheme != "" {
rawUrl = p.defaultScheme + "://" + rawUrl
b, err = p.Parser.Parse(rawUrl)
}
Expand Down
90 changes: 35 additions & 55 deletions errors/codes.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,65 +16,45 @@

package errors

import (
"fmt"
)

// ErrorCode is data type of error codes for different kind of errors
type ErrorCode int32
type ErrorType string

// Validation errors
// IDNA errors
const (
IllegalCodePoint ErrorCode = iota + 100
InvalidPercentEncoding
IllegalLeadingOrTrailingChar
IllegalTabOrNewline
AtInAuthority
IllegalSlashes
IllegalLocalFileAndHostCombo
BadWindowsDriveLetter
IllegalIPv4Address
IllegalIPv6Address
CouldNotDecodeHost
DomainToASCII ErrorType = "Unicode ToASCII records an error or returns the empty string"
DomainToUnicode ErrorType = "Unicode ToUnicode records an error"
)

// Validation failures
// Host parsing errors
const (
FailIllegalCodePoint ErrorCode = iota + 500
FailIllegalScheme
FailRelativeUrlWithNoBase
FailMissingHost
FailIllegalHost
FailIllegalPort
DomainInvalidCodePoint ErrorType = "The host contains a forbidden domain code point"
HostInvalidCodePoint ErrorType = "An opaque host (in a URL that is not special) contains a forbidden host code point"
IPv4EmptyPart ErrorType = "An IPv4 address ends with a U+002E (.)"
IPv4TooManyParts ErrorType = "An IPv4 address has more than four parts"
IPv4NonNumericPart ErrorType = "An IPv4 address contains a non-numeric part"
IPv4NonDecimalPart ErrorType = "The IPv4 address contains numbers expressed using hexadecimal or octal digits"
IPv4OutOfRangePart ErrorType = "An IPv4 address contains a part that is greater than 255"
IPv6Unclosed ErrorType = "An IPv6 address is missing the closing U+005D (])"
IPv6InvalidCompression ErrorType = "An IPv6 address begins with improper compression"
IPv6TooManyPieces ErrorType = "An IPv6 address has more than eight pieces"
IPv6MultipleCompression ErrorType = "An IPv6 address contains multiple instances of '::'"
IPv6InvalidCodePoint ErrorType = "An IPv6 address contains a code point that is neither an ASCII hex digit nor a U+003A (:). Or it unexpectedly ends"
IPv6TooFewPieces ErrorType = "An uncompressed IPv6 address contains fewer than 8 pieces"
IPv4InIPv6TooManyPieces ErrorType = "An IPv4 address is found in an IPv6 address, but the IPv6 address has more than 6 pieces"
IPv4InIPv6InvalidCodePoint ErrorType = "An IPv4 address is found in an IPv6 address and one of the following is true: 1. An IPv4 part is empty or contains a non-ASCII digit. 2. An IPv4 part contains a leading 0. 3. There are too many IPv4 parts"
IPv4InIPv6OutOfRangePart ErrorType = "An IPv4 address is found in an IPv6 address and one of the IPv4 parts is greater than 255"
IPv4InIPv6TooFewParts ErrorType = "An IPv4 address is found in an IPv6 address and there are too few IPv4 parts"
)

func (e ErrorCode) String() string {
return fmt.Sprintf("%d: %s", e, messages[e])
}

func (e ErrorCode) Int32() int32 {
return int32(e)
}

var messages = map[ErrorCode]string{
// Validation errors
IllegalCodePoint: "illegal code point",
InvalidPercentEncoding: "invalid percent encoding",
IllegalLeadingOrTrailingChar: "illegal leading or trailing character",
IllegalTabOrNewline: "illegal tab or newline",
AtInAuthority: "'@' in authority",
IllegalSlashes: "illegal combination of slashes",
IllegalLocalFileAndHostCombo: "illegal combination of host and local file reference",
BadWindowsDriveLetter: "badly formatted windows drive letter",
IllegalIPv4Address: "illegal IPv4 address",
IllegalIPv6Address: "illegal IPv6 address",
CouldNotDecodeHost: "could not decode host",

// Validation failures
FailIllegalCodePoint: "illegal code point",
FailIllegalScheme: "illegal scheme",
FailRelativeUrlWithNoBase: "relative url with missing or invalid base url",
FailMissingHost: "missing host",
FailIllegalHost: "illegal host",
FailIllegalPort: "illegal port",
}
// URL parsing errors
const (
InvalidURLUnit ErrorType = "A code point is found that is not a URL unit"
SpecialSchemeMissingFollowingSolidus ErrorType = "The input’s scheme is not followed by '//'"
MissingSchemeNonRelativeURL ErrorType = "The input is missing a scheme, because it does not begin with an ASCII alpha, and either no base URL was provided or the base URL cannot be used as a base URL because it has an opaque path"
InvalidReverseSolidus ErrorType = "The URL has a special scheme and it uses U+005C (\\) instead of U+002F (/)"
InvalidCredentials ErrorType = "The input includes credentials"
HostMissing ErrorType = "The input has a special scheme, but does not contain a host"
PortOutOfRange ErrorType = "The input's port is outside the range [0-65535]"
PortInvalid ErrorType = "The input's port is not a number"
FileInvalidWindowsDriveLetter ErrorType = "The input is a relative-URL string that starts with a Windows drive letter and the base URL’s scheme is 'file'"
FileInvalidWindowsDriveLetterHost ErrorType = "A file: URL’s host is a Windows drive letter"
)
114 changes: 73 additions & 41 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,22 @@ import (
"fmt"
)

// UrlError is the struct of url error
type UrlError struct {
code ErrorCode
descr string
url string
cause error // the root cause for this error
// ValidationError indicates that the url is not valid
type ValidationError struct {
errorType ErrorType
cause error // the root cause for this error
descr string // description of the error
failure bool // true if the error is a failure, false if it is a warning
url string
}

func (e *UrlError) Error() string {
errMsg := fmt.Sprintf("Error: %s", e.code)
func (e *ValidationError) Error() string {
errMsg := fmt.Sprintf("Error: %s", e.errorType)
if e.descr != "" {
errMsg += fmt.Sprintf(" '%s'", e.descr)
errMsg += fmt.Sprintf(": '%s'", e.descr)
}
if e.url != "" {
errMsg += fmt.Sprintf(", Url: %s", e.url)
errMsg += fmt.Sprintf(". Url: '%s'", e.url)
}
if nil == e.cause {
return errMsg
Expand All @@ -43,29 +44,42 @@ func (e *UrlError) Error() string {
return errMsg + ", Cause: " + e.cause.Error()
}

func (e *UrlError) Unwrap() error {
// Unwrap returns the root cause for this error
func (e *ValidationError) Unwrap() error {
return e.cause
}

func (e *UrlError) Code() ErrorCode {
return e.code
// Type returns the error type
func (e *ValidationError) Type() ErrorType {
return e.errorType
}

func (e *UrlError) Url() string {
// Url returns the url causing the error
func (e *ValidationError) Url() string {
return e.url
}

// Code returns the error code
func Code(err error) ErrorCode {
type coder interface {
Code() ErrorCode
// Failure returns true if the error is a failure, false if it is a warning
func (e *ValidationError) Failure() bool {
return e.failure
}

// Description returns the error description
func (e *ValidationError) Description() string {
return e.descr
}

// Type returns the error type
func Type(err error) ErrorType {
type typer interface {
Type() ErrorType
}

cd, ok := err.(coder)
cd, ok := err.(typer)
if !ok {
return 0
return ""
}
return cd.Code()
return cd.Type()
}

// Description returns the error description
Expand Down Expand Up @@ -94,38 +108,56 @@ func Url(err error) string {
return m.Url()
}

// Failure returns true if the error is a failure, false if it is a warning.
// If the error does not implement the Failure() method, true is returned
func Failure(err error) bool {
type failure interface {
Failure() bool
}

m, ok := err.(failure)
if !ok {
return true
}
return m.Failure()
}

// Error constructs a new error
func Error(code ErrorCode, url string) error {
return &UrlError{
code: code,
url: url,
func Error(errorType ErrorType, url string, failure bool) error {
return &ValidationError{
errorType: errorType,
url: url,
failure: failure,
}
}

// ErrorWithDescr constructs a new error
func ErrorWithDescr(code ErrorCode, descr string, url string) error {
return &UrlError{
code: code,
descr: descr,
url: url,
func ErrorWithDescr(errorType ErrorType, descr string, url string, failure bool) error {
return &ValidationError{
errorType: errorType,
descr: descr,
url: url,
failure: failure,
}
}

// Wrap wraps an error with an error code and url
func Wrap(err error, code ErrorCode, url string) error {
return &UrlError{
code: code,
url: url,
cause: err,
func Wrap(err error, errorType ErrorType, url string, failure bool) error {
return &ValidationError{
errorType: errorType,
url: url,
cause: err,
failure: failure,
}
}

// WrapWithDescr wraps an error with an error code, url and a description
func WrapWithDescr(err error, code ErrorCode, descr string, url string) error {
return &UrlError{
code: code,
descr: descr,
url: url,
cause: err,
func WrapWithDescr(err error, errorType ErrorType, descr string, url string, failure bool) error {
return &ValidationError{
errorType: errorType,
descr: descr,
url: url,
cause: err,
failure: failure,
}
}
17 changes: 7 additions & 10 deletions errors/errors_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,19 @@ import (
)

func TestError(t *testing.T) {
type args struct {
code ErrorCode
url string
}
tests := []struct {
name string
code ErrorCode
url string
name string
errorType ErrorType
url string
failure bool
}{
{"1", IllegalLeadingOrTrailingChar, "http://example.com\t"},
{"1", HostInvalidCodePoint, "http://example.com\t", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := Error(tt.code, tt.url)
err := Error(tt.errorType, tt.url, false)
fmt.Printf("Error: %s\n", err)
fmt.Printf("Code: %s\n", Code(err))
fmt.Printf("Type: %s\n", Type(err))
fmt.Printf("Url: %s\n", Url(err))
//if err := Error(tt.args.code, tt.args.url); (err != nil) != tt.wantErr {
// t.Errorf("Error() error = %v, wantErr %v", err, tt.wantErr)
Expand Down
46 changes: 40 additions & 6 deletions url/errorhandler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,50 @@ import (
"github.com/nlnwa/whatwg-url/errors"
)

func (p *parser) handleError(u *Url, code errors.ErrorCode) error {
// handleError handles an error according to the options set for the parser
func (p *parser) handleError(u *Url, errorType errors.ErrorType, failure bool) error {
e := errors.Error(errorType, u.inputUrl, failure)
if p.opts.reportValidationErrors {
u.validationErrors = append(u.validationErrors, errors.Error(code, u.inputUrl))
u.validationErrors = append(u.validationErrors, e)
}
if p.opts.failOnValidationError {
return errors.Error(code, u.inputUrl)
if failure || p.opts.failOnValidationError {
return e
}
return nil
}

func (p *parser) handleFailure(u *Url, code errors.ErrorCode, err error) (*Url, error) {
return nil, errors.Wrap(err, code, u.inputUrl)
// handleErrorWithDescription handles an error according to the options set for the parser
func (p *parser) handleErrorWithDescription(u *Url, errorType errors.ErrorType, failure bool, descr string) error {
e := errors.ErrorWithDescr(errorType, descr, u.inputUrl, failure)
if p.opts.reportValidationErrors {
u.validationErrors = append(u.validationErrors, e)
}
if failure || p.opts.failOnValidationError {
return e
}
return nil
}

// handleWrappedError handles an error according to the options set for the parser
func (p *parser) handleWrappedError(u *Url, errorType errors.ErrorType, failure bool, cause error) error {
e := errors.Wrap(cause, errorType, u.inputUrl, failure)
if p.opts.reportValidationErrors {
u.validationErrors = append(u.validationErrors, e)
}
if failure || p.opts.failOnValidationError {
return e
}
return nil
}

// handleWrappedErrorWithDescription handles an error according to the options set for the parser
func (p *parser) handleWrappedErrorWithDescription(u *Url, errorType errors.ErrorType, failure bool, cause error, descr string) error {
e := errors.WrapWithDescr(cause, errorType, descr, u.inputUrl, failure)
if p.opts.reportValidationErrors {
u.validationErrors = append(u.validationErrors, e)
}
if failure || p.opts.failOnValidationError {
return e
}
return nil
}
Loading

0 comments on commit 83994ff

Please sign in to comment.