govirtual-file

Simples way to make a []byte into a "virtual" File object in golang?


I know there are Go libraries that create entire filesystems like VFS. But I only want to make a byte array into something that can fulfill the File interface.


Solution

  • There is no ready solution for this in the standard library, but it's not that hard to do it yourself.

    What we need is this http.File interface:

    type File interface {
            io.Closer
            io.Reader
            io.Seeker
            Readdir(count int) ([]os.FileInfo, error)
            Stat() (os.FileInfo, error)
    }
    

    Please note that we can utilize bytes.Reader to do the heavy task, as that alone implements io.Reader and io.Seeker. io.Closer can be a noop, and Readdir() may return nil, nil as we're mocking a file not a directory, its Readdir() won't even be called.

    The "hardest" part is to mock Stat() to return a value that implements os.FileInfo.

    Here's a simple mocked FileInfo:

    type myFileInfo struct {
        name string
        data []byte
    }
    
    func (mif myFileInfo) Name() string       { return mif.name }
    func (mif myFileInfo) Size() int64        { return int64(len(mif.data)) }
    func (mif myFileInfo) Mode() os.FileMode  { return 0444 }        // Read for all
    func (mif myFileInfo) ModTime() time.Time { return time.Time{} } // Return anything
    func (mif myFileInfo) IsDir() bool        { return false }
    func (mif myFileInfo) Sys() interface{}   { return nil }
    

    And with that we have everything to create our mocked http.File:

    type MyFile struct {
        *bytes.Reader
        mif myFileInfo
    }
    
    func (mf *MyFile) Close() error { return nil } // Noop, nothing to do
    
    func (mf *MyFile) Readdir(count int) ([]os.FileInfo, error) {
        return nil, nil // We are not a directory but a single file
    }
    
    func (mf *MyFile) Stat() (os.FileInfo, error) {
        return mf.mif, nil
    }
    

    Example using it (try it on the Go Playground):

    data := []byte{0, 1, 2, 3}
    
    mf := &MyFile{
        Reader: bytes.NewReader(data),
        mif: myFileInfo{
            name: "somename.txt",
            data: data,
        },
    }
    
    var f http.File = mf
    _ = f