You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
249 lines
8.1 KiB
249 lines
8.1 KiB
|
2 years ago
|
// =============================================================================
|
||
|
|
// Active-GO Framework
|
||
|
|
// Copyright (c) 2024 ActiveThing (https://activething.com)
|
||
|
|
// Author: Juan V. Navarro juanvnl@activething.com
|
||
|
|
// =============================================================================
|
||
|
|
//
|
||
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
|
// of this software and associated documentation files (the "Software"), to deal
|
||
|
|
// in the Software without restriction, including without limitation the rights
|
||
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
|
// copies of the Software, and to permit persons to whom the Software is
|
||
|
|
// furnished to do so, subject to the following conditions:
|
||
|
|
//
|
||
|
|
// The above copyright notice and this permission notice shall be included in
|
||
|
|
// all copies or substantial portions of the Software.
|
||
|
|
//
|
||
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||
|
|
// THE SOFTWARE.
|
||
|
|
//
|
||
|
|
// =============================================================================
|
||
|
|
|
||
|
|
package scps
|
||
|
|
|
||
|
|
// Package scope provides a hierarchical key-value store implementation with thread-safety
|
||
|
|
// and parent-child relationship support. It allows for creating isolated scopes that can
|
||
|
|
// inherit values from parent scopes while maintaining their own independent modifications.
|
||
|
|
|
||
|
|
import (
|
||
|
|
"core/rets"
|
||
|
|
"core/vals"
|
||
|
|
"sync"
|
||
|
|
)
|
||
|
|
|
||
|
|
type (
|
||
|
|
|
||
|
|
// Scope represents a hierarchical key-value store with thread-safe operations.
|
||
|
|
// Each Scope can have a parent from which it inherits values, and can be marked
|
||
|
|
// as read-only to prevent modifications.
|
||
|
|
Scope struct {
|
||
|
|
// parent represents the parent scope from which this scope inherits values
|
||
|
|
parent vals.KeyValueGetter
|
||
|
|
// entries stores the key-value pairs in a thread-safe map
|
||
|
|
entries sync.Map
|
||
|
|
// isReadOnly indicates whether the scope can be modified
|
||
|
|
isReadOnly bool
|
||
|
|
}
|
||
|
|
|
||
|
|
// ScopeFnc defines a function type for scope configuration operations.
|
||
|
|
// It's used in the functional options pattern for configuring new scopes.
|
||
|
|
ScopeFnc func(*Scope) error
|
||
|
|
)
|
||
|
|
|
||
|
|
// NewScope creates a new Scope instance with an optional parent and configuration options.
|
||
|
|
// It initializes an empty scope and applies the provided configuration functions in order.
|
||
|
|
//
|
||
|
|
// Parameters:
|
||
|
|
// - parent: The parent scope from which the new scope will inherit values (can be nil)
|
||
|
|
// - ops: Optional configuration functions to be applied to the new scope
|
||
|
|
//
|
||
|
|
// Returns:
|
||
|
|
// - *Scope: The newly created scope
|
||
|
|
// - error: An error if any configuration operation fails
|
||
|
|
func NewScope(parent vals.KeyValueGetter, ops ...ScopeFnc) (*Scope, error) {
|
||
|
|
sc := &Scope{
|
||
|
|
parent: parent,
|
||
|
|
entries: sync.Map{},
|
||
|
|
}
|
||
|
|
for _, op := range ops {
|
||
|
|
if er := op(sc); er != nil {
|
||
|
|
return nil, er
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return sc, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// IsReadOnly returns whether the scope is in read-only mode.
|
||
|
|
// Read-only scopes cannot be modified through SetValue, DelValue, or Reset operations.
|
||
|
|
func (s *Scope) IsReadOnly() bool { return s.isReadOnly }
|
||
|
|
|
||
|
|
// NewChild creates a new child Scope inheriting from the current Scope.
|
||
|
|
// The child Scope can have its own independent key-value pairs and optional configurations.
|
||
|
|
//
|
||
|
|
// Parameters:
|
||
|
|
// - ops: Optional configuration functions to be applied to the child scope
|
||
|
|
//
|
||
|
|
// Returns:
|
||
|
|
// - *Scope: The newly created child scope
|
||
|
|
// - error: An error if creation or configuration fails
|
||
|
|
func (s *Scope) NewChild(ops ...ScopeFnc) (*Scope, error) {
|
||
|
|
sc, er := NewScope(s, ops...)
|
||
|
|
if er != nil {
|
||
|
|
return nil, er
|
||
|
|
}
|
||
|
|
return sc, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// DelValue removes a key-value pair from the current Scope by its key.
|
||
|
|
// If the Scope is marked as read-only, the operation returns an error.
|
||
|
|
//
|
||
|
|
// Parameters:
|
||
|
|
// - key: The key to remove from the scope
|
||
|
|
//
|
||
|
|
// Returns:
|
||
|
|
// - error: ErrReadOnlyScope if the scope is read-only, nil otherwise
|
||
|
|
func (s *Scope) DelValue(key any) error {
|
||
|
|
if s.IsReadOnly() {
|
||
|
|
// No modifications allowed if the Scope is read-only.
|
||
|
|
return G.ErrReadOnlyScope
|
||
|
|
}
|
||
|
|
s.entries.Delete(key) // Delete the key-value pair.
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// GetValue retrieves a value from the scope by its key.
|
||
|
|
// If the key is not found in the current scope and a parent exists,
|
||
|
|
// the request is delegated to the parent scope.
|
||
|
|
//
|
||
|
|
// Parameters:
|
||
|
|
// - key: The key to look up
|
||
|
|
//
|
||
|
|
// Returns:
|
||
|
|
// - rets.Resulter: A result containing the value if found, or an error result if:
|
||
|
|
// - The key is nil (ResultInvalidKey)
|
||
|
|
// - The key is not found in this scope or any parent scope (ResultKeyNotFound)
|
||
|
|
func (s *Scope) GetValue(key any) rets.Result {
|
||
|
|
if key == nil {
|
||
|
|
return g.ResultInvalidKey
|
||
|
|
}
|
||
|
|
if en, ok := s.entries.Load(key); ok {
|
||
|
|
return (en.(scopeEntry)).Result()
|
||
|
|
}
|
||
|
|
if s.parent != nil {
|
||
|
|
return s.parent.GetValue(key)
|
||
|
|
}
|
||
|
|
return g.ResultKeyNotFound
|
||
|
|
}
|
||
|
|
|
||
|
|
// SetValue stores a key-value pair in the current scope.
|
||
|
|
// If the scope is read-only or the key is nil, an error is returned.
|
||
|
|
// If the key already exists and is associated with a provider, the operation fails.
|
||
|
|
//
|
||
|
|
// Parameters:
|
||
|
|
// - key: The key under which to store the value
|
||
|
|
// - value: The value to store
|
||
|
|
//
|
||
|
|
// Returns:
|
||
|
|
// - error: An error if:
|
||
|
|
// - The key is nil (ErrInvalidKey)
|
||
|
|
// - The scope is read-only (ErrReadOnlyScope)
|
||
|
|
// - The key exists and is associated with a provider (ErrInvalidValue)
|
||
|
|
func (s *Scope) SetValue(key, value any) error {
|
||
|
|
var en scopeEntry
|
||
|
|
if key == nil {
|
||
|
|
return G.ErrInvalidKey
|
||
|
|
}
|
||
|
|
if s.isReadOnly {
|
||
|
|
return G.ErrReadOnlyScope
|
||
|
|
}
|
||
|
|
if ob, ok := s.entries.Load(key); ok {
|
||
|
|
en = ob.(scopeEntry)
|
||
|
|
if !en.isValue {
|
||
|
|
// si es un provider no podemos cambiarlo una vez
|
||
|
|
// establecido el valor como un valor simple
|
||
|
|
//
|
||
|
|
return G.ErrInvalidValue
|
||
|
|
}
|
||
|
|
//
|
||
|
|
en.value = value
|
||
|
|
} else {
|
||
|
|
en = *newScopeEntry(value)
|
||
|
|
}
|
||
|
|
s.entries.Store(key, en)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Range iterates over all key-value pairs in the current Scope.
|
||
|
|
// For each pair, it invokes the provided function with the key and value.
|
||
|
|
// The iteration stops if the function returns false.
|
||
|
|
//
|
||
|
|
// Parameters:
|
||
|
|
// - fnc: A function that receives each key-value pair and returns whether to continue iteration
|
||
|
|
func (s *Scope) Range(fnc func(key, value any) bool) {
|
||
|
|
s.entries.Range(func(key, value any) bool {
|
||
|
|
entry := value.(scopeEntry)
|
||
|
|
if entry.isValue {
|
||
|
|
return fnc(key, entry.value)
|
||
|
|
}
|
||
|
|
return fnc(key, entry.provider.Value())
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// Reset clears all key-value pairs from the current Scope.
|
||
|
|
// If the Scope is marked as read-only, the operation returns an error.
|
||
|
|
//
|
||
|
|
// Returns:
|
||
|
|
// - error: ErrReadOnlyScope if the scope is read-only, nil otherwise
|
||
|
|
func (s *Scope) Reset() error {
|
||
|
|
if s.isReadOnly {
|
||
|
|
return G.ErrReadOnlyScope // No modifications allowed if the Scope is read-only.
|
||
|
|
}
|
||
|
|
s.entries.Range(func(k, _ any) bool {
|
||
|
|
s.entries.Delete(k) // Delete each key-value pair.
|
||
|
|
return true
|
||
|
|
})
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// ScopeWithReadOnly returns a ScopeFnc that sets the scope to read-only mode.
|
||
|
|
// Once applied, the scope will not allow any modifications to its values.
|
||
|
|
//
|
||
|
|
// Returns:
|
||
|
|
// - ScopeFnc: A function that when called will mark the scope as read-only
|
||
|
|
//
|
||
|
|
// Example:
|
||
|
|
//
|
||
|
|
// scope, _ := NewScope(ScopeWithReadOnly())
|
||
|
|
// // scope is now read-only
|
||
|
|
func ScopeWithReadOnly() ScopeFnc {
|
||
|
|
return func(s *Scope) error {
|
||
|
|
s.isReadOnly = true
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ScopeWithValue returns a ScopeFnc that sets an initial key-value pair in the scope.
|
||
|
|
// This function is used during scope creation to initialize the scope with values.
|
||
|
|
//
|
||
|
|
// Parameters:
|
||
|
|
// - key: The key under which to store the value (can be of any type)
|
||
|
|
// - value: The value to store (can be of any type)
|
||
|
|
//
|
||
|
|
// Returns:
|
||
|
|
// - ScopeFnc: A function that when called will set the specified key-value pair
|
||
|
|
//
|
||
|
|
// Example:
|
||
|
|
//
|
||
|
|
// scope, _ := NewScope(ScopeWithValue("key", "value"))
|
||
|
|
// // scope now contains "key":"value"
|
||
|
|
func ScopeWithValue(key, value any) ScopeFnc {
|
||
|
|
return func(s *Scope) error {
|
||
|
|
return s.SetValue(key, value)
|
||
|
|
}
|
||
|
|
}
|