Skip to content

feat(filesystem): add filesystem modules for go-fries #915

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

Merged
merged 6 commits into from
Mar 25, 2025
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
1 change: 0 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ linters:
- mnd
- gocyclo
- ineffassign
- lll
- prealloc
- revive
- staticcheck
Expand Down
112 changes: 112 additions & 0 deletions filesystem/filesystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package filesystem

import (
"context"
"errors"
"time"
)

var ErrNotSupported = errors.New("[storage] not supported")

type Filesystem interface {
// Read the value at the given path.
Read(ctx context.Context, path string) ([]byte, error)

// Write the value at the given path.
Write(ctx context.Context, path string, value []byte) error

// Delete the value at the given path.
Delete(ctx context.Context, path string) error

// Exists checks if the path exists.
Exists(ctx context.Context, path string) (bool, error)

// Rename renames the value from the old path to the new path.
Rename(ctx context.Context, oldPath, newPath string) error

// Link creates a hard link from the old path to the new path.
Link(ctx context.Context, oldPath, newPath string) error

// Symlink creates a symbolic link from the old path to the new path.
Symlink(ctx context.Context, oldPath, newPath string) error

// Files lists the files in the given path.
Files(ctx context.Context, path string) ([]string, error)

// AllFiles lists all the files in the given path.(including subdirectories)
AllFiles(ctx context.Context, path string) ([]string, error)

// Directories lists the directories in the given path.
Directories(ctx context.Context, path string) ([]string, error)

// AllDirectories lists all the directories in the given path.(including subdirectories)
AllDirectories(ctx context.Context, path string) ([]string, error)

// MakeDirectory creates a directory at the given path.
MakeDirectory(ctx context.Context, path string) error

// DeleteDirectory deletes the directory at the given path.
DeleteDirectory(ctx context.Context, path string) error

// IsFile checks if the path is a file.
IsFile(ctx context.Context, path string) (bool, error)

// IsDir checks if the path is a directory.
IsDir(ctx context.Context, path string) (bool, error)

// Size returns the size of the file in bytes.
Size(ctx context.Context, path string) (int64, error)

// LastModified returns the last modified time of the file.
LastModified(ctx context.Context, path string) (*time.Time, error)

// Path returns the full path for the given path.
Path(ctx context.Context, path string) string

// Name returns the name of the file, without the extension.
Name(ctx context.Context, path string) string

// Basename returns the base name of the file, with the extension.
Basename(ctx context.Context, path string) string

// Dirname returns the directory name of the file.
Dirname(ctx context.Context, path string) string

// Extension returns the extension of the file.
Extension(ctx context.Context, path string) string
}
Comment on lines +11 to +77
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Missing error handling signatures in interface methods

Several interface methods should include error return values for failed operations, but some path-related methods (like Path, Name, Basename, Dirname, Extension) don't return errors even though they could fail in real implementations.

Consider adding error return values to these methods for consistency and to allow implementations to properly handle errors.


type Copyable interface {
Copy(ctx context.Context, oldPath, newPath string) error
}

type NoopFilesystem struct{}

var (
_ Filesystem = (*NoopFilesystem)(nil)
_ Copyable = (*NoopFilesystem)(nil)
)

func (NoopFilesystem) Read(context.Context, string) ([]byte, error) { return nil, nil }
func (NoopFilesystem) Write(context.Context, string, []byte) error { return nil }
func (NoopFilesystem) Delete(context.Context, string) error { return nil }
func (NoopFilesystem) Exists(context.Context, string) (bool, error) { return true, nil }
func (NoopFilesystem) Rename(context.Context, string, string) error { return nil }
func (NoopFilesystem) Link(context.Context, string, string) error { return nil }
func (NoopFilesystem) Symlink(context.Context, string, string) error { return nil }
func (NoopFilesystem) Files(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) AllFiles(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) Directories(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) AllDirectories(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) MakeDirectory(context.Context, string) error { return nil }
func (NoopFilesystem) DeleteDirectory(context.Context, string) error { return nil }
func (NoopFilesystem) IsFile(context.Context, string) (bool, error) { return false, nil }
func (NoopFilesystem) IsDir(context.Context, string) (bool, error) { return false, nil }
Comment on lines +93 to +104
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Ensure logical consistency in NoopFilesystem boolean methods

The implementation has a logical inconsistency: Exists() returns true (line 93) while IsFile() and IsDir() both return false (lines 103-104). In most filesystems, if a path exists, it should be either a file or directory.

