Skip to content

Commit 1025d3a

Browse files
authored
Merge pull request #6779 from neo4j/neo4j-driver-6
Update parser for ISO durations
2 parents 5ec27ab + 6df6ea3 commit 1025d3a

File tree

16 files changed

+173
-349
lines changed

16 files changed

+173
-349
lines changed

.changeset/wet-nails-cheat.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@neo4j/graphql": patch
3+
---
4+
5+
Duration parser changed to support ISO strings ending in T, for example: "P-238DT"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"eslint-plugin-jest": "29.0.1",
3939
"graphql": "16.11.0",
4040
"jest": "30.2.0",
41-
"neo4j-driver": "5.28.2",
41+
"neo4j-driver": "6.0.0",
4242
"prettier": "3.6.2",
4343
"prettier-2": "npm:[email protected]",
4444
"set-tz": "0.2.0",

packages/graphql/src/graphql/scalars/Duration.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* limitations under the License.
1818
*/
1919

20-
import { parseDuration, MONTHS_PER_YEAR, DAYS_PER_WEEK, SECONDS_PER_HOUR, SECONDS_PER_MINUTE } from "./Duration";
20+
import { DAYS_PER_WEEK, MONTHS_PER_YEAR, parseDuration, SECONDS_PER_HOUR, SECONDS_PER_MINUTE } from "./Duration";
2121

2222
type ParsedDuration = ReturnType<typeof parseDuration>;
2323

@@ -56,6 +56,9 @@ describe("Duration Scalar", () => {
5656
],
5757
["P18870605T120000", { months: 1887 * MONTHS_PER_YEAR + 6, days: 5, seconds: 12 * 60 * 60, nanoseconds: 0 }],
5858
["P1887-06-05T12:00:00", { months: 1887 * 12 + 6, days: 5, seconds: 12 * 60 * 60, nanoseconds: 0 }],
59+
["P-238DT", { months: 0, days: -238, seconds: 0, nanoseconds: 0 }],
60+
["P0M-238DT0S", { months: 0, days: -238, seconds: 0, nanoseconds: 0 }],
61+
["PT0S", { months: 0, days: 0, seconds: 0, nanoseconds: 0 }],
5962
])("should match and parse %s correctly", (duration, parsed) =>
6063
expect(parseDuration(duration)).toStrictEqual(parsed)
6164
);

packages/graphql/src/graphql/scalars/Duration.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import neo4j, { isDuration } from "neo4j-driver";
2626
// Similar constraint allows for only decimal seconds on date time based duration
2727

2828
const DURATION_REGEX_ISO =
29-
/^(?<negated>-?)?P(?!$)(?:(?<years>-?\d+(?:\.\d+(?=Y$))?)Y)?(?:(?<months>-?\d+(?:\.\d+(?=M$))?)M)?(?:(?<weeks>-?(5[0-3]|[1-4][0-9]|[1-9])(?:\.\d+(?=W$))?)W)?(?:(?<days>-?\d+(?:\.\d+(?=D$))?)D)?(?:T(?!$)(?:(?<hours>-?\d+(?:\.\d+(?=H$))?)H)?(?:(?<minutes>-?\d+(?:\.\d+(?=M$))?)M)?(?:(?<seconds>-?\d+(?:\.\d+(?=S$))?)S)?)?$/;
29+
/^(?<negated>-?)?P(?!$)(?:(?<years>-?\d+(?:\.\d+(?=Y$))?)Y)?(?:(?<months>-?\d+(?:\.\d+(?=M$))?)M)?(?:(?<weeks>-?(5[0-3]|[1-4][0-9]|[1-9])(?:\.\d+(?=W$))?)W)?(?:(?<days>-?\d+(?:\.\d+(?=D$))?)D)?(?:T(?:(?<hours>-?\d+(?:\.\d+(?=H$))?)H)?(?:(?<minutes>-?\d+(?:\.\d+(?=M$))?)M)?(?:(?<seconds>-?\d+(?:\.\d+(?=S$))?)S)?)?$/;
3030

