bigquery: produce better error messages for InferSchema
Fixes #1335.
Change-Id: I415dcd0749eb07fb754e5812aaf12d8a86576906
Reviewed-on: https://code-review.googlesource.com/c/gocloud/+/38630
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Jean de Klerk <deklerk@google.com>
diff --git a/bigquery/params.go b/bigquery/params.go
index 868f2bb..5957022 100644
--- a/bigquery/params.go
+++ b/bigquery/params.go
@@ -44,7 +44,7 @@
return "", false, nil, err
}
if name != "" && !validFieldName.MatchString(name) {
- return "", false, nil, errInvalidFieldName
+ return "", false, nil, invalidFieldNameError(name)
}
for _, opt := range opts {
if opt != nullableTagOption {
@@ -56,6 +56,12 @@
return name, keep, opts, nil
}
+type invalidFieldNameError string
+
+func (e invalidFieldNameError) Error() string {
+ return fmt.Sprintf("bigquery: invalid name %q of field in struct", string(e))
+}
+
var fieldCache = fields.NewCache(bqTagParser, nil, nil)
var (
diff --git a/bigquery/schema.go b/bigquery/schema.go
index 3936aa4..a170c7b 100644
--- a/bigquery/schema.go
+++ b/bigquery/schema.go
@@ -137,12 +137,8 @@
)
var (
- errNoStruct = errors.New("bigquery: can only infer schema from struct or pointer to struct")
- errUnsupportedFieldType = errors.New("bigquery: unsupported type of field in struct")
- errInvalidFieldName = errors.New("bigquery: invalid name of field in struct")
- errBadNullable = errors.New(`bigquery: use "nullable" only for []byte and struct pointers; for all other types, use a NullXXX type`)
- errEmptyJSONSchema = errors.New("bigquery: empty JSON schema")
- fieldTypes = map[FieldType]bool{
+ errEmptyJSONSchema = errors.New("bigquery: empty JSON schema")
+ fieldTypes = map[FieldType]bool{
StringFieldType: true,
BytesFieldType: true,
IntegerFieldType: true,
@@ -265,7 +261,7 @@
switch t.Kind() {
case reflect.Ptr:
if t.Elem().Kind() != reflect.Struct {
- return nil, errNoStruct
+ return nil, noStructError{t}
}
t = t.Elem()
fallthrough
@@ -273,15 +269,15 @@
case reflect.Struct:
return inferFields(t)
default:
- return nil, errNoStruct
+ return nil, noStructError{t}
}
}
// inferFieldSchema infers the FieldSchema for a Go type
-func inferFieldSchema(rt reflect.Type, nullable bool) (*FieldSchema, error) {
+func inferFieldSchema(fieldName string, rt reflect.Type, nullable bool) (*FieldSchema, error) {
// Only []byte and struct pointers can be tagged nullable.
if nullable && !(rt == typeOfByteSlice || rt.Kind() == reflect.Ptr && rt.Elem().Kind() == reflect.Struct) {
- return nil, errBadNullable
+ return nil, badNullableError{fieldName, rt}
}
switch rt {
case typeOfByteSlice:
@@ -308,13 +304,13 @@
et := rt.Elem()
if et != typeOfByteSlice && (et.Kind() == reflect.Slice || et.Kind() == reflect.Array) {
// Multi dimensional slices/arrays are not supported by BigQuery
- return nil, errUnsupportedFieldType
+ return nil, unsupportedFieldTypeError{fieldName, rt}
}
if nullableFieldType(et) != "" {
// Repeated nullable types are not supported by BigQuery.
- return nil, errUnsupportedFieldType
+ return nil, unsupportedFieldTypeError{fieldName, rt}
}
- f, err := inferFieldSchema(et, false)
+ f, err := inferFieldSchema(fieldName, et, false)
if err != nil {
return nil, err
}
@@ -323,7 +319,7 @@
return f, nil
case reflect.Ptr:
if rt.Elem().Kind() != reflect.Struct {
- return nil, errUnsupportedFieldType
+ return nil, unsupportedFieldTypeError{fieldName, rt}
}
fallthrough
case reflect.Struct:
@@ -339,7 +335,7 @@
case reflect.Float32, reflect.Float64:
return &FieldSchema{Required: !nullable, Type: FloatFieldType}, nil
default:
- return nil, errUnsupportedFieldType
+ return nil, unsupportedFieldTypeError{fieldName, rt}
}
}
@@ -358,7 +354,7 @@
break
}
}
- f, err := inferFieldSchema(field.Type, nullable)
+ f, err := inferFieldSchema(field.Name, field.Type, nullable)
if err != nil {
return nil, err
}
@@ -494,3 +490,29 @@
return convertSchemaFromJSON(bigQuerySchema)
}
+
+type noStructError struct {
+ typ reflect.Type
+}
+
+func (e noStructError) Error() string {
+ return fmt.Sprintf("bigquery: can only infer schema from struct or pointer to struct, not %s", e.typ)
+}
+
+type badNullableError struct {
+ name string
+ typ reflect.Type
+}
+
+func (e badNullableError) Error() string {
+ return fmt.Sprintf(`bigquery: field %q of type %s: use "nullable" only for []byte and struct pointers; for all other types, use a NullXXX type`, e.name, e.typ)
+}
+
+type unsupportedFieldTypeError struct {
+ name string
+ typ reflect.Type
+}
+
+func (e unsupportedFieldTypeError) Error() string {
+ return fmt.Sprintf("bigquery: field %q: type %s is not supported", e.name, e.typ)
+}
diff --git a/bigquery/schema_test.go b/bigquery/schema_test.go
index 9eb0a39..ce4a750 100644
--- a/bigquery/schema_test.go
+++ b/bigquery/schema_test.go
@@ -714,52 +714,31 @@
}
func TestTagInferenceErrors(t *testing.T) {
- testCases := []struct {
- in interface{}
- err error
- }{
- {
- in: struct {
- LongTag int `bigquery:"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy"`
- }{},
- err: errInvalidFieldName,
- },
- {
- in: struct {
- UnsupporedStartChar int `bigquery:"øab"`
- }{},
- err: errInvalidFieldName,
- },
- {
- in: struct {
- UnsupportedEndChar int `bigquery:"abø"`
- }{},
- err: errInvalidFieldName,
- },
- {
- in: struct {
- UnsupportedMiddleChar int `bigquery:"aøb"`
- }{},
- err: errInvalidFieldName,
- },
- {
- in: struct {
- StartInt int `bigquery:"1abc"`
- }{},
- err: errInvalidFieldName,
- },
- {
- in: struct {
- Hyphens int `bigquery:"a-b"`
- }{},
- err: errInvalidFieldName,
- },
+ testCases := []interface{}{
+ struct {
+ LongTag int `bigquery:"abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxy"`
+ }{},
+ struct {
+ UnsupporedStartChar int `bigquery:"øab"`
+ }{},
+ struct {
+ UnsupportedEndChar int `bigquery:"abø"`
+ }{},
+ struct {
+ UnsupportedMiddleChar int `bigquery:"aøb"`
+ }{},
+ struct {
+ StartInt int `bigquery:"1abc"`
+ }{},
+ struct {
+ Hyphens int `bigquery:"a-b"`
+ }{},
}
for i, tc := range testCases {
- want := tc.err
- _, got := InferSchema(tc.in)
- if got != want {
- t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant:\n%#v", i, got, want)
+
+ _, got := InferSchema(tc)
+ if _, ok := got.(invalidFieldNameError); !ok {
+ t.Errorf("%d: inferring TableSchema: got:\n%#v\nwant invalidFieldNameError", i, got)
}
}
@@ -773,115 +752,114 @@
func TestSchemaErrors(t *testing.T) {
testCases := []struct {
- in interface{}
- err error
+ in interface{}
+ want interface{}
}{
{
- in: []byte{},
- err: errNoStruct,
+ in: []byte{},
+ want: noStructError{},
},
{
- in: new(int),
- err: errNoStruct,
+ in: new(int),
+ want: noStructError{},
},
{
- in: struct{ Uint uint }{},
- err: errUnsupportedFieldType,
+ in: struct{ Uint uint }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ Uint64 uint64 }{},
- err: errUnsupportedFieldType,
+ in: struct{ Uint64 uint64 }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ Uintptr uintptr }{},
- err: errUnsupportedFieldType,
+ in: struct{ Uintptr uintptr }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ Complex complex64 }{},
- err: errUnsupportedFieldType,
+ in: struct{ Complex complex64 }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ Map map[string]int }{},
- err: errUnsupportedFieldType,
+ in: struct{ Map map[string]int }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ Chan chan bool }{},
- err: errUnsupportedFieldType,
+ in: struct{ Chan chan bool }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ Ptr *int }{},
- err: errUnsupportedFieldType,
+ in: struct{ Ptr *int }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ Interface interface{} }{},
- err: errUnsupportedFieldType,
+ in: struct{ Interface interface{} }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ MultiDimensional [][]int }{},
- err: errUnsupportedFieldType,
+ in: struct{ MultiDimensional [][]int }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ MultiDimensional [][][]byte }{},
- err: errUnsupportedFieldType,
+ in: struct{ MultiDimensional [][][]byte }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ SliceOfPointer []*int }{},
- err: errUnsupportedFieldType,
+ in: struct{ SliceOfPointer []*int }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ SliceOfNull []NullInt64 }{},
- err: errUnsupportedFieldType,
+ in: struct{ SliceOfNull []NullInt64 }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ ChanSlice []chan bool }{},
- err: errUnsupportedFieldType,
+ in: struct{ ChanSlice []chan bool }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ NestedChan struct{ Chan []chan bool } }{},
- err: errUnsupportedFieldType,
+ in: struct{ NestedChan struct{ Chan []chan bool } }{},
+ want: unsupportedFieldTypeError{},
},
{
in: struct {
X int `bigquery:",nullable"`
}{},
- err: errBadNullable,
+ want: badNullableError{},
},
{
in: struct {
X bool `bigquery:",nullable"`
}{},
- err: errBadNullable,
+ want: badNullableError{},
},
{
in: struct {
X struct{ N int } `bigquery:",nullable"`
}{},
- err: errBadNullable,
+ want: badNullableError{},
},
{
in: struct {
X []int `bigquery:",nullable"`
}{},
- err: errBadNullable,
+ want: badNullableError{},
},
{
- in: struct{ X *[]byte }{},
- err: errUnsupportedFieldType,
+ in: struct{ X *[]byte }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ X *[]int }{},
- err: errUnsupportedFieldType,
+ in: struct{ X *[]int }{},
+ want: unsupportedFieldTypeError{},
},
{
- in: struct{ X *int }{},
- err: errUnsupportedFieldType,
+ in: struct{ X *int }{},
+ want: unsupportedFieldTypeError{},
},
}
for _, tc := range testCases {
- want := tc.err
_, got := InferSchema(tc.in)
- if got != want {
- t.Errorf("%#v: got:\n%#v\nwant:\n%#v", tc.in, got, want)
+ if reflect.TypeOf(got) != reflect.TypeOf(tc.want) {
+ t.Errorf("%#v: got:\n%#v\nwant type %T", tc.in, got, tc.want)
}
}
}