Skip to content

Adds user-templates-dir to OutputOptions#707

Closed
chanced wants to merge 3 commits intooapi-codegen:masterfrom
chanced:add-user-templates-dir
Closed

Adds user-templates-dir to OutputOptions#707
chanced wants to merge 3 commits intooapi-codegen:masterfrom
chanced:add-user-templates-dir

Conversation

@chanced
Copy link
Copy Markdown

@chanced chanced commented Aug 5, 2022

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
Copy link
Copy Markdown
Author

chanced commented Aug 12, 2022

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

@chanced chanced closed this Aug 12, 2022
@gmulders
Copy link
Copy Markdown

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

@chanced
Copy link
Copy Markdown
Author

chanced commented Aug 12, 2022

@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
Copy link
Copy Markdown
Author

chanced commented Aug 12, 2022

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

@chanced
Copy link
Copy Markdown
Author

chanced commented Aug 12, 2022

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

@chanced chanced reopened this Aug 12, 2022
@gmulders
Copy link
Copy Markdown

Awesome! Thank you @chanced !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants