// Package general_server provides an API request router based on versioning and custom layers. // // The GeneralServer distributes incoming HTTP requests to specific registered servers // depending on the API version or defined logical layer. To operate properly, additional // servers must be registered using the InitGeneral function or AppendToArray method. // // All registered servers must implement the GeneralServerApiContract interface to ensure // correct interaction. The GeneralServer itself implements this interface and can be // passed as an HTTP handler. // // If the requested version is not explicitly registered but matches a configured logical // layer, the server will fallback to the latest registered version for that layer. // Otherwise, an HTTP 400 error is returned. package general_server import ( "encoding/json" "errors" "io" "log/slog" "net/http" "github.com/akyaiy/GoSally-mvp/core/config" ) // serversApiVer is a type alias for string, used to represent API version strings in the GeneralServer. type serversApiVer string /* // GeneralServerApiContract defines the interface for servers that can be registered type GeneralServerApiContract interface { // GetVersion returns the API version of the server. GetVersion() string // Handle and HandleList methods are used to forward requests. Handle(w http.ResponseWriter, r *http.Request) HandleList(w http.ResponseWriter, r *http.Request) } */ type GeneralServerApiContract interface { GetVersion() string Handle(w http.ResponseWriter, r *http.Request) } type GeneralServerContarct interface { GeneralServerApiContract // AppendToArray adds a new server to the GeneralServer's internal map. AppendToArray(GeneralServerApiContract) error } // GeneralServer implements the GeneralServerApiContract and serves as a router for different API versions. type GeneralServer struct { w http.ResponseWriter r *http.Request // servers holds the registered servers by their API version. // The key is the version string, and the value is the server implementing GeneralServerApi servers map[serversApiVer]GeneralServerApiContract log slog.Logger cfg *config.Conf } // GeneralServerInit structure only for initialization general server. type GeneralServerInit struct { Log *slog.Logger Config *config.Conf } // InitGeneral initializes a new GeneralServer with the provided configuration and registered servers. func InitGeneral(o *GeneralServerInit, servers ...GeneralServerApiContract) *GeneralServer { general := &GeneralServer{ servers: make(map[serversApiVer]GeneralServerApiContract), cfg: o.Config, log: *o.Log, } // register the provided servers // s is each server implementing GeneralServerApiContract, this is not a general server for _, s := range servers { general.servers[serversApiVer(s.GetVersion())] = s } return general } // GetVersion returns the API version of the GeneralServer, which is "general". func (s *GeneralServer) GetVersion() string { return "general" } // AppendToArray adds a new server to the GeneralServer's internal map. func (s *GeneralServer) AppendToArray(server GeneralServerApiContract) error { if _, exist := s.servers[serversApiVer(server.GetVersion())]; !exist { s.servers[serversApiVer(server.GetVersion())] = server return nil } return errors.New("server with this version is already exist") } func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { WriteRouterError(w, &RouterError{ Status: "error", StatusCode: http.StatusBadRequest, Payload: map[string]any{ "Message": IssueMethod, }, }) s.log.Info("invalid request received", slog.String("issue", IssueMethod), slog.String("requested-method", r.Method)) return } var payload RawPettiEnvelope body, err := io.ReadAll(r.Body) if err != nil { WriteRouterError(w, &RouterError{ Status: "error", StatusCode: http.StatusBadRequest, Payload: map[string]any{ "Message": IssueToReadBody, }, }) s.log.Info("invalid request received", slog.String("issue", IssueToReadBody)) return } if err := json.Unmarshal(body, &payload); err != nil { WriteRouterError(w, &RouterError{ Status: "error", StatusCode: http.StatusBadRequest, Payload: map[string]any{ "Message": InvalidProtocol, }, }) s.log.Info("invalid request received", slog.String("issue", InvalidProtocol)) return } server, ok := s.servers[serversApiVer(payload.PettiVer)] if !ok { WriteRouterError(w, &RouterError{ Status: "error", StatusCode: http.StatusBadRequest, Payload: map[string]any{ "Message": InvalidProtovolVersion, }, }) s.log.Info("invalid request received", slog.String("issue", InvalidProtovolVersion), slog.String("requested-version", payload.PettiVer)) return } server.Handle(w, r) } /* // Handle processes incoming HTTP requests, routing them to the appropriate server based on the API version. // It checks if the requested version is registered and handles the request accordingly. func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) { s.w = w s.r = r serverReqApiVer := chi.URLParam(r, "ver") log := s.log.With( slog.Group("request", slog.String("version", serverReqApiVer), slog.String("url", s.r.URL.String()), slog.String("method", s.r.Method), ), slog.Group("connection", slog.String("remote", s.r.RemoteAddr), ), ) s.log.Debug("Received request") // transfer control to the server if srv, ok := s.servers[serversApiVer(serverReqApiVer)]; ok { srv.Handle(w, r) return } // if the requested version is not registered, check if it matches a logical layer // and use the latest version for that layer if available // this allows for custom layers to be defined in the configuration // and used as a fallback for unsupported versions // this is useful for cases where the API version is not explicitly registered // but the logical layer is defined in the configuration if slices.Contains(s.cfg.HTTPServer.HTTPServer_Api.Layers, serverReqApiVer) { if srv, ok := s.servers[serversApiVer(s.cfg.HTTPServer.HTTPServer_Api.LatestVer)]; ok { s.log.Debug("Using latest version under custom layer", slog.String("layer", serverReqApiVer), slog.String("fallback-version", s.cfg.HTTPServer.HTTPServer_Api.LatestVer), ) // transfer control to the latest version server under the custom layer srv.Handle(w, r) return } } log.Error("HTTP request error: unsupported API version", slog.Int("status", http.StatusBadRequest)) if err := utils.WriteJSONError(s.w, http.StatusBadRequest, "unsupported API version"); err != nil { s.log.Error("Failed to write JSON", slog.String("err", err.Error())) } } // HandleList processes incoming HTTP requests for listing commands, routing them to the appropriate server based on the API version. func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) { s.w = w s.r = r serverReqApiVer := chi.URLParam(r, "ver") log := s.log.With( slog.Group("request", slog.String("version", serverReqApiVer), slog.String("url", s.r.URL.String()), slog.String("method", s.r.Method), ), slog.Group("connection", slog.String("remote", s.r.RemoteAddr), ), ) log.Debug("Received request") // transfer control to the server if srv, ok := s.servers[serversApiVer(serverReqApiVer)]; ok { srv.HandleList(w, r) return } if slices.Contains(s.cfg.HTTPServer.HTTPServer_Api.Layers, serverReqApiVer) { if srv, ok := s.servers[serversApiVer(s.cfg.HTTPServer.HTTPServer_Api.LatestVer)]; ok { log.Debug("Using latest version under custom layer", slog.String("layer", serverReqApiVer), slog.String("fallback-version", s.cfg.HTTPServer.HTTPServer_Api.LatestVer), ) // transfer control to the latest version server under the custom layer srv.HandleList(w, r) return } } log.Error("HTTP request error: unsupported API version", slog.Int("status", http.StatusBadRequest)) if err := utils.WriteJSONError(s.w, http.StatusBadRequest, "unsupported API version"); err != nil { s.log.Error("Failed to write JSON", slog.String("err", err.Error())) } } */