diff --git a/backend/iclouddrive/api/photos.go b/backend/iclouddrive/api/photos.go index 7b31da02b..a661b09e1 100644 --- a/backend/iclouddrive/api/photos.go +++ b/backend/iclouddrive/api/photos.go @@ -1649,6 +1649,29 @@ type ckBoolField struct { Value bool `json:"value"` } +// UnmarshalJSON parses a CloudKit boolean field, accepting both the +// boolean (true/false) and numeric (0/1) encodings the server uses +func (b *ckBoolField) UnmarshalJSON(data []byte) error { + var raw struct { + Value json.RawMessage `json:"value"` + } + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + if len(raw.Value) == 0 { + return nil + } + if err := json.Unmarshal(raw.Value, &b.Value); err == nil { + return nil + } + var n float64 + if err := json.Unmarshal(raw.Value, &n); err != nil { + return fmt.Errorf("cannot unmarshal %q as CloudKit bool", raw.Value) + } + b.Value = n != 0 + return nil +} + type ckReferenceField struct { Value struct { RecordName string `json:"recordName"` diff --git a/backend/iclouddrive/api/photos_test.go b/backend/iclouddrive/api/photos_test.go index 59ec73168..c451a2e75 100644 --- a/backend/iclouddrive/api/photos_test.go +++ b/backend/iclouddrive/api/photos_test.go @@ -1283,3 +1283,34 @@ func TestAlbumCacheKey(t *testing.T) { assert.True(t, (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f'), "key should be hex: %c", c) } } + +func TestCkBoolFieldUnmarshal(t *testing.T) { + for _, test := range []struct { + in string + want bool + wantErr bool + }{ + {`{"value": true}`, true, false}, + {`{"value": false}`, false, false}, + {`{"value": 1}`, true, false}, + {`{"value": 0}`, false, false}, + {`{"value": "INVALID"}`, false, true}, + } { + var field ckBoolField + err := json.Unmarshal([]byte(test.in), &field) + if test.wantErr { + assert.Error(t, err, test.in) + continue + } + require.NoError(t, err, test.in) + assert.Equal(t, test.want, field.Value, test.in) + } + + // isDeleted arrives as a number (0/1) on some accounts and as a + // boolean on others, so album records must parse both + var record albumRecord + err := json.Unmarshal([]byte(`{"recordName": "album1", "fields": {"isDeleted": {"value": 1}}}`), &record) + require.NoError(t, err) + require.NotNil(t, record.Fields.IsDeleted) + assert.True(t, record.Fields.IsDeleted.Value) +}