mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
This interface allows it to create rich codersdk errors and pass them up to the `wsbuilder` error handling.
130 lines
3.3 KiB
Go
130 lines
3.3 KiB
Go
package dynamicparameters
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"sort"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/coder/coder/v2/codersdk"
|
|
)
|
|
|
|
func ParameterValidationError(diags hcl.Diagnostics) *DiagnosticError {
|
|
return &DiagnosticError{
|
|
Message: "Unable to validate parameters",
|
|
Diagnostics: diags,
|
|
KeyedDiagnostics: make(map[string]hcl.Diagnostics),
|
|
}
|
|
}
|
|
|
|
func TagValidationError(diags hcl.Diagnostics) *DiagnosticError {
|
|
return &DiagnosticError{
|
|
Message: "Failed to parse workspace tags",
|
|
Diagnostics: diags,
|
|
KeyedDiagnostics: make(map[string]hcl.Diagnostics),
|
|
}
|
|
}
|
|
|
|
type DiagnosticError struct {
|
|
// Message is the human-readable message that will be returned to the user.
|
|
Message string
|
|
// Diagnostics are top level diagnostics that will be returned as "Detail" in the response.
|
|
Diagnostics hcl.Diagnostics
|
|
// KeyedDiagnostics translate to Validation errors in the response. A key could
|
|
// be a parameter name, or a tag name. This allows diagnostics to be more closely
|
|
// associated with a specific index/parameter/tag.
|
|
KeyedDiagnostics map[string]hcl.Diagnostics
|
|
}
|
|
|
|
// Error is a pretty bad format for these errors. Try to avoid using this.
|
|
func (e *DiagnosticError) Error() string {
|
|
var diags hcl.Diagnostics
|
|
diags = diags.Extend(e.Diagnostics)
|
|
for _, d := range e.KeyedDiagnostics {
|
|
diags = diags.Extend(d)
|
|
}
|
|
|
|
return diags.Error()
|
|
}
|
|
|
|
func (e *DiagnosticError) HasError() bool {
|
|
if e.Diagnostics.HasErrors() {
|
|
return true
|
|
}
|
|
|
|
for _, diags := range e.KeyedDiagnostics {
|
|
if diags.HasErrors() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (e *DiagnosticError) Append(key string, diag *hcl.Diagnostic) {
|
|
e.Extend(key, hcl.Diagnostics{diag})
|
|
}
|
|
|
|
func (e *DiagnosticError) Extend(key string, diag hcl.Diagnostics) {
|
|
if e.KeyedDiagnostics == nil {
|
|
e.KeyedDiagnostics = make(map[string]hcl.Diagnostics)
|
|
}
|
|
if _, ok := e.KeyedDiagnostics[key]; !ok {
|
|
e.KeyedDiagnostics[key] = hcl.Diagnostics{}
|
|
}
|
|
e.KeyedDiagnostics[key] = e.KeyedDiagnostics[key].Extend(diag)
|
|
}
|
|
|
|
func (e *DiagnosticError) Response() (int, codersdk.Response) {
|
|
resp := codersdk.Response{
|
|
Message: e.Message,
|
|
Validations: nil,
|
|
}
|
|
|
|
// Sort the parameter names so that the order is consistent.
|
|
sortedNames := make([]string, 0, len(e.KeyedDiagnostics))
|
|
for name := range e.KeyedDiagnostics {
|
|
sortedNames = append(sortedNames, name)
|
|
}
|
|
sort.Strings(sortedNames)
|
|
|
|
for _, name := range sortedNames {
|
|
diag := e.KeyedDiagnostics[name]
|
|
resp.Validations = append(resp.Validations, codersdk.ValidationError{
|
|
Field: name,
|
|
Detail: DiagnosticsErrorString(diag),
|
|
})
|
|
}
|
|
|
|
if e.Diagnostics.HasErrors() {
|
|
resp.Detail = DiagnosticsErrorString(e.Diagnostics)
|
|
}
|
|
|
|
return http.StatusBadRequest, resp
|
|
}
|
|
|
|
func DiagnosticErrorString(d *hcl.Diagnostic) string {
|
|
return fmt.Sprintf("%s; %s", d.Summary, d.Detail)
|
|
}
|
|
|
|
func DiagnosticsErrorString(d hcl.Diagnostics) string {
|
|
count := len(d)
|
|
switch {
|
|
case count == 0:
|
|
return "no diagnostics"
|
|
case count == 1:
|
|
return DiagnosticErrorString(d[0])
|
|
default:
|
|
for _, d := range d {
|
|
// Render the first error diag.
|
|
// If there are warnings, do not priority them over errors.
|
|
if d.Severity == hcl.DiagError {
|
|
return fmt.Sprintf("%s, and %d other diagnostic(s)", DiagnosticErrorString(d), count-1)
|
|
}
|
|
}
|
|
|
|
// All warnings? ok...
|
|
return fmt.Sprintf("%s, and %d other diagnostic(s)", DiagnosticErrorString(d[0]), count-1)
|
|
}
|
|
}
|