Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support spec patching
Browse files Browse the repository at this point in the history
byashimov committed Sep 2, 2024
1 parent cbc2eb8 commit 5e03dd0
Showing 5 changed files with 424 additions and 38 deletions.
18 changes: 9 additions & 9 deletions generator/main.go
Original file line number Diff line number Diff line change
@@ -30,12 +30,13 @@ const (
)

type envConfig struct {
Module string `envconfig:"MODULE" default:"github.com/aiven/go-client-codegen"`
Package string `envconfig:"PACKAGE" default:"aiven"`
HandlerDir string `envconfig:"HANDLER_DIR" default:"handler"`
ConfigFile string `envconfig:"CONFIG_FILE" default:"config.yaml"`
ClientFile string `envconfig:"CLIENT_FILE" default:"client_generated.go"`
OpenAPIFile string `envconfig:"OPENAPI_FILE" default:"openapi.json"`
Module string `envconfig:"MODULE" default:"github.com/aiven/go-client-codegen"`
Package string `envconfig:"PACKAGE" default:"aiven"`
HandlerDir string `envconfig:"HANDLER_DIR" default:"handler"`
ConfigFile string `envconfig:"CONFIG_FILE" default:"config.yaml"`
ClientFile string `envconfig:"CLIENT_FILE" default:"client_generated.go"`
OpenAPIFile string `envconfig:"OPENAPI_FILE" default:"openapi.json"`
OpenAPIPatchFile string `envconfig:"OPENAPI_PATCH_FILE" default:"openapi_patch.yaml"`
}

var (
@@ -81,20 +82,19 @@ func exec() error {
return err
}

docBytes, err := os.ReadFile(cfg.OpenAPIFile)
// Reads OpenAPI file and applies a patch
docBytes, err := readOpenAPIPatched(cfg.OpenAPIFile, cfg.OpenAPIPatchFile)
if err != nil {
return err
}

doc := new(Doc)

err = json.Unmarshal(docBytes, doc)
if err != nil {
return err
}

pkgs := make(map[string][]*Path)

for path := range doc.Paths {
v := doc.Paths[path]
for meth, p := range v {
48 changes: 21 additions & 27 deletions generator/models.go
Original file line number Diff line number Diff line change
@@ -134,21 +134,22 @@ const (

// Schema represents a parsed OpenAPI schema.
type Schema struct {
Type SchemaType `json:"type"`
Properties map[string]*Schema `json:"properties"`
Items *Schema `json:"items"`
RequiredProps []string `json:"required"`
Enum []any `json:"enum"`
Default any `json:"default"`
MinItems int `json:"minItems"`
Ref string `json:"$ref"`
Description string `json:"description"`
CamelName string `json:"for-hash-only!"`
required bool
name string
propertyNames []string
parent *Schema
in, out bool // Request or Response DTO
Type SchemaType `json:"type"`
Properties map[string]*Schema `json:"properties"`
AdditionalProperties *Schema `json:"additionalProperties"`
Items *Schema `json:"items"`
RequiredProps []string `json:"required"`
Enum []any `json:"enum"`
Default any `json:"default"`
MinItems int `json:"minItems"`
Ref string `json:"$ref"`
Description string `json:"description"`
CamelName string `json:"for-hash-only!"`
required bool
name string
propertyNames []string
parent *Schema
in, out bool // Request or Response DTO
}

//nolint:funlen,gocognit,gocyclo // It is easy to maintain and read, we don't need to split it
@@ -230,12 +231,6 @@ func (s *Schema) init(doc *Doc, scope map[string]*Schema, name string) {
}
}

// fixme: on the backend
if s.name == "topics.blacklist" && s.parent.Type != SchemaTypeArray {
s.Type = SchemaTypeArray
s.Items = &Schema{Type: SchemaTypeString}
}

if s.Type == SchemaTypeString {
parts := strings.Split(s.name, "_")
suffix := parts[len(parts)-1]
@@ -410,7 +405,11 @@ func getType(s *Schema) *jen.Statement {
a = jen.Op("*").Map(jen.String())
}

if isMapString(s) {
if s.AdditionalProperties != nil {
s.AdditionalProperties.required = true
return a.Add(getType(s.AdditionalProperties))
} else if s.name == "tags" {
// tags are everywhere in the schema, better not to use the patch
return a.String()
} else {
return a.Any()
@@ -429,11 +428,6 @@ func mustMarshal(s any) string {
return string(b)
}

// isMapString for hacking schemaless maps
func isMapString(s *Schema) bool {
return s.name == "tags"
}

func lowerFirst(s string) string {
return strings.ToLower(s[:1]) + s[1:]
}
70 changes: 70 additions & 0 deletions generator/patching.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"reflect"

"gopkg.in/yaml.v3"
)

func patchDict[T map[string]any](dest, patch T) error {
for k, v := range patch {
result, ok := dest[k]
switch {
case !ok:
result = v
case reflect.TypeOf(result) != reflect.TypeOf(v):
return fmt.Errorf("type missmatch for key %s", k)
case reflect.TypeOf(result).Kind() == reflect.Map:
err := patchDict(result.(T), v.(T)) //nolint:forcetypeassert
if err != nil {
return err
}
default:
result = v
}

dest[k] = result
}
return nil
}

// readOpenAPIPatched Reads OpenAPI file (JSON) and applies the patch (YAML)
func readOpenAPIPatched(oaFile, patchFile string) ([]byte, error) {
dest, err := os.ReadFile(filepath.Clean(oaFile))
if err != nil {
return nil, err
}

patch, err := os.ReadFile(filepath.Clean(patchFile))
if errors.Is(err, os.ErrExist) {
// No patch found, exits
return dest, nil
}

if err != nil {
return nil, err
}

var d, p map[string]any
err = json.Unmarshal(dest, &d)
if err != nil {
return nil, err
}

err = yaml.Unmarshal(patch, &p)
if err != nil {
return nil, err
}

err = patchDict(d, p)
if err != nil {
return nil, err
}

return json.Marshal(&d)
}
80 changes: 78 additions & 2 deletions handler/service/service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5e03dd0

Please sign in to comment.