Visitar URL original
fix(knex): omit queries from patch, count and compile errors · feathersjs/feathers@7a77a0a · GitHub
Skip to content

Commit 7a77a0a

Browse files
committed
fix(knex): omit queries from patch, count and compile errors
1 parent 46c4f0e commit 7a77a0a

3 files changed

Lines changed: 101 additions & 8 deletions

File tree

‎packages/knex/src/adapter.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,9 @@ export class KnexAdapter<
199199
const data = filters.$limit === 0 ? [] : await builder.catch(errorHandler)
200200

201201
if (paginate && paginate.default) {
202-
const total = await countBuilder.then((count) => parseInt(count[0] ? count[0].total : 0))
202+
const total = await countBuilder
203+
.then((count) => parseInt(count[0] ? count[0].total : 0))
204+
.catch(errorHandler)
203205

204206
return {
205207
total,
@@ -302,7 +304,7 @@ export class KnexAdapter<
302304
}
303305
const builder = this.createQuery(updateParams)
304306

305-
await builder.update(data, [], { includeTriggerModifications: true })
307+
await builder.update(data, [], { includeTriggerModifications: true }).catch(errorHandler)
306308

307309
const items = await this._findOrGet(null, updateParams)
308310

‎packages/knex/src/error-handler.ts‎

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,39 @@ import { errors } from '@feathersjs/errors'
33
export const ERROR = Symbol.for('@feathersjs/knex/error')
44

55
/**
6-
* Knex prefixes driver errors with the query and its bound values as "<sql> - <message>",
7-
* so keep only the text after the last " - " to not send them to the client.
6+
* Knex adds the query and its bound values to errors, as "<sql> - <message>" for driver
7+
* errors and "<message> query: <sql>" for compile errors. Keep only the message so the
8+
* query is not sent to the client.
89
*/
9-
const omitQuery = (message: string) => {
10-
const index = message.lastIndexOf(' - ')
10+
const omitQuery = (error: any) => {
11+
const { message, code, sqlMessage } = error
12+
13+
if (typeof message !== 'string') {
14+
return message
15+
}
16+
17+
// MySQL drivers keep the original message
18+
if (typeof sqlMessage === 'string' && message.endsWith(sqlMessage)) {
19+
const withCode = `${code}: ${sqlMessage}`
20+
21+
return message.endsWith(withCode) ? withCode : sqlMessage
22+
}
23+
24+
if (message.startsWith('Undefined binding(s) detected')) {
25+
const index = message.indexOf(' query: ')
26+
27+
return index === -1 ? message : message.slice(0, index)
28+
}
29+
30+
// SQLite messages start with the error code
31+
const codeIndex = typeof code === 'string' ? message.lastIndexOf(` - ${code}: `) : -1
32+
const index = codeIndex !== -1 ? codeIndex : message.lastIndexOf(' - ')
1133

1234
return index === -1 ? message : message.slice(index + 3)
1335
}
1436

15-
export function errorHandler(error: any) {
16-
const message = omitQuery(error.message || '')
37+
export function errorHandler(error: any): never {
38+
const message = omitQuery(error)
1739
let feathersError = error
1840

1941
if (error.sqlState && error.sqlState.length) {

‎packages/knex/test/error-handler.test.ts‎

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,65 @@ describe('Knex Error handler', () => {
164164
)
165165
})
166166

167+
it('MySQL errors keep the driver message when values contain " - "', () => {
168+
assert.throws(
169+
() =>
170+
errorHandler({
171+
code: 'ER_DUP_ENTRY',
172+
errno: 1062,
173+
sqlState: '23000',
174+
sqlMessage: "Duplicate entry 'foo - bar' for key 'name'",
175+
message:
176+
"insert into `t` (`name`) values ('foo - bar') - ER_DUP_ENTRY: Duplicate entry 'foo - bar' for key 'name'"
177+
}),
178+
{ name: 'BadRequest', message: "ER_DUP_ENTRY: Duplicate entry 'foo - bar' for key 'name'" }
179+
)
180+
assert.throws(
181+
() =>
182+
errorHandler({
183+
code: 'ER_DUP_ENTRY',
184+
errno: 1062,
185+
sqlState: '23000',
186+
sqlMessage: "Duplicate entry 'foo - bar' for key 'name'",
187+
message:
188+
"insert into `t` (`name`) values ('foo - bar') - Duplicate entry 'foo - bar' for key 'name'"
189+
}),
190+
{ name: 'BadRequest', message: "Duplicate entry 'foo - bar' for key 'name'" }
191+
)
192+
})
193+
194+
it('SQLite errors keep the driver message when values contain " - "', () => {
195+
assert.throws(
196+
() =>
197+
errorHandler({
198+
code: 'SQLITE_CONSTRAINT',
199+
errno: 19,
200+
message:
201+
"insert into `t` (`name`) values ('a - b') - SQLITE_CONSTRAINT: UNIQUE constraint failed: t.name - x"
202+
}),
203+
{ name: 'GeneralError', message: 'SQLITE_CONSTRAINT: UNIQUE constraint failed: t.name - x' }
204+
)
205+
})
206+
207+
it('knex compile errors omit the query', () => {
208+
assert.throws(
209+
() =>
210+
errorHandler(
211+
new Error(
212+
'Undefined binding(s) detected when compiling SELECT. Undefined column(s): [users.id] query: select "users".* from "users" where "users"."id" = ?'
213+
)
214+
),
215+
{
216+
name: 'GeneralError',
217+
message: 'Undefined binding(s) detected when compiling SELECT. Undefined column(s): [users.id]'
218+
}
219+
)
220+
})
221+
222+
it('errors without a message keep the default message', () => {
223+
assert.throws(() => errorHandler({ code: 'ECONNRESET' }), { name: 'GeneralError', message: 'Error' })
224+
})
225+
167226
it('does not leak queries or bound values from a real database', async () => {
168227
const db = knex({ client: 'sqlite3', connection: { filename: ':memory:' }, useNullAsDefault: true })
169228

@@ -187,6 +246,16 @@ describe('Knex Error handler', () => {
187246
return true
188247
}
189248
)
249+
const other = await service.create({ email: 'b@example.com', password: 'hash-3' })
250+
251+
await assert.rejects(
252+
() => service.patch(other.id, { email: 'a@example.com', password: 'secret-hash-4' }),
253+
(error: any) => {
254+
assert.ok(!error.message.includes('update'))
255+
assert.ok(!error.message.includes('secret-hash-4'))
256+
return true
257+
}
258+
)
190259
await assert.rejects(
191260
() => service.find({ query: { nope: 'x' } } as any),
192261
(error: any) => {

0 commit comments

Comments
 (0)