Skip to content

Commit

Permalink
Return errors instead of panics when something goes wrong (#105)
Browse files Browse the repository at this point in the history
* Return errors instead of panics when something goes wrong

* Show field with error and update error messages

* Update error msg

* Update generator.go

Co-authored-by: sam boyer <sdboyer@grafana.com>

---------

Co-authored-by: sam boyer <sdboyer@grafana.com>
  • Loading branch information
spinillos and sam boyer authored Jul 10, 2023
1 parent 7576a48 commit f7f3d2e
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 39 deletions.
3 changes: 3 additions & 0 deletions generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ func TestGenerate(t *testing.T) {
t.Fatal(err)
}
out, err := cuetsy.Generate(i.Value(), cuetsy.Config{
ImportMapper: func(path string) (string, error) {
return path, nil
},
Export: true,
})
if c.CaseType == ErrorType {
Expand Down
89 changes: 54 additions & 35 deletions generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ func (g *generator) genType(name string, v cue.Value) []ts.Decl {
}
tokens = append(tokens, tok)
default:
g.addErr(valError(v, "typescript types may only be generated from a single value or disjunction of values"))
g.addErr(errors.New("typescript types may only be generated from a single value or disjunction of values"))
}

ret := make([]ts.Decl, 2)
Expand Down Expand Up @@ -322,7 +322,7 @@ func enumDefault(v cue.Value) (*tsast.Ident, error) {
a := v.Attribute(attrname)
val, found, err := a.Lookup(0, attrEnumMembers)
if err != nil || !found {
panic(fmt.Sprintf("looking up memberNames: found=%t err=%s", found, err))
return nil, valError(v, "Looking for memberNames: found=%t err=%s", found, err)
}
evals := strings.Split(val, "|")

Expand Down Expand Up @@ -350,7 +350,7 @@ func enumPairs(v cue.Value) ([]enumPair, error) {
a := v.Attribute(attrname)
val, found, err := a.Lookup(0, attrEnumMembers)
if err != nil {
panic(fmt.Sprintf("looking up memberNames: found=%t err=%s", found, err))
return nil, valError(v, "Looking for memberNames: found=%t err=%s", found, err)
}

var evals []string
Expand All @@ -362,7 +362,7 @@ func enumPairs(v cue.Value) ([]enumPair, error) {
evals = append(evals, strings.Title(s))
}
} else {
return nil, fmt.Errorf("must provide memberNames attribute for non-string enums")
return nil, valError(v, "must provide memberNames attribute for non-string enums")
}

var pairs []enumPair
Expand Down Expand Up @@ -423,11 +423,15 @@ func orEnum(v cue.Value) ([]ts.Expr, error) {
return nil, valError(v, "title casing of enum member %q produces an invalid typescript identifier; memberNames must be explicitly given in @cuetsy attribute", text)
}

val, err := tsprintConcrete(dv)
if err != nil {
return nil, err
}
fields = append(fields, tsast.AssignExpr{
// Simple mapping of all enum values (which we are assuming are in
// lowerCamelCase) to corresponding CamelCase
Name: id,
Value: tsprintConcrete(dv),
Value: val,
})
}

Expand Down Expand Up @@ -614,7 +618,8 @@ func findExtends(v cue.Value) ([]ts.Expr, cue.Value, error) {
// one containing the unified result - more complicated with no obvious benefit.
for _, dv := range dvals {
if dv.IncompleteKind() != cue.StructKind && dv.IncompleteKind() != cue.TopKind {
panic("impossible? seems like it should be. if this pops, clearly not!")
// impossible? seems like it should be. if this pops, clearly not!
return valError(v, "error while finding extends")
}

if err := walkExpr(dv); err != nil {
Expand All @@ -623,7 +628,7 @@ func findExtends(v cue.Value) ([]ts.Expr, cue.Value, error) {
}
return nil
default:
panic(fmt.Sprintf("unhandled op type %s", op.String()))
return valError(v, "unhandled op type while finding type to extend: %s", op.String())
}
}

Expand All @@ -645,7 +650,7 @@ func (g *generator) genInterfaceField(v cue.Value) (*typeRef, error) {
tref.T, err = g.tsprintField(v, true, false)
if err != nil {
if !containsCuetsyReference(v) {
g.addErr(valError(v, "could not generate field: %w", err))
g.addErr(err)
return nil, err
}
g.addErr(err)
Expand Down Expand Up @@ -726,11 +731,17 @@ func (g *generator) genEnumReference(v cue.Value) (*typeRef, error) {
var enumUnions map[cue.Value]cue.Value
switch len(conjuncts) {
case 0:
panic("unreachable")
ve := valError(v, "unreachable: no conjuncts while looking for enum references")
g.addErr(ve)
return nil, ve
case 1:
// This case is when we have a union of enums which we need to iterate them to get their values or has a default value.
// It retrieves a list of literals with their references.
enumUnions = g.findEnumUnions(v)
var err error
enumUnions, err = g.findEnumUnions(v)
if err != nil {
return nil, err
}
case 2:
var err error
conjuncts[1] = getDefaultEnumValue(conjuncts[1])
Expand Down Expand Up @@ -787,7 +798,7 @@ func (g *generator) genEnumReference(v cue.Value) (*typeRef, error) {
case 1, 2:
ref.T, err = referenceValueAs(referrer, TypeEnum)
if err != nil {
panic(err)
return nil, err
}
}

Expand Down Expand Up @@ -842,40 +853,40 @@ func (g *generator) genEnumReference(v cue.Value) (*typeRef, error) {
}

// findEnumUnions find the unions between enums like (#Enum & "a") | (#Enum & "b")
func (g generator) findEnumUnions(v cue.Value) map[cue.Value]cue.Value {
func (g generator) findEnumUnions(v cue.Value) (map[cue.Value]cue.Value, error) {
op, values := v.Expr()
if op != cue.OrOp {
return nil
return nil, nil
}

enumsWithUnions := make(map[cue.Value]cue.Value, len(values))
for _, val := range values {
conjuncts := appendSplit(nil, cue.AndOp, val)
if len(conjuncts) != 2 {
return nil
return nil, nil
}
cr, lit := conjuncts[0], conjuncts[1]
if cr.Subsume(lit) != nil {
return nil
return nil, nil
}

switch val.Kind() {
case cue.StringKind, cue.IntKind:
enumValues, _, has := findRefWithKind(v, TypeEnum)
if !has {
return nil
return nil, nil
}
enumsWithUnions[lit] = enumValues
default:
_, vals := val.Expr()
if len(vals) > 1 {
panic(fmt.Sprintf("%s.%s isn't a valid enum value", val.Path().String(), vals[1]))
return nil, valError(v, "%s.%s isn't a valid enum value", val.Path().String(), vals[1])
}
panic(fmt.Sprintf("Invalid value in path %s", val.Path().String()))
return nil, valError(v, "Invalid value in path %s", val.Path().String())
}
}

return enumsWithUnions
return enumsWithUnions, nil
}

func (g generator) findIdent(v, ev, tv cue.Value, fn func(tsast.Ident)) error {
Expand All @@ -895,7 +906,8 @@ func (g generator) findIdent(v, ev, tv cue.Value, fn func(tsast.Ident)) error {
}
}

panic(fmt.Sprintf("unreachable - %#v not equal to any member of %#v, but should have been caught by subsume check", tv, ev))
// unreachable?
return valError(v, "%#v not equal to any member of %#v, but should have been caught by subsume check", tv, ev)
}

func getEnumLiteral(conjuncts []cue.Value) (*cue.Value, error) {
Expand Down Expand Up @@ -997,7 +1009,7 @@ func (g generator) tsPrintDefault(v cue.Value) (bool, ts.Expr, error) {
t.Name = "default" + t.Name
expr = t
default:
panic(fmt.Sprintf("unexpected type %T", expr))
return false, nil, fmt.Errorf("unexpected type %T", expr)
}
}

Expand Down Expand Up @@ -1102,7 +1114,7 @@ func (g generator) tsprintField(v cue.Value, isType bool, isDefault bool) (ts.Ex

return tsast.ObjectLit{Elems: kvs, IsType: isType}, nil
default:
panic(fmt.Sprintf("not expecting op type %d", op))
return nil, valError(v, "not expecting op type %d", op)
}
case cue.ListKind:
// A list is concrete (and thus its complete kind is ListKind instead of
Expand All @@ -1123,7 +1135,7 @@ func (g generator) tsprintField(v cue.Value, isType bool, isDefault bool) (ts.Ex
}
return ts.List(elems...), nil
case cue.StringKind, cue.BoolKind, cue.FloatKind, cue.IntKind:
return tsprintConcrete(v), nil
return tsprintConcrete(v)
case cue.BytesKind:
return nil, valError(v, "bytes have no equivalent in Typescript; use double-quotes (string) instead")
}
Expand Down Expand Up @@ -1186,7 +1198,8 @@ func (g generator) tsprintField(v cue.Value, isType bool, isDefault bool) (ts.Ex
}
return tsast.ListExpr{Expr: expr}, nil
} else {
panic("unreachable - open list must have a type")
// unreachable?
return nil, errors.New("open list must have a type")
}
case cue.NumberKind, cue.StringKind:
// It appears there are only three cases in which we can have an
Expand Down Expand Up @@ -1239,7 +1252,7 @@ func (g generator) tsprintField(v cue.Value, isType bool, isDefault bool) (ts.Ex
return disj(args)
}
default:
panic("unreachable...?")
return nil, valError(v, "no handler for operator: '%s' for kind '%s'", op.String(), ik)
}
fallthrough
case cue.TopKind:
Expand Down Expand Up @@ -1272,24 +1285,24 @@ func getValuesWithDefaults(v cue.Value, cuetsyType cue.Value) []cue.Value {

// ONLY call this function if it has been established that the provided Value is
// Concrete.
func tsprintConcrete(v cue.Value) ts.Expr {
func tsprintConcrete(v cue.Value) (ts.Expr, error) {
switch v.Kind() {
case cue.NullKind:
return ts.Null()
return ts.Null(), nil
case cue.StringKind:
s, _ := v.String()
return ts.Str(s)
return ts.Str(s), nil
case cue.FloatKind:
f, _ := v.Float64()
return ts.Float(f)
return ts.Float(f), nil
case cue.NumberKind, cue.IntKind:
i, _ := v.Int64()
return ts.Int(i)
return ts.Int(i), nil
case cue.BoolKind:
b, _ := v.Bool()
return ts.Bool(b)
return ts.Bool(b), nil
default:
panic("unreachable")
return nil, valError(v, "concrete kind not found: %s", v.Kind())
}
}

Expand All @@ -1315,7 +1328,13 @@ func valError(v cue.Value, format string, args ...interface{}) error {
if s == nil {
return fmt.Errorf(format, args...)
}
return errors.Newf(s.Pos(), format, args...)

msg := ""
if i, ok := s.(*ast.Field); ok {
msg = fmt.Sprintf("Found an error in the field '%s:%d:%d'. ", i.Label, s.Pos().Line(), s.Pos().Column())
}
f := fmt.Sprintf("%sError: %s", msg, format)
return errors.Newf(s.Pos(), f, args...)
}

func refAsInterface(v cue.Value) (ts.Expr, error) {
Expand Down Expand Up @@ -1349,7 +1368,7 @@ func refAsInterface(v cue.Value) (ts.Expr, error) {
if targetsKind(deref, TypeInterface) {
str, ok := dvals[0].Source().(fmt.Stringer)
if !ok {
panic("expected dvals[0].Source() to implement String()")
return nil, valError(v, "expected dvals[0].Source() to implement String()")
}

return tsast.SelectorExpr{
Expand Down Expand Up @@ -1432,7 +1451,7 @@ func referenceValueAs(v cue.Value, kinds ...TSType) (ts.Expr, error) {
if targetsKind(deref, kinds...) {
str, ok := dvals[0].Source().(fmt.Stringer)
if !ok {
panic("expected dvals[0].Source() to implement String()")
return nil, valError(v, "expected dvals[0].Source() to implement String()")
}

return tsast.SelectorExpr{
Expand Down
19 changes: 19 additions & 0 deletions testdata/bad_definition_error.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- cue --
package test

#Test: {
#Type1: {
group: string
options?: [...string]
}
#Type2: {
group: string
details: {
[string]: _
}
}
#UnionType: #Type1 | #Type2
union: #UnionType
} @cuetsy(kind="interface")
-- err --
Found an error in the field 'union:15:5'. Error: no handler for operator: '.' for kind 'struct'
2 changes: 1 addition & 1 deletion testdata/disjunct_struct_error.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ Out: {
} @cuetsy(kind="interface")

-- err --
typescript interfaces cannot be constructed from disjunctions
Found an error in the field 'Out:3:1'. Error: typescript interfaces cannot be constructed from disjunctions
2 changes: 1 addition & 1 deletion testdata/mismatch_len_error.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
E3: "a" | "b" | "c" @cuetsy(kind="enum",memberNames="a|b")

-- err --
typescript enums and memberNames attributes size doesn't match
Found an error in the field 'E3:1:1'. Error: typescript enums and memberNames attributes size doesn't match
2 changes: 1 addition & 1 deletion testdata/nameless_numerics_error.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
ErrNamelessNumerics: 0 | 1 | 2 @cuetsy(kind="enum")

-- err --
typescript numeric enums may only be generated from memberNames attribute
Found an error in the field 'ErrNamelessNumerics:1:1'. Error: typescript numeric enums may only be generated from memberNames attribute
2 changes: 1 addition & 1 deletion testdata/struct_enum_error.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ TableCellDisplayMode: {
} @cuetsy(kind="enum")

-- err --
typescript enums may only be generated from concrete strings, or ints with memberNames attribute
Found an error in the field 'TableCellDisplayMode:1:1'. Error: typescript enums may only be generated from concrete strings, or ints with memberNames attribute

0 comments on commit f7f3d2e

Please sign in to comment.