-
Notifications
You must be signed in to change notification settings - Fork 272
/
servicestruct.go
121 lines (105 loc) · 3.85 KB
/
servicestruct.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
package servicestruct
import (
"fmt"
"go/ast"
"go/token"
"encr.dev/pkg/errors"
"encr.dev/pkg/option"
"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/apis/internal/directive"
"encr.dev/v2/parser/internal/utils"
"encr.dev/v2/parser/resource"
)
// ServiceStruct describes a dependency injection struct for a service.
type ServiceStruct struct {
Decl *schema.TypeDecl // decl is the type declaration
Doc string
// Init is the function for initializing this group.
// It is nil if there is no initialization function.
Init option.Option[*schema.FuncDecl]
}
func (ss *ServiceStruct) Kind() resource.Kind { return resource.ServiceStruct }
func (ss *ServiceStruct) Package() *pkginfo.Package { return ss.Decl.File.Pkg }
func (ss *ServiceStruct) Pos() token.Pos { return ss.Decl.AST.Pos() }
func (ss *ServiceStruct) End() token.Pos { return ss.Decl.AST.End() }
func (ss *ServiceStruct) SortKey() string {
return ss.Decl.File.Pkg.ImportPath.String() + "." + ss.Decl.Name
}
type ParseData struct {
Errs *perr.List
Schema *schema.Parser
File *pkginfo.File
Decl *ast.GenDecl
Dir *directive.Directive
Doc string
}
// Parse parses the service struct in the provided type declaration.
func Parse(d ParseData) *ServiceStruct {
// We don't allow anything on the directive besides "encore:service".
directive.Validate(d.Errs, d.Dir, directive.ValidateSpec{})
// We only support encore:service directives directly on the type declaration,
// not on a group of type declarations.
if len(d.Decl.Specs) != 1 {
d.Errs.Add(errInvalidDirectivePlacement.AtGoNode(d.Dir.AST))
if len(d.Decl.Specs) == 0 {
// We can't continue without at least one spec.
d.Errs.Bailout()
}
}
spec := d.Decl.Specs[0].(*ast.TypeSpec)
declInfo := d.File.Pkg.Names().PkgDecls[spec.Name.Name]
decl := d.Schema.ParseTypeDecl(declInfo)
ss := &ServiceStruct{
Decl: decl,
Doc: d.Doc,
}
// Find the init function for this service struct, if any.
initFunc := d.File.Pkg.Names().PkgDecls["init"+ss.Decl.Name]
if initFunc != nil && initFunc.Type == token.FUNC {
init, ok := d.Schema.ParseFuncDecl(initFunc.File, initFunc.Func)
if ok {
ss.Init = option.Some(init)
}
}
validateServiceStruct(d, ss)
return ss
}
// validateServiceStruct validates that the service struct and associated init function
// has the correct structure.
func validateServiceStruct(d ParseData, ss *ServiceStruct) {
if len(ss.Decl.TypeParams) > 0 {
d.Errs.Add(errServiceStructMustNotBeGeneric.AtGoNode(ss.Decl.TypeParams[0].AST))
}
ss.Init.ForAll(func(initFunc *schema.FuncDecl) {
if len(initFunc.TypeParams) > 0 {
d.Errs.Add(errServiceInitCannotBeGeneric.AtGoNode(initFunc.TypeParams[0].AST))
}
if len(initFunc.Type.Params) > 0 {
d.Errs.Add(errServiceInitCannotHaveParams.AtGoNode(initFunc.Type.Params[0].AST))
}
// Ensure the return type is (*T, error) where T is the service struct.
if len(initFunc.Type.Results) != 2 {
// Wrong number of returns
d.Errs.Add(errServiceInitInvalidReturnType(ss.Decl.Name).AtGoNode(initFunc.AST))
} else if result, n := schemautil.Deref(initFunc.Type.Results[0].Type); n != 1 || !schemautil.IsNamed(result, ss.Decl.File.Pkg.ImportPath, ss.Decl.Name) {
// First type is not *T
d.Errs.Add(
errServiceInitInvalidReturnType(ss.Decl.Name).
AtGoNode(initFunc.AST, errors.AsError(
fmt.Sprintf("got %s", utils.PrettyPrint(initFunc.Type.Results[0].Type.ASTExpr())),
)),
)
} else if !schemautil.IsBuiltinKind(initFunc.Type.Results[1].Type, schema.Error) {
// Second type is not builtin error.
d.Errs.Add(
errServiceInitInvalidReturnType(ss.Decl.Name).
AtGoNode(initFunc.AST, errors.AsError(
fmt.Sprintf("got %s", utils.PrettyPrint(initFunc.Type.Results[1].Type.ASTExpr())),
)),
)
}
})
}