github.com/dendy1/migrate/v4@v4.15.2/source/iofs/iofs.go (about)

     1  //go:build go1.16
     2  // +build go1.16
     3  
     4  package iofs
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"io/fs"
    11  	"path"
    12  	"strconv"
    13  
    14  	"github.com/dendy1/migrate/v4/source"
    15  )
    16  
    17  type driver struct {
    18  	PartialDriver
    19  }
    20  
    21  // New returns a new Driver from io/fs#FS and a relative path.
    22  func New(fsys fs.FS, path string) (source.Driver, error) {
    23  	var i driver
    24  	if err := i.Init(fsys, path); err != nil {
    25  		return nil, fmt.Errorf("failed to init driver with path %s: %w", path, err)
    26  	}
    27  	return &i, nil
    28  }
    29  
    30  // Open is part of source.Driver interface implementation.
    31  // Open cannot be called on the iofs passthrough driver.
    32  func (d *driver) Open(url string) (source.Driver, error) {
    33  	return nil, errors.New("Open() cannot be called on the iofs passthrough driver")
    34  }
    35  
    36  // PartialDriver is a helper service for creating new source drivers working with
    37  // io/fs.FS instances. It implements all source.Driver interface methods
    38  // except for Open(). New driver could embed this struct and add missing Open()
    39  // method.
    40  //
    41  // To prepare PartialDriver for use Init() function.
    42  type PartialDriver struct {
    43  	migrations *source.Migrations
    44  	fsys       fs.FS
    45  	path       string
    46  }
    47  
    48  // Init prepares not initialized IoFS instance to read migrations from a
    49  // io/fs#FS instance and a relative path.
    50  func (d *PartialDriver) Init(fsys fs.FS, path string) error {
    51  	entries, err := fs.ReadDir(fsys, path)
    52  	if err != nil {
    53  		return err
    54  	}
    55  
    56  	ms := source.NewMigrations()
    57  	for _, e := range entries {
    58  		if e.IsDir() {
    59  			continue
    60  		}
    61  		m, err := source.DefaultParse(e.Name())
    62  		if err != nil {
    63  			continue
    64  		}
    65  		file, err := e.Info()
    66  		if err != nil {
    67  			return err
    68  		}
    69  		if !ms.Append(m) {
    70  			return source.ErrDuplicateMigration{
    71  				Migration: *m,
    72  				FileInfo:  file,
    73  			}
    74  		}
    75  	}
    76  
    77  	d.fsys = fsys
    78  	d.path = path
    79  	d.migrations = ms
    80  	return nil
    81  }
    82  
    83  // Close is part of source.Driver interface implementation.
    84  // Closes the file system if possible.
    85  func (d *PartialDriver) Close() error {
    86  	c, ok := d.fsys.(io.Closer)
    87  	if !ok {
    88  		return nil
    89  	}
    90  	return c.Close()
    91  }
    92  
    93  // First is part of source.Driver interface implementation.
    94  func (d *PartialDriver) First() (version uint, err error) {
    95  	if version, ok := d.migrations.First(); ok {
    96  		return version, nil
    97  	}
    98  	return 0, &fs.PathError{
    99  		Op:   "first",
   100  		Path: d.path,
   101  		Err:  fs.ErrNotExist,
   102  	}
   103  }
   104  
   105  // Prev is part of source.Driver interface implementation.
   106  func (d *PartialDriver) Prev(version uint) (prevVersion uint, err error) {
   107  	if version, ok := d.migrations.Prev(version); ok {
   108  		return version, nil
   109  	}
   110  	return 0, &fs.PathError{
   111  		Op:   "prev for version " + strconv.FormatUint(uint64(version), 10),
   112  		Path: d.path,
   113  		Err:  fs.ErrNotExist,
   114  	}
   115  }
   116  
   117  // Next is part of source.Driver interface implementation.
   118  func (d *PartialDriver) Next(version uint) (nextVersion uint, err error) {
   119  	if version, ok := d.migrations.Next(version); ok {
   120  		return version, nil
   121  	}
   122  	return 0, &fs.PathError{
   123  		Op:   "next for version " + strconv.FormatUint(uint64(version), 10),
   124  		Path: d.path,
   125  		Err:  fs.ErrNotExist,
   126  	}
   127  }
   128  
   129  // ReadUp is part of source.Driver interface implementation.
   130  func (d *PartialDriver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {
   131  	if m, ok := d.migrations.Up(version); ok {
   132  		body, err := d.open(path.Join(d.path, m.Raw))
   133  		if err != nil {
   134  			return nil, "", err
   135  		}
   136  		return body, m.Identifier, nil
   137  	}
   138  	return nil, "", &fs.PathError{
   139  		Op:   "read up for version " + strconv.FormatUint(uint64(version), 10),
   140  		Path: d.path,
   141  		Err:  fs.ErrNotExist,
   142  	}
   143  }
   144  
   145  // ReadDown is part of source.Driver interface implementation.
   146  func (d *PartialDriver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {
   147  	if m, ok := d.migrations.Down(version); ok {
   148  		body, err := d.open(path.Join(d.path, m.Raw))
   149  		if err != nil {
   150  			return nil, "", err
   151  		}
   152  		return body, m.Identifier, nil
   153  	}
   154  	return nil, "", &fs.PathError{
   155  		Op:   "read down for version " + strconv.FormatUint(uint64(version), 10),
   156  		Path: d.path,
   157  		Err:  fs.ErrNotExist,
   158  	}
   159  }
   160  
   161  func (d *PartialDriver) open(path string) (fs.File, error) {
   162  	f, err := d.fsys.Open(path)
   163  	if err == nil {
   164  		return f, nil
   165  	}
   166  	// Some non-standard file systems may return errors that don't include the path, that
   167  	// makes debugging harder.
   168  	if !errors.As(err, new(*fs.PathError)) {
   169  		err = &fs.PathError{
   170  			Op:   "open",
   171  			Path: path,
   172  			Err:  err,
   173  		}
   174  	}
   175  	return nil, err
   176  }