allow map[string]interface{}, and other such things, as scalars

This commit is contained in:
Ben Kraft
2021-04-21 11:32:49 -07:00
parent f97d33e6ae
commit 2272ad8a12
8 changed files with 54 additions and 22 deletions
+4 -3
View File
@@ -98,9 +98,10 @@ func TestGenerate(t *testing.T) {
Generated: goFilename,
ExportOperations: queriesFilename,
Scalars: map[string]string{
"ID": "github.com/me/mypkg.ID",
"DateTime": "time.Time",
"Junk": "interface{}",
"ID": "github.com/me/mypkg.ID",
"DateTime": "time.Time",
"Junk": "interface{}",
"ComplexJunk": "[]map[string]*[]*map[string]interface{}",
},
})
if err != nil {
+41 -13
View File
@@ -1,7 +1,9 @@
package generate
import (
"fmt"
"go/types"
"regexp"
"strconv"
"strings"
)
@@ -35,24 +37,49 @@ func (g *generator) ref(fullyQualifiedName string) (qualifiedName string, err er
return g.getRef(fullyQualifiedName, false)
}
var _sliceOrMapPrefixRegexp = regexp.MustCompile(`^(\*|\[\d*\]|map\[string\])*`)
func (g *generator) getRef(fullyQualifiedName string, addImport bool) (qualifiedName string, err error) {
i := strings.LastIndex(fullyQualifiedName, ".")
// Ideally, we want to allow a reference to basically an arbitrary symbol.
// But that's very hard, because it might be quite complicated, like
// struct{ F []map[mypkg.K]otherpkg.V }
// Now in practice, using an unnamed struct is not a great idea, but we do
// want to allow as much as we can that encoding/json knows how to work
// with, since you would reasonably expect us to accept, say,
// map[string][]interface{}. So we allow:
// - any named type (mypkg.T)
// - any predeclared basic type (string, int, etc.)
// - interface{}
// - for any allowed type T, *T, []T, [N]T, and map[string]T
// which effectively excludes:
// - unnamed struct types
// - map[K]V where K is a named type wrapping string
// - any nonstandard spelling of those (interface {/* hi */},
// map[ string ]T)
// TODO: document that somewhere visible
errorMsg := `invalid type-name "%v" (%v); expected a builtin, ` +
`path/to/package.Name, interface{}, or a slice, map, or pointer of those`
if strings.Contains(fullyQualifiedName, " ") {
// TODO: pass in pos here and below
return "", errorf(nil, errorMsg, fullyQualifiedName, "contains spaces")
}
prefix := _sliceOrMapPrefixRegexp.FindString(fullyQualifiedName)
nameToImport := fullyQualifiedName[len(prefix):]
i := strings.LastIndex(nameToImport, ".")
if i == -1 {
// We allow any builtin type, or interface{}. In principle it would be
// fine to allow any interface or struct, but (1) they might refer to a
// type that needs an import, and (2) that just honestly seems
// confusing, why would you want it. But the empty interface,
// specifically, is useful.
if fullyQualifiedName != "interface{}" && types.Universe.Lookup(fullyQualifiedName) == nil {
// TODO: pass in pos here
return "", errorf(nil,
`unknown name "%v"; expected a builtin or path/to/package.Name`, fullyQualifiedName)
if nameToImport != "interface{}" && types.Universe.Lookup(nameToImport) == nil {
return "", errorf(nil, errorMsg, fullyQualifiedName,
fmt.Sprintf(`unknown type-name "%v"`, nameToImport))
}
return fullyQualifiedName, nil
}
pkgPath := fullyQualifiedName[:i]
localName := fullyQualifiedName[i+1:]
pkgPath := nameToImport[:i]
localName := nameToImport[i+1:]
var alias string
if addImport {
alias = g.addImportFor(pkgPath)
@@ -60,10 +87,11 @@ func (g *generator) getRef(fullyQualifiedName string, addImport bool) (qualified
var ok bool
alias, ok = g.imports[pkgPath]
if !ok {
// This is an internal error, not a user error.
return "", errorf(nil, `no alias defined for package "%v"`, pkgPath)
}
}
return alias + "." + localName, nil
return prefix + alias + "." + localName, nil
}
// Returns the import-clause to use in the generated code.
+1 -1
View File
@@ -1 +1 @@
unknown name "bogus"; expected a builtin or path/to/package.Name
invalid type-name "bogus" (unknown type-name "bogus"); expected a builtin, path/to/package.Name, interface{}, or a slice, map, or pointer of those
+1 -1
View File
@@ -1 +1 @@
unknown name "bogus"; expected a builtin or path/to/package.Name
invalid type-name "bogus" (unknown type-name "bogus"); expected a builtin, path/to/package.Name, interface{}, or a slice, map, or pointer of those
+1 -1
View File
@@ -1 +1 @@
query EmptyInterface { getJunk }
query EmptyInterface { getJunk getComplexJunk }
+3 -1
View File
@@ -7,7 +7,8 @@ import (
)
type EmptyInterfaceResponse struct {
GetJunk interface{} `json:"getJunk"`
GetJunk interface{} `json:"getJunk"`
GetComplexJunk []map[string]*[]*map[string]interface{} `json:"getComplexJunk"`
}
func EmptyInterface(
@@ -20,6 +21,7 @@ func EmptyInterface(
`
query EmptyInterface {
getJunk
getComplexJunk
}
`,
&retval,
+1 -1
View File
@@ -2,7 +2,7 @@
"operations": [
{
"operationName": "EmptyInterface",
"query": "\nquery EmptyInterface {\n\tgetJunk\n}\n",
"query": "\nquery EmptyInterface {\n\tgetJunk\n\tgetComplexJunk\n}\n",
"sourceLocation": "testdata/queries/EmptyInterface.graphql"
}
]
+2 -1
View File
@@ -1,6 +1,6 @@
scalar DateTime
scalar Junk
scalar ComplexJunk
enum Role {
STUDENT
@@ -67,6 +67,7 @@ type Query {
convert(dt: DateTime!, tz: String): DateTime!
maybeConvert(dt: DateTime, tz: String): DateTime
getJunk: Junk
getComplexJunk: ComplexJunk
listOfListsOfLists: [[[String!]!]!]!
}