From fda57574599c930eaabb18ab6050ff5258a1707a Mon Sep 17 00:00:00 2001 From: K Date: Wed, 3 Mar 2021 10:30:25 -0800 Subject: [PATCH 1/2] Fill out results struct --- models/result.go | 169 ++++++++++++++++++++++++++++++++------ models/run.go | 61 ++++++++------ models/tool.go | 6 +- test/result_stage_test.go | 3 +- 4 files changed, 183 insertions(+), 56 deletions(-) diff --git a/models/result.go b/models/result.go index dab8f51..a9ac386 100644 --- a/models/result.go +++ b/models/result.go @@ -2,69 +2,186 @@ package models // Result represents the results block in the sarif report type Result struct { - Level string `json:"level"` - Message *textBlock `json:"message"` - RuleID string `json:"ruleId"` - RuleIndex int `json:"ruleIndex"` - Locations []*resultLocation `json:"locations,omitempty"` + Guid *string `json:"guid,omitempty"` + CorrelationGuid *string `json:"correlationGuid,omitempty"` + RuleID *string `json:"ruleId,omitempty"` + RuleIndex *uint `json:"ruleIndex,omitempty"` + Rule *reportingDescriptorReference `json:"rule,omitempty"` + Taxa []*reportingDescriptorReference `json:"taxa,omitempty"` + Kind *string `json:"kind,omitempty"` + Level *string `json:"level,omitempty"` + Message Message `json:"message"` + Locations []*location `json:"locations,omitempty"` + AnalysisTarget *artifactLocation `json:"analysisTarget,omitempty"` + // WebRequest *webRequest `json:"webRequest,omitempty"` + // WebResponse *webResponse `json:"webResponse,omitempty"` + Fingerprints map[string]interface{} `json:"fingerprints,omitempty"` + PartialFingerprints map[string]interface{} `json:"partialFingerprints,omitempty"` + // CodeFlows []*codeFlows `json:"codeFlows,omitempty"` + // Graphs []*graphs `json:"graphs,omitempty"` + // GraphTraversals []*graphTraversals `json:"graphTraversals,omitempty"` + // Stacks []*stack `json:"stacks,omitempty"` + RelatedLocations []*location `json:"relatedLocations,omitempty"` + Suppressions []*suppression `json:"suppressions,omitempty"` + BaselineState *string `json:"baselineState,omitempty"` + Rank *float32 `json:"rank,omitempty"` + // Attachments []*attachment `json:"attachments,omitempty"` + WorkItemUris []string `json:"workItemUris,omitempty"` // can be null + HostedViewerUri *string `json:"hostedViewerUri,omitempty"` + // Provenance *resultProvenance `json:"provenance,omitempty"` + Fixes []*fix `json:"fixes,omitempty"` + OccurrenceCount *uint `json:"occurrenceCount,omitempty"` } -type resultLocation struct { - PhysicalLocation *physicalLocation `json:"physicalLocation,omitempty"` +type reportingDescriptorReference struct { + Id *string `json:"id,omitempty"` + Index *uint `json:"index,omitempty"` + Guid *string `json:"guid,omitempty"` + ToolComponent *toolComponentReference `json:"toolComponent,omitempty"` +} + +type toolComponentReference struct { + Name *string `json:"name"` + Index *uint `json:"index"` + Guid *string `json:"guid"` +} + +type Message struct { + Text *string `json:"text,omitempty"` + Markdown *string `json:"markdown,omitempty"` + Id *string `json:"id,omitempty"` + Arguments []string `json:"arguments,omitempty"` +} + +type location struct { + Id *uint `json:"id,omitempty"` + PhysicalLocation *physicalLocation `json:"physicalLocation,omitempty"` + LogicalLocations []*logicalLocation `json:"logicalLocations,omitempty"` + Message *Message `json:"message,omitempty"` + Annotations []*region `json:"annotations,omitempty"` + Relationships []*locationRelationship `json:"relationships,omitempty"` } type physicalLocation struct { - ArtifactLocation *artifactLocation `json:"artifactLocation"` - Region *region `json:"region"` + ArtifactLocation *artifactLocation `json:"artifactLocation,omitempty"` + Region *region `json:"region,omitempty"` + ContextRegion *region `json:"contextRegion,omitempty"` + Address *address `json:"address,omitempty"` +} + +type logicalLocation struct { + Index *uint `json:"index,omitempty"` + Name *string `json:"name,omitempty"` + FullyQualifiedName *string `json:"fullyQualifiedName,omitempty"` + DecoratedName *string `json:"decoratedName,omitempty"` + Kind *string `json:"kind,omitempty"` + ParentIndex *uint `json:"parentIndex,omitempty"` +} + +type locationRelationship struct { + Target uint `json:"target"` + Kinds []string `json:"kinds,omitempty"` + Description *Message `json:"description,omitempty"` } type region struct { - StartLine int `json:"startLine"` - StartColumn int `json:"startColumn"` + StartLine *int `json:"startLine,omitempty"` + StartColumn *int `json:"startColumn,omitempty"` + EndLine *int `json:"endLine,omitempty"` + EndColumn *int `json:"endColumn,omitempty"` + CharOffset *int `json:"charOffset,omitempty"` + CharLength *int `json:"charLength,omitempty"` + ByteOffset *int `json:"byteOffset,omitempty"` + ByteLength *int `json:"byteLength,omitempty"` + Snippet *artifactContent `json:"snippet,omitempty"` + Message *Message `json:"message,omitempty"` + SourceLanguage *string `json:"sourceLanguage,omitempty"` +} + +type artifactContent struct { + Text *string `json:"text,omitempty"` + Binary *string `json:"binary,omitempty"` + Rendered *multiformatMessageString `json:"rendered,omitempty"` +} + +type multiformatMessageString struct { + Text string `json:"text"` + Markdown *string `json:"markdown,omitempty"` +} + +type address struct { + Index *uint `json:"index,omitempty"` + AbsoluteAddress *uint `json:"absoluteAddress,omitempty"` + RelativeAddress *int `json:"relativeAddress,omitempty"` + OffsetFromParent *int `json:"offsetFromParent,omitempty"` + Length *int `json:"length,omitempty"` + Name *string `json:"name,omitempty"` + FullyQualifiedName *string `json:"fullyQualifiedName,omitempty"` + Kind *string `json:"kind,omitempty"` + ParentIndex *uint `json:"parentIndex,omitempty"` } type artifactLocation struct { - URI string `json:"uri"` - Index int `json:"index"` + URI *string `json:"uri,omitempty"` + URIBaseId *string `json:"uriBaseId,omitempty"` + Index *uint `json:"index,omitempty"` + Description *Message `json:"description,omitempty"` } -type location struct { - URI string `json:"uri"` +type suppression struct { + Kind string `json:"kind"` + Status *string `json:"status"` + Location *location `json:"location"` + Guid *string `json:"guid"` + Justification *string `json:"justification"` +} + +type fix struct { + Description *Message `json:"description,omitempty"` + ArtifactChanges []*artifactChange `json:"artifactChanges"` // required +} + +type artifactChange struct { + ArtifactLocation artifactLocation `json:"artifactLocation"` + Replacements []*replacement `json:"replacements"` //required +} + +type replacement struct { + DeletedRegion region `json:"deletedRegion"` + InsertedContent *artifactContent `json:"insertedContent,omitempty"` } func newRuleResult(ruleID string) *Result { return &Result{ - RuleID: ruleID, + RuleID: &ruleID, } } // WithLevel specifies the level of the finding, error, warning for a result and returns the updated result func (result *Result) WithLevel(level string) *Result { - result.Level = level + result.Level = &level return result } // WithMessage specifies the message for a result and returns the updated result func (result *Result) WithMessage(message string) *Result { - result.Message = &textBlock{ - Text: message, - } + result.Message.Text = &message return result } // WithLocationDetails specifies the location details of the Result and returns the update result func (result *Result) WithLocationDetails(path string, startLine, startColumn int) *Result { - location := &physicalLocation{ + physicalLocation := &physicalLocation{ ArtifactLocation: &artifactLocation{ - URI: path, + URI: &path, }, Region: ®ion{ - StartLine: startLine, - StartColumn: startColumn, + StartLine: &startLine, + StartColumn: &startColumn, }, } - result.Locations = append(result.Locations, &resultLocation{ - PhysicalLocation: location, + result.Locations = append(result.Locations, &location{ + PhysicalLocation: physicalLocation, }) return result } diff --git a/models/run.go b/models/run.go index 2fe7451..9386be9 100644 --- a/models/run.go +++ b/models/run.go @@ -6,43 +6,52 @@ import ( // Run type represents a run of a tool type Run struct { - Tool *tool `json:"tool"` - Artifacts []*LocationWrapper `json:"artifacts,omitempty"` - Results []*Result `json:"results,omitempty"` + Tool tool `json:"tool"` + Artifacts []*artifact `json:"artifacts,omitempty"` + Results []*Result `json:"results"` // can be null } -// LocationWrapper reprents the location details of a run -type LocationWrapper struct { - Location *location `json:"location,omitempty"` +type artifact struct { + Location *artifactLocation `json:"location,omitempty"` + ParentIndex *uint `json:"parentIndex,omitempty"` + Offset *uint `json:"offset"` + Length *uint `json:"length"` + Roles []string `json:"roles"` + MimeType *string `json:"mimeType"` + Contents *artifactContent `json:"contents"` + Encoding *string `json:"encoding"` + SourceLanguage *string `json:"sourceLanguage"` + Hashes map[string]string `json:"hashes"` + LastModifiedTimeUtc *string `json:"lastModifiedTimeUtc"` + Description *Message `json:"description"` } // NewRun allows the creation of a new Run func NewRun(toolName, informationURI string) *Run { - tool := &tool{ - Driver: &driver{ - Name: toolName, - InformationURI: informationURI, - }, - } run := &Run{ - Tool: tool, + Tool: tool{ + Driver: &driver{ + Name: toolName, + InformationURI: informationURI, + }, + }, } return run } // AddArtifact returns the index of an existing artefact, the newly added artifactLocation -func (run *Run) AddArtifact(artifactLocation string) int { +func (run *Run) AddArtifact(uri string) uint { for i, l := range run.Artifacts { - if l.Location.URI == artifactLocation { - return i + if *l.Location.URI == uri { + return uint(i) } } - run.Artifacts = append(run.Artifacts, &LocationWrapper{ - Location: &location{ - URI: artifactLocation, + run.Artifacts = append(run.Artifacts, &artifact{ + Location: &artifactLocation{ + URI: &uri, }, }) - return len(run.Artifacts) - 1 + return uint(len(run.Artifacts) - 1) } // AddRule returns an existing Rule for the ruleID or creates a new Rule and returns it @@ -60,7 +69,7 @@ func (run *Run) AddRule(ruleID string) *Rule { // AddResult returns an existing Result or creates a new one and returns it func (run *Run) AddResult(ruleID string) *Result { for _, result := range run.Results { - if result.RuleID == ruleID { + if *result.RuleID == ruleID { return result } } @@ -72,22 +81,22 @@ func (run *Run) AddResult(ruleID string) *Result { // AddResultDetails adds rules to the driver and artifact locations if they are missing. It adds the result to the result block as well func (run *Run) AddResultDetails(rule *Rule, result *Result, location string) { ruleIndex := run.Tool.Driver.getOrCreateRule(rule) - result.RuleIndex = ruleIndex + result.RuleIndex = &ruleIndex locationIndex := run.AddArtifact(location) updateResultLocationIndex(result, location, locationIndex) } -func updateResultLocationIndex(result *Result, location string, index int) { +func updateResultLocationIndex(result *Result, location string, index uint) { for _, resultLocation := range result.Locations { - if resultLocation.PhysicalLocation.ArtifactLocation.URI == location { - resultLocation.PhysicalLocation.ArtifactLocation.Index = index + if *resultLocation.PhysicalLocation.ArtifactLocation.URI == location { + resultLocation.PhysicalLocation.ArtifactLocation.Index = &index break } } } func (run *Run) GetRuleById(ruleId string) (*Rule, error) { - if run.Tool != nil || run.Tool.Driver != nil { + if run.Tool.Driver != nil { for _, rule := range run.Tool.Driver.Rules { if rule.ID == ruleId { return rule, nil diff --git a/models/tool.go b/models/tool.go index 1412cb7..9cb40e6 100644 --- a/models/tool.go +++ b/models/tool.go @@ -19,14 +19,14 @@ type Rule struct { Properties map[string]string `json:"properties,omitempty"` } -func (driver *driver) getOrCreateRule(rule *Rule) int { +func (driver *driver) getOrCreateRule(rule *Rule) uint { for i, r := range driver.Rules { if r.ID == rule.ID { - return i + return uint(i) } } driver.Rules = append(driver.Rules, rule) - return len(driver.Rules) - 1 + return uint(len(driver.Rules) - 1) } func newRule(ruleID string) *Rule { diff --git a/test/result_stage_test.go b/test/result_stage_test.go index ff808cd..b22a7e7 100644 --- a/test/result_stage_test.go +++ b/test/result_stage_test.go @@ -22,8 +22,9 @@ func createNewResultTest(t *testing.T) (*resultTest, *resultTest, *resultTest) { } func (rt *resultTest) aNewResult() { + id := "test-rule" rt.result = &models.Result{ - RuleID: "test-rule", + RuleID: &id, } rt.result.WithLevel("error"). From 38b8007522b89d3224ae2cbb0c0a748452e293ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C2=A8Owen=20Rumney?= Date: Wed, 3 Mar 2021 21:41:43 +0000 Subject: [PATCH 2/2] Start working through new attributes - start working through new attributes - start thinking about the fluent API for creating the report - reference some of the spec --- models/artifact.go | 72 ++++++++++++++++++ models/location.go | 33 ++++++++ models/message.go | 29 +++++++ models/region.go | 29 +++++++ models/result.go | 166 +++++++++++++++-------------------------- models/run.go | 25 ++----- test/result_test.go | 4 +- test/run_stage_test.go | 2 +- test/run_test.go | 6 +- 9 files changed, 237 insertions(+), 129 deletions(-) create mode 100644 models/artifact.go create mode 100644 models/location.go create mode 100644 models/message.go create mode 100644 models/region.go diff --git a/models/artifact.go b/models/artifact.go new file mode 100644 index 0000000..2000534 --- /dev/null +++ b/models/artifact.go @@ -0,0 +1,72 @@ +package models + +type artifact struct { // https://docs.oasis-open.org/sarif/sarif/v2.1.0/csprd01/sarif-v2.1.0-csprd01.html#_Toc10541049 + Location *artifactLocation `json:"location,omitempty"` + ParentIndex *uint `json:"parentIndex,omitempty"` + Offset *uint `json:"offset,omitempty"` + Length int `json:"length"` + Roles []string `json:"roles,omitempty"` + MimeType *string `json:"mimeType,omitempty"` + Contents *artifactContent `json:"contents,omitempty"` + Encoding *string `json:"encoding,omitempty"` + SourceLanguage *string `json:"sourceLanguage,omitempty"` + Hashes map[string]string `json:"hashes,omitempty"` + LastModifiedTimeUtc *string `json:"lastModifiedTimeUtc,omitempty"` + Description *Message `json:"description,omitempty"` +} + +type artifactLocation struct { // https://docs.oasis-open.org/sarif/sarif/v2.1.0/csprd01/sarif-v2.1.0-csprd01.html#_Toc10540865 + URI *string `json:"uri,omitempty"` + URIBaseId *string `json:"uriBaseId,omitempty"` + Index *uint `json:"index,omitempty"` + Description *Message `json:"description,omitempty"` +} + +type ArtifactLocationBuilder struct { + artifactLocation *artifactLocation +} + +func (alb *ArtifactLocationBuilder) WithUri(uri string) *ArtifactLocationBuilder { + alb.artifactLocation.URI = &uri + return alb +} + +func (alb *ArtifactLocationBuilder) WithIndex(index uint) *ArtifactLocationBuilder { + alb.artifactLocation.Index = &index + return alb +} + +func (alb *ArtifactLocationBuilder) WithUriBaseId(uriBaseId string) *ArtifactLocationBuilder { + alb.artifactLocation.URIBaseId = &uriBaseId + return alb +} + +func (alb *ArtifactLocationBuilder) WithDescription(messageBuilder MessageBuilder) *ArtifactLocationBuilder { + alb.artifactLocation.Description = messageBuilder.Get() + return alb +} + +type artifactContent struct { // https://docs.oasis-open.org/sarif/sarif/v2.1.0/csprd01/sarif-v2.1.0-csprd01.html#_Toc10540860 + Text *string `json:"text,omitempty"` + Binary *string `json:"binary,omitempty"` + Rendered *multiformatMessageString `json:"rendered,omitempty"` +} + +type ArtifactBuilder struct { + run *Run + artifact *artifact +} + +func (run *Run) NewArtifactBuilder() *ArtifactBuilder { + return &ArtifactBuilder{ + run: run, + artifact: &artifact{ + Length: -1, + }, + } +} + +func (ab *ArtifactBuilder) Add() *Run { + ab.run.Artifacts = append(ab.run.Artifacts, ab.artifact) + return ab.run +} diff --git a/models/location.go b/models/location.go new file mode 100644 index 0000000..ff4c496 --- /dev/null +++ b/models/location.go @@ -0,0 +1,33 @@ +package models + +type location struct { + Id *uint `json:"id,omitempty"` + PhysicalLocation *physicalLocation `json:"physicalLocation,omitempty"` + LogicalLocations []*logicalLocation `json:"logicalLocations,omitempty"` + Message *Message `json:"message,omitempty"` + Annotations []*region `json:"annotations,omitempty"` + Relationships []*locationRelationship `json:"relationships,omitempty"` +} + +type physicalLocation struct { + ArtifactLocation *artifactLocation `json:"artifactLocation,omitempty"` + Region *region `json:"region,omitempty"` + ContextRegion *region `json:"contextRegion,omitempty"` + Address *address `json:"address,omitempty"` +} + +type logicalLocation struct { + Index *uint `json:"index,omitempty"` + Name *string `json:"name,omitempty"` + FullyQualifiedName *string `json:"fullyQualifiedName,omitempty"` + DecoratedName *string `json:"decoratedName,omitempty"` + Kind *string `json:"kind,omitempty"` + ParentIndex *uint `json:"parentIndex,omitempty"` +} + +type locationRelationship struct { + Target uint `json:"target"` + Kinds []string `json:"kinds,omitempty"` + Description *Message `json:"description,omitempty"` +} + diff --git a/models/message.go b/models/message.go new file mode 100644 index 0000000..db914d0 --- /dev/null +++ b/models/message.go @@ -0,0 +1,29 @@ +package models + +type MessageBuilder struct { + message *Message +} + +func (m *MessageBuilder) WithText(text string) *MessageBuilder { + m.message.Text = &text + return m +} + +func (m *MessageBuilder) WithMarkdown(markdown string) *MessageBuilder { + m.message.Markdown = &markdown + return m +} + +func (m *MessageBuilder) WithId(id string) *MessageBuilder { + m.message.Id = &id + return m +} + +func (m *MessageBuilder) WithArguments(args []string) *MessageBuilder { + m.message.Arguments = args + return m +} + +func (m *MessageBuilder) Get() *Message { + return m.message +} diff --git a/models/region.go b/models/region.go new file mode 100644 index 0000000..7c33ab1 --- /dev/null +++ b/models/region.go @@ -0,0 +1,29 @@ +package models + +type region struct { // https://docs.oasis-open.org/sarif/sarif/v2.1.0/csprd01/sarif-v2.1.0-csprd01.html#_Toc10541123 + StartLine *int `json:"startLine,omitempty"` + StartColumn *int `json:"startColumn,omitempty"` + EndLine *int `json:"endLine,omitempty"` + EndColumn *int `json:"endColumn,omitempty"` + CharOffset *int `json:"charOffset,omitempty"` + CharLength *int `json:"charLength,omitempty"` + ByteOffset *int `json:"byteOffset,omitempty"` + ByteLength *int `json:"byteLength,omitempty"` + Snippet *artifactContent `json:"snippet,omitempty"` + Message *Message `json:"message,omitempty"` + SourceLanguage *string `json:"sourceLanguage,omitempty"` +} + +type RegionBuilder struct { + region *region +} + +func NewRegionBuilder() *RegionBuilder { + return &RegionBuilder{ + region: ®ion{}, + } +} + +func (rb *RegionBuilder) Get() *region { + return rb.region +} diff --git a/models/result.go b/models/result.go index a9ac386..95cda4c 100644 --- a/models/result.go +++ b/models/result.go @@ -2,152 +2,102 @@ package models // Result represents the results block in the sarif report type Result struct { - Guid *string `json:"guid,omitempty"` - CorrelationGuid *string `json:"correlationGuid,omitempty"` - RuleID *string `json:"ruleId,omitempty"` - RuleIndex *uint `json:"ruleIndex,omitempty"` - Rule *reportingDescriptorReference `json:"rule,omitempty"` - Taxa []*reportingDescriptorReference `json:"taxa,omitempty"` - Kind *string `json:"kind,omitempty"` - Level *string `json:"level,omitempty"` - Message Message `json:"message"` - Locations []*location `json:"locations,omitempty"` - AnalysisTarget *artifactLocation `json:"analysisTarget,omitempty"` + Guid *string `json:"guid,omitempty"` + CorrelationGuid *string `json:"correlationGuid,omitempty"` + RuleID *string `json:"ruleId,omitempty"` + RuleIndex *uint `json:"ruleIndex,omitempty"` + Rule *reportingDescriptorReference `json:"rule,omitempty"` + Taxa []*reportingDescriptorReference `json:"taxa,omitempty"` + Kind *string `json:"kind,omitempty"` + Level *string `json:"level,omitempty"` + Message Message `json:"message"` + Locations []*location `json:"locations,omitempty"` + AnalysisTarget *artifactLocation `json:"analysisTarget,omitempty"` // WebRequest *webRequest `json:"webRequest,omitempty"` // WebResponse *webResponse `json:"webResponse,omitempty"` - Fingerprints map[string]interface{} `json:"fingerprints,omitempty"` - PartialFingerprints map[string]interface{} `json:"partialFingerprints,omitempty"` + Fingerprints map[string]interface{} `json:"fingerprints,omitempty"` + PartialFingerprints map[string]interface{} `json:"partialFingerprints,omitempty"` // CodeFlows []*codeFlows `json:"codeFlows,omitempty"` // Graphs []*graphs `json:"graphs,omitempty"` // GraphTraversals []*graphTraversals `json:"graphTraversals,omitempty"` // Stacks []*stack `json:"stacks,omitempty"` - RelatedLocations []*location `json:"relatedLocations,omitempty"` - Suppressions []*suppression `json:"suppressions,omitempty"` - BaselineState *string `json:"baselineState,omitempty"` - Rank *float32 `json:"rank,omitempty"` + RelatedLocations []*location `json:"relatedLocations,omitempty"` + Suppressions []*suppression `json:"suppressions,omitempty"` + BaselineState *string `json:"baselineState,omitempty"` + Rank *float32 `json:"rank,omitempty"` // Attachments []*attachment `json:"attachments,omitempty"` - WorkItemUris []string `json:"workItemUris,omitempty"` // can be null - HostedViewerUri *string `json:"hostedViewerUri,omitempty"` + WorkItemUris []string `json:"workItemUris,omitempty"` // can be null + HostedViewerUri *string `json:"hostedViewerUri,omitempty"` // Provenance *resultProvenance `json:"provenance,omitempty"` - Fixes []*fix `json:"fixes,omitempty"` - OccurrenceCount *uint `json:"occurrenceCount,omitempty"` + Fixes []*fix `json:"fixes,omitempty"` + OccurrenceCount *uint `json:"occurrenceCount,omitempty"` } type reportingDescriptorReference struct { - Id *string `json:"id,omitempty"` - Index *uint `json:"index,omitempty"` - Guid *string `json:"guid,omitempty"` - ToolComponent *toolComponentReference `json:"toolComponent,omitempty"` + Id *string `json:"id,omitempty"` + Index *uint `json:"index,omitempty"` + Guid *string `json:"guid,omitempty"` + ToolComponent *toolComponentReference `json:"toolComponent,omitempty"` } type toolComponentReference struct { - Name *string `json:"name"` - Index *uint `json:"index"` - Guid *string `json:"guid"` + Name *string `json:"name"` + Index *uint `json:"index"` + Guid *string `json:"guid"` } -type Message struct { - Text *string `json:"text,omitempty"` - Markdown *string `json:"markdown,omitempty"` - Id *string `json:"id,omitempty"` - Arguments []string `json:"arguments,omitempty"` +type Message struct { // https://docs.oasis-open.org/sarif/sarif/v2.1.0/csprd01/sarif-v2.1.0-csprd01.html#_Toc10540897 + Text *string `json:"text,omitempty"` + Markdown *string `json:"markdown,omitempty"` + Id *string `json:"id,omitempty"` + Arguments []string `json:"arguments,omitempty"` } -type location struct { - Id *uint `json:"id,omitempty"` - PhysicalLocation *physicalLocation `json:"physicalLocation,omitempty"` - LogicalLocations []*logicalLocation `json:"logicalLocations,omitempty"` - Message *Message `json:"message,omitempty"` - Annotations []*region `json:"annotations,omitempty"` - Relationships []*locationRelationship `json:"relationships,omitempty"` -} -type physicalLocation struct { - ArtifactLocation *artifactLocation `json:"artifactLocation,omitempty"` - Region *region `json:"region,omitempty"` - ContextRegion *region `json:"contextRegion,omitempty"` - Address *address `json:"address,omitempty"` -} -type logicalLocation struct { - Index *uint `json:"index,omitempty"` - Name *string `json:"name,omitempty"` - FullyQualifiedName *string `json:"fullyQualifiedName,omitempty"` - DecoratedName *string `json:"decoratedName,omitempty"` - Kind *string `json:"kind,omitempty"` - ParentIndex *uint `json:"parentIndex,omitempty"` -} -type locationRelationship struct { - Target uint `json:"target"` - Kinds []string `json:"kinds,omitempty"` - Description *Message `json:"description,omitempty"` -} -type region struct { - StartLine *int `json:"startLine,omitempty"` - StartColumn *int `json:"startColumn,omitempty"` - EndLine *int `json:"endLine,omitempty"` - EndColumn *int `json:"endColumn,omitempty"` - CharOffset *int `json:"charOffset,omitempty"` - CharLength *int `json:"charLength,omitempty"` - ByteOffset *int `json:"byteOffset,omitempty"` - ByteLength *int `json:"byteLength,omitempty"` - Snippet *artifactContent `json:"snippet,omitempty"` - Message *Message `json:"message,omitempty"` - SourceLanguage *string `json:"sourceLanguage,omitempty"` -} -type artifactContent struct { - Text *string `json:"text,omitempty"` - Binary *string `json:"binary,omitempty"` - Rendered *multiformatMessageString `json:"rendered,omitempty"` -} type multiformatMessageString struct { - Text string `json:"text"` - Markdown *string `json:"markdown,omitempty"` + Text string `json:"text"` + Markdown *string `json:"markdown,omitempty"` } type address struct { - Index *uint `json:"index,omitempty"` - AbsoluteAddress *uint `json:"absoluteAddress,omitempty"` - RelativeAddress *int `json:"relativeAddress,omitempty"` - OffsetFromParent *int `json:"offsetFromParent,omitempty"` - Length *int `json:"length,omitempty"` - Name *string `json:"name,omitempty"` - FullyQualifiedName *string `json:"fullyQualifiedName,omitempty"` - Kind *string `json:"kind,omitempty"` - ParentIndex *uint `json:"parentIndex,omitempty"` + Index *uint `json:"index,omitempty"` + AbsoluteAddress *uint `json:"absoluteAddress,omitempty"` + RelativeAddress *int `json:"relativeAddress,omitempty"` + OffsetFromParent *int `json:"offsetFromParent,omitempty"` + Length *int `json:"length,omitempty"` + Name *string `json:"name,omitempty"` + FullyQualifiedName *string `json:"fullyQualifiedName,omitempty"` + Kind *string `json:"kind,omitempty"` + ParentIndex *uint `json:"parentIndex,omitempty"` } -type artifactLocation struct { - URI *string `json:"uri,omitempty"` - URIBaseId *string `json:"uriBaseId,omitempty"` - Index *uint `json:"index,omitempty"` - Description *Message `json:"description,omitempty"` -} + type suppression struct { - Kind string `json:"kind"` - Status *string `json:"status"` - Location *location `json:"location"` - Guid *string `json:"guid"` - Justification *string `json:"justification"` + Kind string `json:"kind"` + Status *string `json:"status"` + Location *location `json:"location"` + Guid *string `json:"guid"` + Justification *string `json:"justification"` } type fix struct { - Description *Message `json:"description,omitempty"` - ArtifactChanges []*artifactChange `json:"artifactChanges"` // required + Description *Message `json:"description,omitempty"` + ArtifactChanges []*artifactChange `json:"artifactChanges"` // required } type artifactChange struct { - ArtifactLocation artifactLocation `json:"artifactLocation"` - Replacements []*replacement `json:"replacements"` //required + ArtifactLocation artifactLocation `json:"artifactLocation"` + Replacements []*replacement `json:"replacements"` //required } type replacement struct { - DeletedRegion region `json:"deletedRegion"` + DeletedRegion region `json:"deletedRegion"` InsertedContent *artifactContent `json:"insertedContent,omitempty"` } @@ -169,6 +119,12 @@ func (result *Result) WithMessage(message string) *Result { return result } +func (result *Result) NewMessageBuilder() *MessageBuilder { + return &MessageBuilder{ + message: &result.Message, + } +} + // WithLocationDetails specifies the location details of the Result and returns the update result func (result *Result) WithLocationDetails(path string, startLine, startColumn int) *Result { physicalLocation := &physicalLocation{ diff --git a/models/run.go b/models/run.go index 9386be9..068e5b6 100644 --- a/models/run.go +++ b/models/run.go @@ -5,26 +5,13 @@ import ( ) // Run type represents a run of a tool -type Run struct { - Tool tool `json:"tool"` - Artifacts []*artifact `json:"artifacts,omitempty"` - Results []*Result `json:"results"` // can be null +type Run struct { // https://docs.oasis-open.org/sarif/sarif/v2.1.0/csprd01/sarif-v2.1.0-csprd01.html#_Toc10540922 + Tool tool `json:"tool"` + Artifacts []*artifact `json:"artifacts,omitempty"` + Results []*Result `json:"results,omitempty"` // can be null } -type artifact struct { - Location *artifactLocation `json:"location,omitempty"` - ParentIndex *uint `json:"parentIndex,omitempty"` - Offset *uint `json:"offset"` - Length *uint `json:"length"` - Roles []string `json:"roles"` - MimeType *string `json:"mimeType"` - Contents *artifactContent `json:"contents"` - Encoding *string `json:"encoding"` - SourceLanguage *string `json:"sourceLanguage"` - Hashes map[string]string `json:"hashes"` - LastModifiedTimeUtc *string `json:"lastModifiedTimeUtc"` - Description *Message `json:"description"` -} + // NewRun allows the creation of a new Run func NewRun(toolName, informationURI string) *Run { @@ -54,6 +41,8 @@ func (run *Run) AddArtifact(uri string) uint { return uint(len(run.Artifacts) - 1) } + + // AddRule returns an existing Rule for the ruleID or creates a new Rule and returns it func (run *Run) AddRule(ruleID string) *Rule { for _, rule := range run.Tool.Driver.Rules { diff --git a/test/result_test.go b/test/result_test.go index 8802653..e547873 100644 --- a/test/result_test.go +++ b/test/result_test.go @@ -7,7 +7,7 @@ import ( func Test_a_new_result_is_created_as_expected(t *testing.T) { given, when, then := createNewResultTest(t) - expected := `{"level":"error","message":{"text":"there was an error"},"ruleId":"test-rule","ruleIndex":0}` + expected := `{"ruleId":"test-rule","level":"error","message":{"text":"there was an error"}}` given.aNewResult() when.theResultIsDisplayedConvertedAString() @@ -17,7 +17,7 @@ func Test_a_new_result_is_created_as_expected(t *testing.T) { func Test_a_new_result_is_created_with_a_location(t *testing.T) { given, when, then := createNewResultTest(t) - expected := `{"level":"error","message":{"text":"there was an error"},"ruleId":"test-rule","ruleIndex":0,"locations":[{"physicalLocation":{"artifactLocation":{"uri":"/tmp/code/location","index":0},"region":{"startLine":1,"startColumn":1}}}]}` + expected := `{"ruleId":"test-rule","level":"error","message":{"text":"there was an error"},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"/tmp/code/location"},"region":{"startLine":1,"startColumn":1}}}]}` given.aNewResult() when.theResultHasALocationAdded(). diff --git a/test/run_stage_test.go b/test/run_stage_test.go index f3f2dbb..ce07ff5 100644 --- a/test/run_stage_test.go +++ b/test/run_stage_test.go @@ -47,7 +47,7 @@ func (rt *runTest) and() *runTest { return rt } -func (rt *runTest) theIndexOfLocationIs(locationURI string, expectedIndex int) *runTest { +func (rt *runTest) theIndexOfLocationIs(locationURI string, expectedIndex uint) *runTest { locationIndex := rt.run.AddArtifact(locationURI) assert.Equal(rt.t, expectedIndex, locationIndex) return rt diff --git a/test/run_test.go b/test/run_test.go index 047f43b..a9b27fc 100644 --- a/test/run_test.go +++ b/test/run_test.go @@ -17,7 +17,7 @@ func Test_create_new_empty_run_looks_as_expected(t *testing.T) { func Test_create_run_with_artifact_as_expected(t *testing.T) { given, when, then := createNewRunTest(t) - expected := `{"tool":{"driver":{"name":"tfsec","informationUri":"https://tfsec.dev"}},"artifacts":[{"location":{"uri":"/tmp/code/location"}}]}` + expected := `{"tool":{"driver":{"name":"tfsec","informationUri":"https://tfsec.dev"}},"artifacts":[{"location":{"uri":"/tmp/code/location"},"length":0}]}` given.aNewRunIsCreated() when.anArtifactIsAddedToTheRun("/tmp/code/location"). @@ -42,7 +42,7 @@ func Test_getting_the_location_index_for_an_existing_run(t *testing.T) { func Test_create_a_run_with_a_result_added(t *testing.T) { given, when, then := createNewRunTest(t) - expected := `{"tool":{"driver":{"name":"tfsec","informationUri":"https://tfsec.dev","rules":[{"id":"AWS001","shortDescription":{"text":"S3 Bucket has an ACL defined which allows public access."},"helpUri":"https://www.tfsec.dev/docs/aws/AWS001","properties":{"propertyName":"propertyValue"}}]}},"artifacts":[{"location":{"uri":"/tmp/result/code"}}],"results":[{"level":"error","message":{"text":"Resource 'my_bucket' has an ACL which allows public access."},"ruleId":"AWS001","ruleIndex":0,"locations":[{"physicalLocation":{"artifactLocation":{"uri":"/tmp/result/code","index":0},"region":{"startLine":1,"startColumn":1}}}]}]}` + expected := `{"tool":{"driver":{"name":"tfsec","informationUri":"https://tfsec.dev","rules":[{"id":"AWS001","shortDescription":{"text":"S3 Bucket has an ACL defined which allows public access."},"helpUri":"https://www.tfsec.dev/docs/aws/AWS001","properties":{"propertyName":"propertyValue"}}]}},"artifacts":[{"location":{"uri":"/tmp/result/code"},"length":0}],"results":[{"ruleId":"AWS001","ruleIndex":0,"level":"error","message":{"text":"Resource 'my_bucket' has an ACL which allows public access."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"/tmp/result/code","index":0},"region":{"startLine":1,"startColumn":1}}}]}]}` given.aNewRunIsCreated() when.aResultIsAddedToTheRun().and(). @@ -53,7 +53,7 @@ func Test_create_a_run_with_a_result_added(t *testing.T) { func Test_create_a_run_with_a_result_added_and_help_text_provided(t *testing.T) { given, when, then := createNewRunTest(t) - expected := `{"tool":{"driver":{"name":"tfsec","informationUri":"https://tfsec.dev","rules":[{"id":"AWS001","shortDescription":{"text":"S3 Bucket has an ACL defined which allows public access."},"help":{"text":"you can learn more about this check https://www.tfsec.dev/docs/aws/AWS001"},"properties":{"propertyName":"propertyValue"}}]}},"artifacts":[{"location":{"uri":"/tmp/result/code"}}],"results":[{"level":"error","message":{"text":"Resource 'my_bucket' has an ACL which allows public access."},"ruleId":"AWS001","ruleIndex":0,"locations":[{"physicalLocation":{"artifactLocation":{"uri":"/tmp/result/code","index":0},"region":{"startLine":1,"startColumn":1}}}]}]}` + expected := `{"tool":{"driver":{"name":"tfsec","informationUri":"https://tfsec.dev","rules":[{"id":"AWS001","shortDescription":{"text":"S3 Bucket has an ACL defined which allows public access."},"help":{"text":"you can learn more about this check https://www.tfsec.dev/docs/aws/AWS001"},"properties":{"propertyName":"propertyValue"}}]}},"artifacts":[{"location":{"uri":"/tmp/result/code"},"length":0}],"results":[{"ruleId":"AWS001","ruleIndex":0,"level":"error","message":{"text":"Resource 'my_bucket' has an ACL which allows public access."},"locations":[{"physicalLocation":{"artifactLocation":{"uri":"/tmp/result/code","index":0},"region":{"startLine":1,"startColumn":1}}}]}]}` given.aNewRunIsCreated() when.aResultIsAddedToTheRunWithHelpText().and().