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

feat: Add mockstorage implementation #1405

Merged
merged 10 commits into from
Jun 6, 2024
28 changes: 28 additions & 0 deletions .github/workflows/test-mockstorage.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
on:
push:
branches:
- master
- main
paths:
- 'mockstorage/**'
pull_request:
paths:
- 'mockstorage/**'
name: "Tests Local Storage"
jobs:
Tests:
strategy:
matrix:
go-version:
- 1.21.x
- 1.22.x
runs-on: ubuntu-latest
steps:
- name: Fetch Repository
uses: actions/checkout@v4
- name: Install Go
uses: actions/setup-go@v5
with:
go-version: '${{ matrix.go-version }}'
- name: Test Mockstorage
run: cd ./mockstorage && go test ./... -v -race
131 changes: 131 additions & 0 deletions mockstorage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
id: memory
title: Memory
---


![Release](https://img.shields.io/github/v/tag/gofiber/storage?filter=mockstorage*)
[![Discord](https://img.shields.io/discord/704680098577514527?style=flat&label=%F0%9F%92%AC%20discord&color=00ACD7)](https://gofiber.io/discord)
![Test](https://img.shields.io/github/actions/workflow/status/gofiber/storage/test-mockstorage.yml?label=Tests)
![Security](https://img.shields.io/github/actions/workflow/status/gofiber/storage/gosec.yml?label=Security)
![Linter](https://img.shields.io/github/actions/workflow/status/gofiber/storage/linter.yml?label=Linter)

A mock storage implementation for Fiber. This storage is not persistent and is only used for testing purposes.

**Note: Requires Go 1.21 and above**
ReneWerner87 marked this conversation as resolved.
Show resolved Hide resolved

### Table of Contents
- [Signatures](#signatures)
- [Installation](#installation)
- [Examples](#examples)
- [Config](#config)
- [Default Config](#default-config)


### Signatures
```go
ReneWerner87 marked this conversation as resolved.
Show resolved Hide resolved
func New(config ...Config) *Storage
func (s *Storage) Get(key string) ([]byte, error)
func (s *Storage) Set(key string, val []byte, exp time.Duration) error
func (s *Storage) Delete(key string) error
func (s *Storage) Reset() error
func (s *Storage) Close() error
func (s *Storage) Conn() map[string]entry
func (s *Storage) Keys() ([][]byte, error)

func SetCustomFuncs(custom *CustomFuncs)
```

### Installation
Mockstorage is tested on the 2 last [Go versions](https://golang.org/dl/) with support for modules. So make sure to initialize one first if you didn't do that yet:
```bash
go mod init github.com/<user>/<repo>
```
And then install the mockstorage implementation:
```bash
go get github.com/gofiber/storage/mockstorage
```

### Examples
Import the storage package.
```go
import "github.com/gofiber/storage/mockstorage"
```

You can use the following possibilities to create a storage:
```go
// Initialize default config
store := mockstorage.New()

// Initialize custom config
store := mockstorage.New(mockstorage.Config{
CustomFuncs: &mockstorage.CustomFuncs{
&CustomFuncs{
GetFunc: func(key string) ([]byte, error) {
if key == "customKey" {
return []byte("customValue"), nil
}
return nil, errors.New("custom key not found")
},
SetFunc: func(key string, val []byte, exp time.Duration) error {
if key == "readonly" {
return errors.New("cannot set readonly key")
}
return nil
},
DeleteFunc: func(key string) error {
if key == "protectedKey" {
return errors.New("cannot delete protected key")
}
return nil
},
// ...
},
},
})

// Set custom functions after initialization
store.SetCustomFuncs(&mockstorage.CustomFuncs{
GetFunc: func(key string) ([]byte, error) {
if key == "customKey" {
return []byte("customValue"), nil
}
return nil, errors.New("custom key not found")
},
SetFunc: func(key string, val []byte, exp time.Duration) error {
if key == "readonly" {
return errors.New("cannot set readonly key")
}
return nil
},
DeleteFunc: func(key string) error {
if key == "protectedKey" {
return errors.New("cannot delete protected key")
}
return nil
},
// ...
})
```

### Config
```go
type Config struct {
CustomFuncs *CustomFuncs
}
```

### Default Config
```go
var ConfigDefault = Config{
CustomFuncs: &CustomFuncs{
GetFunc: nil,
SetFunc: nil,
DeleteFunc: nil,
ResetFunc: nil,
CloseFunc: nil,
ConnFunc: nil,
KeysFunc: nil,
},
}
```
3 changes: 3 additions & 0 deletions mockstorage/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module mockstorage

go 1.21
Empty file added mockstorage/go.sum
Empty file.
170 changes: 170 additions & 0 deletions mockstorage/mockstorage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package mockstorage

import (
"errors"
"sync"
"time"
)

// Config defines the config for mock storage.
type Config struct {
CustomFuncs *CustomFuncs
}

// Storage is the mock storage adapter.
type Storage struct {
mu sync.RWMutex
data map[string]entry
custom *CustomFuncs
}

// entry struct to hold value and expiration time.
type entry struct {
value []byte
exp time.Time
}

// CustomFuncs allows injecting custom behaviors for testing.
type CustomFuncs struct {
GetFunc func(key string) ([]byte, error)
SetFunc func(key string, val []byte, exp time.Duration) error
DeleteFunc func(key string) error
ResetFunc func() error
CloseFunc func() error
ConnFunc func() map[string]entry
KeysFunc func() ([][]byte, error)
}

// New creates a new mock storage with optional configuration.
func New(config ...Config) *Storage {
s := &Storage{
data: make(map[string]entry),
custom: &CustomFuncs{
GetFunc: nil,
SetFunc: nil,
DeleteFunc: nil,
ResetFunc: nil,
CloseFunc: nil,
ConnFunc: nil,
KeysFunc: nil,
},
}

// If a config is provided and it has CustomFuncs, use them
if len(config) > 0 && config[0].CustomFuncs != nil {
s.custom = config[0].CustomFuncs
}

return s
}

// Get retrieves the value for a given key.
func (s *Storage) Get(key string) ([]byte, error) {
if s.custom.GetFunc != nil {
return s.custom.GetFunc(key)
}

s.mu.RLock()
defer s.mu.RUnlock()

e, ok := s.data[key]
if !ok {
return nil, errors.New("key not found")
}
if !e.exp.IsZero() && time.Now().After(e.exp) {
delete(s.data, key)
return nil, errors.New("key expired")
}
return e.value, nil
}

// Set sets the value for a given key with an expiration time.
func (s *Storage) Set(key string, val []byte, exp time.Duration) error {
if s.custom.SetFunc != nil {
return s.custom.SetFunc(key, val, exp)
}

s.mu.Lock()
defer s.mu.Unlock()

var expTime time.Time
if exp > 0 {
expTime = time.Now().Add(exp)
}

s.data[key] = entry{value: val, exp: expTime}
return nil
}

// Delete removes a key from the storage.
func (s *Storage) Delete(key string) error {
if s.custom.DeleteFunc != nil {
return s.custom.DeleteFunc(key)
}

s.mu.Lock()
defer s.mu.Unlock()

delete(s.data, key)
return nil
}

// Reset clears all keys from the storage.
func (s *Storage) Reset() error {
if s.custom.ResetFunc != nil {
return s.custom.ResetFunc()
}

s.mu.Lock()
defer s.mu.Unlock()

s.data = make(map[string]entry)
return nil
}

// Close closes the storage (no-op for mock).
func (s *Storage) Close() error {
if s.custom.CloseFunc != nil {
return s.custom.CloseFunc()
}

// No resources to clean up in mock
return nil
}

// Conn returns the internal data map (for testing purposes).
func (s *Storage) Conn() map[string]entry {
if s.custom.ConnFunc != nil {
return s.custom.ConnFunc()
}

s.mu.RLock()
defer s.mu.RUnlock()

copyData := make(map[string]entry)
for k, v := range s.data {
copyData[k] = v
}
return copyData
}

// Keys returns all keys in the storage.
func (s *Storage) Keys() ([][]byte, error) {
if s.custom.KeysFunc != nil {
return s.custom.KeysFunc()
}

s.mu.RLock()
defer s.mu.RUnlock()

keys := make([][]byte, 0, len(s.data))
for k := range s.data {
keys = append(keys, []byte(k))
}
return keys, nil
}

// SetCustomFuncs allows setting custom function implementations.
func (s *Storage) SetCustomFuncs(custom *CustomFuncs) {
s.custom = custom
}
Loading
Loading