Skip to content

Commit c0976ef

Browse files
committed
feat: embed generated types into "sugared" types
Signed-off-by: Jennifer Power <barnabei.jennifer@gmail.com>
1 parent fdbad59 commit c0976ef

5 files changed

Lines changed: 197 additions & 171 deletions

File tree

control.go

Lines changed: 19 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,26 @@
1-
package gemara
2-
3-
import "github.com/goccy/go-yaml"
4-
5-
// Control describes a safeguard or countermeasure with a clear objective and assessment requirements
6-
type Control struct {
7-
// id allows this entry to be referenced by other elements
8-
Id string `json:"id" yaml:"id"`
9-
10-
// title describes the purpose of this control at a glance
11-
Title string `json:"title" yaml:"title"`
12-
13-
// objective is a unified statement of intent, which may encompass multiple situationally applicable requirements
14-
Objective string `json:"objective" yaml:"objective"`
15-
16-
// group references by id a catalog group that this control belongs to
17-
Group string `json:"group" yaml:"group"`
18-
19-
// assessment-requirements is a list of requirements that must be verified to confirm the control objective has been met
20-
AssessmentRequirements []AssessmentRequirement `json:"assessment-requirements" yaml:"assessment-requirements"`
21-
22-
// guidelines documents relationships between this control and Layer 1 guideline artifacts
23-
Guidelines []MultiEntryMapping `json:"guidelines,omitempty" yaml:"guidelines,omitempty"`
24-
25-
// threats documents relationships between this control and Layer 2 threat artifacts
26-
Threats []MultiEntryMapping `json:"threats,omitempty" yaml:"threats,omitempty"`
1+
// SPDX-License-Identifier: Apache-2.0
272

28-
// state is the lifecycle state of this control
29-
State Lifecycle `json:"state" yaml:"state"`
30-
31-
// replaced-by references the control that supersedes this one when deprecated or retired
32-
ReplacedBy *EntryMapping `json:"replaced-by,omitempty" yaml:"replaced-by,omitempty"`
33-
34-
references_cache []string
35-
}
36-
37-
// UnmarshalYAML allows decoding controls from older/alternate YAML schemas.
38-
// In particular, it supports using `family` instead of the struct's `group` key.
39-
func (c *Control) UnmarshalYAML(data []byte) error {
40-
type controlYAML struct {
41-
Id string `yaml:"id"`
42-
Title string `yaml:"title"`
43-
Objective string `yaml:"objective"`
44-
Group string `yaml:"group,omitempty"`
45-
Family string `yaml:"family,omitempty"`
46-
47-
AssessmentRequirements []AssessmentRequirement `yaml:"assessment-requirements,omitempty"`
48-
49-
Guidelines []MultiEntryMapping `yaml:"guidelines,omitempty"`
50-
Threats []MultiEntryMapping `yaml:"threats,omitempty"`
51-
52-
State Lifecycle `yaml:"state"`
53-
ReplacedBy *EntryMapping `yaml:"replaced-by,omitempty"`
54-
}
55-
56-
var tmp controlYAML
57-
if err := yaml.Unmarshal(data, &tmp); err != nil {
58-
return err
59-
}
3+
package gemara
604

61-
c.Id = tmp.Id
62-
c.Title = tmp.Title
63-
c.Objective = tmp.Objective
64-
if tmp.Group != "" {
65-
c.Group = tmp.Group
66-
} else {
67-
c.Group = tmp.Family
68-
}
5+
import "sync"
696

70-
c.AssessmentRequirements = tmp.AssessmentRequirements
71-
c.Guidelines = tmp.Guidelines
72-
c.Threats = tmp.Threats
73-
c.State = tmp.State
74-
c.ReplacedBy = tmp.ReplacedBy
7+
// SugarControl wraps the generated Control with cached
8+
// cross-reference lookups.
9+
type SugarControl struct {
10+
Control
7511

76-
return nil
12+
referencesOnce sync.Once
13+
referencesCache []string
7714
}
7815

79-
func (c *Control) GetMappingReferences() (refs []string) {
80-
if len(c.references_cache) > 0 {
81-
return c.references_cache
82-
}
83-
for _, ref := range c.Guidelines {
84-
refs = append(refs, ref.ReferenceId)
85-
}
86-
for _, ref := range c.Threats {
87-
refs = append(refs, ref.ReferenceId)
88-
}
89-
return refs
16+
func (c *SugarControl) GetMappingReferences() []string {
17+
c.referencesOnce.Do(func() {
18+
for _, ref := range c.Guidelines {
19+
c.referencesCache = append(c.referencesCache, ref.ReferenceId)
20+
}
21+
for _, ref := range c.Threats {
22+
c.referencesCache = append(c.referencesCache, ref.ReferenceId)
23+
}
24+
})
25+
return c.referencesCache
9026
}

control_catalog.go

Lines changed: 41 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -1,104 +1,57 @@
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
252

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
484

49-
var tmp controlCatalogYAML
50-
if err := yaml.Unmarshal(data, &tmp); err != nil {
51-
return err
52-
}
5+
import "sync"
536

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
5911

60-
c.Title = tmp.Title
61-
c.Metadata = tmp.Metadata
62-
c.Extends = tmp.Extends
12+
groupsOnce sync.Once
13+
groupsCache []string
6314

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
6617

67-
return nil
18+
requirementsOnce sync.Once
19+
requirementsCache map[string][]AssessmentRequirement
6820
}
6921

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
7829
}
7930

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+
)
8738
}
88-
}
89-
return controls
39+
})
40+
return c.controlsCache[group]
9041
}
9142

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+
}
10053
}
10154
}
102-
}
103-
return reqs
55+
})
56+
return c.requirementsCache[applicability]
10457
}

