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 }