diff --git a/x/sqlite/.vscode/extensions.json b/x/sqlite/.vscode/extensions.json new file mode 100644 index 000000000..220ede24a --- /dev/null +++ b/x/sqlite/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "emeraldwalk.runonsave", + "tamasfe.even-better-toml", + "github.vscode-github-actions", + "codezombiech.gitignore", + "golang.go" + ] +} \ No newline at end of file diff --git a/x/sqlite/.vscode/settings.json b/x/sqlite/.vscode/settings.json new file mode 100644 index 000000000..1558e2a1d --- /dev/null +++ b/x/sqlite/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "go.lintTool": "golangci-lint", + "emeraldwalk.runonsave": { + "commands": [ + { + "match": "\\.go$", + "cmd": "golangci-lint run -c ${workspaceFolder}/.golangci-safe.toml ${fileDirname} --fix" + } + ] + } +} \ No newline at end of file diff --git a/x/sqlite/changes_test.go b/x/sqlite/changes_test.go index 96055d6ee..79b7997bd 100644 --- a/x/sqlite/changes_test.go +++ b/x/sqlite/changes_test.go @@ -29,7 +29,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBChanges(t *testing.T) { diff --git a/x/sqlite/common_test.go b/x/sqlite/common_test.go index 773084e95..93e6f8add 100644 --- a/x/sqlite/common_test.go +++ b/x/sqlite/common_test.go @@ -24,7 +24,7 @@ import ( "testing" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) type DB interface { diff --git a/x/sqlite/delete.go b/x/sqlite/delete.go index afe8c64ba..f668b5162 100644 --- a/x/sqlite/delete.go +++ b/x/sqlite/delete.go @@ -17,7 +17,7 @@ import ( "net/http" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func (d *db) Delete(ctx context.Context, docID string, options driver.Options) (string, error) { diff --git a/x/sqlite/delete_test.go b/x/sqlite/delete_test.go index 88becea7a..f921b67ee 100644 --- a/x/sqlite/delete_test.go +++ b/x/sqlite/delete_test.go @@ -25,7 +25,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBDelete(t *testing.T) { diff --git a/x/sqlite/deleteattachment.go b/x/sqlite/deleteattachment.go index 421de00c8..e070ff029 100644 --- a/x/sqlite/deleteattachment.go +++ b/x/sqlite/deleteattachment.go @@ -17,7 +17,7 @@ import ( "net/http" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func (d *db) DeleteAttachment(ctx context.Context, docID, filename string, options driver.Options) (string, error) { diff --git a/x/sqlite/deleteattachment_test.go b/x/sqlite/deleteattachment_test.go index 0b4e6a536..433209675 100644 --- a/x/sqlite/deleteattachment_test.go +++ b/x/sqlite/deleteattachment_test.go @@ -26,7 +26,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBDeleteAttachment(t *testing.T) { diff --git a/x/sqlite/designdocs_test.go b/x/sqlite/designdocs_test.go index fc0f34002..19152dbb0 100644 --- a/x/sqlite/designdocs_test.go +++ b/x/sqlite/designdocs_test.go @@ -23,7 +23,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBDesignDocs(t *testing.T) { diff --git a/x/sqlite/get_test.go b/x/sqlite/get_test.go index de4f5921d..9c1b12b96 100644 --- a/x/sqlite/get_test.go +++ b/x/sqlite/get_test.go @@ -28,7 +28,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBGet(t *testing.T) { diff --git a/x/sqlite/getattachment.go b/x/sqlite/getattachment.go index e9e1aebc9..06f35af09 100644 --- a/x/sqlite/getattachment.go +++ b/x/sqlite/getattachment.go @@ -21,7 +21,7 @@ import ( "net/http" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func (d *db) GetAttachment(ctx context.Context, docID string, filename string, options driver.Options) (*driver.Attachment, error) { diff --git a/x/sqlite/getattachment_test.go b/x/sqlite/getattachment_test.go index 1b97917e7..25c598631 100644 --- a/x/sqlite/getattachment_test.go +++ b/x/sqlite/getattachment_test.go @@ -26,7 +26,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBGetAttachment(t *testing.T) { diff --git a/x/sqlite/getattachmentmeta.go b/x/sqlite/getattachmentmeta.go index c539deb56..4aa56f4d4 100644 --- a/x/sqlite/getattachmentmeta.go +++ b/x/sqlite/getattachmentmeta.go @@ -19,7 +19,7 @@ import ( "net/http" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func (d *db) GetAttachmentMeta(ctx context.Context, docID, filename string, options driver.Options) (*driver.Attachment, error) { diff --git a/x/sqlite/getattachmentmeta_test.go b/x/sqlite/getattachmentmeta_test.go index 924a321cb..e0089ac94 100644 --- a/x/sqlite/getattachmentmeta_test.go +++ b/x/sqlite/getattachmentmeta_test.go @@ -25,7 +25,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBGetAttachmentMeta(t *testing.T) { diff --git a/x/sqlite/getrev.go b/x/sqlite/getrev.go index d0e3d8012..e41ac3d76 100644 --- a/x/sqlite/getrev.go +++ b/x/sqlite/getrev.go @@ -19,7 +19,7 @@ import ( "net/http" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func (d *db) GetRev(ctx context.Context, id string, options driver.Options) (string, error) { diff --git a/x/sqlite/getrev_test.go b/x/sqlite/getrev_test.go index 0995cdcf7..3c97863c6 100644 --- a/x/sqlite/getrev_test.go +++ b/x/sqlite/getrev_test.go @@ -24,7 +24,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestGetRev(t *testing.T) { diff --git a/x/sqlite/go.mod b/x/sqlite/go.mod index 9173a8888..45066c0f3 100644 --- a/x/sqlite/go.mod +++ b/x/sqlite/go.mod @@ -1,4 +1,4 @@ -module github.com/go-kivik/kivik/v4/x/sqlite +module github.com/go-kivik/kivik/x/sqlite/v4 go 1.22.0 diff --git a/x/sqlite/internal/errors.go b/x/sqlite/internal/errors.go new file mode 100644 index 000000000..e6b8ef9c4 --- /dev/null +++ b/x/sqlite/internal/errors.go @@ -0,0 +1,173 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Package internal provides some internal utilities. +package internal + +import ( + "errors" + "fmt" + "net/http" + "regexp" + "strconv" + "strings" +) + +// CompositeError represents an HTTP status, encoded in the first byte as the +// status - 400, plus the error message. +type CompositeError string + +func (c CompositeError) Error() string { + return "kivik: " + string(c[4:]) +} + +// HTTPStatus returns c's HTTP status code. +func (c CompositeError) HTTPStatus() int { + i, _ := strconv.Atoi(string(c[:3])) + return i +} + +// Error represents an error returned by Kivik. +// +// This type definition is not guaranteed to remain stable, or even exported. +// When examining errors programatically, you should rely instead on the +// HTTPStatus() function in this package, rather than on directly observing +// the fields of this type. +type Error struct { + // Status is the HTTP status code associated with this error. Normally + // this is the actual HTTP status returned by the server, but in some cases + // it may be generated by Kivik directly. + Status int + + // Message is the error message. + Message string + + // Err is the originating error, if any. + Err error +} + +var ( + _ error = &Error{} + _ statusCoder = &Error{} +) + +func (e *Error) Error() string { + if e.Err == nil { + return e.msg() + } + if e.Message == "" { + return e.Err.Error() + } + return e.Message + ": " + e.Err.Error() +} + +// HTTPStatus returns the HTTP status code associated with the error, or 500 +// (internal server error), if none. +func (e *Error) HTTPStatus() int { + if e.Status == 0 { + return http.StatusInternalServerError + } + return e.Status +} + +// Unwrap satisfies the errors wrapper interface. +func (e *Error) Unwrap() error { + return e.Err +} + +// Format implements [fmt.Formatter]. +func (e *Error) Format(f fmt.State, c rune) { + const partsLen = 3 + parts := make([]string, 0, partsLen) + if e.Message != "" { + parts = append(parts, e.Message) + } + if c == 'v' { + if f.Flag('+') { + parts = append(parts, fmt.Sprintf("%d / %s", e.Status, http.StatusText(e.Status))) + } + } + if e.Err != nil { + parts = append(parts, e.Err.Error()) + } + _, _ = fmt.Fprint(f, strings.Join(parts, ": ")) +} + +func (e *Error) msg() string { + switch e.Message { + case "": + return http.StatusText(e.HTTPStatus()) + default: + return e.Message + } +} + +type statusCoder interface { + HTTPStatus() int +} + +// HTTPStatus returns the HTTP status code embedded in the error, or 500 +// (internal server error), if there was no specified status code. If err is +// nil, HTTPStatus returns 0. +func HTTPStatus(err error) int { + if err == nil { + return 0 + } + var coder statusCoder + for { + if errors.As(err, &coder) { + return coder.HTTPStatus() + } + if uw := errors.Unwrap(err); uw != nil { + err = uw + continue + } + return http.StatusInternalServerError + } +} + +// StatusErrorDiff returns the empty string if the expected error string and +// status match err. Otherwise, it returns a description of the mismatch. +func StatusErrorDiff(wantErr string, wantStatus int, err error) string { + var ( + msg string + status int + ) + if err != nil { + status = HTTPStatus(err) + msg = err.Error() + } + if msg != wantErr || status != wantStatus { + return fmt.Sprintf("Unexpected error: %s [%d] (expected: %s [%d])", + err, status, wantErr, wantStatus) + } + return "" +} + +// StatusErrorDiffRE returns the empty string if the expected error RE and +// status match err. Otherwise, it returns a description of the mismatch. +func StatusErrorDiffRE(wantErrRE string, wantStatus int, err error) string { + re := regexp.MustCompile(wantErrRE) + var ( + msg string + status int + ) + if err != nil { + status = HTTPStatus(err) + msg = err.Error() + } + if !re.MatchString(msg) || status != wantStatus { + return fmt.Sprintf("Unexpected error: %s [%d] (expected: %s [%d])", + err, status, re, wantStatus) + } + return "" +} diff --git a/x/sqlite/internal/mock/options.go b/x/sqlite/internal/mock/options.go new file mode 100644 index 000000000..eff55d9a1 --- /dev/null +++ b/x/sqlite/internal/mock/options.go @@ -0,0 +1,26 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +// Package mock is a partial duplicate of [github.com/go-kivik/v4/internal/mock]. +package mock + +import "github.com/go-kivik/kivik/v4/driver" + +type nilOption bool + +var _ driver.Options = nilOption(false) + +func (nilOption) Apply(interface{}) {} +func (nilOption) String() string { return "NilOption" } + +// NilOption is a nil option. +const NilOption nilOption = false diff --git a/x/sqlite/json.go b/x/sqlite/json.go index 4d2710650..09675098a 100644 --- a/x/sqlite/json.go +++ b/x/sqlite/json.go @@ -26,7 +26,7 @@ import ( "strconv" "strings" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) type revision struct { diff --git a/x/sqlite/localdocs_test.go b/x/sqlite/localdocs_test.go index 59a0c52a6..1158e0b19 100644 --- a/x/sqlite/localdocs_test.go +++ b/x/sqlite/localdocs_test.go @@ -23,7 +23,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBLocalDocs(t *testing.T) { diff --git a/x/sqlite/openrevs.go b/x/sqlite/openrevs.go index 0b5516507..c44ae1213 100644 --- a/x/sqlite/openrevs.go +++ b/x/sqlite/openrevs.go @@ -22,7 +22,7 @@ import ( "strings" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func (d *db) OpenRevs(ctx context.Context, docID string, revs []string, options driver.Options) (driver.Rows, error) { diff --git a/x/sqlite/openrevs_test.go b/x/sqlite/openrevs_test.go index f9722949d..d42eb0aa7 100644 --- a/x/sqlite/openrevs_test.go +++ b/x/sqlite/openrevs_test.go @@ -24,7 +24,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBOpenRevs(t *testing.T) { diff --git a/x/sqlite/options.go b/x/sqlite/options.go index 11219b308..295012d28 100644 --- a/x/sqlite/options.go +++ b/x/sqlite/options.go @@ -17,7 +17,7 @@ import ( "strconv" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) type optsMap map[string]interface{} diff --git a/x/sqlite/put.go b/x/sqlite/put.go index b3a3dfe68..1c69c61cf 100644 --- a/x/sqlite/put.go +++ b/x/sqlite/put.go @@ -20,7 +20,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func (d *db) Put(ctx context.Context, docID string, doc interface{}, options driver.Options) (string, error) { diff --git a/x/sqlite/put_designdocs_test.go b/x/sqlite/put_designdocs_test.go index 0777b1e07..2964880c5 100644 --- a/x/sqlite/put_designdocs_test.go +++ b/x/sqlite/put_designdocs_test.go @@ -27,7 +27,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) type ddoc struct { diff --git a/x/sqlite/put_test.go b/x/sqlite/put_test.go index 98d6980a1..db81ba570 100644 --- a/x/sqlite/put_test.go +++ b/x/sqlite/put_test.go @@ -27,7 +27,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) type attachmentRow struct { diff --git a/x/sqlite/putattachment.go b/x/sqlite/putattachment.go index b3158a644..30d1a6070 100644 --- a/x/sqlite/putattachment.go +++ b/x/sqlite/putattachment.go @@ -19,7 +19,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func (d *db) PutAttachment(ctx context.Context, docID string, att *driver.Attachment, options driver.Options) (string, error) { diff --git a/x/sqlite/putattachment_test.go b/x/sqlite/putattachment_test.go index 4b1d899a0..8e9e160fd 100644 --- a/x/sqlite/putattachment_test.go +++ b/x/sqlite/putattachment_test.go @@ -28,7 +28,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBPutAttachment(t *testing.T) { diff --git a/x/sqlite/query.go b/x/sqlite/query.go index c02af71cf..af0a888d1 100644 --- a/x/sqlite/query.go +++ b/x/sqlite/query.go @@ -24,7 +24,7 @@ import ( "github.com/dop251/goja" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func fromJSValue(v interface{}) (*string, error) { diff --git a/x/sqlite/query_test.go b/x/sqlite/query_test.go index 34d54e8b7..637568356 100644 --- a/x/sqlite/query_test.go +++ b/x/sqlite/query_test.go @@ -25,7 +25,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestDBQuery(t *testing.T) { diff --git a/x/sqlite/revsdiff.go b/x/sqlite/revsdiff.go index d227272ba..484240de9 100644 --- a/x/sqlite/revsdiff.go +++ b/x/sqlite/revsdiff.go @@ -23,7 +23,7 @@ import ( "sort" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func toRevDiffRequest(revMap interface{}) (map[string][]string, error) { diff --git a/x/sqlite/sqlite.go b/x/sqlite/sqlite.go index 7ac62fcb9..be5d75a6a 100644 --- a/x/sqlite/sqlite.go +++ b/x/sqlite/sqlite.go @@ -25,7 +25,7 @@ import ( _ "modernc.org/sqlite" // SQLite driver "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) type drv struct{} diff --git a/x/sqlite/sqlite_test.go b/x/sqlite/sqlite_test.go index 22ff8a439..60e146c18 100644 --- a/x/sqlite/sqlite_test.go +++ b/x/sqlite/sqlite_test.go @@ -24,7 +24,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) func TestNewClient(t *testing.T) { diff --git a/x/sqlite/util.go b/x/sqlite/util.go index a0f26c730..9b874c36d 100644 --- a/x/sqlite/util.go +++ b/x/sqlite/util.go @@ -23,7 +23,7 @@ import ( "sort" "strings" - "github.com/go-kivik/kivik/v4/internal" + "github.com/go-kivik/kivik/x/sqlite/v4/internal" ) func placeholders(start, count int) string { diff --git a/x/sqlite/views_test.go b/x/sqlite/views_test.go index 75c8da7fd..df61ede73 100644 --- a/x/sqlite/views_test.go +++ b/x/sqlite/views_test.go @@ -25,7 +25,7 @@ import ( "github.com/go-kivik/kivik/v4" "github.com/go-kivik/kivik/v4/driver" - "github.com/go-kivik/kivik/v4/internal/mock" + "github.com/go-kivik/kivik/x/sqlite/v4/internal/mock" ) type rowResult struct {