3131
const DURATION_REGEX_WITH_DELIMITERS =
3232
/^(?<negated>-?)?P(?<years>\d{4})-(?<months>\d{2})-(?<days>\d{2})T(?<hours>\d{2}):(?<minutes>\d{2}):(?<seconds>\d{2})/;
@@ -58,7 +58,7 @@ export const parseDuration = (
5858
const matchNoDelimiter = DURATION_REGEX_NO_DELIMITERS.exec(value);
5959

6060
const match = matchIso || matchDelimiter || matchNoDelimiter;
61-
if (!match) {
61+
if (!match || value === "PT") {
6262
throw new TypeError(`Value must be formatted as Duration: ${value}`);
6363
}
6464

@@ -107,8 +107,6 @@ export const parseDuration = (
107107

108108
// Whether total duration is negative
109109
const coefficient = negated ? -1 : 1;
110-
// coefficient of duration and % may negate zero: converts -0 -> 0
111-
const unsignZero = (a: number) => (Object.is(a, -0) ? 0 : a);
112110

113111
return {
114112
months: unsignZero(coefficient * wholeMonths),
@@ -118,7 +116,12 @@ export const parseDuration = (
118116
};
119117
};
120118

121-
const parse = (value: unknown) => {
119+
/** Converts -0 to 0, returns the number otherwise */
120+
function unsignZero(a: number): number {
121+
return a || 0;
122+
}
123+
124+
function parse(value: unknown) {
122125
if (typeof value === "string") {
123126
const { months, days, seconds, nanoseconds } = parseDuration(value);
124127

@@ -130,7 +133,7 @@ const parse = (value: unknown) => {
130133
}
131134

132135
throw new GraphQLError(`Only string or Duration can be validated as Duration, but received: ${value}`);
133-
};
136+
}
134137

135138
export const GraphQLDuration = new GraphQLScalarType({
136139
name: "Duration",

packages/graphql/tests/integration/array-methods/array-push.int.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,20 @@ describe("array-push", () => {
110110
{
111111
description: "a single Duration element",
112112
inputType: "Duration",
113-
inputValue: `"P2Y"`,
114-
expectedOutputValue: ["P24M0DT0S"],
113+
inputValue: `"P2MT10S"`,
114+
expectedOutputValue: ["P2MT10S"],
115115
},
116116
{
117117
description: "a single Duration element in an array",
118118
inputType: "Duration",
119-
inputValue: `["P2Y"]`,
120-
expectedOutputValue: ["P24M0DT0S"],
119+
inputValue: `["P2MT10S"]`,
120+
expectedOutputValue: ["P2MT10S"],
121121
},
122122
{
123123
description: "multiple Duration elements",
124124
inputType: "Duration",
125-
inputValue: `["P2Y", "P2Y"]`,
126-
expectedOutputValue: ["P24M0DT0S", "P24M0DT0S"],
125+
inputValue: `["P2MT10S", "P2MT10S"]`,
126+
expectedOutputValue: ["P2MT10S", "P2MT10S"],
127127
},
128128
{
129129
description: "a single Date element",

packages/graphql/tests/integration/directives/populatedBy/populatedBy-node-properties.int.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2185,7 +2185,7 @@ describe("@populatedBy directive - Node properties", () => {
21852185
test("Should use on CREATE", async () => {
21862186
const testMovie = testHelper.createUniqueType("Movie");
21872187

2188-
const duration = `P14M3DT14700S`;
2188+
const duration = `P1Y2M3DT4H5M`;
21892189

21902190
const callback = () => Promise.resolve(duration);
21912191

@@ -2238,7 +2238,7 @@ describe("@populatedBy directive - Node properties", () => {
22382238
test("Should use on UPDATE", async () => {
22392239
const testMovie = testHelper.createUniqueType("Movie");
22402240

2241-
const duration = `P14M3DT14700S`;
2241+
const duration = `P1Y2M3DT4H5M`;
22422242

22432243
const callback = () => Promise.resolve(duration);
22442244

packages/graphql/tests/integration/directives/populatedBy/populatedBy-relationship-properties.int.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2458,8 +2458,8 @@ describe("@populatedBy directive - Relationship properties", () => {
24582458
{
24592459
description: "@populatedBy - Duration",
24602460
type: "Duration",
2461-
callback: () => Promise.resolve(`P14M3DT14700S`),
2462-
expectedValue: `P14M3DT14700S`,
2461+
callback: () => Promise.resolve(`P1Y2M3DT4H5M`),
2462+
expectedValue: `P1Y2M3DT4H5M`,
24632463
},
24642464
])("$description", ({ type, callback, expectedValue, expectedValueTemp }) => {
24652465
test("Should use on CREATE", async () => {

packages/graphql/tests/tck/aggregations/where/edge/duration.test.ts

Lines changed: 30 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
7474
\\"param0\\": {
7575
\\"months\\": 12,
7676
\\"days\\": 0,
77-
\\"seconds\\": {
78-
\\"low\\": 0,
79-
\\"high\\": 0
80-
},
81-
\\"nanoseconds\\": {
82-
\\"low\\": 0,
83-
\\"high\\": 0
84-
}
77+
\\"seconds\\": 0,
78+
\\"nanoseconds\\": 0
8579
}
8680
}"
8781
`);
@@ -115,14 +109,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
115109
\\"param0\\": {
116110
\\"months\\": 12,
117111
\\"days\\": 0,
118-
\\"seconds\\": {
119-
\\"low\\": 0,
120-
\\"high\\": 0
121-
},
122-
\\"nanoseconds\\": {
123-
\\"low\\": 0,
124-
\\"high\\": 0
125-
}
112+
\\"seconds\\": 0,
113+
\\"nanoseconds\\": 0
126114
}
127115
}"
128116
`);
@@ -156,14 +144,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
156144
\\"param0\\": {
157145
\\"months\\": 12,
158146
\\"days\\": 0,
159-
\\"seconds\\": {
160-
\\"low\\": 0,
161-
\\"high\\": 0
162-
},
163-
\\"nanoseconds\\": {
164-
\\"low\\": 0,
165-
\\"high\\": 0
166-
}
147+
\\"seconds\\": 0,
148+
\\"nanoseconds\\": 0
167149
}
168150
}"
169151
`);
@@ -197,14 +179,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
197179
\\"param0\\": {
198180
\\"months\\": 12,
199181
\\"days\\": 0,
200-
\\"seconds\\": {
201-
\\"low\\": 0,
202-
\\"high\\": 0
203-
},
204-
\\"nanoseconds\\": {
205-
\\"low\\": 0,
206-
\\"high\\": 0
207-
}
182+
\\"seconds\\": 0,
183+
\\"nanoseconds\\": 0
208184
}
209185
}"
210186
`);
@@ -238,14 +214,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
238214
\\"param0\\": {
239215
\\"months\\": 12,
240216
\\"days\\": 0,
241-
\\"seconds\\": {
242-
\\"low\\": 0,
243-
\\"high\\": 0
244-
},
245-
\\"nanoseconds\\": {
246-
\\"low\\": 0,
247-
\\"high\\": 0
248-
}
217+
\\"seconds\\": 0,
218+
\\"nanoseconds\\": 0
249219
}
250220
}"
251221
`);
@@ -279,14 +249,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
279249
\\"param0\\": {
280250
\\"months\\": 12,
281251
\\"days\\": 0,
282-
\\"seconds\\": {
283-
\\"low\\": 0,
284-
\\"high\\": 0
285-
},
286-
\\"nanoseconds\\": {
287-
\\"low\\": 0,
288-
\\"high\\": 0
289-
}
252+
\\"seconds\\": 0,
253+
\\"nanoseconds\\": 0
290254
}
291255
}"
292256
`);
@@ -320,14 +284,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
320284
\\"param0\\": {
321285
\\"months\\": 12,
322286
\\"days\\": 0,
323-
\\"seconds\\": {
324-
\\"low\\": 0,
325-
\\"high\\": 0
326-
},
327-
\\"nanoseconds\\": {
328-
\\"low\\": 0,
329-
\\"high\\": 0
330-
}
287+
\\"seconds\\": 0,
288+
\\"nanoseconds\\": 0
331289
}
332290
}"
333291
`);
@@ -361,14 +319,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
361319
\\"param0\\": {
362320
\\"months\\": 12,
363321
\\"days\\": 0,
364-
\\"seconds\\": {
365-
\\"low\\": 0,
366-
\\"high\\": 0
367-
},
368-
\\"nanoseconds\\": {
369-
\\"low\\": 0,
370-
\\"high\\": 0
371-
}
322+
\\"seconds\\": 0,
323+
\\"nanoseconds\\": 0
372324
}
373325
}"
374326
`);
@@ -402,14 +354,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
402354
\\"param0\\": {
403355
\\"months\\": 12,
404356
\\"days\\": 0,
405-
\\"seconds\\": {
406-
\\"low\\": 0,
407-
\\"high\\": 0
408-
},
409-
\\"nanoseconds\\": {
410-
\\"low\\": 0,
411-
\\"high\\": 0
412-
}
357+
\\"seconds\\": 0,
358+
\\"nanoseconds\\": 0
413359
}
414360
}"
415361
`);
@@ -443,14 +389,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
443389
\\"param0\\": {
444390
\\"months\\": 12,
445391
\\"days\\": 0,
446-
\\"seconds\\": {
447-
\\"low\\": 0,
448-
\\"high\\": 0
449-
},
450-
\\"nanoseconds\\": {
451-
\\"low\\": 0,
452-
\\"high\\": 0
453-
}
392+
\\"seconds\\": 0,
393+
\\"nanoseconds\\": 0
454394
}
455395
}"
456396
`);
@@ -484,14 +424,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
484424
\\"param0\\": {
485425
\\"months\\": 12,
486426
\\"days\\": 0,
487-
\\"seconds\\": {
488-
\\"low\\": 0,
489-
\\"high\\": 0
490-
},
491-
\\"nanoseconds\\": {
492-
\\"low\\": 0,
493-
\\"high\\": 0
494-
}
427+
\\"seconds\\": 0,
428+
\\"nanoseconds\\": 0
495429
}
496430
}"
497431
`);
@@ -525,14 +459,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
525459
\\"param0\\": {
526460
\\"months\\": 12,
527461
\\"days\\": 0,
528-
\\"seconds\\": {
529-
\\"low\\": 0,
530-
\\"high\\": 0
531-
},
532-
\\"nanoseconds\\": {
533-
\\"low\\": 0,
534-
\\"high\\": 0
535-
}
462+
\\"seconds\\": 0,
463+
\\"nanoseconds\\": 0
536464
}
537465
}"
538466
`);
@@ -566,14 +494,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
566494
\\"param0\\": {
567495
\\"months\\": 12,
568496
\\"days\\": 0,
569-
\\"seconds\\": {
570-
\\"low\\": 0,
571-
\\"high\\": 0
572-
},
573-
\\"nanoseconds\\": {
574-
\\"low\\": 0,
575-
\\"high\\": 0
576-
}
497+
\\"seconds\\": 0,
498+
\\"nanoseconds\\": 0
577499
}
578500
}"
579501
`);
@@ -607,14 +529,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
607529
\\"param0\\": {
608530
\\"months\\": 12,
609531
\\"days\\": 0,
610-
\\"seconds\\": {
611-
\\"low\\": 0,
612-
\\"high\\": 0
613-
},
614-
\\"nanoseconds\\": {
615-
\\"low\\": 0,
616-
\\"high\\": 0
617-
}
532+
\\"seconds\\": 0,
533+
\\"nanoseconds\\": 0
618534
}
619535
}"
620536
`);
@@ -648,14 +564,8 @@ describe("Cypher Aggregations where edge with Duration", () => {
648564
\\"param0\\": {
649565
\\"months\\": 12,
650566
\\"days\\": 0,
651-
\\"seconds\\": {
652-
\\"low\\": 0,
653-
\\"high\\": 0
654-
},
655-
\\"nanoseconds\\": {
656-
\\"low\\": 0,
657-
\\"high\\": 0
658-
}
567+
\\"seconds\\": 0,
568+
\\"nanoseconds\\": 0
659569
}
660570
}"
661571
`);

0 commit comments

Comments
 (0)