Skip to content

Commit 8fd603b

Browse files
fix: getProviders recognizes application/json accept header (#155)
* modified-contentType&-added-testcases * fix: explicit Content-Type null check * fix-eslint-issues * fixes-&-improve-code * fix: cache ok responses * chore: lint fix * chore: reduce number of changes * chore: revert response cache change * chore: remove unused package --------- Co-authored-by: Russell Dempsey <[email protected]>
1 parent 6ce93f2 commit 8fd603b

File tree

3 files changed

+94
-12
lines changed

3 files changed

+94
-12
lines changed

packages/client/.aegir.js

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,55 @@ const options = {
1414
const echo = new EchoServer()
1515
echo.polka.use(body.raw({ type: 'application/vnd.ipfs.ipns-record'}))
1616
echo.polka.use(body.text())
17+
echo.polka.use(body.json())
1718
echo.polka.use((req, res, next) => {
1819
next()
1920
lastCalledUrl = req.url
2021
})
2122
echo.polka.post('/add-providers/:cid', (req, res) => {
2223
callCount++
23-
providers.set(req.params.cid, req.body)
24-
res.end()
24+
try {
25+
26+
let data
27+
try {
28+
// when passed data from a test where `body=providers.map(prov => JSON.stringify(prov)).join('\n')`
29+
data = { Providers: req.body.split('\n').map(line => JSON.parse(line)) }
30+
} catch (err) {
31+
// when passed data from a test where `body=JSON.stringify({ Providers: providers })`
32+
data = req.body
33+
}
34+
35+
providers.set(req.params.cid, data)
36+
res.end(JSON.stringify({ success: true }))
37+
} catch (err) {
38+
console.error('Error in add-providers:', err)
39+
res.statusCode = 400
40+
res.end(JSON.stringify({
41+
error: err.message,
42+
code: 'ERR_INVALID_INPUT'
43+
}))
44+
providers.delete(req.params.cid)
45+
}
2546
})
2647
echo.polka.get('/routing/v1/providers/:cid', (req, res) => {
2748
callCount++
28-
const records = providers.get(req.params.cid) ?? '[]'
29-
providers.delete(req.params.cid)
30-
res.end(records)
49+
try {
50+
const providerData = providers.get(req.params.cid) || { Providers: [] }
51+
const acceptHeader = req.headers.accept
52+
53+
if (acceptHeader?.includes('application/x-ndjson')) {
54+
res.setHeader('Content-Type', 'application/x-ndjson')
55+
const providers = Array.isArray(providerData.Providers) ? providerData.Providers : []
56+
res.end(providers.map(p => JSON.stringify(p)).join('\n'))
57+
} else {
58+
res.setHeader('Content-Type', 'application/json')
59+
res.end(JSON.stringify(providerData))
60+
}
61+
} catch (err) {
62+
console.error('Error in get providers:', err)
63+
res.statusCode = 500
64+
res.end(JSON.stringify({ error: err.message }))
65+
}
3166
})
3267
echo.polka.post('/add-peers/:peerId', (req, res) => {
3368
callCount++

packages/client/src/client.ts

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,24 +122,34 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
122122
const getOptions = { headers: { Accept: 'application/x-ndjson' }, signal }
123123
const res = await this.#makeRequest(url.toString(), getOptions)
124124

125-
if (res.status === 404) {
125+
if (res == null) {
126+
throw new BadResponseError('No response received')
127+
}
128+
if (!res.ok) {
129+
if (res.status === 404) {
126130
// https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes
127131
// 404 (Not Found): must be returned if no matching records are found
128-
throw new NotFoundError('No matching records found')
129-
}
132+
throw new NotFoundError('No matching records found')
133+
}
130134

131-
if (res.status === 422) {
135+
if (res.status === 422) {
132136
// https://specs.ipfs.tech/routing/http-routing-v1/#response-status-codes
133137
// 422 (Unprocessable Entity): request does not conform to schema or semantic constraints
134-
throw new InvalidRequestError('Request does not conform to schema or semantic constraints')
138+
throw new InvalidRequestError('Request does not conform to schema or semantic constraints')
139+
}
140+
throw new BadResponseError(`Unexpected status code: ${res.status}`)
135141
}
136142

137143
if (res.body == null) {
138144
throw new BadResponseError('Routing response had no body')
139145
}
140146

141147
const contentType = res.headers.get('Content-Type')
142-
if (contentType === 'application/json') {
148+
if (contentType == null) {
149+
throw new BadResponseError('No Content-Type header received')
150+
}
151+
152+
if (contentType?.startsWith('application/json')) {
143153
const body = await res.json()
144154

145155
for (const provider of body.Providers) {
@@ -148,13 +158,15 @@ export class DefaultDelegatedRoutingV1HttpApiClient implements DelegatedRoutingV
148158
yield record
149159
}
150160
}
151-
} else {
161+
} else if (contentType.includes('application/x-ndjson')) {
152162
for await (const provider of ndjson(toIt(res.body))) {
153163
const record = this.#conformToPeerSchema(provider)
154164
if (record != null) {
155165
yield record
156166
}
157167
}
168+
} else {
169+
throw new BadResponseError(`Unsupported Content-Type: ${contentType}`)
158170
}
159171
} catch (err) {
160172
log.error('getProviders errored:', err)

packages/client/test/index.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,41 @@ describe('delegated-routing-v1-http-api-client', () => {
6565
})))
6666
})
6767

68+
it('should handle different Content-Type headers for JSON responses', async () => {
69+
const providers = [{
70+
Protocol: 'transport-bitswap',
71+
Schema: 'bitswap',
72+
Metadata: 'gBI=',
73+
ID: (await generateKeyPair('Ed25519')).publicKey.toString(),
74+
Addrs: ['/ip4/41.41.41.41/tcp/1234']
75+
}]
76+
77+
const cid = CID.parse('QmUNLLsPACCz1vLxQVkXqqLX5R1X345qqfHbsf67hvA3Nn')
78+
const contentTypes = [
79+
'application/json',
80+
'application/json; charset=utf-8',
81+
'application/json;charset=UTF-8'
82+
]
83+
84+
for (const contentType of contentTypes) {
85+
// Add providers with proper payload structure
86+
await fetch(`${process.env.ECHO_SERVER}/add-providers/${cid.toString()}`, {
87+
method: 'POST',
88+
headers: {
89+
'Content-Type': contentType
90+
},
91+
body: JSON.stringify({ Providers: providers })
92+
})
93+
94+
await new Promise((resolve) => setTimeout(resolve, 100))
95+
const provs = await all(client.getProviders(cid))
96+
97+
expect(provs).to.have.lengthOf(1, `Failed for Content-Type: ${contentType}`)
98+
expect(provs[0].ID.toString()).to.equal(providers[0].ID)
99+
expect(provs[0].Addrs[0].toString()).to.equal(providers[0].Addrs[0])
100+
}
101+
})
102+
68103
it('should add filter parameters the query of the request url', async () => {
69104
const providers = [{
70105
Protocol: 'transport-bitswap',

0 commit comments

Comments
 (0)