Skip to content

Commit

Permalink
Implement JSON sorting
Browse files Browse the repository at this point in the history
  • Loading branch information
JonahSussman committed Nov 16, 2023
1 parent ea3088b commit 7dac170
Showing 1 changed file with 210 additions and 32 deletions.
242 changes: 210 additions & 32 deletions output/v1/konveyor/violations.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"go.lsp.dev/uri"
"gopkg.in/yaml.v2"
)

const (
Expand All @@ -16,43 +17,70 @@ const (
type RuleSet struct {
// Name is a name for the ruleset.
Name string `yaml:"name,omitempty" json:"name,omitempty"`

// Description text description for the ruleset.
Description string `yaml:"description,omitempty" json:"description,omitempty"`

// Tags list of generated tags from the rules in this ruleset.
Tags []string `yaml:"tags,omitempty" json:"tags,omitempty"`

// Violations is a map containing violations generated for the
// matched rules in this ruleset. Keys are rule IDs, values are
// their respective generated violations.
Violations map[string]Violation `yaml:"violations,omitempty" json:"violations,omitempty"`

// Errors is a map containing errors generated during evaluation
// of rules in this ruleset. Keys are rule IDs, values are
// their respective generated errors.
Errors map[string]string `yaml:"errors,omitempty" json:"errors,omitempty"`

// Unmatched is a list of rule IDs of the rules that weren't matched.
Unmatched []string `yaml:"unmatched,omitempty" json:"unmatched,omitempty"`

// Skipped is a list of rule IDs that were skipped
Skipped []string `yaml:"skipped,omitempty" json:"skipped,omitempty"`
}

func (r RuleSet) MarshalYAML() (interface{}, error) {
// Sorts all fields in a canonical way on a RuleSet
func (r *RuleSet) sortFields() {
sort.Strings(r.Tags)
sort.Strings(r.Unmatched)
sort.Strings(r.Skipped)
}

func (r RuleSet) MarshalYAML() (interface{}, error) {
r.sortFields()
return r, nil
}

func (r RuleSet) MarshalJSON() ([]byte, error) {
b, err := yaml.Marshal(r)
if err != nil {
return b, err
}

m := map[string]any{}
err = yaml.Unmarshal(b, &m)
if err != nil {
return nil, err
}

return json.Marshal(m)
}

type Category string

var (
// Potential - rule states that there may be issue, but the rule author is unsure.
// This is used when you are trying to communicate that something may be wrong/incorrect
// But individual situations may require more context.
Potential Category = "potential"

// Optional - rule states that there is an issue, but that this issue can be fixed later.
// Primary use case is when migrating frameworks, and something has a deprecated notice,
// You should fix this but it wont break the migration/upgrade.
Optional Category = "optional"

// Mandatory - rule states that there is an issue that must be fixed.
// This is used, based on the ruleset, to tell the user that this is a real issue.
// For migrations, this means it must be fixed or the upgrade with fail.
Expand Down Expand Up @@ -83,62 +111,106 @@ type Violation struct {
Effort *int `yaml:"effort,omitempty" json:"effort,omitempty"`
}

func (v Violation) MarshalYAML() (interface{}, error) {
// Sorts all fields in a canonical way on a Violation
func (v *Violation) sortFields() {
sort.Strings(v.Labels)
sort.SliceStable(v.Incidents, func(i, j int) bool {
if v.Incidents[i].URI != v.Incidents[j].URI {
return v.Incidents[i].URI < v.Incidents[j].URI
}

if v.Incidents[i].Message != v.Incidents[j].Message {
return v.Incidents[i].Message < v.Incidents[j].Message
}

if v.Incidents[i].CodeSnip != v.Incidents[j].CodeSnip {
return v.Incidents[i].CodeSnip < v.Incidents[j].CodeSnip
}

if *v.Incidents[i].LineNumber != *v.Incidents[j].LineNumber {
return *v.Incidents[i].LineNumber < *v.Incidents[j].LineNumber
}

return false
sort.SliceStable(v.Incidents, func(i, j int) bool {
return v.Incidents[i].cmpLess(&v.Incidents[j])
})
sort.SliceStable(v.Links, func(i, j int) bool {
if v.Links[i].URL != v.Links[j].URL {
return v.Links[i].URL < v.Links[j].URL
}

if v.Links[i].Title != v.Links[j].Title {
return v.Links[i].Title < v.Links[j].Title
}

return false
sort.SliceStable(v.Links, func(i, j int) bool {
return v.Links[i].cmpLess(&v.Links[j])
})
}

func (v Violation) MarshalYAML() (interface{}, error) {
v.sortFields()
return v, nil
}

func (v Violation) MarshalJSON() ([]byte, error) {
b, err := yaml.Marshal(v)
if err != nil {
return b, err
}

m := map[string]any{}
err = yaml.Unmarshal(b, &m)
if err != nil {
return nil, err
}

return json.Marshal(m)
}

// Incident defines instance of a violation
type Incident struct {
// URI defines location in the codebase where violation is found
URI uri.URI `yaml:"uri" json:"uri"`

// Message text description about the incident
Message string `yaml:"message" json:"message"`
CodeSnip string `yaml:"codeSnip,omitempty" json:"codeSnip,omitempty"`

// Extras reserved for additional data
//Extras json.RawMessage
// Extras json.RawMessage
LineNumber *int `yaml:"lineNumber,omitempty" json:"lineNumber,omitempty"`
Variables map[string]interface{} `yaml:"variables,omitempty" json:"variables,omitempty"`
}

// Lexicographically compares two Incidents
func (i *Incident) cmpLess(other *Incident) bool {
if i.URI != other.URI {
return i.URI < other.URI
}

if i.Message != other.Message {
return i.Message < other.Message
}

if i.CodeSnip != other.CodeSnip {
return i.CodeSnip < other.CodeSnip
}

if i.LineNumber == nil {
x := 0
i.LineNumber = &x
}

if other.LineNumber == nil {
x := 0
other.LineNumber = &x
}

if *(*i).LineNumber != *(*other).LineNumber {
return *(*i).LineNumber < *(*other).LineNumber
}

return false
}

// Link defines an external hyperlink
type Link struct {
URL string `yaml:"url" json:"url"`

// Title optional description
Title string `yaml:"title,omitempty" json:"title,omitempty"`
}

// Lexicographically compares two Links
func (l *Link) cmpLess(other *Link) bool {
if l.Title != other.Title {
return l.Title < other.Title
}

if l.URL != other.URL {
return l.URL < other.URL
}

return false
}

type Dep struct {
Name string `json:"name,omitempty" yaml:"name,omitempty"`
Version string `json:"version,omitempty" yaml:"version,omitempty"`
Expand All @@ -150,12 +222,12 @@ type Dep struct {
FileURIPrefix string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
}

func (d Dep) MarshalYAML() (interface{}, error) {
// Sorts all fields in a canonical way on a Dep
func (d *Dep) sortFields() {
sort.Strings(d.Labels)

return d, nil
}

// Lexicographically compares two Deps
func (d Dep) cmpLess(other Dep) bool {
if d.Name != other.Name {
return d.Name < other.Name
Expand Down Expand Up @@ -190,6 +262,26 @@ func (d Dep) cmpLess(other Dep) bool {
return false
}

func (d Dep) MarshalYAML() (interface{}, error) {
d.sortFields()
return d, nil
}

func (d Dep) MarshalJSON() ([]byte, error) {
b, err := yaml.Marshal(d)
if err != nil {
return b, err
}

m := map[string]any{}
err = yaml.Unmarshal(b, &m)
if err != nil {
return nil, err
}

return json.Marshal(m)
}

func (d *Dep) GetLabels() []string {
return d.Labels
}
Expand All @@ -199,14 +291,100 @@ type DepDAGItem struct {
AddedDeps []DepDAGItem `yaml:"addedDep,omitempty" json:"addedDep,omitempty"`
}

// Sorts all fields in a canonical way on a DepDAGItem
func (d *DepDAGItem) sortFields() {
for i := range d.AddedDeps {
d.AddedDeps[i].sortFields()
}

sort.SliceStable(d.AddedDeps, func(i int, j int) bool {
return d.AddedDeps[i].Dep.cmpLess(d.AddedDeps[j].Dep)
})
}

func (d DepDAGItem) MarshalYAML() (interface{}, error) {
d.sortFields()
return d, nil
}

func (d DepDAGItem) MarshalJSON() ([]byte, error) {
b, err := yaml.Marshal(d)
if err != nil {
return b, err
}

m := map[string]any{}
err = yaml.Unmarshal(b, &m)
if err != nil {
return nil, err
}

return json.Marshal(m)
}

type DepsFlatItem struct {
FileURI string `yaml:"fileURI" json:"fileURI"`
Provider string `yaml:"provider" json:"provider"`
Dependencies []*Dep `yaml:"dependencies" json:"dependencies"`
}

// Sorts all fields in a canonical way on a DepsFlatItem
func (d *DepsFlatItem) sortFields() {
sort.SliceStable(d.Dependencies, func(i, j int) bool {
return (*d.Dependencies[i]).cmpLess(*d.Dependencies[j])
})
}

func (d DepsFlatItem) MarshalYAML() (interface{}, error) {
d.sortFields()
return d, nil
}

func (d DepsFlatItem) MarshalJSON() ([]byte, error) {
b, err := yaml.Marshal(d)
if err != nil {
return b, err
}

m := map[string]any{}
err = yaml.Unmarshal(b, &m)
if err != nil {
return nil, err
}

return json.Marshal(m)
}

type DepsTreeItem struct {
FileURI string `yaml:"fileURI" json:"fileURI"`
Provider string `yaml:"provider" json:"provider"`
Dependencies []DepDAGItem `yaml:"dependencies" json:"dependencies"`
}

// Sorts all fields in a canonical way on a DepsTreeItem
func (d *DepsTreeItem) sortFields() {
// Technically doesn't traverse the entire tree
sort.SliceStable(d.Dependencies, func(i, j int) bool {
return d.Dependencies[i].Dep.cmpLess(d.Dependencies[j].Dep)
})
}

func (d DepsTreeItem) MarshalYAML() (interface{}, error) {
d.sortFields()
return d, nil
}

func (d DepsTreeItem) MarshalJSON() ([]byte, error) {
b, err := yaml.Marshal(d)
if err != nil {
return b, err
}

m := map[string]any{}
err = yaml.Unmarshal(b, &m)
if err != nil {
return nil, err
}

return json.Marshal(m)
}

0 comments on commit 7dac170

Please sign in to comment.