mirror of
https://github.com/kataras/iris.git
synced 2026-01-09 13:05:56 +00:00
SetImmutable for sessions and context's values. https://github.com/iris-contrib/community-board/issues/5
New package "memstore" created, read it to see how I made it. Former-commit-id: 9edc344b938786b2ef68defec03c44259a2d539c
This commit is contained in:
210
core/memstore/memstore.go
Normal file
210
core/memstore/memstore.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// Package memstore contains a store which is just
|
||||
// a collection of key-value entries with immutability capabilities.
|
||||
//
|
||||
// Created after that proposal: https://github.com/iris-contrib/community-board/issues/5
|
||||
// and it's being used by session entries (session lifetime) and context's entries (per-request).
|
||||
//
|
||||
// Developers can use that storage to their own apps if they like its behavior.
|
||||
// It's fast and in the same time you get read-only access (safety) when you need it.
|
||||
package memstore
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/kataras/iris/core/errors"
|
||||
)
|
||||
|
||||
type (
|
||||
// Entry is the entry of the context storage Store - .Values()
|
||||
Entry struct {
|
||||
Key string
|
||||
value interface{}
|
||||
immutable bool // if true then it can't change by its caller.
|
||||
}
|
||||
|
||||
// Store is a collection of key-value entries with immutability capabilities.
|
||||
Store []Entry
|
||||
)
|
||||
|
||||
// Value returns the value of the entry,
|
||||
// respects the immutable.
|
||||
func (e Entry) Value() interface{} {
|
||||
if e.immutable {
|
||||
// take its value, no pointer even if setted with a rreference.
|
||||
vv := reflect.Indirect(reflect.ValueOf(e.value))
|
||||
|
||||
// return copy of that slice
|
||||
if vv.Type().Kind() == reflect.Slice {
|
||||
newSlice := reflect.MakeSlice(vv.Type(), vv.Len(), vv.Cap())
|
||||
reflect.Copy(newSlice, vv)
|
||||
return newSlice.Interface()
|
||||
}
|
||||
// return a copy of that map
|
||||
if vv.Type().Kind() == reflect.Map {
|
||||
newMap := reflect.MakeMap(vv.Type())
|
||||
for _, k := range vv.MapKeys() {
|
||||
newMap.SetMapIndex(k, vv.MapIndex(k))
|
||||
}
|
||||
return newMap.Interface()
|
||||
}
|
||||
// if was *value it will return value{}.
|
||||
return vv.Interface()
|
||||
}
|
||||
return e.value
|
||||
}
|
||||
|
||||
// the id is immutable(true or false)+key
|
||||
// so the users will be able to use the same key
|
||||
// to store two different entries (one immutable and other mutable).
|
||||
// or no? better no, that will confuse and maybe result on unexpected results.
|
||||
// I will just replace the value and the immutable bool value when Set if
|
||||
// a key is already exists.
|
||||
// func (e Entry) identifier() string {}
|
||||
|
||||
func (r *Store) save(key string, value interface{}, immutable bool) {
|
||||
args := *r
|
||||
n := len(args)
|
||||
|
||||
// replace if we can, else just return
|
||||
for i := 0; i < n; i++ {
|
||||
kv := &args[i]
|
||||
if kv.Key == key {
|
||||
if immutable && kv.immutable {
|
||||
// if called by `SetImmutable`
|
||||
// then allow the update, maybe it's a slice that user wants to update by SetImmutable method,
|
||||
// we should allow this
|
||||
kv.value = value
|
||||
kv.immutable = immutable
|
||||
} else if kv.immutable == false {
|
||||
// if it was not immutable then user can alt it via `Set` and `SetImmutable`
|
||||
kv.value = value
|
||||
kv.immutable = immutable
|
||||
}
|
||||
// else it was immutable and called by `Set` then disallow the update
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// expand slice to add it
|
||||
c := cap(args)
|
||||
if c > n {
|
||||
args = args[:n+1]
|
||||
kv := &args[n]
|
||||
kv.Key = key
|
||||
kv.value = value
|
||||
kv.immutable = immutable
|
||||
*r = args
|
||||
return
|
||||
}
|
||||
|
||||
// add
|
||||
kv := Entry{
|
||||
Key: key,
|
||||
value: value,
|
||||
immutable: immutable,
|
||||
}
|
||||
*r = append(args, kv)
|
||||
}
|
||||
|
||||
// Set saves a value to the key-value storage.
|
||||
// See `SetImmutable` and `Get`.
|
||||
func (r *Store) Set(key string, value interface{}) {
|
||||
r.save(key, value, false)
|
||||
}
|
||||
|
||||
// SetImmutable saves a value to the key-value storage.
|
||||
// Unlike `Set`, the output value cannot be changed by the caller later on (when .Get OR .Set)
|
||||
//
|
||||
// An Immutable entry should be only changed with a `SetImmutable`, simple `Set` will not work
|
||||
// if the entry was immutable, for your own safety.
|
||||
//
|
||||
// Use it consistently, it's far slower than `Set`.
|
||||
// Read more about muttable and immutable go types: https://stackoverflow.com/a/8021081
|
||||
func (r *Store) SetImmutable(key string, value interface{}) {
|
||||
r.save(key, value, true)
|
||||
}
|
||||
|
||||
// Get returns the entry's value based on its key.
|
||||
func (r *Store) Get(key string) interface{} {
|
||||
args := *r
|
||||
n := len(args)
|
||||
for i := 0; i < n; i++ {
|
||||
kv := &args[i]
|
||||
if kv.Key == key {
|
||||
return kv.Value()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Visit accepts a visitor which will be filled
|
||||
// by the key-value objects.
|
||||
func (r *Store) Visit(visitor func(key string, value interface{})) {
|
||||
args := *r
|
||||
for i, n := 0, len(args); i < n; i++ {
|
||||
kv := args[i]
|
||||
visitor(kv.Key, kv.Value())
|
||||
}
|
||||
}
|
||||
|
||||
// GetString returns the entry's value as string, based on its key.
|
||||
func (r *Store) GetString(key string) string {
|
||||
if v, ok := r.Get(key).(string); ok {
|
||||
return v
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// ErrIntParse returns an error message when int parse failed
|
||||
// it's not statical error, it depends on the failed value.
|
||||
var ErrIntParse = errors.New("unable to find or parse the integer, found: %#v")
|
||||
|
||||
// GetInt returns the entry's value as int, based on its key.
|
||||
func (r *Store) GetInt(key string) (int, error) {
|
||||
v := r.Get(key)
|
||||
if vint, ok := v.(int); ok {
|
||||
return vint, nil
|
||||
} else if vstring, sok := v.(string); sok {
|
||||
return strconv.Atoi(vstring)
|
||||
}
|
||||
|
||||
return -1, ErrIntParse.Format(v)
|
||||
}
|
||||
|
||||
// GetInt64 returns the entry's value as int64, based on its key.
|
||||
func (r *Store) GetInt64(key string) (int64, error) {
|
||||
return strconv.ParseInt(r.GetString(key), 10, 64)
|
||||
}
|
||||
|
||||
// Remove deletes an entry linked to that "key",
|
||||
// returns true if an entry is actually removed.
|
||||
func (r *Store) Remove(key string) bool {
|
||||
args := *r
|
||||
n := len(args)
|
||||
for i := 0; i < n; i++ {
|
||||
kv := &args[i]
|
||||
if kv.Key == key {
|
||||
// we found the index,
|
||||
// let's remove the item by appending to the temp and
|
||||
// after set the pointer of the slice to this temp args
|
||||
args = append(args[:i], args[i+1:]...)
|
||||
*r = args
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Reset clears all the request entries.
|
||||
func (r *Store) Reset() {
|
||||
*r = (*r)[0:0]
|
||||
}
|
||||
|
||||
// Len returns the full length of the entries.
|
||||
func (r *Store) Len() int {
|
||||
args := *r
|
||||
return len(args)
|
||||
}
|
||||
93
core/memstore/memstore_test.go
Normal file
93
core/memstore/memstore_test.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package memstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
type myTestObject struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func TestMuttable(t *testing.T) {
|
||||
var p Store
|
||||
|
||||
// slice
|
||||
p.Set("slice", []myTestObject{{"value 1"}, {"value 2"}})
|
||||
v := p.Get("slice").([]myTestObject)
|
||||
v[0].name = "modified"
|
||||
|
||||
vv := p.Get("slice").([]myTestObject)
|
||||
if vv[0].name != "modified" {
|
||||
t.Fatalf("expected slice to be muttable but caller was not able to change its value")
|
||||
}
|
||||
|
||||
// map
|
||||
|
||||
p.Set("map", map[string]myTestObject{"key 1": myTestObject{"value 1"}, "key 2": myTestObject{"value 2"}})
|
||||
vMap := p.Get("map").(map[string]myTestObject)
|
||||
vMap["key 1"] = myTestObject{"modified"}
|
||||
|
||||
vvMap := p.Get("map").(map[string]myTestObject)
|
||||
if vvMap["key 1"].name != "modified" {
|
||||
t.Fatalf("expected map to be muttable but caller was not able to change its value")
|
||||
}
|
||||
|
||||
// object pointer of a value, it can change like maps or slices and arrays.
|
||||
p.Set("objp", &myTestObject{"value"})
|
||||
// we expect pointer here, as we set it.
|
||||
vObjP := p.Get("objp").(*myTestObject)
|
||||
|
||||
vObjP.name = "modified"
|
||||
|
||||
vvObjP := p.Get("objp").(*myTestObject)
|
||||
if vvObjP.name != "modified" {
|
||||
t.Fatalf("expected objp to be muttable but caller was able to change its value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestImmutable(t *testing.T) {
|
||||
var p Store
|
||||
|
||||
// slice
|
||||
p.SetImmutable("slice", []myTestObject{{"value 1"}, {"value 2"}})
|
||||
v := p.Get("slice").([]myTestObject)
|
||||
v[0].name = "modified"
|
||||
|
||||
vv := p.Get("slice").([]myTestObject)
|
||||
if vv[0].name == "modified" {
|
||||
t.Fatalf("expected slice to be immutable but caller was able to change its value")
|
||||
}
|
||||
|
||||
// map
|
||||
p.SetImmutable("map", map[string]myTestObject{"key 1": myTestObject{"value 1"}, "key 2": myTestObject{"value 2"}})
|
||||
vMap := p.Get("map").(map[string]myTestObject)
|
||||
vMap["key 1"] = myTestObject{"modified"}
|
||||
|
||||
vvMap := p.Get("map").(map[string]myTestObject)
|
||||
if vvMap["key 1"].name == "modified" {
|
||||
t.Fatalf("expected map to be immutable but caller was able to change its value")
|
||||
}
|
||||
|
||||
// object value, it's immutable at all cases.
|
||||
p.SetImmutable("obj", myTestObject{"value"})
|
||||
vObj := p.Get("obj").(myTestObject)
|
||||
vObj.name = "modified"
|
||||
|
||||
vvObj := p.Get("obj").(myTestObject)
|
||||
if vvObj.name == "modified" {
|
||||
t.Fatalf("expected obj to be immutable but caller was able to change its value")
|
||||
}
|
||||
|
||||
// object pointer of a value, it's immutable at all cases.
|
||||
p.SetImmutable("objp", &myTestObject{"value"})
|
||||
// we expect no pointer here if SetImmutable.
|
||||
// so it can't be changed by-design
|
||||
vObjP := p.Get("objp").(myTestObject)
|
||||
|
||||
vObjP.name = "modified"
|
||||
|
||||
vvObjP := p.Get("objp").(myTestObject)
|
||||
if vvObjP.name == "modified" {
|
||||
t.Fatalf("expected objp to be immutable but caller was able to change its value")
|
||||
}
|
||||
}
|
||||
@@ -173,9 +173,8 @@ func (h *routerHandler) HandleRequest(ctx context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
handlers, params, mustRedirect := t.Entry.ResolveRoute(ctx)
|
||||
handlers, mustRedirect := t.Entry.ResolveRoute(ctx)
|
||||
if len(handlers) > 0 {
|
||||
ctx.SetParams(params)
|
||||
ctx.SetHandlers(handlers)
|
||||
ctx.Handlers()[0](ctx)
|
||||
// to remove the .Next(maybe not a good idea), reduces the performance a bit:
|
||||
|
||||
@@ -329,11 +329,9 @@ func (n *Node) insertChild(numParams uint8, path, fullPath string, handle contex
|
||||
// given context.
|
||||
//
|
||||
// ResolveRoute finds the correct registered route from the Node when the ctx.Handlers() > 0.
|
||||
func (n *Node) ResolveRoute(ctx context.Context) (handlers context.Handlers, p context.RequestParams, tsr bool) { //(p context.RequestParams, tsr bool) {
|
||||
func (n *Node) ResolveRoute(ctx context.Context) (handlers context.Handlers, tsr bool) { //(p context.RequestParams, tsr bool) {
|
||||
path := ctx.Request().URL.Path
|
||||
handlers = ctx.Handlers()
|
||||
// values := ctx.Values()
|
||||
p = ctx.Params()
|
||||
walk: // outer loop for walking the tree
|
||||
for {
|
||||
if len(path) > len(n.path) {
|
||||
@@ -370,14 +368,7 @@ walk: // outer loop for walking the tree
|
||||
}
|
||||
|
||||
// save param value
|
||||
if cap(p) < int(n.maxParams) {
|
||||
p = make(context.RequestParams, 0, n.maxParams)
|
||||
}
|
||||
i := len(p)
|
||||
p = p[:i+1] // expand
|
||||
p[i].Key = n.path[1:]
|
||||
p[i].Value = path[:end]
|
||||
|
||||
ctx.Params().Set(n.path[1:], path[:end])
|
||||
// we need to go deeper!
|
||||
if end < len(path) {
|
||||
if len(n.children) > 0 {
|
||||
@@ -403,15 +394,7 @@ walk: // outer loop for walking the tree
|
||||
return
|
||||
|
||||
case catchAll:
|
||||
// save param value
|
||||
if cap(p) < int(n.maxParams) {
|
||||
p = make(context.RequestParams, 0, n.maxParams)
|
||||
}
|
||||
i := len(p)
|
||||
p = p[:i+1] // expand
|
||||
|
||||
p[i].Key = n.path[2:]
|
||||
p[i].Value = path
|
||||
ctx.Params().Set(n.path[2:], path)
|
||||
handlers = n.handle
|
||||
return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user