diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 11f6e35..7e27c08 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -14,6 +14,12 @@ All notable changes to this project will be documented in this file. The format is based on https://keepachangelog.com/[Keep a Changelog], and this project adheres to https://semver.org/[Semantic Versioning]. +== {compare-url}/v0.2.0\...HEAD[Unreleased] + +=== Changed + +* Change errors to include details ({pull-request-url}/8[#8]) + == {compare-url}/v0.1.0\...v0.2.0[0.2.0] - 2024-04-05 === Added diff --git a/error.go b/error.go index 7c2f95c..43e1f10 100644 --- a/error.go +++ b/error.go @@ -6,43 +6,86 @@ package lzip import "errors" -var ( - // ErrInvalidMagic represents an error due to the magic number was - // invalid. - ErrInvalidMagic = errors.New("lzip: invalid magic number") - - // ErrUnsupportedVersion represents an error due to the version number - // stored in the header indicated the lzip format which is not - // supported by this package. - ErrUnsupportedVersion = errors.New("lzip: unsupported version number") - - // ErrUnknownVersion represents an error due to the version number - // stored in the header was not recognized by this package. - ErrUnknownVersion = errors.New("lzip: unknown version number") - - // ErrDictSizeTooSmall represents an error due to the dictionary size - // was smaller than 4 KiB. - ErrDictSizeTooSmall = errors.New("lzip: dictionary size is too small") - - // ErrDictSizeTooLarge represents an error due to the dictionary size - // was larger than 512 MiB. - ErrDictSizeTooLarge = errors.New("lzip: dictionary size is too large") - - // ErrInvalidCRC represents an error due to a CRC of the original - // uncompressed data mismatched. - ErrInvalidCRC = errors.New("lzip: CRC mismatch") - - // ErrInvalidDataSize represents an error due to the size of the - // original uncompressed data stored in the trailer and the actual size - // of it mismatched. - ErrInvalidDataSize = errors.New("lzip: data size mismatch") - - // ErrInvalidMemberSize represents an error due to the total size of - // the member stored in the trailer and the actual total size of it - // mismatched. - ErrInvalidMemberSize = errors.New("lzip: member size mismatch") - - // ErrDictSizeTooLarge represents an error due to the member size was - // larger than 2 PiB. - ErrMemberSizeTooLarge = errors.New("lzip: member size is too large") -) +// ErrInvalidMagic represents an error due to the magic number was invalid. +var ErrInvalidMagic = errors.New("lzip: invalid magic number") + +// UnsupportedVersionError represents an error due to the version number stored +// in the header indicated the lzip format which is not supported by this +// package. +type UnsupportedVersionError struct { + Version uint8 +} + +func (e *UnsupportedVersionError) Error() string { + return "lzip: unsupported version number" +} + +// UnknownVersionError represents an error due to the version number stored in +// the header was not recognized by this package. +type UnknownVersionError struct { + Version uint8 +} + +func (e *UnknownVersionError) Error() string { + return "lzip: unknown version number" +} + +// DictSizeTooSmallError represents an error due to the dictionary size was +// smaller than 4 KiB. +type DictSizeTooSmallError struct { + DictSize uint32 +} + +func (e *DictSizeTooSmallError) Error() string { + return "lzip: dictionary size is too small" +} + +// DictSizeTooLargeError represents an error due to the dictionary size was +// larger than 512 MiB. +type DictSizeTooLargeError struct { + DictSize uint32 +} + +func (e *DictSizeTooLargeError) Error() string { + return "lzip: dictionary size is too large" +} + +// InvalidCRCError represents an error due to a CRC of the original +// uncompressed data mismatched. +type InvalidCRCError struct { + CRC uint32 +} + +func (e *InvalidCRCError) Error() string { + return "lzip: CRC mismatch" +} + +// InvalidDataSizeError represents an error due to the size of the original +// uncompressed data stored in the trailer and the actual size of it mismatched. +type InvalidDataSizeError struct { + DataSize uint64 +} + +func (e *InvalidDataSizeError) Error() string { + return "lzip: data size mismatch" +} + +// InvalidMemberSizeError represents an error due to the total size of the +// member stored in the trailer and the actual total size of it mismatched. +type InvalidMemberSizeError struct { + MemberSize uint64 +} + +func (e *InvalidMemberSizeError) Error() string { + return "lzip: member size mismatch" +} + +// MemberSizeTooLargeError represents an error due to the member size was +// larger than 2 PiB. +type MemberSizeTooLargeError struct { + MemberSize uint64 +} + +func (e *MemberSizeTooLargeError) Error() string { + return "lzip: member size is too large" +} diff --git a/error_test.go b/error_test.go index 0fbd623..ee4669c 100644 --- a/error_test.go +++ b/error_test.go @@ -5,6 +5,7 @@ package lzip_test import ( + "math" "testing" "github.com/sorairolake/lzip-go" @@ -21,90 +22,122 @@ func TestErrInvalidMagic(t *testing.T) { } } -func TestErrUnsupportedVersion(t *testing.T) { +func TestUnsupportedVersionError(t *testing.T) { t.Parallel() - err := lzip.ErrUnsupportedVersion + err := lzip.UnsupportedVersionError{0} expected := "lzip: unsupported version number" if err.Error() != expected { t.Error("unexpected error message") } + + if v := err.Version; v != 0 { + t.Errorf("expected unsupported version number `%v`, got `%v`", 0, v) + } } -func TestErrUnknownVersion(t *testing.T) { +func TestUnknownVersionError(t *testing.T) { t.Parallel() - err := lzip.ErrUnknownVersion + err := lzip.UnknownVersionError{math.MaxUint8} expected := "lzip: unknown version number" if err.Error() != expected { t.Error("unexpected error message") } + + if v := err.Version; v != math.MaxUint8 { + t.Errorf("expected unknown version number `%v`, got `%v`", math.MaxUint8, v) + } } -func TestErrDictSizeTooSmall(t *testing.T) { +func TestDictSizeTooSmallError(t *testing.T) { t.Parallel() - err := lzip.ErrDictSizeTooSmall + err := lzip.DictSizeTooSmallError{lzip.MinDictSize - 1} expected := "lzip: dictionary size is too small" if err.Error() != expected { t.Error("unexpected error message") } + + if size := err.DictSize; size != (lzip.MinDictSize - 1) { + t.Errorf("expected too small dictionary size `%v`, got `%v`", lzip.MinDictSize-1, size) + } } -func TestErrDictSizeTooLarge(t *testing.T) { +func TestDictSizeTooLargeError(t *testing.T) { t.Parallel() - err := lzip.ErrDictSizeTooLarge + err := lzip.DictSizeTooLargeError{lzip.MaxDictSize + 1} expected := "lzip: dictionary size is too large" if err.Error() != expected { t.Error("unexpected error message") } + + if size := err.DictSize; size != (lzip.MaxDictSize + 1) { + t.Errorf("expected too large dictionary size `%v`, got `%v`", lzip.MaxDictSize+1, size) + } } -func TestErrInvalidCRC(t *testing.T) { +func TestInvalidCRCError(t *testing.T) { t.Parallel() - err := lzip.ErrInvalidCRC + err := lzip.InvalidCRCError{0} expected := "lzip: CRC mismatch" if err.Error() != expected { t.Error("unexpected error message") } + + if crc := err.CRC; crc != 0 { + t.Errorf("expected invalid CRC `%v`, got `%v`", 0, crc) + } } -func TestErrInvalidDataSize(t *testing.T) { +func TestInvalidDataSizeError(t *testing.T) { t.Parallel() - err := lzip.ErrInvalidDataSize + err := lzip.InvalidDataSizeError{0} expected := "lzip: data size mismatch" if err.Error() != expected { t.Error("unexpected error message") } + + if size := err.DataSize; size != 0 { + t.Errorf("expected invalid data size `%v`, got `%v`", 0, size) + } } -func TestErrInvalidMemberSize(t *testing.T) { +func TestInvalidMemberSizeError(t *testing.T) { t.Parallel() - err := lzip.ErrInvalidMemberSize + err := lzip.InvalidMemberSizeError{0} expected := "lzip: member size mismatch" if err.Error() != expected { t.Error("unexpected error message") } + + if size := err.MemberSize; size != 0 { + t.Errorf("expected invalid member size `%v`, got `%v`", 0, size) + } } -func TestErrMemberSizeTooLarge(t *testing.T) { +func TestMemberSizeTooLargeError(t *testing.T) { t.Parallel() - err := lzip.ErrMemberSizeTooLarge + err := lzip.MemberSizeTooLargeError{lzip.MaxMemberSize + 1} expected := "lzip: member size is too large" if err.Error() != expected { t.Error("unexpected error message") } + + if size := err.MemberSize; size != (lzip.MaxMemberSize + 1) { + t.Errorf("expected too large member size `%v`, got `%v`", lzip.MaxMemberSize+1, size) + } } diff --git a/reader.go b/reader.go index 2669d01..af14464 100644 --- a/reader.go +++ b/reader.go @@ -38,10 +38,10 @@ func NewReader(r io.Reader) (*Reader, error) { switch v := header[4]; v { case 0: - return nil, ErrUnsupportedVersion + return nil, &UnsupportedVersionError{v} case 1: default: - return nil, ErrUnknownVersion + return nil, &UnknownVersionError{v} } dictSize := uint32(1 << (header[5] & 0x1f)) @@ -49,9 +49,9 @@ func NewReader(r io.Reader) (*Reader, error) { switch { case dictSize < MinDictSize: - return nil, ErrDictSizeTooSmall + return nil, &DictSizeTooSmallError{dictSize} case dictSize > MaxDictSize: - return nil, ErrDictSizeTooLarge + return nil, &DictSizeTooLargeError{dictSize} } rb, err := io.ReadAll(r) @@ -66,8 +66,8 @@ func NewReader(r io.Reader) (*Reader, error) { copy(lzmaHeader[5:], rb[len(rb)-16:len(rb)-8]) z.trailer.memberSize = uint64(headerSize + len(rb)) - if z.trailer.memberSize > MaxMemberSize { - return nil, ErrMemberSizeTooLarge + if memberSize := z.trailer.memberSize; memberSize > MaxMemberSize { + return nil, &MemberSizeTooLargeError{memberSize} } rb = slices.Concat(lzmaHeader[:], rb) @@ -106,17 +106,17 @@ func (z *Reader) Read(p []byte) (n int, err error) { crc := binary.LittleEndian.Uint32(trailer[:4]) if crc != z.trailer.crc { - return n, ErrInvalidCRC + return n, &InvalidCRCError{crc} } dataSize := binary.LittleEndian.Uint64(trailer[4:12]) if dataSize != z.trailer.dataSize { - return n, ErrInvalidDataSize + return n, &InvalidDataSizeError{dataSize} } memberSize := binary.LittleEndian.Uint64(trailer[12:]) if memberSize != z.trailer.memberSize { - return n, ErrInvalidMemberSize + return n, &InvalidMemberSizeError{memberSize} } } diff --git a/reader_test.go b/reader_test.go index 34601af..aaa43b2 100644 --- a/reader_test.go +++ b/reader_test.go @@ -63,8 +63,20 @@ func TestReaderUnknownVersion(t *testing.T) { t.Fatal(err) } - if _, err := lzip.NewReader(file); !errors.Is(err, lzip.ErrUnknownVersion) { - t.Error("unexpected error type") + _, err = lzip.NewReader(file) + if err == nil { + t.Fatal("unexpected success") + } + + var unknownVersionError *lzip.UnknownVersionError + if !errors.As(err, &unknownVersionError) { + t.Fatal("unexpected error type") + } + + const expected = 2 + + if v := unknownVersionError.Version; v != expected { + t.Errorf("expected unrecognized version number `%v`, got `%v`", expected, v) } } @@ -76,7 +88,19 @@ func TestReaderDictSizeTooSmall(t *testing.T) { t.Fatal(err) } - if _, err := lzip.NewReader(file); !errors.Is(err, lzip.ErrDictSizeTooSmall) { - t.Error("unexpected error type") + _, err = lzip.NewReader(file) + if err == nil { + t.Fatal("unexpected success") + } + + var dictSizeTooSmallError *lzip.DictSizeTooSmallError + if !errors.As(err, &dictSizeTooSmallError) { + t.Fatal("unexpected error type") + } + + const expected = 1 << 11 + + if size := dictSizeTooSmallError.DictSize; size != expected { + t.Errorf("expected too small dictionary size `%v`, got `%v`", expected, size) } } diff --git a/writer.go b/writer.go index 089b1c1..4f1777f 100644 --- a/writer.go +++ b/writer.go @@ -39,11 +39,11 @@ func newWriterOptions() *WriterOptions { // Verify checks if [WriterOptions] is valid. func (o *WriterOptions) Verify() error { - switch { - case o.DictSize < MinDictSize: - return ErrDictSizeTooSmall - case o.DictSize > MaxDictSize: - return ErrDictSizeTooLarge + switch dictSize := o.DictSize; { + case dictSize < MinDictSize: + return &DictSizeTooSmallError{dictSize} + case dictSize > MaxDictSize: + return &DictSizeTooLargeError{dictSize} } return nil @@ -137,8 +137,8 @@ func (z *Writer) Close() error { binary.LittleEndian.PutUint64(trailer[4:12], z.trailer.dataSize) binary.LittleEndian.PutUint64(trailer[12:], headerSize+uint64(len(cb))+trailerSize) - if binary.LittleEndian.Uint64(trailer[12:]) > MaxMemberSize { - return ErrMemberSizeTooLarge + if memberSize := binary.LittleEndian.Uint64(trailer[12:]); memberSize > MaxMemberSize { + return &MemberSizeTooLargeError{memberSize} } _, err := z.w.Write(trailer[:]) diff --git a/writer_test.go b/writer_test.go index 5dca10c..027caa9 100644 --- a/writer_test.go +++ b/writer_test.go @@ -65,7 +65,7 @@ func TestWriterOptions(t *testing.T) { text := string(data) - opt := &lzip.WriterOptions{4 * 1024 * 1024} + opt := &lzip.WriterOptions{1 << 22} var buf bytes.Buffer @@ -105,43 +105,91 @@ func TestWriterOptions(t *testing.T) { func TestWriterOptionsDictSizeTooSmall(t *testing.T) { t.Parallel() - opt := &lzip.WriterOptions{(4 * 1024) - 1} + const expected = (1 << 12) - 1 - if _, err := lzip.NewWriterOptions(io.Discard, opt); !errors.Is(err, lzip.ErrDictSizeTooSmall) { - t.Error("unexpected error type") + opt := &lzip.WriterOptions{expected} + + _, err := lzip.NewWriterOptions(io.Discard, opt) + if err == nil { + t.Fatal("unexpected success") + } + + var dictSizeTooSmallError *lzip.DictSizeTooSmallError + if !errors.As(err, &dictSizeTooSmallError) { + t.Fatal("unexpected error type") + } + + if size := dictSizeTooSmallError.DictSize; size != expected { + t.Errorf("expected too small dictionary size `%v`, got `%v`", expected, size) } } func TestWriterOptionsDictSizeTooLarge(t *testing.T) { t.Parallel() - opt := &lzip.WriterOptions{(512 * 1024 * 1024) + 1} + const expected = (1 << 29) + 1 + + opt := &lzip.WriterOptions{expected} + + _, err := lzip.NewWriterOptions(io.Discard, opt) + if err == nil { + t.Fatal("unexpected success") + } + + var dictSizeTooLargeError *lzip.DictSizeTooLargeError + if !errors.As(err, &dictSizeTooLargeError) { + t.Fatal("unexpected error type") + } - if _, err := lzip.NewWriterOptions(io.Discard, opt); !errors.Is(err, lzip.ErrDictSizeTooLarge) { - t.Error("unexpected error type") + if size := dictSizeTooLargeError.DictSize; size != expected { + t.Errorf("expected too large dictionary size `%v`, got `%v`", expected, size) } } func TestVerifyWriterOptions(t *testing.T) { t.Parallel() - opt := &lzip.WriterOptions{4 * 1024} + opt := &lzip.WriterOptions{1 << 12} if err := opt.Verify(); err != nil { t.Fatal(err) } - opt = &lzip.WriterOptions{512 * 1024 * 1024} + opt = &lzip.WriterOptions{1 << 29} if err := opt.Verify(); err != nil { t.Fatal(err) } - opt = &lzip.WriterOptions{(4 * 1024) - 1} - if err := opt.Verify(); !errors.Is(err, lzip.ErrDictSizeTooSmall) { - t.Error("unexpected error type") + var expected uint32 = (1 << 12) - 1 + opt = &lzip.WriterOptions{expected} + + err := opt.Verify() + if err == nil { + t.Fatal("unexpected success") + } + + var dictSizeTooSmallError *lzip.DictSizeTooSmallError + if !errors.As(err, &dictSizeTooSmallError) { + t.Fatal("unexpected error type") + } + + if size := dictSizeTooSmallError.DictSize; size != expected { + t.Errorf("expected too small dictionary size `%v`, got `%v`", expected, size) + } + + expected = (1 << 29) + 1 + opt = &lzip.WriterOptions{expected} + + err = opt.Verify() + if err == nil { + t.Fatal("unexpected success") + } + + var dictSizeTooLargeError *lzip.DictSizeTooLargeError + if !errors.As(err, &dictSizeTooLargeError) { + t.Fatal("unexpected error type") } - opt = &lzip.WriterOptions{(512 * 1024 * 1024) + 1} - if err := opt.Verify(); !errors.Is(err, lzip.ErrDictSizeTooLarge) { - t.Error("unexpected error type") + if size := dictSizeTooLargeError.DictSize; size != expected { + t.Errorf("expected too large dictionary size `%v`, got `%v`", expected, size) } }