Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,53 @@ jobs:
- name: Test
run: go test -v -race ./...

- name: Test TinyGo validation fallback
run: go test -tags=tinygo ./...

- name: Build WebAssembly
run: GOOS=js GOARCH=wasm go build ./...

- name: Build WASI
run: GOOS=wasip1 GOARCH=wasm go build ./...

tinygo:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Go
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod

- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: '24'
package-manager-cache: false

- name: Install TinyGo and Wasmtime
working-directory: ${{ runner.temp }}
run: |
curl --fail --location --silent --show-error --output tinygo.tar.gz https://github.com/tinygo-org/tinygo/releases/download/v0.42.0/tinygo0.42.0.linux-amd64.tar.gz
echo 'b87688fa2e19cee7d813cad7fd7dadb71dff3198e47125aba66ba4af5e490438 tinygo.tar.gz' | sha256sum --check
tar -xzf tinygo.tar.gz
echo "$RUNNER_TEMP/tinygo/bin" >> "$GITHUB_PATH"
curl --fail --location --silent --show-error --output wasmtime.tar.xz https://github.com/bytecodealliance/wasmtime/releases/download/v44.0.1/wasmtime-v44.0.1-x86_64-linux.tar.xz
echo 'afd58715f105e3a7f454169daed22168c5736ec5f225fb04c4ac62c54c9508a3 wasmtime.tar.xz' | sha256sum --check
tar -xJf wasmtime.tar.xz
echo "$RUNNER_TEMP/wasmtime-v44.0.1-x86_64-linux" >> "$GITHUB_PATH"

- name: Test TinyGo WebAssembly SARIF round trip and validation fallback
run: tinygo test -target=wasm -run '^TestTinyGoValidationUnsupported$' -v .

- name: Test TinyGo WASI SARIF round trip and validation fallback
run: tinygo test -target=wasip1 -run '^TestTinyGoValidationUnsupported$' -v .

