Reorganize documentation to make room to grow (#84)
## Summary: In this commit I reorganize much of our documentation into a new `docs` directory, where there will hopefully be more room to grow and to organize things in a user-friendly way. There's almost no net-new documentation, although of course it's a great time to review it anyway. In particular: - I moved the documentation for the `genqlient.yaml` config file into an example file instead of GoDoc (which now just points to the example file); I think this will be a lot clearer for casual users. - I moved the documentation for the `@genqlient` directive out of GoDoc and into a GraphQL schema file (since while it's a comment it's all real syntax), likewise, and made the `GenqlientDirective` type private (since there's now nothing useful to do with it). - I moved `DESIGN.md` and the logo into `docs/` (just to keep the toplevel a bit cleaner), and separated the Contributing section of the README into `docs/CONTRIBUTING.md` (which github will automatically link on various issue and PR pages). This leaves it so that: - README.md is the only documentation at the toplevel (and will become just the high-level introduction as I add more user docs to `docs/`) - GoDoc is only documentation for if you want to call genqlient programmatically (which is fairly limited as the API surface is quite small: it's now just Main, Generate, and Config, plus a constructor, a single method, and a bunch of fields on the latter) In future commits, I'll add some more new documentation to the `docs` directory. Issue: https://github.com/Khan/genqlient/issues/26 ## Test plan: make check (and read the docs) Author: benjaminjkraft Reviewers: jvoll, benjaminjkraft, aberkan, dnerdy, MiguelCastillo, mahtabsabet Required Reviewers: Approved By: jvoll Checks: ✅ Lint, ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Test (1.17), ✅ Test (1.16), ✅ Test (1.15), ✅ Test (1.14), ✅ Lint Pull Request URL: https://github.com/Khan/genqlient/pull/84
This commit is contained in:
+14
-107
@@ -15,92 +15,21 @@ import (
|
||||
//
|
||||
// Callers must call ValidateAndFillDefaults before using the config.
|
||||
type Config struct {
|
||||
// The filename with the GraphQL schema (in SDL format); defaults to
|
||||
// schema.graphql
|
||||
Schema string `yaml:"schema"`
|
||||
|
||||
// Filenames or globs with the operations for which to generate code;
|
||||
// defaults to genqlient.graphql.
|
||||
//
|
||||
// These may be .graphql files, containing the queries in SDL format, or
|
||||
// Go files, in which case any string-literal starting with (optional
|
||||
// whitespace and) the string "# @genqlient" will be extracted as a query.
|
||||
Operations []string `yaml:"operations"`
|
||||
|
||||
// If set, a file at this path will be generated containing the exact
|
||||
// operations that genqlient will send to the server.
|
||||
//
|
||||
// This is useful for systems which require queries to be explicitly
|
||||
// safelisted, especially for cases like queries involving fragments where
|
||||
// it may not exactly match the input queries. The JSON is an object of
|
||||
// the form
|
||||
// {"operations": [{
|
||||
// "operationName": "operationname",
|
||||
// "query": "query operationName { ... }",
|
||||
// "sourceLocation": "myqueriesfile.graphql",
|
||||
// }]}
|
||||
// Keys may be added in the future.
|
||||
//
|
||||
// By default, no such file is written.
|
||||
ExportOperations string `yaml:"export_operations"`
|
||||
|
||||
// The filename to which to write the generated code; defaults to
|
||||
// generated.go
|
||||
Generated string `yaml:"generated"`
|
||||
|
||||
// The package name for the output code; defaults to the directory name of
|
||||
// Generated
|
||||
Package string `yaml:"package"`
|
||||
|
||||
// Set to the fully-qualified name of a Go type which generated helpers
|
||||
// should accept and use as the context.Context for HTTP requests.
|
||||
//
|
||||
// Defaults to context.Context; set to "-" to omit context entirely (i.e.
|
||||
// use context.Background()). Must be a type which implements
|
||||
// context.Context.
|
||||
ContextType string `yaml:"context_type"`
|
||||
|
||||
// If set, a function to get a graphql.Client, perhaps from the context.
|
||||
// By default, the client must be passed explicitly to each genqlient
|
||||
// generated query-helper.
|
||||
//
|
||||
// This is useful if you have a shared client, either a global, or
|
||||
// available from context, and don't want to pass it explicitly. In this
|
||||
// case the signature of the genqlient-generated helpers will omit the
|
||||
// `graphql.Context` and they will call this function instead.
|
||||
//
|
||||
// Must be the fully-qualified name of a function which accepts a context
|
||||
// (of the type configured as ContextType (above), which defaults to
|
||||
// `context.Context`, or a function of no arguments if ContextType is set
|
||||
// to the empty string) and returns (graphql.Client, error). If the
|
||||
// client-getter returns an error, the helper will return the error
|
||||
// without making a query.
|
||||
ClientGetter string `yaml:"client_getter"`
|
||||
|
||||
// A map from GraphQL type name to Go fully-qualified type name to override
|
||||
// the Go type genqlient will use for this GraphQL type.
|
||||
//
|
||||
// This is primarily used for custom scalars, or to map builtin scalars to
|
||||
// a nonstandard type. By default, builtin scalars are mapped to the
|
||||
// obvious Go types (String and ID to string, Int to int, Float to float64,
|
||||
// and Boolean to bool), but this setting will extend or override those
|
||||
// mappings.
|
||||
//
|
||||
// genqlient does not validate these types in any way; they must define
|
||||
// whatever logic is needed (MarshalJSON/UnmarshalJSON or JSON tags) to
|
||||
// convert to/from JSON. For this reason, it's not recommended to use this
|
||||
// setting to map object, interface, or union types, because nothing
|
||||
// guarantees that the fields requested in the query match those present in
|
||||
// the Go type.
|
||||
//
|
||||
// To get equivalent behavior in just one query, use @genqlient(bind: ...);
|
||||
// see GenqlientDirective.Bind for more details.
|
||||
Bindings map[string]*TypeBinding `yaml:"bindings"`
|
||||
// The following fields are documented at:
|
||||
// https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||
Schema string `yaml:"schema"`
|
||||
Operations []string `yaml:"operations"`
|
||||
Generated string `yaml:"generated"`
|
||||
Package string `yaml:"package"`
|
||||
ExportOperations string `yaml:"export_operations"`
|
||||
ContextType string `yaml:"context_type"`
|
||||
ClientGetter string `yaml:"client_getter"`
|
||||
Bindings map[string]*TypeBinding `yaml:"bindings"`
|
||||
|
||||
// Set to true to use features that aren't fully ready to use.
|
||||
//
|
||||
// This is primarily intended for genqlient's own tests. These features
|
||||
// are likely BROKEN and come with NO EXPECTATION OF COMPATIBBILITY. Use
|
||||
// are likely BROKEN and come with NO EXPECTATION OF COMPATIBILITY. Use
|
||||
// them at your own risk!
|
||||
AllowBrokenFeatures bool `yaml:"allow_broken_features"`
|
||||
|
||||
@@ -110,32 +39,10 @@ type Config struct {
|
||||
}
|
||||
|
||||
// A TypeBinding represents a Go type to which genqlient will bind a particular
|
||||
// GraphQL type. See Config.Bind, above, for more details.
|
||||
// GraphQL type, and is documented further at:
|
||||
// https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||
type TypeBinding struct {
|
||||
// The fully-qualified name of the Go type to which to bind. For example:
|
||||
// time.Time
|
||||
// map[string]interface{}
|
||||
// github.com/you/yourpkg/subpkg.MyType
|
||||
Type string `yaml:"type"`
|
||||
// If set, a GraphQL selection which must exactly match the fields
|
||||
// requested whenever this type is used. Only applies if the GraphQL type
|
||||
// is a composite output type (object, interface, or union).
|
||||
//
|
||||
// This is useful if Type is a struct whose UnmarshalJSON or other methods
|
||||
// expect that you requested certain fields. You can specify those fields
|
||||
// like
|
||||
// MyType:
|
||||
// type: path/to/my.GoType
|
||||
// expect_exact_fields: "{ id name }"
|
||||
// and then genqlient will reject if you make a query
|
||||
// { fieldOfMytype { id title } }
|
||||
// The fields must match exactly, including the ordering: "{ name id }"
|
||||
// will be rejected. But the arguments and directives, if any, need not
|
||||
// match.
|
||||
//
|
||||
// TODO(benkraft): Also add ExpectIncludesFields and ExpectSubsetOfFields,
|
||||
// or something, if you want to say, for example, that you have to request
|
||||
// certain fields but others are optional.
|
||||
Type string `yaml:"type"`
|
||||
ExpectExactFields string `yaml:"expect_exact_fields"`
|
||||
}
|
||||
|
||||
|
||||
+9
-9
@@ -37,7 +37,7 @@ func (g *generator) baseTypeForOperation(operation ast.Operation) (*ast.Definiti
|
||||
// result will be unmarshaled.
|
||||
func (g *generator) convertOperation(
|
||||
operation *ast.OperationDefinition,
|
||||
queryOptions *GenqlientDirective,
|
||||
queryOptions *genqlientDirective,
|
||||
) (goType, error) {
|
||||
name := operation.Name + "Response"
|
||||
|
||||
@@ -87,7 +87,7 @@ var builtinTypes = map[string]string{
|
||||
// argument to a GraphQL operation.
|
||||
func (g *generator) convertInputType(
|
||||
typ *ast.Type,
|
||||
options, queryOptions *GenqlientDirective,
|
||||
options, queryOptions *genqlientDirective,
|
||||
) (goType, error) {
|
||||
// note prefix is ignored here (see generator.typeName), as is selectionSet
|
||||
// (for input types we use the whole thing)).
|
||||
@@ -102,7 +102,7 @@ func (g *generator) convertType(
|
||||
namePrefix *prefixList,
|
||||
typ *ast.Type,
|
||||
selectionSet ast.SelectionSet,
|
||||
options, queryOptions *GenqlientDirective,
|
||||
options, queryOptions *genqlientDirective,
|
||||
) (goType, error) {
|
||||
// We check for local bindings here, so that you can bind, say, a
|
||||
// `[String!]` to a struct instead of a slice. Global bindings can only
|
||||
@@ -145,7 +145,7 @@ func (g *generator) convertDefinition(
|
||||
def *ast.Definition,
|
||||
pos *ast.Position,
|
||||
selectionSet ast.SelectionSet,
|
||||
options, queryOptions *GenqlientDirective,
|
||||
options, queryOptions *genqlientDirective,
|
||||
) (goType, error) {
|
||||
// Check if we should use an existing type. (This is usually true for
|
||||
// GraphQL scalars, but we allow you to bind non-scalar types too, if you
|
||||
@@ -315,7 +315,7 @@ func (g *generator) convertSelectionSet(
|
||||
namePrefix *prefixList,
|
||||
selectionSet ast.SelectionSet,
|
||||
containingTypedef *ast.Definition,
|
||||
queryOptions *GenqlientDirective,
|
||||
queryOptions *genqlientDirective,
|
||||
) ([]*goStructField, error) {
|
||||
fields := make([]*goStructField, 0, len(selectionSet))
|
||||
for _, selection := range selectionSet {
|
||||
@@ -422,7 +422,7 @@ func (g *generator) convertSelectionSet(
|
||||
// the fragment's type. This is distinct from the rules for when a fragment
|
||||
// spread is legal, which is true when the fragment would be active for *any*
|
||||
// of the concrete types the spread-context could have (see
|
||||
// https://spec.graphql.org/draft/#sec-Fragment-Spreads or DESIGN.md).
|
||||
// https://spec.graphql.org/draft/#sec-Fragment-Spreads or docs/DESIGN.md).
|
||||
//
|
||||
// containingTypedef is as described in convertInlineFragment, below.
|
||||
// fragmentTypedef is the definition of the fragment's type-condition, i.e. the
|
||||
@@ -456,12 +456,12 @@ func fragmentMatches(containingTypedef, fragmentTypedef *ast.Definition) bool {
|
||||
//
|
||||
// In general, we treat such fragments' fields as if they were fields of the
|
||||
// parent selection-set (except of course they are only included in types the
|
||||
// fragment matches); see DESIGN.md for more.
|
||||
// fragment matches); see docs/DESIGN.md for more.
|
||||
func (g *generator) convertInlineFragment(
|
||||
namePrefix *prefixList,
|
||||
fragment *ast.InlineFragment,
|
||||
containingTypedef *ast.Definition,
|
||||
queryOptions *GenqlientDirective,
|
||||
queryOptions *genqlientDirective,
|
||||
) ([]*goStructField, error) {
|
||||
// You might think fragmentTypedef would be fragment.ObjectDefinition, but
|
||||
// actually that's the type into which the fragment is spread.
|
||||
@@ -601,7 +601,7 @@ func (g *generator) convertNamedFragment(fragment *ast.FragmentDefinition) (goTy
|
||||
func (g *generator) convertField(
|
||||
namePrefix *prefixList,
|
||||
field *ast.Field,
|
||||
fieldOptions, queryOptions *GenqlientDirective,
|
||||
fieldOptions, queryOptions *genqlientDirective,
|
||||
) (*goStructField, error) {
|
||||
if field.Definition == nil {
|
||||
// Unclear why gqlparser hasn't already rejected this,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Default genqlient config, see
|
||||
# go doc github.com/Khan/genqlient/generate.Config
|
||||
# for more options.
|
||||
# Default genqlient config; for full documentation see:
|
||||
# https://github.com/Khan/genqlient/blob/main/docs/genqlient.yaml
|
||||
schema: schema.graphql
|
||||
operations:
|
||||
- genqlient.graphql
|
||||
|
||||
@@ -69,7 +69,7 @@ type argument struct {
|
||||
GoType string
|
||||
GraphQLName string
|
||||
IsSlice bool
|
||||
Options *GenqlientDirective
|
||||
Options *genqlientDirective
|
||||
}
|
||||
|
||||
func newGenerator(
|
||||
@@ -139,7 +139,7 @@ func (g *generator) Types() (string, error) {
|
||||
|
||||
func (g *generator) getArgument(
|
||||
arg *ast.VariableDefinition,
|
||||
operationDirective *GenqlientDirective,
|
||||
operationDirective *genqlientDirective,
|
||||
) (argument, error) {
|
||||
_, directive, err := g.parsePrecedingComment(arg, arg.Position)
|
||||
if err != nil {
|
||||
|
||||
@@ -8,86 +8,17 @@ import (
|
||||
"github.com/vektah/gqlparser/v2/parser"
|
||||
)
|
||||
|
||||
// GenqlientDirective represents the @genqlient quasi-directive, used to
|
||||
// configure genqlient on a query-by-query basis.
|
||||
//
|
||||
// The syntax of the directive is just like a GraphQL directive, except it goes
|
||||
// in a comment on the line immediately preceding the field. (This is because
|
||||
// GraphQL expects directives in queries to be defined by the server, not by
|
||||
// the client, so it would reject a real @genqlient directive as nonexistent.)
|
||||
//
|
||||
// Directives may be applied to fields, arguments, or the entire query.
|
||||
// Directives on the line preceding the query apply to all relevant nodes in
|
||||
// the query; other directives apply to all nodes on the following line. (In
|
||||
// all cases it's fine for there to be other comments in between the directive
|
||||
// and the node(s) to which it applies.) For example, in the following query:
|
||||
// # @genqlient(n: "a")
|
||||
//
|
||||
// # @genqlient(n: "b")
|
||||
// #
|
||||
// # Comment describing the query
|
||||
// #
|
||||
// # @genqlient(n: "c")
|
||||
// query MyQuery(arg1: String,
|
||||
// # @genqlient(n: "d")
|
||||
// arg2: String, arg3: String,
|
||||
// arg4: String,
|
||||
// ) {
|
||||
// # @genqlient(n: "e")
|
||||
// field1, field2
|
||||
// field3
|
||||
// }
|
||||
// the directive "a" is ignored, "b" and "c" apply to all relevant nodes in the
|
||||
// query, "d" applies to arg2 and arg3, and "e" applies to field1 and field2.
|
||||
type GenqlientDirective struct {
|
||||
pos *ast.Position
|
||||
|
||||
// If set, this argument will be omitted if it's equal to its Go zero
|
||||
// value, or is an empty slice.
|
||||
//
|
||||
// For example, given the following query:
|
||||
// # @genqlient(omitempty: true)
|
||||
// query MyQuery(arg: String) { ... }
|
||||
// genqlient will generate a function
|
||||
// MyQuery(ctx context.Context, client graphql.Client, arg string) ...
|
||||
// which will pass {"arg": null} to GraphQL if arg is "", and the actual
|
||||
// value otherwise.
|
||||
//
|
||||
// Only applicable to arguments of nullable types.
|
||||
// Represents the genqlient directive, described in detail in
|
||||
// docs/genqlient_directive.graphql.
|
||||
type genqlientDirective struct {
|
||||
pos *ast.Position
|
||||
Omitempty *bool
|
||||
|
||||
// If set, this argument or field will use a pointer type in Go. Response
|
||||
// types always use pointers, but otherwise we typically do not.
|
||||
//
|
||||
// This can be useful if it's a type you'll need to pass around (and want a
|
||||
// pointer to save copies) or if you wish to distinguish between the Go
|
||||
// zero value and null (for nullable fields).
|
||||
Pointer *bool
|
||||
|
||||
// If set, this argument or field will use the given Go type instead of a
|
||||
// genqlient-generated type.
|
||||
//
|
||||
// The value should be the fully-qualified type name to use for the field,
|
||||
// for example:
|
||||
// time.Time
|
||||
// map[string]interface{}
|
||||
// []github.com/you/yourpkg/subpkg.MyType
|
||||
// Note that the type is the type of the whole field, e.g. if your field in
|
||||
// GraphQL has type `[DateTime]`, you'd do
|
||||
// # @genqlient(bind: "[]time.Time")
|
||||
// (But you're not required to; if you want to map to some type DateList,
|
||||
// you can do that, as long as its UnmarshalJSON method can accept a list
|
||||
// of datetimes.)
|
||||
//
|
||||
// See Config.Bindings for more details; this is effectively to a local
|
||||
// version of that global setting and should be used with similar care.
|
||||
// If set to "-", overrides any such global setting and uses a
|
||||
// genqlient-generated type.
|
||||
Bind string
|
||||
Pointer *bool
|
||||
Bind string
|
||||
}
|
||||
|
||||
func (dir *GenqlientDirective) GetOmitempty() bool { return dir.Omitempty != nil && *dir.Omitempty }
|
||||
func (dir *GenqlientDirective) GetPointer() bool { return dir.Pointer != nil && *dir.Pointer }
|
||||
func (dir *genqlientDirective) GetOmitempty() bool { return dir.Omitempty != nil && *dir.Omitempty }
|
||||
func (dir *genqlientDirective) GetPointer() bool { return dir.Pointer != nil && *dir.Pointer }
|
||||
|
||||
func setBool(dst **bool, v *ast.Value) error {
|
||||
ei, err := v.Value(nil) // no vars allowed
|
||||
@@ -113,14 +44,14 @@ func setString(dst *string, v *ast.Value) error {
|
||||
return errorf(v.Position, "expected string, got non-string value %T(%v)", ei, ei)
|
||||
}
|
||||
|
||||
func fromGraphQL(dir *ast.Directive) (*GenqlientDirective, error) {
|
||||
func fromGraphQL(dir *ast.Directive) (*genqlientDirective, error) {
|
||||
if dir.Name != "genqlient" {
|
||||
// Actually we just won't get here; we only get here if the line starts
|
||||
// with "# @genqlient", unless there's some sort of bug.
|
||||
return nil, errorf(dir.Position, "the only valid comment-directive is @genqlient, got %v", dir.Name)
|
||||
}
|
||||
|
||||
var retval GenqlientDirective
|
||||
var retval genqlientDirective
|
||||
retval.pos = dir.Position
|
||||
|
||||
var err error
|
||||
@@ -143,7 +74,7 @@ func fromGraphQL(dir *ast.Directive) (*GenqlientDirective, error) {
|
||||
return &retval, nil
|
||||
}
|
||||
|
||||
func (dir *GenqlientDirective) validate(node interface{}) error {
|
||||
func (dir *genqlientDirective) validate(node interface{}) error {
|
||||
switch node := node.(type) {
|
||||
case *ast.OperationDefinition:
|
||||
if dir.Bind != "" {
|
||||
@@ -177,7 +108,7 @@ func (dir *GenqlientDirective) validate(node interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (dir *GenqlientDirective) merge(other *GenqlientDirective) *GenqlientDirective {
|
||||
func (dir *genqlientDirective) merge(other *genqlientDirective) *genqlientDirective {
|
||||
retval := *dir
|
||||
if other.Omitempty != nil {
|
||||
retval.Omitempty = other.Omitempty
|
||||
@@ -194,8 +125,8 @@ func (dir *GenqlientDirective) merge(other *GenqlientDirective) *GenqlientDirect
|
||||
func (g *generator) parsePrecedingComment(
|
||||
node interface{},
|
||||
pos *ast.Position,
|
||||
) (comment string, directive *GenqlientDirective, err error) {
|
||||
directive = new(GenqlientDirective)
|
||||
) (comment string, directive *genqlientDirective, err error) {
|
||||
directive = new(genqlientDirective)
|
||||
if pos == nil || pos.Src == nil { // node was added by genqlient itself
|
||||
return "", directive, nil // treated as if there were no comment
|
||||
}
|
||||
|
||||
+4
-4
@@ -2,7 +2,7 @@ package generate
|
||||
|
||||
// This file generates the names for genqlient's generated types. This is
|
||||
// somewhat tricky because the names need to be unique, stable, and, to the
|
||||
// extent possible, human-readable and -writable. See DESIGN.md for an
|
||||
// extent possible, human-readable and -writable. See docs/DESIGN.md for an
|
||||
// overview of the considerations; in short, we need long names.
|
||||
//
|
||||
// Specifically, the names we generate are of the form:
|
||||
@@ -33,9 +33,9 @@ package generate
|
||||
// One subtlety in the above description is: is the "MyType" the interface or
|
||||
// the impelmentation? When it's a suffix, the answer is both: we generate
|
||||
// both MyFieldMyInterface and MyFieldMyImplementation, and the latter, in Go,
|
||||
// implements the former. (See DESIGN.md for more.) But as an infix, we use
|
||||
// the type on which the field is requested. Concretely, the following schema
|
||||
// and query:
|
||||
// implements the former. (See docs/DESIGN.md for more.) But as an infix, we
|
||||
// use the type on which the field is requested. Concretely, the following
|
||||
// schema and query:
|
||||
// type Query { f: I }
|
||||
// interface I { g: G }
|
||||
// type T implements I { g: G, h: H }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# We test all the spread cases from DESIGN.md, see there for more context on
|
||||
# each, as well as various other nonsense. But for abstract-in-abstract
|
||||
# We test all the spread cases from docs/DESIGN.md, see there for more context
|
||||
# on each, as well as various other nonsense. But for abstract-in-abstract
|
||||
# spreads, we can't test cases (4b) and (4c), where I implements J or vice
|
||||
# versa, because gqlparser doesn't support interfaces that implement other
|
||||
# interfaces yet.
|
||||
|
||||
+2
-2
@@ -858,8 +858,8 @@ type ComplexInlineFragmentsRootTopic struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// We test all the spread cases from DESIGN.md, see there for more context on
|
||||
// each, as well as various other nonsense. But for abstract-in-abstract
|
||||
// We test all the spread cases from docs/DESIGN.md, see there for more context
|
||||
// on each, as well as various other nonsense. But for abstract-in-abstract
|
||||
// spreads, we can't test cases (4b) and (4c), where I implements J or vice
|
||||
// versa, because gqlparser doesn't support interfaces that implement other
|
||||
// interfaces yet.
|
||||
|
||||
Reference in New Issue
Block a user