fix: updated project name and sl documentation
This commit is contained in:
@@ -1,32 +1,46 @@
|
||||
// The [sl] package has two main concepts, the [ServiceLocator] itself is the main object that one should pass around through the application. A [ServiceLocator] has a list of slots that can be filled with [InjectLazy] and [InjectValue] and retrieved with [Use]. As slots should be unique they can only be created with the [NewSlot] function.
|
||||
//
|
||||
// The usual way to use this module is to make slots for go interfaces and then pass implementations using the [InjectValue] and [InjectLazy] functions.
|
||||
package sl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type SlotKey[T any] *struct{}
|
||||
// Logger is the debug logger, in the future this will be disabled and discard by default.
|
||||
//
|
||||
// As this is the service locator module it was meaning less to pass this through the ServiceLocator itself (without making the whole module more complex)
|
||||
var Logger *log.Logger = log.New(os.Stderr, "[sl]", log.LstdFlags)
|
||||
|
||||
func NewSlot[T any]() SlotKey[T] {
|
||||
return SlotKey[T](new(struct{}))
|
||||
// slot is just a "typed" unique "symbol".
|
||||
type slot[T any] *struct{}
|
||||
|
||||
// NewSlot is the only way to create instances of the slot type. Each instance is unique.
|
||||
//
|
||||
// This then lets you attach a service instance of type "T" to a [ServiceLocator] object.
|
||||
func NewSlot[T any]() slot[T] {
|
||||
return slot[T](new(struct{}))
|
||||
}
|
||||
|
||||
type slot struct {
|
||||
// slotEntry represents a service that can lazily initialized (using "createFunc"). Once initialized the instance is kept in the "value" field. The field "typeName" just for debugging purposes.
|
||||
type slotEntry struct {
|
||||
createFunc func(*ServiceLocator) (any, error)
|
||||
created bool
|
||||
value any
|
||||
|
||||
typ string
|
||||
typeName string
|
||||
}
|
||||
|
||||
func (s *slot) checkInitialized(l *ServiceLocator) error {
|
||||
func (s *slotEntry) 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)
|
||||
Logger.Printf(`initialized lazy value of type %T for slot of type %s`, v, s.typeName)
|
||||
|
||||
s.created = true
|
||||
s.value = v
|
||||
@@ -35,20 +49,25 @@ func (s *slot) checkInitialized(l *ServiceLocator) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServiceLocator is the main context passed around to retrive service instances, the interface uses generics so to inject and retrive service instances you should use the functions [InjectValue], [InjectLazy] and [Use].
|
||||
type ServiceLocator struct {
|
||||
providers map[any]*slot
|
||||
providers map[any]*slotEntry
|
||||
}
|
||||
|
||||
// New creates a new [ServiceLocator] context to pass around in the application.
|
||||
func New() *ServiceLocator {
|
||||
return &ServiceLocator{
|
||||
providers: map[any]*slot{},
|
||||
providers: map[any]*slotEntry{},
|
||||
}
|
||||
}
|
||||
|
||||
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]())
|
||||
// InjectValue will inject a concrete instance inside the ServiceLocator "l" for the given "slotKey". This should be used for injecting "static" services, for instances whose construction depend on other services you should use the [InjectLazy] function.
|
||||
//
|
||||
// This is generic over "T" to check that instances for the given slot type check as "T" can also be an interface.
|
||||
func InjectValue[T any](l *ServiceLocator, slotKey slot[T], value T) T {
|
||||
Logger.Printf(`injected value of type %T for slot of type %s`, value, getTypeName[T]())
|
||||
|
||||
l.providers[slotKey] = &slot{
|
||||
l.providers[slotKey] = &slotEntry{
|
||||
nil,
|
||||
true,
|
||||
value,
|
||||
@@ -57,20 +76,26 @@ func InjectValue[T any](l *ServiceLocator, slotKey SlotKey[T], value T) 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]())
|
||||
// InjectLazy will inject an instance inside the given ServiceLocator and "slotKey" that is created only when requested with a call to the [Use] function.
|
||||
//
|
||||
// This is generic over "T" to check that instances for the given slot type check as "T" can also be an interface.
|
||||
func InjectLazy[T any](l *ServiceLocator, slotKey slot[T], createFunc func(*ServiceLocator) (T, error)) {
|
||||
Logger.Printf(`injected lazy for slot of type %s`, getTypeName[T]())
|
||||
|
||||
l.providers[slotKey] = &slot{
|
||||
l.providers[slotKey] = &slotEntry{
|
||||
createFunc: func(l *ServiceLocator) (any, error) {
|
||||
return createFunc(l)
|
||||
},
|
||||
created: false,
|
||||
value: nil,
|
||||
typ: getTypeName[T](),
|
||||
created: false,
|
||||
value: nil,
|
||||
typeName: getTypeName[T](),
|
||||
}
|
||||
}
|
||||
|
||||
func Use[T any](l *ServiceLocator, slotKey SlotKey[T]) (T, error) {
|
||||
// Use retrieves the value of type T associated with the given slot key from the provided ServiceLocator instance.
|
||||
//
|
||||
// If the ServiceLocator does not have a value for the slot key, or if the value wasn't correctly initialized (in the case of a lazy slot), an error is returned.
|
||||
func Use[T any](l *ServiceLocator, slotKey slot[T]) (T, error) {
|
||||
var zero T
|
||||
|
||||
slot, ok := l.providers[slotKey]
|
||||
@@ -85,71 +110,11 @@ func Use[T any](l *ServiceLocator, slotKey SlotKey[T]) (T, error) {
|
||||
|
||||
v := slot.value.(T)
|
||||
|
||||
log.Printf(`using slot of type %s with value of type %T`, getTypeName[T](), v)
|
||||
Logger.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
|
||||
// }
|
||||
|
||||
// getTypeName is a trick to get the name of a type (even if it is an interface type)
|
||||
func getTypeName[T any]() string {
|
||||
var zero T
|
||||
return fmt.Sprintf(`%T`, &zero)[1:]
|
||||
|
||||
Reference in New Issue
Block a user