//go:build go1.16 // +build go1.16 package iofs import ( "errors" "fmt" "io" "io/fs" "path" "strconv" "github.com/golang-migrate/migrate/v4/source" ) type driver struct { PartialDriver } // New returns a new Driver from io/fs#FS and a relative path. func New(fsys fs.FS, path string) (source.Driver, error) { var i driver if err := i.Init(fsys, path); err != nil { return nil, fmt.Errorf("failed to init driver with path %s: %w", path, err) } return &i, nil } // Open is part of source.Driver interface implementation. // Open cannot be called on the iofs passthrough driver. func (d *driver) Open(url string) (source.Driver, error) { return nil, errors.New("Open() cannot be called on the iofs passthrough driver") } // PartialDriver is a helper service for creating new source drivers working with // io/fs.FS 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 fsys fs.FS path string } // Init prepares not initialized IoFS instance to read migrations from a // io/fs#FS instance and a relative path. func (d *PartialDriver) Init(fsys fs.FS, path string) error { entries, err := fs.ReadDir(fsys, path) if err != nil { return err } ms := source.NewMigrations() for _, e := range entries { if e.IsDir() { continue } m, err := source.DefaultParse(e.Name()) if err != nil { continue } file, err := e.Info() if err != nil { return err } if !ms.Append(m) { return source.ErrDuplicateMigration{ Migration: *m, FileInfo: file, } } } d.fsys = fsys d.path = path d.migrations = ms return nil } // Close is part of source.Driver interface implementation. // Closes the file system if possible. func (d *PartialDriver) Close() error { c, ok := d.fsys.(io.Closer) if !ok { return nil } return c.Close() } // First is part of source.Driver interface implementation. func (d *PartialDriver) First() (version uint, err error) { if version, ok := d.migrations.First(); ok { return version, nil } return 0, &fs.PathError{ Op: "first", Path: d.path, Err: fs.ErrNotExist, } } // Prev is part of source.Driver interface implementation. func (d *PartialDriver) Prev(version uint) (prevVersion uint, err error) { if version, ok := d.migrations.Prev(version); ok { return version, nil } return 0, &fs.PathError{ Op: "prev for version " + strconv.FormatUint(uint64(version), 10), Path: d.path, Err: fs.ErrNotExist, } } // Next is part of source.Driver interface implementation. func (d *PartialDriver) Next(version uint) (nextVersion uint, err error) { if version, ok := d.migrations.Next(version); ok { return version, nil } return 0, &fs.PathError{ Op: "next for version " + strconv.FormatUint(uint64(version), 10), Path: d.path, Err: fs.ErrNotExist, } } // ReadUp is part of source.Driver interface implementation. func (d *PartialDriver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { if m, ok := d.migrations.Up(version); ok { body, err := d.open(path.Join(d.path, m.Raw)) if err != nil { return nil, "", err } return body, m.Identifier, nil } return nil, "", &fs.PathError{ Op: "read up for version " + strconv.FormatUint(uint64(version), 10), Path: d.path, Err: fs.ErrNotExist, } } // ReadDown is part of source.Driver interface implementation. func (d *PartialDriver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { if m, ok := d.migrations.Down(version); ok { body, err := d.open(path.Join(d.path, m.Raw)) if err != nil { return nil, "", err } return body, m.Identifier, nil } return nil, "", &fs.PathError{ Op: "read down for version " + strconv.FormatUint(uint64(version), 10), Path: d.path, Err: fs.ErrNotExist, } } func (d *PartialDriver) open(path string) (fs.File, error) { f, err := d.fsys.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(*fs.PathError)) { err = &fs.PathError{ Op: "open", Path: path, Err: err, } } return nil, err }