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

Adds `user-templates-dir` to `OutputOptions`

Open chanced opened this issue 3 years ago • 6 comments

This addresses #607 and is an alternative or companion to #653.

Adds a new configuration parameter, user-templates-dir, on OutputOptions which allows for assigning a directory path to parse user-template overrides. It re-uses the existing logic found in loadTemplateOverrides but is replicated in the codegen package.

user-templates takes precedence over user-templates-dir.

chanced avatar Aug 05 '22 23:08 chanced

Setting up a local command that calls codegen.Generate seems like the better path than adding onto the API. Closing this PR.

chanced avatar Aug 12 '22 13:08 chanced

@chanced Could you elaborate on this? What do you mean by local command?

gmulders avatar Aug 12 '22 14:08 gmulders

@gmulders

The way I ended up addressing this was to create a wrapper application which loaded templates & template functions and then called 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-api)

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

chanced avatar Aug 12 '22 15:08 chanced

@w32blaster I saw your comment on the other pull request regarding templates. You may be interested in the code above.

chanced avatar Aug 12 '22 15:08 chanced

maintainers, I'm re-opening this incase you find merit in it. Please feel free to close.

chanced avatar Aug 12 '22 16:08 chanced

Awesome! Thank you @chanced !

gmulders avatar Aug 12 '22 16:08 gmulders