Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated parser to return similar named errors as in spec #16

Merged
merged 1 commit into from
Jun 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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