aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-28 04:15:23 -0800
committerFuwn <[email protected]>2026-02-28 04:15:23 -0800
commitc7fa9bd5887666e12b555396b5ce64b8af2b3c5f (patch)
tree925ab01f9f0e1772c572ba41d7ccdf8586616234
parentfix(thin): retry DID fetches on 429 and align DID doc shape (diff)
downloadplutia-test-c7fa9bd5887666e12b555396b5ce64b8af2b3c5f.tar.xz
plutia-test-c7fa9bd5887666e12b555396b5ce64b8af2b3c5f.zip
fix(api): align tombstoned DID compatibility with plc.directory
-rw-r--r--internal/api/plc_compatibility_test.go50
-rw-r--r--internal/api/server.go96
2 files changed, 105 insertions, 41 deletions
diff --git a/internal/api/plc_compatibility_test.go b/internal/api/plc_compatibility_test.go
index 45428d8..a669f0c 100644
--- a/internal/api/plc_compatibility_test.go
+++ b/internal/api/plc_compatibility_test.go
@@ -302,6 +302,56 @@ func TestPLCCompatibilityNotFoundUsesPLCErrorShape(t *testing.T) {
}
}
+func TestPLCCompatibilityTombstonedDIDReturnsNotAvailable404(t *testing.T) {
+ ts, store, _, cleanup := newCompatibilityServer(t)
+ defer cleanup()
+
+ const tombDID = "did:plc:tombstone-test"
+
+ if err := store.PutState(types.StateV1{
+ DID: tombDID,
+ ChainTipHash: "bafy-tomb-tip",
+ DIDDocument: []byte(`{"id":"did:plc:tombstone-test","deactivated":true}`),
+ }); err != nil {
+ t.Fatalf("put tombstoned state: %v", err)
+ }
+
+ check := func(path string) {
+ t.Helper()
+
+ resp, err := http.Get(ts.URL + path)
+ if err != nil {
+ t.Fatalf("get %s: %v", path, err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusNotFound {
+ t.Fatalf("%s status: got %d want 404", path, resp.StatusCode)
+ }
+
+ if got := resp.Header.Get("Content-Type"); got != "application/json; charset=utf-8" {
+ t.Fatalf("%s content-type mismatch: got %q want %q", path, got, "application/json; charset=utf-8")
+ }
+
+ var body map[string]any
+ if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
+ t.Fatalf("%s decode body: %v", path, err)
+ }
+
+ if len(body) != 1 {
+ t.Fatalf("%s expected single message field, got: %v", path, body)
+ }
+
+ want := "DID not available: " + tombDID
+ if got, _ := body["message"].(string); got != want {
+ t.Fatalf("%s message mismatch: got %q want %q", path, got, want)
+ }
+ }
+
+ check("/" + tombDID)
+ check("/" + tombDID + "/data")
+}
+
func newCompatibilityServer(t *testing.T) (*httptest.Server, *storage.PebbleStore, []types.ExportRecord, func()) {
t.Helper()
diff --git a/internal/api/server.go b/internal/api/server.go
index 9895a65..284c2b2 100644
--- a/internal/api/server.go
+++ b/internal/api/server.go
@@ -479,43 +479,15 @@ func (s *Server) handlePLCCompatibility(w http.ResponseWriter, r *http.Request)
}
func (s *Server) handleGetDIDCompatibility(w http.ResponseWriter, r *http.Request, did string) {
- var state types.StateV1
- if s.ingestor != nil {
- resolvedState, err := s.ingestor.ResolveState(r.Context(), did)
- if err != nil {
- if errors.Is(err, ingest.ErrDIDNotFound) {
- writeCompatibilityErr(w, http.StatusNotFound, "DID not registered: "+did)
-
- return
- }
-
- if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
- writeCompatibilityErr(w, http.StatusGatewayTimeout, err.Error())
-
- return
- }
-
- writeCompatibilityErr(w, http.StatusInternalServerError, err.Error())
-
- return
- }
-
- state = resolvedState
- } else {
- stateVal, ok, err := s.store.GetState(did)
- if err != nil {
- writeCompatibilityErr(w, http.StatusInternalServerError, err.Error())
-
- return
- }
-
- if !ok {
- writeCompatibilityErr(w, http.StatusNotFound, "DID not registered: "+did)
+ state, ok := s.resolveCompatibilityState(w, r, did)
+ if !ok {
+ return
+ }
- return
- }
+ if isTombstonedDIDDocument(state.DIDDocument) {
+ writeCompatibilityErr(w, http.StatusNotFound, "DID not available: "+did)
- state = stateVal
+ return
}
data, err := s.ingestor.LoadCurrentPLCData(r.Context(), did)
@@ -539,11 +511,7 @@ func (s *Server) handleGetDIDCompatibility(w http.ResponseWriter, r *http.Reques
}
status := http.StatusOK
- deactivated := isTombstonedDIDDocument(state.DIDDocument)
-
- if deactivated {
- status = http.StatusGone
- }
+ deactivated := false
writeJSONWithContentType(w, status, "application/did+ld+json", buildPLCDIDDocument(did, data, deactivated))
}
@@ -666,6 +634,17 @@ func (s *Server) handleGetDIDDataCompatibility(w http.ResponseWriter, r *http.Re
return
}
+ state, ok := s.resolveCompatibilityState(w, r, did)
+ if !ok {
+ return
+ }
+
+ if isTombstonedDIDDocument(state.DIDDocument) {
+ writeCompatibilityErr(w, http.StatusNotFound, "DID not available: "+did)
+
+ return
+ }
+
data, err := s.ingestor.LoadCurrentPLCData(r.Context(), did)
if err != nil {
@@ -689,6 +668,41 @@ func (s *Server) handleGetDIDDataCompatibility(w http.ResponseWriter, r *http.Re
writeJSONWithContentType(w, http.StatusOK, "application/json", data)
}
+func (s *Server) resolveCompatibilityState(w http.ResponseWriter, r *http.Request, did string) (types.StateV1, bool) {
+ if s.ingestor != nil {
+ state, err := s.ingestor.ResolveState(r.Context(), did)
+ if err != nil {
+ if errors.Is(err, ingest.ErrDIDNotFound) {
+ writeCompatibilityErr(w, http.StatusNotFound, "DID not registered: "+did)
+ return types.StateV1{}, false
+ }
+
+ if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
+ writeCompatibilityErr(w, http.StatusGatewayTimeout, err.Error())
+ return types.StateV1{}, false
+ }
+
+ writeCompatibilityErr(w, http.StatusInternalServerError, err.Error())
+ return types.StateV1{}, false
+ }
+
+ return state, true
+ }
+
+ stateVal, found, err := s.store.GetState(did)
+ if err != nil {
+ writeCompatibilityErr(w, http.StatusInternalServerError, err.Error())
+ return types.StateV1{}, false
+ }
+
+ if !found {
+ writeCompatibilityErr(w, http.StatusNotFound, "DID not registered: "+did)
+ return types.StateV1{}, false
+ }
+
+ return stateVal, true
+}
+
func (s *Server) handleExportCompatibility(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.Header().Set("Allow", http.MethodGet)
@@ -964,7 +978,7 @@ func extractServicesMap(v any) map[string]map[string]string {
}
func writeCompatibilityErr(w http.ResponseWriter, code int, message string) {
- writeJSON(w, code, map[string]any{"message": message})
+ writeJSONWithContentType(w, code, "application/json; charset=utf-8", map[string]any{"message": message})
}
func (s *Server) withTimeout(next http.Handler) http.Handler {