Skip to content

Commit b14e7c9

Browse files
ifeanyi-ugwuardatan
authored andcommitted
Add missing documentation for scalar types & feat(scalars): add GeoJSON scalar (#2621)
* Add missing documentation for scalar types - Added .mdx documentation for scalar types: GUID, HSLA, ISO8601Duration, LCCSubclass, Long, SESSN, UnsignedFloat, and UnsignedInt. - Resolves issue #2019 regarding missing scalar type documentation. * feat(scalars): add GeoJSON scalar - Introduced a custom GeoJSON scalar type. - Added unit tests to ensure proper validation and functionality of the GeoJSON scalar. - Updated documentation website to reflect the new scalar type and usage. - Resolves issue #2061.
1 parent 4c64044 commit b14e7c9

23 files changed

+1174
-112
lines changed

bundle-test/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"bundlesize": [
1616
{
1717
"path": "./dist/index.js",
18-
"maxSize": "100 kB",
18+
"maxSize": "120 kB",
1919
"compression": "none"
2020
}
2121
]

src/index.ts

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
GraphQLDID,
1515
GraphQLDuration,
1616
GraphQLEmailAddress,
17+
GraphQLGeoJSON,
1718
GraphQLGUID,
1819
GraphQLHexadecimal,
1920
GraphQLHexColorCode,
@@ -211,73 +212,74 @@ export {
211212
};
212213

213214
export const resolvers: Record<string, GraphQLScalarType> = {
215+
AccountNumber: GraphQLAccountNumber,
216+
BigInt: GraphQLBigInt,
217+
Byte: GraphQLByte,
218+
CountryCode: GraphQLCountryCode,
219+
Cuid: GraphQLCuid,
220+
Currency: GraphQLCurrency,
214221
Date: GraphQLDate,
215-
Time: GraphQLTime,
216222
DateTime: GraphQLDateTime,
217223
DateTimeISO: GraphQLDateTimeISO,
218-
Timestamp: GraphQLTimestamp,
219-
TimeZone: GraphQLTimeZone,
220-
UtcOffset: GraphQLUtcOffset,
224+
DeweyDecimal: GraphQLDeweyDecimal,
225+
DID: GraphQLDID,
221226
Duration: GraphQLDuration,
222-
ISO8601Duration: GraphQLISO8601Duration,
223-
LocalDate: GraphQLLocalDate,
224-
LocalTime: GraphQLLocalTime,
225-
LocalDateTime: GraphQLLocalDateTime,
226-
LocalEndTime: GraphQLLocalEndTime,
227227
EmailAddress: GraphQLEmailAddress,
228-
NegativeFloat: GraphQLNegativeFloat,
229-
NegativeInt: GraphQLNegativeInt,
230-
NonEmptyString: GraphQLNonEmptyString,
231-
NonNegativeFloat: GraphQLNonNegativeFloat,
232-
NonNegativeInt: GraphQLNonNegativeInt,
233-
NonPositiveFloat: GraphQLNonPositiveFloat,
234-
NonPositiveInt: GraphQLNonPositiveInt,
235-
PhoneNumber: GraphQLPhoneNumber,
236-
PositiveFloat: GraphQLPositiveFloat,
237-
PositiveInt: GraphQLPositiveInt,
238-
PostalCode: GraphQLPostalCode,
239-
UnsignedFloat: GraphQLUnsignedFloat,
240-
UnsignedInt: GraphQLUnsignedInt,
241-
URL: GraphQLURL,
242-
BigInt: GraphQLBigInt,
243-
Byte: GraphQLByte,
244-
Long: GraphQLLong,
245-
SafeInt: GraphQLSafeInt,
246-
UUID: GraphQLUUID,
228+
GeoJSON: GraphQLGeoJSON,
247229
GUID: GraphQLGUID,
248230
Hexadecimal: GraphQLHexadecimal,
249231
HexColorCode: GraphQLHexColorCode,
250232
HSL: GraphQLHSL,
251233
HSLA: GraphQLHSLA,
234+
IBAN: GraphQLIBAN,
252235
IP: GraphQLIP,
236+
IPCPatent: GraphQLIPCPatent,
253237
IPv4: GraphQLIPv4,
254238
IPv6: GraphQLIPv6,
255239
ISBN: GraphQLISBN,
240+
ISO8601Duration: GraphQLISO8601Duration,
241+
JSON: GraphQLJSON,
242+
JSONObject: GraphQLJSONObject,
256243
JWT: GraphQLJWT,
257244
Latitude: GraphQLLatitude,
245+
LCCSubclass: GraphQLLCCSubclass,
246+
LocalDate: GraphQLLocalDate,
247+
LocalDateTime: GraphQLLocalDateTime,
248+
Locale: GraphQLLocale,
249+
LocalEndTime: GraphQLLocalEndTime,
250+
LocalTime: GraphQLLocalTime,
251+
Long: GraphQLLong,
258252
Longitude: GraphQLLongitude,
259253
MAC: GraphQLMAC,
254+
NegativeFloat: GraphQLNegativeFloat,
255+
NegativeInt: GraphQLNegativeInt,
256+
NonEmptyString: GraphQLNonEmptyString,
257+
NonNegativeFloat: GraphQLNonNegativeFloat,
258+
NonNegativeInt: GraphQLNonNegativeInt,
259+
NonPositiveFloat: GraphQLNonPositiveFloat,
260+
NonPositiveInt: GraphQLNonPositiveInt,
261+
ObjectID: GraphQLObjectID,
262+
PhoneNumber: GraphQLPhoneNumber,
260263
Port: GraphQLPort,
264+
PositiveFloat: GraphQLPositiveFloat,
265+
PositiveInt: GraphQLPositiveInt,
266+
PostalCode: GraphQLPostalCode,
261267
RGB: GraphQLRGB,
262268
RGBA: GraphQLRGBA,
263-
USCurrency: GraphQLUSCurrency,
264-
Currency: GraphQLCurrency,
265-
JSON: GraphQLJSON,
266-
JSONObject: GraphQLJSONObject,
267-
IBAN: GraphQLIBAN,
268-
ObjectID: GraphQLObjectID,
269-
Void: GraphQLVoid,
270-
DID: GraphQLDID,
271-
CountryCode: GraphQLCountryCode,
272-
Locale: GraphQLLocale,
273269
RoutingNumber: GraphQLRoutingNumber,
274-
AccountNumber: GraphQLAccountNumber,
275-
Cuid: GraphQLCuid,
270+
SafeInt: GraphQLSafeInt,
276271
SemVer: GraphQLSemVer,
277272
SESSN: GraphQLSESSN,
278-
DeweyDecimal: GraphQLDeweyDecimal,
279-
LCCSubclass: GraphQLLCCSubclass,
280-
IPCPatent: GraphQLIPCPatent,
273+
Time: GraphQLTime,
274+
Timestamp: GraphQLTimestamp,
275+
TimeZone: GraphQLTimeZone,
276+
UnsignedFloat: GraphQLUnsignedFloat,
277+
UnsignedInt: GraphQLUnsignedInt,
278+
URL: GraphQLURL,
279+
USCurrency: GraphQLUSCurrency,
280+
UtcOffset: GraphQLUtcOffset,
281+
UUID: GraphQLUUID,
282+
Void: GraphQLVoid,
281283
};
282284

283285
export {
@@ -427,3 +429,8 @@ export {
427429
GraphQLLCCSubclass,
428430
GraphQLIPCPatent,
429431
};
432+
433+
export { GeoJSON as GeoJSONTypeDefinition } from './typeDefs.js';
434+
export { GraphQLGeoJSON as GeoJSONResolver };
435+
export { GraphQLGeoJSON };
436+
export { GeoJSON as GeoJSONMock } from './mocks.js';

src/mocks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export const GeoJSON = () => 'Example GeoJSON';
12
const BigIntMock = () => BigInt(Number.MAX_SAFE_INTEGER);
23
const ByteMock = () => new Uint8Array([1988, 1981, 1965, 1963, 1959, 1955]);
34
const DateMock = () => '2007-12-03';

src/scalars/GeoJSON/GeoJSON.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { GraphQLScalarType, Kind, ValueNode } from 'graphql';
2+
import { createGraphQLError } from '../../error.js';
3+
import { generateGeoJSONType } from './codegenScalarType.js';
4+
import { geojsonSchema } from './jsonSchema.js';
5+
import { GeoJSONObject } from './types.js';
6+
import {
7+
isValidBBox,
8+
isValidFeature,
9+
isValidFeatureCollection,
10+
isValidGeometry,
11+
} from './validators.js';
12+
13+
const validate = (value: any, ast?: ValueNode): GeoJSONObject => {
14+
let parsed: unknown;
15+
16+
if (typeof value === 'string') {
17+
try {
18+
parsed = JSON.parse(value);
19+
} catch {
20+
throw createGraphQLError(
21+
'Invalid GeoJSON: Failed to parse JSON string',
22+
ast ? { nodes: ast } : undefined,
23+
);
24+
}
25+
} else {
26+
parsed = value;
27+
}
28+
29+
if (!parsed || typeof parsed !== 'object') {
30+
throw createGraphQLError(
31+
'Invalid GeoJSON: Must be an object',
32+
ast ? { nodes: ast } : undefined,
33+
);
34+
}
35+
36+
if (!('type' in parsed)) {
37+
throw createGraphQLError(
38+
'Invalid GeoJSON: Missing type property',
39+
ast ? { nodes: ast } : undefined,
40+
);
41+
}
42+
43+
// Validate bbox if present
44+
if ('bbox' in parsed && parsed.bbox !== undefined) {
45+
if (!isValidBBox(parsed.bbox)) {
46+
throw createGraphQLError(
47+
'Invalid GeoJSON: Invalid bbox format',
48+
ast ? { nodes: ast } : undefined,
49+
);
50+
}
51+
}
52+
53+
// Validate based on type
54+
/*if (parsed.type === 'Feature') {
55+
if (!isValidFeature(parsed)) {
56+
throw createGraphQLError(
57+
'Invalid GeoJSON: Invalid Feature object',
58+
ast ? { nodes: ast } : undefined,
59+
);
60+
}
61+
} else if (parsed.type === 'FeatureCollection') {
62+
if (!isValidFeatureCollection(parsed)) {
63+
throw createGraphQLError(
64+
'Invalid GeoJSON: Invalid FeatureCollection object',
65+
ast ? { nodes: ast } : undefined,
66+
);
67+
}
68+
} else if (!isValidGeometry(parsed)) {
69+
throw createGraphQLError(
70+
'Invalid GeoJSON: Invalid Geometry object',
71+
ast ? { nodes: ast } : undefined,
72+
);
73+
}*/
74+
if (isValidFeature(parsed)) {
75+
return parsed;
76+
} else if (isValidFeatureCollection(parsed)) {
77+
return parsed;
78+
} else if (isValidGeometry(parsed)) {
79+
return parsed;
80+
}
81+
82+
// return parsed as GeoJSONObject;
83+
throw createGraphQLError(
84+
'Invalid GeoJSON: Object does not match any valid GeoJSON type',
85+
ast ? { nodes: ast } : undefined,
86+
);
87+
};
88+
89+
export const GraphQLGeoJSON = /*#__PURE__*/ new GraphQLScalarType({
90+
name: 'GeoJSON',
91+
description:
92+
'A GeoJSON object as defined by RFC 7946: https://datatracker.ietf.org/doc/html/rfc7946',
93+
94+
serialize(value) {
95+
return validate(value);
96+
},
97+
98+
parseValue(value) {
99+
return validate(value);
100+
},
101+
102+
parseLiteral(ast) {
103+
if (ast.kind !== Kind.STRING && ast.kind !== Kind.OBJECT) {
104+
throw createGraphQLError(
105+
`Can only validate strings or objects as GeoJSON but got a: ${ast.kind}`,
106+
{ nodes: [ast] },
107+
);
108+
}
109+
return validate(ast.kind === Kind.STRING ? ast.value : ast, ast);
110+
},
111+
112+
extensions: {
113+
codegenScalarType: generateGeoJSONType(),
114+
jsonSchema: geojsonSchema,
115+
},
116+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Helper function to generate Position type
2+
const generatePositionType = () => `[number, number] | [number, number, number]`;
3+
4+
// Helper function to generate BBox type
5+
const generateBBoxType = () =>
6+
`[number, number, number, number] | [number, number, number, number, number, number]`;
7+
8+
// Helper function to generate Point type
9+
const generatePointType = () => `{
10+
type: "Point";
11+
coordinates: ${generatePositionType()};
12+
bbox?: ${generateBBoxType()};
13+
}`;
14+
15+
// Helper function to generate MultiPoint type
16+
const generateMultiPointType = () => `{
17+
type: "MultiPoint";
18+
coordinates: ${generatePositionType()}[];
19+
bbox?: ${generateBBoxType()};
20+
}`;
21+
22+
// Helper function to generate LineString type
23+
const generateLineStringType = () => `{
24+
type: "LineString";
25+
coordinates: ${generatePositionType()}[];
26+
bbox?: ${generateBBoxType()};
27+
}`;
28+
29+
// Helper function to generate MultiLineString type
30+
const generateMultiLineStringType = () => `{
31+
type: "MultiLineString";
32+
coordinates: ${generatePositionType()}[][];
33+
bbox?: ${generateBBoxType()};
34+
}`;
35+
36+
// Helper function to generate Polygon type
37+
const generatePolygonType = () => `{
38+
type: "Polygon";
39+
coordinates: ${generatePositionType()}[][];
40+
bbox?: ${generateBBoxType()};
41+
}`;
42+
43+
// Helper function to generate MultiPolygon type
44+
const generateMultiPolygonType = () => `{
45+
type: "MultiPolygon";
46+
coordinates: ${generatePositionType()}[][][];
47+
bbox?: ${generateBBoxType()};
48+
}`;
49+
50+
// Helper function to generate GeometryCollection type
51+
const generateGeometryCollectionType = () => `{
52+
type: "GeometryCollection";
53+
geometries: (${generatePointType()} | ${generateMultiPointType()} | ${generateLineStringType()} | ${generateMultiLineStringType()} | ${generatePolygonType()} | ${generateMultiPolygonType()})[];
54+
bbox?: ${generateBBoxType()};
55+
}`;
56+
57+
// Helper function to generate Feature type
58+
const generateFeatureType = () => `{
59+
type: "Feature";
60+
geometry: (${generatePointType()} | ${generateMultiPointType()} | ${generateLineStringType()} | ${generateMultiLineStringType()} | ${generatePolygonType()} | ${generateMultiPolygonType()} | ${generateGeometryCollectionType()}) | null;
61+
properties: { [key: string]: any } | null;
62+
bbox?: ${generateBBoxType()};
63+
}`;
64+
65+
// Helper function to generate FeatureCollection type
66+
const generateFeatureCollectionType = () => `{
67+
type: "FeatureCollection";
68+
features: ${generateFeatureType()}[];
69+
bbox?: ${generateBBoxType()};
70+
}`;
71+
72+
// Generate the complete GeoJSON type
73+
export const generateGeoJSONType = () =>
74+
`(${generatePointType()} | ${generateMultiPointType()} | ${generateLineStringType()} | ${generateMultiLineStringType()} | ${generatePolygonType()} | ${generateMultiPolygonType()} | ${generateGeometryCollectionType()} | ${generateFeatureType()} | ${generateFeatureCollectionType()})`;

src/scalars/GeoJSON/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { GraphQLGeoJSON } from './GeoJSON.js';

0 commit comments

Comments
 (0)