github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/pkg/packager/lint/validator.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  
     4  // Package lint contains functions for verifying jackal yaml files are valid
     5  package lint
     6  
     7  import (
     8  	"fmt"
     9  	"path/filepath"
    10  
    11  	"github.com/Racer159/jackal/src/pkg/message"
    12  	"github.com/Racer159/jackal/src/types"
    13  	"github.com/defenseunicorns/pkg/helpers"
    14  	"github.com/fatih/color"
    15  )
    16  
    17  type category int
    18  
    19  const (
    20  	categoryError   category = 1
    21  	categoryWarning category = 2
    22  )
    23  
    24  type validatorMessage struct {
    25  	yqPath         string
    26  	description    string
    27  	item           string
    28  	packageRelPath string
    29  	packageName    string
    30  	category       category
    31  }
    32  
    33  func (c category) String() string {
    34  	if c == categoryError {
    35  		return message.ColorWrap("Error", color.FgRed)
    36  	} else if c == categoryWarning {
    37  		return message.ColorWrap("Warning", color.FgYellow)
    38  	}
    39  	return ""
    40  }
    41  
    42  func (vm validatorMessage) String() string {
    43  	if vm.item != "" {
    44  		vm.item = fmt.Sprintf(" - %s", vm.item)
    45  	}
    46  	return fmt.Sprintf("%s%s", vm.description, vm.item)
    47  }
    48  
    49  // Validator holds the warnings/errors and messaging that we get from validation
    50  type Validator struct {
    51  	findings             []validatorMessage
    52  	jsonSchema           []byte
    53  	typedJackalPackage   types.JackalPackage
    54  	untypedJackalPackage interface{}
    55  	baseDir              string
    56  }
    57  
    58  // DisplayFormattedMessage message sent to user based on validator results
    59  func (v Validator) DisplayFormattedMessage() {
    60  	if !v.hasFindings() {
    61  		message.Successf("0 findings for %q", v.typedJackalPackage.Metadata.Name)
    62  	}
    63  	v.printValidationTable()
    64  }
    65  
    66  // IsSuccess returns true if there are not any errors
    67  func (v Validator) IsSuccess() bool {
    68  	for _, finding := range v.findings {
    69  		if finding.category == categoryError {
    70  			return false
    71  		}
    72  	}
    73  	return true
    74  }
    75  
    76  func (v Validator) packageRelPathToUser(vm validatorMessage) string {
    77  	if helpers.IsOCIURL(vm.packageRelPath) {
    78  		return vm.packageRelPath
    79  	}
    80  	return filepath.Join(v.baseDir, vm.packageRelPath)
    81  }
    82  
    83  func (v Validator) printValidationTable() {
    84  	if !v.hasFindings() {
    85  		return
    86  	}
    87  
    88  	mapOfFindingsByPath := make(map[string][]validatorMessage)
    89  	for _, finding := range v.findings {
    90  		mapOfFindingsByPath[finding.packageRelPath] = append(mapOfFindingsByPath[finding.packageRelPath], finding)
    91  	}
    92  
    93  	header := []string{"Type", "Path", "Message"}
    94  
    95  	for packageRelPath, findings := range mapOfFindingsByPath {
    96  		lintData := [][]string{}
    97  		for _, finding := range findings {
    98  			lintData = append(lintData, []string{finding.category.String(), finding.getPath(), finding.String()})
    99  		}
   100  		message.Notef("Linting package %q at %s", findings[0].packageName, v.packageRelPathToUser(findings[0]))
   101  		message.Table(header, lintData)
   102  		message.Info(v.getFormattedFindingCount(packageRelPath, findings[0].packageName))
   103  	}
   104  }
   105  
   106  func (v Validator) getFormattedFindingCount(relPath string, packageName string) string {
   107  	warningCount := 0
   108  	errorCount := 0
   109  	for _, finding := range v.findings {
   110  		if finding.packageRelPath != relPath {
   111  			continue
   112  		}
   113  		if finding.category == categoryWarning {
   114  			warningCount++
   115  		}
   116  		if finding.category == categoryError {
   117  			errorCount++
   118  		}
   119  	}
   120  	wordWarning := "warnings"
   121  	if warningCount == 1 {
   122  		wordWarning = "warning"
   123  	}
   124  	wordError := "errors"
   125  	if errorCount == 1 {
   126  		wordError = "error"
   127  	}
   128  	return fmt.Sprintf("%d %s and %d %s in %q",
   129  		warningCount, wordWarning, errorCount, wordError, packageName)
   130  }
   131  
   132  func (vm validatorMessage) getPath() string {
   133  	if vm.yqPath == "" {
   134  		return ""
   135  	}
   136  	return message.ColorWrap(vm.yqPath, color.FgCyan)
   137  }
   138  
   139  func (v Validator) hasFindings() bool {
   140  	return len(v.findings) > 0
   141  }
   142  
   143  func (v *Validator) addWarning(vmessage validatorMessage) {
   144  	vmessage.category = categoryWarning
   145  	v.findings = helpers.Unique(append(v.findings, vmessage))
   146  }
   147  
   148  func (v *Validator) addError(vMessage validatorMessage) {
   149  	vMessage.category = categoryError
   150  	v.findings = helpers.Unique(append(v.findings, vMessage))
   151  }