Skip to content

Commit ca85db1

Browse files
committed
Cast edgedb exclusivity violation to its own class
This makes instanceof checks easy, and provides the property and object FQN names
1 parent 729bb18 commit ca85db1

File tree

4 files changed

+89
-22
lines changed

4 files changed

+89
-22
lines changed

src/core/edgedb/edgedb.service.ts

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
33
import { $, Executor } from 'edgedb';
44
import { retry, RetryOptions } from '~/common/retry';
55
import { TypedEdgeQL } from './edgeql';
6+
import { ExclusivityViolationError } from './exclusivity-violation.error';
67
import { InlineQueryCardinalityMap } from './generated-client/inline-queries';
78
import { OptionsContext, OptionsFn } from './options.context';
89
import { Client } from './reexports';
@@ -58,29 +59,36 @@ export class EdgeDB {
5859
): Promise<R>;
5960

6061
async run(query: any, args?: any) {
61-
if (query instanceof TypedEdgeQL) {
62-
const cardinality = InlineQueryCardinalityMap.get(query.query);
63-
if (!cardinality) {
64-
throw new Error(`Query was not found from inline query generation`);
65-
}
66-
const exeMethod = cardinalityToExecutorMethod[cardinality];
62+
try {
63+
if (query instanceof TypedEdgeQL) {
64+
const cardinality = InlineQueryCardinalityMap.get(query.query);
65+
if (!cardinality) {
66+
throw new Error(`Query was not found from inline query generation`);
67+
}
68+
const exeMethod = cardinalityToExecutorMethod[cardinality];
6769

68-
return await this.executor.current[exeMethod](query.query, args);
69-
}
70+
return await this.executor.current[exeMethod](query.query, args);
71+
}
7072

71-
if (query.run) {
72-
// eslint-disable-next-line @typescript-eslint/return-await
73-
return await query.run(this.executor.current, args);
74-
}
73+
if (query.run) {
74+
// eslint-disable-next-line @typescript-eslint/return-await
75+
return await query.run(this.executor.current, args);
76+
}
7577

76-
if (typeof query === 'function') {
77-
// eslint-disable-next-line @typescript-eslint/return-await
78-
return await query(this.executor.current, args);
79-
}
78+
if (typeof query === 'function') {
79+
// eslint-disable-next-line @typescript-eslint/return-await
80+
return await query(this.executor.current, args);
81+
}
8082

81-
// For REPL, as this is untyped and assumes many/empty cardinality
82-
if (typeof query === 'string') {
83-
return await this.executor.current.query(query, args);
83+
// For REPL, as this is untyped and assumes many/empty cardinality
84+
if (typeof query === 'string') {
85+
return await this.executor.current.query(query, args);
86+
}
87+
} catch (e) {
88+
if (ExclusivityViolationError.is(e)) {
89+
throw ExclusivityViolationError.cast(e);
90+
}
91+
throw e;
8492
}
8593

8694
throw new Error('Could not figure out how to run given query');

src/core/edgedb/error.util.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { ConstraintViolationError } from 'edgedb';
1+
import { ExclusivityViolationError } from './exclusivity-violation.error';
22

33
export const isExclusivityViolation = (e: unknown, property: string) =>
4-
e instanceof ConstraintViolationError &&
5-
e.message === `${property} violates exclusivity constraint`;
4+
e instanceof ExclusivityViolationError && e.property === property;
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { ConstraintViolationError } from 'edgedb';
2+
3+
export class ExclusivityViolationError extends ConstraintViolationError {
4+
constructor(
5+
readonly objectFQN: string,
6+
readonly property: string,
7+
message?: string,
8+
options?: {
9+
cause?: unknown;
10+
},
11+
) {
12+
super(message, options);
13+
}
14+
15+
static is(e: unknown): e is ConstraintViolationError {
16+
return (
17+
e instanceof ConstraintViolationError &&
18+
(e as any)._message.endsWith(' violates exclusivity constraint')
19+
);
20+
}
21+
22+
static cast(e: ConstraintViolationError) {
23+
if (e instanceof ExclusivityViolationError) {
24+
return e;
25+
}
26+
27+
// @ts-expect-error it's a private field
28+
const message: string = e._message;
29+
// @ts-expect-error it's a private field
30+
const query: string = e._query;
31+
// @ts-expect-error it's a private field
32+
const attrs: Map<number, Uint8Array> = e._attrs;
33+
34+
const detail = new TextDecoder('utf8').decode(attrs.get(2 /* details */));
35+
const matches = detail.match(
36+
/^property '(.+)' of object type '(.+)' violates exclusivity constraint$/,
37+
);
38+
if (!matches) {
39+
throw new Error(
40+
`Could not parse exclusivity violation error; details: ${detail}`,
41+
);
42+
}
43+
const property = matches[1];
44+
const fqn = matches[2];
45+
const ex = new ExclusivityViolationError(fqn, property, message, {
46+
cause: e.cause,
47+
});
48+
49+
ex.stack = e.stack!.replace(
50+
/^ConstraintViolationError:/,
51+
'ExclusivityViolationError:',
52+
);
53+
// @ts-expect-error it's a private field
54+
ex._query = query;
55+
// @ts-expect-error it's a private field
56+
ex._attrs = attrs;
57+
return ex;
58+
}
59+
}

src/core/edgedb/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ export { edgeql, EdgeQLArgsOf, EdgeQLReturnOf } from './edgeql';
33
export * from './edgedb.service';
44
export * from './withScope';
55
export * from './error.util';
6+
export * from './exclusivity-violation.error';

0 commit comments

Comments
 (0)