github.com/go-swagger/go-swagger@v0.31.0/generator/support.go (about)

     1  // Copyright 2015 go-swagger maintainers
     2  
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //    http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package generator
    17  
    18  import (
    19  	"bytes"
    20  	"encoding/json"
    21  	"errors"
    22  	"fmt"
    23  	"log"
    24  	"path"
    25  	"path/filepath"
    26  	"sort"
    27  
    28  	"github.com/go-openapi/analysis"
    29  	"github.com/go-openapi/loads"
    30  	"github.com/go-openapi/spec"
    31  	"github.com/go-openapi/swag"
    32  )
    33  
    34  // GenerateServer generates a server application
    35  func GenerateServer(name string, modelNames, operationIDs []string, opts *GenOpts) error {
    36  	generator, err := newAppGenerator(name, modelNames, operationIDs, opts)
    37  	if err != nil {
    38  		return err
    39  	}
    40  	return generator.Generate()
    41  }
    42  
    43  // GenerateSupport generates the supporting files for an API
    44  func GenerateSupport(name string, modelNames, operationIDs []string, opts *GenOpts) error {
    45  	generator, err := newAppGenerator(name, modelNames, operationIDs, opts)
    46  	if err != nil {
    47  		return err
    48  	}
    49  	return generator.GenerateSupport(nil)
    50  }
    51  
    52  // GenerateMarkdown documentation for a swagger specification
    53  func GenerateMarkdown(output string, modelNames, operationIDs []string, opts *GenOpts) error {
    54  	if output == "." || output == "" {
    55  		output = "markdown.md"
    56  	}
    57  
    58  	if err := opts.EnsureDefaults(); err != nil {
    59  		return err
    60  	}
    61  	if opts.Target != "" && opts.Target != "." {
    62  		output = filepath.Join(opts.Target, output)
    63  	}
    64  	MarkdownSectionOpts(opts, output)
    65  
    66  	generator, err := newAppGenerator("", modelNames, operationIDs, opts)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	return generator.GenerateMarkdown()
    72  }
    73  
    74  func newAppGenerator(name string, modelNames, operationIDs []string, opts *GenOpts) (*appGenerator, error) {
    75  	if err := opts.CheckOpts(); err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	if err := opts.setTemplates(); err != nil {
    80  		return nil, err
    81  	}
    82  
    83  	specDoc, analyzed, err := opts.analyzeSpec()
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	models, err := gatherModels(specDoc, modelNames)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	operations := gatherOperations(analyzed, operationIDs)
    94  
    95  	if len(operations) == 0 && !opts.IgnoreOperations {
    96  		return nil, errors.New("no operations were selected")
    97  	}
    98  
    99  	opts.Name = appNameOrDefault(specDoc, name, defaultServerName)
   100  	if opts.IncludeMain && opts.MainPackage == "" {
   101  		// default target for the generated main
   102  		opts.MainPackage = swag.ToCommandName(mainNameOrDefault(specDoc, name, defaultServerName) + "-server")
   103  	}
   104  
   105  	apiPackage := opts.LanguageOpts.ManglePackagePath(opts.APIPackage, defaultOperationsTarget)
   106  	return &appGenerator{
   107  		Name:              opts.Name,
   108  		Receiver:          "o",
   109  		SpecDoc:           specDoc,
   110  		Analyzed:          analyzed,
   111  		Models:            models,
   112  		Operations:        operations,
   113  		Target:            opts.Target,
   114  		DumpData:          opts.DumpData,
   115  		Package:           opts.LanguageOpts.ManglePackageName(apiPackage, defaultOperationsTarget),
   116  		APIPackage:        apiPackage,
   117  		ModelsPackage:     opts.LanguageOpts.ManglePackagePath(opts.ModelPackage, defaultModelsTarget),
   118  		ServerPackage:     opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget),
   119  		ClientPackage:     opts.LanguageOpts.ManglePackagePath(opts.ClientPackage, defaultClientTarget),
   120  		OperationsPackage: filepath.Join(opts.LanguageOpts.ManglePackagePath(opts.ServerPackage, defaultServerTarget), apiPackage),
   121  		Principal:         opts.PrincipalAlias(),
   122  		DefaultScheme:     opts.DefaultScheme,
   123  		DefaultProduces:   opts.DefaultProduces,
   124  		DefaultConsumes:   opts.DefaultConsumes,
   125  		GenOpts:           opts,
   126  	}, nil
   127  }
   128  
   129  type appGenerator struct {
   130  	Name              string
   131  	Receiver          string
   132  	SpecDoc           *loads.Document
   133  	Analyzed          *analysis.Spec
   134  	Package           string
   135  	APIPackage        string
   136  	ModelsPackage     string
   137  	ServerPackage     string
   138  	ClientPackage     string
   139  	OperationsPackage string
   140  	MainPackage       string
   141  	Principal         string
   142  	Models            map[string]spec.Schema
   143  	Operations        map[string]opRef
   144  	Target            string
   145  	DumpData          bool
   146  	DefaultScheme     string
   147  	DefaultProduces   string
   148  	DefaultConsumes   string
   149  	GenOpts           *GenOpts
   150  }
   151  
   152  func (a *appGenerator) Generate() error {
   153  	app, err := a.makeCodegenApp()
   154  	if err != nil {
   155  		return err
   156  	}
   157  
   158  	if a.DumpData {
   159  		return dumpData(app)
   160  	}
   161  
   162  	// NOTE: relative to previous implem with chan.
   163  	// IPC removed concurrent execution because of the FuncMap that is being shared
   164  	// templates are now lazy loaded so there is concurrent map access I can't guard
   165  	if a.GenOpts.IncludeModel {
   166  		log.Printf("rendering %d models", len(app.Models))
   167  		for _, md := range app.Models {
   168  			mod := md
   169  			mod.IncludeModel = true
   170  			mod.IncludeValidator = a.GenOpts.IncludeValidator
   171  			if err := a.GenOpts.renderDefinition(&mod); err != nil {
   172  				return err
   173  			}
   174  		}
   175  	}
   176  
   177  	if a.GenOpts.IncludeHandler {
   178  		log.Printf("rendering %d operation groups (tags)", app.OperationGroups.Len())
   179  		for _, g := range app.OperationGroups {
   180  			opg := g
   181  			log.Printf("rendering %d operations for %s", opg.Operations.Len(), opg.Name)
   182  			for _, p := range opg.Operations {
   183  				op := p
   184  				if err := a.GenOpts.renderOperation(&op); err != nil {
   185  					return err
   186  				}
   187  			}
   188  			// optional OperationGroups templates generation
   189  			if err := a.GenOpts.renderOperationGroup(&opg); err != nil {
   190  				return fmt.Errorf("error while rendering operation group: %w", err)
   191  			}
   192  		}
   193  	}
   194  
   195  	if a.GenOpts.IncludeSupport {
   196  		log.Printf("rendering support")
   197  		if err := a.GenerateSupport(&app); err != nil {
   198  			return err
   199  		}
   200  	}
   201  	return nil
   202  }
   203  
   204  func (a *appGenerator) GenerateSupport(ap *GenApp) error {
   205  	app := ap
   206  	if ap == nil {
   207  		// allows for calling GenerateSupport standalone
   208  		ca, err := a.makeCodegenApp()
   209  		if err != nil {
   210  			return err
   211  		}
   212  		app = &ca
   213  	}
   214  
   215  	baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target)
   216  	serverPath := path.Join(baseImport,
   217  		a.GenOpts.LanguageOpts.ManglePackagePath(a.ServerPackage, defaultServerTarget))
   218  
   219  	pkgAlias := deconflictPkg(importAlias(serverPath), renameServerPackage)
   220  	app.DefaultImports[pkgAlias] = serverPath
   221  	app.ServerPackageAlias = pkgAlias
   222  
   223  	if a.GenOpts.IncludeCLi { // no need to add this import when there is no CLI
   224  		// add client import for cli generation
   225  		clientPath := path.Join(baseImport,
   226  			a.GenOpts.LanguageOpts.ManglePackagePath(a.ClientPackage, defaultClientTarget))
   227  		clientPkgAlias := importAlias(clientPath)
   228  		app.DefaultImports[clientPkgAlias] = clientPath
   229  	}
   230  
   231  	return a.GenOpts.renderApplication(app)
   232  }
   233  
   234  func (a *appGenerator) GenerateMarkdown() error {
   235  	app, err := a.makeCodegenApp()
   236  	if err != nil {
   237  		return err
   238  	}
   239  
   240  	return a.GenOpts.renderApplication(&app)
   241  }
   242  
   243  func (a *appGenerator) makeSecuritySchemes() GenSecuritySchemes {
   244  	requiredSecuritySchemes := make(map[string]spec.SecurityScheme, len(a.Analyzed.RequiredSecuritySchemes()))
   245  	for _, scheme := range a.Analyzed.RequiredSecuritySchemes() {
   246  		if req, ok := a.SpecDoc.Spec().SecurityDefinitions[scheme]; ok && req != nil {
   247  			requiredSecuritySchemes[scheme] = *req
   248  		}
   249  	}
   250  	return gatherSecuritySchemes(requiredSecuritySchemes, a.Name, a.Principal, a.Receiver, a.GenOpts.PrincipalIsNullable())
   251  }
   252  
   253  func (a *appGenerator) makeCodegenApp() (GenApp, error) {
   254  	log.Println("building a plan for generation")
   255  
   256  	sw := a.SpecDoc.Spec()
   257  	receiver := a.Receiver
   258  
   259  	consumes, _ := a.makeConsumes()
   260  	produces, _ := a.makeProduces()
   261  	security := a.makeSecuritySchemes()
   262  
   263  	log.Println("generation target", a.Target)
   264  
   265  	baseImport := a.GenOpts.LanguageOpts.baseImport(a.Target)
   266  	defaultImports := a.GenOpts.defaultImports()
   267  
   268  	imports := make(map[string]string, 50)
   269  	alias := deconflictPkg(a.GenOpts.LanguageOpts.ManglePackageName(a.OperationsPackage, defaultOperationsTarget), renameAPIPackage)
   270  	if !a.GenOpts.IsClient { // we don't want to inject this import for clients
   271  		imports[alias] = path.Join(
   272  			baseImport,
   273  			a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget))
   274  	}
   275  
   276  	implAlias := ""
   277  	if a.GenOpts.ImplementationPackage != "" {
   278  		implAlias = deconflictPkg(a.GenOpts.LanguageOpts.ManglePackageName(a.GenOpts.ImplementationPackage, defaultImplementationTarget), renameImplementationPackage)
   279  		imports[implAlias] = a.GenOpts.ImplementationPackage
   280  	}
   281  
   282  	log.Printf("planning definitions (found: %d)", len(a.Models))
   283  
   284  	genModels := make(GenDefinitions, 0, len(a.Models))
   285  	for mn, m := range a.Models {
   286  		model, err := makeGenDefinition(
   287  			mn,
   288  			a.ModelsPackage,
   289  			m,
   290  			a.SpecDoc,
   291  			a.GenOpts,
   292  		)
   293  		if err != nil {
   294  			return GenApp{}, fmt.Errorf("error in model %s while planning definitions: %w", mn, err)
   295  		}
   296  		if model != nil {
   297  			if !model.External {
   298  				genModels = append(genModels, *model)
   299  			}
   300  
   301  			// Copy model imports to operation imports
   302  			// TODO(fredbi): mangle model pkg aliases
   303  			for alias, pkg := range model.Imports {
   304  				target := a.GenOpts.LanguageOpts.ManglePackageName(alias, "")
   305  				imports[target] = pkg
   306  			}
   307  		}
   308  	}
   309  	sort.Sort(genModels)
   310  
   311  	log.Printf("planning operations (found: %d)", len(a.Operations))
   312  
   313  	genOps := make(GenOperations, 0, len(a.Operations))
   314  	consumesIndex := make(map[string][]string)
   315  	producesIndex := make(map[string][]string)
   316  	pristineDoc := a.SpecDoc.Pristine()
   317  
   318  	for operationName, opp := range a.Operations {
   319  		o := opp.Op
   320  		o.ID = operationName
   321  
   322  		bldr := codeGenOpBuilder{
   323  			ModelsPackage:    a.ModelsPackage,
   324  			Principal:        a.GenOpts.PrincipalAlias(),
   325  			Target:           a.Target,
   326  			DefaultImports:   defaultImports,
   327  			Imports:          imports,
   328  			DefaultScheme:    a.DefaultScheme,
   329  			Doc:              a.SpecDoc,
   330  			PristineDefs:     pristineDoc,
   331  			Analyzed:         a.Analyzed,
   332  			BasePath:         a.SpecDoc.BasePath(),
   333  			GenOpts:          a.GenOpts,
   334  			Name:             operationName,
   335  			Operation:        *o,
   336  			Method:           opp.Method,
   337  			Path:             opp.Path,
   338  			IncludeValidator: a.GenOpts.IncludeValidator,
   339  			APIPackage:       a.APIPackage, // defaults to main operations package
   340  			DefaultProduces:  a.DefaultProduces,
   341  			DefaultConsumes:  a.DefaultConsumes,
   342  		}
   343  
   344  		tag, tags, ok := bldr.analyzeTags()
   345  		if !ok {
   346  			continue // operation filtered according to CLI params
   347  		}
   348  
   349  		bldr.Authed = len(a.Analyzed.SecurityRequirementsFor(o)) > 0
   350  		bldr.Security = a.Analyzed.SecurityRequirementsFor(o)
   351  		bldr.SecurityDefinitions = a.Analyzed.SecurityDefinitionsFor(o)
   352  		bldr.RootAPIPackage = a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, defaultServerTarget)
   353  
   354  		st := o.Tags
   355  		if a.GenOpts != nil {
   356  			st = a.GenOpts.Tags
   357  		}
   358  		intersected := intersectTags(o.Tags, st)
   359  		if len(st) > 0 && len(intersected) == 0 {
   360  			continue
   361  		}
   362  
   363  		op, err := bldr.MakeOperation()
   364  		if err != nil {
   365  			return GenApp{}, err
   366  		}
   367  
   368  		op.ReceiverName = receiver
   369  		op.Tags = tags // ordered tags for this operation, possibly filtered by CLI params
   370  
   371  		allConsumes := pruneEmpty(op.ConsumesMediaTypes)
   372  		if bldr.DefaultConsumes != "" {
   373  			allConsumes = append(allConsumes, bldr.DefaultConsumes)
   374  		}
   375  		consumesIndex[bldr.Name] = allConsumes
   376  
   377  		allProduces := pruneEmpty(op.ProducesMediaTypes)
   378  		if bldr.DefaultProduces != "" {
   379  			allProduces = append(allProduces, bldr.DefaultProduces)
   380  		}
   381  		producesIndex[bldr.Name] = allProduces
   382  
   383  		if !a.GenOpts.SkipTagPackages && tag != "" {
   384  			importPath := filepath.ToSlash(
   385  				path.Join(
   386  					baseImport,
   387  					a.GenOpts.LanguageOpts.ManglePackagePath(a.OperationsPackage, defaultOperationsTarget),
   388  					a.GenOpts.LanguageOpts.ManglePackageName(bldr.APIPackage, defaultOperationsTarget),
   389  				))
   390  
   391  			// check for possible conflicts that requires import aliasing
   392  			pth, aliasUsed := defaultImports[bldr.APIPackageAlias]
   393  			if (a.GenOpts.IsClient && bldr.APIPackageAlias == a.GenOpts.ClientPackage) || // we don't want import to shadow the current package
   394  				(a.GenOpts.IncludeCLi && bldr.APIPackageAlias == a.GenOpts.CliPackage) ||
   395  				(aliasUsed && pth != importPath) { // was already imported with a different target
   396  				op.PackageAlias = renameOperationPackage(tags, bldr.APIPackageAlias)
   397  				bldr.APIPackageAlias = op.PackageAlias
   398  			}
   399  			defaultImports[bldr.APIPackageAlias] = importPath
   400  		}
   401  
   402  		genOps = append(genOps, op)
   403  	}
   404  	sort.Sort(genOps)
   405  
   406  	opsGroupedByPackage := make(map[string]GenOperations, len(genOps))
   407  	for _, operation := range genOps {
   408  		opsGroupedByPackage[operation.PackageAlias] = append(opsGroupedByPackage[operation.PackageAlias], operation)
   409  	}
   410  
   411  	log.Printf("grouping operations into packages (packages: %d)", len(opsGroupedByPackage))
   412  
   413  	opGroups := make(GenOperationGroups, 0, len(opsGroupedByPackage))
   414  	for k, v := range opsGroupedByPackage {
   415  		log.Printf("operations for package %q (found: %d)", k, len(v))
   416  		sort.Sort(v)
   417  
   418  		consumesInGroup := make([]string, 0, 2)
   419  		producesInGroup := make([]string, 0, 2)
   420  
   421  		// trim duplicate extra schemas within the same package
   422  		vv := make(GenOperations, 0, len(v))
   423  		seenExtraSchema := make(map[string]bool)
   424  		for _, op := range v {
   425  			uniqueExtraSchemas := make(GenSchemaList, 0, len(op.ExtraSchemas))
   426  			for _, xs := range op.ExtraSchemas {
   427  				if _, alreadyThere := seenExtraSchema[xs.Name]; !alreadyThere {
   428  					seenExtraSchema[xs.Name] = true
   429  					uniqueExtraSchemas = append(uniqueExtraSchemas, xs)
   430  				}
   431  			}
   432  			op.ExtraSchemas = uniqueExtraSchemas
   433  			vv = append(vv, op)
   434  
   435  			consumesInGroup = concatUnique(consumesInGroup, consumesIndex[op.Name])
   436  			producesInGroup = concatUnique(producesInGroup, producesIndex[op.Name])
   437  		}
   438  		var pkg string
   439  		if len(vv) > 0 {
   440  			pkg = vv[0].Package
   441  		} else {
   442  			pkg = k
   443  		}
   444  
   445  		opGroup := GenOperationGroup{
   446  			GenCommon: GenCommon{
   447  				Copyright:        a.GenOpts.Copyright,
   448  				TargetImportPath: baseImport,
   449  			},
   450  			Name:           pkg,
   451  			PackageAlias:   k,
   452  			Operations:     vv,
   453  			DefaultImports: defaultImports,
   454  			Imports:        imports,
   455  			RootPackage:    a.APIPackage,
   456  			GenOpts:        a.GenOpts,
   457  		}
   458  
   459  		if a.GenOpts.IsClient {
   460  			// generating extra options to switch media type in client
   461  			if len(consumesInGroup) > 1 || len(producesInGroup) > 1 {
   462  				sort.Strings(producesInGroup)
   463  				sort.Strings(consumesInGroup)
   464  				options := &GenClientOptions{
   465  					ProducesMediaTypes: producesInGroup,
   466  					ConsumesMediaTypes: consumesInGroup,
   467  				}
   468  				opGroup.ClientOptions = options
   469  			}
   470  		}
   471  		opGroups = append(opGroups, opGroup)
   472  	}
   473  	sort.Sort(opGroups)
   474  
   475  	log.Println("planning meta data and facades")
   476  
   477  	var collectedSchemes, extraSchemes []string
   478  	for _, op := range genOps {
   479  		collectedSchemes = concatUnique(collectedSchemes, op.Schemes)
   480  		extraSchemes = concatUnique(extraSchemes, op.ExtraSchemes)
   481  	}
   482  	sort.Strings(collectedSchemes)
   483  	sort.Strings(extraSchemes)
   484  
   485  	host := "localhost"
   486  	if sw.Host != "" {
   487  		host = sw.Host
   488  	}
   489  
   490  	basePath := "/"
   491  	if sw.BasePath != "" {
   492  		basePath = sw.BasePath
   493  	}
   494  
   495  	jsonb, _ := json.MarshalIndent(a.SpecDoc.OrigSpec(), "", "  ")
   496  	flatjsonb, _ := json.MarshalIndent(a.SpecDoc.Spec(), "", "  ")
   497  
   498  	return GenApp{
   499  		GenCommon: GenCommon{
   500  			Copyright:        a.GenOpts.Copyright,
   501  			TargetImportPath: baseImport,
   502  		},
   503  		APIPackage:                 a.GenOpts.LanguageOpts.ManglePackageName(a.ServerPackage, defaultServerTarget),
   504  		APIPackageAlias:            alias,
   505  		ImplementationPackageAlias: implAlias,
   506  		Package:                    a.Package,
   507  		ReceiverName:               receiver,
   508  		Name:                       a.Name,
   509  		Host:                       host,
   510  		BasePath:                   basePath,
   511  		Schemes:                    schemeOrDefault(collectedSchemes, a.DefaultScheme),
   512  		ExtraSchemes:               extraSchemes,
   513  		ExternalDocs:               trimExternalDoc(sw.ExternalDocs),
   514  		Tags:                       trimTags(sw.Tags),
   515  		Info:                       trimInfo(sw.Info),
   516  		Consumes:                   consumes,
   517  		Produces:                   produces,
   518  		DefaultConsumes:            a.DefaultConsumes,
   519  		DefaultProduces:            a.DefaultProduces,
   520  		DefaultImports:             defaultImports,
   521  		Imports:                    imports,
   522  		SecurityDefinitions:        security,
   523  		SecurityRequirements:       securityRequirements(a.SpecDoc.Spec().Security), // top level securityRequirements
   524  		Models:                     genModels,
   525  		Operations:                 genOps,
   526  		OperationGroups:            opGroups,
   527  		Principal:                  a.GenOpts.PrincipalAlias(),
   528  		SwaggerJSON:                generateReadableSpec(jsonb),
   529  		FlatSwaggerJSON:            generateReadableSpec(flatjsonb),
   530  		ExcludeSpec:                a.GenOpts.ExcludeSpec,
   531  		GenOpts:                    a.GenOpts,
   532  
   533  		PrincipalIsNullable: a.GenOpts.PrincipalIsNullable(),
   534  	}, nil
   535  }
   536  
   537  // generateReadableSpec makes swagger json spec as a string instead of bytes
   538  // the only character that needs to be escaped is '`' symbol, since it cannot be escaped in the GO string
   539  // that is quoted as `string data`. The function doesn't care about the beginning or the ending of the
   540  // string it escapes since all data that needs to be escaped is always in the middle of the swagger spec.
   541  func generateReadableSpec(spec []byte) string {
   542  	buf := &bytes.Buffer{}
   543  	for _, b := range string(spec) {
   544  		if b == '`' {
   545  			buf.WriteString("`+\"`\"+`")
   546  		} else {
   547  			buf.WriteRune(b)
   548  		}
   549  	}
   550  	return buf.String()
   551  }
   552  
   553  func trimExternalDoc(in *spec.ExternalDocumentation) *spec.ExternalDocumentation {
   554  	if in == nil {
   555  		return nil
   556  	}
   557  
   558  	return &spec.ExternalDocumentation{
   559  		URL:         in.URL,
   560  		Description: trimBOM(in.Description),
   561  	}
   562  }
   563  
   564  func trimInfo(in *spec.Info) *spec.Info {
   565  	if in == nil {
   566  		return nil
   567  	}
   568  
   569  	return &spec.Info{
   570  		InfoProps: spec.InfoProps{
   571  			Contact:        in.Contact,
   572  			Title:          trimBOM(in.Title),
   573  			Description:    trimBOM(in.Description),
   574  			TermsOfService: trimBOM(in.TermsOfService),
   575  			License:        in.License,
   576  			Version:        in.Version,
   577  		},
   578  		VendorExtensible: in.VendorExtensible,
   579  	}
   580  }
   581  
   582  func trimTags(in []spec.Tag) []spec.Tag {
   583  	if in == nil {
   584  		return nil
   585  	}
   586  
   587  	tags := make([]spec.Tag, 0, len(in))
   588  
   589  	for _, tag := range in {
   590  		tags = append(tags, spec.Tag{
   591  			TagProps: spec.TagProps{
   592  				Name:         tag.Name,
   593  				Description:  trimBOM(tag.Description),
   594  				ExternalDocs: trimExternalDoc(tag.ExternalDocs),
   595  			},
   596  		})
   597  	}
   598  
   599  	return tags
   600  }