Skip to content

Commit f41cc8e

Browse files
authored
Merge pull request #2252 from floccusaddon/feat/http-error-with-item-info
feat(Adapters): Add item context to HTTP errors
2 parents 371a184 + 5b470f4 commit f41cc8e

6 files changed

Lines changed: 303 additions & 129 deletions

File tree

src/errors/Error.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Bookmark, TItemLocation } from '../lib/Tree'
1+
import { Bookmark, TItem, TItemLocation } from '../lib/Tree'
22
import { statusCodes } from '../lib/statusCodes'
33

44
export class FloccusError extends Error {
@@ -160,14 +160,22 @@ export class HttpError extends TransientError {
160160
public status: number
161161
public method: string
162162
public statusMessage: string
163+
public item: TItem<TItemLocation>|undefined|null
163164

164-
constructor(status: number, method: string) {
165+
constructor(status: number, method: string, item?: TItem<TItemLocation>) {
165166
super(
166-
`E019: HTTP status ${status}. Failed ${method} request (${statusCodes[status]}). Check your server configuration and log.`
167+
`E019: HTTP status ${status}. Failed ${method} request (${statusCodes[status]})` +
168+
(item
169+
? ` for item #${item.id}[${item.title.substring(0, 100)}]${
170+
'url' in item ? `(${item.url.substring(0, 100)})` : ''
171+
} parentId: ${item.parentId}`
172+
: '') +
173+
`. Check your server configuration and log.`
167174
)
168175
this.status = status
169176
this.method = method
170177
this.statusMessage = statusCodes[status]
178+
this.item = item
171179
Object.setPrototypeOf(this, HttpError.prototype)
172180
}
173181
}

src/lib/adapters/Karakeep.ts

Lines changed: 118 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Adapter from '../interfaces/Adapter'
2-
import { Bookmark, Folder, ItemLocation } from '../Tree'
2+
import { Bookmark, Folder, ItemLocation, TItem, TItemLocation } from '../Tree'
33
import PQueue from 'p-queue'
44
import { ICapabilities, IHashSettings, IResource } from '../interfaces/Resource'
55
import Logger from '../Logger'
@@ -118,12 +118,7 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
118118
}
119119
}
120120

121-
async createBookmark(bookmark: {
122-
id: string | number
123-
url: string
124-
title: string
125-
parentId: string | number
126-
}): Promise<string | number> {
121+
async createBookmark(bookmark: Bookmark<TItemLocation>): Promise<string | number> {
127122
Logger.log('(karakeep)CREATE', { bookmark })
128123
const response = await this.sendRequest(
129124
'POST',
@@ -133,7 +128,9 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
133128
type: 'link',
134129
url: bookmark.url,
135130
title: bookmark.title,
136-
}
131+
},
132+
false,
133+
bookmark
137134
)
138135
if (response.alreadyExists) {
139136
await this.sendRequest(
@@ -142,25 +139,23 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
142139
'application/json',
143140
{
144141
title: bookmark.title,
145-
}
142+
},
143+
false,
144+
bookmark
146145
)
147146
}
148147
await this.sendRequest(
149148
'PUT',
150149
`/api/v1/lists/${bookmark.parentId}/bookmarks/${response.id}`,
151150
'application/json',
152151
undefined,
153-
/* returnRawResponse */ true
152+
true,
153+
bookmark
154154
)
155155
return `${response.id};${bookmark.parentId}`
156156
}
157157

158-
async updateBookmark(bookmark: {
159-
id: string | number
160-
url: string
161-
title: string
162-
parentId: string | number
163-
}): Promise<void> {
158+
async updateBookmark(bookmark: Bookmark<TItemLocation>): Promise<void> {
164159
Logger.log('(karakeep)UPDATE', { bookmark })
165160
const [id, oldParentId] = this.parseBookmarkId(bookmark.id)
166161
await this.sendRequest(
@@ -170,7 +165,9 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
170165
{
171166
url: bookmark.url,
172167
title: bookmark.title,
173-
}
168+
},
169+
false,
170+
bookmark
174171
)
175172

176173
if (oldParentId !== bookmark.parentId) {
@@ -180,55 +177,61 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
180177
`/api/v1/lists/${oldParentId}/bookmarks/${id}`,
181178
'application/json',
182179
undefined,
183-
/* returnRawResponse */ true
180+
true,
181+
bookmark
184182
),
185183
this.sendRequest(
186184
'PUT',
187185
`/api/v1/lists/${bookmark.parentId}/bookmarks/${id}`,
188186
'application/json',
189187
undefined,
190-
/* returnRawResponse */ true
188+
true,
189+
bookmark
191190
),
192191
])
193192
}
194193
bookmark.id = `${id};${bookmark.parentId}`
195194
}
196195

