Files
GSP/toolkit/internal/gsp/model.go

303 lines
8.1 KiB
Go

package gsp
import (
"fmt"
"gopkg.in/yaml.v3"
)
var resolutionRank = map[string]int{
"": 0,
"L0": 0,
"L1": 1,
"L2": 2,
"L3": 3,
"L4": 4,
"L5": 5,
}
type Unit struct {
ID string `json:"id" yaml:"id"`
Title string `json:"title,omitempty" yaml:"title"`
Context string `json:"context,omitempty" yaml:"context"`
Resolution string `json:"resolution,omitempty" yaml:"resolution"`
With Relations `json:"with,omitempty" yaml:"with"`
Refines string `json:"refines,omitempty" yaml:"refines"`
Type string `json:"type,omitempty" yaml:"type"`
Links Links `json:"links,omitempty" yaml:"links"`
File string `json:"file,omitempty" yaml:"-"`
}
type Relation struct {
ID string `json:"id" yaml:"id"`
Context string `json:"context,omitempty" yaml:"context"`
}
type Relations []Relation
func (r *Relations) UnmarshalYAML(value *yaml.Node) error {
if value.Kind == 0 || value.Tag == "!!null" {
*r = nil
return nil
}
if value.Kind != yaml.SequenceNode {
return fmt.Errorf("with must be a list")
}
out := make([]Relation, 0, len(value.Content))
for _, item := range value.Content {
switch item.Kind {
case yaml.ScalarNode:
if item.Value == "" {
return fmt.Errorf("with item cannot be empty")
}
out = append(out, Relation{ID: item.Value})
case yaml.MappingNode:
var rel Relation
if err := item.Decode(&rel); err != nil {
return err
}
if rel.ID == "" {
return fmt.Errorf("with object item requires id")
}
out = append(out, rel)
default:
return fmt.Errorf("with item must be a string or object")
}
}
*r = out
return nil
}
type Link struct {
Path string `json:"path" yaml:"path"`
Role string `json:"role,omitempty" yaml:"role"`
Context string `json:"context,omitempty" yaml:"context"`
}
type Links []Link
func (l *Links) UnmarshalYAML(value *yaml.Node) error {
if value.Kind == 0 || value.Tag == "!!null" {
*l = nil
return nil
}
switch value.Kind {
case yaml.ScalarNode:
if value.Value == "" {
return fmt.Errorf("links item cannot be empty")
}
*l = Links{{Path: value.Value}}
return nil
case yaml.SequenceNode:
out := make([]Link, 0, len(value.Content))
for _, item := range value.Content {
switch item.Kind {
case yaml.ScalarNode:
if item.Value == "" {
return fmt.Errorf("links item cannot be empty")
}
out = append(out, Link{Path: item.Value})
case yaml.MappingNode:
var link Link
if err := item.Decode(&link); err != nil {
return err
}
if link.Path == "" {
return fmt.Errorf("links object item requires path")
}
out = append(out, link)
default:
return fmt.Errorf("links item must be a string or object")
}
}
*l = out
return nil
default:
return fmt.Errorf("links must be a string or list")
}
}
type Issue struct {
Level string `json:"level"`
Code string `json:"code"`
ID string `json:"id,omitempty"`
File string `json:"file,omitempty"`
Message string `json:"message"`
}
type Report struct {
OK bool `json:"ok"`
Errors []Issue `json:"errors,omitempty"`
Warnings []Issue `json:"warnings,omitempty"`
Notices []Issue `json:"notices,omitempty"`
}
func (r *Report) addError(code, id, file, message string) {
r.Errors = append(r.Errors, Issue{Level: "error", Code: code, ID: id, File: file, Message: message})
r.OK = false
}
func (r *Report) addWarning(code, id, file, message string) {
r.Warnings = append(r.Warnings, Issue{Level: "warning", Code: code, ID: id, File: file, Message: message})
}
func (r *Report) addNotice(code, id, file, message string) {
r.Notices = append(r.Notices, Issue{Level: "notice", Code: code, ID: id, File: file, Message: message})
}
type Project struct {
Root string
Manifest *Manifest
Units []*Unit
ByID map[string]*Unit
Duplicates map[string][]*Unit
LoadIssues []Issue
}
type IndexEntry struct {
ID string `json:"id"`
Title string `json:"title,omitempty"`
File string `json:"file"`
Type string `json:"type,omitempty"`
Resolution string `json:"resolution,omitempty"`
With []string `json:"with,omitempty"`
Refines string `json:"refines,omitempty"`
Links []string `json:"links,omitempty"`
}
type FlattenResult struct {
Entry string `json:"entry"`
Depth int `json:"depth"`
Units []*Unit `json:"units"`
Warnings []Issue `json:"warnings,omitempty"`
}
type TraceResult struct {
Entry string `json:"entry"`
Depth int `json:"depth"`
Nodes []*Unit `json:"nodes"`
Edges []GraphEdge `json:"edges"`
Warnings []Issue `json:"warnings,omitempty"`
}
type PackResult struct {
Entry string `json:"entry"`
Intent string `json:"intent,omitempty"`
Stage string `json:"stage,omitempty"`
Depth int `json:"depth"`
Budget int `json:"budget,omitempty"`
Truncated bool `json:"truncated"`
Units []*Unit `json:"units"`
Links []ResolvedLink `json:"links,omitempty"`
Summary Summary `json:"summary"`
ApproxChars int `json:"approxChars"`
Warnings []Issue `json:"warnings,omitempty"`
}
type Summary struct {
UnitCount int `json:"unitCount"`
MinResolution string `json:"minResolution,omitempty"`
MaxResolution string `json:"maxResolution,omitempty"`
TypeCounts map[string]int `json:"typeCounts,omitempty"`
MissingCount int `json:"missingCount,omitempty"`
LinkCount int `json:"linkCount,omitempty"`
}
type LinkResult struct {
Entry string `json:"entry"`
Depth int `json:"depth"`
Links []ResolvedLink `json:"links,omitempty"`
Summary LinkSummary `json:"summary"`
Warnings []Issue `json:"warnings,omitempty"`
}
type LinkSummary struct {
Total int `json:"total"`
ByKind map[string]int `json:"byKind,omitempty"`
ByRole map[string]int `json:"byRole,omitempty"`
ByState map[string]int `json:"byState,omitempty"`
}
type ResolvedLink struct {
Owner string `json:"owner"`
Title string `json:"title,omitempty"`
Path string `json:"path"`
Role string `json:"role"`
Kind string `json:"kind"`
Status string `json:"status"`
Exists bool `json:"exists,omitempty"`
Context string `json:"context,omitempty"`
File string `json:"file,omitempty"`
}
type ImpactResult struct {
Entry string `json:"entry"`
Depth int `json:"depth"`
Summary ImpactSummary `json:"summary"`
Direct []ImpactEntry `json:"direct,omitempty"`
Indirect []ImpactEntry `json:"indirect,omitempty"`
Edges []GraphEdge `json:"edges,omitempty"`
Warnings []Issue `json:"warnings,omitempty"`
}
type ImpactSummary struct {
DirectCount int `json:"directCount"`
IndirectCount int `json:"indirectCount"`
TotalCount int `json:"totalCount"`
MaxDepth int `json:"maxDepth"`
}
type ImpactEntry struct {
ID string `json:"id"`
Title string `json:"title,omitempty"`
Type string `json:"type,omitempty"`
Resolution string `json:"resolution,omitempty"`
File string `json:"file,omitempty"`
Depth int `json:"depth"`
Via string `json:"via,omitempty"`
Kind string `json:"kind,omitempty"`
}
type Graph struct {
Nodes []GraphNode `json:"nodes"`
Edges []GraphEdge `json:"edges"`
}
type GraphNode struct {
ID string `json:"id"`
Title string `json:"title,omitempty"`
Type string `json:"type,omitempty"`
Resolution string `json:"resolution,omitempty"`
File string `json:"file,omitempty"`
Missing bool `json:"missing,omitempty"`
}
type GraphEdge struct {
From string `json:"from"`
To string `json:"to"`
Kind string `json:"kind"`
}
type Filter struct {
IncludeTypes map[string]bool
ExcludeTypes map[string]bool
}
func (f Filter) Allows(unit *Unit) bool {
if unit == nil {
return false
}
if len(f.IncludeTypes) > 0 && !f.IncludeTypes[unit.Type] {
return false
}
if len(f.ExcludeTypes) > 0 && f.ExcludeTypes[unit.Type] {
return false
}
return true
}
func resolutionValue(value string) (int, bool) {
rank, ok := resolutionRank[value]
return rank, ok
}