Skip to content

Commit 29668f5

Browse files
committed
lints and tests
Signed-off-by: Eddie Knight <knight@linux.com>
1 parent 7b037fb commit 29668f5

14 files changed

Lines changed: 387 additions & 73 deletions

control_catalog_yaml.go

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

document_example_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ func ExampleGuidanceCatalog() {
1515
---
1616
**Front Matter:** {{ .FrontMatter }}
1717
---
18-
{{- $doc := . }}{{ range .Families }}
18+
{{- $doc := . }}{{ range .Groups }}
1919
2020
### {{ .Title }} ({{ .Id }})
2121
{{ .Description }}
2222
#### Guidelines:
23-
{{- $familyId := .Id }}{{ range $doc.Guidelines }}{{ if eq .Family $familyId }}
23+
{{- $familyId := .Id }}{{ range $doc.Guidelines }}{{ if eq .Group $familyId }}
2424
2525
##### {{ .Title }} ({{ .Id }})
2626
**Objective:** {{ .Objective }}

enums.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ type RelationshipType int
3333
// MethodType enumerates the category of evaluation or enforcement method.
3434
type MethodType int
3535

36+
// ModeType enumerates whether enforcement/evaluation is manual or automated.
37+
type ModeType int
38+
39+
// Disposition enumerates the possible enforcement outcomes.
40+
type Disposition int
41+
42+
// EnforcementStep is a reference to the code path that performed an enforcement action.
43+
type EnforcementStep string
44+
3645
// Severity defines the allowed impact levels for a risk.
3746
type Severity int
3847

@@ -112,6 +121,17 @@ const (
112121
MethodGate
113122
)
114123

124+
const (
125+
ModeManual ModeType = iota
126+
ModeAutomated
127+
)
128+
129+
const (
130+
DispositionEnforced Disposition = iota
131+
DispositionTolerated
132+
DispositionClear
133+
)
134+
115135
const (
116136
SeverityLow Severity = iota
117137
SeverityMedium
@@ -276,6 +296,28 @@ var (
276296
"Gate": MethodGate,
277297
}
278298

299+
modeTypeToString = map[ModeType]string{
300+
ModeManual: "Manual",
301+
ModeAutomated: "Automated",
302+
}
303+
304+
stringToModeType = map[string]ModeType{
305+
"Manual": ModeManual,
306+
"Automated": ModeAutomated,
307+
}
308+
309+
dispositionToString = map[Disposition]string{
310+
DispositionEnforced: "Enforced",
311+
DispositionTolerated: "Tolerated",
312+
DispositionClear: "Clear",
313+
}
314+
315+
stringToDisposition = map[string]Disposition{
316+
"Enforced": DispositionEnforced,
317+
"Tolerated": DispositionTolerated,
318+
"Clear": DispositionClear,
319+
}
320+
279321
severityToString = map[Severity]string{
280322
SeverityLow: "Low",
281323
SeverityMedium: "Medium",
@@ -598,6 +640,60 @@ func (m *MethodType) UnmarshalJSON(data []byte) error {
598640
return unmarshalJSONEnum(data, stringToMethodType, "MethodType", m)
599641
}
600642

643+
func (m ModeType) String() string {
644+
if s, ok := modeTypeToString[m]; ok {
645+
return s
646+
}
647+
return fmt.Sprintf("ModeType(%d)", m)
648+
}
649+
650+
// MarshalYAML ensures that ModeType is serialized as a string in YAML
651+
func (m ModeType) MarshalYAML() (interface{}, error) {
652+
return marshalYAMLString(m)
653+
}
654+
655+
// MarshalJSON ensures that ModeType is serialized as a string in JSON
656+
func (m ModeType) MarshalJSON() ([]byte, error) {
657+
return marshalJSONString(m)
658+
}
659+
660+
// UnmarshalYAML ensures that ModeType can be deserialized from a YAML string
661+
func (m *ModeType) UnmarshalYAML(data []byte) error {
662+
return unmarshalYAMLEnum(data, stringToModeType, "ModeType", m)
663+
}
664+
665+
// UnmarshalJSON ensures that ModeType can be deserialized from a JSON string
666+
func (m *ModeType) UnmarshalJSON(data []byte) error {
667+
return unmarshalJSONEnum(data, stringToModeType, "ModeType", m)
668+
}
669+
670+
func (d Disposition) String() string {
671+
if s, ok := dispositionToString[d]; ok {
672+
return s
673+
}
674+
return fmt.Sprintf("Disposition(%d)", d)
675+
}
676+
677+
// MarshalYAML ensures that Disposition is serialized as a string in YAML
678+
func (d Disposition) MarshalYAML() (interface{}, error) {
679+
return marshalYAMLString(d)
680+
}
681+
682+
// MarshalJSON ensures that Disposition is serialized as a string in JSON
683+
func (d Disposition) MarshalJSON() ([]byte, error) {
684+
return marshalJSONString(d)
685+
}
686+
687+
// UnmarshalYAML ensures that Disposition can be deserialized from a YAML string
688+
func (d *Disposition) UnmarshalYAML(data []byte) error {
689+
return unmarshalYAMLEnum(data, stringToDisposition, "Disposition", d)
690+
}
691+
692+
// UnmarshalJSON ensures that Disposition can be deserialized from a JSON string
693+
func (d *Disposition) UnmarshalJSON(data []byte) error {
694+
return unmarshalJSONEnum(data, stringToDisposition, "Disposition", d)
695+
}
696+
601697
func (s Severity) String() string {
602698
if str, ok := severityToString[s]; ok {
603699
return str

enums_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,43 @@ func TestMethodTypeString(t *testing.T) {
232232
}
233233
}
234234

235+
func TestModeTypeString(t *testing.T) {
236+
tests := []struct {
237+
v ModeType
238+
expected string
239+
}{
240+
{ModeManual, "Manual"},
241+
{ModeAutomated, "Automated"},
242+
}
243+
244+
for _, tt := range tests {
245+
t.Run(tt.expected, func(t *testing.T) {
246+
if got := tt.v.String(); got != tt.expected {
247+
t.Errorf("String() = %q, want %q", got, tt.expected)
248+
}
249+
})
250+
}
251+
}
252+
253+
func TestDispositionString(t *testing.T) {
254+
tests := []struct {
255+
v Disposition
256+
expected string
257+
}{
258+
{DispositionEnforced, "Enforced"},
259+
{DispositionTolerated, "Tolerated"},
260+
{DispositionClear, "Clear"},
261+
}
262+
263+
for _, tt := range tests {
264+
t.Run(tt.expected, func(t *testing.T) {
265+
if got := tt.v.String(); got != tt.expected {
266+
t.Errorf("String() = %q, want %q", got, tt.expected)
267+
}
268+
})
269+
}
270+
}
271+
235272
func TestSeverityString(t *testing.T) {
236273
tests := []struct {
237274
v Severity

gemaraconv/catalog.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,19 @@ func CatalogToOSCAL(catalog *gemara.ControlCatalog, opts ...GenerateOption) (osc
3535
}
3636

3737
familyMap := make(map[string]gemara.Group)
38-
for _, family := range catalog.Families {
38+
for _, family := range catalog.Groups {
3939
familyMap[family.Id] = family
4040
}
4141

42-
controlsByFamily := make(map[string][]gemara.Control)
42+
controlsByGroup := make(map[string][]gemara.Control)
4343
for _, control := range catalog.Controls {
44-
controlsByFamily[control.Family] = append(controlsByFamily[control.Family], control)
44+
controlsByGroup[control.Group] = append(controlsByGroup[control.Group], control)
4545
}
4646

4747
catalogGroups := []oscal.Group{}
4848

49-
for _, family := range catalog.Families {
50-
controls := controlsByFamily[family.Id]
49+
for _, family := range catalog.Groups {
50+
controls := controlsByGroup[family.Id]
5151
if len(controls) == 0 {
5252
continue
5353
}

gemaraconv/catalog_test.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var testCases = []struct {
2525
Version: "devel",
2626
},
2727
Title: "Test Catalog",
28-
Families: []gemara.Group{
28+
Groups: []gemara.Group{
2929
{
3030
Id: "AC",
3131
Title: "access-control",
@@ -34,9 +34,9 @@ var testCases = []struct {
3434
},
3535
Controls: []gemara.Control{
3636
{
37-
Id: "AC-01",
38-
Family: "AC",
39-
Title: "Access Control Policy",
37+
Id: "AC-01",
38+
Group: "AC",
39+
Title: "Access Control Policy",
4040
AssessmentRequirements: []gemara.AssessmentRequirement{
4141
{
4242
Id: "AC-01.1",
@@ -58,7 +58,7 @@ var testCases = []struct {
5858
Version: "devel",
5959
},
6060
Title: "Test Catalog Multiple",
61-
Families: []gemara.Group{
61+
Groups: []gemara.Group{
6262
{
6363
Id: "AC",
6464
Title: "access-control",
@@ -72,9 +72,9 @@ var testCases = []struct {
7272
},
7373
Controls: []gemara.Control{
7474
{
75-
Id: "AC-01",
76-
Family: "AC",
77-
Title: "Access Control Policy",
75+
Id: "AC-01",
76+
Group: "AC",
77+
Title: "Access Control Policy",
7878
AssessmentRequirements: []gemara.AssessmentRequirement{
7979
{
8080
Id: "AC-01.1",
@@ -83,9 +83,9 @@ var testCases = []struct {
8383
},
8484
},
8585
{
86-
Id: "BR-01",
87-
Family: "BR",
88-
Title: "Business Requirements Policy",
86+
Id: "BR-01",
87+
Group: "BR",
88+
Title: "Business Requirements Policy",
8989
AssessmentRequirements: []gemara.AssessmentRequirement{
9090
{
9191
Id: "BR-01.1",
@@ -128,10 +128,10 @@ func TestFromCatalog(t *testing.T) {
128128
assert.NotEmpty(t, oscalCatalog.UUID)
129129
assert.Equal(t, tt.expectedTitle, oscalCatalog.Metadata.Title)
130130
assert.Equal(t, tt.catalog.Metadata.Version, oscalCatalog.Metadata.Version)
131-
assert.Equal(t, len(tt.catalog.Families), len(*oscalCatalog.Groups))
131+
assert.Equal(t, len(tt.catalog.Groups), len(*oscalCatalog.Groups))
132132

133133
// Compare each control family
134-
for i, family := range tt.catalog.Families {
134+
for i, family := range tt.catalog.Groups {
135135
groups := (*oscalCatalog.Groups)
136136
group := groups[i]
137137
assert.Equal(t, family.Id, group.ID)

gemaraconv/guidance.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func GuidanceToOSCAL(g *gemara.GuidanceCatalog, guidanceDocHref string, opts ...
3030

3131
// Create catalog
3232
// Return early for empty documents
33-
if len(g.Families) == 0 {
33+
if len(g.Groups) == 0 {
3434
return oscal.Catalog{}, oscal.Profile{}, fmt.Errorf("document %s does not have defined families", g.Metadata.Id)
3535
}
3636

@@ -53,18 +53,18 @@ func GuidanceToOSCAL(g *gemara.GuidanceCatalog, guidanceDocHref string, opts ...
5353
}
5454

5555
// Group guidelines by family
56-
guidelinesByFamily := make(map[string][]gemara.Guideline)
56+
guidelinesByGroup := make(map[string][]gemara.Guideline)
5757
for _, guideline := range g.Guidelines {
5858
// Skip guidelines that extend external controls - these belong only in the profile as alterations
5959
if guideline.Extends != nil && guideline.Extends.ReferenceId != "" {
6060
continue
6161
}
62-
guidelinesByFamily[guideline.Family] = append(guidelinesByFamily[guideline.Family], guideline)
62+
guidelinesByGroup[guideline.Group] = append(guidelinesByGroup[guideline.Group], guideline)
6363
}
6464

6565
var groups []oscal.Group
66-
for _, family := range g.Families {
67-
guidelines := guidelinesByFamily[family.Id]
66+
for _, family := range g.Groups {
67+
guidelines := guidelinesByGroup[family.Id]
6868
if len(guidelines) > 0 {
6969
groups = append(groups, createControlGroup(g, family, guidelines, resourcesMap))
7070
}

gemaraconv/sarif_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,16 +326,16 @@ func makeAssessmentLog(entryID, description string, result gemara.Result, messag
326326

327327
func makeCatalog(controlID, controlTitle, controlObjective, reqID, reqText, reqRecommendation string) *gemara.ControlCatalog {
328328
return &gemara.ControlCatalog{
329-
Families: []gemara.Group{
329+
Groups: []gemara.Group{
330330
{
331331
Id: "test-family",
332-
Title: "Test Family",
332+
Title: "Test Group",
333333
},
334334
},
335335
Controls: []gemara.Control{
336336
{
337337
Id: controlID,
338-
Family: "test-family",
338+
Group: "test-family",
339339
Title: controlTitle,
340340
Objective: controlObjective,
341341
AssessmentRequirements: []gemara.AssessmentRequirement{

0 commit comments

Comments
 (0)