From fee285d09b4932eaab395a201107bcf9c97b7cfe Mon Sep 17 00:00:00 2001 From: Yousuf Jawwad Date: Mon, 18 Sep 2023 13:29:09 +0500 Subject: [PATCH] chore(): breu orm related features --- pkg/codegen/codegen.go | 50 +++++++++++++++++++++++++-------- pkg/codegen/configuration.go | 12 ++++---- pkg/codegen/extension.go | 2 ++ pkg/codegen/merge_schemas_v1.go | 2 +- pkg/codegen/schema.go | 35 +++++++++++++++++++---- pkg/codegen/template_helpers.go | 39 +++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 22 deletions(-) diff --git a/pkg/codegen/codegen.go b/pkg/codegen/codegen.go index 09bf08317e..21a8030262 100644 --- a/pkg/codegen/codegen.go +++ b/pkg/codegen/codegen.go @@ -19,11 +19,13 @@ import ( "bytes" "context" "embed" + "errors" "fmt" "io" "io/fs" "net/http" "os" + "path" "runtime/debug" "sort" "strings" @@ -132,18 +134,43 @@ func Generate(spec *openapi3.T, opts Configuration) (string, error) { return "", fmt.Errorf("error parsing oapi-codegen templates: %w", err) } - // load user-provided templates. Will Override built-in versions. - for name, template := range opts.OutputOptions.UserTemplates { - utpl := t.New(name) - - txt, err := GetUserTemplateText(template) - if err != nil { - return "", fmt.Errorf("error loading user-provided template %q: %w", name, err) + // 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) + } + } } - - _, err = utpl.Parse(txt) - if err != nil { - return "", fmt.Errorf("error parsing user-provided template %q: %w", name, 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 { + return "", fmt.Errorf("error parsing user-provided template %q: %w", tpl.Name(), err) + } } } @@ -515,6 +542,7 @@ func GenerateTypesForSchemas(t *template.Template, schemas map[string]*openapi3. types = append(types, goSchema.GetAdditionalTypeDefs()...) } + return types, nil } diff --git a/pkg/codegen/configuration.go b/pkg/codegen/configuration.go index 9533f166e2..7fff93e433 100644 --- a/pkg/codegen/configuration.go +++ b/pkg/codegen/configuration.go @@ -88,11 +88,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 diff --git a/pkg/codegen/extension.go b/pkg/codegen/extension.go index 29c9727a48..7bb5c2f59f 100644 --- a/pkg/codegen/extension.go +++ b/pkg/codegen/extension.go @@ -22,6 +22,8 @@ const ( extEnumVarNames = "x-enum-varnames" extEnumNames = "x-enumNames" extDeprecationReason = "x-deprecated-reason" + extBreuEntity = "x-breu-entity" + extBreuEntityType = "x-breu-entity-type" ) func extString(extPropValue interface{}) (string, error) { diff --git a/pkg/codegen/merge_schemas_v1.go b/pkg/codegen/merge_schemas_v1.go index af83582381..86a14f6952 100644 --- a/pkg/codegen/merge_schemas_v1.go +++ b/pkg/codegen/merge_schemas_v1.go @@ -91,7 +91,7 @@ func GenStructFromAllOf(allOf []*openapi3.SchemaRef, path []string) (string, err return "", err } objectParts = append(objectParts, " // Embedded fields due to inline allOf schema") - objectParts = append(objectParts, GenFieldsFromProperties(goSchema.Properties)...) + objectParts = append(objectParts, GenStuctFieldsFromSchema(goSchema)...) if goSchema.HasAdditionalProperties { addPropsType := goSchema.AdditionalPropertiesType.GoType diff --git a/pkg/codegen/schema.go b/pkg/codegen/schema.go index 7f48f71050..9cd6dc4eb9 100644 --- a/pkg/codegen/schema.go +++ b/pkg/codegen/schema.go @@ -35,6 +35,9 @@ type Schema struct { // type definition `type Foo bool` DefineViaAlias bool + BreuEntity string // BreuEntity is the name of the entity in the Breu schema. + BreuEntityType string // BreuEntityType is the type of the entity in the Breu schema. + // The original OpenAPIv3 Schema. OAPISchema *openapi3.Schema } @@ -219,6 +222,8 @@ func PropertiesEqual(a, b Property) bool { } func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { + entity := "" + entitype := "" // Add a fallback value in case the sref is nil. // i.e. the parent schema defines a type:array, but the array has // no items defined. Therefore, we have at least valid Go-Code. @@ -228,6 +233,16 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { schema := sref.Value + // Check for x-breu-entity + if extension, ok := schema.Extensions[extBreuEntity]; ok { + entity = extension.(string) + } + + // Check for x-breu-entity-type + if extension, ok := schema.Extensions[extBreuEntityType]; ok { + entitype = extension.(string) + } + // If Ref is set on the SchemaRef, it means that this type is actually a reference to // another type. We're not de-referencing, so simply use the referenced type. if IsGoTypeReference(sref.Ref) { @@ -246,8 +261,10 @@ func GenerateGoSchema(sref *openapi3.SchemaRef, path []string) (Schema, error) { } outSchema := Schema{ - Description: schema.Description, - OAPISchema: schema, + Description: schema.Description, + OAPISchema: schema, + BreuEntity: entity, + BreuEntityType: entitype, } // AllOf is interesting, and useful. It's the union of a number of other @@ -635,9 +652,10 @@ type FieldDescriptor struct { IsRef bool // Is this schema a reference to predefined object? } -// GenFieldsFromProperties produce corresponding field names with JSON annotations, +// GenStuctFieldsFromSchema produce corresponding field names with JSON annotations, // given a list of schema descriptors -func GenFieldsFromProperties(props []Property) []string { +func GenStuctFieldsFromSchema(schema Schema) []string { + props := schema.Properties var fields []string for i, p := range props { field := "" @@ -722,6 +740,12 @@ func GenFieldsFromProperties(props []Property) []string { } } } + + // Support x-breu-entity + if schema.BreuEntity != "" && schema.BreuEntityType == "cql" { + fieldTags["cql"] = p.JsonFieldName + } + // Convert the fieldTags map into Go field annotations. keys := SortedStringKeys(fieldTags) tags := make([]string, len(keys)) @@ -731,6 +755,7 @@ func GenFieldsFromProperties(props []Property) []string { field += "`" + strings.Join(tags, " ") + "`" fields = append(fields, field) } + return fields } @@ -749,7 +774,7 @@ func GenStructFromSchema(schema Schema) string { // Start out with struct { objectParts := []string{"struct {"} // Append all the field definitions - objectParts = append(objectParts, GenFieldsFromProperties(schema.Properties)...) + objectParts = append(objectParts, GenStuctFieldsFromSchema(schema)...) // Close the struct if schema.HasAdditionalProperties { objectParts = append(objectParts, diff --git a/pkg/codegen/template_helpers.go b/pkg/codegen/template_helpers.go index 87394c8941..b2615086c7 100644 --- a/pkg/codegen/template_helpers.go +++ b/pkg/codegen/template_helpers.go @@ -17,6 +17,7 @@ import ( "bytes" "fmt" "os" + "strconv" "strings" "text/template" @@ -294,6 +295,41 @@ func stripNewLines(s string) string { return r.Replace(s) } +func split(sep, orig string) map[string]string { + parts := strings.Split(orig, sep) + res := make(map[string]string, len(parts)) + for i, v := range parts { + res["_"+strconv.Itoa(i)] = v + } + return res +} + +func splitn(sep string, n int, orig string) map[string]string { + parts := strings.SplitN(orig, sep, n) + res := make(map[string]string, len(parts)) + for i, v := range parts { + res["_"+strconv.Itoa(i)] = v + } + return res +} + +// substring creates a substring of the given string. +// +// If start is < 0, this calls string[:end]. +// +// If start is >= 0 and end < 0 or end bigger than s length, this calls string[start:] +// +// Otherwise, this calls string[start, end]. +func substring(start, end int, s string) string { + if start < 0 { + return s[:end] + } + if end < 0 || end > len(s) { + return s[start:] + } + return s[start:end] +} + // TemplateFunctions is passed to the template engine, and we can call each // function here by keyName from the template code. var TemplateFunctions = template.FuncMap{ @@ -321,4 +357,7 @@ var TemplateFunctions = template.FuncMap{ "stripNewLines": stripNewLines, "sanitizeGoIdentity": SanitizeGoIdentity, "toGoComment": StringWithTypeNameToGoComment, + "split": split, + "splitn": splitn, + "substring": substring, }