-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lsp: show correct opa error hint links (#728)
When regal encounters a parse error, we can sometimes workout the correct opa errors page to send the user to. This PR makes use of some existing code that matches errors to help pages. Signed-off-by: Charlie Egan <[email protected]>
- Loading branch information
1 parent
1aa0f50
commit 9d7530a
Showing
5 changed files
with
151 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
//nolint:lll | ||
package hints | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"regexp" | ||
|
||
"github.com/mitchellh/mapstructure" | ||
) | ||
|
||
// GetForError will return any matched, Styra-documented, errors for a given Go error. | ||
func GetForError(e error) ([]string, error) { | ||
e0 := unwrapToFP(e) | ||
|
||
msgs, err := extractMessages(e0) | ||
if err != nil { | ||
return []string{}, fmt.Errorf("failed to extract messages: %w", err) | ||
} | ||
|
||
if len(msgs) < 1 { | ||
return []string{}, errors.New("no messages found") | ||
} | ||
|
||
msg := msgs[0] | ||
|
||
var hintKeys []string | ||
|
||
for u, r := range patterns { | ||
if r.MatchString(msg) { | ||
hintKeys = append(hintKeys, u) | ||
} | ||
} | ||
|
||
return hintKeys, nil | ||
} | ||
|
||
//nolint:gochecknoglobals | ||
var patterns = map[string]*regexp.Regexp{ | ||
`eval-conflict-error/complete-rules-must-not-produce-multiple-outputs`: regexp.MustCompile(`^eval_conflict_error: complete rules must not produce multiple outputs$`), | ||
`eval-conflict-error/object-keys-must-be-unique`: regexp.MustCompile(`^object insert conflict$|^eval_conflict_error: object keys must be unique$`), | ||
`rego-unsafe-var-error/var-name-is-unsafe`: regexp.MustCompile(`^rego_unsafe_var_error: var .* is unsafe$`), | ||
`rego-recursion-error/rule-name-is-recursive`: regexp.MustCompile(`^rego_recursion_error: rule .* is recursive:`), | ||
`rego-parse-error/var-cannot-be-used-for-rule-name`: regexp.MustCompile(`^rego_parse_error: var cannot be used for rule name$`), | ||
`rego-type-error/conflicting-rules-name-found`: regexp.MustCompile(`^rego_type_error: conflicting rules .* found$`), | ||
`rego-type-error/match-error`: regexp.MustCompile(`^rego_type_error: match error`), | ||
`rego-type-error/arity-mismatch`: regexp.MustCompile(`^rego_type_error: .*: arity mismatch`), | ||
`rego-type-error/function-has-arity-got-argument`: regexp.MustCompile(`^rego_type_error: function .* has arity [0-9]+, got [0-9]+ arguments?$`), | ||
`rego-compile-error/assigned-var-name-unused`: regexp.MustCompile(`^rego_compile_error: assigned var .* unused$`), | ||
`rego-parse-error/unexpected-assign-token`: regexp.MustCompile(`^rego_parse_error: unexpected assign token:`), | ||
`rego-parse-error/unexpected-identifier-token`: regexp.MustCompile(`^rego_parse_error: unexpected identifier token:`), | ||
`rego-parse-error/unexpected-left-curly-token`: regexp.MustCompile(`^rego_parse_error: unexpected { token:`), | ||
`rego-parse-error/unexpected-right-curly-token`: regexp.MustCompile(`^rego_parse_error: unexpected } token`), | ||
`rego-parse-error/unexpected-name-keyword`: regexp.MustCompile(`^rego_parse_error: unexpected .* keyword:`), | ||
`rego-parse-error/unexpected-string-token`: regexp.MustCompile(`^rego_parse_error: unexpected string token:`), | ||
`rego-type-error/multiple-default-rules-name-found`: regexp.MustCompile(`^rego_type_error: multiple default rules .* found$`), | ||
} | ||
|
||
func unwrapToFP(e error) error { | ||
if w := errors.Unwrap(e); w != nil { | ||
return unwrapToFP(w) | ||
} | ||
|
||
return e | ||
} | ||
|
||
type message struct { | ||
Message string `json:"message"` | ||
Code string `json:"code"` | ||
} | ||
|
||
func extractMessages(e error) ([]string, error) { | ||
msgs := []message{} | ||
|
||
decoder, err := mapstructure.NewDecoder( | ||
&mapstructure.DecoderConfig{ | ||
TagName: "json", | ||
Result: &msgs, | ||
}, | ||
) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create decoder: %w", err) | ||
} | ||
|
||
if err := decoder.Decode(e); err != nil { | ||
return nil, fmt.Errorf("failed to decode error: %w", err) | ||
} | ||
|
||
m := make([]string, len(msgs)) | ||
|
||
for i := range msgs { | ||
m[i] = msgs[i].Code | ||
if m[i] != "" { | ||
m[i] += ": " | ||
} | ||
|
||
m[i] += msgs[i].Message | ||
} | ||
|
||
return m, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package hints | ||
|
||
import ( | ||
"slices" | ||
"testing" | ||
|
||
"github.com/styrainc/regal/internal/parse" | ||
) | ||
|
||
func TestHints(t *testing.T) { | ||
t.Parallel() | ||
|
||
mod := `package foo | ||
incomplete` | ||
|
||
_, err := parse.Module("test.rego", mod) | ||
if err == nil { | ||
t.Fatal("expected error") | ||
} | ||
|
||
hints, err := GetForError(err) | ||
if err != nil { | ||
t.Fatalf("unexpected error: %s", err) | ||
} | ||
|
||
expectedHints := []string{"rego-parse-error/var-cannot-be-used-for-rule-name"} | ||
if !slices.Equal(hints, expectedHints) { | ||
t.Fatalf("expected\n%v but got\n%v", expectedHints, hints) | ||
} | ||
} |