For a no-op implementation, consider making these logically consistent:

-func (NoopFilesystem) Exists(context.Context, string) (bool, error)             { return true, nil }
+func (NoopFilesystem) Exists(context.Context, string) (bool, error)             { return false, nil }

Or alternatively:

-func (NoopFilesystem) IsFile(context.Context, string) (bool, error)             { return false, nil }
+func (NoopFilesystem) IsFile(context.Context, string) (bool, error)             { return true, nil }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (NoopFilesystem) Exists(context.Context, string) (bool, error) { return true, nil }
func (NoopFilesystem) Rename(context.Context, string, string) error { return nil }
func (NoopFilesystem) Link(context.Context, string, string) error { return nil }
func (NoopFilesystem) Symlink(context.Context, string, string) error { return nil }
func (NoopFilesystem) Files(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) AllFiles(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) Directories(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) AllDirectories(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) MakeDirectory(context.Context, string) error { return nil }
func (NoopFilesystem) DeleteDirectory(context.Context, string) error { return nil }
func (NoopFilesystem) IsFile(context.Context, string) (bool, error) { return false, nil }
func (NoopFilesystem) IsDir(context.Context, string) (bool, error) { return false, nil }
func (NoopFilesystem) Exists(context.Context, string) (bool, error) { return false, nil }
func (NoopFilesystem) Rename(context.Context, string, string) error { return nil }
func (NoopFilesystem) Link(context.Context, string, string) error { return nil }
func (NoopFilesystem) Symlink(context.Context, string, string) error { return nil }
func (NoopFilesystem) Files(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) AllFiles(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) Directories(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) AllDirectories(context.Context, string) ([]string, error) { return nil, nil }
func (NoopFilesystem) MakeDirectory(context.Context, string) error { return nil }
func (NoopFilesystem) DeleteDirectory(context.Context, string) error { return nil }
func (NoopFilesystem) IsFile(context.Context, string) (bool, error) { return false, nil }
func (NoopFilesystem) IsDir(context.Context, string) (bool, error) { return false, nil }

func (NoopFilesystem) Size(context.Context, string) (int64, error) { return 0, nil }
func (NoopFilesystem) LastModified(context.Context, string) (*time.Time, error) { return nil, nil }
func (NoopFilesystem) Path(context.Context, string) string { return "" }
func (NoopFilesystem) Name(context.Context, string) string { return "" }
func (NoopFilesystem) Basename(context.Context, string) string { return "" }
func (NoopFilesystem) Dirname(context.Context, string) string { return "" }
func (NoopFilesystem) Extension(context.Context, string) string { return "" }
func (NoopFilesystem) Copy(context.Context, string, string) error { return nil }
53 changes: 53 additions & 0 deletions filesystem/filesystem_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package filesystem

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
)

func TestStorage(t *testing.T) {
var (
noop = NoopFilesystem{}
ctx = context.Background()
)

_, err := noop.Read(ctx, "test")
assert.NoError(t, err)

assert.NoError(t, noop.Write(ctx, "noop", []byte("noop")))
assert.NoError(t, noop.Delete(ctx, "noop"))

has, err := noop.Exists(ctx, "noop")
assert.NoError(t, err)
assert.True(t, has)

assert.NoError(t, noop.Rename(ctx, "noop", "noop"))
assert.NoError(t, noop.Link(ctx, "noop", "noop"))
assert.NoError(t, noop.Symlink(ctx, "noop", "noop"))

files, err := noop.Files(ctx, "noop")
assert.NoError(t, err)
assert.Len(t, files, 0)

allFiles, err := noop.AllFiles(ctx, "noop")
assert.NoError(t, err)
assert.Len(t, allFiles, 0)

directories, err := noop.Directories(ctx, "noop")
assert.NoError(t, err)
assert.Len(t, directories, 0)

allDirectories, err := noop.AllDirectories(ctx, "noop")
assert.NoError(t, err)
assert.Len(t, allDirectories, 0)

isFile, err := noop.IsFile(ctx, "noop")
assert.NoError(t, err)
assert.False(t, isFile)

isDir, err := noop.IsDir(ctx, "noop")
assert.NoError(t, err)
assert.False(t, isDir)
}
11 changes: 11 additions & 0 deletions filesystem/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module github.com/go-fries/fries/filesystem/v3

go 1.23.0

require github.com/stretchr/testify v1.10.0

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions filesystem/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading