diff --git a/generate/config.go b/generate/config.go index 1cb28d66..98e1c97d 100644 --- a/generate/config.go +++ b/generate/config.go @@ -36,6 +36,7 @@ type Config struct { OptionalGenericType string `yaml:"optional_generic_type"` StructReferences bool `yaml:"use_struct_references"` Extensions bool `yaml:"use_extensions"` + Plugins Plugins `yaml:"plugins"` // The directory of the config-file (relative to which all the other paths // are resolved). Set by ValidateAndFillDefaults. @@ -44,6 +45,16 @@ type Config struct { pkgPath string } +type Plugins struct { + FieldTags []FieldTagPluginConfig `yaml:"field_tags"` + fieldTagPlugins []FieldTagPlugin `yaml:"-"` +} + +type FieldTagPluginConfig struct { + Name string `yaml:"name"` + ValueFuncPath string `yaml:"path"` +} + // A TypeBinding represents a Go type to which genqlient will bind a particular // GraphQL type, and is documented further in the [genqlient.yaml docs]. // @@ -310,6 +321,16 @@ func (c *Config) ValidateAndFillDefaults(baseDir string) error { return err } + fieldTagPlugins := make([]FieldTagPlugin, len(c.Plugins.FieldTags)) + for i, pluginConfig := range c.Plugins.FieldTags { + loadedFieldTagFunc, err := LoadFieldTagPlugins(pluginConfig.Name, pluginConfig.ValueFuncPath) + if err != nil { + return errorf(nil, "unable to load field_tags plugin %v %v: %v", pluginConfig.Name, pluginConfig.ValueFuncPath, err) + } + fieldTagPlugins[i] = *loadedFieldTagFunc + } + c.Plugins.fieldTagPlugins = fieldTagPlugins + return nil } diff --git a/generate/plugins.go b/generate/plugins.go new file mode 100644 index 00000000..589ec334 --- /dev/null +++ b/generate/plugins.go @@ -0,0 +1,35 @@ +package generate + +import ( + "fmt" + "plugin" +) + +type FieldTagPlugin struct { + Name string + ValueFunc func(PluginInput) (*string, error) +} + +type PluginInput struct { + GraphQLName string + Description string +} + +func LoadFieldTagPlugins(name string, path string) (*FieldTagPlugin, error) { + pl, err := plugin.Open(path) + if err != nil { + return nil, err + } + + funcF, err := pl.Lookup("FieldTagPlugin") + if err != nil { + return nil, err + } + + castedFunc, ok := funcF.(func(PluginInput) (*string, error)) + if !ok { + return nil, fmt.Errorf("expected 'func(PluginInput) (string, error)' function, got %T", funcF) + } + + return &FieldTagPlugin{Name: name, ValueFunc: castedFunc}, nil +} diff --git a/generate/types.go b/generate/types.go index bb822e7b..76d402a3 100644 --- a/generate/types.go +++ b/generate/types.go @@ -390,19 +390,36 @@ func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error { fmt.Fprintf(w, "type %s struct {\n", typ.GoName) for _, field := range typ.Fields { writeDescription(w, field.Description) - jsonTag := `"` + field.JSONName + var tags string + + jsonTag := `json:"` + field.JSONName if field.Omitempty { jsonTag += ",omitempty" } jsonTag += `"` + if field.NeedsMarshaling() { // certain types are handled in our (Un)MarshalJSON (see below) - jsonTag = `"-"` + jsonTag = `json:"-"` + } + + tags = jsonTag + + pluginInput := PluginInput{field.GraphQLName, field.Description} + for _, plugin := range typ.Generator.Config.Plugins.fieldTagPlugins { + res, err := plugin.ValueFunc(pluginInput) + if err != nil { + return fmt.Errorf("error running plugin %s on field %s: %w", plugin.Name, field.GoName, err) + } + if res == nil { + continue + } + tags += fmt.Sprintf(` %s:%q`, plugin.Name, *res) } + // Note for embedded types field.GoName is "", which produces the code // we want! - fmt.Fprintf(w, "\t%s %s `json:%s`\n", - field.GoName, field.GoType.Reference(), jsonTag) + fmt.Fprintf(w, "\t%s %s `%s`\n", field.GoName, field.GoType.Reference(), tags) } fmt.Fprintf(w, "}\n")