Skip to content

Commit e4183e1

Browse files
committed
Add and Improve type guards for is methods
The following methods were update to include type-guards and improve the generics over when using the type-guards: * isNode * isRelationship * isUnboundRelationship * isPath * isPathSegment * isPoint * isDuration * isLocalTime * isTime * isDate * isLocalDateTime * isDateTime Usage example without generics: ``` function handleDuration(duration: unknown) { if (isDuration(duration)) { // from this point i can access the duration properties // and methods without issue const months: Integer = duration.months } else { // this is not a duration object // @ts-expect-error const months = duration.months } } ``` Usage example with generics: ``` function handleDuration(duration: unknown) { if (isDuration<number>(duration)) { // from this point i can access the duration properties // and methods without issue const months: number = duration.months // @ts-expect-error const intMonths: Integer = duration.months } else { // this is not a duration object // @ts-expect-error const months = duration.months } } ``` ⚠️ Generic types missmatch in runtime will not trigger errors.
1 parent b53f683 commit e4183e1

File tree

9 files changed

+373
-33
lines changed

9 files changed

+373
-33
lines changed

packages/core/src/graph-types.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,10 @@ Object.defineProperty(
117117
* @param {Object} obj the object to test.
118118
* @return {boolean} `true` if given object is a {@link Node}, `false` otherwise.
119119
*/
120-
function isNode (obj: object): obj is Node {
120+
function isNode<
121+
T extends NumberOrInteger = Integer,
122+
P extends Properties = Properties,
123+
Label extends string = string> (obj: unknown): obj is Node<T, P, Label> {
121124
return hasIdentifierProperty(obj, NODE_IDENTIFIER_PROPERTY)
122125
}
123126

@@ -228,7 +231,10 @@ Object.defineProperty(
228231
* @param {Object} obj the object to test.
229232
* @return {boolean} `true` if given object is a {@link Relationship}, `false` otherwise.
230233
*/
231-
function isRelationship (obj: object): obj is Relationship {
234+
function isRelationship<
235+
T extends NumberOrInteger = Integer,
236+
P extends Properties = Properties,
237+
Type extends string = string> (obj: unknown): obj is Relationship<T, P, Type> {
232238
return hasIdentifierProperty(obj, RELATIONSHIP_IDENTIFIER_PROPERTY)
233239
}
234240

@@ -346,7 +352,10 @@ Object.defineProperty(
346352
* @param {Object} obj the object to test.
347353
* @return {boolean} `true` if given object is a {@link UnboundRelationship}, `false` otherwise.
348354
*/
349-
function isUnboundRelationship (obj: object): obj is UnboundRelationship {
355+
function isUnboundRelationship<
356+
T extends NumberOrInteger = Integer,
357+
P extends Properties = Properties,
358+
Type extends string = string> (obj: unknown): obj is UnboundRelationship<T, P, Type> {
350359
return hasIdentifierProperty(obj, UNBOUND_RELATIONSHIP_IDENTIFIER_PROPERTY)
351360
}
352361

@@ -394,7 +403,7 @@ Object.defineProperty(
394403
* @param {Object} obj the object to test.
395404
* @return {boolean} `true` if given object is a {@link PathSegment}, `false` otherwise.
396405
*/
397-
function isPathSegment (obj: object): obj is PathSegment {
406+
function isPathSegment<T extends NumberOrInteger = Integer> (obj: unknown): obj is PathSegment<T> {
398407
return hasIdentifierProperty(obj, PATH_SEGMENT_IDENTIFIER_PROPERTY)
399408
}
400409

@@ -448,7 +457,7 @@ Object.defineProperty(
448457
* @param {Object} obj the object to test.
449458
* @return {boolean} `true` if given object is a {@link Path}, `false` otherwise.
450459
*/
451-
function isPath (obj: object): obj is Path {
460+
function isPath<T extends NumberOrInteger = Integer> (obj: unknown): obj is Path<T> {
452461
return hasIdentifierProperty(obj, PATH_IDENTIFIER_PROPERTY)
453462
}
454463

packages/core/src/spatial-types.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ Object.defineProperty(Point.prototype, POINT_IDENTIFIER_PROPERTY, {
9393
* @param {Object} obj the object to test.
9494
* @return {boolean} `true` if given object is a {@link Point}, `false` otherwise.
9595
*/
96-
export function isPoint (obj?: any): obj is Point {
97-
return obj != null && obj[POINT_IDENTIFIER_PROPERTY] === true
96+
export function isPoint<T extends NumberOrInteger = Integer> (obj: unknown): obj is Point<T> {
97+
const anyObj: any | null | undefined = obj
98+
return obj != null && anyObj[POINT_IDENTIFIER_PROPERTY] === true
9899
}

packages/core/src/temporal-types.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ Object.defineProperty(
108108
* @param {Object} obj the object to test.
109109
* @return {boolean} `true` if given object is a {@link Duration}, `false` otherwise.
110110
*/
111-
export function isDuration (obj: object): obj is Duration {
111+
export function isDuration<T extends NumberOrInteger = Integer> (obj: unknown): obj is Duration<T> {
112112
return hasIdentifierProperty(obj, DURATION_IDENTIFIER_PROPERTY)
113113
}
114114

@@ -206,7 +206,7 @@ Object.defineProperty(
206206
* @param {Object} obj the object to test.
207207
* @return {boolean} `true` if given object is a {@link LocalTime}, `false` otherwise.
208208
*/
209-
export function isLocalTime (obj: object): boolean {
209+
export function isLocalTime<T extends NumberOrInteger = Integer> (obj: unknown): obj is LocalTime<T> {
210210
return hasIdentifierProperty(obj, LOCAL_TIME_IDENTIFIER_PROPERTY)
211211
}
212212

@@ -315,7 +315,7 @@ Object.defineProperty(
315315
* @param {Object} obj the object to test.
316316
* @return {boolean} `true` if given object is a {@link Time}, `false` otherwise.
317317
*/
318-
export function isTime (obj: object): obj is Time {
318+
export function isTime<T extends NumberOrInteger = Integer> (obj: unknown): obj is Time<T> {
319319
return hasIdentifierProperty(obj, TIME_IDENTIFIER_PROPERTY)
320320
}
321321

@@ -399,7 +399,7 @@ Object.defineProperty(
399399
* @param {Object} obj - The object to test.
400400
* @return {boolean} `true` if given object is a {@link Date}, `false` otherwise.
401401
*/
402-
export function isDate (obj: object): boolean {
402+
export function isDate<T extends NumberOrInteger = Integer> (obj: unknown): obj is Date<T> {
403403
return hasIdentifierProperty(obj, DATE_IDENTIFIER_PROPERTY)
404404
}
405405

@@ -532,7 +532,7 @@ Object.defineProperty(
532532
* @param {Object} obj - The object to test.
533533
* @return {boolean} `true` if given object is a {@link LocalDateTime}, `false` otherwise.
534534
*/
535-
export function isLocalDateTime (obj: any): obj is LocalDateTime {
535+
export function isLocalDateTime<T extends NumberOrInteger = Integer> (obj: unknown): obj is LocalDateTime<T> {
536536
return hasIdentifierProperty(obj, LOCAL_DATE_TIME_IDENTIFIER_PROPERTY)
537537
}
538538

@@ -733,7 +733,7 @@ Object.defineProperty(
733733
* @param {Object} obj - The object to test.
734734
* @return {boolean} `true` if given object is a {@link DateTime}, `false` otherwise.
735735
*/
736-
export function isDateTime (obj: object): boolean {
736+
export function isDateTime<T extends NumberOrInteger = Integer> (obj: unknown): obj is DateTime<T> {
737737
return hasIdentifierProperty(obj, DATE_TIME_IDENTIFIER_PROPERTY)
738738
}
739739

packages/core/test/graph-types.test.ts

Lines changed: 182 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ import {
2222
Relationship,
2323
isRelationship,
2424
UnboundRelationship,
25-
isUnboundRelationship
25+
isUnboundRelationship,
26+
Path,
27+
PathSegment,
28+
isPath,
29+
isPathSegment
2630
} from '../src/graph-types'
2731

2832
import {
@@ -70,12 +74,30 @@ describe('Node', () => {
7074
expect(node.toString()).toMatchSnapshot()
7175
})
7276

73-
test.each(validNodes())('should be consider a node', (node: any) => {
77+
test.each(validNodes())('should be consider a node', (node: unknown) => {
7478
expect(isNode(node)).toBe(true)
79+
80+
if (isNode(node)) {
81+
const typedNode: Node = node
82+
expect(typedNode).toEqual(node)
83+
} else {
84+
// @ts-expect-error
85+
const typedNode: Node = node
86+
expect(typedNode).toEqual(node)
87+
}
7588
})
7689

77-
test.each(nonNodes())('should not consider a non-node object as node', nonNode => {
90+
test.each(nonNodes())('should not consider a non-node object as node', (nonNode: unknown) => {
7891
expect(isNode(nonNode)).toBe(false)
92+
93+
if (isNode(nonNode)) {
94+
const typedNode: Node = nonNode
95+
expect(typedNode).toEqual(nonNode)
96+
} else {
97+
// @ts-expect-error
98+
const typedNode: Node = nonNode
99+
expect(typedNode).toEqual(nonNode)
100+
}
79101
})
80102

81103
test('should type mapping labels', () => {
@@ -219,6 +241,32 @@ describe('Relationship', () => {
219241
const _: 'DIRECTED' = a.type
220242
})
221243

244+
test.each(validRelationships())('should be consider a relationship', (relationship: unknown) => {
245+
expect(isRelationship(relationship)).toBe(true)
246+
247+
if (isRelationship(relationship)) {
248+
const typedRelationship: Relationship = relationship
249+
expect(typedRelationship).toEqual(relationship)
250+
} else {
251+
// @ts-expect-error
252+
const typedRelationship: Relationship = relationship
253+
expect(typedRelationship).toEqual(relationship)
254+
}
255+
})
256+
257+
test.each(nonRelationships())('should not consider a non-relationship object as relationship', (nonRelationship: unknown) => {
258+
expect(isRelationship(nonRelationship)).toBe(false)
259+
260+
if (isRelationship(nonRelationship)) {
261+
const typedRelationship: Relationship = nonRelationship
262+
expect(typedRelationship).toEqual(nonRelationship)
263+
} else {
264+
// @ts-expect-error
265+
const typedRelationship: Relationship = nonRelationship
266+
expect(typedRelationship).toEqual(nonRelationship)
267+
}
268+
})
269+
222270
function validRelationships (): any[] {
223271
return [
224272
[new Relationship(1, 2, 3, 'Rel', {}, 'elementId', 'startNodeElementId', 'endNodeElementId')],
@@ -346,6 +394,32 @@ describe('UnboundRelationship', () => {
346394
const _: 'DIRECTED' = a.type
347395
})
348396

397+
test.each(validUnboundRelationships())('should be consider a unbound relationship', (unboundRelationship: unknown) => {
398+
expect(isUnboundRelationship(unboundRelationship)).toBe(true)
399+
400+
if (isUnboundRelationship(unboundRelationship)) {
401+
const typedRelationship: UnboundRelationship = unboundRelationship
402+
expect(typedRelationship).toEqual(unboundRelationship)
403+
} else {
404+
// @ts-expect-error
405+
const typedRelationship: UnboundRelationship = unboundRelationship
406+
expect(typedRelationship).toEqual(unboundRelationship)
407+
}
408+
})
409+
410+
test.each(nonUnboundRelationships())('should not consider a non-unbound relationship object as unbound relationship', (nonUnboundRelationship: unknown) => {
411+
expect(isUnboundRelationship(nonUnboundRelationship)).toBe(false)
412+
413+
if (isUnboundRelationship(nonUnboundRelationship)) {
414+
const typedRelationship: UnboundRelationship = nonUnboundRelationship
415+
expect(typedRelationship).toEqual(nonUnboundRelationship)
416+
} else {
417+
// @ts-expect-error
418+
const typedRelationship: UnboundRelationship = nonUnboundRelationship
419+
expect(typedRelationship).toEqual(nonUnboundRelationship)
420+
}
421+
})
422+
349423
function validUnboundRelationships (): any[] {
350424
return [
351425
[new UnboundRelationship(1, 'Rel', {}, 'elementId')],
@@ -388,6 +462,111 @@ describe('UnboundRelationship', () => {
388462
}
389463
})
390464

465+
describe('Path', () => {
466+
test.each(validPaths())('should be consider a path', (path: unknown) => {
467+
expect(isPath(path)).toBe(true)
468+
469+
if (isPath(path)) {
470+
const typed: Path = path
471+
expect(typed).toEqual(path)
472+
} else {
473+
// @ts-expect-error
474+
const typed: Path = path
475+
expect(typed).toEqual(path)
476+
}
477+
})
478+
479+
test.each(nonPaths())('should not consider a non-path object as path', (nonPath: unknown) => {
480+
expect(isPath(nonPath)).toBe(false)
481+
482+
if (isPath(nonPath)) {
483+
const typed: Path = nonPath
484+
expect(typed).toEqual(nonPath)
485+
} else {
486+
// @ts-expect-error
487+
const typed: Path = nonPath
488+
expect(typed).toEqual(nonPath)
489+
}
490+
})
491+
492+
function validPaths (): any[] {
493+
return [
494+
[new Path(new Node(1, [], {}), new Node(2, [], {}), [])],
495+
[new Path(new Node(1, [], {}), new Node(2, [], {}), [new PathSegment(new Node(1, [], {}), new Relationship(1, 1, 2, 'type', {}), new Node(2, [], {}))])]
496+
]
497+
}
498+
499+
function nonPaths (): any[] {
500+
return [
501+
[{
502+
start: new Node(1, [], {}),
503+
end: new Node(2, [], {}),
504+
length: 1,
505+
segments: [
506+
new PathSegment(
507+
new Node(1, [], {}),
508+
new Relationship(1, 1, 2, 'type', {}),
509+
new Node(2, [], {}))
510+
]
511+
}],
512+
[null],
513+
[undefined],
514+
[{}],
515+
[1]
516+
]
517+
}
518+
})
519+
520+
describe('Path', () => {
521+
test.each(validPathSegments())('should be consider a path segment', (pathSegment: unknown) => {
522+
expect(isPathSegment(pathSegment)).toBe(true)
523+
524+
if (isPathSegment(pathSegment)) {
525+
const typed: PathSegment = pathSegment
526+
expect(typed).toEqual(pathSegment)
527+
} else {
528+
// @ts-expect-error
529+
const typed: PathSegment = pathSegment
530+
expect(typed).toEqual(pathSegment)
531+
}
532+
})
533+
534+
test.each(nonPathSegments())('should not consider a non-path object as path segument', (nonPathSegment: unknown) => {
535+
expect(isPathSegment(nonPathSegment)).toBe(false)
536+
537+
if (isPathSegment(nonPathSegment)) {
538+
const typed: PathSegment = nonPathSegment
539+
expect(typed).toEqual(nonPathSegment)
540+
} else {
541+
// @ts-expect-error
542+
const typed: PathSegment = nonPathSegment
543+
expect(typed).toEqual(nonPathSegment)
544+
}
545+
})
546+
547+
function validPathSegments (): any[] {
548+
return [
549+
[new PathSegment(new Node(1, [], {}), new Relationship(1, 1, 2, 'type', {}), new Node(2, [], {}))],
550+
[new PathSegment(new Node(int(1), [], {}), new Relationship(int(1), int(1), int(2), 'type', {}), new Node(int(2), [], {}))]
551+
]
552+
}
553+
554+
function nonPathSegments (): any[] {
555+
return [
556+
[{
557+
558+
start: new Node(1, [], {}),
559+
end: new Node(2, [], {}),
560+
relationship: new Relationship(1, 1, 2, 'type', {})
561+
}],
562+
[null],
563+
[undefined],
564+
[{}],
565+
[1]
566+
]
567+
}
568+
})
569+
391570
function validIdentityAndExpectedElementIds (): any[] {
392571
return [
393572
[10, '10'],

packages/core/test/spatial-types.test.ts

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,34 @@ describe('isPoint', () => {
9494
new Point(CARTESIAN_3D_CRS_CODE, 19.24, 100.29, 20.22222),
9595
new Point(CARTESIAN_3D_CRS_CODE, 19.24, 100.29),
9696
new Point(0, 19.24, 100.29, 20.22222)
97-
])('isPoint(%s) should be truthy', point =>
97+
])('isPoint(%s) should be truthy', (point: unknown) => {
9898
expect(isPoint(point)).toBeTruthy()
99-
)
99+
100+
if (isPoint(point)) {
101+
const typedPoint: Point = point
102+
expect(typedPoint).toEqual(point)
103+
} else {
104+
// @ts-expect-error
105+
const typedPoint: Point = point
106+
expect(typedPoint).toEqual(point)
107+
}
108+
})
100109

101110
test.each([
102111
{ srid: CARTESIAN_3D_CRS_CODE, x: 18.24, y: 13.8, z: 124 },
103112
{ srid: 0, x: 18.24, y: 13.8 },
104113
['srid', CARTESIAN_3D_CRS_CODE, 'x', 18.24, 'y', 12.8, 'z', 124],
105114
'Point(1, 2, 3, 4)'
106-
])('isPoint(%s) should be falsy', point => expect(isPoint(point)).toBeFalsy())
115+
])('isPoint(%s) should be falsy', (point: unknown) => {
116+
expect(isPoint(point)).toBeFalsy()
117+
118+
if (isPoint(point)) {
119+
const typedPoint: Point = point
120+
expect(typedPoint).toEqual(point)
121+
} else {
122+
// @ts-expect-error
123+
const typedPoint: Point = point
124+
expect(typedPoint).toEqual(point)
125+
}
126+
})
107127
})

0 commit comments

Comments
 (0)