main 0.0.1
andrew 3 weeks ago
parent de17fff9a4
commit 41a912a007

@ -3,13 +3,12 @@ package errors
// ErrorCode — тип для кода ошибки. // ErrorCode — тип для кода ошибки.
type ErrorCode string type ErrorCode string
// Перечисление кодов ошибок для валидации.
const ( const (
// Общие ошибки
ErrEmptyFile ErrorCode = "EMPTY_FILE" // файл пуст или отсутствует ErrEmptyFile ErrorCode = "EMPTY_FILE" // файл пуст или отсутствует
ErrYamlSyntax ErrorCode = "YAML_SYNTAX_ERROR" // ошибка синтаксического анализа YAML ErrYamlSyntax ErrorCode = "YAML_SYNTAX_ERROR" // ошибка синтаксического анализа YAML
// Ошибки, связанные с job
ErrJobNameBlank ErrorCode = "JOB_NAME_BLANK" // имя задачи пустое ErrJobNameBlank ErrorCode = "JOB_NAME_BLANK" // имя задачи пустое
ErrJobNameTooLong ErrorCode = "JOB_NAME_TOO_LONG"
ErrMissingScript ErrorCode = "MISSING_SCRIPT" // отсутствует обязательное поле script (или run) ErrMissingScript ErrorCode = "MISSING_SCRIPT" // отсутствует обязательное поле script (или run)
ErrBothScriptAndRun ErrorCode = "BOTH_SCRIPT_RUN" // одновременно присутствуют script и run ErrBothScriptAndRun ErrorCode = "BOTH_SCRIPT_RUN" // одновременно присутствуют script и run
ErrJobStageNotExist ErrorCode = "JOB_STAGE_NOT_EXIST" // указан stage, которого нет в списке разрешённых ErrJobStageNotExist ErrorCode = "JOB_STAGE_NOT_EXIST" // указан stage, которого нет в списке разрешённых
@ -58,6 +57,7 @@ const (
ErrServiceInvalid ErrorCode = "SERVICE_INVALID" ErrServiceInvalid ErrorCode = "SERVICE_INVALID"
ErrStageInvalid ErrorCode = "STAGE_INVALID" ErrStageInvalid ErrorCode = "STAGE_INVALID"
ErrVariableInvalid ErrorCode = "VARIABLE_INVALID" ErrVariableInvalid ErrorCode = "VARIABLE_INVALID"
ErrVariableNameTooLong ErrorCode = "VARIABLE_NAME_TOO_LONG"
ErrInvalidStagesOrder ErrorCode = "INVALID_STAGES_ORDER" ErrInvalidStagesOrder ErrorCode = "INVALID_STAGES_ORDER"
ErrVariablesInvalid ErrorCode = "VARIABLES_INVALID" ErrVariablesInvalid ErrorCode = "VARIABLES_INVALID"
ErrVariablesInvalidKey ErrorCode = "VARIABLES_INVALID_KEY" ErrVariablesInvalidKey ErrorCode = "VARIABLES_INVALID_KEY"
@ -66,3 +66,53 @@ const (
ErrBooleanValue ErrorCode = "BOOLEAN_VALUE" // значение должно быть булевым ErrBooleanValue ErrorCode = "BOOLEAN_VALUE" // значение должно быть булевым
ErrIncludeRulesInvalid ErrorCode = "INCLUDE_RULES_INVALID" // значение exists или changes не является строкой или массивом строк ErrIncludeRulesInvalid ErrorCode = "INCLUDE_RULES_INVALID" // значение exists или changes не является строкой или массивом строк
) )
// ErrorSeverity maps each error code to a severity level.
var ErrorSeverity = map[ErrorCode]SeverityLevel{
// Critical errors
ErrEmptyFile: Critical,
ErrYamlSyntax: Critical,
ErrMissingJobs: Critical,
ErrJobNameBlank: Critical,
ErrMissingScript: Critical,
ErrBothScriptAndRun: Critical,
ErrMissingStage: Critical,
ErrJobStageNotExist: Critical,
ErrUndefinedDependency: Critical,
ErrInvalidStageOrder: Critical,
ErrUndefinedNeed: Critical,
ErrCyclicDependency: Critical,
ErrStartInMissing: Critical,
ErrStartInInvalid: Critical,
ErrStartInTooLong: Critical,
ErrStartInMustBeBlank: Critical,
ErrVariableInvalid: Critical,
ErrVariablesInvalid: Critical,
ErrVariablesInvalidKey: Critical,
ErrServiceInvalid: Critical,
ErrBeforeScriptInvalid: Critical,
ErrChangesNotArrayOfStrings: Critical,
ErrChangesInvalidType: Critical,
ErrChangesTooManyEntries: Critical,
ErrChangesMissingPaths: Critical,
ErrPathsNotArrayOfStrings: Critical,
// Warnings
ErrDuplicateNeeds: Warning,
ErrNoVisibleJob: Warning,
ErrUnknownRootKey: Warning,
ErrUnknownKey: Warning,
ErrInvalidWhen: Warning,
ErrInvalidOnly: Warning,
ErrInvalidRulesFormat: Warning,
ErrRulesOnlyExcept: Warning,
ErrRulesOnly: Warning,
ErrRulesExcept: Warning,
ErrInvalidExpressionSyntax: Warning,
ErrUnknownRulesKey: Warning,
// Notes (if needed, add informational codes here)
ErrJobNameTooLong: Note,
ErrVariableNameTooLong: Note,
ErrNeedNameTooLong: Critical,
}

@ -5,14 +5,27 @@ import (
"strings" "strings"
) )
// ValidationErrorDetail хранит данные об одной ошибке. // SeverityLevel represents the severity of an error.
type SeverityLevel string
const (
// Critical errors prevent the configuration from being valid.
Critical SeverityLevel = "CRITICAL"
// Warning are issues that are problematic but may not break the configuration.
Warning SeverityLevel = "WARNING"
// Note are non-critical messages or hints.
Note SeverityLevel = "NOTE"
)
// ValidationErrorDetail stores data about one error
type ValidationErrorDetail struct { type ValidationErrorDetail struct {
Code ErrorCode // Код ошибки Code ErrorCode // Error code
Message string // Текст сообщения Message string // Text of error
Line int // Номер строки, где возникла ошибка Line int // Number of line, where error appeared
Column int // Позиция в строке Column int // Column
EndLine int // Конечная строка EndLine int // Endline
EndColumn int // Конечный столбец EndColumn int // EndColumn
Severity SeverityLevel // Severity leevel of an error
} }
type UnknownKeyError struct { type UnknownKeyError struct {
@ -23,31 +36,34 @@ type UnknownKeyError struct {
EndColumn int EndColumn int
} }
// ValidationError представляет совокупность ошибок валидации. // ValidationError represents all errors
type ValidationError struct { type ValidationError struct {
Errors []ValidationErrorDetail Errors []ValidationErrorDetail
} }
// Error возвращает строковое представление всех ошибок. // Error returns string of all errors
func (ve *ValidationError) Error() string { func (ve *ValidationError) Error() string {
var sb strings.Builder var sb strings.Builder
sb.WriteString("Найдены ошибки валидации:\n") sb.WriteString("Validation errors found:\n")
for i, errDetail := range ve.Errors { for i, errDetail := range ve.Errors {
sb.WriteString(fmt.Sprintf("%d) [%s] (Line %d, Col %d): %s\n", i+1, errDetail.Code, errDetail.Line, errDetail.Column, errDetail.Message)) sb.WriteString(fmt.Sprintf("%d) [%s] (%s) (Line %d, Col %d): %s\n",
i+1, errDetail.Code, errDetail.Severity, errDetail.Line, errDetail.Column, errDetail.Message))
} }
return sb.String() return sb.String()
} }
// AddDetail добавляет новую ошибку в список. // AddDetail adds new error in a list
func (ve *ValidationError) AddDetail(code ErrorCode, message string, line, column int) { func (ve *ValidationError) AddDetail(code ErrorCode, message string, line, column int) {
ve.Errors = append(ve.Errors, ValidationErrorDetail{ ve.Errors = append(ve.Errors, ValidationErrorDetail{
Code: code, Code: code,
Message: message, Message: message,
Line: line, Line: line,
Column: column, Column: column,
Severity: ErrorSeverity[code],
}) })
} }
// AddDetailWithSpan adds a new error detail with a span.
func (ve *ValidationError) AddDetailWithSpan(code ErrorCode, message string, line, col, endLine, endCol int) { func (ve *ValidationError) AddDetailWithSpan(code ErrorCode, message string, line, col, endLine, endCol int) {
ve.Errors = append(ve.Errors, ValidationErrorDetail{ ve.Errors = append(ve.Errors, ValidationErrorDetail{
Code: code, Code: code,
@ -56,10 +72,11 @@ func (ve *ValidationError) AddDetailWithSpan(code ErrorCode, message string, lin
Column: col, Column: col,
EndLine: endLine, EndLine: endLine,
EndColumn: endCol, EndColumn: endCol,
Severity: ErrorSeverity[code],
}) })
} }
// IsEmpty проверяет, имеются ли ошибки. // IsEmpty check if error exists
func (ve *ValidationError) IsEmpty() bool { func (ve *ValidationError) IsEmpty() bool {
return len(ve.Errors) == 0 return len(ve.Errors) == 0
} }

@ -4,10 +4,12 @@ package errors
var ErrorMessages = map[ErrorCode]string{ var ErrorMessages = map[ErrorCode]string{
ErrEmptyFile: "Please provide content of .gitflame-ci.yml", ErrEmptyFile: "Please provide content of .gitflame-ci.yml",
ErrYamlSyntax: "YAML syntax error: %v", ErrYamlSyntax: "YAML syntax error: %v",
ErrUnknownRootKey: "unexpected key: '%s'", ErrUnknownRootKey: "unexpected key: '%s'. Currently we not support it.",
ErrBeforeScriptInvalid: "before_script config should be a string or a nested array of strings up to 10 levels deep", ErrBeforeScriptInvalid: "before_script config should be a string or a nested array of strings up to 10 levels deep",
ErrJobNameTooLong: "job name is too long, probably should reduce number of symbols",
ErrServiceInvalid: "config should be a hash or a string", ErrServiceInvalid: "config should be a hash or a string",
ErrStageInvalid: "stage at index %d is empty; stage config should be a string", ErrStageInvalid: "stage at index %d is empty; stage config should be a string",
ErrVariableNameTooLong: "variable name %s is too long, probably should reduce number of symbols",
ErrVariablesInvalid: "variables config should be a map", ErrVariablesInvalid: "variables config should be a map",
ErrVariablesInvalidKey: "variable '%s' uses invalid data keys: %s", ErrVariablesInvalidKey: "variable '%s' uses invalid data keys: %s",
ErrMissingJobs: "no jobs found in the configuration", ErrMissingJobs: "no jobs found in the configuration",
@ -27,7 +29,7 @@ var ErrorMessages = map[ErrorCode]string{
ErrInvalidOnly: "job '%s': 'only' must be a non-empty string or an array of non-empty strings", ErrInvalidOnly: "job '%s': 'only' must be a non-empty string or an array of non-empty strings",
ErrNoVisibleJob: "no visible jobs found in the jobs section", ErrNoVisibleJob: "no visible jobs found in the jobs section",
ErrCyclicDependency: "cycle detection: a cycle was detected among jobs: %v", ErrCyclicDependency: "cycle detection: a cycle was detected among jobs: %v",
ErrArtifactsPathsBlank: "job '%s': artifacts paths can't be blank", ErrArtifactsPathsBlank: "job '%s': artifacts paths can't be blank. Currently we supporting `paths' syntax only.",
ErrChangesTooManyEntries: "has too many entries (maximum 50)", ErrChangesTooManyEntries: "has too many entries (maximum 50)",
ErrChangesNotArrayOfStrings: "changes config should be an array of strings", ErrChangesNotArrayOfStrings: "changes config should be an array of strings",
ErrChangesMissingPaths: "changes config hash must contain key 'paths'", ErrChangesMissingPaths: "changes config hash must contain key 'paths'",

@ -44,8 +44,6 @@ type Job struct {
Rules interface{} `yaml:"rules,omitempty"` // для динамических правил Rules interface{} `yaml:"rules,omitempty"` // для динамических правил
Artifacts *Artifacts `yaml:"artifacts,omitempty"` // блок artifacts Artifacts *Artifacts `yaml:"artifacts,omitempty"` // блок artifacts
// ... могут быть и другие поля
When string `yaml:"when,omitempty"` When string `yaml:"when,omitempty"`
StartIn string `yaml:"start_in,omitempty"` StartIn string `yaml:"start_in,omitempty"`
Only interface{} `yaml:"only,omitempty"` Only interface{} `yaml:"only,omitempty"`
@ -68,5 +66,6 @@ type Job struct {
// MaxJobNameLength здесь зададим некоторые константы, используемые для валидации // MaxJobNameLength здесь зададим некоторые константы, используемые для валидации
const ( const (
MaxJobNameLength = 63 // Примерно, как Ci::BuildNeed::MAX_JOB_NAME_LENGTH MaxJobNameLength = 63
MaxVariableNameLength = 63
) )

@ -2,7 +2,7 @@ package gitlabcivalidator
import ( import (
"fmt" "fmt"
"gitflame.ru/CI_VLDR/errors" "gitflame.ru/Azaki/CI_VLDR/errors"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
@ -11,64 +11,74 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
var config GitLabCIConfig var config GitLabCIConfig
var root yaml.Node var root yaml.Node
// Parse YAML into the root node // Parse YAML into the root node.
err := yaml.Unmarshal(data, &root) err := yaml.Unmarshal(data, &root)
if err != nil { if err != nil {
return nil, fmt.Errorf("YAML syntax error: %w", err) return nil, fmt.Errorf("YAML syntax error: %w", err)
} }
// If root is empty, the YAML file is empty or invalid
if len(root.Content) == 0 { if len(root.Content) == 0 {
return nil, fmt.Errorf(".gitlab-ci.yml file is empty or does not contain valid data") return nil, fmt.Errorf(".gitlab-ci.yml file is empty or does not contain valid data")
} }
// Expect the root node to be a YAML document
if root.Kind != yaml.DocumentNode || len(root.Content) == 0 { if root.Kind != yaml.DocumentNode || len(root.Content) == 0 {
return nil, fmt.Errorf("invalid YAML format: expected a root object") return nil, fmt.Errorf("invalid YAML format: expected a root object")
} }
// The main object is the first child
mainNode := root.Content[0] mainNode := root.Content[0]
// Decode the root object into the configuration structure // Decode known top-level keys into config.
err = mainNode.Decode(&config) err = mainNode.Decode(&config)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decode GitLab CI configuration: %w", err) return nil, fmt.Errorf("failed to decode GitLab CI configuration: %w", err)
} }
// Manually parse the 'jobs' node // Allowed top-level keys that are NOT jobs.
for i := 0; i < len(mainNode.Content)-1; i += 2 { allowedKeys := map[string]struct{}{
keyNode := mainNode.Content[i] "default": {},
valNode := mainNode.Content[i+1] "include": {},
"before_script": {},
if keyNode.Value == "jobs" && valNode.Kind == yaml.MappingNode { "image": {},
// 'jobs' found, parsing manually "services": {},
config.Jobs = make(map[string]*Job) "after_script": {},
"variables": {},
"stages": {},
"cache": {},
"workflow": {},
"changes": {},
"paths": {},
}
for j := 0; j < len(valNode.Content)-1; j += 2 { // Initialize config.Jobs if not already set.
jobKeyNode := valNode.Content[j] // job name if config.Jobs == nil {
jobValNode := valNode.Content[j+1] // job definition (mapping or otherwise) config.Jobs = make(map[string]*Job)
}
// Create a new job with its name and positional info. // Now, iterate over the top-level mapping keys.
if mainNode.Kind == yaml.MappingNode {
for i := 0; i < len(mainNode.Content)-1; i += 2 {
keyNode := mainNode.Content[i]
valNode := mainNode.Content[i+1]
// If the key is not one of the allowed top-level keys, treat it as a job.
if _, ok := allowedKeys[keyNode.Value]; !ok {
// Create a new Job.
job := &Job{ job := &Job{
Name: jobKeyNode.Value, Name: keyNode.Value,
Line: jobKeyNode.Line, Line: keyNode.Line,
Column: jobKeyNode.Column, Column: keyNode.Column,
DependencyLines: make(map[string]struct{ Line, Column int }), DependencyLines: make(map[string]struct{ Line, Column int }),
} }
// Even if the job value is not a mapping node, add the job to config for further validation.
// If the job value is a mapping node, decode its content. if valNode.Kind == yaml.MappingNode {
if jobValNode.Kind == yaml.MappingNode { // Optionally, you can validate unknown keys inside the job mapping here.
err := jobValNode.Decode(job) // For example:
validateJobMapping(valNode, ve)
// Decode the job mapping.
err = valNode.Decode(job)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to decode job '%s': %w", job.Name, err) return nil, fmt.Errorf("failed to decode job '%s': %w", job.Name, err)
} }
validateJobMapping(jobValNode, ve) // Process inner fields manually (e.g. dependencies, needs, only, etc.)
// Process inner keys (dependencies, needs, only, etc.) for d := 0; d < len(valNode.Content)-1; d += 2 {
for d := 0; d < len(jobValNode.Content)-1; d += 2 { depKey := valNode.Content[d]
depKey := jobValNode.Content[d] depVal := valNode.Content[d+1]
depVal := jobValNode.Content[d+1]
if depKey.Value == "dependencies" && depVal.Kind == yaml.SequenceNode { if depKey.Value == "dependencies" && depVal.Kind == yaml.SequenceNode {
for k := 0; k < len(depVal.Content); k++ { for k := 0; k < len(depVal.Content); k++ {
depNode := depVal.Content[k] depNode := depVal.Content[k]
@ -79,7 +89,6 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
}{depNode.Line, depNode.Column} }{depNode.Line, depNode.Column}
} }
} }
if depKey.Value == "needs" && depVal.Kind == yaml.SequenceNode { if depKey.Value == "needs" && depVal.Kind == yaml.SequenceNode {
job.NeedLines = make(map[string]struct { job.NeedLines = make(map[string]struct {
Line int Line int
@ -94,7 +103,6 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
}{needNode.Line, needNode.Column} }{needNode.Line, needNode.Column}
} }
} }
if depKey.Value == "only" { if depKey.Value == "only" {
if depVal.Kind == yaml.ScalarNode { if depVal.Kind == yaml.ScalarNode {
job.Only = depVal.Value job.Only = depVal.Value
@ -108,10 +116,12 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
job.OnlyLine = depKey.Line job.OnlyLine = depKey.Line
job.OnlyColumn = depKey.Column job.OnlyColumn = depKey.Column
} }
// You can add additional manual processing here.
} }
} else {
// If the job value is not a mapping, leave job fields as default.
} }
// If jobValNode is not a mapping, leave the job with default (nil) fields. // Save the job in config.
// Now add the job to the configuration regardless.
config.Jobs[job.Name] = job config.Jobs[job.Name] = job
} }
} }
@ -123,19 +133,19 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
// findUnknownRootKeysWithSpan scans the mainNode for unknown keys and returns their information. // findUnknownRootKeysWithSpan scans the mainNode for unknown keys and returns their information.
func findUnknownRootKeysWithSpan(mainNode *yaml.Node) []errors.UnknownKeyError { func findUnknownRootKeysWithSpan(mainNode *yaml.Node) []errors.UnknownKeyError {
allowedKeys := map[string]struct{}{ allowedKeys := map[string]struct{}{
"default": {}, "default": {},
"include": {}, "include": {},
"before_script": {}, //"before_script": {},
"image": {}, "image": {},
"services": {}, "services": {},
"after_script": {}, "after_script": {},
"variables": {}, "variables": {},
"stages": {}, "stages": {},
"cache": {}, "cache": {},
"workflow": {}, "workflow": {},
"jobs": {}, "jobs": {},
"changes": {}, "changes": {},
"paths": {}, "paths": {},
} }
var unknowns []errors.UnknownKeyError var unknowns []errors.UnknownKeyError
@ -145,7 +155,7 @@ func findUnknownRootKeysWithSpan(mainNode *yaml.Node) []errors.UnknownKeyError {
for i := 0; i < len(mainNode.Content)-1; i += 2 { for i := 0; i < len(mainNode.Content)-1; i += 2 {
keyNode := mainNode.Content[i] keyNode := mainNode.Content[i]
if _, ok := allowedKeys[keyNode.Value]; !ok { if _, ok := allowedKeys[keyNode.Value]; !ok && keyNode.Value == "before_script" {
endLine, endCol := keyNode.Line, keyNode.Column+len(keyNode.Value) endLine, endCol := keyNode.Line, keyNode.Column+len(keyNode.Value)
unknowns = append(unknowns, errors.UnknownKeyError{ unknowns = append(unknowns, errors.UnknownKeyError{
Key: keyNode.Value, Key: keyNode.Value,

@ -2,7 +2,7 @@ package gitlabcivalidator
import ( import (
"fmt" "fmt"
"gitflame.ru/CI_VLDR/errors" "gitflame.ru/Azaki/CI_VLDR/errors"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"strings" "strings"
"time" "time"
@ -50,7 +50,7 @@ func ValidateGitLabCIFile(content string, opts ValidationOptions) error {
// Получаем корневой узел // Получаем корневой узел
mainNode := root.Content[0] mainNode := root.Content[0]
// Проверяем неизвестные ключи и добавляем ошибки в объект валидации //Проверяем неизвестные ключи и добавляем ошибки в объект валидации
unknowns := findUnknownRootKeysWithSpan(mainNode) unknowns := findUnknownRootKeysWithSpan(mainNode)
for _, unk := range unknowns { for _, unk := range unknowns {
validationErr.AddDetailWithSpan(errors.ErrUnknownRootKey, validationErr.AddDetailWithSpan(errors.ErrUnknownRootKey,
@ -170,7 +170,11 @@ func validateVariables(cfg *GitLabCIConfig, ve *errors.ValidationError) {
} }
for varName, val := range varsMap { for varName, val := range varsMap {
if isScalar(val) { if isScalar(val) {
// ok if len(varName) > MaxVariableNameLength {
ve.AddDetailWithSpan(errors.ErrVariableNameTooLong, fmt.Sprintf(errors.ErrorMessages[errors.ErrVariableNameTooLong], varName),
0, 0, 0, 0,
)
}
} else if nested, ok := val.(map[string]interface{}); ok { } else if nested, ok := val.(map[string]interface{}); ok {
invalidKeys := []string{} invalidKeys := []string{}
for k := range nested { for k := range nested {
@ -220,8 +224,13 @@ func validateJobs(cfg *GitLabCIConfig, ve *errors.ValidationError) {
ve.AddDetailWithSpan(errors.ErrJobNameBlank, ve.AddDetailWithSpan(errors.ErrJobNameBlank,
errors.ErrorMessages[errors.ErrJobNameBlank], errors.ErrorMessages[errors.ErrJobNameBlank],
job.Line, job.Column, job.Line, job.Column+len(job.Name)) job.Line, job.Column, job.Line, job.Column+len(job.Name))
continue
} }
if len(job.Name) > MaxJobNameLength {
ve.AddDetailWithSpan(errors.ErrJobNameTooLong, errors.ErrorMessages[errors.ErrJobNameTooLong],
job.Line, job.Column, job.Line, job.Column+len(job.Name))
}
if job.Name[0] != '.' { if job.Name[0] != '.' {
visibleCount++ visibleCount++
if job.Stage == "" { if job.Stage == "" {
@ -580,6 +589,9 @@ func validateJobMapping(jobNode *yaml.Node, ve *errors.ValidationError) {
"start_in": {}, "start_in": {},
"only": {}, "only": {},
"except": {}, "except": {},
"image": {},
"variables": {},
"services": {},
// add any additional allowed keys for a job... // add any additional allowed keys for a job...
} }
validateMappingKeys(jobNode, allowedKeys, "job", ve) validateMappingKeys(jobNode, allowedKeys, "job", ve)

@ -1,4 +1,4 @@
module gitflame.ru/CI_VLDR module gitflame.ru/Azaki/CI_VLDR
go 1.18 go 1.18

@ -1,60 +1,71 @@
package main package main
import ( import (
"bufio"
"fmt" "fmt"
"log" "os"
"gitflame.ru/CI_VLDR/gitlabcivalidator" "gitflame.ru/Azaki/CI_VLDR/gitlabcivalidator"
) )
func main() { func main() {
// Корректный (упрощённый) YAML // Корректный (упрощённый) YAML
validYAML := ` // validYAML := `
stages: //stages:
- build // - build
- test // - test
jobs: //jobs:
build_job: // build_job:
stage: build // stage: build
script: // script:
- echo "Building..." // - echo "Building..."
test_job: // test_job:
stage: test // stage: test
dependencies: ["build_job"] // dependencies: ["build_job"]
script: // script:
- echo "Testing..." // - echo "Testing..."
` //`
// Тип, ссылка и текст ошибки, показатели где нахоидтся ошибка... // Тип, ссылка и текст ошибки, показатели где нахоидтся ошибка...
// Некорректный YAML — отсутствует видимая задача, есть только скрытая // Некорректный YAML — отсутствует видимая задача, есть только скрытая
invalidYAML := ` // invalidYAML := `
stages: //stages:
- build // - build
- test // - test
jobs: //jobs:
teaaa: // teaaa:
build_job: // build_job:
validateJobMapping(jobValNode, ve): // validateJobMapping(jobValNode, ve):
script: // script:
- c // - c
needs: ["test_job2"] // needs: ["test_job2"]
test_job2: // test_job2:
stage: test // stage: test
` //`
fmt.Println("=== Пример 1: Корректный YAML ===") scanner := bufio.NewScanner(os.Stdin)
err := gitlabcivalidator.ValidateGitLabCIFile(validYAML, gitlabcivalidator.ValidationOptions{}) var inputYAML string
if err != nil { for scanner.Scan() {
log.Fatalf("Ожидали, что ошибок не будет, но возникли: %v\n", err) inputYAML += scanner.Text() + "\n"
} else { }
fmt.Println("Валидация успешна! Ошибок не найдено.")
if err := scanner.Err(); err != nil {
fmt.Println("Ошибка чтения ввода:", err)
return
} }
fmt.Println("\n=== Пример 2: Некорректный YAML ===") err := gitlabcivalidator.ValidateGitLabCIFile(inputYAML, gitlabcivalidator.ValidationOptions{})
err2 := gitlabcivalidator.ValidateGitLabCIFile(invalidYAML, gitlabcivalidator.ValidationOptions{}) if err != nil {
if err2 != nil { fmt.Println(err.Error())
fmt.Println("Ошибка валидации (ожидаемо для данного файла):")
fmt.Println(err2.Error())
} else { } else {
log.Fatal("Ожидали ошибку, но её не возникло") fmt.Println("no errors")
} }
//fmt.Println("\n=== Пример 2: Некорректный YAML ===")
//err2 := gitlabcivalidator.ValidateGitLabCIFile(invalidYAML, gitlabcivalidator.ValidationOptions{})
//if err2 != nil {
// fmt.Println("Ошибка валидации (ожидаемо для данного файла):")
// fmt.Println(err2.Error())
//} else {
// log.Fatal("Ожидали ошибку, но её не возникло")
//}
} }

Loading…
Cancel
Save