package atp import ( "context" "fmt" ) // Record represents a single record from a PDS. type Record struct { URI string CID string Value map[string]any } // ListResult holds a page of records and an optional cursor for the next page. type ListResult struct { Records []Record Cursor string // empty when there are no more pages } // BlobRef represents a reference to an uploaded blob. type BlobRef struct { Type string `json:"$type"` Ref CIDLink `json:"ref"` MimeType string `json:"mimeType"` Size int `json:"size"` } // CIDLink is the ref field inside a BlobRef. type CIDLink struct { Link string `json:"$link"` } // RecordFetcher can fetch a record by collection and record key. // atp.Client satisfies this interface. type RecordFetcher interface { GetRecord(ctx context.Context, collection string, rkey string) (*Record, error) } // ResolveRecord parses an AT-URI, validates the collection matches the // expected NSID, fetches the record from the PDS using the provided fetcher, // and converts it to a typed struct via the caller-supplied convert function. // Returns nil, nil if atURI is empty. func ResolveRecord[T any]( ctx context.Context, fetcher RecordFetcher, atURI string, expectedCollection string, convert func(value map[string]any, uri string) (*T, error), ) (*T, error) { if atURI == "" { return nil, nil } u, err := ParseATURI(atURI) if err != nil { return nil, err } if u.Collection != expectedCollection { return nil, fmt.Errorf("expected %s collection, got %s", expectedCollection, u.Collection) } rec, err := fetcher.GetRecord(ctx, u.Collection, u.RKey) if err != nil { return nil, fmt.Errorf("failed to fetch %s record: %w", expectedCollection, err) } result, err := convert(rec.Value, atURI) if err != nil { return nil, fmt.Errorf("failed to convert %s record: %w", expectedCollection, err) } return result, nil }