197-
async removeBookmark(bookmark: {
198-
id: string | number
199-
parentId: string | number
200-
}): Promise<void> {
196+
async removeBookmark(bookmark: Bookmark<TItemLocation>): Promise<void> {
201197
Logger.log('(karakeep)DELETE', { bookmark })
202198

203199
const [id, parentId] = this.parseBookmarkId(bookmark.id)
204200

205-
// Remove the bookmark from the list
206-
await this.sendRequest(
207-
'DELETE',
208-
`/api/v1/lists/${parentId}/bookmarks/${id}`,
209-
'application/json',
210-
undefined,
211-
/* returnRawResponse */ true
212-
)
213-
214-
// If the bookmark is not in any list, delete it from the server
215-
const bookmarkLists = await this.getListsOfBookmark(id)
216-
if (bookmarkLists.size === 0) {
201+
try {
202+
// Remove the bookmark from the list
217203
await this.sendRequest(
218204
'DELETE',
219-
`/api/v1/bookmarks/${id}`,
205+
`/api/v1/lists/${parentId}/bookmarks/${id}`,
220206
'application/json',
221207
undefined,
222-
/* returnRawResponse */ true
208+
true,
209+
bookmark
223210
)
211+
212+
// If the bookmark is not in any list, delete it from the server
213+
const bookmarkLists = await this.getListsOfBookmark(id)
214+
if (bookmarkLists.size === 0) {
215+
await this.sendRequest(
216+
'DELETE',
217+
`/api/v1/bookmarks/${id}`,
218+
'application/json',
219+
undefined,
220+
true,
221+
bookmark
222+
)
223+
}
224+
} catch (e) {
225+
if (e instanceof HttpError) {
226+
if (e.status === 404) {
227+
return
228+
}
229+
}
230+
throw e
224231
}
225232
}
226233

227-
async createFolder(folder: {
228-
id: string | number
229-
title?: string
230-
parentId: string | number
231-
}): Promise<string | number> {
234+
async createFolder(folder: Folder<TItemLocation>): Promise<string | number> {
232235
Logger.log('(karakeep)CREATEFOLDER', { folder })
233236
const response = await this.sendRequest(
234237
'POST',
@@ -239,16 +242,14 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
239242
icon: '📔',
240243
type: 'manual',
241244
parentId: folder.parentId,
242-
}
245+
},
246+
false,
247+
folder
243248
)
244249
return response.id
245250
}
246251

247-
async updateFolder(folder: {
248-
id: string | number
249-
title?: string
250-
parentId: string | number
251-
}): Promise<void> {
252+
async updateFolder(folder: Folder<TItemLocation>): Promise<void> {
252253
Logger.log('(karakeep)UPDATEFOLDER', { folder })
253254
await this.sendRequest(
254255
'PATCH',
@@ -257,14 +258,16 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
257258
{
258259
name: folder.title,
259260
parentId: folder.parentId,
260-
}
261+
},
262+
false,
263+
folder
261264
)
262265
}
263266

264267
/**
265268
* Removes the list from karakeep, but also all its content recursively
266269
*/
267-
async removeFolder(folder: { id: string | number }): Promise<void> {
270+
async removeFolder(folder: Folder<TItemLocation>): Promise<void> {
268271
Logger.log('(karakeep)DELETEFOLDER', { folder })
269272

270273
const deleteListContent = async() => {
@@ -276,17 +279,27 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
276279
'GET',
277280
`/api/v1/lists/${folder.id}/bookmarks?includeContent=false&${
278281
nextCursor ? 'cursor=' + nextCursor : ''
279-
}`
282+
}`,
283+
undefined,
284+
undefined,
285+
false,
286+
folder
280287
)
281288
nextCursor = response.nextCursor
282289
bookmarkIds.push(...response.bookmarks.map((b) => b.id))
283290
} while (nextCursor !== null)
284291
await Promise.all(
285292
bookmarkIds.map((id) =>
286-
this.removeBookmark({
287-
id: `${id};${folder.id}`,
288-
parentId: folder.id,
289-
})
293+
// create a dummy bookmark
294+
this.removeBookmark(
295+
new Bookmark({
296+
id: `${id};${folder.id}`,
297+
parentId: folder.id,
298+
url: 'about:blank',
299+
title: '',
300+
location: ItemLocation.SERVER,
301+
})
302+
)
290303
)
291304
)
292305
}
@@ -300,23 +313,37 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
300313

301314
await Promise.all(
302315
childrenListIds.map((listId) =>
303-
this.removeFolder({
316+
// create dummy folder
317+
this.removeFolder(new Folder({
304318
id: listId,
305-
})
319+
parentId: folder.id,
320+
title: '',
321+
location: ItemLocation.SERVER,
322+
}))
306323
)
307324
)
308325
}
309326

310327
await Promise.all([deleteListContent(), deleteListFolders()])
311328

312329
// Delete the list itself "after" deleting all its content in case any failure occurs in the previous steps
313-
await this.sendRequest(
314-
'DELETE',
315-
`/api/v1/lists/${folder.id}`,
316-
'application/json',
317-
undefined,
318-
/* returnRawResponse */ true
319-
)
330+
try {
331+
await this.sendRequest(
332+
'DELETE',
333+
`/api/v1/lists/${folder.id}`,
334+
'application/json',
335+
undefined,
336+
true,
337+
folder
338+
)
339+
} catch (e) {
340+
if (e instanceof HttpError) {
341+
if (e.status === 404) {
342+
return
343+
}
344+
}
345+
throw e
346+
}
320347
}
321348

322349
async getBookmarksTree(
@@ -428,7 +455,8 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
428455
relUrl: string,
429456
type: string = null,
430457
body: any = null,
431-
returnRawResponse = false
458+
returnRawResponse = false,
459+
item: TItem<TItemLocation> = null
432460
): Promise<any> {
433461
const url = this.server.url + relUrl
434462
let res
@@ -447,7 +475,7 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
447475
Logger.log(`QUEUING ${verb} ${url}`)
448476

449477
if (!IS_BROWSER) {
450-
return this.sendRequestNative(verb, url, type, body, returnRawResponse)
478+
return this.sendRequestNative(verb, url, type, body, returnRawResponse, item)
451479
}
452480

453481
try {
@@ -487,16 +515,21 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
487515
throw new RedirectError()
488516
}
489517

490-
if (returnRawResponse) {
491-
return res
492-
}
493-
494518
if (res.status === 403) {
495519
throw new AuthenticationError()
496520
}
497521
if (res.status === 503 || res.status >= 400) {
498-
throw new HttpError(res.status, verb)
522+
Logger.log(
523+
`${verb} ${url}: Server responded with ${res.status}: ` +
524+
(await res.text()).substring(0, 250)
525+
)
526+
throw new HttpError(res.status, verb, item)
527+
}
528+
529+
if (returnRawResponse) {
530+
return res
499531
}
532+
500533
let json
501534
try {
502535
json = await res.json()
@@ -512,7 +545,8 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
512545
url: string,
513546
type: string,
514547
body: any,
515-
returnRawResponse: boolean
548+
returnRawResponse: boolean,
549+
item: TItem<TItemLocation> = null
516550
) {
517551
let res
518552
let timedOut = false
@@ -561,7 +595,18 @@ export default class KarakeepAdapter implements Adapter, IResource<typeof ItemLo
561595
throw new AuthenticationError()
562596
}
563597
if (res.status === 503 || res.status >= 400) {
564-
throw new HttpError(res.status, verb)
598+
let responseData: string
599+
try {
600+
responseData =
601+
typeof res.data === 'string' ? res.data : JSON.stringify(res.data)
602+
} catch (e) {
603+
responseData = String(res.data)
604+
}
605+
Logger.log(
606+
`${verb} ${url}: Server responded with ${res.status}: ` +
607+
responseData.substring(0, 250)
608+
)
609+
throw new HttpError(res.status, verb, item)
565610
}
566611
const json = res.data
567612

0 commit comments

Comments
 (0)