github.com/thetreep/go-swagger@v0.0.0-20240223100711-35af64f14f01/cmd/swagger/commands/generate/shared.go (about)

     1  package generate
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/go-openapi/analysis"
    11  	"github.com/go-openapi/swag"
    12  	flags "github.com/jessevdk/go-flags"
    13  	"github.com/spf13/viper"
    14  	"github.com/thetreep/go-swagger/generator"
    15  )
    16  
    17  // FlattenCmdOptions determines options to the flatten spec preprocessing
    18  type FlattenCmdOptions struct {
    19  	WithExpand  bool     `long:"with-expand" description:"expands all $ref's in spec prior to generation (shorthand to --with-flatten=expand)"  group:"shared"`
    20  	WithFlatten []string `long:"with-flatten" description:"flattens all $ref's in spec prior to generation" choice:"minimal" choice:"full" choice:"expand" choice:"verbose" choice:"noverbose" choice:"remove-unused" choice:"keep-names" default:"minimal" default:"verbose" group:"shared"`
    21  }
    22  
    23  // SetFlattenOptions builds flatten options from command line args
    24  func (f *FlattenCmdOptions) SetFlattenOptions(dflt *analysis.FlattenOpts) (res *analysis.FlattenOpts) {
    25  	res = &analysis.FlattenOpts{}
    26  	if dflt != nil {
    27  		*res = *dflt
    28  	}
    29  	if f == nil {
    30  		return
    31  	}
    32  	verboseIsSet := false
    33  	minimalIsSet := false
    34  	expandIsSet := false
    35  	if f.WithExpand {
    36  		res.Expand = true
    37  		expandIsSet = true
    38  	}
    39  	for _, opt := range f.WithFlatten {
    40  		switch opt {
    41  		case "verbose":
    42  			res.Verbose = true
    43  			verboseIsSet = true
    44  		case "noverbose":
    45  			if !verboseIsSet {
    46  				// verbose flag takes precedence
    47  				res.Verbose = false
    48  				verboseIsSet = true
    49  			}
    50  		case "remove-unused":
    51  			res.RemoveUnused = true
    52  		case "expand":
    53  			res.Expand = true
    54  			expandIsSet = true
    55  		case "full":
    56  			if !minimalIsSet && !expandIsSet {
    57  				// minimal flag takes precedence
    58  				res.Minimal = false
    59  				minimalIsSet = true
    60  			}
    61  		case "minimal":
    62  			if !expandIsSet {
    63  				// expand flag takes precedence
    64  				res.Minimal = true
    65  				minimalIsSet = true
    66  			}
    67  		case "keep-names":
    68  			res.KeepNames = true
    69  		}
    70  	}
    71  	return
    72  }
    73  
    74  type sharedCommand interface {
    75  	apply(*generator.GenOpts)
    76  	getConfigFile() string
    77  	generate(*generator.GenOpts) error
    78  	log(string)
    79  }
    80  
    81  type schemeOptions struct {
    82  	Principal     string `short:"P" long:"principal" description:"the model to use for the security principal"`
    83  	DefaultScheme string `long:"default-scheme" description:"the default scheme for this API" default:"http"`
    84  
    85  	PrincipalIface bool `long:"principal-is-interface" description:"the security principal provided is an interface, not a struct"`
    86  }
    87  
    88  func (so schemeOptions) apply(opts *generator.GenOpts) {
    89  	opts.Principal = so.Principal
    90  	opts.PrincipalCustomIface = so.PrincipalIface
    91  	opts.DefaultScheme = so.DefaultScheme
    92  }
    93  
    94  type mediaOptions struct {
    95  	DefaultProduces string `long:"default-produces" description:"the default mime type that API operations produce" default:"application/json"`
    96  	DefaultConsumes string `long:"default-consumes" description:"the default mime type that API operations consume" default:"application/json"`
    97  }
    98  
    99  func (m mediaOptions) apply(opts *generator.GenOpts) {
   100  	opts.DefaultProduces = m.DefaultProduces
   101  	opts.DefaultConsumes = m.DefaultConsumes
   102  
   103  	const xmlIdentifier = "xml"
   104  	opts.WithXML = strings.Contains(opts.DefaultProduces, xmlIdentifier) || strings.Contains(opts.DefaultConsumes, xmlIdentifier)
   105  }
   106  
   107  // WithShared adds the shared options group
   108  type WithShared struct {
   109  	Shared sharedOptions `group:"Options common to all code generation commands"`
   110  }
   111  
   112  func (w WithShared) getConfigFile() string {
   113  	return string(w.Shared.ConfigFile)
   114  }
   115  
   116  type sharedOptionsCommon struct {
   117  	Spec                  flags.Filename `long:"spec" short:"f" description:"the spec file to use (default swagger.{json,yml,yaml})" group:"shared"`
   118  	Target                flags.Filename `long:"target" short:"t" default:"./" description:"the base directory for generating the files" group:"shared"`
   119  	Template              string         `long:"template" description:"load contributed templates" choice:"stratoscale" group:"shared"`
   120  	TemplateDir           flags.Filename `long:"template-dir" short:"T" description:"alternative template override directory" group:"shared"`
   121  	ConfigFile            flags.Filename `long:"config-file" short:"C" description:"configuration file to use for overriding template options" group:"shared"`
   122  	CopyrightFile         flags.Filename `long:"copyright-file" short:"r" description:"copyright file used to add copyright header" group:"shared"`
   123  	AdditionalInitialisms []string       `long:"additional-initialism" description:"consecutive capitals that should be considered intialisms" group:"shared"`
   124  	AllowTemplateOverride bool           `long:"allow-template-override" description:"allows overriding protected templates" group:"shared"`
   125  	SkipValidation        bool           `long:"skip-validation" description:"skips validation of spec prior to generation" group:"shared"`
   126  	DumpData              bool           `long:"dump-data" description:"when present dumps the json for the template generator instead of generating files" group:"shared"`
   127  	StrictResponders      bool           `long:"strict-responders" description:"Use strict type for the handler return value"`
   128  	FlattenCmdOptions
   129  }
   130  
   131  func (s sharedOptionsCommon) apply(opts *generator.GenOpts) {
   132  	opts.Spec = string(s.Spec)
   133  	opts.Target = string(s.Target)
   134  	opts.Template = s.Template
   135  	opts.TemplateDir = string(s.TemplateDir)
   136  	opts.AllowTemplateOverride = s.AllowTemplateOverride
   137  	opts.ValidateSpec = !s.SkipValidation
   138  	opts.DumpData = s.DumpData
   139  	opts.FlattenOpts = s.FlattenCmdOptions.SetFlattenOptions(opts.FlattenOpts)
   140  	opts.Copyright = string(s.CopyrightFile)
   141  	opts.StrictResponders = s.StrictResponders
   142  
   143  	swag.AddInitialisms(s.AdditionalInitialisms...)
   144  }
   145  
   146  func setCopyright(copyrightFile string) (string, error) {
   147  	// read the Copyright from file path in opts
   148  	if copyrightFile == "" {
   149  		return "", nil
   150  	}
   151  	bytebuffer, err := os.ReadFile(copyrightFile)
   152  	if err != nil {
   153  		return "", err
   154  	}
   155  	return string(bytebuffer), nil
   156  }
   157  
   158  func createSwagger(s sharedCommand) error {
   159  	cfg, err := readConfig(s.getConfigFile())
   160  	if err != nil {
   161  		return err
   162  	}
   163  	setDebug(cfg) // viper config Debug
   164  
   165  	opts := new(generator.GenOpts)
   166  	s.apply(opts)
   167  
   168  	opts.Copyright, err = setCopyright(opts.Copyright)
   169  	if err != nil {
   170  		return fmt.Errorf("could not load copyright file: %v", err)
   171  	}
   172  
   173  	if opts.Template != "" {
   174  		contribOptionsOverride(opts)
   175  	}
   176  
   177  	if err = opts.EnsureDefaults(); err != nil {
   178  		return err
   179  	}
   180  
   181  	if err = configureOptsFromConfig(cfg, opts); err != nil {
   182  		return err
   183  	}
   184  
   185  	if err = s.generate(opts); err != nil {
   186  		return err
   187  	}
   188  
   189  	basepath, err := filepath.Abs(".")
   190  	if err != nil {
   191  		return err
   192  	}
   193  
   194  	targetAbs, err := filepath.Abs(opts.Target)
   195  	if err != nil {
   196  		return err
   197  	}
   198  	rp, err := filepath.Rel(basepath, targetAbs)
   199  	if err != nil {
   200  		return err
   201  	}
   202  
   203  	s.log(rp)
   204  
   205  	return nil
   206  }
   207  
   208  func readConfig(filename string) (*viper.Viper, error) {
   209  	if filename == "" {
   210  		return nil, nil
   211  	}
   212  
   213  	abspath, err := filepath.Abs(filename)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	log.Println("trying to read config from", abspath)
   218  	return generator.ReadConfig(abspath)
   219  }
   220  
   221  func configureOptsFromConfig(cfg *viper.Viper, opts *generator.GenOpts) error {
   222  	if cfg == nil {
   223  		return nil
   224  	}
   225  
   226  	var def generator.LanguageDefinition
   227  	if err := cfg.Unmarshal(&def); err != nil {
   228  		return err
   229  	}
   230  	return def.ConfigureOpts(opts)
   231  }
   232  
   233  func setDebug(cfg *viper.Viper) {
   234  	// viper config debug
   235  	if os.Getenv("DEBUG") != "" || os.Getenv("SWAGGER_DEBUG") != "" {
   236  		if cfg != nil {
   237  			cfg.Debug()
   238  		} else {
   239  			log.Println("No config read")
   240  		}
   241  	}
   242  }