Skip to content

Commit

Permalink
Merge pull request #1 from chanced/add-user-templates-dir
Browse files Browse the repository at this point in the history
adds `user-templates-dir` to `OutputOptions`
  • Loading branch information
chanced committed Aug 5, 2022
2 parents 9a9e66f + 063b836 commit 4800af6
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 15 deletions.
59 changes: 57 additions & 2 deletions pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"embed"
"fmt"
"io/fs"
"io/ioutil"
"path"
"runtime/debug"
"sort"
"strings"
Expand Down Expand Up @@ -123,11 +125,28 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {
return "", fmt.Errorf("error parsing oapi-codegen templates: %w", err)
}

// This the specified user-templates-dir
if opts.OutputOptions.UserTemplatesDir != "" {
overrides, err := loadTemplateOverrides(opts.OutputOptions.UserTemplatesDir)
if err != nil {
return "", fmt.Errorf("error parsing user templates directory: %w", err)
}
if opts.OutputOptions.UserTemplates == nil {
opts.OutputOptions.UserTemplates = make(map[string]string)
}

for k, v := range overrides {
if _, ok := opts.OutputOptions.UserTemplates[k]; !ok {
opts.OutputOptions.UserTemplates[k] = v
}
}
}

// Override built-in templates with user-provided versions
for _, tpl := range t.Templates() {
if _, ok := opts.OutputOptions.UserTemplates[tpl.Name()]; ok {
if override, ok := opts.OutputOptions.UserTemplates[tpl.Name()]; ok {
utpl := t.New(tpl.Name())
if _, err := utpl.Parse(opts.OutputOptions.UserTemplates[tpl.Name()]); err != nil {
if _, err := utpl.Parse(override); err != nil {
return "", fmt.Errorf("error parsing user-provided template %q: %w", tpl.Name(), err)
}
}
Expand Down Expand Up @@ -896,3 +915,39 @@ func GetResponsesImports(responses map[string]*openapi3.ResponseRef) (map[string
}
return res, nil
}

func loadTemplateOverrides(templatesDir string) (map[string]string, error) {
templates := make(map[string]string)

if templatesDir == "" {
return templates, nil
}

files, err := ioutil.ReadDir(templatesDir)
if err != nil {
return nil, err
}

for _, f := range files {
// Recursively load subdirectory files, using the path relative to the templates
// directory as the key. This allows for overriding the files in the service-specific
// directories (e.g. echo, chi, etc.).
if f.IsDir() {
subFiles, err := loadTemplateOverrides(path.Join(templatesDir, f.Name()))
if err != nil {
return nil, err
}
for subDir, subFile := range subFiles {
templates[path.Join(f.Name(), subDir)] = subFile
}
continue
}
data, err := ioutil.ReadFile(path.Join(templatesDir, f.Name()))
if err != nil {
return nil, err
}
templates[f.Name()] = string(data)
}

return templates, nil
}
68 changes: 63 additions & 5 deletions pkg/codegen/codegen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ func checkLint(t *testing.T, filename string, code []byte) {
}

func TestExamplePetStoreCodeGeneration(t *testing.T) {

// Input vars for code generation:
packageName := "api"
opts := Configuration{
Expand Down Expand Up @@ -73,7 +72,6 @@ func TestExamplePetStoreCodeGeneration(t *testing.T) {
}

func TestExamplePetStoreCodeGenerationWithUserTemplates(t *testing.T) {

userTemplates := map[string]string{"typedef.tmpl": "//blah"}

// Input vars for code generation:
Expand Down Expand Up @@ -108,8 +106,70 @@ func TestExamplePetStoreCodeGenerationWithUserTemplates(t *testing.T) {
assert.Contains(t, code, "//blah")
}

func TestExamplePetStoreParseFunction(t *testing.T) {
func TestExamplePetStoreCodeGenerationWithUserTemplatesDir(t *testing.T) {
// Input vars for code generation:

opts := Configuration{
PackageName: "api",
Generate: GenerateOptions{
ChiServer: true,
},
OutputOptions: OutputOptions{
UserTemplatesDir: "test_templates",
},
}

// Get a spec from the example PetStore definition:
swagger, err := examplePetstore.GetSwagger()
assert.NoError(t, err)

// Run our code generation:
code, err := Generate(swagger, opts)
assert.NoError(t, err)
assert.NotEmpty(t, code)

// Check that we have valid (formattable) code:
_, err = format.Source([]byte(code))
assert.NoError(t, err)

// Check that we have a package:
assert.Contains(t, code, "package api")

// Check that the built-in template has been overriden
assert.Contains(t, code, "//__placeholder__")

// Tests that `user-templates` takes precedence over
// `user-templates-dir`

// Input vars for code generation:
opts = Configuration{
PackageName: "api",
Generate: GenerateOptions{
Models: true,
},
OutputOptions: OutputOptions{
UserTemplates: map[string]string{"typedef.tmpl": "//blah"},
UserTemplatesDir: "test_templates",
},
}

// Run our code generation:
code, err = Generate(swagger, opts)
assert.NoError(t, err)
assert.NotEmpty(t, code)

// Check that we have valid (formattable) code:
_, err = format.Source([]byte(code))
assert.NoError(t, err)

// Check that we have a package:
assert.Contains(t, code, "package api")

// Check that the built-in template has been overriden
assert.Contains(t, code, "//blah")
}

func TestExamplePetStoreParseFunction(t *testing.T) {
bodyBytes := []byte(`{"id": 5, "name": "testpet", "tag": "cat"}`)

cannedResponse := &http.Response{
Expand All @@ -129,7 +189,6 @@ func TestExamplePetStoreParseFunction(t *testing.T) {
}

func TestExampleOpenAPICodeGeneration(t *testing.T) {

// Input vars for code generation:
packageName := "testswagger"
opts := Configuration{
Expand Down Expand Up @@ -231,7 +290,6 @@ func TestXGoTypeImport(t *testing.T) {

// Make sure the generated code is valid:
checkLint(t, "test.gen.go", []byte(code))

}

//go:embed test_spec.yaml
Expand Down
16 changes: 8 additions & 8 deletions pkg/codegen/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ type CompatibilityOptions struct {

// OutputOptions are used to modify the output code in some way.
type OutputOptions struct {
SkipFmt bool `yaml:"skip-fmt,omitempty"` // Whether to skip go imports on the generated code
SkipPrune bool `yaml:"skip-prune,omitempty"` // Whether to skip pruning unused components on the generated code
IncludeTags []string `yaml:"include-tags,omitempty"` // Only include operations that have one of these tags. Ignored when empty.
ExcludeTags []string `yaml:"exclude-tags,omitempty"` // Exclude operations that have one of these tags. Ignored when empty.
UserTemplates map[string]string `yaml:"user-templates,omitempty"` // Override built-in templates from user-provided files

ExcludeSchemas []string `yaml:"exclude-schemas,omitempty"` // Exclude from generation schemas with given names. Ignored when empty.
ResponseTypeSuffix string `yaml:"response-type-suffix,omitempty"` // The suffix used for responses types
SkipFmt bool `yaml:"skip-fmt,omitempty"` // Whether to skip go imports on the generated code
SkipPrune bool `yaml:"skip-prune,omitempty"` // Whether to skip pruning unused components on the generated code
IncludeTags []string `yaml:"include-tags,omitempty"` // Only include operations that have one of these tags. Ignored when empty.
ExcludeTags []string `yaml:"exclude-tags,omitempty"` // Exclude operations that have one of these tags. Ignored when empty.
UserTemplates map[string]string `yaml:"user-templates,omitempty"` // Override built-in templates from user-provided files
UserTemplatesDir string `yaml:"user-templates-dir,omitempty"` // Override built-in templates from user-provided files
ExcludeSchemas []string `yaml:"exclude-schemas,omitempty"` // Exclude from generation schemas with given names. Ignored when empty.
ResponseTypeSuffix string `yaml:"response-type-suffix,omitempty"` // The suffix used for responses types
}

// UpdateDefaults sets reasonable default values for unset fields in Configuration
Expand Down
1 change: 1 addition & 0 deletions pkg/codegen/test_templates/chi/chi-interface.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
//__placeholder__

0 comments on commit 4800af6

Please sign in to comment.