/* * Copyright 2026 Oleg Borodin * * This work is published and licensed under a Creative Commons * Attribution-NonCommercial-NoDerivatives 4.0 International License. * * Distribution of this work is permitted, but commercial use and * modifications are strictly prohibited. */ package router import ( "bytes" "context" "encoding/json" "io" "net/http" "net/url" "strconv" ) type Context struct { Ctx context.Context Request *http.Request Writer http.ResponseWriter PathMap map[string]string Bools map[string]bool Strings map[string]string StatusCode int } func NewContext(writer http.ResponseWriter, request *http.Request) *Context { rctx := &Context{ Writer: writer, Request: request, Ctx: request.Context(), PathMap: make(map[string]string), Bools: make(map[string]bool), Strings: make(map[string]string), } return rctx } // Aux maps func (rctx *Context) SetBool(key string, value bool) { rctx.Bools[key] = value } func (rctx *Context) GetBool(key string) (bool, bool) { exists, value := rctx.Bools[key] return exists, value } func (rctx *Context) SetString(key string, value string) { rctx.Strings[key] = value } func (rctx *Context) GetString(key string) (string, bool) { value, exists := rctx.Strings[key] return value, exists } // Request func (rctx *Context) GetSubpath(key string) (string, bool) { value, exists := rctx.PathMap[key] return value, exists } func (rctx *Context) URL() *url.URL { return rctx.Request.URL } func (rctx *Context) GetQuery(key string) string { return rctx.Request.URL.Query().Get(key) } func (rctx *Context) GetHeader(key string) string { return rctx.Request.Header.Get(key) } func (rctx *Context) GetHeaders() http.Header { return rctx.Request.Header } func (rctx *Context) GetContext() context.Context { return rctx.Request.Context() } // Binding const emptyJSON = "{}" func (rctx *Context) BindJSON(obj any) error { buffer := bytes.NewBuffer(nil) _, err := io.Copy(buffer, rctx.Request.Body) if err != nil { return err } reqBody := buffer.Bytes() if len(reqBody) == 0 { reqBody = []byte(emptyJSON) } err = json.Unmarshal(reqBody, obj) if err != nil { return err } return err } func (rctx *Context) BindQuery(obj any) error { qMap := make(map[string]string) for key, val := range rctx.Request.URL.Query() { if len(val) == 1 { qMap[key] = val[0] } } return bindObj(obj, qMap, "param") } // Response func (rctx *Context) SetHeader(key, value string) { rctx.Writer.Header().Set(key, value) } func (rctx *Context) SetStatus(httpStatus int) { rctx.StatusCode = httpStatus rctx.Writer.WriteHeader(httpStatus) } func (rctx *Context) SendJSON(statusCode int, payload any) { rctx.StatusCode = statusCode buffer := bytes.NewBuffer(nil) json.NewEncoder(buffer).Encode(payload) rctx.Writer.Header().Set("Content-Type", "application/json") size := strconv.FormatInt(int64(len(buffer.Bytes())), 10) rctx.Writer.Header().Set("Content-Length", size) rctx.Writer.WriteHeader(statusCode) rctx.Writer.Write(buffer.Bytes()) } func (rctx *Context) SendText(statusCode int, payload string) { rctx.StatusCode = statusCode size := strconv.FormatInt(int64(len(payload)), 10) rctx.Writer.Header().Set("Content-Type", "text/plain") rctx.Writer.Header().Set("Content-Length", size) rctx.Writer.WriteHeader(statusCode) rctx.Writer.Write([]byte(payload)) } func (rctx *Context) SendBytes(statusCode int, ctype string, payload []byte) { rctx.StatusCode = statusCode size := strconv.FormatInt(int64(len(payload)), 10) rctx.Writer.Header().Set("Content-Type", ctype) rctx.Writer.Header().Set("Content-Length", size) rctx.Writer.WriteHeader(statusCode) rctx.Writer.Write(payload) }