|
1 | | -package gemara |
2 | | - |
3 | | -import ( |
4 | | - "slices" |
5 | | - |
6 | | - "github.com/goccy/go-yaml" |
7 | | -) |
8 | | - |
9 | | -// ControlCatalog describes a set of related controls and relevant metadata |
10 | | -type ControlCatalog struct { |
11 | | - // title describes the purpose of this catalog at a glance |
12 | | - Title string `json:"title" yaml:"title"` |
13 | | - |
14 | | - // metadata provides detailed data about this catalog |
15 | | - Metadata Metadata `json:"metadata" yaml:"metadata"` |
16 | | - |
17 | | - // controls is a list of unique controls defined by this catalog |
18 | | - Controls []Control `json:"controls,omitempty" yaml:"controls,omitempty"` |
19 | | - |
20 | | - // groups contains a list of groups that can be referenced by entries in this catalog |
21 | | - Groups []Group `json:"groups,omitempty" yaml:"groups,omitempty"` |
22 | | - |
23 | | - // extends references catalogs that this catalog builds upon |
24 | | - Extends []ArtifactMapping `json:"extends,omitempty" yaml:"extends,omitempty"` |
| 1 | +// SPDX-License-Identifier: Apache-2.0 |
25 | 2 |
|
26 | | - Imports []MultiEntryMapping `json:"imports,omitempty" yaml:"imports,omitempty"` |
27 | | - |
28 | | - groups_cache []string |
29 | | - controls_cache map[string][]Control |
30 | | - requirements_cache map[string][]AssessmentRequirement |
31 | | -} |
32 | | - |
33 | | -// UnmarshalYAML allows decoding control catalogs from older/alternate YAML schemas. |
34 | | -// It supports mapping `families` -> `groups`. |
35 | | -func (c *ControlCatalog) UnmarshalYAML(data []byte) error { |
36 | | - type controlCatalogYAML struct { |
37 | | - Groups []Group `yaml:"groups,omitempty"` |
38 | | - Families []Group `yaml:"families,omitempty"` |
39 | | - |
40 | | - Title string `yaml:"title"` |
41 | | - Metadata Metadata `yaml:"metadata"` |
42 | | - |
43 | | - Extends []ArtifactMapping `yaml:"extends,omitempty"` |
44 | | - Imports []MultiEntryMapping `yaml:"imports,omitempty"` |
45 | | - |
46 | | - Controls []Control `yaml:"controls,omitempty"` |
47 | | - } |
| 3 | +package gemara |
48 | 4 |
|
49 | | - var tmp controlCatalogYAML |
50 | | - if err := yaml.Unmarshal(data, &tmp); err != nil { |
51 | | - return err |
52 | | - } |
| 5 | +import "sync" |
53 | 6 |
|
54 | | - c.Groups = tmp.Groups |
55 | | - if len(c.Groups) == 0 { |
56 | | - c.Groups = tmp.Families |
57 | | - } |
58 | | - c.Controls = tmp.Controls |
| 7 | +// SugarControlCatalog wraps the generated ControlCatalog with |
| 8 | +// pre-built indexes for efficient group, control, and requirement lookups. |
| 9 | +type SugarControlCatalog struct { |
| 10 | + ControlCatalog |
59 | 11 |
|
60 | | - c.Title = tmp.Title |
61 | | - c.Metadata = tmp.Metadata |
62 | | - c.Extends = tmp.Extends |
| 12 | + groupsOnce sync.Once |
| 13 | + groupsCache []string |
63 | 14 |
|
64 | | - // Keep imports exactly as decoded (nil vs empty can matter to tests). |
65 | | - c.Imports = tmp.Imports |
| 15 | + controlsOnce sync.Once |
| 16 | + controlsCache map[string][]Control |
66 | 17 |
|
67 | | - return nil |
| 18 | + requirementsOnce sync.Once |
| 19 | + requirementsCache map[string][]AssessmentRequirement |
68 | 20 | } |
69 | 21 |
|
70 | | -func (c *ControlCatalog) GetGroupNames() (groups []string) { |
71 | | - if len(c.groups_cache) > 0 { |
72 | | - return c.groups_cache |
73 | | - } |
74 | | - for _, group := range c.Groups { |
75 | | - groups = append(groups, group.Title) |
76 | | - } |
77 | | - return groups |
| 22 | +func (c *SugarControlCatalog) GetGroupNames() []string { |
| 23 | + c.groupsOnce.Do(func() { |
| 24 | + for _, group := range c.Groups { |
| 25 | + c.groupsCache = append(c.groupsCache, group.Title) |
| 26 | + } |
| 27 | + }) |
| 28 | + return c.groupsCache |
78 | 29 | } |
79 | 30 |
|
80 | | -func (c *ControlCatalog) GetControlsForGroup(group string) (controls []Control) { |
81 | | - if c.controls_cache != nil && len(c.controls_cache[group]) > 0 { |
82 | | - return c.controls_cache[group] |
83 | | - } |
84 | | - for _, control := range c.Controls { |
85 | | - if control.Group == group { |
86 | | - controls = append(controls, control) |
| 31 | +func (c *SugarControlCatalog) GetControlsForGroup(group string) []Control { |
| 32 | + c.controlsOnce.Do(func() { |
| 33 | + c.controlsCache = make(map[string][]Control) |
| 34 | + for _, control := range c.Controls { |
| 35 | + c.controlsCache[control.Group] = append( |
| 36 | + c.controlsCache[control.Group], control, |
| 37 | + ) |
87 | 38 | } |
88 | | - } |
89 | | - return controls |
| 39 | + }) |
| 40 | + return c.controlsCache[group] |
90 | 41 | } |
91 | 42 |
|
92 | | -func (c *ControlCatalog) GetRequirementForApplicability(applicability string) (reqs []AssessmentRequirement) { |
93 | | - if c.requirements_cache != nil && len(c.requirements_cache[applicability]) > 0 { |
94 | | - return c.requirements_cache[applicability] |
95 | | - } |
96 | | - for _, control := range c.Controls { |
97 | | - for _, assessment := range control.AssessmentRequirements { |
98 | | - if slices.Contains(assessment.Applicability, applicability) { |
99 | | - reqs = append(reqs, assessment) |
| 43 | +func (c *SugarControlCatalog) GetRequirementForApplicability(applicability string) []AssessmentRequirement { |
| 44 | + c.requirementsOnce.Do(func() { |
| 45 | + c.requirementsCache = make(map[string][]AssessmentRequirement) |
| 46 | + for _, control := range c.Controls { |
| 47 | + for _, req := range control.AssessmentRequirements { |
| 48 | + for _, app := range req.Applicability { |
| 49 | + c.requirementsCache[app] = append( |
| 50 | + c.requirementsCache[app], req, |
| 51 | + ) |
| 52 | + } |
100 | 53 | } |
101 | 54 | } |
102 | | - } |
103 | | - return reqs |
| 55 | + }) |
| 56 | + return c.requirementsCache[applicability] |
104 | 57 | } |
0 commit comments