From c2c9b190e7112e8369f8bff04052322b9890cd1a Mon Sep 17 00:00:00 2001 From: Igor Derkach Date: Fri, 6 Dec 2024 14:57:56 +0400 Subject: [PATCH] Intermediate commit --- cmd/go-asyncapi/config.go | 21 +++ cmd/go-asyncapi/generate.go | 167 ++++++++++++-------- internal/asyncapi/channel.go | 6 +- internal/asyncapi/correlationid.go | 2 +- internal/asyncapi/message.go | 13 +- internal/asyncapi/object.go | 37 +++-- internal/asyncapi/parameter.go | 10 +- internal/asyncapi/server.go | 4 +- internal/common/compile_context.go | 50 +++--- internal/common/render.go | 75 ++++----- internal/compiler/compiler.go | 12 +- internal/compiler/parse.go | 4 +- internal/render/asyncapi.go | 4 + internal/render/bindings.go | 16 +- internal/render/channel.go | 61 ++++---- internal/render/context/context.go | 59 ++++--- internal/render/correlationid.go | 56 +++---- internal/render/lang/base.go | 16 +- internal/render/lang/goarray.go | 7 + internal/render/lang/gomap.go | 7 + internal/render/lang/gosimple.go | 2 +- internal/render/lang/gostruct.go | 72 ++++----- internal/render/lang/gotypealias.go | 7 + internal/render/lang/govalue.go | 2 +- internal/render/lang/promise.go | 25 +-- internal/render/lang/union.go | 7 + internal/render/message.go | 47 +++--- internal/render/parameter.go | 6 +- internal/render/server.go | 20 ++- internal/render/servervariable.go | 6 +- internal/render/template.go | 228 ---------------------------- internal/selector/selector.go | 18 +-- internal/specurl/url.go | 2 +- internal/tpl/load.go | 8 + internal/tpl/template.go | 216 ++++++++++++++++++++++++++ internal/utils/names.go | 46 +++--- internal/utils/render.go | 86 +++++++---- internal/writer/implementations.go | 4 +- internal/writer/render.go | 151 ++++++++++++++---- templates/main.tmpl | 32 ++-- templates/preambule.tmpl | 11 ++ 41 files changed, 912 insertions(+), 711 deletions(-) create mode 100644 cmd/go-asyncapi/config.go delete mode 100644 internal/render/template.go create mode 100644 internal/tpl/template.go diff --git a/cmd/go-asyncapi/config.go b/cmd/go-asyncapi/config.go new file mode 100644 index 0000000..afe42f8 --- /dev/null +++ b/cmd/go-asyncapi/config.go @@ -0,0 +1,21 @@ +package main + +type ( + toolConfigSelection struct { + ObjectKindRe string `yaml:"objectKindRe"` + ModuleURLRe string `yaml:"moduleURLRe"` + PathRe string `yaml:"pathRe"` + Template string `yaml:"template"` + File string `yaml:"file"` + Package string `yaml:"package"` + TemplateArgs map[string]string `yaml:"templateArgs"` + } + + toolConfigRender struct { + Selections []toolConfigSelection `yaml:"selections"` + } + + toolConfig struct { + Render toolConfigRender `yaml:"render"` + } +) \ No newline at end of file diff --git a/cmd/go-asyncapi/generate.go b/cmd/go-asyncapi/generate.go index 711f1bf..6c52d69 100644 --- a/cmd/go-asyncapi/generate.go +++ b/cmd/go-asyncapi/generate.go @@ -3,10 +3,10 @@ package main import ( "encoding/json" "fmt" + "gopkg.in/yaml.v3" "io" "os" "path" - "regexp" "strings" "time" @@ -53,8 +53,8 @@ type generatePubSubArgs struct { Spec string `arg:"required,positional" help:"AsyncAPI specification file path or url" placeholder:"PATH"` ProjectModule string `arg:"-M,--project-module" help:"Project module name to use [default: extracted from go.mod file in the current working directory]" placeholder:"MODULE"` - TargetPackage string `arg:"-T,--target-package" help:"Package for generated code [default: {target-dir-name}]" placeholder:"PACKAGE"` TemplateDir string `arg:"--template-dir" help:"Directory with custom templates" placeholder:"DIR"` + ConfigFile string `arg:"--config-file" help:"YAML configuration file path" placeholder:"PATH"` generateObjectSelectionOpts ImplementationsOpts AllowRemoteRefs bool `arg:"--allow-remote-refs" help:"Allow fetching spec files from remote $ref URLs"` @@ -83,32 +83,32 @@ type ImplementationsOpts struct { } type generateObjectSelectionOpts struct { - SelectChannelsAll bool `arg:"--select-channels-all" help:"Select all channels to be generated"` - SelectChannelsRe string `arg:"--select-channels-re" help:"Select channels whose name in document matches the regex" placeholder:"REGEX"` - IgnoreChannelsAll bool `arg:"--ignore-channels-all" help:"Ignore all channels to be generated"` - IgnoreChannelsRe string `arg:"--ignore-channels-re" help:"Ignore channels whose name in document matches the regex" placeholder:"REGEX"` - ReuseChannelsModule string `arg:"--reuse-channels-module" help:"Reuse the module with channels code" placeholder:"MODULE"` - - SelectMessagesAll bool `arg:"--select-messages-all" help:"Select all messages to be generated"` - SelectMessagesRe string `arg:"--select-messages-re" help:"Select messages whose name in document matches the regex" placeholder:"REGEX"` - IgnoreMessagesAll bool `arg:"--ignore-messages-all" help:"Ignore all messages to be generated"` - IgnoreMessagesRe string `arg:"--ignore-messages-re" help:"Ignore messages whose name in document matches the regex" placeholder:"REGEX"` - ReuseMessagesModule string `arg:"--reuse-messages-module" help:"Reuse the module with messages code" placeholder:"MODULE"` - - SelectModelsAll bool `arg:"--select-models-all" help:"Select all models to be generated"` - SelectModelsRe string `arg:"--select-models-re" help:"Select models whose name in document matches the regex" placeholder:"REGEX"` - IgnoreModelsAll bool `arg:"--ignore-models-all" help:"Ignore all models to be generated"` - IgnoreModelsRe string `arg:"--ignore-models-re" help:"Ignore models whose name in document matches the regex" placeholder:"REGEX"` - ReuseModelsModule string `arg:"--reuse-models-module" help:"Reuse the module with models code" placeholder:"MODULE"` - - SelectServersAll bool `arg:"--select-servers=all" help:"Select all servers to be generated"` - SelectServersRe string `arg:"--select-servers-re" help:"Select servers whose name in document matches the regex" placeholder:"REGEX"` - IgnoreServersAll bool `arg:"--ignore-servers-all" help:"Ignore all servers to be generated"` - IgnoreServersRe string `arg:"--ignore-servers-re" help:"Ignore servers whose name in document matches the regex" placeholder:"REGEX"` - ReuseServersModule string `arg:"--reuse-servers-module" help:"Reuse the module with servers code" placeholder:"MODULE"` + //SelectChannelsAll bool `arg:"--select-channels-all" help:"Select all channels to be generated"` + //SelectChannelsRe string `arg:"--select-channels-re" help:"Select channels whose name in document matches the regex" placeholder:"REGEX"` + //IgnoreChannelsAll bool `arg:"--ignore-channels-all" help:"Ignore all channels to be generated"` + //IgnoreChannelsRe string `arg:"--ignore-channels-re" help:"Ignore channels whose name in document matches the regex" placeholder:"REGEX"` + //ReuseChannelsModule string `arg:"--reuse-channels-module" help:"Reuse the module with channels code" placeholder:"MODULE"` + // + //SelectMessagesAll bool `arg:"--select-messages-all" help:"Select all messages to be generated"` + //SelectMessagesRe string `arg:"--select-messages-re" help:"Select messages whose name in document matches the regex" placeholder:"REGEX"` + //IgnoreMessagesAll bool `arg:"--ignore-messages-all" help:"Ignore all messages to be generated"` + //IgnoreMessagesRe string `arg:"--ignore-messages-re" help:"Ignore messages whose name in document matches the regex" placeholder:"REGEX"` + //ReuseMessagesModule string `arg:"--reuse-messages-module" help:"Reuse the module with messages code" placeholder:"MODULE"` + // + //SelectModelsAll bool `arg:"--select-models-all" help:"Select all models to be generated"` + //SelectModelsRe string `arg:"--select-models-re" help:"Select models whose name in document matches the regex" placeholder:"REGEX"` + //IgnoreModelsAll bool `arg:"--ignore-models-all" help:"Ignore all models to be generated"` + //IgnoreModelsRe string `arg:"--ignore-models-re" help:"Ignore models whose name in document matches the regex" placeholder:"REGEX"` + //ReuseModelsModule string `arg:"--reuse-models-module" help:"Reuse the module with models code" placeholder:"MODULE"` + // + //SelectServersAll bool `arg:"--select-servers=all" help:"Select all servers to be generated"` + //SelectServersRe string `arg:"--select-servers-re" help:"Select servers whose name in document matches the regex" placeholder:"REGEX"` + //IgnoreServersAll bool `arg:"--ignore-servers-all" help:"Ignore all servers to be generated"` + //IgnoreServersRe string `arg:"--ignore-servers-re" help:"Ignore servers whose name in document matches the regex" placeholder:"REGEX"` + //ReuseServersModule string `arg:"--reuse-servers-module" help:"Reuse the module with servers code" placeholder:"MODULE"` NoImplementations bool `arg:"--no-implementations" help:"Do not generate any protocol implementation"` - NoEncoding bool `arg:"--no-encoding" help:"Do not generate encoders/decoders code"` + //NoEncoding bool `arg:"--no-encoding" help:"Do not generate encoders/decoders code"` } func generate(cmd *GenerateCmd) error { @@ -117,8 +117,6 @@ func generate(cmd *GenerateCmd) error { } isPub, isSub, pubSubOpts := getPubSubVariant(cmd) - targetPkg, _ := lo.Coalesce(pubSubOpts.TargetPackage, path.Base(cmd.TargetDir)) - mainLogger.Debugf("Target package name is %s", targetPkg) if !isSub && !isPub { return fmt.Errorf("%w: no publisher or subscriber set to generate", ErrWrongCliArgs) } @@ -126,7 +124,7 @@ func generate(cmd *GenerateCmd) error { if err != nil { return fmt.Errorf("%w: %w", ErrWrongCliArgs, err) } - renderOpts, err := getRenderOpts(*pubSubOpts, cmd.TargetDir, targetPkg) + renderOpts, err := getRenderOpts(*pubSubOpts, cmd.TargetDir) if err != nil { return fmt.Errorf("%w: %w", ErrWrongCliArgs, err) } @@ -154,6 +152,11 @@ func generate(cmd *GenerateCmd) error { return fmt.Errorf("schema render: %w", err) } + // Formatting + if err = writer.FormatFiles(files); err != nil { + return fmt.Errorf("formatting code: %w", err) + } + // Writing if err = writer.WriteToFiles(files, cmd.TargetDir); err != nil { return fmt.Errorf("writing code to files: %w", err) @@ -341,52 +344,52 @@ func getPubSubVariant(cmd *GenerateCmd) (pub bool, sub bool, variant *generatePu } func getCompileOpts(opts generatePubSubArgs, isPub, isSub bool) (common.CompileOpts, error) { - var err error + //var err error res := common.CompileOpts{ - NoEncodingPackage: opts.NoEncoding, + //NoEncodingPackage: opts.NoEncoding, AllowRemoteRefs: opts.AllowRemoteRefs, RuntimeModule: opts.RuntimeModule, GeneratePublishers: isPub, GenerateSubscribers: isSub, } - includeAll := !opts.SelectChannelsAll && !opts.SelectMessagesAll && !opts.SelectModelsAll && !opts.SelectServersAll - f := func(all, ignoreAll bool, re, ignoreRe string) (r common.ObjectCompileOpts, e error) { - r.Enable = (includeAll || all) && !ignoreAll - if re != "" { - if r.IncludeRegex, e = regexp.Compile(re); e != nil { - return - } - } - if ignoreRe != "" { - if r.ExcludeRegex, e = regexp.Compile(ignoreRe); e != nil { - return - } - } - return - } + //includeAll := !opts.SelectChannelsAll && !opts.SelectMessagesAll && !opts.SelectModelsAll && !opts.SelectServersAll + //f := func(all, ignoreAll bool, re, ignoreRe string) (r common.ObjectCompileOpts, e error) { + // r.Enable = (includeAll || all) && !ignoreAll + // if re != "" { + // if r.IncludeRegex, e = regexp.Compile(re); e != nil { + // return + // } + // } + // if ignoreRe != "" { + // if r.ExcludeRegex, e = regexp.Compile(ignoreRe); e != nil { + // return + // } + // } + // return + //} - if res.ChannelOpts, err = f(opts.SelectChannelsAll, opts.IgnoreChannelsAll, opts.SelectChannelsRe, opts.IgnoreChannelsRe); err != nil { - return res, err - } + //if res.ChannelOpts, err = f(opts.SelectChannelsAll, opts.IgnoreChannelsAll, opts.SelectChannelsRe, opts.IgnoreChannelsRe); err != nil { + // return res, err + //} //if opts.ReuseChannelsModule != "" { // res.ReusePackages[asyncapi.PackageScopeChannels] = opts.ReuseChannelsModule //} - if res.MessageOpts, err = f(opts.SelectMessagesAll, opts.IgnoreMessagesAll, opts.SelectMessagesRe, opts.IgnoreMessagesRe); err != nil { - return res, err - } + //if res.MessageOpts, err = f(opts.SelectMessagesAll, opts.IgnoreMessagesAll, opts.SelectMessagesRe, opts.IgnoreMessagesRe); err != nil { + // return res, err + //} //if opts.ReuseMessagesModule != "" { // res.ReusePackages[asyncapi.PackageScopeMessages] = opts.ReuseMessagesModule //} - if res.ModelOpts, err = f(opts.SelectModelsAll, opts.IgnoreModelsAll, opts.SelectModelsRe, opts.IgnoreModelsRe); err != nil { - return res, err - } + //if res.ModelOpts, err = f(opts.SelectModelsAll, opts.IgnoreModelsAll, opts.SelectModelsRe, opts.IgnoreModelsRe); err != nil { + // return res, err + //} //if opts.ReuseModelsModule != "" { // res.ReusePackages[asyncapi.PackageScopeModels] = opts.ReuseModelsModule //} - if res.ServerOpts, err = f(opts.SelectServersAll, opts.IgnoreServersAll, opts.SelectServersRe, opts.IgnoreServersRe); err != nil { - return res, err - } + //if res.ServerOpts, err = f(opts.SelectServersAll, opts.IgnoreServersAll, opts.SelectServersRe, opts.IgnoreServersRe); err != nil { + // return res, err + //} //if opts.ReuseServersModule != "" { // res.ReusePackages[asyncapi.PackageScopeServers] = opts.ReuseServersModule //} @@ -411,14 +414,37 @@ func getResolver(opts generatePubSubArgs) compiler.SpecFileResolver { } } -func getRenderOpts(opts generatePubSubArgs, targetDir, targetPkg string) (common.RenderOpts, error) { +func getRenderOpts(opts generatePubSubArgs, targetDir string) (common.RenderOpts, error) { res := common.RenderOpts{ RuntimeModule: opts.RuntimeModule, - TargetPackage: targetPkg, TargetDir: targetDir, TemplateDir: opts.TemplateDir, } + // TODO: logging + // Selections + if opts.ConfigFile == "" { + conf, err := loadConfig(opts.ConfigFile) + if err != nil { + return res, err + } + for _, item := range conf.Render.Selections { + pkg, _ := lo.Coalesce(item.Package, lo.Ternary(targetDir != "", path.Base(targetDir), "main")) + templateName, _ := lo.Coalesce(item.Template, "main") + sel := common.RenderSelectionConfig{ + Template: templateName, + File: item.File, + Package: pkg, + TemplateArgs: item.TemplateArgs, + ObjectKindRe: item.ObjectKindRe, + ModuleURLRe: item.ModuleURLRe, + PathRe: item.PathRe, + } + res.Selections = append(res.Selections, sel) + } + } + + // ImportBase importBase := opts.ProjectModule if importBase == "" { b, err := getImportBase() @@ -427,13 +453,32 @@ func getRenderOpts(opts generatePubSubArgs, targetDir, targetPkg string) (common } importBase = b } - importBase = path.Join(importBase, targetPkg) mainLogger.Debugf("Target import base is %s", importBase) res.ImportBase = importBase return res, nil } +func loadConfig(fileName string) (toolConfig, error) { + var conf toolConfig + + f, err := os.Open(fileName) + if err != nil { + return conf, fmt.Errorf("cannot open config file: %w", err) + } + defer f.Close() + + buf, err := io.ReadAll(f) + if err != nil { + return conf, fmt.Errorf("cannot read config file: %w", err) + } + + if err = yaml.Unmarshal(buf, &conf); err != nil { + return conf, fmt.Errorf("cannot parse YAML config file: %w", err) + } + return conf, nil +} + func protocolBuilders() map[string]asyncapi.ProtocolBuilder { return map[string]asyncapi.ProtocolBuilder{ amqp.Builder.ProtocolName(): amqp.Builder, diff --git a/internal/asyncapi/channel.go b/internal/asyncapi/channel.go index fe25a0c..45f31b8 100644 --- a/internal/asyncapi/channel.go +++ b/internal/asyncapi/channel.go @@ -40,10 +40,9 @@ func (c Channel) Compile(ctx *common.CompileContext) error { func (c Channel) buildChannels(ctx *common.CompileContext, channelKey string) ([]common.Renderable, error) { var res []common.Renderable - _, isComponent := ctx.Stack.Top().Flags[common.SchemaTagComponent] ignore := c.XIgnore || - (!ctx.CompileOpts.GeneratePublishers && !ctx.CompileOpts.GenerateSubscribers) || - !ctx.CompileOpts.ChannelOpts.IsAllowedName(channelKey) + (!ctx.CompileOpts.GeneratePublishers && !ctx.CompileOpts.GenerateSubscribers) // || + //!ctx.CompileOpts.ChannelOpts.IsAllowedName(channelKey) if ignore { ctx.Logger.Debug("Channel denoted to be ignored") res = append(res, &render.ProtoChannel{Channel: &render.Channel{Dummy: true}}) @@ -53,7 +52,6 @@ func (c Channel) buildChannels(ctx *common.CompileContext, channelKey string) ([ ctx.Logger.Trace("Ref", "$ref", c.Ref) prm := lang.NewRenderablePromise(c.Ref, common.PromiseOriginUser) // Set a channel to be rendered if we reference it from `channels` document section - prm.DirectRender = !isComponent ctx.PutPromise(prm) res = append(res, prm) return res, nil diff --git a/internal/asyncapi/correlationid.go b/internal/asyncapi/correlationid.go index d77c024..f76908c 100644 --- a/internal/asyncapi/correlationid.go +++ b/internal/asyncapi/correlationid.go @@ -33,7 +33,7 @@ func (c CorrelationID) Compile(ctx *common.CompileContext) error { } func (c CorrelationID) build(ctx *common.CompileContext, correlationIDKey string) (common.Renderable, error) { - ignore := c.XIgnore || !ctx.CompileOpts.MessageOpts.Enable + ignore := c.XIgnore //|| !ctx.CompileOpts.MessageOpts.Enable if ignore { ctx.Logger.Debug("CorrelationID denoted to be ignored") return &render.CorrelationID{}, nil diff --git a/internal/asyncapi/message.go b/internal/asyncapi/message.go index adb5e49..453646f 100644 --- a/internal/asyncapi/message.go +++ b/internal/asyncapi/message.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/bdragon300/go-asyncapi/internal/render/lang" - "github.com/bdragon300/go-asyncapi/internal/specurl" "github.com/bdragon300/go-asyncapi/internal/types" yaml "gopkg.in/yaml.v3" @@ -56,7 +55,7 @@ func (m Message) build(ctx *common.CompileContext, messageKey string) ([]common. var res []common.Renderable _, isComponent := ctx.Stack.Top().Flags[common.SchemaTagComponent] - ignore := m.XIgnore || (isComponent && !ctx.CompileOpts.MessageOpts.IsAllowedName(messageKey)) + ignore := m.XIgnore || isComponent //&& !ctx.CompileOpts.MessageOpts.IsAllowedName(messageKey)) if ignore { ctx.Logger.Debug("Message denoted to be ignored") res = append(res, &render.ProtoMessage{Message: &render.Message{Dummy: true}}) @@ -70,12 +69,11 @@ func (m Message) build(ctx *common.CompileContext, messageKey string) ([]common. return res, nil } + // Being defined in "channels" section, we use the message key as the message name. Otherwise, generate a name, + // because the key will always be "message". msgName := messageKey - // If the message is not a component, but inlined in a channel (i.e. in channels package), the messageKey always - // will be "message". So, we need to generate a unique name for the message, considering if it's - // publish/subscribe message, because we don't generate a separate code object for a channel operation, - // therefore a channel can have two identical messages. - if ctx.CurrentPackage() == PackageScopeChannels { + pathStack := ctx.Stack.Items() + if pathStack[0].PathItem == "channels" { msgName = ctx.GenerateObjName(m.XGoName, "") } msgName, _ = lo.Coalesce(m.XGoName, msgName) @@ -127,7 +125,6 @@ func (m Message) build(ctx *common.CompileContext, messageKey string) ([]common. Name: ctx.GenerateObjName(msgName, "Bindings"), HasDefinition: true, }, - Fields: nil, } ref := ctx.PathStackRef("bindings") diff --git a/internal/asyncapi/object.go b/internal/asyncapi/object.go index 9202500..c0409f0 100644 --- a/internal/asyncapi/object.go +++ b/internal/asyncapi/object.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "github.com/bdragon300/go-asyncapi/internal/render/lang" - "github.com/bdragon300/go-asyncapi/run/http" + "slices" "strconv" "github.com/bdragon300/go-asyncapi/internal/types" @@ -84,7 +84,7 @@ func (o Object) Compile(ctx *common.CompileContext) error { func (o Object) build(ctx *common.CompileContext, flags map[common.SchemaTag]string, objectKey string) (common.GolangType, error) { _, isComponent := flags[common.SchemaTagComponent] - ignore := o.XIgnore || (isComponent && !ctx.CompileOpts.ModelOpts.IsAllowedName(objectKey)) + ignore := o.XIgnore || isComponent //&& !ctx.CompileOpts.ModelOpts.IsAllowedName(objectKey)) if ignore { ctx.Logger.Debug("Object denoted to be ignored") return &lang.GoSimple{Name: "any", IsInterface: true}, nil @@ -237,6 +237,7 @@ func (o Object) getDefaultObjectType(ctx *common.CompileContext) *types.Union2[s func (o Object) buildLangStruct(ctx *common.CompileContext, flags map[common.SchemaTag]string) (*lang.GoStruct, error) { _, hasDefinition := flags[common.SchemaTagDefinition] + _, isComponent := flags[common.SchemaTagComponent] objName, _ := lo.Coalesce(o.XGoName, o.Title) res := lang.GoStruct{ BaseType: lang.BaseType{ @@ -244,17 +245,25 @@ func (o Object) buildLangStruct(ctx *common.CompileContext, flags map[common.Sch Description: o.Description, HasDefinition: hasDefinition, }, + ObjectKind: lo.Ternary(isComponent, common.ObjectKindSchema, common.ObjectKindOther), } // TODO: cache the object name in case any sub-schemas recursively reference it - var messagesPrm *lang.ListPromise[*render.Message] + var contentTypesFunc func() []string _, isMarshal := flags[common.SchemaTagMarshal] if isMarshal { - messagesPrm = lang.NewListCbPromise[*render.Message](func(item common.Renderable, _ []string) bool { + messagesPrm := lang.NewListCbPromise[*render.Message](func(item common.Renderable, _ []string) bool { _, ok := item.(*render.Message) return ok }) ctx.PutListPromise(messagesPrm) + contentTypesFunc = func() []string { + tagNames := lo.Uniq(lo.Map(messagesPrm.T(), func(item *render.Message, _ int) string { + return item.EffectiveContentType() + })) + slices.Sort(tagNames) + return tagNames + } } // Embed external type into the current one, if x-go-type->embedded == true @@ -279,14 +288,14 @@ func (o Object) buildLangStruct(ctx *common.CompileContext, flags map[common.Sch propName, _ := lo.Coalesce(entry.Value.XGoName, entry.Key) xTags, xTagNames, xTagVals := entry.Value.xGoTagsInfo(ctx) f := lang.GoStructField{ - Name: utils.ToGolangName(propName, true), - MarshalName: entry.Key, - Description: entry.Value.Description, - Type: langObj, - TagsSource: messagesPrm, - ExtraTags: xTags, - ExtraTagNames: xTagNames, - ExtraTagValues: xTagVals, + Name: utils.ToGolangName(propName, true), + MarshalName: entry.Key, + Description: entry.Value.Description, + Type: langObj, + ContentTypesFunc: contentTypesFunc, + ExtraTags: xTags, + ExtraTagNames: xTagNames, + ExtraTagValues: xTagVals, } res.Fields = append(res.Fields, f) } @@ -341,7 +350,7 @@ func (o Object) buildLangStruct(ctx *common.CompileContext, flags map[common.Sch KeyType: &lang.GoSimple{Name: "string"}, ValueType: &valTyp, }, - TagsSource: messagesPrm, + ContentTypesFunc: contentTypesFunc, } res.Fields = append(res.Fields, f) } @@ -396,6 +405,7 @@ func (o Object) buildLangArray(ctx *common.CompileContext, flags map[common.Sche func (o Object) buildUnionStruct(ctx *common.CompileContext, flags map[common.SchemaTag]string) (*lang.UnionStruct, error) { _, hasDefinition := flags[common.SchemaTagDefinition] + _, isComponent := flags[common.SchemaTagComponent] objName, _ := lo.Coalesce(o.XGoName, o.Title) res := lang.UnionStruct{ GoStruct: lang.GoStruct{ @@ -404,6 +414,7 @@ func (o Object) buildUnionStruct(ctx *common.CompileContext, flags map[common.Sc Description: o.Description, HasDefinition: hasDefinition, }, + ObjectKind: lo.Ternary(isComponent, common.ObjectKindSchema, common.ObjectKindOther), }, } diff --git a/internal/asyncapi/parameter.go b/internal/asyncapi/parameter.go index 7a27af3..78c2e72 100644 --- a/internal/asyncapi/parameter.go +++ b/internal/asyncapi/parameter.go @@ -29,11 +29,11 @@ func (p Parameter) Compile(ctx *common.CompileContext) error { } func (p Parameter) build(ctx *common.CompileContext, parameterKey string) (common.Renderable, error) { - ignore := !ctx.CompileOpts.ChannelOpts.Enable - if ignore { - ctx.Logger.Debug("Parameter denoted to be ignored along with all channels") - return &render.Parameter{Dummy: true}, nil - } + //ignore := !ctx.CompileOpts.ChannelOpts.Enable + //if ignore { + // ctx.Logger.Debug("Parameter denoted to be ignored along with all channels") + // return &render.Parameter{Dummy: true}, nil + //} if p.Ref != "" { ctx.Logger.Trace("Ref", "$ref", p.Ref) res := lang.NewRenderablePromise(p.Ref, common.PromiseOriginUser) diff --git a/internal/asyncapi/server.go b/internal/asyncapi/server.go index 7ca5d60..a34a70f 100644 --- a/internal/asyncapi/server.go +++ b/internal/asyncapi/server.go @@ -39,7 +39,7 @@ func (s Server) Compile(ctx *common.CompileContext) error { func (s Server) build(ctx *common.CompileContext, serverKey string) (common.Renderable, error) { _, isComponent := ctx.Stack.Top().Flags[common.SchemaTagComponent] - ignore := s.XIgnore || !ctx.CompileOpts.ServerOpts.IsAllowedName(serverKey) + ignore := s.XIgnore //|| !ctx.CompileOpts.ServerOpts.IsAllowedName(serverKey) if ignore { ctx.Logger.Debug("Server denoted to be ignored") return &render.ProtoServer{Server: &render.Server{Dummy: true}}, nil @@ -48,7 +48,6 @@ func (s Server) build(ctx *common.CompileContext, serverKey string) (common.Rend ctx.Logger.Trace("Ref", "$ref", s.Ref) prm := lang.NewRenderablePromise(s.Ref, common.PromiseOriginUser) // Set a server to be rendered if we reference it from `servers` document section - prm.DirectRender = !isComponent ctx.PutPromise(prm) return prm, nil } @@ -83,7 +82,6 @@ func (s Server) build(ctx *common.CompileContext, serverKey string) (common.Rend Name: ctx.GenerateObjName(srvName, "Bindings"), HasDefinition: true, }, - Fields: nil, } ref := ctx.PathStackRef("bindings") diff --git a/internal/common/compile_context.go b/internal/common/compile_context.go index e43387b..36a519e 100644 --- a/internal/common/compile_context.go +++ b/internal/common/compile_context.go @@ -3,7 +3,6 @@ package common import ( "errors" "path" - "regexp" "strings" "github.com/bdragon300/go-asyncapi/internal/specurl" @@ -16,7 +15,10 @@ import ( const nameWordSep = "_" -var ErrDefinitionIsNotAssignedYet = errors.New("definition is not assigned yet") +// ErrObjectDefinitionUnknownYet is returned when some template tries to get a package in the generated code for an +// object, but the definition of this object has not been rendered, therefore the package is unknown yet. When this +// error is returned, a template caused this error goes to the end of the rendering queue. +var ErrObjectDefinitionUnknownYet = errors.New("object definition is unknown yet") type GolangTypeDefinitionInfo struct { Selection RenderSelectionConfig @@ -40,34 +42,34 @@ type CompilationStorage interface { } type CompileOpts struct { - ChannelOpts ObjectCompileOpts - MessageOpts ObjectCompileOpts - ModelOpts ObjectCompileOpts - ServerOpts ObjectCompileOpts - NoEncodingPackage bool // TODO: remove in favor of selections + //ChannelOpts ObjectCompileOpts + //MessageOpts ObjectCompileOpts + //ModelOpts ObjectCompileOpts + //ServerOpts ObjectCompileOpts + //NoEncodingPackage bool // TODO: remove in favor of selections AllowRemoteRefs bool RuntimeModule string GeneratePublishers bool GenerateSubscribers bool } -type ObjectCompileOpts struct { - Enable bool - IncludeRegex *regexp.Regexp - ExcludeRegex *regexp.Regexp -} - -func (o ObjectCompileOpts) IsAllowedName(name string) bool { - switch { - case !o.Enable: - return false - case o.ExcludeRegex != nil && o.ExcludeRegex.MatchString(name): - return false - case o.IncludeRegex != nil: - return o.IncludeRegex.MatchString(name) - } - return true -} +//type ObjectCompileOpts struct { +// Enable bool +// IncludeRegex *regexp.Regexp +// ExcludeRegex *regexp.Regexp +//} +// +//func (o ObjectCompileOpts) IsAllowedName(name string) bool { +// switch { +// case !o.Enable: +// return false +// case o.ExcludeRegex != nil && o.ExcludeRegex.MatchString(name): +// return false +// case o.IncludeRegex != nil: +// return o.IncludeRegex.MatchString(name) +// } +// return true +//} type ContextStackItem struct { PathItem string diff --git a/internal/common/render.go b/internal/common/render.go index 43ce53d..c85cc57 100644 --- a/internal/common/render.go +++ b/internal/common/render.go @@ -1,69 +1,47 @@ package common // ObjectKind is an enumeration of all possible object kinds used in the AsyncAPI specification. -type ObjectKind string +type ObjectKind int const ( - ObjectKindLang ObjectKind = "lang" // Utility language object, not a spec component (type, value, interface, etc.) - ObjectKindSchema ObjectKind = "schema" - ObjectKindServer ObjectKind = "server" - ObjectKindServerVariable ObjectKind = "serverVariable" - ObjectKindChannel ObjectKind = "channel" - ObjectKindMessage ObjectKind = "message" - ObjectKindParameter ObjectKind = "parameter" - ObjectKindCorrelationID ObjectKind = "correlationID" - ObjectKindServerBindings ObjectKind = "serverBindings" - ObjectKindChannelBindings ObjectKind = "channelBindings" - ObjectKindMessageBindings ObjectKind = "messageBindings" - ObjectKindOperationBindings ObjectKind = "operationBindings" - ObjectKindAsyncAPI ObjectKind = "asyncapi" // Utility object represents the entire AsyncAPI document + ObjectKindOther ObjectKind = iota // Utility language object, not intended for selection (type, value, interface, etc.) + ObjectKindSchema + ObjectKindServer + ObjectKindServerVariable + ObjectKindChannel + ObjectKindMessage + ObjectKindParameter + ObjectKindCorrelationID + ObjectKindAsyncAPI // Utility object represents the entire AsyncAPI document ) type Renderable interface { Kind() ObjectKind // Selectable returns true if object can be selected to pass to the templates for rendering. Selectable() bool + String() string } +type ( + RenderSelectionConfig struct { + Template string + File string + Package string + TemplateArgs map[string]string // TODO: pass template args to templates + ObjectKindRe string + ModuleURLRe string + PathRe string + } +) + type RenderOpts struct { RuntimeModule string ImportBase string - TargetPackage string TargetDir string TemplateDir string Selections []RenderSelectionConfig } -type RenderSelectionOutputGroupBy string - -const ( - // RenderSelectionOutputGroupByNone -- a template call for every compile object - RenderSelectionOutputGroupByNone RenderSelectionOutputGroupBy = "none" - // RenderSelectionOutputGroupByAll -- a single template call for all compile objects - RenderSelectionOutputGroupByAll RenderSelectionOutputGroupBy = "all" -) - -type ( - RenderSelectionFilterConfig struct { - ObjectKindRe string `yaml:"objectKindRe"` - ModuleURLRe string `yaml:"moduleURLRe"` - PathRe string `yaml:"pathRe"` - } - - RenderSelectionOutputConfig struct { - GroupBy RenderSelectionOutputGroupBy `yaml:"groupBy"` - Template string `yaml:"template"` - File string `yaml:"file"` - Package string `yaml:"package"` - TemplateArgs map[string]string `yaml:"templateArgs"` - } - - RenderSelectionConfig struct { - RenderSelectionFilterConfig - RenderSelectionOutputConfig - } -) - type RenderContext interface { RuntimeModule(subPackage string) string QualifiedName(parts ...string) string @@ -71,3 +49,10 @@ type RenderContext interface { QualifiedGeneratedPackage(obj GolangType) (string, error) CurrentDefinitionInfo() *GolangTypeDefinitionInfo } + +type ImportItem struct { + Alias string + PackageName string + PackagePath string +} + diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go index de9da29..532506d 100644 --- a/internal/compiler/compiler.go +++ b/internal/compiler/compiler.go @@ -136,12 +136,12 @@ func (c *Module) Compile(ctx *common.CompileContext) error { if err := WalkAndCompile(ctx, reflect.ValueOf(c.parsedSpec)); err != nil { return fmt.Errorf("spec: %w", err) } - if !ctx.CompileOpts.NoEncodingPackage { - c.logger.Trace("Compile the encoding package", "specURL", c.specURL) - if err := EncodingCompile(ctx); err != nil { - return fmt.Errorf("encoding package: %w", err) - } - } + //if !ctx.CompileOpts.NoEncodingPackage { + // c.logger.Trace("Compile the encoding package", "specURL", c.specURL) + // if err := EncodingCompile(ctx); err != nil { + // return fmt.Errorf("encoding package: %w", err) + // } + //} return nil } diff --git a/internal/compiler/parse.go b/internal/compiler/parse.go index dc08458..b6cbd9f 100644 --- a/internal/compiler/parse.go +++ b/internal/compiler/parse.go @@ -31,8 +31,8 @@ func guessSpecKind(decoder anyDecoder) (SpecKind, compiledObject, error) { case test.Asyncapi != "": return SpecKindAsyncapi, &asyncapi.AsyncAPI{}, nil case test.Openapi != "": - panic("openapi not implemented") + panic("openapi not implemented") // TODO } - panic("jsonschema not implemented") + panic("jsonschema not implemented") // TODO // Assume that some data is jsonschema, TODO: maybe it's better to match more strict? } diff --git a/internal/render/asyncapi.go b/internal/render/asyncapi.go index 7f82675..c8da96e 100644 --- a/internal/render/asyncapi.go +++ b/internal/render/asyncapi.go @@ -28,6 +28,10 @@ func (a AsyncAPI) EffectiveDefaultContentType() string { return res } +func (a AsyncAPI) String() string { + return "AsyncAPI" +} + //// SpecEffectiveContentTypes returns a list of all unique content types used in the spec. This includes all content //// types from all messages and the default content type. //func (a AsyncAPI) SpecEffectiveContentTypes() []string { diff --git a/internal/render/bindings.go b/internal/render/bindings.go index 42eb7da..75a15c9 100644 --- a/internal/render/bindings.go +++ b/internal/render/bindings.go @@ -9,19 +9,17 @@ import ( // Bindings never renders itself, only as a part of other object type Bindings struct { Name string - // ObjectKindMessageBindings, ObjectKindOperationBindings, ObjectKindChannelBindings, ObjectKindServerBindings - ObjectKind common.ObjectKind Values types.OrderedMap[string, *lang.GoValue] // Binding values by protocol // Value of jsonschema fields as json marshalled strings JSONValues types.OrderedMap[string, types.OrderedMap[string, string]] // Binbing values by protocol } -func (b Bindings) Kind() common.ObjectKind { - return b.ObjectKind +func (b *Bindings) Kind() common.ObjectKind { + return common.ObjectKindOther // TODO: separate Bindings from Channel, leaving only the Promise, and make its own 4 ObjectKinds } -func (b Bindings) Selectable() bool { +func (b *Bindings) Selectable() bool { return false } @@ -29,10 +27,10 @@ func (b Bindings) Selectable() bool { //func (b *Bindings) ID() string { // return b.Name //} -// -//func (b *Bindings) String() string { -// return "Bindings " + b.Name -//} + +func (b *Bindings) String() string { + return "Bindings " + b.Name +} //func (b *Bindings) RenderBindingsMethod( // ctx *common.RenderContext, diff --git a/internal/render/channel.go b/internal/render/channel.go index 51b271f..6cc2690 100644 --- a/internal/render/channel.go +++ b/internal/render/channel.go @@ -1,12 +1,10 @@ package render import ( + "github.com/bdragon300/go-asyncapi/internal/common" "github.com/bdragon300/go-asyncapi/internal/render/context" "github.com/bdragon300/go-asyncapi/internal/render/lang" "github.com/samber/lo" - "sort" - - "github.com/bdragon300/go-asyncapi/internal/common" ) type Channel struct { @@ -107,9 +105,9 @@ func (c Channel) Selectable() bool { // return c.Name //} // -//func (c Channel) String() string { -// return "Channel " + c.Name -//} +func (c Channel) String() string { + return "Channel " + c.Name +} //func (c Channel) renderChannelNameFunc(ctx *common.RenderContext) []*j.Statement { // ctx.Logger.Trace("renderChannelNameFunc") @@ -144,31 +142,30 @@ func (c Channel) Selectable() bool { //} // ServersProtocols returns supported protocol list for the given servers, throwing out unsupported ones -// TODO: move to top-level template -func (c Channel) ServersProtocols() []string { - res := lo.Uniq(lo.FilterMap(c.ServersPromise.Targets(), func(item *Server, _ int) (string, bool) { - _, ok := ctx.ProtoRenderers[item.Protocol] - if !ok { - ctx.Logger.Warnf("Skip protocol %q since it is not supported", item.Protocol) - } - return item.Protocol, ok && !item.Dummy - })) - sort.Strings(res) - return res -} +//func (c Channel) ServersProtocols() []string { +// res := lo.Uniq(lo.FilterMap(c.ServersPromise.T(), func(item *Server, _ int) (string, bool) { +// _, ok := ctx.ProtoRenderers[item.Protocol] +// if !ok { +// ctx.Logger.Warnf("Skip protocol %q since it is not supported", item.Protocol) +// } +// return item.Protocol, ok && !item.Dummy +// })) +// sort.Strings(res) +// return res +//} func (c Channel) BindingsProtocols() (res []string) { if c.BindingsChannelPromise != nil { - res = append(res, c.BindingsChannelPromise.Target().Values.Keys()...) - res = append(res, c.BindingsChannelPromise.Target().JSONValues.Keys()...) + res = append(res, c.BindingsChannelPromise.T().Values.Keys()...) + res = append(res, c.BindingsChannelPromise.T().JSONValues.Keys()...) } if c.BindingsPublishPromise != nil { - res = append(res, c.BindingsPublishPromise.Target().Values.Keys()...) - res = append(res, c.BindingsPublishPromise.Target().JSONValues.Keys()...) + res = append(res, c.BindingsPublishPromise.T().Values.Keys()...) + res = append(res, c.BindingsPublishPromise.T().JSONValues.Keys()...) } if c.BindingsSubscribePromise != nil { - res = append(res, c.BindingsSubscribePromise.Target().Values.Keys()...) - res = append(res, c.BindingsSubscribePromise.Target().JSONValues.Keys()...) + res = append(res, c.BindingsSubscribePromise.T().Values.Keys()...) + res = append(res, c.BindingsSubscribePromise.T().JSONValues.Keys()...) } return lo.Uniq(res) } @@ -179,20 +176,20 @@ func (c Channel) ProtoBindingsValue(protoName string) common.Renderable { EmptyCurlyBrackets: true, } if c.BindingsChannelPromise != nil { - if b, ok := c.BindingsChannelPromise.Target().Values.Get(protoName); ok { - ctx.Logger.Debug("Channel bindings", "proto", protoName) + if b, ok := c.BindingsChannelPromise.T().Values.Get(protoName); ok { + //ctx.Logger.Debug("Channel bindings", "proto", protoName) res = b } } if c.BindingsPublishPromise != nil { - if v, ok := c.BindingsPublishPromise.Target().Values.Get(protoName); ok { - ctx.Logger.Debug("Publish operation bindings", "proto", protoName) + if v, ok := c.BindingsPublishPromise.T().Values.Get(protoName); ok { + //ctx.Logger.Debug("Publish operation bindings", "proto", protoName) res.StructValues.Set("PublisherBindings", v) } } if c.BindingsSubscribePromise != nil { - if v, ok := c.BindingsSubscribePromise.Target().Values.Get(protoName); ok { - ctx.Logger.Debug("Subscribe operation bindings", "proto", protoName) + if v, ok := c.BindingsSubscribePromise.T().Values.Get(protoName); ok { + //ctx.Logger.Debug("Subscribe operation bindings", "proto", protoName) res.StructValues.Set("SubscriberBindings", v) } } @@ -205,3 +202,7 @@ type ProtoChannel struct { ProtoName string } + +func (p ProtoChannel) String() string { + return "ProtoChannel " + p.Name +} \ No newline at end of file diff --git a/internal/render/context/context.go b/internal/render/context/context.go index a87fe88..e25f366 100644 --- a/internal/render/context/context.go +++ b/internal/render/context/context.go @@ -1,25 +1,24 @@ package context import ( + "cmp" "fmt" "github.com/bdragon300/go-asyncapi/internal/common" + "github.com/bdragon300/go-asyncapi/internal/utils" "github.com/samber/lo" "path" + "slices" "strings" + "unicode" ) var Context common.RenderContext -type ImportItem struct { - Alias string - PackageName string -} - // TODO: add object path? type RenderContextImpl struct { RenderOpts common.RenderOpts CurrentSelectionConfig common.RenderSelectionConfig - imports map[string]ImportItem // Key: package path + imports map[string]common.ImportItem // Key: package path } func (c *RenderContextImpl) RuntimeModule(subPackage string) string { @@ -27,8 +26,8 @@ func (c *RenderContextImpl) RuntimeModule(subPackage string) string { } func (c *RenderContextImpl) QualifiedName(parts ...string) string { - name, pkgPath, pkgName := qualifiedToImport(parts) - return fmt.Sprintf("%s.%s", c.importPackage(pkgPath, pkgName), name) + pkgPath, pkgName, name := qualifiedToImport(parts) + return fmt.Sprintf("%s.%s", c.importPackage(pkgPath, pkgName), utils.ToGolangName(name, unicode.IsUpper(rune(name[0])))) } // QualifiedGeneratedPackage checks if the object is in the generated package of CurrentSelectionConfig and returns @@ -53,15 +52,31 @@ func (c *RenderContextImpl) QualifiedGeneratedPackage(obj common.GolangType) (st func (c *RenderContextImpl) QualifiedRuntimeName(parts ...string) string { p := append([]string{c.RenderOpts.ImportBase}, parts...) - name, pkgPath, pkgName := qualifiedToImport(p) - return fmt.Sprintf("%s.%s", c.importPackage(pkgPath, pkgName), name) + pkgPath, pkgName, name := qualifiedToImport(p) + return fmt.Sprintf("%s.%s", c.importPackage(pkgPath, pkgName), utils.ToGolangName(name, unicode.IsUpper(rune(name[0])))) +} + +func (c *RenderContextImpl) CurrentDefinitionInfo() *common.GolangTypeDefinitionInfo { + return &common.GolangTypeDefinitionInfo{Selection: c.CurrentSelectionConfig} +} + +func (c *RenderContextImpl) CurrentSelection() common.RenderSelectionConfig { + return c.CurrentSelectionConfig +} + +func (c *RenderContextImpl) Imports() []common.ImportItem { + res := lo.Values(c.imports) + slices.SortFunc(res, func(a, b common.ImportItem) int { + return cmp.Compare(a.PackagePath, b.PackagePath) + }) + return res } func (c *RenderContextImpl) importPackage(pkgPath string, pkgName string) string { if _, ok := c.imports[pkgPath]; !ok { - res := ImportItem{PackageName: pkgName} + res := common.ImportItem{PackageName: pkgName, PackagePath: pkgPath} // Find imports with the same package name - namesakes := lo.Filter(lo.Entries(c.imports), func(item lo.Entry[string, ImportItem], _ int) bool { + namesakes := lo.Filter(lo.Entries(c.imports), func(item lo.Entry[string, common.ImportItem], _ int) bool { return item.Key != pkgPath && item.Value.PackageName == pkgName }) if len(namesakes) > 0 { @@ -76,10 +91,6 @@ func (c *RenderContextImpl) importPackage(pkgPath string, pkgName string) string return pkgName } -func (c *RenderContextImpl) CurrentDefinitionInfo() *common.GolangTypeDefinitionInfo { - return &common.GolangTypeDefinitionInfo{Selection: c.CurrentSelectionConfig} -} - //// LogStartRender is typically called at the beginning of a D or U method and logs that the //// object is started to be rendered. It also logs the object's name, type, and the current package. //// Every call to LogStartRender should be followed by a call to LogFinishRender which logs that the object is finished to be @@ -110,14 +121,14 @@ func (c *RenderContextImpl) CurrentDefinitionInfo() *common.GolangTypeDefinition // qualifiedToImport converts the qual* template function parameters to qualified name and import package path. // And also it returns the package name (the last part of the package path). -func qualifiedToImport(parts []string) (name string, pkgPath string, pkgName string) { - // parts["a"] -> ["", "a", "a"] - // parts["", "a"] -> ["a", "", ""] - // parts["a.x"] -> ["x", "a", "a"] - // parts["a/b/c"] -> ["", "a/b/c", "c"] - // parts["a", "x"] -> ["x", "a", "a"] - // parts["a/b.c", "x"] -> ["x", "a/b.c", "bc"] - // parts["n", "d", "a/b.c", "x"] -> ["x", "n/d/a/b.c", "bc"] +func qualifiedToImport(parts []string) (pkgPath string, pkgName string, name string) { + // parts["a"] -> ["a", "a", ""] + // parts["", "a"] -> ["", "", "a"] + // parts["a.x"] -> ["a", "a", "x"] + // parts["a/b/c"] -> ["a/b/c", "c", ""] + // parts["a", "x"] -> ["a", "a", "x"] + // parts["a/b.c", "x"] -> ["a/b.c", "bc", "x"] + // parts["n", "d", "a/b.c", "x"] -> ["n/d/a/b.c", "bc", "x"] switch len(parts) { case 0: panic("Empty parameters, at least one is required") diff --git a/internal/render/correlationid.go b/internal/render/correlationid.go index 8651f5f..4ce4f74 100644 --- a/internal/render/correlationid.go +++ b/internal/render/correlationid.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/bdragon300/go-asyncapi/internal/render/context" "github.com/bdragon300/go-asyncapi/internal/render/lang" - "github.com/bdragon300/go-asyncapi/internal/tpl" + "github.com/bdragon300/go-asyncapi/internal/utils" "net/url" "strconv" "strings" @@ -18,8 +18,8 @@ const structReceiverName = "m" type CorrelationIDStructField string const ( - CorrelationIDStructFieldPayload CorrelationIDStructField = "Payload" - CorrelationIDStructFieldHeaders CorrelationIDStructField = "Headers" + CorrelationIDStructFieldPayload CorrelationIDStructField = "payload" + CorrelationIDStructFieldHeaders CorrelationIDStructField = "headers" ) // CorrelationID never renders itself, only as a part of message struct @@ -49,16 +49,16 @@ func (c CorrelationID) Selectable() bool { //func (c CorrelationID) ID() string { // return c.Name //} -// -//func (c CorrelationID) String() string { -// return "CorrelationID " + c.Name -//} + +func (c CorrelationID) String() string { + return "CorrelationID " + c.Name +} func (c CorrelationID) RenderSetterBody(inVar string, inVarType *lang.GoStruct) string { - ctx.LogStartRender("CorrelationID.RenderSetterBody", "", c.Name, "definition", false) - defer ctx.LogFinishRender() + //ctx.LogStartRender("CorrelationID.RenderSetterBody", "", c.Name, "definition", false) + //defer ctx.LogFinishRender() - f, ok := lo.Find(inVarType.Fields, func(item lang.GoStructField) bool { return item.Name == c.StructField }) + f, ok := lo.Find(inVarType.Fields, func(item lang.GoStructField) bool { return item.Name == string(c.StructField) }) if !ok { panic(fmt.Errorf("field %s not found in inVarType", c.StructField)) } @@ -95,7 +95,7 @@ func (c CorrelationID) RenderSetterBody(inVar string, inVarType *lang.GoStruct) } func (c CorrelationID) TargetVarType(varType *lang.GoStruct) common.GolangType { - f, ok := lo.Find(varType.Fields, func(item lang.GoStructField) bool { return item.Name == c.StructField }) + f, ok := lo.Find(varType.Fields, func(item lang.GoStructField) bool { return item.Name == string(c.StructField) }) if !ok { panic(fmt.Errorf("field %s not found in varType", c.StructField)) } @@ -159,10 +159,10 @@ func (c CorrelationID) TargetVarType(varType *lang.GoStruct) common.GolangType { //} func (c CorrelationID) RenderGetterBody(outVar string, outVarType *lang.GoStruct) string { - ctx.LogStartRender("CorrelationID.RenderGetterDefinition", "", c.Name, "definition", false) - defer ctx.LogFinishRender() + //ctx.LogStartRender("CorrelationID.RenderGetterDefinition", "", c.Name, "definition", false) + //defer ctx.LogFinishRender() - f, ok := lo.Find(outVarType.Fields, func(item lang.GoStructField) bool { return item.Name == c.StructField }) + f, ok := lo.Find(outVarType.Fields, func(item lang.GoStructField) bool { return item.Name == string(c.StructField) }) if !ok { panic(fmt.Errorf("field %s not found in outVarType", c.StructField)) } @@ -246,7 +246,7 @@ func (c CorrelationID) renderValueExtractionCode( validationCode bool, ) (items []correlationIDExpansionStep, err error) { // TODO: consider also AdditionalProperties in object - ctx.Logger.Trace("Render correlationId extraction code", "path", path, "initialType", initialType.String()) + //ctx.Logger.Trace("Render correlationId extraction code", "path", path, "initialType", initialType.String()) pathIdx := 0 baseType := initialType @@ -266,7 +266,7 @@ func (c CorrelationID) renderValueExtractionCode( switch typ := baseType.(type) { case *lang.GoStruct: - ctx.Logger.Trace("In GoStruct", "path", path[:pathIdx], "name", typ.ID(), "member", memberName) + //ctx.Logger.Trace("In GoStruct", "path", path[:pathIdx], "name", typ.ID(), "member", memberName) fld, ok := lo.Find(typ.Fields, func(item lang.GoStructField) bool { return item.MarshalName == memberName }) if !ok { err = fmt.Errorf( @@ -280,8 +280,8 @@ func (c CorrelationID) renderValueExtractionCode( body = []string{fmt.Sprintf("%s := %s", nextAnchor, varValueStmts)} case *lang.GoMap: // TODO: x-parameter in correlationIDs spec section to set numbers as "0" for string keys or 0 for int keys - ctx.Logger.Trace("In GoMap", "path", path[:pathIdx], "name", typ.ID(), "member", memberName) - varValueStmts = fmt.Sprintf("%s[%s]", anchor, tpl.templateGoLit(memberName)) + //ctx.Logger.Trace("In GoMap", "path", path[:pathIdx], "name", typ.ID(), "member", memberName) + varValueStmts = fmt.Sprintf("%s[%s]", anchor, utils.ToGoLiteral(memberName)) baseType = typ.ValueType varExpr := fmt.Sprintf("var %s %s", nextAnchor, typ.ValueType.U()) if t, ok := typ.ValueType.(lang.GolangPointerType); ok && t.IsPointer() { @@ -297,7 +297,7 @@ func (c CorrelationID) renderValueExtractionCode( ifExpr += fmt.Sprintf(` else { err = %s("key %%q not found in map on path /%s", %s) return - }`, fmtErrorf, strings.Join(path[:pathIdx], "/"), tpl.templateGoLit(memberName)) + }`, fmtErrorf, strings.Join(path[:pathIdx], "/"), utils.ToGoLiteral(memberName)) } body = []string{ fmt.Sprintf(`if %s == nil { @@ -307,7 +307,7 @@ func (c CorrelationID) renderValueExtractionCode( ifExpr, } case *lang.GoArray: - ctx.Logger.Trace("In GoArray", "path", path[:pathIdx], "name", typ.ID(), "member", memberName) + //ctx.Logger.Trace("In GoArray", "path", path[:pathIdx], "name", typ.ID(), "member", memberName) if _, ok := memberName.(string); ok { err = fmt.Errorf( "index %q is not a number, array %s, path: /%s", @@ -322,13 +322,13 @@ func (c CorrelationID) renderValueExtractionCode( body = append(body, fmt.Sprintf(`if len(%s) <= %s { err = %s("index %%q is out of range in array of length %%d on path /%s", %s, len(%s)) return - }`, anchor, tpl.templateGoLit(memberName), fmtErrorf, strings.Join(path[:pathIdx], "/"), tpl.templateGoLit(memberName), anchor)) + }`, anchor, utils.ToGoLiteral(memberName), fmtErrorf, strings.Join(path[:pathIdx], "/"), utils.ToGoLiteral(memberName), anchor)) } - varValueStmts = fmt.Sprintf("%s[%s]", anchor, tpl.templateGoLit(memberName)) + varValueStmts = fmt.Sprintf("%s[%s]", anchor, utils.ToGoLiteral(memberName)) baseType = typ.ItemsType body = append(body, fmt.Sprintf("%s := %s", nextAnchor, varValueStmts)) case *lang.GoSimple: // Should be a terminal type in chain, raise error otherwise (if any path parts left to resolve) - ctx.Logger.Trace("In GoSimple", "path", path[:pathIdx], "name", typ.ID(), "member", memberName) + //ctx.Logger.Trace("In GoSimple", "path", path[:pathIdx], "name", typ.ID(), "member", memberName) if pathIdx >= len(path)-1 { // Primitive types should get addressed by the last path item err = fmt.Errorf( "type %q cannot be resolved further, path: /%s", @@ -339,10 +339,10 @@ func (c CorrelationID) renderValueExtractionCode( } baseType = typ case lang.GolangTypeWrapperType: - ctx.Logger.Trace( - "In wrapper type", - "path", path[:pathIdx], "name", typ.String(), "type", fmt.Sprintf("%T", typ), "member", memberName, - ) + //ctx.Logger.Trace( + // "In wrapper type", + // "path", path[:pathIdx], "name", typ.String(), "type", fmt.Sprintf("%T", typ), "member", memberName, + //) t, ok := typ.UnwrapGolangType() if !ok { err = fmt.Errorf( @@ -355,7 +355,7 @@ func (c CorrelationID) renderValueExtractionCode( baseType = t continue default: - ctx.Logger.Trace("Unknown type", "path", path[:pathIdx], "name", typ.String(), "type", fmt.Sprintf("%T", typ)) + //ctx.Logger.Trace("Unknown type", "path", path[:pathIdx], "name", typ.String(), "type", fmt.Sprintf("%T", typ)) err = fmt.Errorf( "type %s is not addressable, path: /%s", typ.TypeName(), diff --git a/internal/render/lang/base.go b/internal/render/lang/base.go index 56d6cc8..baaeacc 100644 --- a/internal/render/lang/base.go +++ b/internal/render/lang/base.go @@ -2,7 +2,6 @@ package lang import ( "github.com/bdragon300/go-asyncapi/internal/common" - "github.com/bdragon300/go-asyncapi/internal/render" "github.com/bdragon300/go-asyncapi/internal/render/context" "github.com/bdragon300/go-asyncapi/internal/tpl" "strings" @@ -21,7 +20,7 @@ type BaseType struct { } func (b *BaseType) Kind() common.ObjectKind { - return common.ObjectKindLang + return common.ObjectKindOther } func (b *BaseType) Selectable() bool { @@ -32,27 +31,20 @@ func (b *BaseType) TypeName() string { return b.Name } -func (b *BaseType) String() string { - if b.Import != "" { - return "GoType /" + b.Import + "." + b.Name - } - return "GoType " + b.Name -} - func (b *BaseType) IsPointer() bool { return false } func (b *BaseType) DefinitionInfo() (*common.GolangTypeDefinitionInfo, error) { if b.definitionInfo == nil { - return nil, common.ErrDefinitionIsNotAssignedYet + return nil, common.ErrObjectDefinitionUnknownYet } return b.definitionInfo, nil } type GolangTypeWrapperType interface { UnwrapGolangType() (common.GolangType, bool) - String() string + //String() string } type GolangPointerType interface { // TODO: replace with common.GolangType? or move where it is used @@ -70,7 +62,7 @@ func renderTemplate[T common.Renderable](name string, obj T) string { panic("template not found: " + name) } - tmpl = tmpl.Funcs(render.GetTemplateFunctions(context.Context)) + tmpl = tmpl.Funcs(tpl.GetTemplateFunctions(context.Context)) if err := tmpl.Execute(&b, obj); err != nil { panic(err) } diff --git a/internal/render/lang/goarray.go b/internal/render/lang/goarray.go index 6adac01..fd9be88 100644 --- a/internal/render/lang/goarray.go +++ b/internal/render/lang/goarray.go @@ -53,3 +53,10 @@ func (a GoArray) U() string { //return []*jen.Statement{jen.Index().Add(items...)} return renderTemplate("lang/goarray/usage", &a) } + +func (a GoArray) String() string { + if a.Import != "" { + return "GoArray /" + a.Import + "." + a.Name + } + return "GoArray " + a.Name +} \ No newline at end of file diff --git a/internal/render/lang/gomap.go b/internal/render/lang/gomap.go index 818d6b1..09bf3ad 100644 --- a/internal/render/lang/gomap.go +++ b/internal/render/lang/gomap.go @@ -46,3 +46,10 @@ func (m GoMap) U() string { //return []*jen.Statement{jen.Map((&jen.Statement{}).Add(keyType...)).Add(valueType...)} return renderTemplate("lang/gomap/usage", &m) } + +func (m GoMap) String() string { + if m.Import != "" { + return "GoMap /" + m.Import + "." + m.Name + } + return "GoMap " + m.Name +} \ No newline at end of file diff --git a/internal/render/lang/gosimple.go b/internal/render/lang/gosimple.go index 483456b..700d061 100644 --- a/internal/render/lang/gosimple.go +++ b/internal/render/lang/gosimple.go @@ -11,7 +11,7 @@ type GoSimple struct { } func (p GoSimple) Kind() common.ObjectKind { - return common.ObjectKindLang + return common.ObjectKindOther } func (p GoSimple) Selectable() bool { diff --git a/internal/render/lang/gostruct.go b/internal/render/lang/gostruct.go index 61c8157..c65dd30 100644 --- a/internal/render/lang/gostruct.go +++ b/internal/render/lang/gostruct.go @@ -2,7 +2,6 @@ package lang import ( "fmt" - "github.com/bdragon300/go-asyncapi/internal/render" "github.com/bdragon300/go-asyncapi/internal/render/context" "slices" "strconv" @@ -10,8 +9,6 @@ import ( "github.com/bdragon300/go-asyncapi/internal/types" - "github.com/samber/lo" - "github.com/bdragon300/go-asyncapi/internal/common" ) @@ -19,7 +16,7 @@ import ( type GoStruct struct { BaseType Fields []GoStructField - // Typically it's ObjectKindLang or ObjectKindSchema + // Typically it's ObjectKindOther or ObjectKindSchema ObjectKind common.ObjectKind } @@ -85,16 +82,23 @@ func (s GoStruct) IsStruct() bool { return true } +func (s GoStruct) String() string { + if s.Import != "" { + return "GoStruct /" + s.Import + "." + s.Name + } + return "GoStruct " + s.Name +} + // GoStructField defines the data required to generate a field in Go. type GoStructField struct { - Name string - MarshalName string - Description string - Type common.GolangType - TagsSource *ListPromise[*render.Message] // FIXME: rework this - ExtraTags types.OrderedMap[string, string] // Just append these tags as constant, overwrite other tags on overlap - ExtraTagNames []string // Append these tags and fill them the same value as others - ExtraTagValues []string // Add these comma-separated values to all tags (excluding ExtraTags) + Name string + MarshalName string + Description string + Type common.GolangType + ContentTypesFunc func() []string // Returns list of content types associated with the struct + ExtraTags types.OrderedMap[string, string] // Just append these tags as constant, overwrite other tags on overlap + ExtraTagNames []string // Append these tags and fill them the same value as others + ExtraTagValues []string // Add these comma-separated values to all tags (excluding ExtraTags) } //func (f GoStructField) renderDefinition() []*jen.Statement { @@ -111,9 +115,9 @@ type GoStructField struct { // items := utils.ToCode(f.Type.U()) // stmt = stmt.Add(items...) // -// if f.TagsSource != nil { +// if f.ContentTypesFunc != nil { // tagValues := append([]string{f.MarshalName}, f.ExtraTagValues...) -// tagNames := lo.Uniq(lo.FilterMap(f.TagsSource.Targets(), func(item *render.Message, _ int) (string, bool) { +// tagNames := lo.Uniq(lo.FilterMap(f.ContentTypesFunc.Targets(), func(item *render.Message, _ int) (string, bool) { // format := render.getFormatByContentType(item.ContentType) // FIXME: rework this // return format, format != "" // })) @@ -131,27 +135,8 @@ type GoStructField struct { // return res //} -func (f GoStructField) GetTags() types.OrderedMap[string, string] { - tagValues := append([]string{f.MarshalName}, f.ExtraTagValues...) - tagNames := lo.Uniq(lo.FilterMap(f.TagsSource.Targets(), func(item *render.Message, _ int) (string, bool) { - format := render.getFormatByContentType(item.ContentType) // FIXME: rework this - return format, format != "" - })) - tagNames = append(tagNames, f.ExtraTagNames...) - slices.Sort(tagNames) - - var res types.OrderedMap[string, string] - for _, item := range tagNames { - res.Set(item, strings.Join(tagValues, ",")) - } - for _, item := range f.ExtraTags.Entries() { - res.Set(item.Key, item.Value) - } - return res -} - func(f GoStructField) RenderTags() string { - tags := f.GetTags() + tags := f.getTags() var b strings.Builder for _, e := range tags.Entries() { @@ -170,3 +155,22 @@ func(f GoStructField) RenderTags() string { return str } + +func (f GoStructField) getTags() types.OrderedMap[string, string] { + tagValues := append([]string{f.MarshalName}, f.ExtraTagValues...) + var tagNames []string + if f.ContentTypesFunc != nil { + tagNames = f.ContentTypesFunc() + } + tagNames = append(tagNames, f.ExtraTagNames...) + slices.Sort(tagNames) + + var res types.OrderedMap[string, string] + for _, item := range tagNames { + res.Set(item, strings.Join(tagValues, ",")) + } + for _, item := range f.ExtraTags.Entries() { + res.Set(item.Key, item.Value) + } + return res +} diff --git a/internal/render/lang/gotypealias.go b/internal/render/lang/gotypealias.go index f73cd2e..e2713a6 100644 --- a/internal/render/lang/gotypealias.go +++ b/internal/render/lang/gotypealias.go @@ -64,3 +64,10 @@ func (p GoTypeAlias) IsStruct() bool { } return false } + +func (p GoTypeAlias) String() string { + if p.Import != "" { + return "GoTypeAlias /" + p.Import + "." + p.Name + } + return "GoTypeAlias " + p.Name +} \ No newline at end of file diff --git a/internal/render/lang/govalue.go b/internal/render/lang/govalue.go index 1f94312..e47f3fb 100644 --- a/internal/render/lang/govalue.go +++ b/internal/render/lang/govalue.go @@ -24,7 +24,7 @@ type GolangPointerWrapperType interface { } func (gv GoValue) Kind() common.ObjectKind { - return common.ObjectKindLang + return common.ObjectKindOther } func (gv GoValue) Selectable() bool { diff --git a/internal/render/lang/promise.go b/internal/render/lang/promise.go index 3192716..535e040 100644 --- a/internal/render/lang/promise.go +++ b/internal/render/lang/promise.go @@ -41,7 +41,7 @@ func (r *Promise[T]) FindCallback() func(item common.Renderable, path []string) return r.findCb } -func (r *Promise[T]) Target() T { +func (r *Promise[T]) T() T { return r.target } @@ -114,7 +114,7 @@ func (r *ListPromise[T]) FindCallback() func(item common.Renderable, path []stri return r.findCb } -func (r *ListPromise[T]) Targets() []T { +func (r *ListPromise[T]) T() []T { return r.targets } @@ -126,26 +126,15 @@ func NewRenderablePromise(ref string, origin common.PromiseOrigin) *RenderablePr type RenderablePromise struct { Promise[common.Renderable] - // DirectRender marks the promise to be rendered directly, even if object it points to not marked to do so. - // Be careful, in order to avoid duplicated object appearing in the output, this flag should be set only for - // objects which are not marked to be rendered directly - DirectRender bool } func (r *RenderablePromise) Kind() common.ObjectKind { return r.target.Kind() } -func (r *RenderablePromise) D() string { - return r.target.D() -} - -func (r *RenderablePromise) U() string { - return r.target.U() -} - +// TODO: return always false func (r *RenderablePromise) Selectable() bool { - return r.DirectRender // Prevent rendering the object we're point to for several times + return r.origin == common.PromiseOriginUser && r.target.Selectable() } func (r *RenderablePromise) String() string { @@ -160,10 +149,6 @@ func NewGolangTypePromise(ref string, origin common.PromiseOrigin) *GolangTypePr type GolangTypePromise struct { Promise[common.GolangType] - // DirectRender marks the promise to be rendered directly, even if object it points to not marked to do so. - // Be careful, in order to avoid duplicated object appearing in the output, this flag should be set only for - // objects which are not marked to be rendered directly - DirectRender bool // TODO: rework or remove } func (r *GolangTypePromise) Kind() common.ObjectKind { @@ -175,7 +160,7 @@ func (r *GolangTypePromise) TypeName() string { } func (r *GolangTypePromise) Selectable() bool { - return r.DirectRender // Prevent rendering the object we're point to for several times + return r.origin == common.PromiseOriginUser && r.target.Selectable() } func (r *GolangTypePromise) IsPointer() bool { diff --git a/internal/render/lang/union.go b/internal/render/lang/union.go index 797d7ec..fa7820c 100644 --- a/internal/render/lang/union.go +++ b/internal/render/lang/union.go @@ -56,6 +56,13 @@ func (s UnionStruct) UnionStruct() common.GolangType { return &strct } +func (s UnionStruct) String() string { + if s.Import != "" { + return "UnionStruct /" + s.Import + "." + s.Name + } + return "UnionStruct " + s.Name +} + //func (s UnionStruct) renderMethods() []*jen.Statement { // ctx.Logger.Trace("renderMethods") // diff --git a/internal/render/message.go b/internal/render/message.go index f682204..0c7b521 100644 --- a/internal/render/message.go +++ b/internal/render/message.go @@ -1,11 +1,9 @@ package render import ( + "github.com/bdragon300/go-asyncapi/internal/common" "github.com/bdragon300/go-asyncapi/internal/render/lang" "github.com/samber/lo" - "sort" - - "github.com/bdragon300/go-asyncapi/internal/common" ) type Message struct { @@ -33,14 +31,14 @@ func (m Message) Selectable() bool { } func (m Message) EffectiveContentType() string { - res, _ := lo.Coalesce(m.ContentType, m.AsyncAPIPromise.Target().EffectiveDefaultContentType()) + res, _ := lo.Coalesce(m.ContentType, m.AsyncAPIPromise.T().EffectiveDefaultContentType()) return res } func (m Message) BindingsProtocols() (res []string) { if m.BindingsPromise != nil { - res = append(res, m.BindingsPromise.Target().Values.Keys()...) - res = append(res, m.BindingsPromise.Target().JSONValues.Keys()...) + res = append(res, m.BindingsPromise.T().Values.Keys()...) + res = append(res, m.BindingsPromise.T().JSONValues.Keys()...) } return lo.Uniq(res) } @@ -84,16 +82,16 @@ func (m Message) BindingsProtocols() (res []string) { // return m.Name //} // -//func (m Message) String() string { -// return "Message " + m.Name -//} +func (m Message) String() string { + return "Message " + m.Name +} func (m Message) HasProtoBindings(protoName string) bool { if m.BindingsPromise == nil { return false } - _, ok1 := m.BindingsPromise.Target().Values.Get(protoName) - _, ok2 := m.BindingsPromise.Target().JSONValues.Get(protoName) + _, ok1 := m.BindingsPromise.T().Values.Get(protoName) + _, ok2 := m.BindingsPromise.T().JSONValues.Get(protoName) return ok1 || ok2 } @@ -284,20 +282,23 @@ func (m Message) HasProtoBindings(protoName string) bool { //} // ServerProtocols returns supported protocol list for the given servers, throwing out unsupported ones -// TODO: move to top-level template -func (m Message) ServerProtocols() []string { - res := lo.Uniq(lo.FilterMap(m.AllServersPromise.Targets(), func(item *Server, _ int) (string, bool) { - _, ok := ctx.ProtoRenderers[item.Protocol] - if !ok { - ctx.Logger.Warnf("Skip protocol %q since it is not supported", item.Protocol) - } - return item.Protocol, ok && !item.Dummy - })) - sort.Strings(res) - return res -} +//func (m Message) ServerProtocols() []string { +// res := lo.Uniq(lo.FilterMap(m.AllServersPromise.T(), func(item *Server, _ int) (string, bool) { +// _, ok := ctx.ProtoRenderers[item.Protocol] +// if !ok { +// ctx.Logger.Warnf("Skip protocol %q since it is not supported", item.Protocol) +// } +// return item.Protocol, ok && !item.Dummy +// })) +// sort.Strings(res) +// return res +//} type ProtoMessage struct { *Message ProtoName string +} + +func (p ProtoMessage) String() string { + return "Message " + p.Name } \ No newline at end of file diff --git a/internal/render/parameter.go b/internal/render/parameter.go index 2242fd4..d8fdf40 100644 --- a/internal/render/parameter.go +++ b/internal/render/parameter.go @@ -33,9 +33,9 @@ func (p Parameter) Selectable() bool { // return p.Name //} // -//func (p Parameter) String() string { -// return "Parameter " + p.Name -//} +func (p Parameter) String() string { + return "Parameter " + p.Name +} //func (p Parameter) renderMethods(ctx *common.RenderContext) []*j.Statement { // ctx.Logger.Trace("renderMethods") diff --git a/internal/render/server.go b/internal/render/server.go index 8762d43..5f6562f 100644 --- a/internal/render/server.go +++ b/internal/render/server.go @@ -119,12 +119,12 @@ func (s Server) Selectable() bool { // return s.Name //} // -//func (s Server) String() string { -// return "Server " + s.Name -//} +func (s Server) String() string { + return "Server " + s.Name +} func (s Server) GetRelevantChannels() []*Channel { - return lo.FilterMap(s.AllChannelsPromise.Targets(), func(p *Channel, _ int) (*Channel, bool) { + return lo.FilterMap(s.AllChannelsPromise.T(), func(p *Channel, _ int) (*Channel, bool) { // Empty/omitted servers field in channel means "all servers" ok := len(p.SpecServerNames) == 0 || lo.Contains(p.SpecServerNames, s.SpecKey) return p, ok && !p.Dummy @@ -133,8 +133,8 @@ func (s Server) GetRelevantChannels() []*Channel { func (c Server) BindingsProtocols() (res []string) { if c.BindingsPromise != nil { - res = append(res, c.BindingsPromise.Target().Values.Keys()...) - res = append(res, c.BindingsPromise.Target().JSONValues.Keys()...) + res = append(res, c.BindingsPromise.T().Values.Keys()...) + res = append(res, c.BindingsPromise.T().JSONValues.Keys()...) } return lo.Uniq(res) } @@ -145,8 +145,8 @@ func (c Server) ProtoBindingsValue(protoName string) common.Renderable { EmptyCurlyBrackets: true, } if c.BindingsPromise != nil { - if b, ok := c.BindingsPromise.Target().Values.Get(protoName); ok { - ctx.Logger.Debug("Server bindings", "proto", protoName) + if b, ok := c.BindingsPromise.T().Values.Get(protoName); ok { + //ctx.Logger.Debug("Server bindings", "proto", protoName) res = b } } @@ -158,4 +158,8 @@ type ProtoServer struct { Type *lang.GoStruct // Nil if server is dummy or has unsupported protocol ProtoName string +} + +func (p ProtoServer) String() string { + return "ProtoServer " + p.Name } \ No newline at end of file diff --git a/internal/render/servervariable.go b/internal/render/servervariable.go index fdcecc3..5e6152e 100644 --- a/internal/render/servervariable.go +++ b/internal/render/servervariable.go @@ -32,6 +32,6 @@ func (s ServerVariable) Selectable() bool { // return s.Name //} // -//func (s ServerVariable) String() string { -// return "ServerVariable " + s.Name -//} +func (s ServerVariable) String() string { + return "ServerVariable " + s.Name +} diff --git a/internal/render/template.go b/internal/render/template.go deleted file mode 100644 index 6f05aa0..0000000 --- a/internal/render/template.go +++ /dev/null @@ -1,228 +0,0 @@ -package render - -import ( - "fmt" - "github.com/bdragon300/go-asyncapi/internal/common" - "github.com/bdragon300/go-asyncapi/internal/render/context" - "github.com/bdragon300/go-asyncapi/internal/render/lang" - "github.com/bdragon300/go-asyncapi/internal/tpl" - "github.com/bdragon300/go-asyncapi/internal/utils" - "github.com/go-sprout/sprout" - "github.com/samber/lo" - "strings" - "text/template" - "unicode" -) - -var templateFunctions sprout.FunctionMap - -func init() { - handler := sprout.New() - templateFunctions = handler.Build() -} - -type TemplateSelections struct { - Objects []common.Renderable -} - -func (r TemplateSelections) SelectLangs() []common.Renderable { - return lo.Filter(r.Objects, func(item common.Renderable, _ int) bool { - return item.Kind() == common.ObjectKindLang - }) -} - -func (r TemplateSelections) SelectSchemas() []*lang.GoStruct { - return selectObjects[*lang.GoStruct](r.Objects, common.ObjectKindSchema) -} - -func (r TemplateSelections) SelectServers() []*ProtoServer { - return selectObjects[*ProtoServer](r.Objects, common.ObjectKindServer) -} - -func (r TemplateSelections) SelectServerVariables() []*ServerVariable { - return selectObjects[*ServerVariable](r.Objects, common.ObjectKindServerVariable) -} - -func (r TemplateSelections) SelectChannels() []*ProtoChannel { - return selectObjects[*ProtoChannel](r.Objects, common.ObjectKindChannel) -} - -func (r TemplateSelections) SelectMessages() []*ProtoMessage { - return selectObjects[*ProtoMessage](r.Objects, common.ObjectKindMessage) -} - -func (r TemplateSelections) SelectParameters() []*Parameter { - return selectObjects[*Parameter](r.Objects, common.ObjectKindParameter) -} - -func (r TemplateSelections) SelectCorrelationIDs() []*CorrelationID { - return selectObjects[*CorrelationID](r.Objects, common.ObjectKindCorrelationID) -} - -func (r TemplateSelections) SelectServerBindings() []*Bindings { - return selectObjects[*Bindings](r.Objects, common.ObjectKindServerBindings) -} - -func (r TemplateSelections) SelectChannelBindings() []*Bindings { - return selectObjects[*Bindings](r.Objects, common.ObjectKindChannelBindings) -} - -func (r TemplateSelections) SelectOperationBindings() []*Bindings { - return selectObjects[*Bindings](r.Objects, common.ObjectKindOperationBindings) -} - -func (r TemplateSelections) SelectMessageBindings() []*Bindings { - return selectObjects[*Bindings](r.Objects, common.ObjectKindMessageBindings) -} - -func (r TemplateSelections) SelectAsyncAPI() *AsyncAPI { - res := selectObjects[*AsyncAPI](r.Objects, common.ObjectKindAsyncAPI) - if len(res) == 0 { - return nil - } - return res[0] -} - - -func NewTemplateContext(renderContext *context.RenderContextImpl, selections TemplateSelections) TemplateContext { - return TemplateContext{ - renderContext: renderContext, - objectSelections: selections, - } -} - -type TemplateContext struct { - renderContext *context.RenderContextImpl - objectSelections TemplateSelections -} - -func (t TemplateContext) Selections() TemplateSelections { - return t.objectSelections -} - - -func GetTemplateFunctions(ctx common.RenderContext) template.FuncMap { - extraFuncs := template.FuncMap{ - "golit": func(val any) string { return templateGoLit(val) }, - "goptr": func(val common.GolangType) (*lang.GoPointer, error) { return templateGoPtr(val) }, - "unwrapgoptr": func(val common.GolangType) common.GolangType { - if v, ok := any(val).(lang.GolangTypeWrapperType); ok { - if wt, ok := v.UnwrapGolangType(); ok { - return wt - } - } - return nil - }, - "goid": func(name string) string { return templateGoID(name) }, - "gocomment": func(text string) (string, error) { return templateGoComment(text) }, - "qual": func(parts ...string) string { return ctx.QualifiedName(parts...) }, - "qualgenpkg": func(obj common.GolangType) (string, error) { - pkg, err := ctx.QualifiedGeneratedPackage(obj) - if pkg == "" { - return "", err - } - return pkg + ".", err - }, - "qualrun": func(parts ...string) string { return ctx.QualifiedRuntimeName(parts...) }, // TODO: check if .Import and qual is enough - "runTemplate": func(templateName string, ctx any) (string, error) { - tmpl := tpl.LoadTemplate(templateName) - var bld strings.Builder - if err := tmpl.Execute(&bld, ctx); err != nil { - return "", err - } - return bld.String(), nil - }, - } - - return lo.Assign(templateFunctions, extraFuncs) -} - - -func templateGoLit(val any) string { - type usageDrawable interface { - U() string - } - - var res string - switch val.(type) { - case usageDrawable: - return val.(usageDrawable).U() - case bool, string, int, complex128: - // default constant types can be left bare - return fmt.Sprintf("%#v", val) - case float64: - res = fmt.Sprintf("%#v", val) - if !strings.Contains(res, ".") && !strings.Contains(res, "e") { - // If the formatted value is not in scientific notation, and does not have a dot, then - // we add ".0". Otherwise, it will be interpreted as an int. - // See: - // https://github.com/dave/jennifer/issues/39 - // https://github.com/golang/go/issues/26363 - res += ".0" - } - return res - case float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr: - // other built-in types need specific type info - return fmt.Sprintf("%T(%#v)", val, val) - case complex64: - // fmt package already renders parenthesis for complex64 - return fmt.Sprintf("%T%#v", val, val) - } - - panic(fmt.Sprintf("unsupported type for literal: %T", val)) -} - -func templateGoPtr(val common.GolangType) (*lang.GoPointer, error) { - if val == nil { - return nil, fmt.Errorf("cannot get a pointer to nil") - } - return &lang.GoPointer{Type: val}, nil -} - -func templateGoID(val string) string { - if val == "" { - return "" - } - return utils.ToGolangName(val, unicode.IsUpper(rune(val[0]))) -} - -func templateGoComment(text string) (string, error) { - if strings.HasPrefix(text, "//") || strings.HasPrefix(text, "/*") { - // automatic formatting disabled. - return text, nil - } - - var b strings.Builder - if strings.Contains(text, "\n") { - if _, err := b.WriteString("/*\n"); err != nil { - return "", err - } - } else { - if _, err := b.WriteString("// "); err != nil { - return "", err - } - } - if _, err := b.WriteString(text); err != nil { - return "", err - } - if strings.Contains(text, "\n") { - if !strings.HasSuffix(text, "\n") { - if _, err := b.WriteString("\n"); err != nil { - return "", err - } - } - if _, err := b.WriteString("*/"); err != nil { - return "", err - } - } - return b.String(), nil -} - -func selectObjects[T common.Renderable](selections []common.Renderable, kind common.ObjectKind) []T { - return lo.FilterMap(selections, func(item common.Renderable, _ int) (T, bool) { - if item.Kind() == kind { - return item.(T), true - } - return lo.Empty[T](), false - }) -} diff --git a/internal/selector/selector.go b/internal/selector/selector.go index fe248aa..365f496 100644 --- a/internal/selector/selector.go +++ b/internal/selector/selector.go @@ -7,8 +7,8 @@ import ( "regexp" ) -func SelectObjects(objects []compiler.Object, filters common.RenderSelectionFilterConfig) []compiler.Object { - filterChain := getFiltersChain(filters) +func SelectObjects(objects []compiler.Object, selection common.RenderSelectionConfig) []compiler.Object { + filterChain := getFiltersChain(selection) return lo.Filter(objects, func(object compiler.Object, _ int) bool { for _, filter := range filterChain { @@ -36,25 +36,25 @@ func SelectObjects(objects []compiler.Object, filters common.RenderSelectionFilt type filterFunc func(compiler.Object) bool -func getFiltersChain(filters common.RenderSelectionFilterConfig) []filterFunc { +func getFiltersChain(selection common.RenderSelectionConfig) []filterFunc { var filterChain []filterFunc filterChain = append(filterChain, func(object compiler.Object) bool { return object.Object.Selectable() }) - if filters.ObjectKindRe != "" { - re := regexp.MustCompile(filters.ObjectKindRe) // TODO: compile 1 time (and below) + if selection.ObjectKindRe != "" { + re := regexp.MustCompile(selection.ObjectKindRe) // TODO: compile 1 time (and below) filterChain = append(filterChain, func(object compiler.Object) bool { return re.MatchString(string(object.Object.Kind())) }) } - if filters.ModuleURLRe != "" { - re := regexp.MustCompile(filters.ModuleURLRe) + if selection.ModuleURLRe != "" { + re := regexp.MustCompile(selection.ModuleURLRe) filterChain = append(filterChain, func(object compiler.Object) bool { return re.MatchString(object.ModuleURL.SpecID) }) } - if filters.PathRe != "" { - re := regexp.MustCompile(filters.PathRe) + if selection.PathRe != "" { + re := regexp.MustCompile(selection.PathRe) filterChain = append(filterChain, func(object compiler.Object) bool { return re.MatchString(object.ModuleURL.PointerRef()) }) diff --git a/internal/specurl/url.go b/internal/specurl/url.go index 1738563..8c7a995 100644 --- a/internal/specurl/url.go +++ b/internal/specurl/url.go @@ -5,7 +5,7 @@ import ( "strings" "github.com/samber/lo" - "golang.org/x/exp/slices" + "slices" ) func Parse(ref string) *URL { diff --git a/internal/tpl/load.go b/internal/tpl/load.go index 2841ced..c9cb2d2 100644 --- a/internal/tpl/load.go +++ b/internal/tpl/load.go @@ -2,11 +2,19 @@ package tpl import ( "github.com/bdragon300/go-asyncapi/templates" + "github.com/go-sprout/sprout" "text/template" ) var inlineTemplates = template.Must(template.ParseFS(templates.Templates)) +var sproutFunctions sprout.FunctionMap + +func init() { + handler := sprout.New() + sproutFunctions = handler.Build() +} + func LoadTemplate(name string) *template.Template { // TODO: template dir // TODO: cache diff --git a/internal/tpl/template.go b/internal/tpl/template.go new file mode 100644 index 0000000..10191d7 --- /dev/null +++ b/internal/tpl/template.go @@ -0,0 +1,216 @@ +package tpl + +import ( + "fmt" + "github.com/bdragon300/go-asyncapi/internal/common" + "github.com/bdragon300/go-asyncapi/internal/utils" + "github.com/samber/lo" + "reflect" + "strings" + "text/template" + "unicode" +) + +type renderContext interface { + Imports() []common.ImportItem + CurrentSelection() common.RenderSelectionConfig +} + +func NewTemplateContext(renderContext renderContext, object common.Renderable, index int) TemplateContext { + return TemplateContext{ + renderContext: renderContext, + object: object, + index: index, + } +} + +// TemplateContext is passed as value to the root template on selections processing. +type TemplateContext struct { + renderContext renderContext + object common.Renderable + index int +} + +func (t TemplateContext) Imports() []common.ImportItem { + return t.renderContext.Imports() +} + +func (t TemplateContext) CurrentSelection() common.RenderSelectionConfig { + return t.renderContext.CurrentSelection() +} + +func (t TemplateContext) Index() int { + return t.index +} + +func (t TemplateContext) OtherObj() common.Renderable { + if t.object.Kind() == common.ObjectKindOther { + return t.object + } + return nil +} + +func (t TemplateContext) SchemaObj() common.Renderable { + return retrieveObject(t.object, common.ObjectKindSchema) +} + +func (t TemplateContext) ServerObj() common.Renderable { + return retrieveObject(t.object, common.ObjectKindServer) +} + +func (t TemplateContext) ServerVariableObj() common.Renderable { + return retrieveObject(t.object, common.ObjectKindServerVariable) +} + +func (t TemplateContext) ChannelObj() common.Renderable { + return retrieveObject(t.object, common.ObjectKindChannel) +} + +func (t TemplateContext) MessageObj() common.Renderable { + return retrieveObject(t.object, common.ObjectKindMessage) +} + +func (t TemplateContext) ParametersObj() common.Renderable { + return retrieveObject(t.object, common.ObjectKindParameter) +} + +func (t TemplateContext) CorrelationIDObj() common.Renderable { + return retrieveObject(t.object, common.ObjectKindCorrelationID) +} + +func (t TemplateContext) AsyncAPIObj() common.Renderable { + return retrieveObject(t.object, common.ObjectKindAsyncAPI) +} + +func retrieveObject(obj common.Renderable, kind common.ObjectKind) common.Renderable { + if obj.Kind() != kind { + return nil + } + // Unwrap promise(s) until we get the actual object + for { + v, ok := obj.(common.ObjectPromise) + if !ok { + break + } + obj = reflect.ValueOf(v).MethodByName("T").Call(nil)[0].Interface().(common.Renderable) + } + return obj +} + +func GetTemplateFunctions(ctx common.RenderContext) template.FuncMap { + type golangTypeWrapperType interface { + UnwrapGolangType() (common.GolangType, bool) + } + + extraFuncs := template.FuncMap{ + "golit": func(val any) string { return templateGoLit(val) }, + "goptr": func(val common.GolangType) (string, error) { return templateGoPtr(val) }, + "unwrapgoptr": func(val common.GolangType) common.GolangType { + if v, ok := any(val).(golangTypeWrapperType); ok { + if wt, ok := v.UnwrapGolangType(); ok { + return wt + } + } + return nil + }, + "goid": func(name string) string { return templateGoID(name) }, + "gocomment": func(text string) (string, error) { return templateGoComment(text) }, + "qual": func(parts ...string) string { return ctx.QualifiedName(parts...) }, + "qualgenpkg": func(obj common.GolangType) (string, error) { + pkg, err := ctx.QualifiedGeneratedPackage(obj) + if pkg == "" { + return "", err + } + return pkg + ".", err + }, + "qualrun": func(parts ...string) string { return ctx.QualifiedRuntimeName(parts...) }, // TODO: check if .Import and qual is enough + "execTemplate": func(templateName string, ctx any) (string, error) { + tmpl := LoadTemplate(templateName) + var bld strings.Builder + if err := tmpl.Execute(&bld, ctx); err != nil { + return "", err + } + return bld.String(), nil + }, + // TODO: function to run external command + } + + return lo.Assign(sproutFunctions, extraFuncs) +} + +func templateGoLit(val any) string { + type usageDrawable interface { + U() string + } + + if v, ok := val.(usageDrawable); ok { + return v.U() + } + return utils.ToGoLiteral(val) +} + +func templateGoPtr(val common.GolangType) (string, error) { + type golangPointerType interface { + IsPointer() bool + } + + if val == nil { + return "", fmt.Errorf("cannot get a pointer to nil") + } + var isPtr bool + if v, ok := val.(golangPointerType); ok && v.IsPointer() { + isPtr = true + } + if isPtr { + return "*" + val.U(), nil + } + return val.U(), nil +} + +func templateGoID(val string) string { + if val == "" { + return "" + } + return utils.ToGolangName(val, unicode.IsUpper(rune(val[0]))) +} + +func templateGoComment(text string) (string, error) { + if strings.HasPrefix(text, "//") || strings.HasPrefix(text, "/*") { + // automatic formatting disabled. + return text, nil + } + + var b strings.Builder + if strings.Contains(text, "\n") { + if _, err := b.WriteString("/*\n"); err != nil { + return "", err + } + } else { + if _, err := b.WriteString("// "); err != nil { + return "", err + } + } + if _, err := b.WriteString(text); err != nil { + return "", err + } + if strings.Contains(text, "\n") { + if !strings.HasSuffix(text, "\n") { + if _, err := b.WriteString("\n"); err != nil { + return "", err + } + } + if _, err := b.WriteString("*/"); err != nil { + return "", err + } + } + return b.String(), nil +} + +//func selectObjects[T common.Renderable](selections []common.Renderable, kind common.ObjectKind) []T { +// return lo.FilterMap(selections, func(item common.Renderable, _ int) (T, bool) { +// if item.Kind() == kind { +// return item.(T), true +// } +// return lo.Empty[T](), false +// }) +//} diff --git a/internal/utils/names.go b/internal/utils/names.go index e57698d..359c6bc 100644 --- a/internal/utils/names.go +++ b/internal/utils/names.go @@ -1,8 +1,6 @@ package utils import ( - "crypto/md5" - "encoding/base32" "go/token" "regexp" "strings" @@ -98,30 +96,30 @@ func TransformInitialisms(name string) string { return string(res) } -func ToLowerFirstLetter(s string) string { - if s == "" { - return "" - } - return strings.ToLower(string(s[0])) + s[1:] -} +//func ToLowerFirstLetter(s string) string { +// if s == "" { +// return "" +// } +// return strings.ToLower(string(s[0])) + s[1:] +//} func JoinNonemptyStrings(sep string, s ...string) string { return strings.Join(lo.Compact(s), sep) } -func ToFileName(rawString string) string { - // Replace everything except alphanumerics to underscores - newString := string(fileNameReplaceRe.ReplaceAll([]byte(rawString), []byte("_"))) - - // Cut underscores that may appear at string endings - newString = strings.Trim(newString, "_") - - // newString may left empty after normalization, because rawString is empty or has "/", "_", or smth - // So, the filename will be the md5 hash from rawString in base32 form - if newString == "" { - hsh := md5.New() - newString = ToFileName(base32.StdEncoding.EncodeToString(hsh.Sum([]byte(rawString)))) - } - - return strcase.SnakeCase(newString) -} +//func ToFileName(rawString string) string { +// // Replace everything except alphanumerics to underscores +// newString := string(fileNameReplaceRe.ReplaceAll([]byte(rawString), []byte("_"))) +// +// // Cut underscores that may appear at string endings +// newString = strings.Trim(newString, "_") +// +// // newString may left empty after normalization, because rawString is empty or has "/", "_", or smth +// // So, the filename will be the md5 hash from rawString in base32 form +// if newString == "" { +// hsh := md5.New() +// newString = ToFileName(base32.StdEncoding.EncodeToString(hsh.Sum([]byte(rawString)))) +// } +// +// return strcase.SnakeCase(newString) +//} diff --git a/internal/utils/render.go b/internal/utils/render.go index 41d22fb..331cee1 100644 --- a/internal/utils/render.go +++ b/internal/utils/render.go @@ -3,39 +3,65 @@ package utils import ( "fmt" "strings" - - "github.com/dave/jennifer/jen" ) -func ToCode(in []*jen.Statement) []jen.Code { - result := make([]jen.Code, len(in)) - for i, item := range in { - result[i] = any(item).(jen.Code) - } - return result -} - -func QualSprintf(format string, args ...any) jen.Code { - res := &jen.Statement{} - format = strings.ReplaceAll(format, "%Q(", "%%Q(") - s := fmt.Sprintf(format, args...) - - // Expression: %Q(encoding/json,Marshal) - blocks := strings.Split(s, "%Q(") - if len(blocks) == 0 { - return jen.Op("") - } +//func ToCode(in []*jen.Statement) []jen.Code { +// result := make([]jen.Code, len(in)) +// for i, item := range in { +// result[i] = any(item).(jen.Code) +// } +// return result +//} +// +//func QualSprintf(format string, args ...any) jen.Code { +// res := &jen.Statement{} +// format = strings.ReplaceAll(format, "%Q(", "%%Q(") +// s := fmt.Sprintf(format, args...) +// +// // Expression: %Q(encoding/json,Marshal) +// blocks := strings.Split(s, "%Q(") +// if len(blocks) == 0 { +// return jen.Op("") +// } +// +// res = res.Add(jen.Op(blocks[0])) +// for _, p := range blocks[1:] { +// parts := strings.SplitN(p, ")", 2) +// params := strings.SplitN(parts[0], ",", 2) +// code := jen.Qual(params[0], params[1]) +// if len(parts) > 1 { +// code = code.Op(parts[1]) +// } +// res = res.Add(code) +// } +// +// return res +//} - res = res.Add(jen.Op(blocks[0])) - for _, p := range blocks[1:] { - parts := strings.SplitN(p, ")", 2) - params := strings.SplitN(parts[0], ",", 2) - code := jen.Qual(params[0], params[1]) - if len(parts) > 1 { - code = code.Op(parts[1]) +func ToGoLiteral(val any) string { + var res string + switch val.(type) { + case bool, string, int, complex128: + // default constant types can be left bare + return fmt.Sprintf("%#v", val) + case float64: + res = fmt.Sprintf("%#v", val) + if !strings.Contains(res, ".") && !strings.Contains(res, "e") { + // If the formatted value is not in scientific notation, and does not have a dot, then + // we add ".0". Otherwise, it will be interpreted as an int. + // See: + // https://github.com/dave/jennifer/issues/39 + // https://github.com/golang/go/issues/26363 + res += ".0" } - res = res.Add(code) + return res + case float32, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr: + // other built-in types need specific type info + return fmt.Sprintf("%T(%#v)", val, val) + case complex64: + // fmt package already renders parenthesis for complex64 + return fmt.Sprintf("%T%#v", val, val) } - return res -} + panic(fmt.Sprintf("unsupported type for literal: %T", val)) +} \ No newline at end of file diff --git a/internal/writer/implementations.go b/internal/writer/implementations.go index 9bde4c3..fcde321 100644 --- a/internal/writer/implementations.go +++ b/internal/writer/implementations.go @@ -12,6 +12,8 @@ import ( "github.com/bdragon300/go-asyncapi/internal/utils" ) +const implementationCodePreamble = "Code generated by go-asyncapi. DO NOT EDIT." + func WriteImplementation(implDir, baseDir string) (int, error) { if err := os.MkdirAll(baseDir, 0o750); err != nil { return 0, fmt.Errorf("create directory %q: %w", baseDir, err) @@ -35,7 +37,7 @@ func WriteImplementation(implDir, baseDir string) (int, error) { } // Write a preamble only for go source code files if strings.HasPrefix(line1, "package") { - if c, err := io.WriteString(w, "\n// "+GeneratedCodePreamble+"\n"); err == nil { + if c, err := io.WriteString(w, "\n// "+implementationCodePreamble+"\n"); err == nil { n += int64(c) } else { return n, err diff --git a/internal/writer/render.go b/internal/writer/render.go index 66d6497..2c8f313 100644 --- a/internal/writer/render.go +++ b/internal/writer/render.go @@ -2,14 +2,16 @@ package writer import ( "bytes" + "errors" "fmt" "github.com/bdragon300/go-asyncapi/internal/common" - "github.com/bdragon300/go-asyncapi/internal/render" "github.com/bdragon300/go-asyncapi/internal/render/context" "github.com/bdragon300/go-asyncapi/internal/selector" "github.com/bdragon300/go-asyncapi/internal/tpl" "github.com/bdragon300/go-asyncapi/internal/types" "github.com/samber/lo" + "go/format" + "io" "os" "path" "text/template" @@ -53,46 +55,115 @@ type renderSource interface { AllObjects() []compiler.Object } -func RenderPackages(source renderSource, opts common.RenderOpts) (fileContents map[string]*bytes.Buffer, err error) { - fileContents = make(map[string]*bytes.Buffer) +type renderQueueItem struct { + selection common.RenderSelectionConfig + object compiler.Object + index int +} + +func RenderPackages(source renderSource, opts common.RenderOpts) (map[string]*bytes.Buffer, error) { + files := make(map[string]*bytes.Buffer) // TODO: logging - for _, selection := range opts.Selections { - objects := selector.SelectObjects(source.AllObjects(), selection.RenderSelectionFilterConfig) - if len(objects) == 0 { - continue - } + queue := buildRenderQueue(source, opts.Selections) + var deferred []renderQueueItem - ctx := &context.RenderContextImpl{RenderOpts: opts} - selectionObjects := lo.Map(objects, func(item compiler.Object, _ int) common.Renderable { return item.Object}) - tplCtx := render.NewTemplateContext(ctx, render.TemplateSelections{Objects: selectionObjects}) - context.Context = ctx - // TODO: template in file name - if _, ok := fileContents[selection.File]; !ok { - fileContents[selection.File] = &bytes.Buffer{} - } + for len(queue) > 0 { + for _, item := range queue { + ctx := &context.RenderContextImpl{RenderOpts: opts, CurrentSelectionConfig: item.selection} + tplCtx := tpl.NewTemplateContext(ctx, item.object.Object, item.index) + context.Context = ctx - // TODO: redefinition preambule in config/cli args - var tmpl *template.Template - if tmpl = tpl.LoadTemplate("preamble"); tmpl == nil { - return nil, fmt.Errorf("template not found: preamble") - } - tmpl = tmpl.Funcs(render.GetTemplateFunctions(ctx)) - if err = tmpl.Execute(fileContents[selection.File], tplCtx); err != nil { - return - } + rd, err := renderFile(ctx, tplCtx, item.selection.Template) + switch { + case errors.Is(err, common.ErrObjectDefinitionUnknownYet): + // Template can't be rendered right now due to unknown object definition, defer it + deferred = append(deferred, item) + continue + case err != nil: + return nil, err + } + + fileName, err := renderInlineTemplate(ctx, tplCtx, item.selection.File) + switch { + case errors.Is(err, common.ErrObjectDefinitionUnknownYet): + // Template can't be rendered right now due to unknown object definition, defer it + deferred = append(deferred, item) + continue + case err != nil: + return nil, err + } - if tmpl = tpl.LoadTemplate(selection.Template); tmpl == nil { - return nil, fmt.Errorf("template not found: %s", selection.Template) + if _, ok := files[fileName]; !ok { + files[fileName] = &bytes.Buffer{} + } + + if _, err = files[fileName].ReadFrom(rd); err != nil { + return nil, err + } } - tmpl = tmpl.Funcs(render.GetTemplateFunctions(ctx)) - if err = tmpl.Execute(fileContents[selection.File], tplCtx); err != nil { - return + if len(deferred) == len(queue) { + return nil, fmt.Errorf( + "cyclic dependencies detected in templates: %v", + lo.Map(deferred, func(item renderQueueItem, _ int) string { return item.selection.Template }), + ) } + queue, deferred = deferred, nil } + return files, nil +} + +func buildRenderQueue(source renderSource, selections []common.RenderSelectionConfig) (res []renderQueueItem) { + for _, selection := range selections { + objects := selector.SelectObjects(source.AllObjects(), selection) + for ind, obj := range objects { + res = append(res, renderQueueItem{selection, obj, ind}) + } + } return } +func renderFile(renderCtx *context.RenderContextImpl, tplCtx tpl.TemplateContext, templateName string) (io.Reader, error) { + var res bytes.Buffer + var tmpl *template.Template + + // Execute the main template first to accumulate imports and other data, that will be rendered in preamble + var buf bytes.Buffer + if tmpl = tpl.LoadTemplate(templateName); tmpl == nil { + return nil, fmt.Errorf("template %q not found", templateName) + } + tmpl = tmpl.Funcs(tpl.GetTemplateFunctions(renderCtx)) + if err := tmpl.Execute(&buf, tplCtx); err != nil { + return nil, err + } + + // TODO: redefinition preambule in config/cli args + if tmpl = tpl.LoadTemplate("preamble"); tmpl == nil { + return nil, fmt.Errorf("template %q not found", "preamble") + } + tmpl = tmpl.Funcs(tpl.GetTemplateFunctions(renderCtx)) + if err := tmpl.Execute(&res, tplCtx); err != nil { + return nil, err + } + if _, err := res.ReadFrom(&buf); err != nil { + return nil, err + } + + return &res, nil +} + +func renderInlineTemplate(renderCtx *context.RenderContextImpl, tplCtx tpl.TemplateContext, text string) (string, error) { + var res bytes.Buffer + tmpl, err := template.New("").Funcs(tpl.GetTemplateFunctions(renderCtx)).Parse(text) + if err != nil { + return "", err + } + if err := tmpl.Execute(&res, tplCtx); err != nil { + return "", err + } + return res.String(), nil +} + //func RenderPackages(source renderSource, opts common.RenderOpts) (fileContents map[string]*bytes.Buffer, err error) { // fileContents = make(map[string]*bytes.Buffer) // logger := types.NewLogger("Rendering 🎨") @@ -177,6 +248,26 @@ func RenderPackages(source renderSource, opts common.RenderOpts) (fileContents m // return //} +// FormatFiles formats the files in-place in the map using gofmt +func FormatFiles(files map[string]*bytes.Buffer) error { + logger := types.NewLogger("Formatting 📐") + logger.Info("Run formatting") + + for fileName, buf := range files { + logger.Debug("File", "name", fileName) + formatted, err := format.Source(buf.Bytes()) + if err != nil { + return err + } + buf.Reset() + buf.Write(formatted) + logger.Debug("File formatted", "name", fileName, "bytes", buf.Len()) + } + + logger.Info("Formatting completed", "files", len(files)) + return nil +} + func WriteToFiles(files map[string]*bytes.Buffer, baseDir string) error { logger := types.NewLogger("Writing 📝") logger.Info("Run writing") diff --git a/templates/main.tmpl b/templates/main.tmpl index 11988c1..34d995c 100644 --- a/templates/main.tmpl +++ b/templates/main.tmpl @@ -1,30 +1,22 @@ -{{if .SelectAsyncAPI}} - {{template "encoding" .SelectAsyncAPI}} -{{end}} - -{{range .Selections.SelectSchemas}} +{{if .SchemaObj}} {{template "schema" .}} {{end}} -{{range .Selections.SelectServers}} - {{template "server" .}} - {{runTemplate (print .ProtoName "/" "server") .}} +{{if .ServerObj}} + {{template "server" .ServerObj }} + {{execTemplate (print .ServerObj.ProtoName "/" "server") .ServerObj }} {{end}} -{{range .Selections.SelectParameters}} - {{template "parameter" .}} +{{if .ParameterObj}} + {{template "parameter" .ParameterObj}} {{end}} -{{$channels := .Selections.SelectChannels}} -{{if $channels}}{{template "channel" $channels.0}}{{end}} - -{{range $channels}} - {{runTemplate (print .ProtoName "/" "channel") .}} +{{if .ChannelObj}} + {{if eq .Index 0}}{{template "channel" .ChannelObj}}{{end}} + {{execTemplate (print .ChannelObj.ProtoName "/" "channel") .}} {{end}} -{{$messages := .Selections.SelectMessages}} -{{if $messages}}{{template "message" $messages.0}}{{end}} - -{{range $messages}} - {{runTemplate (print .ProtoName "/" "message") .}} +{{if .MessageObj}} + {{if eq .Index 0}}{{template "message" .MessageObj}}{{end}} + {{execTemplate (print .MessageObj.ProtoName "/" "message") .}} {{end}} diff --git a/templates/preambule.tmpl b/templates/preambule.tmpl index 010851a..c131452 100644 --- a/templates/preambule.tmpl +++ b/templates/preambule.tmpl @@ -1 +1,12 @@ // Code generated by go-asyncapi tool. DO NOT EDIT. + +package {{.CurrentSelection.Package}} + +{{ $imports := .Imports }} +{{if $imports}} +import ( +{{range $imports}} + {{if .Alias}}{{.Alias}} {{end}}"{{.PackagePath}}" +{{end}} +) +{{end}}