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

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)
}
}

Powered by TurnKey Linux.