diff options
| author | Fuwn <[email protected]> | 2026-02-26 20:20:17 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-26 20:20:17 -0800 |
| commit | e264ed8f36b26c5684ea653ad7299603ccffe02b (patch) | |
| tree | 95bbb4ad51740d7bcc0d834bd1aaa587df22f233 | |
| parent | fix: align PLC compatibility read endpoints with plc.directory schema (diff) | |
| download | plutia-test-e264ed8f36b26c5684ea653ad7299603ccffe02b.tar.xz plutia-test-e264ed8f36b26c5684ea653ad7299603ccffe02b.zip | |
feat: Apply Iku formatting
| -rw-r--r-- | cmd/plccompat/main.go | 117 | ||||
| -rw-r--r-- | cmd/plutia/keygen.go | 5 | ||||
| -rw-r--r-- | cmd/plutia/keygen_test.go | 11 | ||||
| -rw-r--r-- | internal/api/server.go | 19 | ||||
| -rw-r--r-- | internal/ingest/client.go | 1 | ||||
| -rw-r--r-- | internal/ingest/service.go | 15 |
6 files changed, 158 insertions, 10 deletions
diff --git a/cmd/plccompat/main.go b/cmd/plccompat/main.go index 133a766..c0fcf80 100644 --- a/cmd/plccompat/main.go +++ b/cmd/plccompat/main.go @@ -54,20 +54,22 @@ func main() { sampleN = flag.Int("sample", 5, "random DID samples from local export") seed = flag.Int64("seed", time.Now().UnixNano(), "random seed") ) + flag.Parse() if *count < 1 { fmt.Fprintln(os.Stderr, "count must be >= 1") os.Exit(2) } + if *sampleN < 1 { fmt.Fprintln(os.Stderr, "sample must be >= 1") os.Exit(2) } client := &http.Client{Timeout: 30 * time.Second} - dids, tombstone, legacy, err := discoverDIDs(client, strings.TrimRight(*localBase, "/"), 2500, *sampleN, *seed) + if err != nil { fmt.Fprintf(os.Stderr, "discover dids: %v\n", err) os.Exit(1) @@ -88,6 +90,7 @@ func main() { path := "/" + did + suffix res := compareJSONEndpoint(client, rep.LocalBase, rep.UpstreamBase, path, "did="+did) rep.Results = append(rep.Results, res) + if !res.Compatible { rep.AllCompatible = false } @@ -98,6 +101,7 @@ func main() { exportPath := fmt.Sprintf("/export?count=%d", *count) exportRes := compareNDJSONEndpoint(client, rep.LocalBase, rep.UpstreamBase, exportPath) rep.Results = append(rep.Results, exportRes) + if !exportRes.Compatible { rep.AllCompatible = false } @@ -106,6 +110,7 @@ func main() { missingDID := "did:plc:this-does-not-exist-for-compat-check" notFoundRes := compareJSONEndpoint(client, rep.LocalBase, rep.UpstreamBase, "/"+missingDID, "missing_did") rep.Results = append(rep.Results, notFoundRes) + if !notFoundRes.Compatible { rep.AllCompatible = false } @@ -114,6 +119,7 @@ func main() { if tombstone != "" { goneRes := compareJSONEndpoint(client, rep.LocalBase, rep.UpstreamBase, "/"+tombstone, "tombstone_did") rep.Results = append(rep.Results, goneRes) + if !goneRes.Compatible { rep.AllCompatible = false } @@ -124,7 +130,9 @@ func main() { } enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + _ = enc.Encode(rep) if !rep.AllCompatible { @@ -135,9 +143,11 @@ func main() { func discoverDIDs(client *http.Client, base string, count int, sampleN int, seed int64) ([]string, string, string, error) { path := fmt.Sprintf("%s/export?count=%d", base, count) res, err := doFetch(client, path) + if err != nil { return nil, "", "", err } + if res.status != http.StatusOK { return nil, "", "", fmt.Errorf("local export status=%d", res.status) } @@ -148,7 +158,9 @@ func discoverDIDs(client *http.Client, base string, count int, sampleN int, seed } r := bufio.NewScanner(bytes.NewReader(res.body)) + r.Buffer(make([]byte, 0, 64*1024), 10*1024*1024) + unique := map[string]struct{}{} ordered := make([]string, 0, count) tombstone := "" @@ -156,21 +168,28 @@ func discoverDIDs(client *http.Client, base string, count int, sampleN int, seed for r.Scan() { line := bytes.TrimSpace(r.Bytes()) + if len(line) == 0 { continue } + var row rec + if err := json.Unmarshal(line, &row); err != nil { continue } + if row.DID == "" { continue } + if _, ok := unique[row.DID]; !ok { unique[row.DID] = struct{}{} ordered = append(ordered, row.DID) } + typ, _ := row.Operation["type"].(string) + switch typ { case "plc_tombstone", "tombstone": if tombstone == "" { @@ -182,6 +201,7 @@ func discoverDIDs(client *http.Client, base string, count int, sampleN int, seed } } } + if err := r.Err(); err != nil { return nil, "", "", err } @@ -197,12 +217,15 @@ func discoverDIDs(client *http.Client, base string, count int, sampleN int, seed rng := rand.New(rand.NewSource(seed)) perm := rng.Perm(len(ordered)) out := make([]string, 0, sampleN+2) + for i := 0; i < sampleN; i++ { out = append(out, ordered[perm[i]]) } + if tombstone != "" && !contains(out, tombstone) { out = append(out, tombstone) } + if legacy != "" && !contains(out, legacy) { out = append(out, legacy) } @@ -212,20 +235,22 @@ func discoverDIDs(client *http.Client, base string, count int, sampleN int, seed func compareJSONEndpoint(client *http.Client, localBase, upstreamBase, path, ctx string) endpointResult { res := endpointResult{Endpoint: path, Context: ctx, Compatible: true} - localURL := localBase + path upURL := upstreamBase + path - l, lErr := doFetch(client, localURL) u, uErr := doFetch(client, upURL) + if lErr != nil || uErr != nil { res.Compatible = false + if lErr != nil { res.Issues = append(res.Issues, "local fetch error: "+lErr.Error()) } + if uErr != nil { res.Issues = append(res.Issues, "upstream fetch error: "+uErr.Error()) } + return res } @@ -238,6 +263,7 @@ func compareJSONEndpoint(client *http.Client, localBase, upstreamBase, path, ctx res.Compatible = false res.Issues = append(res.Issues, fmt.Sprintf("status mismatch local=%d upstream=%d", l.status, u.status)) } + if normalizeContentType(l.contentType) != normalizeContentType(u.contentType) { res.Compatible = false res.Issues = append(res.Issues, fmt.Sprintf("content-type mismatch local=%q upstream=%q", normalizeContentType(l.contentType), normalizeContentType(u.contentType))) @@ -253,6 +279,7 @@ func compareJSONEndpoint(client *http.Client, localBase, upstreamBase, path, ctx switch kind { case "did", "data", "error": lm, um, strictIssues := compareJSONBodies(l.body, u.body) + if lm || um || len(strictIssues) > 0 { issues = append(issues, strictIssues...) } @@ -271,17 +298,20 @@ func compareNDJSONEndpoint(client *http.Client, localBase, upstreamBase, path st res := endpointResult{Endpoint: path, Context: "export", Compatible: true} localURL := localBase + path upURL := upstreamBase + path - l, lErr := doFetch(client, localURL) u, uErr := doFetch(client, upURL) + if lErr != nil || uErr != nil { res.Compatible = false + if lErr != nil { res.Issues = append(res.Issues, "local fetch error: "+lErr.Error()) } + if uErr != nil { res.Issues = append(res.Issues, "upstream fetch error: "+uErr.Error()) } + return res } @@ -294,6 +324,7 @@ func compareNDJSONEndpoint(client *http.Client, localBase, upstreamBase, path st res.Compatible = false res.Issues = append(res.Issues, fmt.Sprintf("status mismatch local=%d upstream=%d", l.status, u.status)) } + if normalizeContentType(l.contentType) != normalizeContentType(u.contentType) { res.Compatible = false res.Issues = append(res.Issues, fmt.Sprintf("content-type mismatch local=%q upstream=%q", normalizeContentType(l.contentType), normalizeContentType(u.contentType))) @@ -301,25 +332,32 @@ func compareNDJSONEndpoint(client *http.Client, localBase, upstreamBase, path st localRows, lErr := parseNDJSON(l.body) upRows, uErr := parseNDJSON(u.body) + if lErr != nil || uErr != nil { res.Compatible = false + if lErr != nil { res.Issues = append(res.Issues, "local ndjson parse error: "+lErr.Error()) } + if uErr != nil { res.Issues = append(res.Issues, "upstream ndjson parse error: "+uErr.Error()) } + return res } for i, row := range localRows { issues := validateExportShape(row) + for _, issue := range issues { res.Issues = append(res.Issues, fmt.Sprintf("local line %d: %s", i+1, issue)) } } + for i, row := range upRows { issues := validateExportShape(row) + for _, issue := range issues { res.Issues = append(res.Issues, fmt.Sprintf("upstream line %d: %s", i+1, issue)) } @@ -351,6 +389,7 @@ func endpointKind(path string) string { func validateEndpointJSONShape(kind string, status int, body []byte, side string) []string { decoded, err := decodeJSON(body) + if err != nil { return []string{fmt.Sprintf("%s json decode error: %v", side, err)} } @@ -362,6 +401,7 @@ func validateEndpointJSONShape(kind string, status int, body []byte, side string return prefixIssues(side, validateDIDDocumentShape(decoded)) case http.StatusGone: issues := validateDIDDocumentShape(decoded) + if len(issues) == 0 { return nil } @@ -427,7 +467,9 @@ func validateEndpointJSONShape(kind string, status int, body []byte, side string func decodeJSON(body []byte) (interface{}, error) { var v interface{} + dec := json.NewDecoder(bytes.NewReader(body)) + dec.UseNumber() if err := dec.Decode(&v); err != nil { @@ -443,6 +485,7 @@ func prefixIssues(side string, issues []string) []string { } out := make([]string, 0, len(issues)) + for _, issue := range issues { out = append(out, fmt.Sprintf("%s %s", side, issue)) } @@ -452,11 +495,13 @@ func prefixIssues(side string, issues []string) []string { func validateMessageErrorShape(v interface{}) []string { obj, ok := v.(map[string]interface{}) + if !ok { return []string{"expected object error body"} } msg, ok := obj["message"].(string) + if !ok || strings.TrimSpace(msg) == "" { return []string{"missing message string field"} } @@ -470,11 +515,13 @@ func validateMessageErrorShape(v interface{}) []string { func validateDIDDocumentShape(v interface{}) []string { obj, ok := v.(map[string]interface{}) + if !ok { return []string{"expected object did document"} } issues := make([]string, 0) + if _, ok := obj["@context"].([]interface{}); !ok { issues = append(issues, "missing @context array") } @@ -521,6 +568,7 @@ func validateDIDDocumentShape(v interface{}) []string { func validatePLCDataShape(v interface{}) []string { obj, ok := v.(map[string]interface{}) + if !ok { return []string{"expected object plc data"} } @@ -532,10 +580,11 @@ func validatePLCDataShape(v interface{}) []string { "alsoKnownAs": "array", "services": "object", } - issues := make([]string, 0) + for key, wantType := range required { got, ok := obj[key] + if !ok { issues = append(issues, "missing field "+key) @@ -560,13 +609,16 @@ func validatePLCDataShape(v interface{}) []string { func validateOperationArray(v interface{}) []string { rows, ok := v.([]interface{}) + if !ok { return []string{"expected array of operations"} } issues := make([]string, 0) + for idx, row := range rows { rowIssues := validateOperationShape(row) + for _, issue := range rowIssues { issues = append(issues, fmt.Sprintf("row %d: %s", idx, issue)) } @@ -577,13 +629,16 @@ func validateOperationArray(v interface{}) []string { func validateAuditArrayShape(v interface{}) []string { rows, ok := v.([]interface{}) + if !ok { return []string{"expected array of audit entries"} } issues := make([]string, 0) + for idx, row := range rows { obj, ok := row.(map[string]interface{}) + if !ok { issues = append(issues, fmt.Sprintf("row %d: expected object", idx)) @@ -600,6 +655,7 @@ func validateAuditArrayShape(v interface{}) []string { for key, want := range required { val, ok := obj[key] + if !ok { issues = append(issues, fmt.Sprintf("row %d: missing field %s", idx, key)) @@ -623,11 +679,13 @@ func validateAuditArrayShape(v interface{}) []string { func validateOperationShape(v interface{}) []string { obj, ok := v.(map[string]interface{}) + if !ok { return []string{"expected operation object"} } typ, _ := obj["type"].(string) + if strings.TrimSpace(typ) == "" { return []string{"missing type string"} } @@ -676,20 +734,27 @@ func validateOperationShape(v interface{}) []string { func doFetch(client *http.Client, rawURL string) (fetchResp, error) { u, err := url.Parse(rawURL) + if err != nil { return fetchResp{}, err } + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { return fetchResp{}, err } + resp, err := client.Do(req) + if err != nil { return fetchResp{}, err } + defer resp.Body.Close() b, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024)) + if err != nil { return fetchResp{}, err } @@ -704,28 +769,39 @@ func doFetch(client *http.Client, rawURL string) (fetchResp, error) { func parseNDJSON(body []byte) ([]interface{}, error) { out := make([]interface{}, 0) s := bufio.NewScanner(bytes.NewReader(body)) + s.Buffer(make([]byte, 0, 64*1024), 10*1024*1024) + for s.Scan() { line := bytes.TrimSpace(s.Bytes()) + if len(line) == 0 { continue } + var v interface{} + dec := json.NewDecoder(bytes.NewReader(line)) + dec.UseNumber() + if err := dec.Decode(&v); err != nil { return nil, err } + out = append(out, v) } + if err := s.Err(); err != nil { return nil, err } + return out, nil } func validateExportShape(v interface{}) []string { obj, ok := v.(map[string]interface{}) + if !ok { return []string{"expected object line"} } @@ -737,14 +813,17 @@ func validateExportShape(v interface{}) []string { "nullified": "bool", "createdAt": "string", } - issues := make([]string, 0) + for k, want := range required { vv, ok := obj[k] + if !ok { issues = append(issues, "missing field "+k) + continue } + if typeName(vv) != want { issues = append(issues, fmt.Sprintf("field %s type=%s want=%s", k, typeName(vv), want)) } @@ -757,28 +836,37 @@ func validateExportShape(v interface{}) []string { } sort.Strings(issues) + return issues } func compareJSONBodies(localBody, upBody []byte) (bool, bool, []string) { var l, u interface{} + ld := json.NewDecoder(bytes.NewReader(localBody)) + ld.UseNumber() + if err := ld.Decode(&l); err != nil { return false, false, []string{"local json decode error: " + err.Error()} } + ud := json.NewDecoder(bytes.NewReader(upBody)) + ud.UseNumber() + if err := ud.Decode(&u); err != nil { return false, false, []string{"upstream json decode error: " + err.Error()} } lm, um, issues := diffAny(l, u, "$") + return lm, um, issues } func diffAny(local, upstream interface{}, path string) (hasLocalMismatch bool, hasUpMismatch bool, issues []string) { lt, ut := typeName(local), typeName(upstream) + if lt != ut { return true, true, []string{fmt.Sprintf("%s type mismatch local=%s upstream=%s", path, lt, ut)} } @@ -786,43 +874,55 @@ func diffAny(local, upstream interface{}, path string) (hasLocalMismatch bool, h switch l := local.(type) { case map[string]interface{}: u := upstream.(map[string]interface{}) + for k := range u { if _, ok := l[k]; !ok { hasLocalMismatch = true issues = append(issues, fmt.Sprintf("%s missing field in local: %s", path, k)) } } + for k := range l { if _, ok := u[k]; !ok { hasUpMismatch = true issues = append(issues, fmt.Sprintf("%s extra field in local: %s", path, k)) } } + for k := range l { uv, ok := u[k] + if !ok { continue } + lm, um, sub := diffAny(l[k], uv, path+"."+k) + if lm { hasLocalMismatch = true } + if um { hasUpMismatch = true } + issues = append(issues, sub...) } case []interface{}: u := upstream.([]interface{}) n := min(len(l), len(u)) + for i := 0; i < n; i++ { lm, um, sub := diffAny(l[i], u[i], fmt.Sprintf("%s[%d]", path, i)) + if lm { hasLocalMismatch = true } + if um { hasUpMismatch = true } + issues = append(issues, sub...) } } @@ -851,10 +951,13 @@ func typeName(v interface{}) string { func normalizeContentType(v string) string { v = strings.TrimSpace(v) + if v == "" { return "" } + parts := strings.Split(v, ";") + return strings.ToLower(strings.TrimSpace(parts[0])) } @@ -868,6 +971,7 @@ func contains(xs []string, target string) bool { return true } } + return false } @@ -875,5 +979,6 @@ func min(a, b int) int { if a < b { return a } + return b } diff --git a/cmd/plutia/keygen.go b/cmd/plutia/keygen.go index f2f5820..2828f60 100644 --- a/cmd/plutia/keygen.go +++ b/cmd/plutia/keygen.go @@ -27,6 +27,7 @@ func runKeygen(args []string) error { } fingerprint, err := writeMirrorPrivateKey(*out, *force, rand.Reader) + if err != nil { return err } @@ -70,16 +71,19 @@ func writeAtomicFile(path string, data []byte, force bool, mode os.FileMode) err } tmp, err := os.CreateTemp(dir, ".plutia-keygen-*") + if err != nil { return fmt.Errorf("create temp key file: %w", err) } tmpPath := tmp.Name() closed := false + defer func() { if !closed { _ = tmp.Close() } + _ = os.Remove(tmpPath) }() @@ -98,6 +102,7 @@ func writeAtomicFile(path string, data []byte, force bool, mode os.FileMode) err if err := tmp.Close(); err != nil { return fmt.Errorf("close temp key file: %w", err) } + closed = true if force { diff --git a/cmd/plutia/keygen_test.go b/cmd/plutia/keygen_test.go index 533eec3..1d30913 100644 --- a/cmd/plutia/keygen_test.go +++ b/cmd/plutia/keygen_test.go @@ -16,18 +16,20 @@ func TestWriteMirrorPrivateKey(t *testing.T) { seed := bytes.Repeat([]byte{0x42}, ed25519.SeedSize) out := filepath.Join(t.TempDir(), "mirror.key") - fingerprint, err := writeMirrorPrivateKey(out, false, bytes.NewReader(seed)) + if err != nil { t.Fatalf("write key: %v", err) } b, err := os.ReadFile(out) + if err != nil { t.Fatalf("read key file: %v", err) } expectedFile := hex.EncodeToString(seed) + "\n" + if string(b) != expectedFile { t.Fatalf("key file content mismatch: got %q want %q", string(b), expectedFile) } @@ -35,6 +37,7 @@ func TestWriteMirrorPrivateKey(t *testing.T) { pub := ed25519.NewKeyFromSeed(seed).Public().(ed25519.PublicKey) sum := sha256.Sum256(pub) expectedFP := "ed25519:" + hex.EncodeToString(sum[:8]) + if fingerprint != expectedFP { t.Fatalf("fingerprint mismatch: got %s want %s", fingerprint, expectedFP) } @@ -44,11 +47,13 @@ func TestWriteMirrorPrivateKey_NoForceRefusesOverwrite(t *testing.T) { t.Parallel() out := filepath.Join(t.TempDir(), "mirror.key") + if err := os.WriteFile(out, []byte("existing\n"), 0o600); err != nil { t.Fatalf("seed existing file: %v", err) } _, err := writeMirrorPrivateKey(out, false, bytes.NewReader(bytes.Repeat([]byte{0x01}, ed25519.SeedSize))) + if err == nil { t.Fatalf("expected overwrite refusal error") } @@ -62,21 +67,25 @@ func TestWriteMirrorPrivateKey_ForceOverwrites(t *testing.T) { t.Parallel() out := filepath.Join(t.TempDir(), "mirror.key") + if err := os.WriteFile(out, []byte("existing\n"), 0o600); err != nil { t.Fatalf("seed existing file: %v", err) } seed := bytes.Repeat([]byte{0x03}, ed25519.SeedSize) + if _, err := writeMirrorPrivateKey(out, true, bytes.NewReader(seed)); err != nil { t.Fatalf("force overwrite key: %v", err) } b, err := os.ReadFile(out) + if err != nil { t.Fatalf("read overwritten file: %v", err) } expectedFile := hex.EncodeToString(seed) + "\n" + if string(b) != expectedFile { t.Fatalf("overwritten content mismatch: got %q want %q", string(b), expectedFile) } diff --git a/internal/api/server.go b/internal/api/server.go index e9e1e15..822d865 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -469,6 +469,7 @@ func (s *Server) handleGetDIDCompatibility(w http.ResponseWriter, r *http.Reques } data, err := s.ingestor.LoadCurrentPLCData(r.Context(), did) + if err != nil { if errors.Is(err, ingest.ErrDIDNotFound) { writeCompatibilityErr(w, http.StatusNotFound, "DID not registered: "+did) @@ -488,8 +489,8 @@ func (s *Server) handleGetDIDCompatibility(w http.ResponseWriter, r *http.Reques } status := http.StatusOK - deactivated := isTombstonedDIDDocument(state.DIDDocument) + if deactivated { status = http.StatusGone } @@ -738,10 +739,11 @@ func buildPLCDIDDocument(did string, plcData map[string]any, deactivated bool) p AlsoKnownAs: extractStringArray(plcData["alsoKnownAs"]), Deactivated: deactivated, } - verificationMethods := extractVerificationMethodMap(plcData["verificationMethods"]) + if len(verificationMethods) > 0 { names := make([]string, 0, len(verificationMethods)) + for name := range verificationMethods { names = append(names, name) } @@ -749,8 +751,10 @@ func buildPLCDIDDocument(did string, plcData map[string]any, deactivated bool) p sort.Strings(names) doc.VerificationMethod = make([]plcVerificationEntry, 0, len(names)) + for _, name := range names { value := verificationMethods[name] + if strings.TrimSpace(value) == "" { continue } @@ -765,8 +769,10 @@ func buildPLCDIDDocument(did string, plcData map[string]any, deactivated bool) p } services := extractServicesMap(plcData["services"]) + if len(services) > 0 { names := make([]string, 0, len(services)) + for name := range services { names = append(names, name) } @@ -774,6 +780,7 @@ func buildPLCDIDDocument(did string, plcData map[string]any, deactivated bool) p sort.Strings(names) doc.Service = make([]plcServiceEntry, 0, len(names)) + for _, name := range names { entry := services[name] typ := entry["type"] @@ -798,8 +805,10 @@ func extractStringArray(v any) []string { switch raw := v.(type) { case []string: out := make([]string, 0, len(raw)) + for _, item := range raw { item = strings.TrimSpace(item) + if item == "" { continue } @@ -810,8 +819,10 @@ func extractStringArray(v any) []string { return out case []any: out := make([]string, 0, len(raw)) + for _, item := range raw { s, _ := item.(string) + if strings.TrimSpace(s) == "" { continue } @@ -840,6 +851,7 @@ func extractVerificationMethodMap(v any) map[string]string { case map[string]any: for name, raw := range vm { key, _ := raw.(string) + if strings.TrimSpace(key) == "" { continue } @@ -858,6 +870,7 @@ func extractServicesMap(v any) map[string]map[string]string { case map[string]map[string]string: for name, entry := range services { endpoint := strings.TrimSpace(entry["endpoint"]) + if endpoint == "" { endpoint = strings.TrimSpace(entry["serviceEndpoint"]) } @@ -874,12 +887,14 @@ func extractServicesMap(v any) map[string]map[string]string { case map[string]any: for name, raw := range services { entry, ok := raw.(map[string]any) + if !ok { continue } typ, _ := entry["type"].(string) endpoint, _ := entry["endpoint"].(string) + if endpoint == "" { endpoint, _ = entry["serviceEndpoint"].(string) } diff --git a/internal/ingest/client.go b/internal/ingest/client.go index 29457cf..ad145c3 100644 --- a/internal/ingest/client.go +++ b/internal/ingest/client.go @@ -370,7 +370,6 @@ func isSpace(b byte) bool { func (c *Client) fetchFromFile(after uint64, limit uint64) ([]types.ExportRecord, error) { path := strings.TrimPrefix(c.source, "file://") - path = filepath.Clean(path) b, err := os.ReadFile(path) diff --git a/internal/ingest/service.go b/internal/ingest/service.go index 6cb7512..a6f7cd7 100644 --- a/internal/ingest/service.go +++ b/internal/ingest/service.go @@ -1057,16 +1057,19 @@ func normalizePLCDataFromOperation(did string, op map[string]any, fallbackRotati } services := normalizeServices(op) + if len(services) > 0 { data["services"] = services } verificationMethods := normalizeVerificationMethods(op) + if len(verificationMethods) > 0 { data["verificationMethods"] = verificationMethods } rotationKeys := extractStringSlice(op["rotationKeys"]) + if len(rotationKeys) == 0 { if recovery, _ := op["recoveryKey"].(string); strings.TrimSpace(recovery) != "" { rotationKeys = append(rotationKeys, recovery) @@ -1100,11 +1103,13 @@ func normalizePLCDataFromDIDDocument(did string, doc map[string]any, fallbackRot for _, rawVM := range rawVMList { vm, ok := rawVM.(map[string]any) + if !ok { continue } name := "atproto" + if id, _ := vm["id"].(string); id != "" { if idx := strings.LastIndex(id, "#"); idx >= 0 && idx < len(id)-1 { name = id[idx+1:] @@ -1126,13 +1131,16 @@ func normalizePLCDataFromDIDDocument(did string, doc map[string]any, fallbackRot for _, rawService := range rawServiceList { service, ok := rawService.(map[string]any) + if !ok { continue } name := "atproto_pds" + if id, _ := service["id"].(string); id != "" { name = strings.TrimPrefix(id, "#") + if strings.TrimSpace(name) == "" { name = "atproto_pds" } @@ -1140,6 +1148,7 @@ func normalizePLCDataFromDIDDocument(did string, doc map[string]any, fallbackRot typ, _ := service["type"].(string) endpoint, _ := service["serviceEndpoint"].(string) + if endpoint == "" { endpoint, _ = service["endpoint"].(string) } @@ -1168,12 +1177,14 @@ func normalizeServices(op map[string]any) map[string]map[string]string { if rawServices, ok := op["services"].(map[string]any); ok { for name, rawEntry := range rawServices { entry, ok := rawEntry.(map[string]any) + if !ok { continue } typ, _ := entry["type"].(string) endpoint, _ := entry["endpoint"].(string) + if endpoint == "" { endpoint, _ = entry["serviceEndpoint"].(string) } @@ -1207,6 +1218,7 @@ func normalizeVerificationMethods(op map[string]any) map[string]string { if rawVM, ok := op["verificationMethods"].(map[string]any); ok { for name, rawValue := range rawVM { value, _ := rawValue.(string) + if strings.TrimSpace(value) == "" { continue } @@ -1228,6 +1240,7 @@ func normalizeVerificationMethods(op map[string]any) map[string]string { func extractStringSlice(v any) []string { raw, ok := v.([]any) + if !ok { return nil } @@ -1236,6 +1249,7 @@ func extractStringSlice(v any) []string { for _, item := range raw { s, _ := item.(string) + if strings.TrimSpace(s) == "" { continue } @@ -1256,6 +1270,7 @@ func dedupeStrings(input []string) []string { for _, item := range input { item = strings.TrimSpace(item) + if item == "" { continue } |