Skip to content

Commit

Permalink
goat: generic record data (#751)
Browse files Browse the repository at this point in the history
Little hack to make `goat` work better with generic record data, aka
when we don't know the Lexicon. In this context, just dumping the JSON
to stdout (pretty printed).

Sharing b/c we might want to do this generally, at least for API
endpoints: `json.RawMessage` instead of `lexutil.LexiconTypeDecoder`
when we encounter `unknown` schema type. Note that this won't work for
records, and the schema rules do allow `unknown` in records, it just
hasn't happened yet.

Another place we encounter this is DID documents, which don't even have
`$type` in the JSON.
  • Loading branch information
bnewbold committed Sep 8, 2024
2 parents 0de3e30 + b712403 commit f9efd8c
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 20 deletions.
27 changes: 9 additions & 18 deletions cmd/goat/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,31 @@ package main
import (
"context"
"fmt"
"io"
"log/slog"
"net/http"

"github.com/bluesky-social/indigo/atproto/data"
"github.com/bluesky-social/indigo/atproto/identity"
"github.com/bluesky-social/indigo/atproto/syntax"
"github.com/bluesky-social/indigo/xrpc"
)

func fetchRecord(ctx context.Context, ident identity.Identity, aturi syntax.ATURI) (any, error) {
pdsURL := ident.PDSEndpoint()

slog.Debug("fetching record", "did", ident.DID.String(), "collection", aturi.Collection().String(), "rkey", aturi.RecordKey().String())
url := fmt.Sprintf("%s/xrpc/com.atproto.repo.getRecord?repo=%s&collection=%s&rkey=%s",
pdsURL, ident.DID, aturi.Collection(), aturi.RecordKey())
resp, err := http.Get(url)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("fetch failed")
xrpcc := xrpc.Client{
Host: ident.PDSEndpoint(),
}
respBytes, err := io.ReadAll(resp.Body)
resp, err := RepoGetRecord(ctx, &xrpcc, "", aturi.Collection().String(), ident.DID.String(), aturi.RecordKey().String())
if err != nil {
return nil, err
}

body, err := data.UnmarshalJSON(respBytes)
if err != nil {
return nil, err
if nil == resp.Value {
return nil, fmt.Errorf("empty record in response")
}
record, ok := body["value"].(map[string]any)
if !ok {
return nil, fmt.Errorf("fetched record was not an object")
record, err := data.UnmarshalJSON(*resp.Value)
if err != nil {
return nil, fmt.Errorf("fetched record was invalid data: %w", err)
}
return record, nil
}
4 changes: 2 additions & 2 deletions cmd/goat/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ func runRecordList(cctx *cli.Context) error {
cursor := ""
for {
// collection string, cursor string, limit int64, repo string, reverse bool, rkeyEnd string, rkeyStart string
resp, err := comatproto.RepoListRecords(ctx, &xrpcc, nsid, cursor, 100, ident.DID.String(), false, "", "")
resp, err := RepoListRecords(ctx, &xrpcc, nsid, cursor, 100, ident.DID.String(), false, "", "")
if err != nil {
return err
}
Expand Down Expand Up @@ -295,7 +295,7 @@ func runRecordUpdate(cctx *cli.Context) error {
rkey := cctx.String("rkey")

// NOTE: need to fetch existing record CID to perform swap. this is optional in theory, but golang can't deal with "optional" and "nullable", so we always need to set this (?)
existing, err := comatproto.RepoGetRecord(ctx, xrpcc, "", nsid, xrpcc.Auth.Did, rkey)
existing, err := RepoGetRecord(ctx, xrpcc, "", nsid, xrpcc.Auth.Did, rkey)
if err != nil {
return err
}
Expand Down
42 changes: 42 additions & 0 deletions cmd/goat/repogetRecord.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copied from indigo:api/atproto/repolistRecords.go

package main

// schema: com.atproto.repo.getRecord

import (
"context"
"encoding/json"

"github.com/bluesky-social/indigo/xrpc"
)

// RepoGetRecord_Output is the output of a com.atproto.repo.getRecord call.
type RepoGetRecord_Output struct {
Cid *string `json:"cid,omitempty" cborgen:"cid,omitempty"`
Uri string `json:"uri" cborgen:"uri"`
// NOTE: changed from lex decoder to json.RawMessage
Value *json.RawMessage `json:"value" cborgen:"value"`
}

// RepoGetRecord calls the XRPC method "com.atproto.repo.getRecord".
//
// cid: The CID of the version of the record. If not specified, then return the most recent version.
// collection: The NSID of the record collection.
// repo: The handle or DID of the repo.
// rkey: The Record Key.
func RepoGetRecord(ctx context.Context, c *xrpc.Client, cid string, collection string, repo string, rkey string) (*RepoGetRecord_Output, error) {
var out RepoGetRecord_Output

params := map[string]interface{}{
"cid": cid,
"collection": collection,
"repo": repo,
"rkey": rkey,
}
if err := c.Do(ctx, xrpc.Query, "", "com.atproto.repo.getRecord", params, nil, &out); err != nil {
return nil, err
}

return &out, nil
}
53 changes: 53 additions & 0 deletions cmd/goat/repolistRecords.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copied from indigo:api/atproto/repolistRecords.go

package main

// schema: com.atproto.repo.listRecords

import (
"context"
"encoding/json"

"github.com/bluesky-social/indigo/xrpc"
)

// RepoListRecords_Output is the output of a com.atproto.repo.listRecords call.
type RepoListRecords_Output struct {
Cursor *string `json:"cursor,omitempty" cborgen:"cursor,omitempty"`
Records []*RepoListRecords_Record `json:"records" cborgen:"records"`
}

// RepoListRecords_Record is a "record" in the com.atproto.repo.listRecords schema.
type RepoListRecords_Record struct {
Cid string `json:"cid" cborgen:"cid"`
Uri string `json:"uri" cborgen:"uri"`
// NOTE: changed from lex decoder to json.RawMessage
Value *json.RawMessage `json:"value" cborgen:"value"`
}

// RepoListRecords calls the XRPC method "com.atproto.repo.listRecords".
//
// collection: The NSID of the record type.
// limit: The number of records to return.
// repo: The handle or DID of the repo.
// reverse: Flag to reverse the order of the returned records.
// rkeyEnd: DEPRECATED: The highest sort-ordered rkey to stop at (exclusive)
// rkeyStart: DEPRECATED: The lowest sort-ordered rkey to start from (exclusive)
func RepoListRecords(ctx context.Context, c *xrpc.Client, collection string, cursor string, limit int64, repo string, reverse bool, rkeyEnd string, rkeyStart string) (*RepoListRecords_Output, error) {
var out RepoListRecords_Output

params := map[string]interface{}{
"collection": collection,
"cursor": cursor,
"limit": limit,
"repo": repo,
"reverse": reverse,
"rkeyEnd": rkeyEnd,
"rkeyStart": rkeyStart,
}
if err := c.Do(ctx, xrpc.Query, "", "com.atproto.repo.listRecords", params, nil, &out); err != nil {
return nil, err
}

return &out, nil
}

0 comments on commit f9efd8c

Please sign in to comment.