whatcanGOwrong
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
# httpfs
|
||||
|
||||
## Usage
|
||||
|
||||
This package could be used to create new migration source drivers that uses
|
||||
`http.FileSystem` to read migration files.
|
||||
|
||||
Struct `httpfs.PartialDriver` partly implements the `source.Driver` interface. It has all
|
||||
the methods except for `Open()`. Embedding this struct and adding `Open()` method
|
||||
allows users of this package to create new migration sources. Example:
|
||||
|
||||
```go
|
||||
struct mydriver {
|
||||
httpfs.PartialDriver
|
||||
}
|
||||
|
||||
func (d *mydriver) Open(url string) (source.Driver, error) {
|
||||
var fs http.FileSystem
|
||||
var path string
|
||||
var ds mydriver
|
||||
|
||||
// acquire fs and path from url
|
||||
// set-up ds if necessary
|
||||
|
||||
if err := ds.Init(fs, path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ds, nil
|
||||
}
|
||||
```
|
||||
|
||||
This package also provides a simple `source.Driver` implementation that works
|
||||
with `http.FileSystem` provided by the user of this package. It is created with
|
||||
`httpfs.New()` call.
|
||||
|
||||
Example of using `http.Dir()` to read migrations from `sql` directory:
|
||||
|
||||
```go
|
||||
src, err := httpfs.New(http.Dir("sql"))
|
||||
if err != nil {
|
||||
// do something
|
||||
}
|
||||
m, err := migrate.NewWithSourceInstance("httpfs", src, "database://url")
|
||||
if err != nil {
|
||||
// do something
|
||||
}
|
||||
err = m.Up()
|
||||
...
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
package httpfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4/source"
|
||||
)
|
||||
|
||||
// driver is a migration source driver for reading migrations from
|
||||
// http.FileSystem instances. It implements source.Driver interface and can be
|
||||
// used as a migration source for the main migrate library.
|
||||
type driver struct {
|
||||
PartialDriver
|
||||
}
|
||||
|
||||
// New creates a new migrate source driver from a http.FileSystem instance and a
|
||||
// relative path to migration files within the virtual FS.
|
||||
func New(fs http.FileSystem, path string) (source.Driver, error) {
|
||||
var d driver
|
||||
if err := d.Init(fs, path); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &d, nil
|
||||
}
|
||||
|
||||
// Open completes the implementetion of source.Driver interface. Other methods
|
||||
// are implemented by the embedded PartialDriver struct.
|
||||
func (d *driver) Open(url string) (source.Driver, error) {
|
||||
return nil, errors.New("Open() cannot be called on the httpfs passthrough driver")
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package httpfs_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4/source/httpfs"
|
||||
st "github.com/golang-migrate/migrate/v4/source/testing"
|
||||
)
|
||||
|
||||
func TestNewOK(t *testing.T) {
|
||||
d, err := httpfs.New(http.Dir("testdata"), "sql")
|
||||
if err != nil {
|
||||
t.Errorf("New() expected not error, got: %s", err)
|
||||
}
|
||||
st.Test(t, d)
|
||||
}
|
||||
|
||||
func TestNewErrors(t *testing.T) {
|
||||
d, err := httpfs.New(http.Dir("does-not-exist"), "")
|
||||
if err == nil {
|
||||
t.Errorf("New() expected to return error")
|
||||
}
|
||||
if d != nil {
|
||||
t.Errorf("New() expected to return nil driver")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpen(t *testing.T) {
|
||||
d, err := httpfs.New(http.Dir("testdata/sql"), "")
|
||||
if err != nil {
|
||||
t.Error("New() expected no error")
|
||||
return
|
||||
}
|
||||
d, err = d.Open("")
|
||||
if d != nil {
|
||||
t.Error("Open() expected to return nil driver")
|
||||
}
|
||||
if err == nil {
|
||||
t.Error("Open() expected to return error")
|
||||
}
|
||||
}
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
package httpfs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4/source"
|
||||
)
|
||||
|
||||
// PartialDriver is a helper service for creating new source drivers working with
|
||||
// http.FileSystem instances. It implements all source.Driver interface methods
|
||||
// except for Open(). New driver could embed this struct and add missing Open()
|
||||
// method.
|
||||
//
|
||||
// To prepare PartialDriver for use Init() function.
|
||||
type PartialDriver struct {
|
||||
migrations *source.Migrations
|
||||
fs http.FileSystem
|
||||
path string
|
||||
}
|
||||
|
||||
// Init prepares not initialized PartialDriver instance to read migrations from a
|
||||
// http.FileSystem instance and a relative path.
|
||||
func (p *PartialDriver) Init(fs http.FileSystem, path string) error {
|
||||
root, err := fs.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
files, err := root.Readdir(0)
|
||||
if err != nil {
|
||||
_ = root.Close()
|
||||
return err
|
||||
}
|
||||
if err = root.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ms := source.NewMigrations()
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
m, err := source.DefaultParse(file.Name())
|
||||
if err != nil {
|
||||
continue // ignore files that we can't parse
|
||||
}
|
||||
|
||||
if !ms.Append(m) {
|
||||
return source.ErrDuplicateMigration{
|
||||
Migration: *m,
|
||||
FileInfo: file,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.fs = fs
|
||||
p.path = path
|
||||
p.migrations = ms
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close is part of source.Driver interface implementation. This is a no-op.
|
||||
func (p *PartialDriver) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// First is part of source.Driver interface implementation.
|
||||
func (p *PartialDriver) First() (version uint, err error) {
|
||||
if version, ok := p.migrations.First(); ok {
|
||||
return version, nil
|
||||
}
|
||||
return 0, &os.PathError{
|
||||
Op: "first",
|
||||
Path: p.path,
|
||||
Err: os.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
// Prev is part of source.Driver interface implementation.
|
||||
func (p *PartialDriver) Prev(version uint) (prevVersion uint, err error) {
|
||||
if version, ok := p.migrations.Prev(version); ok {
|
||||
return version, nil
|
||||
}
|
||||
return 0, &os.PathError{
|
||||
Op: "prev for version " + strconv.FormatUint(uint64(version), 10),
|
||||
Path: p.path,
|
||||
Err: os.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
// Next is part of source.Driver interface implementation.
|
||||
func (p *PartialDriver) Next(version uint) (nextVersion uint, err error) {
|
||||
if version, ok := p.migrations.Next(version); ok {
|
||||
return version, nil
|
||||
}
|
||||
return 0, &os.PathError{
|
||||
Op: "next for version " + strconv.FormatUint(uint64(version), 10),
|
||||
Path: p.path,
|
||||
Err: os.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadUp is part of source.Driver interface implementation.
|
||||
func (p *PartialDriver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {
|
||||
if m, ok := p.migrations.Up(version); ok {
|
||||
body, err := p.open(path.Join(p.path, m.Raw))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return body, m.Identifier, nil
|
||||
}
|
||||
return nil, "", &os.PathError{
|
||||
Op: "read up for version " + strconv.FormatUint(uint64(version), 10),
|
||||
Path: p.path,
|
||||
Err: os.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadDown is part of source.Driver interface implementation.
|
||||
func (p *PartialDriver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {
|
||||
if m, ok := p.migrations.Down(version); ok {
|
||||
body, err := p.open(path.Join(p.path, m.Raw))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return body, m.Identifier, nil
|
||||
}
|
||||
return nil, "", &os.PathError{
|
||||
Op: "read down for version " + strconv.FormatUint(uint64(version), 10),
|
||||
Path: p.path,
|
||||
Err: os.ErrNotExist,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PartialDriver) open(path string) (http.File, error) {
|
||||
f, err := p.fs.Open(path)
|
||||
if err == nil {
|
||||
return f, nil
|
||||
}
|
||||
// Some non-standard file systems may return errors that don't include the path, that
|
||||
// makes debugging harder.
|
||||
if !errors.As(err, new(*os.PathError)) {
|
||||
err = &os.PathError{
|
||||
Op: "open",
|
||||
Path: path,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
package httpfs_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4/source"
|
||||
"github.com/golang-migrate/migrate/v4/source/httpfs"
|
||||
st "github.com/golang-migrate/migrate/v4/source/testing"
|
||||
)
|
||||
|
||||
type driver struct{ httpfs.PartialDriver }
|
||||
|
||||
func (d *driver) Open(url string) (source.Driver, error) { return nil, errors.New("X") }
|
||||
|
||||
type driverExample struct {
|
||||
httpfs.PartialDriver
|
||||
}
|
||||
|
||||
func (d *driverExample) Open(url string) (source.Driver, error) {
|
||||
parts := strings.Split(url, ":")
|
||||
dir := parts[0]
|
||||
path := ""
|
||||
if len(parts) >= 2 {
|
||||
path = parts[1]
|
||||
}
|
||||
|
||||
var de driverExample
|
||||
return &de, de.Init(http.Dir(dir), path)
|
||||
}
|
||||
|
||||
func TestDriverExample(t *testing.T) {
|
||||
d, err := (*driverExample)(nil).Open("testdata:sql")
|
||||
if err != nil {
|
||||
t.Errorf("Open() returned error: %s", err)
|
||||
}
|
||||
st.Test(t, d)
|
||||
}
|
||||
|
||||
func TestPartialDriverInit(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fs http.FileSystem
|
||||
path string
|
||||
ok bool
|
||||
}{
|
||||
{
|
||||
name: "valid dir and empty path",
|
||||
fs: http.Dir("testdata/sql"),
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "valid dir and non-empty path",
|
||||
fs: http.Dir("testdata"),
|
||||
path: "sql",
|
||||
ok: true,
|
||||
},
|
||||
{
|
||||
name: "invalid dir",
|
||||
fs: http.Dir("does-not-exist"),
|
||||
},
|
||||
{
|
||||
name: "file instead of dir",
|
||||
fs: http.Dir("testdata/sql/1_foobar.up.sql"),
|
||||
},
|
||||
{
|
||||
name: "dir with duplicates",
|
||||
fs: http.Dir("testdata/duplicates"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var d driver
|
||||
err := d.Init(test.fs, test.path)
|
||||
if test.ok {
|
||||
if err != nil {
|
||||
t.Errorf("Init() returned error %s", err)
|
||||
}
|
||||
st.Test(t, &d)
|
||||
if err = d.Close(); err != nil {
|
||||
t.Errorf("Init().Close() returned error %s", err)
|
||||
}
|
||||
} else {
|
||||
if err == nil {
|
||||
t.Errorf("Init() expected error but did not get one")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestFirstWithNoMigrations(t *testing.T) {
|
||||
var d driver
|
||||
fs := http.Dir("testdata/no-migrations")
|
||||
|
||||
if err := d.Init(fs, ""); err != nil {
|
||||
t.Errorf("No error on Init() expected, got: %v", err)
|
||||
}
|
||||
|
||||
if _, err := d.First(); err == nil {
|
||||
t.Errorf("Expected error on First(), got: %v", err)
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
1 up
|
||||
+1
@@ -0,0 +1 @@
|
||||
1 up
|
||||
go/pkg/mod/github.com/golang-migrate/migrate/v4@v4.17.1/source/httpfs/testdata/sql/1_foobar.down.sql
Vendored
+1
@@ -0,0 +1 @@
|
||||
1 down
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
1 up
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
3 up
|
||||
go/pkg/mod/github.com/golang-migrate/migrate/v4@v4.17.1/source/httpfs/testdata/sql/4_foobar.down.sql
Vendored
+1
@@ -0,0 +1 @@
|
||||
4 down
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
4 up
|
||||
go/pkg/mod/github.com/golang-migrate/migrate/v4@v4.17.1/source/httpfs/testdata/sql/5_foobar.down.sql
Vendored
+1
@@ -0,0 +1 @@
|
||||
5 down
|
||||
go/pkg/mod/github.com/golang-migrate/migrate/v4@v4.17.1/source/httpfs/testdata/sql/7_foobar.down.sql
Vendored
+1
@@ -0,0 +1 @@
|
||||
7 down
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
7 up
|
||||
Reference in New Issue
Block a user