migrate

package
v0.7.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 19, 2026 License: MIT Imports: 22 Imported by: 0

Documentation

Overview

Package migrate generates a .defederator.yml plus a stub cross_service/client.go from an existing genqlient.yaml, so a service can switch from genqlient to defederator with minimal manual edits.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefederatorYAML

func DefederatorYAML(in *YAMLInput) (string, error)

DefederatorYAML converts a migration input into a .defederator.yml YAML string. Pure function — no I/O.

Conversion rules:

  • schema: → schema: (verbatim)
  • operations: → query: (same list)
  • generated: → client.filename: (path with genqlient replaced by defederator, ./ prefix)
  • bindings: → kept verbatim from genqlient.yaml (defederator generate runs inside the webapp module, so package paths like github.com/Khan/webapp/pkg/content.Author resolve correctly)
  • url_mode: enum is always added (webapp supergraph uses placeholder URLs)
  • generate.clientInterfaceName: FederationClient is always added
  • generate.optional: pointer is always added
  • INPUT_OBJECT types from the supergraph SDL are added as genqlient-package bindings

func LintFixHint

func LintFixHint() string

LintFixHint returns the single-line suggestion printed after a successful migrate run. After recompilation, the webapp kacontextinterface analyzer (ADR-429) commonly flags ctx interfaces that now transitively reference service_discovery.KAContext. The analyzer emits SuggestedFix records that golangci-lint applies in place when invoked with --fix, so the user can resolve the whole batch with one command rather than hand-editing each site.

func LoadOperationSources

func LoadOperationSources(patterns []string, baseDir string) ([]*ast.Source, error)

LoadOperationSources reads every operation file listed in genqlient.yaml's `operations:` (or `query:`) section and returns one gqlparser ast.Source per operation string. Glob patterns are expanded relative to baseDir.

Both .graphql and .go files are supported: .go files are scanned for `# @genqlient`-tagged string literals (via generator.QuerySourcesFromGoFile).

Used by migrate to identify which subgraphs and INPUT_OBJECT types the service's operations actually reference, so the generated `.defederator.yml` and `cross_service/client.go` can be pruned accordingly.

func OperationUsedEnums

func OperationUsedEnums(
	schema *ast.Schema,
	sources []*ast.Source,
) ([]string, error)

OperationUsedEnums returns the sorted, deduplicated set of enum type names that appear in any operation's variable types or response selection field types across the given sources, restricted to types declared in schema.

Used to emit enum bindings in `.defederator.yml` that point at the corresponding genqlient-generated Go types. This way the user's existing genqlient-typed values (e.g. genqlient.OptOutStatusOptedIn) can be passed to defederator client methods without casts.

func OperationVariableInputObjects

func OperationVariableInputObjects(
	schema *ast.Schema,
	sources []*ast.Source,
) ([]string, error)

OperationVariableInputObjects returns the sorted, deduplicated set of INPUT_OBJECT type names that appear as operation variable types across the given query sources, restricted to types declared in schema.

Used to prune the `.defederator.yml` bindings: only INPUT_OBJECTs actually passed as cross-service operation arguments need a binding.

func Render

func Render(d *Data) (string, error)

Render executes the embedded client.gotpl template and returns Go source. Pure.

func Run

func Run(_ context.Context, dir string, opts Options) error

Run migrates a genqlient-based service directory to defederator.

Files are not overwritten unless opts.Force is true. With opts.DryRun, files are printed to stdout and not written.

func UsedSubgraphs

func UsedSubgraphs(sg *federation.Supergraph, sources []*ast.Source) []string

UsedSubgraphs returns the sorted, deduplicated set of join__Graph enum names that the given operation sources actually touch, as determined by planning each operation with the federation query planner.

Used to prune `_subgraphServices` in the generated cross_service/client.go: only subgraphs whose fields appear in at least one operation need a service-discovery lookup at runtime.

