github.com/Desuuuu/genqlient@v0.5.3/generate/config.go (about) 1 package generate 2 3 import ( 4 _ "embed" 5 "go/token" 6 "os" 7 "path/filepath" 8 9 "gopkg.in/yaml.v2" 10 ) 11 12 var cfgFilenames = []string{".genqlient.yml", ".genqlient.yaml", "genqlient.yml", "genqlient.yaml"} 13 14 // Config represents genqlient's configuration, generally read from 15 // genqlient.yaml. 16 // 17 // Callers must call ValidateAndFillDefaults before using the config. 18 type Config struct { 19 // The following fields are documented at: 20 // https://github.com/Desuuuu/genqlient/blob/main/docs/genqlient.yaml 21 Schema StringList `yaml:"schema"` 22 Operations StringList `yaml:"operations"` 23 Generated string `yaml:"generated"` 24 Package string `yaml:"package"` 25 ExportOperations string `yaml:"export_operations"` 26 ContextType string `yaml:"context_type"` 27 ClientGetter string `yaml:"client_getter"` 28 Bindings map[string]*TypeBinding `yaml:"bindings"` 29 StructReferences bool `yaml:"use_struct_references"` 30 OptionalPointers bool `yaml:"optional_pointers"` 31 Extensions bool `yaml:"use_extensions"` 32 33 // Set to true to use features that aren't fully ready to use. 34 // 35 // This is primarily intended for genqlient's own tests. These features 36 // are likely BROKEN and come with NO EXPECTATION OF COMPATIBILITY. Use 37 // them at your own risk! 38 AllowBrokenFeatures bool `yaml:"allow_broken_features"` 39 40 // The directory of the config-file (relative to which all the other paths 41 // are resolved). Set by ValidateAndFillDefaults. 42 baseDir string 43 } 44 45 // A TypeBinding represents a Go type to which genqlient will bind a particular 46 // GraphQL type, and is documented further at: 47 // https://github.com/Desuuuu/genqlient/blob/main/docs/genqlient.yaml 48 type TypeBinding struct { 49 Type string `yaml:"type"` 50 ExpectExactFields string `yaml:"expect_exact_fields"` 51 Marshaler string `yaml:"marshaler"` 52 Unmarshaler string `yaml:"unmarshaler"` 53 } 54 55 // ValidateAndFillDefaults ensures that the configuration is valid, and fills 56 // in any options that were unspecified. 57 // 58 // The argument is the directory relative to which paths will be interpreted, 59 // typically the directory of the config file. 60 func (c *Config) ValidateAndFillDefaults(baseDir string) error { 61 c.baseDir = baseDir 62 for i := range c.Schema { 63 c.Schema[i] = filepath.Join(baseDir, c.Schema[i]) 64 } 65 for i := range c.Operations { 66 c.Operations[i] = filepath.Join(baseDir, c.Operations[i]) 67 } 68 c.Generated = filepath.Join(baseDir, c.Generated) 69 if c.ExportOperations != "" { 70 c.ExportOperations = filepath.Join(baseDir, c.ExportOperations) 71 } 72 73 if c.ContextType == "" { 74 c.ContextType = "context.Context" 75 } 76 77 if c.Package == "" { 78 abs, err := filepath.Abs(c.Generated) 79 if err != nil { 80 return errorf(nil, "unable to guess package-name: %v", err) 81 } 82 83 base := filepath.Base(filepath.Dir(abs)) 84 if !token.IsIdentifier(base) { 85 return errorf(nil, "unable to guess package-name: %v is not a valid identifier", base) 86 } 87 88 c.Package = base 89 } 90 91 return nil 92 } 93 94 // ReadAndValidateConfig reads the configuration from the given file, validates 95 // it, and returns it. 96 func ReadAndValidateConfig(filename string) (*Config, error) { 97 text, err := os.ReadFile(filename) 98 if err != nil { 99 return nil, errorf(nil, "unreadable config file %v: %v", filename, err) 100 } 101 102 var config Config 103 err = yaml.UnmarshalStrict(text, &config) 104 if err != nil { 105 return nil, errorf(nil, "invalid config file %v: %v", filename, err) 106 } 107 108 err = config.ValidateAndFillDefaults(filepath.Dir(filename)) 109 if err != nil { 110 return nil, errorf(nil, "invalid config file %v: %v", filename, err) 111 } 112 113 return &config, nil 114 } 115 116 // ReadAndValidateConfigFromDefaultLocations looks for a config file in the 117 // current directory, and all parent directories walking up the tree. The 118 // closest config file will be returned. 119 func ReadAndValidateConfigFromDefaultLocations() (*Config, error) { 120 cfgFile, err := findCfg() 121 if err != nil { 122 return nil, err 123 } 124 return ReadAndValidateConfig(cfgFile) 125 } 126 127 //go:embed default_genqlient.yaml 128 var defaultConfig []byte 129 130 func initConfig(filename string) error { 131 return os.WriteFile(filename, defaultConfig, 0o644) 132 } 133 134 // findCfg searches for the config file in this directory and all parents up the tree 135 // looking for the closest match 136 func findCfg() (string, error) { 137 dir, err := os.Getwd() 138 if err != nil { 139 return "", errorf(nil, "unable to get working dir to findCfg: %v", err) 140 } 141 142 cfg := findCfgInDir(dir) 143 144 for cfg == "" && dir != filepath.Dir(dir) { 145 dir = filepath.Dir(dir) 146 cfg = findCfgInDir(dir) 147 } 148 149 if cfg == "" { 150 return "", os.ErrNotExist 151 } 152 153 return cfg, nil 154 } 155 156 func findCfgInDir(dir string) string { 157 for _, cfgName := range cfgFilenames { 158 path := filepath.Join(dir, cfgName) 159 if _, err := os.Stat(path); err == nil { 160 return path 161 } 162 } 163 return "" 164 }