package api import ( "encoding/json" "io" "log" "net/http" "regexp" "runtime" "runtime/debug" "strconv" "strings" "time" ) var entityIDPattern = regexp.MustCompile(`^[A-Z]{2,3}-[0-9]{7}$`) func parseID(path, prefix string) (string, bool) { if !strings.HasPrefix(path, prefix) { return "", false } trimmed := strings.TrimPrefix(path, prefix) if trimmed == "" || strings.Contains(trimmed, "/") { return "", false } if !entityIDPattern.MatchString(trimmed) { return "", false } return trimmed, true } func parseSubresourceID(path, prefix, suffix string) (string, bool) { if !strings.HasPrefix(path, prefix) || !strings.HasSuffix(path, suffix) { return "", false } trimmed := strings.TrimPrefix(path, prefix) trimmed = strings.TrimSuffix(trimmed, suffix) if trimmed == "" || strings.Contains(trimmed, "/") { return "", false } if !entityIDPattern.MatchString(trimmed) { return "", false } return trimmed, true } func decodeJSON(r *http.Request, dest any) error { decoder := json.NewDecoder(r.Body) decoder.DisallowUnknownFields() if err := decoder.Decode(dest); err != nil { return err } if err := decoder.Decode(&struct{}{}); err != io.EOF { return err } return nil } func writeJSON(w http.ResponseWriter, status int, payload any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) if err := json.NewEncoder(w).Encode(payload); err != nil { log.Printf("write json response failed status=%d err=%v", status, err) } } func writeError(w http.ResponseWriter, status int, message string, cause ...error) { if pc, file, line, ok := runtime.Caller(1); ok { fn := runtime.FuncForPC(pc) fnName := "" if fn != nil { fnName = fn.Name() } if status >= http.StatusInternalServerError { root := "" if len(cause) > 0 && cause[0] != nil { root = cause[0].Error() } log.Printf( "api error status=%d message=%q cause=%q caller=%s:%d func=%s\nstack=%s", status, message, root, file, line, fnName, strings.TrimSpace(string(debug.Stack())), ) } else { log.Printf("api error status=%d message=%q caller=%s:%d func=%s", status, message, file, line, fnName) } } else { if status >= http.StatusInternalServerError { root := "" if len(cause) > 0 && cause[0] != nil { root = cause[0].Error() } log.Printf("api error status=%d message=%q cause=%q\nstack=%s", status, message, root, strings.TrimSpace(string(debug.Stack()))) } else { log.Printf("api error status=%d message=%q", status, message) } } writeJSON(w, status, map[string]string{"error": message}) } func parseTimeParam(value string) (time.Time, error) { return time.Parse(time.RFC3339, value) } func parseIntParam(value string) (int, error) { parsed, err := strconv.Atoi(value) if err != nil { return 0, err } return parsed, nil } func parseFloatParam(value string) (float64, error) { return strconv.ParseFloat(value, 64) }