Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions pkg/codegen/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ import (
"bufio"
"bytes"
"embed"
"errors"
"fmt"
"io/fs"
"os"
"path"
"runtime/debug"
"sort"
"strings"
Expand Down Expand Up @@ -130,6 +133,36 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) {

// Override built-in templates with user-provided versions
for _, tpl := range t.Templates() {
// Check for template in provided template directory
if dir := opts.OutputOptions.UserTemplatesDir; dir != "" {
fp := path.Join(dir, tpl.Name())
_, err := os.Stat(fp)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("error accessing user-provided template %q: %w", fp, err)
}
if err == nil {
utpl := t.New(tpl.Name())
data, err := os.ReadFile(fp)
if err != nil {
return "", fmt.Errorf("error reading user-provided template %q: %w", fp, err)
}
if _, err := utpl.Parse(string(data)); err != nil {
return "", fmt.Errorf("error parsing user-provided template %q: %w", fp, err)
}
}
}
// Check for template in a provided file path
if fp, ok := opts.OutputOptions.UserTemplateFiles[tpl.Name()]; ok {
utpl := t.New(tpl.Name())
data, err := os.ReadFile(fp)
if err != nil {
return "", fmt.Errorf("error reading user-provided template %q: %w", fp, err)
}
if _, err := utpl.Parse(string(data)); err != nil {
return "", fmt.Errorf("error parsing user-provided template %q: %w", fp, err)
}
}
// Check for template provided inline in the configuration
if _, ok := opts.OutputOptions.UserTemplates[tpl.Name()]; ok {
utpl := t.New(tpl.Name())
if _, err := utpl.Parse(opts.OutputOptions.UserTemplates[tpl.Name()]); err != nil {
Expand Down
87 changes: 59 additions & 28 deletions pkg/codegen/codegen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,20 @@ package codegen
import (
"bytes"
_ "embed"
"fmt"
"go/format"
"io"
"net/http"
"testing"

examplePetstoreClient "github.com/deepmap/oapi-codegen/examples/petstore-expanded"
examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api"
"github.com/deepmap/oapi-codegen/pkg/util"
"github.com/getkin/kin-openapi/openapi3"
"github.com/golangci/lint-1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

examplePetstoreClient "github.com/deepmap/oapi-codegen/examples/petstore-expanded"
examplePetstore "github.com/deepmap/oapi-codegen/examples/petstore-expanded/echo/api"
"github.com/deepmap/oapi-codegen/pkg/util"
)

const (
Expand Down Expand Up @@ -76,39 +78,68 @@ func TestExamplePetStoreCodeGeneration(t *testing.T) {
}

func TestExamplePetStoreCodeGenerationWithUserTemplates(t *testing.T) {

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

// Input vars for code generation:
packageName := "api"
opts := Configuration{
PackageName: packageName,
Generate: GenerateOptions{
Models: true,
},
OutputOptions: OutputOptions{
UserTemplates: userTemplates,
ChiServer: true,
Models: true,
},
}

// 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, "//blah")
for set := 0; set < 8; set++ {
var (
oo OutputOptions
useDirectoryOverride = set&4 != 0
useFileOverride = set&2 != 0
useInlineOverride = set&1 != 0
)
if useDirectoryOverride {
oo.UserTemplatesDir = "testdata"
}
if useFileOverride {
oo.UserTemplateFiles = map[string]string{"chi/chi-interface.tmpl": "testdata/chi-interface.tmpl"}
}
if useInlineOverride {
oo.UserTemplates = map[string]string{"chi/chi-interface.tmpl": "// inline override"}
}
opts.OutputOptions = oo

testName := fmt.Sprintf(
"override: directory=%v, file=%v, inline=%v",
useDirectoryOverride,
useFileOverride,
useInlineOverride,
)
t.Run(testName, func(t *testing.T) {
// 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 generated code has the appropriate template override applied
switch {
case useInlineOverride:
assert.Contains(t, code, "// inline override")
case useFileOverride:
assert.Contains(t, code, "// file override")
case useDirectoryOverride:
assert.Contains(t, code, "// directory override")
}
})
}
}

func TestExamplePetStoreParseFunction(t *testing.T) {
Expand Down
12 changes: 7 additions & 5 deletions pkg/codegen/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,13 @@ 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
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
UserTemplateFiles map[string]string `yaml:"user-template-files,omitempty"` // Same as UserTemplates, with filenames as values instead
UserTemplatesDir string `yaml:"user-templates-dir,omitempty"`

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
Expand Down
1 change: 1 addition & 0 deletions pkg/codegen/testdata/chi-interface.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// file override
1 change: 1 addition & 0 deletions pkg/codegen/testdata/chi/chi-interface.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// directory override