lint:
runs-on: ubuntu-latest
steps:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ for _, run := range log.Runs {

`Validate` checks a `*sarif.Log` against the bundled SARIF 2.1.0 JSON schema using `github.com/santhosh-tekuri/jsonschema/v6`.

Under TinyGo, parsing, writing, and generated defaults remain available. Schema validation is unsupported: `Validate` returns `errors.ErrUnsupported`, `Valid` returns false, and `Schema` is unavailable.

```go
if sarif.Valid(log) {
// log is valid SARIF 2.1.0
Expand Down
69 changes: 1 addition & 68 deletions sarif.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,11 @@
package sarif

import (
"bytes"
"embed"
"encoding/json"
"fmt"
"io"
"os"
"reflect"
"sync"

"github.com/santhosh-tekuri/jsonschema/v6"
)

//go:embed schema/sarif-schema-2.1.0.json
var schemaFS embed.FS

const schemaPath = "schema/sarif-schema-2.1.0.json"

var (
compiledSchema *jsonschema.Schema
compiledSchemaErr error
compiledSchemaOnce sync.Once
)

// Load reads a SARIF log from path.
Expand Down Expand Up @@ -62,30 +46,8 @@ func Dump(log *Log, w io.Writer, pretty bool) error {
return nil
}

// Validate validates a SARIF log against the bundled SARIF 2.1.0 schema.
func Validate(log *Log) error {
schema, err := Schema()
if err != nil {
return err
}

data, err := json.Marshal(log)
if err != nil {
return fmt.Errorf("validate sarif: %w", err)
}

value, err := jsonschema.UnmarshalJSON(bytes.NewReader(data))
if err != nil {
return fmt.Errorf("validate sarif: %w", err)
}

if err := schema.Validate(value); err != nil {
return fmt.Errorf("validate sarif: %w", err)
}
return nil
}

// Valid reports whether log validates against the bundled SARIF 2.1.0 schema.
// It returns false under TinyGo, where validation is unsupported.
func Valid(log *Log) bool {
return Validate(log) == nil
}
Expand Down Expand Up @@ -114,32 +76,3 @@ func includeNonDefault(value any, defaultValue any) bool {
}
return !reflect.DeepEqual(value, defaultValue)
}

// Schema returns the compiled bundled SARIF 2.1.0 JSON schema.
func Schema() (*jsonschema.Schema, error) {
compiledSchemaOnce.Do(func() {
data, err := schemaFS.ReadFile(schemaPath)
if err != nil {
compiledSchemaErr = fmt.Errorf("compile sarif schema: %w", err)
return
}

compiler := jsonschema.NewCompiler()
compiler.DefaultDraft(jsonschema.Draft7)
doc, err := jsonschema.UnmarshalJSON(bytes.NewReader(data))
if err != nil {
compiledSchemaErr = fmt.Errorf("compile sarif schema: %w", err)
return
}
if err := compiler.AddResource(schemaPath, doc); err != nil {
compiledSchemaErr = fmt.Errorf("compile sarif schema: %w", err)
return
}

compiledSchema, compiledSchemaErr = compiler.Compile(schemaPath)
if compiledSchemaErr != nil {
compiledSchemaErr = fmt.Errorf("compile sarif schema: %w", compiledSchemaErr)
}
})
return compiledSchema, compiledSchemaErr
}
103 changes: 0 additions & 103 deletions sarif_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,6 @@ import (
"testing"
)

func TestMinimalLogValidates(t *testing.T) {
log := &Log{
Version: "2.1.0",
Runs: []Run{},
}

if err := Validate(log); err != nil {
t.Fatalf("Validate() error = %v", err)
}
}

func TestParseDumpRoundTrip(t *testing.T) {
log, err := Parse([]byte(`{"version":"2.1.0","runs":[]}`))
if err != nil {
Expand Down Expand Up @@ -150,95 +139,3 @@ func TestLoad(t *testing.T) {
t.Fatalf("Version = %q, want 2.1.0", log.Version)
}
}

func TestResultLogValidates(t *testing.T) {
artifactLocation := NewArtifactLocation()
artifactLocation.URI = "src/main.go"
region := NewRegion()
region.StartLine = 10
region.StartColumn = 5
location := NewLocation()
location.PhysicalLocation = PhysicalLocation{
ArtifactLocation: artifactLocation,
Region: region,
}
result := NewResult()
result.RuleID = "no-unused-vars"
result.RuleIndex = 0
result.Level = "warning"
result.Message = Message{Text: "Variable 'x' is unused"}
result.Locations = []Location{location}
defaultConfiguration := NewReportingConfiguration()
defaultConfiguration.Level = "error"

log := &Log{
Version: "2.1.0",
Runs: []Run{
{
Tool: Tool{
Driver: ToolComponent{
Name: "test-linter",
Version: "1.0.0",
Rules: []ReportingDescriptor{
{
ID: "no-unused-vars",
Name: "NoUnusedVars",
ShortDescription: MultiformatMessageString{
Text: "Disallow unused variables",
},
DefaultConfiguration: defaultConfiguration,
},
},
},
},
Results: []Result{result},
},
},
}

if err := Validate(log); err != nil {
t.Fatalf("Validate() error = %v", err)
}

data, err := Marshal(log, false)
if err != nil {
t.Fatalf("Marshal() error = %v", err)
}
if !strings.Contains(string(data), `"ruleIndex":0`) {
t.Fatalf("Marshal() omitted meaningful zero ruleIndex: %s", data)
}
if strings.Contains(string(data), `null`) {
t.Fatalf("Marshal() emitted null default fields: %s", data)
}
for _, unexpected := range []string{`"enabled":`, `"rank":`, `"index":`} {
if strings.Contains(string(data), unexpected) {
t.Fatalf("Marshal() emitted unset schema-defaulted field %s: %s", unexpected, data)
}
}
var encoded struct {
Runs []struct {
Results []struct {
Locations []struct {
ID *int `json:"id"`
} `json:"locations"`
} `json:"results"`
} `json:"runs"`
}
if err := json.Unmarshal(data, &encoded); err != nil {
t.Fatalf("Unmarshal() error = %v", err)
}
if encoded.Runs[0].Results[0].Locations[0].ID != nil {
t.Fatalf("Marshal() emitted unset location id: %s", data)
}
}

func TestValidateRejectsInvalidLog(t *testing.T) {
log := &Log{
Version: "2.0.0",
Runs: []Run{},
}

if err := Validate(log); err == nil {
t.Fatal("Validate() error = nil, want invalid version error")
}
}
77 changes: 77 additions & 0 deletions validate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
//go:build !tinygo

package sarif

import (
"bytes"
"embed"
"encoding/json"
"fmt"
"sync"

"github.com/santhosh-tekuri/jsonschema/v6"
)

//go:embed schema/sarif-schema-2.1.0.json
var schemaFS embed.FS

const schemaPath = "schema/sarif-schema-2.1.0.json"

var (
compiledSchema *jsonschema.Schema
compiledSchemaErr error
compiledSchemaOnce sync.Once
)

// Validate validates a SARIF log against the bundled SARIF 2.1.0 schema.
func Validate(log *Log) error {
schema, err := Schema()
if err != nil {
return err
}

data, err := json.Marshal(log)
if err != nil {
return fmt.Errorf("validate sarif: %w", err)
}

value, err := jsonschema.UnmarshalJSON(bytes.NewReader(data))
if err != nil {
return fmt.Errorf("validate sarif: %w", err)
}

if err := schema.Validate(value); err != nil {
return fmt.Errorf("validate sarif: %w", err)
}
return nil
}

// Schema returns the compiled bundled SARIF 2.1.0 JSON schema.
// It is unavailable under TinyGo.
func Schema() (*jsonschema.Schema, error) {
compiledSchemaOnce.Do(func() {
data, err := schemaFS.ReadFile(schemaPath)
if err != nil {
compiledSchemaErr = fmt.Errorf("compile sarif schema: %w", err)
return
}

compiler := jsonschema.NewCompiler()
compiler.DefaultDraft(jsonschema.Draft7)
doc, err := jsonschema.UnmarshalJSON(bytes.NewReader(data))
if err != nil {
compiledSchemaErr = fmt.Errorf("compile sarif schema: %w", err)
return
}
if err := compiler.AddResource(schemaPath, doc); err != nil {
compiledSchemaErr = fmt.Errorf("compile sarif schema: %w", err)
return
}

compiledSchema, compiledSchemaErr = compiler.Compile(schemaPath)
if compiledSchemaErr != nil {
compiledSchemaErr = fmt.Errorf("compile sarif schema: %w", compiledSchemaErr)
}
})
return compiledSchema, compiledSchemaErr
}
Loading
Loading