extensions.gno
4.37 Kb · 143 lines
1package daokit
2
3// Extensions system enables DAOs to expose additional functionality that can be
4// accessed by other packages or realms. It provides a secure way to make
5// specific DAO capabilities available without exposing internal implementation details.
6
7import "gno.land/p/samcrew/avl"
8
9// Interface must be implemented by any object that wants to be
10// registered as a DAO extension. Extensions expose functionality through
11// well-defined interfaces while maintaining encapsulation.
12type Extension interface {
13 // Returns metadata about this extension including its path, version,
14 // query path for external access, and privacy settings.
15 Info() ExtensionInfo
16}
17
18type ExtensionInfo struct {
19 Path string // Unique extension identifier (e.g., "gno.land/p/samcrew/basedao.MembersView")
20 Version string // Extension version (e.g., "1", "2.0", etc.)
21 QueryPath string // Path for external queries to access this extension's data
22 // If true, only the registering realm can obtain the extension VALUE
23 // through DAO.Extension — which is a capability, not a secret. Realm state
24 // on a public chain is world-readable over ABCI regardless, and this
25 // ExtensionInfo (including QueryPath) is listed by ExtensionsList without a
26 // check. Private restricts who can call the extension's methods under the
27 // DAO's authority; it does not hide anything.
28 Private bool
29}
30
31type ExtensionsList interface {
32 // Returns the total number of registered extensions.
33 Len() int
34 // Returns extension info at the specified index, or nil if index is out of bounds.
35 Get(index int) *ExtensionInfo
36 // Returns a slice of ExtensionInfo from startIndex to endIndex.
37 Slice(startIndex, endIndex int) []ExtensionInfo
38 // Iterates over all extensions, calling fn for each one.
39 // Iteration stops if fn returns true.
40 ForEach(fn func(index int, value ExtensionInfo) bool)
41}
42
43type ExtensionsStore struct {
44 Tree avl.Tree
45}
46
47// Registers a new extension using its path as the key.
48// Returns true if the extension was successfully registered.
49// If an extension with the same path already exists, it will be replaced.
50func (es *ExtensionsStore) Set(ext Extension) bool {
51 info := ext.Info()
52 return es.Tree.Set(info.Path, ext)
53}
54
55func (es *ExtensionsStore) Get(path string) (Extension, bool) {
56 iface, ok := es.Tree.Get(path)
57 if !ok {
58 return nil, false
59 }
60 return iface.(Extension), true
61}
62
63// Remove unregisters an extension by its path.
64// Returns the removed extension and true if found, nil and false otherwise.
65func (es *ExtensionsStore) Remove(path string) (Extension, bool) {
66 iface, ok := es.Tree.Remove(path)
67 if !ok {
68 return nil, false
69 }
70 return iface.(Extension), true
71}
72
73// Returns an ExtensionsList interface for iterating over all registered extensions.
74// This provides read-only access to the extensions registry.
75func (es *ExtensionsStore) List() ExtensionsList {
76 return &extensionsList{es: es}
77}
78
79type extensionsList struct {
80 es *ExtensionsStore
81}
82
83// Iterates over all extensions, calling fn for each extension with its index and info.
84// Iteration stops early if fn returns true.
85func (e *extensionsList) ForEach(fn func(index int, value ExtensionInfo) bool) {
86 if e.es.Tree.Size() == 0 {
87 return
88 }
89
90 index := 0
91 e.es.Tree.IterateByOffset(0, e.es.Tree.Size(), func(_ string, value any) bool {
92 result := fn(index, value.(Extension).Info())
93 index++
94 return result
95 })
96}
97
98// Returns the ExtensionInfo at the specified index.
99func (e *extensionsList) Get(index int) *ExtensionInfo {
100 if index < 0 || index >= e.es.Tree.Size() {
101 return nil
102 }
103
104 _, value := e.es.Tree.GetByIndex(index)
105 info := value.(Extension).Info()
106 return &info
107}
108
109// Returns the total number of registered extensions.
110func (e *extensionsList) Len() int {
111 return e.es.Tree.Size()
112}
113
114// Returns a slice of ExtensionInfo from startIndex (inclusive) to endIndex (exclusive).
115// Bounds are automatically normalized: negative startIndex becomes 0, endIndex beyond size becomes size.
116func (e *extensionsList) Slice(startIndex int, endIndex int) []ExtensionInfo {
117 size := e.es.Tree.Size()
118
119 // Normalize bounds
120 if startIndex < 0 {
121 startIndex = 0
122 }
123
124 if endIndex > size {
125 endIndex = size
126 }
127
128 if startIndex >= endIndex {
129 return nil
130 }
131
132 count := endIndex - startIndex
133 result := make([]ExtensionInfo, count)
134
135 i := 0
136 e.es.Tree.IterateByOffset(startIndex, count, func(_ string, value any) bool {
137 result[i] = value.(Extension).Info()
138 i++
139 return false
140 })
141
142 return result
143}