diff --git a/pkg/appview/handlers/api.go b/pkg/appview/handlers/api.go index ff17a2d..fe40f20 100644 --- a/pkg/appview/handlers/api.go +++ b/pkg/appview/handlers/api.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "encoding/json" + "errors" "fmt" "log" "net/http" @@ -123,7 +124,7 @@ func (h *UnstarRepositoryHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque err = pdsClient.DeleteRecord(r.Context(), atproto.StarCollection, rkey) if err != nil { // If record doesn't exist, still return success (idempotent) - if err.Error() != "record not found" { + if !errors.Is(err, atproto.ErrRecordNotFound) { log.Printf("UnstarRepository: Failed to delete star record: %v", err) http.Error(w, fmt.Sprintf("Failed to delete star: %v", err), http.StatusInternalServerError) return diff --git a/pkg/atproto/client.go b/pkg/atproto/client.go index dec939c..d99e752 100644 --- a/pkg/atproto/client.go +++ b/pkg/atproto/client.go @@ -5,6 +5,7 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -13,6 +14,11 @@ import ( "github.com/bluesky-social/indigo/atproto/client" ) +// Sentinel errors +var ( + ErrRecordNotFound = errors.New("record not found") +) + // Client wraps ATProto operations for the registry type Client struct { pdsEndpoint string @@ -118,8 +124,12 @@ func (c *Client) GetRecord(ctx context.Context, collection, rkey string) (*Recor var result Record err := c.indigoClient.Get(ctx, "com.atproto.repo.getRecord", params, &result) if err != nil { - if strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "not found") { - return nil, fmt.Errorf("record not found") + // Check for RecordNotFound error from indigo's APIError type + var apiErr *client.APIError + if errors.As(err, &apiErr) { + if apiErr.StatusCode == 404 || apiErr.Name == "RecordNotFound" { + return nil, ErrRecordNotFound + } } return nil, fmt.Errorf("getRecord failed: %w", err) } @@ -148,12 +158,17 @@ func (c *Client) GetRecord(ctx context.Context, collection, rkey string) (*Recor defer resp.Body.Close() if resp.StatusCode == http.StatusNotFound { - return nil, fmt.Errorf("record not found") + return nil, ErrRecordNotFound } if resp.StatusCode != http.StatusOK { bodyBytes, _ := io.ReadAll(resp.Body) - return nil, fmt.Errorf("get record failed with status %d: %s", resp.StatusCode, string(bodyBytes)) + bodyStr := string(bodyBytes) + // Check for RecordNotFound error (PDS returns 400 with this error) + if strings.Contains(bodyStr, "RecordNotFound") { + return nil, ErrRecordNotFound + } + return nil, fmt.Errorf("get record failed with status %d: %s", resp.StatusCode, bodyStr) } var result Record diff --git a/pkg/atproto/manifest_store.go b/pkg/atproto/manifest_store.go index 895cb32..00e9db3 100644 --- a/pkg/atproto/manifest_store.go +++ b/pkg/atproto/manifest_store.go @@ -3,6 +3,7 @@ package atproto import ( "context" "encoding/json" + "errors" "fmt" "maps" "strings" @@ -47,7 +48,7 @@ func (s *ManifestStore) Exists(ctx context.Context, dgst digest.Digest) (bool, e _, err := s.client.GetRecord(ctx, ManifestCollection, rkey) if err != nil { // If not found, return false without error - if err.Error() == "record not found" { + if errors.Is(err, ErrRecordNotFound) { return false, nil } return false, err diff --git a/pkg/atproto/profile.go b/pkg/atproto/profile.go index 2b9e3f0..f3b4652 100644 --- a/pkg/atproto/profile.go +++ b/pkg/atproto/profile.go @@ -3,6 +3,7 @@ package atproto import ( "context" "encoding/json" + "errors" "fmt" ) @@ -63,28 +64,7 @@ func UpdateProfile(ctx context.Context, client *Client, profile *SailorProfileRe return nil } -// isNotFoundError checks if an error is a 404 not found error +// isNotFoundError checks if an error is a record not found error func isNotFoundError(err error) bool { - // This is a simple check - in practice, you might need to parse the error more carefully - if err == nil { - return false - } - errStr := err.Error() - return contains(errStr, "404") || contains(errStr, "not found") || contains(errStr, "RecordNotFound") -} - -// contains checks if a string contains a substring (case-insensitive helper) -func contains(s, substr string) bool { - return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && - (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || - findSubstring(s, substr))) -} - -func findSubstring(s, substr string) bool { - for i := 0; i <= len(s)-len(substr); i++ { - if s[i:i+len(substr)] == substr { - return true - } - } - return false + return errors.Is(err, ErrRecordNotFound) } diff --git a/pkg/hold/registration.go b/pkg/hold/registration.go index 8d742c3..5206ba3 100644 --- a/pkg/hold/registration.go +++ b/pkg/hold/registration.go @@ -3,6 +3,7 @@ package hold import ( "context" "encoding/json" + "errors" "fmt" "log" "net/http" @@ -288,7 +289,7 @@ func (s *HoldService) hasAllowAllCrewRecord() (bool, error) { record, err := client.GetRecord(ctx, atproto.HoldCrewCollection, "allow-all") if err != nil { // Record doesn't exist - if strings.Contains(err.Error(), "not found") { + if errors.Is(err, atproto.ErrRecordNotFound) { return false, nil } return false, fmt.Errorf("failed to get crew record: %w", err)