Files
Error-Code-System/server/server.go
T
weichen 28d2d50743 Enable canonical versioned Go module consumption
Why: Allow consumers to download an immutable module revision without nested source checkouts or local path replacements.

Evidence: SELF package tests and the prior canonical-path rejection are recorded in the Dev dependency-workspace task card; remote consumption is replayed against the committed candidate.

Impact: Change the module identity and its Go imports while preserving API behavior. Existing historical submodule revisions remain buildable.

Refs: Versioned dependencies U1/U3; feature/dependency-workspace
2026-10-13 15:06:48 +00:00

148 lines
4.2 KiB
Go

// Package server 提供错误码查询与校验 HTTP 服务。默认监听 :8091。
package server
import (
"encoding/json"
"net/http"
"strings"
"git-ssh.u1680832.nyat.app/weichen/Error-Code-System.git/errcode"
"git-ssh.u1680832.nyat.app/weichen/Error-Code-System.git/httpx"
)
// DefaultAddr 是 errcode-serve 的默认监听地址。
const DefaultAddr = ":8091"
// Server 对外提供已登记错误码的只读 API。
type Server struct {
Reg *errcode.Registry
}
// New 使用给定注册表。nil 表示 errcode.Default(需已导入 domains)。
func New(reg *errcode.Registry) *Server { return &Server{Reg: reg} }
// Handler 返回带追踪中间件的路由。
func (s *Server) Handler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("GET /v1/health", s.handleHealth)
mux.HandleFunc("GET /v1/codes", s.handleList)
mux.HandleFunc("GET /v1/codes/{code}", s.handleGet)
mux.HandleFunc("POST /v1/validate", s.handleValidate)
mux.HandleFunc("GET /v1/export", s.handleExport)
return httpx.Trace(mux)
}
// ListenAndServe 在 addr 上服务。addr 为空时用 DefaultAddr。
func (s *Server) ListenAndServe(addr string) error {
if strings.TrimSpace(addr) == "" {
addr = DefaultAddr
}
return http.ListenAndServe(addr, s.Handler())
}
func (s *Server) reg() *errcode.Registry {
if s != nil && s.Reg != nil {
return s.Reg
}
return errcode.Default
}
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]any{"ok": true})
}
func (s *Server) handleList(w http.ResponseWriter, r *http.Request) {
defs := s.reg().Search(r.URL.Query().Get("q"))
if domain := r.URL.Query().Get("domain"); domain != "" {
filtered := make([]errcode.Definition, 0, len(defs))
for _, def := range defs {
if def.Code.Domain() == domain {
filtered = append(filtered, def)
}
}
defs = filtered
}
writeJSON(w, http.StatusOK, defs)
}
func (s *Server) handleGet(w http.ResponseWriter, r *http.Request) {
code := r.PathValue("code")
def, ok := s.reg().Lookup(code)
if !ok {
httpx.WriteError(s.reg(), w, r, errcode.New(errcode.SysNotFound, code))
return
}
writeJSON(w, http.StatusOK, def)
}
type validateRequest struct {
Code string `json:"code"`
Codes []string `json:"codes"`
}
type validateResult struct {
Input string `json:"input"`
Known bool `json:"known"`
Resolved string `json:"resolved"`
Definition errcode.Definition `json:"definition"`
}
func (s *Server) handleValidate(w http.ResponseWriter, r *http.Request) {
var req validateRequest
dec := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20))
if err := dec.Decode(&req); err != nil {
httpx.WriteError(s.reg(), w, r, errcode.Wrap(err, errcode.SysInvalidArgument, "请求体不是合法 JSON"))
return
}
inputs := req.Codes
if req.Code != "" {
inputs = append([]string{req.Code}, inputs...)
}
if len(inputs) == 0 {
httpx.WriteError(s.reg(), w, r, errcode.New(errcode.SysInvalidArgument, "code 或 codes 必填"))
return
}
results := make([]validateResult, 0, len(inputs))
for _, input := range inputs {
def, ok := s.reg().Lookup(input)
resolved := def
if !ok {
resolved = s.reg().Resolve(input)
}
results = append(results, validateResult{
Input: input,
Known: ok,
Resolved: string(resolved.Code),
Definition: resolved,
})
}
writeJSON(w, http.StatusOK, map[string]any{"results": results})
}
func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) {
data, err := s.reg().ExportJSON()
if err != nil {
httpx.WriteError(s.reg(), w, r, errcode.Wrap(err, errcode.SysInternal, "导出失败"))
return
}
if domain := r.URL.Query().Get("domain"); domain != "" {
raw, err := json.Marshal(s.reg().List(domain))
if err != nil {
httpx.WriteError(s.reg(), w, r, errcode.Wrap(err, errcode.SysInternal, "导出失败"))
return
}
data = append(raw, '\n')
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(data)
}
func writeJSON(w http.ResponseWriter, code int, v any) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(code)
enc := json.NewEncoder(w)
enc.SetEscapeHTML(false)
_ = enc.Encode(v)
}