@@ -16,12 +16,13 @@ import (
1616
1717// WidgetRegistry holds loaded widget definitions keyed by uppercase MDL name.
1818type WidgetRegistry struct {
19- byMDLName map [string ]* WidgetDefinition // keyed by uppercase MDLName
20- byWidgetID map [string ]* WidgetDefinition // keyed by widgetId
19+ byMDLName map [string ]* WidgetDefinition // keyed by uppercase MDLName
20+ byWidgetID map [string ]* WidgetDefinition // keyed by widgetId
21+ knownOperations map [string ]bool // operations accepted during validation
2122}
2223
23- // knownOperations is the set of operation names supported by the widget engine.
24- var knownOperations = map [string ]bool {
24+ // defaultKnownOperations is the set of operation names supported by the widget engine.
25+ var defaultKnownOperations = map [string ]bool {
2526 "attribute" : true ,
2627 "association" : true ,
2728 "primitive" : true ,
@@ -34,11 +35,37 @@ var knownOperations = map[string]bool{
3435 "attributeObjects" : true ,
3536}
3637
38+ // knownOperations is the active set used for validation, initialized from
39+ // defaultKnownOperations and now stored per-registry to avoid global mutable state.
40+
41+ func copyOps (src map [string ]bool ) map [string ]bool {
42+ dst := make (map [string ]bool , len (src ))
43+ for k , v := range src {
44+ dst [k ] = v
45+ }
46+ return dst
47+ }
48+
3749// NewWidgetRegistry creates a registry pre-loaded with embedded definitions.
50+ // Uses the default set of known operations for validation.
3851func NewWidgetRegistry () (* WidgetRegistry , error ) {
52+ return NewWidgetRegistryWithOps (nil )
53+ }
54+
55+ // NewWidgetRegistryWithOps creates a registry pre-loaded with embedded definitions,
56+ // extending the default known operations with extraOps for validation.
57+ // This allows user-defined widgets to declare custom operations that would otherwise
58+ // fail validation. Pass nil for the default set.
59+ func NewWidgetRegistryWithOps (extraOps map [string ]bool ) (* WidgetRegistry , error ) {
60+ ops := copyOps (defaultKnownOperations )
61+ for op := range extraOps {
62+ ops [op ] = true
63+ }
64+
3965 reg := & WidgetRegistry {
40- byMDLName : make (map [string ]* WidgetDefinition ),
41- byWidgetID : make (map [string ]* WidgetDefinition ),
66+ byMDLName : make (map [string ]* WidgetDefinition ),
67+ byWidgetID : make (map [string ]* WidgetDefinition ),
68+ knownOperations : ops ,
4269 }
4370
4471 entries , err := definitions .EmbeddedFS .ReadDir ("." )
@@ -61,7 +88,7 @@ func NewWidgetRegistry() (*WidgetRegistry, error) {
6188 return nil , mdlerrors .NewBackend (fmt .Sprintf ("parse definition %s" , entry .Name ()), err )
6289 }
6390
64- if err := validateDefinitionOperations (& def , entry .Name ()); err != nil {
91+ if err := reg . validateDefinitionOperations (& def , entry .Name ()); err != nil {
6592 return nil , err
6693 }
6794
@@ -156,7 +183,7 @@ func (r *WidgetRegistry) loadDefinitionsFromDir(dir string) error {
156183 return mdlerrors .NewValidationf ("invalid definition %s: widgetId and mdlName are required" , entry .Name ())
157184 }
158185
159- if err := validateDefinitionOperations (& def , entry .Name ()); err != nil {
186+ if err := r . validateDefinitionOperations (& def , entry .Name ()); err != nil {
160187 return err
161188 }
162189
@@ -180,22 +207,22 @@ func (r *WidgetRegistry) loadDefinitionsFromDir(dir string) error {
180207// validateDefinitionOperations checks that all operation names in a definition
181208// are recognized by the known operations set, and validates source/operation
182209// compatibility and mapping order dependencies.
183- func validateDefinitionOperations (def * WidgetDefinition , source string ) error {
184- if err := validateMappings (def .PropertyMappings , source , "" ); err != nil {
210+ func ( r * WidgetRegistry ) validateDefinitionOperations (def * WidgetDefinition , source string ) error {
211+ if err := r . validateMappings (def .PropertyMappings , source , "" ); err != nil {
185212 return err
186213 }
187214 for _ , s := range def .ChildSlots {
188- if ! knownOperations [s .Operation ] {
215+ if ! r . knownOperations [s .Operation ] {
189216 return mdlerrors .NewValidationf ("%s: unknown operation %q in childSlots for key %q" , source , s .Operation , s .PropertyKey )
190217 }
191218 }
192219 for _ , mode := range def .Modes {
193220 ctx := fmt .Sprintf ("mode %q " , mode .Name )
194- if err := validateMappings (mode .PropertyMappings , source , ctx ); err != nil {
221+ if err := r . validateMappings (mode .PropertyMappings , source , ctx ); err != nil {
195222 return err
196223 }
197224 for _ , s := range mode .ChildSlots {
198- if ! knownOperations [s .Operation ] {
225+ if ! r . knownOperations [s .Operation ] {
199226 return mdlerrors .NewValidationf ("%s: unknown operation %q in %schildSlots for key %q" , source , s .Operation , ctx , s .PropertyKey )
200227 }
201228 }
@@ -213,10 +240,10 @@ var incompatibleSourceOps = map[string]map[string]bool{
213240
214241// validateMappings validates a slice of property mappings for operation existence,
215242// source/operation compatibility, and mapping order (Association requires prior DataSource).
216- func validateMappings (mappings []PropertyMapping , source , modeCtx string ) error {
243+ func ( r * WidgetRegistry ) validateMappings (mappings []PropertyMapping , source , modeCtx string ) error {
217244 hasDataSource := false
218245 for _ , m := range mappings {
219- if ! knownOperations [m .Operation ] {
246+ if ! r . knownOperations [m .Operation ] {
220247 return mdlerrors .NewValidationf ("%s: unknown operation %q in %spropertyMappings for key %q" , source , m .Operation , modeCtx , m .PropertyKey )
221248 }
222249 // Check source/operation compatibility
0 commit comments