-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathdescriptor.go
More file actions
499 lines (428 loc) · 14.9 KB
/
Copy pathdescriptor.go
File metadata and controls
499 lines (428 loc) · 14.9 KB
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
package godi
import (
"fmt"
"reflect"
"strconv"
"sync/atomic"
"github.com/junioryono/godi/v5/internal/reflection"
)
// Global atomic counter for fast void-return service key generation
var voidKeyCounter atomic.Uint64
// descriptor is the internal registration record for a service. It is not
// exported: callers inspect registrations through the read-only ServiceInfo
// view returned by Collection.ToSlice.
type descriptor struct {
// Type is the service type this descriptor produces
Type reflect.Type
// Key is optional - for named/keyed services
Key any
// Group this provider belongs to
Group string
// Lifetime determines instance caching behavior
Lifetime Lifetime
// Constructor is the reflected function value
Constructor reflect.Value
// ConstructorType is the type of the constructor function
ConstructorType reflect.Type
// Dependencies are the analyzed dependencies
Dependencies []*reflection.Dependency
// As is an optional list of interface types this service can be registered as
// This is typically used for interface-based services
As []any
// IsInstance indicates if this descriptor holds an instance value
IsInstance bool
// Instance is the actual instance value when IsInstance is true
Instance any
// MultiReturnIndex indicates which return value this descriptor represents:
// -1 for single returns or Out structs, >= 0 for a specific return index
// in a multi-return constructor.
MultiReturnIndex int
// VoidReturn indicates if the constructor has no valid return values
VoidReturn bool
// Analysis results cached for performance
isFunc bool
isResultObject bool
resultFields []reflection.ResultField
isParamObject bool
paramFields []reflection.ParamField
// info is the analyzed constructor metadata, stashed at registration time
// so the hot resolution path can skip a Lock + map lookup + interface
// boxing per resolve. Populated by newDescriptorWithAnalyzer.
info *reflection.ConstructorInfo
// siblings links every descriptor produced by the same constructor
// invocation (multi-return constructors and result objects, including
// this descriptor itself). One constructor call must cache an instance
// for each sibling, regardless of its key or group. Populated by
// collection.addService.
siblings []*descriptor
// isAlias marks descriptors registered through godi.As. Alias siblings
// advertise one produced value under several interface types and therefore
// share singleton/scoped construction and cache identity.
isAlias bool
// resultFieldIndex is the Out-struct field index this descriptor was
// created from. -1 when the descriptor is not a result-object field.
resultFieldIndex int
}
// newDescriptor creates a new descriptor from a service with the given lifetime and options
func newDescriptor(service any, lifetime Lifetime, opts ...AddOption) (*descriptor, error) {
return newDescriptorWithAnalyzer(service, lifetime, nil, opts...)
}
// newDescriptorWithAnalyzer creates a new descriptor using the provided analyzer for caching
func newDescriptorWithAnalyzer(service any, lifetime Lifetime, analyzer *reflection.Analyzer, opts ...AddOption) (*descriptor, error) {
if service == nil {
return nil, &ValidationError{
ServiceType: nil,
Cause: ErrConstructorNil,
}
}
// Parse options
options := &addOptions{}
for _, opt := range opts {
if opt != nil {
opt.applyAddOption(options)
}
}
// Validate options
if err := options.Validate(); err != nil {
return nil, err
}
// Get constructor value and type
constructorValue := reflect.ValueOf(service)
// Check for nil pointers
if !constructorValue.IsValid() || (constructorValue.Kind() == reflect.Pointer && constructorValue.IsNil()) {
return nil, &ValidationError{
ServiceType: nil,
Cause: ErrConstructorNil,
}
}
constructorType := constructorValue.Type()
// Check if it's an instance (not a function)
isInstance := constructorType.Kind() != reflect.Func
// Use provided analyzer or create one (for backward compatibility)
if analyzer == nil {
analyzer = reflection.New()
}
info, err := analyzer.Analyze(service)
if err != nil {
return nil, &ReflectionAnalysisError{
Constructor: service,
Operation: "analyze",
Cause: err,
}
}
// Reuse the dependencies already computed by Analyze rather than calling
// GetDependencies (which would issue a second Analyze cache lookup).
dependencies := info.Dependencies()
// Create descriptor
descriptor := &descriptor{
Lifetime: lifetime,
Constructor: constructorValue,
ConstructorType: constructorType,
Dependencies: dependencies,
Group: options.Group,
IsInstance: isInstance,
Instance: nil,
MultiReturnIndex: -1,
resultFieldIndex: -1,
}
// Store the instance if it's not a function
if isInstance {
descriptor.Instance = service
descriptor.Type = constructorType
} else {
numReturns := constructorType.NumOut()
descriptor.VoidReturn = numReturns == 0
if !descriptor.VoidReturn {
// Check if there are only errors in returns
areAllErrors := true
for i := range numReturns {
if !constructorType.Out(i).Implements(reflect.TypeFor[error]()) {
areAllErrors = false
break
}
}
descriptor.VoidReturn = areAllErrors
}
if descriptor.VoidReturn {
descriptor.Type = reflect.TypeFor[struct{}]()
if descriptor.Key == nil {
// Use fast atomic counter instead of UUID
descriptor.Key = "v" + strconv.FormatUint(voidKeyCounter.Add(1), 36)
}
} else {
// Normal function with returns
descriptor.Type = constructorType.Out(0)
}
}
// Apply options
if options.Name != "" {
descriptor.Key = options.Name
}
// Cache analysis results for performance
descriptor.isFunc = info.IsFunc
descriptor.isResultObject = info.IsResultObject
descriptor.isParamObject = info.IsParamObject
descriptor.info = info
// Store param fields if it's a param object
if info.IsParamObject && len(info.Parameters) > 0 {
descriptor.paramFields = make([]reflection.ParamField, 0, len(info.Parameters))
for _, param := range info.Parameters {
descriptor.paramFields = append(descriptor.paramFields, reflection.ParamField{
Name: param.Name,
Type: param.Type,
Key: param.Key,
Group: param.Group,
Optional: param.Optional,
Index: param.Index,
})
}
}
// Store result fields if it's a result object
if info.IsResultObject && len(info.Returns) > 0 {
descriptor.resultFields = make([]reflection.ResultField, 0, len(info.Returns))
for _, ret := range info.Returns {
if !ret.IsError {
descriptor.resultFields = append(descriptor.resultFields, reflection.ResultField{
Name: ret.Name,
Type: ret.Type,
Key: ret.Key,
Group: ret.Group,
Index: ret.Index,
})
}
}
}
return descriptor, nil
}
// clone returns a shallow copy of the descriptor with the sibling links
// cleared. Registration paths that derive several descriptors from one
// analyzed constructor (result-object fields, multi-return values, interface
// bindings) clone the source and override only the fields that differ, so a
// new descriptor field is inherited by every derived descriptor
// automatically instead of having to be added to each construction site.
func (d *descriptor) clone() *descriptor {
c := *d
c.siblings = nil
return &c
}
// siblingForField returns the sibling descriptor registered for the given
// Out-struct field index, or nil when this descriptor has no sibling links
// (e.g. it was constructed outside the normal Add* path).
func (d *descriptor) siblingForField(index int) *descriptor {
for _, sibling := range d.siblings {
if sibling.resultFieldIndex == index {
return sibling
}
}
return nil
}
// GetType returns the service type this descriptor produces.
// This method implements the Provider interface from the graph package,
// enabling the descriptor to participate in dependency resolution.
func (d *descriptor) GetType() reflect.Type {
return d.Type
}
// GetKey returns the optional key for named/keyed services.
// Returns nil for non-keyed services. This method implements the Provider
// interface from the graph package for keyed service resolution.
func (d *descriptor) GetKey() any {
return d.Key
}
// GetGroup returns the group this provider belongs to.
// Returns empty string if not part of a group. This method implements
// the Provider interface from the graph package for group-based resolution.
func (d *descriptor) GetGroup() string {
return d.Group
}
// GetDependencies returns the analyzed dependencies for this descriptor.
// These dependencies must be resolved before this service can be created.
// This method implements the Provider interface from the graph package.
func (d *descriptor) GetDependencies() []*reflection.Dependency {
return d.Dependencies
}
// Validate validates the descriptor's configuration.
// It checks that the descriptor has a valid type, constructor, and lifetime,
// and ensures that key and group are not both set simultaneously.
func (d *descriptor) Validate() error {
if d.Type == nil {
return &ValidationError{
ServiceType: nil,
Cause: ErrDescriptorNil,
}
}
if !d.Constructor.IsValid() {
return &ValidationError{
ServiceType: d.Type,
Cause: ErrConstructorNil,
}
}
if d.ConstructorType == nil {
return &ValidationError{
ServiceType: d.Type,
Cause: ErrConstructorNil,
}
}
if d.Key != nil && d.Group != "" {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("descriptor cannot have both key and group set"),
}
}
// Validate lifetime
switch d.Lifetime {
case Singleton, Scoped, Transient:
// Valid lifetimes
default:
return &LifetimeError{Value: d.Lifetime}
}
// A pre-built value cannot provide scoped or transient semantics: resolving
// it would return the same object for every scope or resolution. Require a
// constructor whenever the container is responsible for creating instances.
if d.IsInstance && d.Lifetime != Singleton {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("instance values can only be registered with singleton lifetime; use a constructor for %s", d.Lifetime),
}
}
if d.VoidReturn && d.Lifetime == Transient {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("transient constructors must return a service value"),
}
}
if d.isFunc && d.ConstructorType.IsVariadic() {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("variadic constructors are not supported; use a parameter object or slice dependency"),
}
}
// For function constructors, validate return types
if d.isFunc && !d.VoidReturn {
if err := d.validateReturnTypes(); err != nil {
return err
}
}
// Validate parameter types (dependencies)
if err := d.validateParameterTypes(); err != nil {
return err
}
return nil
}
// validateReturnTypes validates that constructor return types are valid
func (d *descriptor) validateReturnTypes() error {
if d.ConstructorType == nil || d.ConstructorType.Kind() != reflect.Func {
return nil
}
numOut := d.ConstructorType.NumOut()
if d.isResultObject {
// Result objects may only be returned as (Out) or (Out, error).
if numOut > 2 {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("constructor returning a result object (godi.Out) can return at most (Out, error), got %d return values", numOut),
}
}
if numOut == 2 && !d.ConstructorType.Out(1).Implements(reflect.TypeFor[error]()) {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("constructor returning a result object (godi.Out) must have error as its second return value, got %s", d.ConstructorType.Out(1)),
}
}
}
for i := range numOut {
outType := d.ConstructorType.Out(i)
// Check for invalid types
if outType.Kind() == reflect.Invalid {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("constructor return type at index %d is invalid", i),
}
}
// Error returns are only meaningful in the last position; anywhere
// else they would be silently registered as services of type error.
if outType.Implements(reflect.TypeFor[error]()) {
if i != numOut-1 {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("constructor error return must be the last return value, found error at index %d of %d", i, numOut),
}
}
continue
}
// Check for chan return types (generally not suitable for DI)
if outType.Kind() == reflect.Chan {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("constructor return type at index %d is a channel type, which is not supported as a service type", i),
}
}
// Check for unsafe pointer
if outType.Kind() == reflect.UnsafePointer {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("constructor return type at index %d is an unsafe pointer, which is not supported as a service type", i),
}
}
}
return nil
}
// validateParameterTypes validates that constructor parameter types are valid for DI
func (d *descriptor) validateParameterTypes() error {
if d.isParamObject {
// Group-tagged fields of an In struct must be slices: they receive
// every member of the group.
for _, pf := range d.paramFields {
if pf.Group != "" && pf.Type.Kind() != reflect.Slice {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("field %s has a group tag and must be a slice, got %s", pf.Name, pf.Type),
}
}
}
} else if d.isFunc && d.ConstructorType != nil {
// A parameter object (godi.In) is only recognized when it is the
// constructor's sole parameter. Reject In structs mixed with other
// parameters instead of silently resolving them as plain services.
for in := range d.ConstructorType.Ins() {
if reflection.HasEmbeddedIn(in) {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("parameter objects (godi.In) must be the constructor's only parameter"),
}
}
}
}
for _, dep := range d.Dependencies {
if dep == nil {
continue
}
depType := dep.Type
if depType == nil {
continue
}
// Check for invalid dependency types
if depType.Kind() == reflect.Invalid {
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("dependency type is invalid"),
}
}
// Check for unsupported primitive types as dependencies (not slices for groups)
// Group dependencies are slices, which should not have chan/unsafe pointer validation
if dep.Group == "" {
switch depType.Kind() {
case reflect.Chan:
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("channel type %s is not supported as a dependency; use an interface or struct instead", depType),
}
case reflect.UnsafePointer:
return &ValidationError{
ServiceType: d.Type,
Cause: fmt.Errorf("unsafe pointer is not supported as a dependency"),
}
}
}
}
return nil
}