new meta-core api, now redis is not exposed as an "API"

This commit is contained in:
Pierre
2026-05-24 17:43:17 +02:00
parent aa45f4f32e
commit 415595c1c6
24 changed files with 2686 additions and 116 deletions
+84 -8
View File
@@ -63,10 +63,48 @@ type DataPathResponse struct {
Exists bool `json:"exists"`
}
// ErrorResponse is the response for errors
// ErrorResponse is the typed error envelope returned on every 4xx/5xx.
//
// Shape and slug vocabulary documented in docs/api-mediated-access.md
// ("Error envelope"). Stable slug strings let clients switch on the failure
// category without parsing the message; `retryable` tells callers whether
// a backoff-and-retry has any chance of succeeding.
type ErrorResponse struct {
Error string `json:"error"`
Message string `json:"message,omitempty"`
Error string `json:"error"`
Message string `json:"message,omitempty"`
Retryable bool `json:"retryable"`
}
// Stable error-slug strings. Pinned in the doc; consumers may match on
// these. Do not rename without a deprecation cycle.
const (
ErrAliasCollision = "alias_collision"
ErrUnknownRoot = "unknown_root"
ErrUnknownCID = "unknown_cid"
ErrSchemaViolation = "schema_violation"
ErrStorageUnavailable = "storage_unavailable"
ErrInternal = "internal"
)
// defaultSlugFor maps an HTTP status code to the most likely error slug.
// Handlers that want to override (e.g. /api/meta/{cid} → "unknown_cid"
// instead of "unknown_root" on 404) call writeErrorSlug directly.
func defaultSlugFor(status int) (slug string, retryable bool) {
switch status {
case http.StatusConflict:
return ErrAliasCollision, false
case http.StatusNotFound:
return ErrUnknownRoot, false
case http.StatusBadRequest:
return ErrSchemaViolation, false
case http.StatusServiceUnavailable:
return ErrStorageUnavailable, true
case http.StatusInternalServerError:
return ErrInternal, true
default:
// 4xx → not retryable, 5xx → retryable. Conservative default.
return ErrInternal, status >= 500
}
}
var startTime = time.Now()
@@ -169,6 +207,10 @@ func (s *Server) handleGetMeta(w http.ResponseWriter, r *http.Request) {
return
}
// Resolve CID → UUID via the reverse index so reads find data wherever
// the watcher minted the root, regardless of which CID the caller knows.
hashID = s.storage.ResolveRoot(hashID)
metadata, err := s.storage.GetMetadataFlat(hashID)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
@@ -203,6 +245,10 @@ func (s *Server) handlePutMeta(w http.ResponseWriter, r *http.Request) {
return
}
// CID → UUID resolution so writes land on the canonical root, not on
// a parallel midhash-rooted entry the watcher's UUID never sees.
hashID = s.storage.ResolveRoot(hashID)
var metadata map[string]string
if err := json.NewDecoder(r.Body).Decode(&metadata); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON body")
@@ -235,6 +281,8 @@ func (s *Server) handleDeleteMeta(w http.ResponseWriter, r *http.Request) {
return
}
hashID = s.storage.ResolveRoot(hashID)
deleted, err := s.storage.DeleteMetadata(hashID)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
@@ -421,7 +469,7 @@ func (s *Server) handleGetFileByCID(w http.ResponseWriter, r *http.Request) {
}
if relPath == "" {
writeError(w, http.StatusNotFound, "file not found for CID")
writeErrorSlug(w, http.StatusNotFound, ErrUnknownCID, "file not found for CID", false)
return
}
@@ -431,7 +479,7 @@ func (s *Server) handleGetFileByCID(w http.ResponseWriter, r *http.Request) {
// Check if file exists
fileInfo, err := os.Stat(fullPath)
if err != nil {
writeError(w, http.StatusNotFound, "file does not exist on disk")
writeErrorSlug(w, http.StatusNotFound, ErrUnknownCID, "file does not exist on disk", false)
return
}
@@ -522,11 +570,26 @@ func writeJSON(w http.ResponseWriter, status int, data interface{}) {
json.NewEncoder(w).Encode(data)
}
// writeError writes an error response
// writeError writes the typed error envelope. The slug + retryable bit
// are inferred from the status (see defaultSlugFor); call writeErrorSlug
// directly when a more specific slug applies.
func writeError(w http.ResponseWriter, status int, message string) {
slug, retryable := defaultSlugFor(status)
writeJSON(w, status, ErrorResponse{
Error: http.StatusText(status),
Message: message,
Error: slug,
Message: message,
Retryable: retryable,
})
}
// writeErrorSlug writes the typed envelope with an explicit slug and
// retryable bit. Use this for endpoint-specific slugs (alias_collision,
// unknown_cid) where the default mapping from HTTP status would be wrong.
func writeErrorSlug(w http.ResponseWriter, status int, slug, message string, retryable bool) {
writeJSON(w, status, ErrorResponse{
Error: slug,
Message: message,
Retryable: retryable,
})
}
@@ -546,6 +609,11 @@ func (s *Server) handlePatchMeta(w http.ResponseWriter, r *http.Request) {
return
}
// PATCH is the hot write path for meta-sort. Resolve so plugin output
// merges into the watcher's UUID root rather than minting a parallel
// midhash-rooted entry.
hashID = s.storage.ResolveRoot(hashID)
var metadata map[string]string
if err := json.NewDecoder(r.Body).Decode(&metadata); err != nil {
writeError(w, http.StatusBadRequest, "invalid JSON body")
@@ -587,6 +655,8 @@ func (s *Server) handleGetProperty(w http.ResponseWriter, r *http.Request) {
return
}
hashID = s.storage.ResolveRoot(hashID)
value, err := s.storage.GetProperty(hashID, key)
if err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
@@ -632,6 +702,8 @@ func (s *Server) handlePutProperty(w http.ResponseWriter, r *http.Request) {
return
}
hashID = s.storage.ResolveRoot(hashID)
// Read body
buf := make([]byte, 1024*1024) // 1MB max
n, err := r.Body.Read(buf)
@@ -686,6 +758,8 @@ func (s *Server) handleDeleteProperty(w http.ResponseWriter, r *http.Request) {
return
}
hashID = s.storage.ResolveRoot(hashID)
if err := s.storage.DeleteProperty(hashID, key); err != nil {
writeError(w, http.StatusInternalServerError, err.Error())
return
@@ -721,6 +795,8 @@ func (s *Server) handleAddToSet(w http.ResponseWriter, r *http.Request) {
return
}
hashID = s.storage.ResolveRoot(hashID)
// Read body
buf := make([]byte, 1024*1024) // 1MB max
n, err := r.Body.Read(buf)