package sl import ( "fmt" "log" ) type SlotKey[T any] *struct{} func NewSlot[T any]() SlotKey[T] { return SlotKey[T](new(struct{})) } type slot struct { createFunc func(*ServiceLocator) (any, error) created bool value any typ string } func (s *slot) checkInitialized(l *ServiceLocator) error { if !s.created { v, err := s.createFunc(l) if err != nil { return err } log.Printf(`initialized lazy value of type %T for slot of type %s`, v, s.typ) s.created = true s.value = v } return nil } type ServiceLocator struct { providers map[any]*slot } func New() *ServiceLocator { return &ServiceLocator{ providers: map[any]*slot{}, } } func InjectValue[T any](l *ServiceLocator, slotKey SlotKey[T], value T) T { log.Printf(`injected value of type %T for slot of type %s`, value, getTypeName[T]()) l.providers[slotKey] = &slot{ nil, true, value, getTypeName[T](), } return value } func InjectLazy[T any](l *ServiceLocator, slotKey SlotKey[T], createFunc func(*ServiceLocator) (T, error)) { log.Printf(`injected lazy for slot of type %s`, getTypeName[T]()) l.providers[slotKey] = &slot{ createFunc: func(l *ServiceLocator) (any, error) { return createFunc(l) }, created: false, value: nil, typ: getTypeName[T](), } } func Use[T any](l *ServiceLocator, slotKey SlotKey[T]) (T, error) { var zero T slot, ok := l.providers[slotKey] if !ok { return zero, fmt.Errorf(`no injected value for type %s`, getTypeName[T]()) } err := slot.checkInitialized(l) if err != nil { return zero, err } v := slot.value.(T) log.Printf(`using slot of type %s with value of type %T`, getTypeName[T](), v) return v, nil } // // Require forces the initialization of a slot // func Require[T any](l *ServiceLocator, slotKey SlotKey[T]) error { // var zero T // slot, ok := l.providers[slotKey] // if !ok { // return fmt.Errorf(`no injected value for type %T`, zero) // } // return slot.checkInitialized(l) // } // // MustRequire forces the initialization of a slot or panics if the slot is missing // func MustRequire[T any](l *ServiceLocator, slotKey SlotKey[T]) { // err := Require(l, slotKey) // if err != nil { // panic(err) // } // } // // Inject will set the implementation for "S" to "value" (the service will be initialized when needed after all of its dependencies) // func Inject[S Service](l *Context, value S) { // key := getTypeName[S]() // log.Printf(`injecting value of type %T for interface %s`, value, key) // l.providers[key] = &provider{false, nil, value} // } // // InjectValue will set the implementation for "S" to "value" and mark this service as already initialized (as this is just a constant) // func InjectValue[S Service](l *Context, value S) S { // key := getTypeName[S]() // log.Printf(`injecting value of type %T for interface %s`, value, key) // l.providers[key] = &provider{true, nil, value} // return value // } // // Use will retrive from the service locator the implementation set for the type "T" and initialize the service if yet to be initialized // func Use[T Service](l *Context) (T, error) { // provider, ok := l.providers[getTypeName[T]()] // if !ok { // var zero T // return zero, fmt.Errorf(`no injected value for type "%T"`, zero) // } // if provider.initialized { // service := provider.service.(T) // return service, nil // } // log.Printf(`initializing %T`, provider.service) // if err := provider.service.Initialize(l); err != nil { // var zero T // return zero, err // } // provider.initialized = true // service := provider.service.(T) // return service, nil // } func getTypeName[T any]() string { var zero T return fmt.Sprintf(`%T`, &zero)[1:] }