Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk): improved error reporting by adding detail to getConfig and getDmmf error #13736

Merged
merged 34 commits into from
Jun 24, 2022
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
df0ab4e
sdk: improved error reporting by adding detail to getConfig and getDm…
jkomyno Jun 9, 2022
2bd0e4d
Merge branch 'main' into feat/detail-errors-on-panics
jkomyno Jun 9, 2022
fac5d19
Merge branch 'main' into feat/detail-errors-on-panics
jkomyno Jun 13, 2022
993cb6c
sdk: added openssl fix proposal
jkomyno Jun 13, 2022
495cdd3
sdk: added comments to errorHelpers
jkomyno Jun 13, 2022
c0f8ddc
sdk: updated structured error output in getDmmf
jkomyno Jun 13, 2022
4f0632b
sdk: updated structured error output in getConfig
jkomyno Jun 13, 2022
5fe64b2
Merge branch 'main' into feat/detail-errors-on-panics
jkomyno Jun 13, 2022
08ebb32
Merge branch 'main' of https://github.com/prisma/prisma into feat/det…
jkomyno Jun 13, 2022
7b28482
ci: fix tests
jkomyno Jun 13, 2022
fa3be88
Merge branch 'feat/detail-errors-on-panics' of https://github.com/pri…
jkomyno Jun 13, 2022
c33e96b
sdk: add tests for loadNodeAPILibrary
jkomyno Jun 15, 2022
7c5e68c
Merge branch 'main' into feat/detail-errors-on-panics
jkomyno Jun 15, 2022
30be1be
Update packages/sdk/src/engine-commands/getConfig.ts
jkomyno Jun 17, 2022
b0c345d
Update packages/sdk/src/engine-commands/getConfig.ts
jkomyno Jun 17, 2022
3da108f
Update packages/sdk/src/engine-commands/getDmmf.ts
jkomyno Jun 17, 2022
bfee01e
Update packages/sdk/src/engine-commands/getDmmf.ts
jkomyno Jun 17, 2022
0dcc301
Merge branch 'main' into feat/detail-errors-on-panics
jkomyno Jun 17, 2022
f23287d
ci: fix tests for getDmmf
jkomyno Jun 17, 2022
2e15166
Merge branch 'main' into feat/detail-errors-on-panics
jkomyno Jun 17, 2022
37163a1
ci: fix tests
jkomyno Jun 17, 2022
05f2323
ci: fix tests
jkomyno Jun 17, 2022
09fad4d
ci: fix tests
jkomyno Jun 17, 2022
0c4d8cb
ci: fix tests
jkomyno Jun 17, 2022
bc68781
ci: fix tests
jkomyno Jun 17, 2022
fa96e90
ci: fixed getConfig tests
jkomyno Jun 17, 2022
e8d3385
Merge branch 'main' into feat/detail-errors-on-panics
jkomyno Jun 17, 2022
3a714d0
chore: merged main and fixed conflicts
jkomyno Jun 21, 2022
89aff5c
chore: simplified query-engine test assertions
jkomyno Jun 21, 2022
3278fdf
chore: fix typo
jkomyno Jun 21, 2022
9239c14
fix: tests
jkomyno Jun 21, 2022
5b30a0e
Merge branch 'main' into feat/detail-errors-on-panics
jkomyno Jun 22, 2022
f49f931
internals: updated openssl error message
jkomyno Jun 22, 2022
c67bd53
Update packages/internals/src/__tests__/engine-commands/queryEngineCo…
jkomyno Jun 23, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/client/src/__tests__/dmmf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,8 @@ describe('dmmf', () => {
await getDMMF({ datamodel })
} catch (e) {
expect(stripAnsi(e.message)).toMatchInlineSnapshot(`
Get DMMF: Schema parsing
Get DMMF: Schema parsing - Error while interacting with query-engine-node-api library
Error code: P1012
error: Error validating: You defined the enum \`PostKind\`. But the current connector does not support enums.
--> schema.prisma:14
|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ describe('generator', () => {
})
} catch (e) {
expect(stripAnsi(e.message)).toMatchInlineSnapshot(`
Get DMMF: Schema parsing
Get DMMF: Schema parsing - Error while interacting with query-engine-node-api library
Error code: P1012
error: Error validating model "public": The model name \`public\` is invalid. It is a reserved name. Please change it. Read more at https://pris.ly/d/naming-models
--> schema.prisma:10
|
Expand Down
3 changes: 3 additions & 0 deletions packages/sdk/src/engine-commands/errorHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { formatTable } from '../utils/formatTable'
import { version } from '../utils/getVersion'

/**
* Adds `Prisma CLI Version : x.x.x` at the bottom of the error output.
*/
export function addVersionDetailsToErrorMessage(message: string) {
const rows = [['Prisma CLI Version', version]]
return `${message}
Expand Down
78 changes: 62 additions & 16 deletions packages/sdk/src/engine-commands/getConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,44 @@ export type GetConfigOptions = {
retry?: number
ignoreEnvVarErrors?: boolean
}

type GetConfigErrorInit = {
// e.g., `Schema parsing - Error while interacting with query-engine-node-api library`
reason: string

// e.g., Error validating model "public": The model name \`public\` is invalid.
message: string
} & (
| {
readonly _tag: 'parsed'
jkomyno marked this conversation as resolved.
Show resolved Hide resolved

// e.g., `P1012`
errorCode?: string
}
| {
readonly _tag: 'unparsed'
jkomyno marked this conversation as resolved.
Show resolved Hide resolved
}
)

export class GetConfigError extends Error {
constructor(message: string, public readonly _error?: Error) {
super(addVersionDetailsToErrorMessage(`${chalk.redBright.bold('Get config: ')}${message}`))
constructor(params: GetConfigErrorInit) {
const headline = chalk.redBright.bold('Get Config: ')

const constructedErrorMessage = match(params)
.with({ _tag: 'parsed' }, ({ errorCode, message, reason }) => {
const errorCodeMessage = errorCode ? `Error code: ${errorCode}` : ''
return `${reason}
${errorCodeMessage}
${message}`
})
.with({ _tag: 'unparsed' }, ({ message, reason }) => {
const detailsHeader = chalk.red.bold('Details:')
return `${reason}
${detailsHeader}${message}`
})
.exhaustive()

super(addVersionDetailsToErrorMessage(`${headline}${constructedErrorMessage}`))
}
}

Expand Down Expand Up @@ -67,7 +102,7 @@ async function getConfigNodeAPI(options: GetConfigOptions) {
if (E.isLeft(preliminaryEither)) {
const { left: e } = preliminaryEither
debugErrorType(e)
throw new GetConfigError(e.reason, e.error)
throw new GetConfigError({ _tag: 'unparsed', message: e.error.message, reason: e.reason })
}
const { queryEnginePath } = preliminaryEither.right
debug(`Using CLI Query Engine (Node-API Library) at: ${queryEnginePath}`)
Expand Down Expand Up @@ -129,7 +164,7 @@ async function getConfigNodeAPI(options: GetConfigOptions) {
() => JSON.parse(errorOutput),
() => {
debug(`Coudln't apply JSON.parse to "${errorOutput}"`)
return new GetConfigError(errorOutput, e.error)
return new GetConfigError({ _tag: 'unparsed', message: errorOutput, reason: e.reason })
},
),
E.map((errorOutputAsJSON: Record<string, string>) => {
Expand All @@ -153,7 +188,10 @@ async function getConfigNodeAPI(options: GetConfigOptions) {
.otherwise((error: any) => {
return chalk.redBright(`${error.error_code}\n\n`) + error
})
return new GetConfigError(message, e.error)

const { error_code: errorCode } = errorOutputAsJSON as { error_code: string | undefined }

return new GetConfigError({ _tag: 'parsed', message, reason: e.reason, errorCode })
}),
E.getOrElseW(identity),
)
Expand All @@ -162,7 +200,7 @@ async function getConfigNodeAPI(options: GetConfigOptions) {
})
.otherwise((e) => {
debugErrorType(e)
return new GetConfigError(e.reason, e.error)
return new GetConfigError({ _tag: 'unparsed', message: e.error.message, reason: e.reason })
})

throw error
Expand All @@ -179,7 +217,7 @@ async function getConfigBinary(options: GetConfigOptions) {
if (E.isLeft(preliminaryEither)) {
const { left: e } = preliminaryEither
debugErrorType(e)
throw new GetConfigError(e.reason, e.error)
throw new GetConfigError({ _tag: 'unparsed', message: e.error.message, reason: e.reason })
}
const { queryEnginePath, tempDatamodelPath } = preliminaryEither.right
debug(`Using CLI Query Engine (Binary) at: ${queryEnginePath}`)
Expand Down Expand Up @@ -287,31 +325,39 @@ async function getConfigBinary(options: GetConfigOptions) {
() => JSON.parse(errorOutput),
() => {
debug(`Coudln't apply JSON.parse to "${errorOutput}"`)
return new GetConfigError(errorOutput, e.error)
return new GetConfigError({ _tag: 'unparsed', message: errorOutput, reason: e.reason })
},
),
E.map((errorOutputAsJSON: Record<string, string>) => {
const defaultMessage = `${chalk.redBright(errorOutputAsJSON.message)}\n`
const message = match(errorOutputAsJSON)
.with({ error_code: 'P1012' }, (error) => {
return chalk.redBright(`Schema Parsing ${error.error_code}\n\n`) + defaultMessage
const getConfigErrorInit = match(errorOutputAsJSON)
.with({ error_code: 'P1012' }, (eJSON) => {
return {
reason: `${chalk.redBright.bold('Schema parsing')} - ${e.reason}`,
errorCode: eJSON.error_code,
}
})
.with({ error_code: P.string }, (error) => {
return chalk.redBright(`${error.error_code}\n\n`) + defaultMessage
.with({ error_code: P.string }, (eJSON) => {
return {
reason: e.reason,
errorCode: eJSON.error_code,
}
})
.otherwise(() => {
return defaultMessage
return {
reason: e.reason,
}
})

return new GetConfigError(message, e.error)
return new GetConfigError({ _tag: 'parsed', message: defaultMessage, ...getConfigErrorInit })
}),
E.getOrElse(identity),
)
return actualError
})
.otherwise((e) => {
debugErrorType(e)
return new GetConfigError(e.reason, e.error)
return new GetConfigError({ _tag: 'unparsed', message: e.error.message, reason: e.reason })
})

throw error
Expand Down
74 changes: 62 additions & 12 deletions packages/sdk/src/engine-commands/getDmmf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,43 @@ export type GetDMMFOptions = {
previewFeatures?: string[]
}

type GetDmmfErrorInit = {
// e.g., `Schema parsing - Error while interacting with query-engine-node-api library`
reason: string

// e.g., Error validating model "public": The model name \`public\` is invalid.
message: string
} & (
| {
readonly _tag: 'parsed'
jkomyno marked this conversation as resolved.
Show resolved Hide resolved

// e.g., `P1012`
errorCode?: string
}
| {
readonly _tag: 'unparsed'
jkomyno marked this conversation as resolved.
Show resolved Hide resolved
}
)

export class GetDmmfError extends Error {
constructor(message: string, public readonly _error?: Error) {
super(addVersionDetailsToErrorMessage(`${chalk.redBright.bold('Get DMMF: ')}${message}`))
constructor(params: GetDmmfErrorInit) {
const headline = chalk.redBright.bold('Get DMMF: ')

const constructedErrorMessage = match(params)
.with({ _tag: 'parsed' }, ({ errorCode, message, reason }) => {
const errorCodeMessage = errorCode ? `Error code: ${errorCode}` : ''
return `${reason}
${errorCodeMessage}
${message}`
})
.with({ _tag: 'unparsed' }, ({ message, reason }) => {
const detailsHeader = chalk.red.bold('Details:')
return `${reason}
${detailsHeader}${message}`
})
.exhaustive()

super(addVersionDetailsToErrorMessage(`${headline}${constructedErrorMessage}`))
}
}

Expand Down Expand Up @@ -73,7 +107,7 @@ async function getDmmfNodeAPI(options: GetDMMFOptions) {
if (E.isLeft(preliminaryEither)) {
const { left: e } = preliminaryEither
debugErrorType(e)
throw new GetDmmfError(e.reason, e.error)
throw new GetDmmfError({ _tag: 'unparsed', message: e.error.message, reason: e.reason })
}
const { queryEnginePath } = preliminaryEither.right
debug(`Using CLI Query Engine (Node-API Library) at: ${queryEnginePath}`)
Expand Down Expand Up @@ -170,7 +204,7 @@ async function getDmmfNodeAPI(options: GetDMMFOptions) {
() => JSON.parse(errorOutput),
() => {
debug(`Coudln't apply JSON.parse to "${errorOutput}"`)
return new GetDmmfError(errorOutput, e.error)
return new GetDmmfError({ _tag: 'unparsed', message: errorOutput, reason: e.reason })
},
),
E.map((errorOutputAsJSON: Record<string, string>) => {
Expand All @@ -188,7 +222,14 @@ async function getDmmfNodeAPI(options: GetDMMFOptions) {
}

const defaultMessage = addMissingOpenSSLInfo(errorOutputAsJSON.message)
return new GetDmmfError(chalk.redBright.bold('Schema parsing\n') + defaultMessage, e.error)
const { error_code: errorCode } = errorOutputAsJSON as { error_code: string | undefined }

return new GetDmmfError({
_tag: 'parsed',
message: defaultMessage,
reason: `${chalk.redBright.bold('Schema parsing')} - ${e.reason}`,
errorCode,
})
}),
E.getOrElseW(identity),
)
Expand All @@ -197,7 +238,7 @@ async function getDmmfNodeAPI(options: GetDMMFOptions) {
})
.otherwise((e) => {
debugErrorType(e)
return new GetDmmfError(e.reason, e.error)
return new GetDmmfError({ _tag: 'unparsed', message: e.error.message, reason: e.reason })
})

throw error
Expand All @@ -214,7 +255,7 @@ async function getDmmfBinary(options: GetDMMFOptions): Promise<DMMF.Document> {
if (E.isLeft(preliminaryEither)) {
const { left: e } = preliminaryEither
debugErrorType(e)
throw new GetDmmfError(e.reason, e.error)
throw new GetDmmfError({ _tag: 'unparsed', message: e.error.message, reason: e.reason })
}
const { queryEnginePath, tempDatamodelPath } = preliminaryEither.right
debug(`Using CLI Query Engine (Binary) at: ${queryEnginePath}`)
Expand Down Expand Up @@ -363,13 +404,18 @@ async function getDmmfBinary(options: GetDMMFOptions): Promise<DMMF.Document> {
() => JSON.parse(errorOutput),
() => {
debug(`Coudln't apply JSON.parse to "${errorOutput}"`)
return new GetDmmfError(errorOutput, e.error)
return new GetDmmfError({ _tag: 'unparsed', message: errorOutput, reason: e.reason })
},
),
E.map((errorOutputAsJSON: Record<string, string>) => {
const defaultMessage = `${chalk.redBright(errorOutputAsJSON.message)}`
const message = addMissingOpenSSLInfo(defaultMessage)
return new GetDmmfError(chalk.redBright.bold('Schema parsing\n') + message, e.error)
const defaultMessage = addMissingOpenSSLInfo(`${chalk.redBright(errorOutputAsJSON.message)}`)
const { error_code: errorCode } = errorOutputAsJSON as { error_code: string | undefined }
return new GetDmmfError({
_tag: 'parsed',
message: defaultMessage,
reason: `${chalk.redBright.bold('Schema parsing')} - ${e.reason}`,
errorCode,
})
}),
E.getOrElse(identity),
)
Expand All @@ -379,7 +425,11 @@ async function getDmmfBinary(options: GetDMMFOptions): Promise<DMMF.Document> {
.with({ type: 'parse-json' }, (e) => {
debugErrorType(e)
const message = `Problem while parsing the query engine response at ${queryEnginePath}. ${e.result.stdout}\n${e.error?.stack}`
const error = new GetDmmfError(chalk.redBright.bold('JSON parsing\n') + message, e.error)
const error = new GetDmmfError({
_tag: 'unparsed',
message: message,
reason: `${chalk.redBright.bold('JSON parsing')} - ${e.reason}\n`,
})
return E.right(error)
})
.with({ type: 'retry' }, (e) => {
Expand Down
24 changes: 19 additions & 5 deletions packages/sdk/src/engine-commands/queryEngineCommons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { pipe } from 'fp-ts/lib/function'
import * as TE from 'fp-ts/TaskEither'
import fs from 'fs'
import tmpWrite from 'temp-write'
import { match } from 'ts-pattern'

import { resolveBinary } from '../resolveBinary'
import { load } from '../utils/load'
Expand Down Expand Up @@ -75,11 +76,24 @@ export function loadNodeAPILibrary(queryEnginePath: string) {
return pipe(
E.tryCatch(
() => load<NodeAPILibraryTypes.Library>(queryEnginePath),
(e) => ({
type: 'connection-error' as const,
reason: 'Unable to establish a connection to query-engine-node-api library',
error: e as Error,
}),
(e) => {
const error = e as Error
const defaultErrorMessage = `Unable to establish a connection to query-engine-node-api library.`
const proposedErrorFixMessage = match(error.message)
.when(
(errMessage) => errMessage.includes('openssl'),
jkomyno marked this conversation as resolved.
Show resolved Hide resolved
() => {
return ` Have you tried installing 'openssl'?`
jkomyno marked this conversation as resolved.
Show resolved Hide resolved
},
)
.otherwise(() => '')
const reason = `${defaultErrorMessage}${proposedErrorFixMessage}`
return {
type: 'connection-error' as const,
reason,
error,
}
},
),
TE.fromEither,
TE.map((NodeAPIQueryEngineLibrary) => ({ NodeAPIQueryEngineLibrary })),
Expand Down