diff --git a/cmd/build/main.go b/cmd/build/main.go index 606df98..7742d8f 100644 --- a/cmd/build/main.go +++ b/cmd/build/main.go @@ -8,7 +8,6 @@ import ( "phc/website/services/database" "phc/website/services/server" "phc/website/services/server/dev" - lista_utenti "phc/website/services/server/lista-utenti" "phc/website/sl" ) @@ -17,23 +16,20 @@ func main() { // sl.Inject[config.Interface](l, &config.EnvConfig{}) - sl.InjectValue[config.Interface](l, &config.Custom{ - ModeValue: "production", - HostValue: ":4000", + sl.InjectValue(l, config.Slot, &config.Config{ + Mode: "production", + Host: ":4000", }) - sl.InjectValue[database.Database](l, &database.Memory{}) + sl.InjectValue(l, database.Slot, database.Database( + &database.Memory{}), + ) - sl.Inject(l, &dev.Dev{}) - sl.Inject(l, &lista_utenti.ListaUtenti{}) - sl.Inject(l, &server.Server{}) - - _, err := sl.Use[*server.Server](l) - if err != nil { + if _, err := server.Configure(l); err != nil { log.Fatal(err) } - dev, err := sl.Use[*dev.Dev](l) + dev, err := sl.Use(l, dev.Slot) if err != nil { log.Fatal(err) } @@ -48,4 +44,6 @@ func main() { if err := enc.Encode(dev.HtmlRouteBindings); err != nil { log.Fatal(err) } + + log.Printf(`generated "out/routes.json"`) } diff --git a/cmd/server/main.go b/cmd/server/main.go index 369c3cc..f155e4b 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -6,22 +6,18 @@ import ( "phc/website/services/config" "phc/website/services/database" "phc/website/services/server" - "phc/website/services/server/dev" - lista_utenti "phc/website/services/server/lista-utenti" "phc/website/sl" ) func main() { l := sl.New() - // sl.Inject[config.Interface](l, &config.EnvConfig{}) - - config := sl.InjectValue[config.Interface](l, &config.Custom{ - ModeValue: "production", - HostValue: ":4000", + cfg := sl.InjectValue(l, config.Slot, &config.Config{ + Mode: "production", + Host: ":4000", }) - sl.InjectValue[database.Database](l, &database.Memory{ + sl.InjectValue[database.Database](l, database.Slot, &database.Memory{ Users: []model.User{ { Id: "claire", @@ -38,14 +34,10 @@ func main() { }, }) - sl.Inject(l, &dev.Dev{}) - sl.Inject(l, &lista_utenti.ListaUtenti{}) - sl.Inject(l, &server.Server{}) - - server, err := sl.Use[*server.Server](l) + srv, err := server.Configure(l) if err != nil { log.Fatal(err) } - log.Fatal(server.Router.Listen(config.Host())) + log.Fatal(srv.Router.Listen(cfg.Host)) } diff --git a/frontend/pages/index.html b/frontend/index.html similarity index 100% rename from frontend/pages/index.html rename to frontend/index.html diff --git a/services/config/config.go b/services/config/config.go index b23c50e..b28038c 100644 --- a/services/config/config.go +++ b/services/config/config.go @@ -6,47 +6,30 @@ import ( "github.com/joho/godotenv" ) -type Interface interface { - sl.Service +var Slot = sl.NewSlot[*Config]() - Mode() string - Host() string +type Config struct { + Mode string + Host string } -type Custom struct { - ModeValue string - HostValue string -} - -func (c Custom) Mode() string { return c.ModeValue } -func (c Custom) Host() string { return c.HostValue } - -func (Custom) Initialize(l *sl.ServiceLocator) error { - return nil -} - -type EnvConfig struct { - mode string - host string -} - -func (c EnvConfig) Mode() string { return c.mode } -func (c EnvConfig) Host() string { return c.host } - -func (c *EnvConfig) Initialize(l *sl.ServiceLocator) error { +func Load(l *sl.ServiceLocator) (*Config, error) { m, err := godotenv.Read(".env") if err != nil { - return err + return nil, err } - c.mode = "production" + mode := "production" if v, ok := m["MODE"]; ok { - c.mode = v + mode = v } - c.host = ":4000" + host := ":4000" if v, ok := m["HOST"]; ok { - c.host = v + host = v } - return nil + return &Config{ + mode, + host, + }, nil } diff --git a/services/database/database.go b/services/database/database.go index ade0837..5029eb3 100644 --- a/services/database/database.go +++ b/services/database/database.go @@ -6,9 +6,9 @@ import ( "phc/website/sl" ) -type Database interface { - sl.Service +var Slot = sl.NewSlot[Database]() +type Database interface { CreateUser(user model.User) error ReadUser(id string) (model.User, error) ReadUsers() ([]model.User, error) @@ -20,8 +20,6 @@ type Memory struct { Users []model.User } -func (m *Memory) Initialize(l *sl.ServiceLocator) error { return nil } - func (m *Memory) CreateUser(user model.User) error { m.Users = append(m.Users, user) return nil diff --git a/services/server/dev/dev.go b/services/server/dev/dev.go index e387b46..fe7d3b7 100644 --- a/services/server/dev/dev.go +++ b/services/server/dev/dev.go @@ -9,35 +9,39 @@ import ( "github.com/gofiber/fiber/v2" ) +var Slot = sl.NewSlot[*Dev]() + type Dev struct { HtmlRouteBindings map[string]string } -func (m *Dev) Initialize(l *sl.ServiceLocator) error { - m.HtmlRouteBindings = map[string]string{} +func New(l *sl.ServiceLocator) (*Dev, error) { + dev := &Dev{ + map[string]string{}, + } - router, err := sl.Use[*routes.Router](l) + router, err := sl.Use(l, routes.Api) if err != nil { - return err + return nil, err } - router.Get("/api/dev/routes", func(c *fiber.Ctx) error { - return c.JSON(m.HtmlRouteBindings) + router.Get("/dev/routes", func(c *fiber.Ctx) error { + return c.JSON(dev.HtmlRouteBindings) }) - return nil + return dev, nil } // UseVitePage this hook will link the provided "mountPoint" to the "frontendHtml" page -func UseVitePage[T any](l *sl.ServiceLocator, mountPoint, frontendHtml string) func(c *fiber.Ctx) error { +func UseVitePage(l *sl.ServiceLocator, mountPoint, frontendHtml string) func(c *fiber.Ctx) error { log.Printf(`registering vite route %q for %q`, frontendHtml, mountPoint) - dev, err := sl.Use[*Dev](l) + dev, err := sl.Use(l, Slot) if err != nil { log.Fatal(err) } - dev.HtmlRouteBindings[mountPoint] = path.Join("./frontend/", frontendHtml) + dev.HtmlRouteBindings[mountPoint] = frontendHtml return func(c *fiber.Ctx) error { return c.SendFile(path.Join("./out/frontend/", frontendHtml)) diff --git a/services/server/lista-utenti/lista-utenti.go b/services/server/lista-utenti/lista-utenti.go index f6e01f2..aa0caa4 100644 --- a/services/server/lista-utenti/lista-utenti.go +++ b/services/server/lista-utenti/lista-utenti.go @@ -9,24 +9,22 @@ import ( "github.com/gofiber/fiber/v2" ) -type ListaUtenti struct{} - -func (s *ListaUtenti) Initialize(l *sl.ServiceLocator) error { - - db, err := sl.Use[database.Database](l) +func Configure(l *sl.ServiceLocator) error { + db, err := sl.Use(l, database.Slot) if err != nil { return err } - router, err := sl.Use[*routes.Router](l) + r, err := sl.Use(l, routes.Root) if err != nil { return err } - router.Get("/utenti", - dev.UseVitePage[ListaUtenti](l, "/utenti", "pages/lista-utenti/index.html"), + r.Get("/utenti", + dev.UseVitePage(l, "/utenti", "pages/lista-utenti/index.html"), ) - router.Get("/api/lista-utenti", func(c *fiber.Ctx) error { + + r.Get("/api/lista-utenti", func(c *fiber.Ctx) error { users, err := db.ReadUsers() if err != nil { return err diff --git a/services/server/lista-utenti/lista-utenti_test.go b/services/server/lista-utenti/lista-utenti_test.go index 6dfb1e7..022e8d1 100644 --- a/services/server/lista-utenti/lista-utenti_test.go +++ b/services/server/lista-utenti/lista-utenti_test.go @@ -8,8 +8,6 @@ import ( "net/http" "phc/website/model" "phc/website/services/database" - "phc/website/services/server/dev" - lista_utenti "phc/website/services/server/lista-utenti" "phc/website/services/server/routes" "phc/website/sl" "testing" @@ -22,7 +20,7 @@ import ( func Test1(t *testing.T) { l := sl.New() - sl.InjectValue[database.Database](l, &database.Memory{ + sl.InjectValue[database.Database](l, database.Slot, &database.Memory{ Users: []model.User{ { Id: "claire", @@ -39,11 +37,8 @@ func Test1(t *testing.T) { }, }) - sl.Inject(l, &dev.Dev{}) - sl.Inject(l, &lista_utenti.ListaUtenti{}) - r := fiber.New() - sl.InjectValue(l, routes.NewRouter(r)) + sl.InjectValue(l, routes.Root, fiber.Router(r)) req, err := http.NewRequest("GET", "http://localhost:4000/api/lista-utenti", nil) if err != nil { diff --git a/services/server/routes/routes.go b/services/server/routes/routes.go index 99de087..1eb795c 100644 --- a/services/server/routes/routes.go +++ b/services/server/routes/routes.go @@ -6,10 +6,6 @@ import ( "github.com/gofiber/fiber/v2" ) -func NewRouter(r fiber.Router) *Router { - return &Router{r} -} +var Root = sl.NewSlot[fiber.Router]() -type Router struct{ fiber.Router } - -func (s *Router) Initialize(l *sl.ServiceLocator) error { return nil } +var Api = sl.NewSlot[fiber.Router]() diff --git a/services/server/server.go b/services/server/server.go index cdae8b0..8557e78 100644 --- a/services/server/server.go +++ b/services/server/server.go @@ -11,16 +11,21 @@ import ( type Server struct{ Router *fiber.App } -func (s *Server) Initialize(l *sl.ServiceLocator) error { - s.Router = fiber.New(fiber.Config{}) - sl.InjectValue(l, routes.NewRouter(s.Router)) +func Configure(l *sl.ServiceLocator) (*Server, error) { + r := fiber.New(fiber.Config{}) - if _, err := sl.Use[*dev.Dev](l); err != nil { - return err + sl.InjectValue(l, routes.Root, r.Group("/")) + sl.InjectValue(l, routes.Api, r.Group("/api")) + + devServerInterop, err := dev.New(l) + if err != nil { + return nil, err } - if _, err := sl.Use[*lista_utenti.ListaUtenti](l); err != nil { - return err + sl.InjectValue(l, dev.Slot, devServerInterop) + + if err := lista_utenti.Configure(l); err != nil { + return nil, err } - return nil + return &Server{r}, nil } diff --git a/sl/sl.go b/sl/sl.go index d15d9da..13f21c0 100644 --- a/sl/sl.go +++ b/sl/sl.go @@ -5,69 +5,146 @@ import ( "log" ) -type provider struct { - initialized bool - service Service -} +type SlotKey[T any] *struct{} -type ServiceLocator struct { - providers map[string]*provider - hooks map[string][]Service +func NewSlot[T any]() SlotKey[T] { + return SlotKey[T](new(struct{})) } -type Service interface { - Initialize(l *ServiceLocator) error +type slot struct { + createFunc func(*ServiceLocator) (any, error) + created bool + value any + + typ string } -func New() *ServiceLocator { - return &ServiceLocator{ - providers: map[string]*provider{}, - hooks: map[string][]Service{}, +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.value = v } -} -func getTypeName[T any]() string { - var v T - return fmt.Sprintf(`%T`, &v)[1:] + return nil } -// 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 *ServiceLocator, value S) { - key := getTypeName[S]() - log.Printf(`injecting value of type %T for interface %s`, value, key) +type ServiceLocator struct { + providers map[any]*slot +} - l.providers[key] = &provider{false, value} +func New() *ServiceLocator { + return &ServiceLocator{ + providers: map[any]*slot{}, + } } -// 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 *ServiceLocator, value S) S { - key := getTypeName[S]() - log.Printf(`injecting value of type %T for interface %s`, value, key) +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[key] = &provider{true, value} + l.providers[slotKey] = &slot{nil, true, value, getTypeName[T]()} 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 *ServiceLocator) (T, error) { - provider, ok := l.providers[getTypeName[T]()] - if !ok { - var zero T - return zero, fmt.Errorf(`no injected value for type "%T"`, zero) +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 - if provider.initialized { - service := provider.service.(T) - return service, nil + slot, ok := l.providers[slotKey] + if !ok { + return zero, fmt.Errorf(`no injected value for type %s`, getTypeName[T]()) } - log.Printf(`initializing %T`, provider.service) - if err := provider.service.Initialize(l); err != nil { - var zero T + err := slot.checkInitialized(l) + if err != nil { return zero, err } - provider.initialized = true - service := provider.service.(T) - return service, nil + 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:] } diff --git a/vite.config.js b/vite.config.js index bad0ec6..c31fffb 100644 --- a/vite.config.js +++ b/vite.config.js @@ -17,7 +17,6 @@ const retriveGoRoutes = { const routesReq = await fetch('http://127.0.0.1:4000/api/dev/routes') const routes = await routesReq.json() - console.dir(routes, { depth: null }) return routes }, @@ -51,8 +50,8 @@ export default defineConfig(async config => { configureServer(server) { Object.entries(routes).forEach(([route, file]) => { server.middlewares.use(route, async (req, res, next) => { - let htmlPage = await readFile(resolve(__dirname, file), 'utf8') - htmlPage = htmlPage.replace(/\.\//g, dirname(file) + '/') + let htmlPage = await readFile(resolve(__dirname, './frontend/', file), 'utf8') + htmlPage = htmlPage.replace(/\.\//g, '/' + dirname(file) + '/') const url = file const html = await server.transformIndexHtml(url, htmlPage)