Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let caller hander error cases #101

Merged
merged 7 commits into from
Apr 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ type Options struct {
// OnDirExists can specify what to do when there is a directory already existing in destination.
OnDirExists func(src, dest string) DirExistsAction

// OnError can let users decide how to handle errors (e.g., you can suppress specific error).
OnError func(src, dest, string, err error) error

// Skip can specify which files should be skipped
Skip func(srcinfo os.FileInfo, src, dest string) (bool, error)

Expand Down
40 changes: 40 additions & 0 deletions all_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,46 @@ func TestOptions_CopyRateLimit(t *testing.T) {
Expect(t, elapsed > 5*time.Second).ToBe(true)
}

func TestOptions_OnFileError(t *testing.T) {
opt := Options{
OnError: nil,
}

// existing, process nromally
err := Copy("test/data/case17", "test/data.copy/case17", opt)
Expect(t, err).ToBe(nil)

// not existing, process err
err = Copy("test/data/case17/non-existing", "test/data.copy/case17/non-existing", opt)
Expect(t, os.IsNotExist(err)).ToBe(true)

_, err = os.Stat("test/data.copy/case17/non-existing")
Expect(t, os.IsNotExist(err)).ToBe(true)

// existing, nil err not passed
opt.OnError = func(_, _ string, err error) error {
return err
}
err = Copy("test/data/case17", "test/data.copy/case17", opt)
Expect(t, err).ToBe(nil)

// not existing, process err
opt.OnError = func(_, _ string, err error) error { return err }
err = Copy("test/data/case17/non-existing", "test/data.copy/case17/non-existing", opt)
Expect(t, os.IsNotExist(err)).ToBe(true)

_, err = os.Stat("test/data.copy/case17/non-existing")
Expect(t, os.IsNotExist(err)).ToBe(true)

// not existing, ignore err
opt.OnError = func(_, _ string, err error) error { return nil }
err = Copy("test/data/case17/non-existing", "test/data.copy/case17/non-existing", opt)
Expect(t, err).ToBe(nil)

_, err = os.Stat("test/data.copy/case17/non-existing")
Expect(t, os.IsNotExist(err)).ToBe(true)
}

type SleepyReader struct {
src *os.File
sec time.Duration
Expand Down
23 changes: 16 additions & 7 deletions copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,20 @@ type timespec struct {
}

// Copy copies src to dest, doesn't matter if src is a directory or a file.
func Copy(src, dest string, opt ...Options) error {
func Copy(src, dest string, opts ...Options) error {
opt := assureOptions(src, dest, opts...)
info, err := os.Lstat(src)
if err != nil {
return err
return onError(src, dest, err, opt)
}
return switchboard(src, dest, info, assureOptions(src, dest, opt...))
return switchboard(src, dest, info, opt)
}

// switchboard switches proper copy functions regarding file type, etc...
// If there would be anything else here, add a case to this switchboard.
func switchboard(src, dest string, info os.FileInfo, opt Options) (err error) {

if info.Mode()&os.ModeDevice != 0 && !opt.Specials {
return err
return onError(src, dest, err, opt)
}

switch {
Expand All @@ -42,7 +42,7 @@ func switchboard(src, dest string, info os.FileInfo, opt Options) (err error) {
err = fcopy(src, dest, info, opt)
}

return err
return onError(src, dest, err, opt)
}

// copyNextOrSkip decide if this src should be copied or not.
Expand Down Expand Up @@ -130,7 +130,6 @@ func fcopy(src, dest string, info os.FileInfo, opt Options) (err error) {
// with scanning contents inside the directory
// and pass everything to "copy" recursively.
func dcopy(srcdir, destdir string, info os.FileInfo, opt Options) (err error) {

if skip, err := onDirExists(opt, srcdir, destdir); err != nil {
return err
} else if skip {
Expand Down Expand Up @@ -235,3 +234,13 @@ func fclose(f *os.File, reported *error) {
*reported = err
}
}

// onError lets caller to handle errors
// occured when copying a file.
func onError(src, dest string, err error, opt Options) error {
if opt.OnError == nil {
return err
}

return opt.OnError(src, dest, err)
}
4 changes: 4 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ type Options struct {
// OnDirExists can specify what to do when there is a directory already existing in destination.
OnDirExists func(src, dest string) DirExistsAction

// OnErr lets called decide whether or not to continue on particular copy error.
OnError func(src, dest string, err error) error

// Skip can specify which files should be skipped
Skip func(srcinfo os.FileInfo, src, dest string) (bool, error)

Expand Down Expand Up @@ -95,6 +98,7 @@ func getDefaultOptions(src, dest string) Options {
return Shallow // Do shallow copy
},
OnDirExists: nil, // Default behavior is "Merge".
OnError: nil, // Default is "accept error"
Skip: nil, // Do not skip anything
AddPermission: 0, // Add nothing
PermissionControl: PerservePermission, // Just preserve permission
Expand Down
5 changes: 5 additions & 0 deletions test/data/case17/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
So if you wanted to ignore error you should add something like this:
```go
opt.OnError = func(src, dst string, _ error) error { return nil }
```
The default value is nil and accepts raised error.