An operation that fails to plan (e.g. references a type the supergraph doesn't have, which would be a programmer error) is skipped silently so that migrate is no stricter than `defederator generate` itself.

Types

type AuthFlavors

type AuthFlavors struct {
	User       bool
	Admin      bool
	LocaleUser bool
}

AuthFlavors records which gqlclient auth modes a service's cross-service code invokes. Each field is true when at least one call site in the operation Go files uses the corresponding pattern:

  • User: ctx.GraphQL().AsUser()
  • Admin: ctx.GraphQL().AsServiceAdmin()
  • LocaleUser: ctx.GraphQL().WithKALocale(...).AsUser()

The set drives which federation-client constructors migrate emits in cross_service/client.go.

func AuthFlavorsFromOperationDir

func AuthFlavorsFromOperationDir(dir string) (AuthFlavors, error)

AuthFlavorsFromOperationDir scans every .go file in the given directory (non-recursive) and returns the union of auth flavors detected. This is the convenience entrypoint migrate.Run uses: the migrate command knows the cross_service dir, hands it here, and gets back the flavor set.

func DetectAuthFlavors

func DetectAuthFlavors(goFiles []string) (AuthFlavors, error)

DetectAuthFlavors scans the contents of the given Go source files for gqlclient auth-mode call chains and returns the union of flavors found.

Missing files are skipped silently. Read errors return immediately with the error; caller decides whether to proceed with a partial result.

Detection is regex-based on raw source text rather than AST: every flavor we care about has a unique, unambiguous lexical form (an `.AsXxx()` method call at the end of a chain). False positives in comments are unlikely in practice because webapp cross-service files don't quote these call chains.

func (AuthFlavors) Any

func (a AuthFlavors) Any() bool

Any reports whether any flavor was detected. Used by the template to decide between the single-constructor default and the multi-factory shape.

func (AuthFlavors) Multi

func (a AuthFlavors) Multi() bool

Multi reports whether more than one flavor is present, which is the signal that the multi-factory pattern is required (separate User/Admin/LocaleUser constructors plus a shared newJobFederationClient).

type Data

type Data struct {
	ServiceName     string          // e.g. "ai-guide"
	ServiceDir      string          // absolute or relative path passed to migrate
	PackageName     string          // always "cross_service"
	ImportAlias     string          // always "defed"
	DefedImportPath string          // e.g. "github.com/Khan/webapp/services/ai-guide/generated/defederator"
	URLFuncName     string          // e.g. "aiGuideSubgraphURLs"
	Subgraphs       []SubgraphEntry // join__Graph entries the service touches
	AuthFlavors     AuthFlavors     // which factory shape to emit
}

Data holds all values the client.gotpl template needs.

func DataFromDir

func DataFromDir(
	dir string,
	modulePath string,
	subgraphs []SubgraphEntry,
	flavors AuthFlavors,
) *Data

DataFromDir derives template Data from the service directory path, the webapp module path (from go.mod), the (already-pruned) subgraph list, and the detected auth flavors. Pure function — no I/O.

type GenqlientConfig

type GenqlientConfig struct {
	Schema     string
	Operations []string
	Generated  string
	Bindings   map[string]config.TypeBinding
}

GenqlientConfig is the parsed subset of a genqlient.yaml relevant to migration.

type Options

type Options struct {
	Force  bool // overwrite existing .defederator.yml and client.go
	DryRun bool // print what would be written; write nothing
	// SkipNextSteps suppresses the post-migration instruction block. Set when
	// the caller is going to chain into another step (e.g. generate) that
	// makes the printed advice misleading.
	SkipNextSteps bool
}

Options configures a migration run.

type SubgraphEntry

type SubgraphEntry struct {
	EnumName    string // e.g. "AI_GUIDE"
	ServiceName string // e.g. "ai-guide" (from @join__graph identifier directive arg)
}

SubgraphEntry is a single join__Graph enum value parsed from the supergraph SDL.

func FilterSubgraphs

func FilterSubgraphs(
	all []SubgraphEntry,
	usedEnumNames []string,
	ownServiceName string,
) []SubgraphEntry

FilterSubgraphs keeps only the SubgraphEntries whose EnumName appears in usedEnumNames, preserving original declaration order. The own-service entry (matching ownServiceName) is always kept regardless of usage so that the generated map looks complete for the local service.

usedEnumNames must be sorted; passing the result of UsedSubgraphs satisfies that. ownServiceName may be "" to skip the always-keep behavior.

func ParseSubgraphs

func ParseSubgraphs(sdl string) ([]SubgraphEntry, error)

ParseSubgraphs reads a Federation v2 supergraph SDL and returns all join__Graph enum values in declaration order. Pure function — no I/O.

For each enum value, the service name is taken from the @join__graph(identifier:) directive argument. If the argument is absent the service name is derived by lowercasing the enum name and replacing underscores with hyphens.

type YAMLInput

type YAMLInput struct {
	Genqlient    GenqlientConfig
	InputObjects []string // INPUT_OBJECT names used by operations, sorted
	Enums        []string // ENUM names used by operations, sorted
	GenqlientPkg string   // e.g. "github.com/Khan/webapp/services/foo/generated/genqlient"
}

YAMLInput collects everything DefederatorYAML needs to produce a .defederator.yml. It is the pure-function boundary: the caller resolves I/O; DefederatorYAML is a pure transformation.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL