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