porting all github actions from bluesky-social/indigo to tangled CI
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 261 lines 5.7 kB view raw
1package data 2 3import ( 4 "encoding" 5 "encoding/base64" 6 "fmt" 7 "reflect" 8 9 "github.com/ipfs/go-cid" 10) 11 12func parseFloat(f float64) (int64, error) { 13 if f != float64(int64(f)) { 14 return 0, fmt.Errorf("number is not a safe integer: %f", f) 15 } 16 return int64(f), nil 17} 18 19func parseAtom(atom any) (any, error) { 20 switch v := atom.(type) { 21 case nil: 22 return v, nil 23 case bool: 24 return v, nil 25 case *bool: 26 return *v, nil 27 case int64: 28 return v, nil 29 case *int64: 30 return *v, nil 31 case int: 32 return int64(v), nil 33 case *int: 34 return int64(*v), nil 35 case float64: 36 return parseFloat(v) 37 case *float64: 38 return parseFloat(*v) 39 case string: 40 if len(v) > MAX_RECORD_STRING_LEN { 41 return nil, fmt.Errorf("string too long: %d", len(v)) 42 } 43 return v, nil 44 case *string: 45 return parseAtom(*v) 46 case cid.Cid: 47 return CIDLink(v), nil 48 case *cid.Cid: 49 return CIDLink(*v), nil 50 case []byte: 51 return Bytes(v), nil 52 case *[]byte: 53 return Bytes(*v), nil 54 case []any: 55 return parseArray(v) 56 case *[]any: 57 return parseArray(*v) 58 case map[string]any: 59 return parseMap(v) 60 case *map[string]any: 61 return parseMap(*v) 62 case encoding.TextMarshaler: 63 s, err := v.MarshalText() 64 if err != nil { 65 return nil, fmt.Errorf("failed to marshal text (%s): %w", reflect.TypeOf(v), err) 66 } 67 return s, nil 68 default: 69 return nil, fmt.Errorf("unexpected type: %s", reflect.TypeOf(v)) 70 } 71} 72 73func parseArray(l []any) ([]any, error) { 74 if len(l) > MAX_CBOR_CONTAINER_LEN { 75 return nil, fmt.Errorf("data array length too long: %d", len(l)) 76 } 77 out := make([]any, len(l)) 78 for i, v := range l { 79 p, err := parseAtom(v) 80 if err != nil { 81 return nil, err 82 } 83 out[i] = p 84 } 85 return out, nil 86} 87 88func parseMap(obj map[string]any) (any, error) { 89 if len(obj) > MAX_CBOR_CONTAINER_LEN { 90 return nil, fmt.Errorf("data object has too many fields: %d", len(obj)) 91 } 92 if _, ok := obj["$link"]; ok { 93 return parseLink(obj) 94 } 95 if _, ok := obj["$bytes"]; ok { 96 return parseBytes(obj) 97 } 98 if typeVal, ok := obj["$type"]; ok { 99 if typeStr, ok := typeVal.(string); ok { 100 if typeStr == "blob" { 101 b, err := parseBlob(obj) 102 if err != nil { 103 return nil, err 104 } 105 return *b, nil 106 } 107 if len(typeStr) == 0 { 108 return nil, fmt.Errorf("$type field must contain a non-empty string") 109 } 110 } else { 111 return nil, fmt.Errorf("$type field must contain a non-empty string") 112 } 113 } 114 // legacy blob type 115 if len(obj) == 2 { 116 if _, ok := obj["mimeType"]; ok { 117 if _, ok := obj["cid"]; ok { 118 b, err := parseLegacyBlob(obj) 119 if err != nil { 120 return nil, err 121 } 122 return *b, nil 123 } 124 } 125 } 126 out := make(map[string]any, len(obj)) 127 for k, val := range obj { 128 if len(k) > MAX_OBJECT_KEY_LEN { 129 return nil, fmt.Errorf("data object key too long: %d", len(k)) 130 } 131 atom, err := parseAtom(val) 132 if err != nil { 133 return nil, err 134 } 135 out[k] = atom 136 } 137 return out, nil 138} 139 140func parseLink(obj map[string]any) (CIDLink, error) { 141 var zero CIDLink 142 if len(obj) != 1 { 143 return zero, fmt.Errorf("$link objects must have a single field") 144 } 145 v, ok := obj["$link"].(string) 146 if !ok { 147 return zero, fmt.Errorf("$link field missing or not a string") 148 } 149 c, err := cid.Parse(v) 150 if err != nil { 151 return zero, fmt.Errorf("invalid $link CID: %w", err) 152 } 153 if !c.Defined() { 154 return zero, fmt.Errorf("undefined (null) CID in $link") 155 } 156 return CIDLink(c), nil 157} 158 159func parseBytes(obj map[string]any) (Bytes, error) { 160 if len(obj) != 1 { 161 return nil, fmt.Errorf("$bytes objects must have a single field") 162 } 163 v, ok := obj["$bytes"].(string) 164 if !ok { 165 return nil, fmt.Errorf("$bytes field missing or not a string") 166 } 167 b, err := base64.RawStdEncoding.DecodeString(v) 168 if err != nil { 169 return nil, fmt.Errorf("decoding $byte value: %w", err) 170 } 171 return Bytes(b), nil 172} 173 174// NOTE: doesn't handle legacy blobs yet! 175func parseBlob(obj map[string]any) (*Blob, error) { 176 if len(obj) != 4 { 177 return nil, fmt.Errorf("blobs expected to have 4 fields") 178 } 179 if obj["$type"] != "blob" { 180 return nil, fmt.Errorf("blobs expected to have $type=blob") 181 } 182 var size int64 183 var err error 184 switch v := obj["size"].(type) { 185 case int: 186 size = int64(v) 187 case int64: 188 size = v 189 case float64: 190 size, err = parseFloat(v) 191 if err != nil { 192 return nil, err 193 } 194 default: 195 return nil, fmt.Errorf("blob 'size' missing or not a number") 196 } 197 mimeType, ok := obj["mimeType"].(string) 198 if !ok { 199 return nil, fmt.Errorf("blob 'mimeType' missing or not a string") 200 } 201 rawRef, ok := obj["ref"] 202 if !ok { 203 return nil, fmt.Errorf("blob 'ref' missing") 204 } 205 var ref CIDLink 206 switch v := rawRef.(type) { 207 case map[string]any: 208 cl, err := parseLink(v) 209 if err != nil { 210 return nil, err 211 } 212 ref = cl 213 case cid.Cid: 214 ref = CIDLink(v) 215 case CIDLink: 216 ref = v 217 default: 218 return nil, fmt.Errorf("blob 'ref' unexpected type") 219 } 220 221 return &Blob{ 222 Size: size, 223 MimeType: mimeType, 224 Ref: ref, 225 }, nil 226} 227 228func parseLegacyBlob(obj map[string]any) (*Blob, error) { 229 if len(obj) != 2 { 230 return nil, fmt.Errorf("legacy blobs expected to have 2 fields") 231 } 232 var err error 233 mimeType, ok := obj["mimeType"].(string) 234 if !ok { 235 return nil, fmt.Errorf("blob 'mimeType' missing or not a string") 236 } 237 cidStr, ok := obj["cid"] 238 if !ok { 239 return nil, fmt.Errorf("blob 'cid' missing") 240 } 241 c, err := cid.Parse(cidStr) 242 if err != nil { 243 return nil, fmt.Errorf("invalid CID: %w", err) 244 } 245 return &Blob{ 246 Size: -1, 247 MimeType: mimeType, 248 Ref: CIDLink(c), 249 }, nil 250} 251 252func parseObject(obj map[string]any) (map[string]any, error) { 253 out, err := parseMap(obj) 254 if err != nil { 255 return nil, err 256 } 257 if outObj, ok := out.(map[string]any); ok { 258 return outObj, nil 259 } 260 return nil, fmt.Errorf("top-level datum was not an object") 261}