oapi-codegen icon indicating copy to clipboard operation
oapi-codegen copied to clipboard

Template overrides in new config style not possible?

Open gerardsn opened this issue 3 years ago • 4 comments

Firstly, thanks for the great work.

While converting our configs to the new style I found that the config generator adds the actual templates as plain text under output-options.user-templates instead of referencing the template location provided with --templates. Having to update every config file after the template changes defeats the purpose of having a template.

Is there currently an option in the new config style that works similar to the old --templates flag? If not, is it possible to add an alternative to the new config that behaves like the old flag?

gerardsn avatar Jun 03 '22 10:06 gerardsn

The approach I took was to create a local application which runs codegen.Generate.

This is rough but it'll give you an idea:

package main

import (
	"context"
	"embed"
	"flag"
	"io/fs"
	"log"
	"os"
	"path"
	"strings"
	"text/template"

	"github.com/deepmap/oapi-codegen/pkg/codegen"
	"github.com/deepmap/oapi-codegen/pkg/util"
	"github.com/getkin/kin-openapi/openapi3"
	"gopkg.in/yaml.v3"
)

type configuration struct {
	codegen.Configuration `yaml:",inline"`

	// OutputFile is the filename to output.
	OutputFile string `yaml:"output,omitempty"`
}

//go:embed templates
var templates embed.FS

// add template functions here
var templateFunctions template.FuncMap = template.FuncMap{}

func main() {
	log.SetFlags(0)
	var cfgpath string
	flag.StringVar(&cfgpath, "config", "", "path to config file")
	flag.Parse()
	if cfgpath == "" {
		log.Fatal("--config is required")
	}
	if flag.NArg() < 1 {
		log.Fatal("Please specify a path to an OpenAPI 3.0 spec file")
	}

	// loading specification
	input := flag.Arg(0)
	spec, err := util.LoadSwagger(input)
	if err != nil {
		log.Fatalf("error loading openapi specification: %v", err)
	}
	err = spec.Validate(context.Background())
	if err != nil {
		log.Fatalf("error validating openapi specification: %v", err)
	}

	// loading configuration
	cfgdata, err := os.ReadFile(cfgpath)
	if err != nil {
		log.Fatalf("error reading config file: %s", err)
	}
	var cfg configuration
	err = yaml.Unmarshal(cfgdata, &cfg)
	if err != nil {
		log.Fatalf("error unmarshaling config %v", err)
	}

	// generating output
	output, err := generate(spec, cfg.Configuration, templates)
	if err != nil {
		log.Fatalf("error generating code: %v", err)
	}

	// writing output to file
	outFile, err := os.Create(cfg.OutputFile)
	if err != nil {
		log.Fatalf("error creating output file: %v", err)
	}
	_, err = outFile.Write([]byte(output))
	if err != nil {
		log.Fatalf("error writing output file: %v", err)
	}
	outFile.Close()
}

func generate(spec *openapi3.T, config codegen.Configuration, templates embed.FS) (string, error) {
	var err error
	config, err = addTemplateOverrides(config, templates)
	if err != nil {
		return "", err
	}
	// adding local template functions
	for k, v := range templateFunctions {
		codegen.TemplateFunctions[k] = v
	}
	return codegen.Generate(spec, config)
}

func addTemplateOverrides(config codegen.Configuration, templates embed.FS) (codegen.Configuration, error) {
	overrides := config.OutputOptions.UserTemplates
	if overrides == nil {
		overrides = make(map[string]string)
	}
	err := fs.WalkDir(templates, ".", func(p string, d fs.DirEntry, err error) error {
		if !d.IsDir() {
			if err != nil {
				return err
			}
			f, err := templates.ReadFile(p)
			if err != nil {
				return err
			}
			// using .gtpl for light syntax highlighting
			name := strings.TrimSuffix(p, path.Ext(p)) + ".tmpl"
			name = strings.Join(strings.Split(name, "/")[1:], "/")
			overrides[name] = string(f)
		}
		return nil
	})
	config.OutputOptions.UserTemplates = overrides
	return config, err
}

To generate with it, you'd do something like (assuming you named it generate-go)

//go:generate go run github.com/{github-user}/{project-id}/cmd/generate-go --config=api.server.yaml ../../spec/api.yaml

chanced avatar Aug 12 '22 16:08 chanced

@gerardsn The user-templates I define in the YAML configuration file also aren't getting applied for me; are they working for you? What solution did you wind up using?

dlek avatar Sep 23 '22 18:09 dlek

@dlek

The user-templates I define in the YAML configuration file also aren't getting applied for me; are they working for you?

Yes, they are working for me. I generated the config files using the "old setup" and the --output-config flag.

What solution did you wind up using?

We have had no changes to our templates, so I'm still using the config files with inline template definitions for now.

gerardsn avatar Sep 26 '22 08:09 gerardsn

@gerardsn Thanks for your reply. I was doing the same--but the templates I was using involve a subdirectory in the original (oapi-codegen) template location, and so I needed the specify the templates accordingly:

output-options:
  user-templates:
    echo/echo-interface.tmpl: |
      // ServerInterface represents all server handlers.
      type ServerInterface interface {
      [...]

(I also used different YAML syntax for multiline strings to make the templates more readable and editable in the configuration.)

dlek avatar Oct 06 '22 15:10 dlek

thanks @dlek your code snippet helps

anandsunderraman avatar May 22 '23 19:05 anandsunderraman

As noted in https://github.com/deepmap/oapi-codegen/pull/653#issuecomment-1710457277 it's possible to do this via https://github.com/deepmap/oapi-codegen/pull/968 - closing this issue (but please let me know if I've missed something!)

jamietanna avatar Jan 25 '24 14:01 jamietanna