From 12faceb46a15fb1d8d396c97f7e2377f9948d5d3 Mon Sep 17 00:00:00 2001 From: pomdtr Date: Wed, 3 May 2023 16:29:58 +0200 Subject: [PATCH 1/2] simplify validation errors --- cmd/validate.go | 6 +- internal/api/api.go | 6 +- internal/spacefile/spacefile.go | 211 ++++++-------------------------- 3 files changed, 44 insertions(+), 179 deletions(-) diff --git a/cmd/validate.go b/cmd/validate.go index ca3cf47..684de6e 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -32,13 +32,11 @@ func newCmdValidate() *cobra.Command { } func validate(projectDir string) error { - shared.Logger.Printf("\n%s Validating Spacefile...", emoji.Package) + shared.Logger.Printf("\n%sValidating Spacefile...", emoji.Package) s, err := spacefile.LoadSpacefile(projectDir) if err != nil { - shared.Logger.Println(styles.Errorf("\n%s Detected some issues with your Spacefile. Please fix them before pushing your code.", emoji.ErrorExclamation)) - shared.Logger.Println() - shared.Logger.Println(err.Error()) + shared.Logger.Println(styles.Errorf("\n%s Detected some issues with your Spacefile. Please fix them before pushing your code.\n", emoji.ErrorExclamation)) return err } diff --git a/internal/api/api.go b/internal/api/api.go index 4273ffa..0e79975 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -305,19 +305,19 @@ func (c *DetaClient) PushSpacefile(r *PushSpacefileRequest) (*PushSpacefileRespo } o, err := c.request(i) - if err != nil { return nil, err } + if !(o.Status >= 200 && o.Status <= 299) { msg := o.Error.Detail - return nil, fmt.Errorf("failed to push spacefile file, %v", msg) + return nil, fmt.Errorf(msg) } var resp PushSpacefileResponse err = json.Unmarshal(o.Body, &resp) if err != nil { - return nil, fmt.Errorf("failed to push spacefile file %w", err) + return nil, fmt.Errorf("unable to read response: %w", err) } return &resp, nil diff --git a/internal/spacefile/spacefile.go b/internal/spacefile/spacefile.go index 1162c73..c076119 100644 --- a/internal/spacefile/spacefile.go +++ b/internal/spacefile/spacefile.go @@ -46,198 +46,62 @@ type Spacefile struct { Micros []*shared.Micro `yaml:"micros,omitempty"` } -func extractMicro(v any, index int) (map[string]any, bool) { +type SpacefileValidationError struct { + wrapped *jsonschema.ValidationError + raw any +} + +func extractMicroName(v any, index int) (string, bool) { spacefile, ok := v.(map[string]interface{}) if !ok { - return nil, false + return "", false } micros, ok := spacefile["micros"].([]interface{}) if !ok { - return nil, false + return "", false } micro, ok := micros[index].(map[string]interface{}) if !ok { - return nil, false - } - - return micro, true -} - -func extractPresets(v any, microIndex int) (map[string]any, bool) { - micro, ok := extractMicro(v, microIndex) - if !ok { - return nil, false - } - - presets, ok := micro["presets"].(map[string]interface{}) - if !ok { - return nil, false - } - - return presets, true -} - -func extractAction(v any, microIndex int, actionIndex int) (map[string]any, bool) { - micro, ok := extractMicro(v, microIndex) - if !ok { - return nil, false - } - - actions, ok := micro["actions"].([]interface{}) - if !ok { - return nil, false - } - - action, ok := actions[actionIndex].(map[string]interface{}) - if !ok { - return nil, false - } - - return action, true -} - -func extractEnv(v any, microIndex int, envIndex int) (map[string]any, bool) { - presets, ok := extractPresets(v, microIndex) - if !ok { - return nil, false - } - - envs, ok := presets["env"].([]interface{}) - if !ok { - return nil, false - } - - env, ok := envs[envIndex].(map[string]interface{}) - if !ok { - return nil, false - } - - return env, true -} - -func extractApiKey(v any, microIndex int, apiKeyIndex int) (map[string]any, bool) { - presets, ok := extractPresets(v, microIndex) - if !ok { - return nil, false + return "", false } - apiKeys, ok := presets["api_keys"].([]interface{}) + name, ok := micro["name"].(string) if !ok { - return nil, false + return "", false } - apiKey, ok := apiKeys[apiKeyIndex].(map[string]interface{}) - if !ok { - return nil, false - } - - return apiKey, true + return name, true } -var ( - microReg = regexp.MustCompile(`\/micros\/(\d+)$`) - actionReg = regexp.MustCompile(`\/micros\/(\d+)\/actions\/(\d+)$`) - commandsReg = regexp.MustCompile(`\/micros\/(\d+)\/commands$`) - includeReg = regexp.MustCompile(`\/micros\/(\d+)\/include$`) - publicRoutesReg = regexp.MustCompile(`\/micros\/(\d+)\/public_routes$`) - presetsReg = regexp.MustCompile(`\/micros\/(\d+)\/presets$`) - envReg = regexp.MustCompile(`\/micros\/(\d+)\/presets\/env\/(\d+)$`) - apiKeyReg = regexp.MustCompile(`\/micros\/(\d+)\/presets\/api_keys\/(\d+)$`) - numberReg = regexp.MustCompile(`^\d+$`) -) - -func PrettyValidationErrors(ve *jsonschema.ValidationError, v any, prefix string) string { - // Skip the root error - if ve.KeywordLocation == "" { - return PrettyValidationErrors(ve.Causes[0], v, prefix) - } - - // If there are no causes, just print the message - if len(ve.Causes) == 0 { - message := strings.Replace(ve.Message, "additionalProperties", "unknown field", 1) - parts := strings.Split(ve.InstanceLocation, "/") - - leaf := parts[len(parts)-1] - if leaf == "" || numberReg.MatchString(leaf) { - return fmt.Sprintf("%sL %s", prefix, message) - } - - return fmt.Sprintf("%sL %s -> %s", prefix, leaf, message) - } - - var rows []string - if matches := microReg.FindStringSubmatch(ve.InstanceLocation); len(matches) == 2 { - i, _ := strconv.Atoi(matches[1]) - micro, ok := extractMicro(v, i) - if !ok { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L Micro at index "+matches[1])) - } - - if name, ok := micro["name"].(string); ok { - rows = append(rows, fmt.Sprintf("%sL Micro '%s'", prefix, name)) - } else { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L Micro at index "+matches[1])) - } - } else if matches := presetsReg.FindStringSubmatch(ve.InstanceLocation); len(matches) == 2 { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L Presets")) - } else if matches := publicRoutesReg.FindStringSubmatch(ve.InstanceLocation); len(matches) == 2 { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L Public Routes")) - } else if matches := commandsReg.FindStringSubmatch(ve.InstanceLocation); len(matches) == 2 { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L Commands")) - } else if matches := includeReg.FindStringSubmatch(ve.InstanceLocation); len(matches) == 2 { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L Include")) - } else if matches := actionReg.FindStringSubmatch(ve.InstanceLocation); len(matches) == 3 { - i, _ := strconv.Atoi(matches[1]) - j, _ := strconv.Atoi(matches[2]) - action, ok := extractAction(v, i, j) - if !ok { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L Action at index "+matches[2])) - } - - if name, ok := action["name"].(string); ok { - rows = append(rows, fmt.Sprintf("%sL Action '%s'", prefix, name)) - } else { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L Action at index "+matches[2])) - } - } else if matches := envReg.FindStringSubmatch(ve.InstanceLocation); len(matches) == 3 { - i, _ := strconv.Atoi(matches[1]) - j, _ := strconv.Atoi(matches[2]) - env, ok := extractEnv(v, i, j) - if !ok { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L Env at index "+matches[2])) - } - - if name, ok := env["name"].(string); ok { - rows = append(rows, fmt.Sprintf("%sL Env '%s'", prefix, name)) - } else { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L Env at index "+matches[2])) - } - } else if matches := apiKeyReg.FindStringSubmatch(ve.InstanceLocation); len(matches) == 3 { - i, _ := strconv.Atoi(matches[1]) - j, _ := strconv.Atoi(matches[2]) - apiKey, ok := extractApiKey(v, i, j) - if !ok { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L API Key at index "+matches[2])) +var microReg = regexp.MustCompile(`\/micros\/(\d+)`) + +func (ve SpacefileValidationError) Error() string { + leaf := ve.wrapped + queue := []*jsonschema.ValidationError{leaf} + rootErrors := []string{} + for len(queue) > 0 { + if len(leaf.Causes) == 0 { + if matches := microReg.FindStringSubmatch(leaf.InstanceLocation); len(matches) > 0 { + i := matches[1] + idx, _ := strconv.Atoi(i) + + if name, ok := extractMicroName(ve.raw, idx); ok { + rootErrors = append(rootErrors, fmt.Sprintf("L %s: %s", strings.Replace(leaf.InstanceLocation, i, name, 1), leaf.Message)) + } else { + rootErrors = append(rootErrors, fmt.Sprintf("L %s: %s", leaf.InstanceLocation, leaf.Message)) + } + } } - - if name, ok := apiKey["name"].(string); ok { - rows = append(rows, fmt.Sprintf("%sL API Key '%s'", prefix, name)) - } else { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L API Key at index "+matches[2])) + leaf = queue[0] + queue = queue[1:] + if len(leaf.Causes) > 0 { + queue = append(queue, leaf.Causes...) } - } else if ve.InstanceLocation == "" { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "Spacefile")) - } else { - rows = append(rows, fmt.Sprintf("%s%s", prefix, "L "+ve.InstanceLocation)) - } - - for _, c := range ve.Causes { - rows = append(rows, PrettyValidationErrors(c, v, prefix+" ")) } - return strings.Join(rows, "\n") + return fmt.Sprintf("validation failed:\n%s", strings.Join(rootErrors, "\n")) } func LoadSpacefile(projectDir string) (*Spacefile, error) { @@ -264,7 +128,10 @@ func LoadSpacefile(projectDir string) (*Spacefile, error) { if err := spacefileSchema.Validate(v); err != nil { var ve *jsonschema.ValidationError if errors.As(err, &ve) { - return nil, fmt.Errorf(PrettyValidationErrors(ve, v, "")) + return nil, SpacefileValidationError{ + wrapped: ve, + raw: v, + } } } From 36cc15b45af93105adeeb824a1dcbf725e1dba63 Mon Sep 17 00:00:00 2001 From: pomdtr Date: Thu, 4 May 2023 11:24:00 +0200 Subject: [PATCH 2/2] refactor code --- .vscode/settings.json | 14 ++++++------ internal/spacefile/spacefile.go | 38 +++++++++++++++++++-------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 7aeaf46..4ee31ee 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,8 @@ { - "files.associations": { - "Spacefile": "yaml" - }, - "yaml.schemas": { - "internal/spacefile/schemas/spacefile.json": [ - "Spacefile" - ] - } + "files.associations": { + "Spacefile": "yaml" + }, + "yaml.schemas": { + "internal/spacefile/schemas/spacefile.schema.json": ["Spacefile"] + } } diff --git a/internal/spacefile/spacefile.go b/internal/spacefile/spacefile.go index c076119..b6a47c1 100644 --- a/internal/spacefile/spacefile.go +++ b/internal/spacefile/spacefile.go @@ -78,25 +78,31 @@ func extractMicroName(v any, index int) (string, bool) { var microReg = regexp.MustCompile(`\/micros\/(\d+)`) func (ve SpacefileValidationError) Error() string { - leaf := ve.wrapped - queue := []*jsonschema.ValidationError{leaf} + errorMsg := func(leaf *jsonschema.ValidationError) string { + matches := microReg.FindStringSubmatch(leaf.InstanceLocation) + if len(matches) == 0 { + return fmt.Sprintf("L %s: %s", leaf.InstanceLocation, leaf.Message) + } + + i := matches[1] + idx, _ := strconv.Atoi(i) + name, ok := extractMicroName(ve.raw, idx) + if !ok { + return fmt.Sprintf("L %s: %s", leaf.InstanceLocation, leaf.Message) + } + + return fmt.Sprintf("L %s: %s", strings.Replace(leaf.InstanceLocation, i, name, 1), leaf.Message) + } + + queue := []*jsonschema.ValidationError{ve.wrapped} rootErrors := []string{} for len(queue) > 0 { - if len(leaf.Causes) == 0 { - if matches := microReg.FindStringSubmatch(leaf.InstanceLocation); len(matches) > 0 { - i := matches[1] - idx, _ := strconv.Atoi(i) - - if name, ok := extractMicroName(ve.raw, idx); ok { - rootErrors = append(rootErrors, fmt.Sprintf("L %s: %s", strings.Replace(leaf.InstanceLocation, i, name, 1), leaf.Message)) - } else { - rootErrors = append(rootErrors, fmt.Sprintf("L %s: %s", leaf.InstanceLocation, leaf.Message)) - } - } - } - leaf = queue[0] + leaf := queue[0] queue = queue[1:] - if len(leaf.Causes) > 0 { + + if len(leaf.Causes) == 0 { + rootErrors = append(rootErrors, errorMsg(leaf)) + } else { queue = append(queue, leaf.Causes...) } }