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