Skip to content

Commit e7977f0

Browse files
Merge pull request #148 from autonomys/fix-serialize-bigint
fix: properly serialize bigint in objects
2 parents fe4d7b2 + 078673f commit e7977f0

File tree

7 files changed

+210
-8
lines changed

7 files changed

+210
-8
lines changed

.github/workflows/image-deployment.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ on:
88
required: true
99
type: choice
1010
options:
11-
- files_gateway_taurus_staging
1211
- files_gateway_mainnet_staging
13-
- files_gateway_taurus_production
1412
- files_gateway_mainnet_production
1513
gateway_image_tag:
1614
description: 'Docker image tag visit in the releases page at (https://github.com/autonomys/subspace/pkgs/container/gateway)'
@@ -53,7 +51,7 @@ jobs:
5351
echo "Using manual inputs"
5452
else
5553
# Push trigger - use defaults
56-
TARGET_MACHINES="files_gateway_taurus_staging"
54+
TARGET_MACHINES="files_gateway_mainnet_staging"
5755
GATEWAY_IMAGE_TAG="" # or set a default like "latest"
5856
INDEXER_IMAGE_TAG="" # or set a default like "latest"
5957
FILE_RETRIEVER_IMAGE_TAG="" # or set a default like "latest"

ansible/ansible.cfg

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ fact_caching = memory
77
retry_files_enabled = False
88
deprecation_warnings = False
99
callback_whitelist = profile_tasks
10-
stdout_callback = yaml
1110

1211
[ssh_connection]
1312
ssh_args = -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ControlMaster=auto -o ControlPersist=60s

ansible/auto-files-gateway-deployment.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@
6161
echo "$env_file" > {{ env_file_path }}
6262
register: env_output
6363

64+
- name: Pull docker images
65+
ansible.builtin.shell: |
66+
/usr/bin/docker compose --env-file {{ env_file_path }} -f ~/deploy/auto-files-gateway/docker/file-retriever/docker-compose.yml -f ~/deploy/auto-files-gateway/docker/file-retriever/docker-compose.prod.yml -f ~/deploy/auto-files-gateway/docker/object-mapping-indexer/docker-compose.yml -f ~/deploy/auto-files-gateway/docker/object-mapping-indexer/docker-compose.prod.yml pull
67+
6468
- name: Launch docker compose
6569
ansible.builtin.shell: |
6670
/usr/bin/docker compose --env-file {{ env_file_path }} -f ~/deploy/auto-files-gateway/docker/file-retriever/docker-compose.yml -f ~/deploy/auto-files-gateway/docker/file-retriever/docker-compose.prod.yml -f ~/deploy/auto-files-gateway/docker/object-mapping-indexer/docker-compose.yml -f ~/deploy/auto-files-gateway/docker/object-mapping-indexer/docker-compose.prod.yml up -d
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import { toSerializable } from '../src/utils/express.js'
2+
3+
describe('toSerializable', () => {
4+
describe('null and undefined handling', () => {
5+
it('should return null as-is', () => {
6+
expect(toSerializable(null)).toBeNull()
7+
})
8+
9+
it('should return undefined as-is', () => {
10+
expect(toSerializable(undefined)).toBeUndefined()
11+
})
12+
})
13+
14+
describe('primitive handling', () => {
15+
it('should return strings as-is', () => {
16+
expect(toSerializable('hello')).toBe('hello')
17+
})
18+
19+
it('should return numbers as-is', () => {
20+
expect(toSerializable(42)).toBe(42)
21+
})
22+
23+
it('should return booleans as-is', () => {
24+
expect(toSerializable(true)).toBe(true)
25+
expect(toSerializable(false)).toBe(false)
26+
})
27+
})
28+
29+
describe('bigint handling', () => {
30+
it('should convert bigint to string', () => {
31+
expect(toSerializable(BigInt(123456789))).toBe('123456789')
32+
})
33+
34+
it('should convert large bigint to string', () => {
35+
const largeBigInt = BigInt('9007199254740993')
36+
expect(toSerializable(largeBigInt)).toBe('9007199254740993')
37+
})
38+
})
39+
40+
describe('Date handling', () => {
41+
it('should convert Date to ISO string', () => {
42+
const date = new Date('2025-12-02T10:30:00.000Z')
43+
expect(toSerializable(date)).toBe('2025-12-02T10:30:00.000Z')
44+
})
45+
46+
it('should handle Date with different timezone', () => {
47+
const date = new Date(0) // Unix epoch
48+
expect(toSerializable(date)).toBe('1970-01-01T00:00:00.000Z')
49+
})
50+
})
51+
52+
describe('array handling', () => {
53+
it('should process simple arrays', () => {
54+
expect(toSerializable([1, 2, 3])).toEqual([1, 2, 3])
55+
})
56+
57+
it('should recursively process array elements', () => {
58+
const date = new Date('2025-12-02T10:30:00.000Z')
59+
const input = [BigInt(123), date, 'hello']
60+
expect(toSerializable(input)).toEqual([
61+
'123',
62+
'2025-12-02T10:30:00.000Z',
63+
'hello',
64+
])
65+
})
66+
67+
it('should handle nested arrays', () => {
68+
const input = [[BigInt(1)], [BigInt(2)]]
69+
expect(toSerializable(input)).toEqual([['1'], ['2']])
70+
})
71+
})
72+
73+
describe('object handling', () => {
74+
it('should process simple objects', () => {
75+
expect(toSerializable({ a: 1, b: 2 })).toEqual({ a: 1, b: 2 })
76+
})
77+
78+
it('should recursively process object values', () => {
79+
const date = new Date('2025-12-02T10:30:00.000Z')
80+
const input = {
81+
size: BigInt(1024),
82+
timestamp: date,
83+
name: 'test',
84+
}
85+
expect(toSerializable(input)).toEqual({
86+
size: '1024',
87+
timestamp: '2025-12-02T10:30:00.000Z',
88+
name: 'test',
89+
})
90+
})
91+
92+
it('should handle nested objects', () => {
93+
const input = {
94+
outer: {
95+
inner: {
96+
value: BigInt(42),
97+
},
98+
},
99+
}
100+
expect(toSerializable(input)).toEqual({
101+
outer: {
102+
inner: {
103+
value: '42',
104+
},
105+
},
106+
})
107+
})
108+
})
109+
110+
describe('complex nested structures', () => {
111+
it('should handle ExtendedIPLDMetadata-like structure', () => {
112+
const input = {
113+
cid: 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi',
114+
blockHeight: 12345,
115+
blockHash: '0xabc123',
116+
extrinsicId: 'ext-001',
117+
extrinsicHash: '0xdef456',
118+
indexInBlock: 0,
119+
links: ['link1', 'link2'],
120+
blake3Hash: 'hash123',
121+
timestamp: new Date('2025-12-02T10:30:00.000Z'),
122+
size: BigInt('1048576'),
123+
type: 'file',
124+
}
125+
126+
const result = toSerializable(input)
127+
128+
expect(result).toEqual({
129+
cid: 'bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi',
130+
blockHeight: 12345,
131+
blockHash: '0xabc123',
132+
extrinsicId: 'ext-001',
133+
extrinsicHash: '0xdef456',
134+
indexInBlock: 0,
135+
links: ['link1', 'link2'],
136+
blake3Hash: 'hash123',
137+
timestamp: '2025-12-02T10:30:00.000Z',
138+
size: '1048576',
139+
type: 'file',
140+
})
141+
})
142+
143+
it('should handle array of objects with mixed types', () => {
144+
const input = [
145+
{
146+
id: 1,
147+
value: BigInt(100),
148+
created: new Date('2025-01-01T00:00:00Z'),
149+
},
150+
{
151+
id: 2,
152+
value: BigInt(200),
153+
created: new Date('2025-01-02T00:00:00Z'),
154+
},
155+
]
156+
157+
expect(toSerializable(input)).toEqual([
158+
{ id: 1, value: '100', created: '2025-01-01T00:00:00.000Z' },
159+
{ id: 2, value: '200', created: '2025-01-02T00:00:00.000Z' },
160+
])
161+
})
162+
163+
it('should handle objects with array properties containing special types', () => {
164+
const input = {
165+
timestamps: [
166+
new Date('2025-01-01T00:00:00Z'),
167+
new Date('2025-01-02T00:00:00Z'),
168+
],
169+
sizes: [BigInt(100), BigInt(200)],
170+
}
171+
172+
expect(toSerializable(input)).toEqual({
173+
timestamps: ['2025-01-01T00:00:00.000Z', '2025-01-02T00:00:00.000Z'],
174+
sizes: ['100', '200'],
175+
})
176+
})
177+
})
178+
})