control_catalog_yaml.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package gemara
4+
5+
import "github.com/goccy/go-yaml"
6+
7+
// UnmarshalYAML allows decoding control catalogs from older/alternate YAML schemas.
8+
// It supports mapping `families` -> `groups`.
9+
func (c *ControlCatalog) UnmarshalYAML(data []byte) error {
10+
type controlCatalogYAML struct {
11+
Groups []Group `yaml:"groups,omitempty"`
12+
Families []Group `yaml:"families,omitempty"`
13+
14+
Title string `yaml:"title"`
15+
Metadata Metadata `yaml:"metadata"`
16+
17+
Extends []ArtifactMapping `yaml:"extends,omitempty"`
18+
Imports []MultiEntryMapping `yaml:"imports,omitempty"`
19+
20+
Controls []Control `yaml:"controls,omitempty"`
21+
}
22+
23+
var tmp controlCatalogYAML
24+
if err := yaml.Unmarshal(data, &tmp); err != nil {
25+
return err
26+
}
27+
28+
c.Groups = tmp.Groups
29+
if len(c.Groups) == 0 {
30+
c.Groups = tmp.Families
31+
}
32+
c.Controls = tmp.Controls
33+
34+
c.Title = tmp.Title
35+
c.Metadata = tmp.Metadata
36+
c.Extends = tmp.Extends
37+
c.Imports = tmp.Imports
38+
39+
return nil
40+
}

control_yaml.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
package gemara
4+
5+
import "github.com/goccy/go-yaml"
6+
7+
// UnmarshalYAML allows decoding controls from older/alternate YAML schemas.
8+
// In particular, it supports using `family` instead of the struct's `group` key.
9+
func (c *Control) UnmarshalYAML(data []byte) error {
10+
type controlYAML struct {
11+
Id string `yaml:"id"`
12+
Title string `yaml:"title"`
13+
Objective string `yaml:"objective"`
14+
Group string `yaml:"group,omitempty"`
15+
Family string `yaml:"family,omitempty"`
16+
17+
AssessmentRequirements []AssessmentRequirement `yaml:"assessment-requirements,omitempty"`
18+
19+
Guidelines []MultiEntryMapping `yaml:"guidelines,omitempty"`
20+
Threats []MultiEntryMapping `yaml:"threats,omitempty"`
21+
22+
State Lifecycle `yaml:"state"`
23+
ReplacedBy *EntryMapping `yaml:"replaced-by,omitempty"`
24+
}
25+
26+
var tmp controlYAML
27+
if err := yaml.Unmarshal(data, &tmp); err != nil {
28+
return err
29+
}
30+
31+
c.Id = tmp.Id
32+
c.Title = tmp.Title
33+
c.Objective = tmp.Objective
34+
if tmp.Group != "" {
35+
c.Group = tmp.Group
36+
} else {
37+
c.Group = tmp.Family
38+
}
39+
40+
c.AssessmentRequirements = tmp.AssessmentRequirements
41+
c.Guidelines = tmp.Guidelines
42+
c.Threats = tmp.Threats
43+
c.State = tmp.State
44+
c.ReplacedBy = tmp.ReplacedBy
45+
46+
return nil
47+
}

0 commit comments

Comments
 (0)