/
config.go
154 lines (134 loc) · 4.24 KB
/
config.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package config
import (
"fmt"
"go/ast"
"go/token"
"encr.dev/pkg/errors"
"encr.dev/pkg/paths"
"encr.dev/v2/internals/perr"
"encr.dev/v2/internals/pkginfo"
"encr.dev/v2/internals/schema"
"encr.dev/v2/internals/schema/schemautil"
"encr.dev/v2/parser/infra/internal/parseutil"
"encr.dev/v2/parser/resource"
"encr.dev/v2/parser/resource/resourceparser"
)
// Load represents a config load statement.
type Load struct {
AST *ast.CallExpr
File *pkginfo.File
// Type is the type of the config struct being loaded.
// It's guaranteed to be a (possibly pointer to a) named struct type.
Type schema.Type
// FuncCall is the AST node that represents the config.Load expression.
FuncCall *ast.CallExpr
}
func (*Load) Kind() resource.Kind { return resource.ConfigLoad }
func (l *Load) Package() *pkginfo.Package { return l.File.Pkg }
func (l *Load) ASTExpr() ast.Expr { return l.AST }
func (l *Load) Pos() token.Pos { return l.AST.Pos() }
func (l *Load) End() token.Pos { return l.AST.End() }
func (l *Load) SortKey() string {
return fmt.Sprintf("%s:%s:%d", l.File.Pkg.ImportPath, l.File.Name, l.AST.Pos())
}
var LoadParser = &resourceparser.Parser{
Name: "ConfigLoad",
InterestingImports: []paths.Pkg{"encore.dev/config"},
Run: func(p *resourceparser.Pass) {
name := pkginfo.QualifiedName{PkgPath: "encore.dev/config", Name: "Load"}
spec := &parseutil.ReferenceSpec{
MinTypeArgs: 0,
MaxTypeArgs: 1,
Parse: parseLoad,
}
parseutil.FindPkgNameRefs(p.Pkg, []pkginfo.QualifiedName{name}, func(file *pkginfo.File, name pkginfo.QualifiedName, stack []ast.Node) {
parseutil.ParseReference(p, spec, parseutil.ReferenceData{
File: file,
Stack: stack,
ResourceFunc: name,
})
})
},
}
func parseLoad(d parseutil.ReferenceInfo) {
errs := d.Pass.Errs
if len(d.Call.Args) > 0 {
errs.Add(errInvalidLoad.AtGoNode(d.Call))
return
}
if len(d.TypeArgs) != 1 {
errs.Add(errInvalidConfigType.AtGoNode(d.Call))
}
// Resolve the named struct used for the config type
ref, ok := schemautil.ResolveNamedStruct(d.TypeArgs[0], false)
if !ok {
errs.Add(errInvalidConfigType.AtGoNode(d.TypeArgs[0].ASTExpr()))
return
}
load := &Load{
AST: d.Call,
File: d.File,
Type: d.TypeArgs[0],
FuncCall: d.Call,
}
concrete := schemautil.ConcretizeWithTypeArgs(errs, ref.ToType(), ref.TypeArgs)
walkCfgToVerify(d.Pass.Errs, load, concrete, false)
d.Pass.RegisterResource(load)
d.Pass.AddBind(d.File, d.Ident, load)
}
func walkCfgToVerify(errs *perr.List, load *Load, decl schema.Type, insideConfigValue bool) {
switch decl := decl.(type) {
case schema.BuiltinType:
// no-op ok
case schema.NamedType:
if decl.DeclInfo.File.Pkg.ImportPath == "encore.dev/config" {
if insideConfigValue {
errs.Add(errNestedValueUsage.
AtGoNode(decl.ASTExpr(), errors.AsError("cannot use config.Value inside a config.Value")).
AtGoNode(load, errors.AsHelp("config loaded here")),
)
}
switch decl.DeclInfo.Name {
case "Value", "Values":
// Value / Values are magic wrappers that are used to indicate a realtime
// config update
if len(decl.TypeArgs) > 0 {
walkCfgToVerify(errs, load, decl.TypeArgs[0], true)
// return so we don't verify the standard type
return
}
}
} else {
insideConfigValue = false
}
walkCfgToVerify(errs, load, decl.Decl().Type, insideConfigValue)
case schema.PointerType:
walkCfgToVerify(errs, load, decl.Elem, false)
case schema.ListType:
walkCfgToVerify(errs, load, decl.Elem, false)
case schema.MapType:
walkCfgToVerify(errs, load, decl.Key, false)
walkCfgToVerify(errs, load, decl.Value, false)
case schema.StructType:
for _, field := range decl.Fields {
if !field.IsExported() {
errs.Add(errUnexportedField.
AtGoNode(field.AST).
AtGoNode(load, errors.AsHelp("config loaded here")),
)
} else if field.IsAnonymous() {
errs.Add(errAnonymousField.
AtGoNode(field.AST).
AtGoNode(load, errors.AsHelp("config loaded here")),
)
} else {
walkCfgToVerify(errs, load, field.Type, false)
}
}
default:
errs.Add(errInvalidConfigTypeUsed.
AtGoNode(decl.ASTExpr(), errors.AsError("unsupported type")).
AtGoNode(load, errors.AsHelp("config loaded here")),
)
}
}