1package data
2
3import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "io"
8
9 "github.com/ipfs/go-cid"
10 cbg "github.com/whyrusleeping/cbor-gen"
11)
12
13// Represents the "blob" type from the atproto data model.
14//
15// This struct does not get marshaled/unmarshaled directly in to JSON or CBOR; see the BlobSchema and LegacyBlobSchema structs. This is the type that should be included in golang struct definitions.
16//
17// When representing a "legacy" blob (no size field, string CID), size == -1.
18type Blob struct {
19 Ref CIDLink
20 MimeType string
21 Size int64
22}
23
24type LegacyBlobSchema struct {
25 Cid string `json:"cid" cborgen:"cid"`
26 MimeType string `json:"mimeType" cborgen:"mimeType"`
27}
28
29type BlobSchema struct {
30 LexiconTypeID string `json:"$type" cborgen:"$type,const=blob"`
31 Ref CIDLink `json:"ref" cborgen:"ref"`
32 MimeType string `json:"mimeType" cborgen:"mimeType"`
33 Size int64 `json:"size" cborgen:"size"`
34}
35
36func (b Blob) MarshalJSON() ([]byte, error) {
37 if b.Size < 0 {
38 lb := LegacyBlobSchema{
39 Cid: b.Ref.String(),
40 MimeType: b.MimeType,
41 }
42 return json.Marshal(lb)
43 } else {
44 nb := BlobSchema{
45 LexiconTypeID: "blob",
46 Ref: b.Ref,
47 MimeType: b.MimeType,
48 Size: b.Size,
49 }
50 return json.Marshal(nb)
51 }
52}
53
54func (b *Blob) UnmarshalJSON(raw []byte) error {
55 typ, err := ExtractTypeJSON(raw)
56 if err != nil {
57 return fmt.Errorf("parsing blob type: %v", err)
58 }
59
60 if typ == "blob" {
61 var bs BlobSchema
62 err := json.Unmarshal(raw, &bs)
63 if err != nil {
64 return fmt.Errorf("parsing blob JSON: %v", err)
65 }
66 b.Ref = bs.Ref
67 b.MimeType = bs.MimeType
68 b.Size = bs.Size
69 if bs.Size < 0 {
70 return fmt.Errorf("parsing blob: negative size: %d", bs.Size)
71 }
72 } else {
73 var legacy LegacyBlobSchema
74 err := json.Unmarshal(raw, &legacy)
75 if err != nil {
76 return fmt.Errorf("parsing legacy blob: %v", err)
77 }
78 refCid, err := cid.Decode(legacy.Cid)
79 if err != nil {
80 return fmt.Errorf("parsing CID in legacy blob: %v", err)
81 }
82 b.Ref = CIDLink(refCid)
83 b.MimeType = legacy.MimeType
84 b.Size = -1
85 }
86 return nil
87}
88
89func (b *Blob) MarshalCBOR(w io.Writer) error {
90 if b == nil {
91 _, err := w.Write(cbg.CborNull)
92 return err
93 }
94 if b.Size < 0 {
95 lb := LegacyBlobSchema{
96 Cid: b.Ref.String(),
97 MimeType: b.MimeType,
98 }
99 return lb.MarshalCBOR(w)
100 } else {
101 bs := BlobSchema{
102 LexiconTypeID: "blob",
103 Ref: b.Ref,
104 MimeType: b.MimeType,
105 Size: b.Size,
106 }
107 return bs.MarshalCBOR(w)
108 }
109}
110
111func (lb *Blob) UnmarshalCBOR(r io.Reader) error {
112 typ, b, err := ExtractTypeCBORReader(r)
113 if err != nil {
114 return fmt.Errorf("parsing $blob CBOR type: %w", err)
115 }
116
117 *lb = Blob{}
118 if typ == "blob" {
119 var bs BlobSchema
120 err := bs.UnmarshalCBOR(bytes.NewReader(b))
121 if err != nil {
122 return fmt.Errorf("parsing $blob CBOR: %v", err)
123 }
124 lb.Ref = bs.Ref
125 lb.MimeType = bs.MimeType
126 lb.Size = bs.Size
127 if bs.Size < 0 {
128 return fmt.Errorf("parsing $blob CBOR: negative size: %d", bs.Size)
129 }
130 } else {
131 legacy := LegacyBlobSchema{}
132 err := legacy.UnmarshalCBOR(bytes.NewReader(b))
133 if err != nil {
134 return fmt.Errorf("parsing legacy blob CBOR: %v", err)
135 }
136 refCid, err := cid.Decode(legacy.Cid)
137 if err != nil {
138 return fmt.Errorf("parsing CID in legacy blob CBOR: %v", err)
139 }
140 lb.Ref = CIDLink(refCid)
141 lb.MimeType = legacy.MimeType
142 lb.Size = -1
143 }
144
145 return nil
146}