services/file-retriever/src/http/controllers/file.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { authMiddleware } from '../middlewares/auth.js'
33
import { fileComposer } from '../../services/fileComposer.js'
44
import { pipeline } from 'stream'
55
import { logger } from '../../drivers/logger.js'
6-
import { asyncSafeHandler } from '../../utils/express.js'
6+
import { asyncSafeHandler, toSerializable } from '../../utils/express.js'
77
import { uniqueHeaderValue } from '../../utils/http.js'
88
import { HttpError } from '../middlewares/error.js'
99
import { dsnFetcher } from '../../services/dsnFetcher.js'
@@ -28,7 +28,7 @@ fileRouter.get(
2828
}
2929

3030
const metadata = await dsnFetcher.fetchNodeMetadata(cid)
31-
res.status(200).json(metadata)
31+
res.status(200).json(toSerializable(metadata))
3232
}),
3333
)
3434

services/file-retriever/src/http/controllers/node.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Router } from 'express'
22
import { dsnFetcher } from '../../services/dsnFetcher.js'
3-
import { asyncSafeHandler } from '../../utils/express.js'
3+
import { asyncSafeHandler, toSerializable } from '../../utils/express.js'
44
import { safeIPLDDecode } from '../../utils/dagData.js'
55

66
const nodeRouter = Router()
@@ -23,7 +23,7 @@ nodeRouter.get(
2323

2424
const ipldNode = safeIPLDDecode(node)
2525

26-
res.json(ipldNode)
26+
res.json(toSerializable(ipldNode))
2727
}),
2828
)
2929

services/file-retriever/src/utils/express.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,26 @@ export const asyncSafeHandler = (
1111
}
1212
}
1313
}
14+
15+
export const toSerializable = <T>(obj: T): T => {
16+
if (obj === null || obj === undefined) {
17+
return obj
18+
}
19+
if (typeof obj === 'bigint') {
20+
return obj.toString() as unknown as T
21+
}
22+
if (Array.isArray(obj)) {
23+
return obj.map(toSerializable) as unknown as T
24+
}
25+
if (obj instanceof Date) {
26+
return obj.toISOString() as unknown as T
27+
}
28+
if (typeof obj === 'object') {
29+
const result: Record<string, unknown> = {}
30+
for (const [key, value] of Object.entries(obj)) {
31+
result[key] = toSerializable(value)
32+
}
33+
return result as T
34+
}
35+
return obj
36+
}

0 commit comments

Comments
 (0)