Skip to content

Commit 89a867a

Browse files
committed
feat: add json schema annotations for SOFA
1 parent b3776e9 commit 89a867a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+964
-902
lines changed

.changeset/slow-comics-bake.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'graphql-scalars': minor
3+
---
4+
5+
Add JSON Schema annotations for SOFA

src/scalars/AccountNumber.ts

Lines changed: 17 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,29 @@
1-
import {
2-
GraphQLScalarType,
3-
GraphQLScalarTypeConfig,
4-
Kind,
5-
locatedError,
6-
} from 'graphql';
1+
import { GraphQLScalarType, GraphQLScalarTypeConfig, Kind, locatedError } from 'graphql';
72

83
interface Validator {
94
(rtn: string): boolean;
105
}
116

12-
const validator: Validator = (rtn) => /^([a-zA-Z0-9]){5,17}$/.test(rtn);
7+
const regexp = /^([a-zA-Z0-9]){5,17}$/;
8+
9+
const validator: Validator = rtn => regexp.test(rtn);
1310

1411
const validate = (account: unknown): string => {
1512
if (typeof account !== 'string') {
1613
throw locatedError(new TypeError('can only parse String'), null);
1714
}
1815

1916
if (!validator(account)) {
20-
throw locatedError(
21-
new TypeError('must be alphanumeric between 5-17'),
22-
null,
23-
);
17+
throw locatedError(new TypeError('must be alphanumeric between 5-17'), null);
2418
}
2519

2620
return account;
2721
};
2822

29-
export const GraphQLAccountNumberConfig: GraphQLScalarTypeConfig<
30-
string,
31-
string
32-
> = {
23+
export const GraphQLAccountNumberConfig: GraphQLScalarTypeConfig<string, string> = {
3324
name: 'AccountNumber',
3425
description:
35-
'Banking account number is a string of 5 to 17 alphanumeric values for ' +
36-
'representing an generic account number',
26+
'Banking account number is a string of 5 to 17 alphanumeric values for ' + 'representing an generic account number',
3727

3828
serialize(value: unknown) {
3929
return validate(value);
@@ -48,14 +38,16 @@ export const GraphQLAccountNumberConfig: GraphQLScalarTypeConfig<
4838
return validate(ast.value);
4939
}
5040

51-
throw locatedError(
52-
new TypeError(
53-
`Account Number can only parse String but got '${ast.kind}'`,
54-
),
55-
ast,
56-
);
41+
throw locatedError(new TypeError(`Account Number can only parse String but got '${ast.kind}'`), ast);
42+
},
43+
extensions: {
44+
codegenScalarType: 'string',
45+
jsonSchema: {
46+
title: 'AccountNumber',
47+
type: 'string',
48+
pattern: regexp.source,
49+
},
5750
},
5851
};
5952

60-
export const GraphQLAccountNumber: GraphQLScalarType =
61-
/*#__PURE__*/ new GraphQLScalarType(GraphQLAccountNumberConfig);
53+
export const GraphQLAccountNumber: GraphQLScalarType = /*#__PURE__*/ new GraphQLScalarType(GraphQLAccountNumberConfig);

src/scalars/BigInt.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ export const GraphQLBigIntConfig: GraphQLScalarTypeConfig<bigint, bigint | BigIn
8888
},
8989
extensions: {
9090
codegenScalarType: 'bigint',
91+
jsonSchema: {
92+
type: 'integer',
93+
format: 'int64',
94+
},
9195
},
9296
};
9397

src/scalars/Byte.ts

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,16 @@ import {
99
} from 'graphql';
1010

1111
type BufferJson = { type: 'Buffer'; data: number[] };
12-
const base64Validator =
13-
/^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/;
12+
const base64Validator = /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/;
1413
function hexValidator(value: string) {
1514
// Ensure that any leading 0 is removed from the hex string to avoid false negatives.
1615
const sanitizedValue = value.charAt(0) === '0' ? value.slice(1) : value;
1716
// For larger strings, we run into issues with MAX_SAFE_INTEGER, so split the string
1817
// into smaller pieces to avoid this issue.
1918
if (value.length > 8) {
2019
let parsedString = '';
21-
for (
22-
let startIndex = 0, endIndex = 8;
23-
startIndex < value.length;
24-
startIndex += 8, endIndex += 8
25-
) {
26-
parsedString += parseInt(value.slice(startIndex, endIndex), 16).toString(
27-
16,
28-
);
20+
for (let startIndex = 0, endIndex = 8; startIndex < value.length; startIndex += 8, endIndex += 8) {
21+
parsedString += parseInt(value.slice(startIndex, endIndex), 16).toString(16);
2922
}
3023
return parsedString === sanitizedValue;
3124
}
@@ -34,19 +27,13 @@ function hexValidator(value: string) {
3427

3528
function validate(value: Buffer | string | BufferJson) {
3629
if (typeof value !== 'string' && !(value instanceof global.Buffer)) {
37-
throw new TypeError(
38-
`Value is not an instance of Buffer: ${JSON.stringify(value)}`,
39-
);
30+
throw new TypeError(`Value is not an instance of Buffer: ${JSON.stringify(value)}`);
4031
}
4132
if (typeof value === 'string') {
4233
const isBase64 = base64Validator.test(value);
4334
const isHex = hexValidator(value);
4435
if (!isBase64 && !isHex) {
45-
throw new TypeError(
46-
`Value is not a valid base64 or hex encoded string: ${JSON.stringify(
47-
value,
48-
)}`,
49-
);
36+
throw new TypeError(`Value is not a valid base64 or hex encoded string: ${JSON.stringify(value)}`);
5037
}
5138
return global.Buffer.from(value, isHex ? 'hex' : 'base64');
5239
}
@@ -57,25 +44,13 @@ function validate(value: Buffer | string | BufferJson) {
5744
function parseObject(ast: ObjectValueNode) {
5845
const key = ast.fields[0].value;
5946
const value = ast.fields[1].value;
60-
if (
61-
ast.fields.length === 2 &&
62-
key.kind === Kind.STRING &&
63-
key.value === 'Buffer' &&
64-
value.kind === Kind.LIST
65-
) {
66-
return global.Buffer.from(
67-
value.values.map((astValue: IntValueNode) => parseInt(astValue.value)),
68-
);
47+
if (ast.fields.length === 2 && key.kind === Kind.STRING && key.value === 'Buffer' && value.kind === Kind.LIST) {
48+
return global.Buffer.from(value.values.map((astValue: IntValueNode) => parseInt(astValue.value)));
6949
}
70-
throw new TypeError(
71-
`Value is not a JSON representation of Buffer: ${print(ast)}`,
72-
);
50+
throw new TypeError(`Value is not a JSON representation of Buffer: ${print(ast)}`);
7351
}
7452

75-
export const GraphQLByteConfig: GraphQLScalarTypeConfig<
76-
Buffer | string | BufferJson,
77-
Buffer
78-
> = /*#__PURE__*/ {
53+
export const GraphQLByteConfig: GraphQLScalarTypeConfig<Buffer | string | BufferJson, Buffer> = /*#__PURE__*/ {
7954
name: 'Byte',
8055
description: 'The `Byte` scalar type represents byte value as a Buffer',
8156
serialize: validate,
@@ -87,15 +62,16 @@ export const GraphQLByteConfig: GraphQLScalarTypeConfig<
8762
case Kind.OBJECT:
8863
return parseObject(ast);
8964
default:
90-
throw new TypeError(
91-
`Can only parse base64 or hex encoded strings as Byte, but got a: ${ast.kind}`,
92-
);
65+
throw new TypeError(`Can only parse base64 or hex encoded strings as Byte, but got a: ${ast.kind}`);
9366
}
9467
},
9568
extensions: {
9669
codegenScalarType: 'Buffer | string',
70+
jsonSchema: {
71+
type: 'string',
72+
format: 'byte',
73+
},
9774
},
9875
};
9976

100-
export const GraphQLByte: GraphQLScalarType =
101-
/*#__PURE__*/ new GraphQLScalarType(GraphQLByteConfig);
77+
export const GraphQLByte: GraphQLScalarType = /*#__PURE__*/ new GraphQLScalarType(GraphQLByteConfig);

src/scalars/CountryCode.ts

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Kind, GraphQLError, GraphQLScalarType } from 'graphql';
22

3-
const validate = (value: any) => {
4-
const COUNTRY_CODE_REGEX =
5-
/^(AD|AE|AF|AG|AI|AL|AM|AO|AQ|AR|AS|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BJ|BL|BM|BN|BO|BQ|BR|BS|BT|BV|BW|BY|BZ|CA|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|CR|CU|CV|CW|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EE|EG|EH|ER|ES|ET|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|IO|IQ|IR|IS|IT|JE|JM|JO|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MF|MG|MH|MK|ML|MM|MN|MO|MP|MQ|MR|MS|MT|MU|MV|MW|MX|MY|MZ|NA|NC|NE|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|SS|ST|SV|SX|SY|SZ|TC|TD|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TR|TT|TV|TW|TZ|UA|UG|UM|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|YE|YT|ZA|ZM|ZW)$/i;
3+
const COUNTRY_CODE_REGEX =
4+
/^(AD|AE|AF|AG|AI|AL|AM|AO|AQ|AR|AS|AT|AU|AW|AX|AZ|BA|BB|BD|BE|BF|BG|BH|BI|BJ|BL|BM|BN|BO|BQ|BR|BS|BT|BV|BW|BY|BZ|CA|CC|CD|CF|CG|CH|CI|CK|CL|CM|CN|CO|CR|CU|CV|CW|CX|CY|CZ|DE|DJ|DK|DM|DO|DZ|EC|EE|EG|EH|ER|ES|ET|FI|FJ|FK|FM|FO|FR|GA|GB|GD|GE|GF|GG|GH|GI|GL|GM|GN|GP|GQ|GR|GS|GT|GU|GW|GY|HK|HM|HN|HR|HT|HU|ID|IE|IL|IM|IN|IO|IQ|IR|IS|IT|JE|JM|JO|JP|KE|KG|KH|KI|KM|KN|KP|KR|KW|KY|KZ|LA|LB|LC|LI|LK|LR|LS|LT|LU|LV|LY|MA|MC|MD|ME|MF|MG|MH|MK|ML|MM|MN|MO|MP|MQ|MR|MS|MT|MU|MV|MW|MX|MY|MZ|NA|NC|NE|NF|NG|NI|NL|NO|NP|NR|NU|NZ|OM|PA|PE|PF|PG|PH|PK|PL|PM|PN|PR|PS|PT|PW|PY|QA|RE|RO|RS|RU|RW|SA|SB|SC|SD|SE|SG|SH|SI|SJ|SK|SL|SM|SN|SO|SR|SS|ST|SV|SX|SY|SZ|TC|TD|TF|TG|TH|TJ|TK|TL|TM|TN|TO|TR|TT|TV|TW|TZ|UA|UG|UM|US|UY|UZ|VA|VC|VE|VG|VI|VN|VU|WF|WS|YE|YT|ZA|ZM|ZW)$/i;
65

6+
const validate = (value: any) => {
77
if (typeof value !== 'string') {
88
throw new TypeError(`Value is not string: ${value}`);
99
}
@@ -14,27 +14,29 @@ const validate = (value: any) => {
1414
return value;
1515
};
1616

17-
export const GraphQLCountryCode: GraphQLScalarType =
18-
/*#__PURE__*/ new GraphQLScalarType({
19-
name: 'CountryCode',
20-
description: 'A country code as defined by ISO 3166-1 alpha-2',
21-
serialize(value) {
22-
return validate(value);
23-
},
17+
export const GraphQLCountryCode: GraphQLScalarType = /*#__PURE__*/ new GraphQLScalarType({
18+
name: 'CountryCode',
19+
description: 'A country code as defined by ISO 3166-1 alpha-2',
20+
serialize(value) {
21+
return validate(value);
22+
},
2423

25-
parseValue(value) {
26-
return validate(value);
27-
},
24+
parseValue(value) {
25+
return validate(value);
26+
},
2827

29-
parseLiteral(ast) {
30-
if (ast.kind !== Kind.STRING) {
31-
throw new GraphQLError(
32-
`Can only validate strings as country codes but got a: ${ast.kind}`,
33-
);
34-
}
35-
return validate(ast.value);
36-
},
37-
extensions: {
38-
codegenScalarType: 'string',
28+
parseLiteral(ast) {
29+
if (ast.kind !== Kind.STRING) {
30+
throw new GraphQLError(`Can only validate strings as country codes but got a: ${ast.kind}`);
31+
}
32+
return validate(ast.value);
33+
},
34+
extensions: {
35+
codegenScalarType: 'string',
36+
jsonSchema: {
37+
title: 'CountryCode',
38+
type: 'string',
39+
pattern: COUNTRY_CODE_REGEX.source,
3940
},
40-
});
41+
},
42+
});

src/scalars/Cuid.ts

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,8 @@
1-
import {
2-
Kind,
3-
GraphQLError,
4-
GraphQLScalarType,
5-
GraphQLScalarTypeConfig,
6-
} from 'graphql';
1+
import { Kind, GraphQLError, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql';
72

8-
const validate = (value: any) => {
9-
const CUID_REGEX = /^c[^\s-]{8,}$/i;
3+
const CUID_REGEX = /^c[^\s-]{8,}$/i;
104

5+
const validate = (value: any) => {
116
if (typeof value !== 'string') {
127
throw new TypeError(`Value is not string: ${value}`);
138
}
@@ -33,9 +28,7 @@ export const GraphQLCuidConfig = /*#__PURE__*/ {
3328

3429
parseLiteral(ast) {
3530
if (ast.kind !== Kind.STRING) {
36-
throw new GraphQLError(
37-
`Can only validate strings as cuids but got a: ${ast.kind}`,
38-
);
31+
throw new GraphQLError(`Can only validate strings as cuids but got a: ${ast.kind}`);
3932
}
4033

4134
return validate(ast.value);
@@ -45,8 +38,12 @@ export const GraphQLCuidConfig = /*#__PURE__*/ {
4538
specifiedByUrl: specifiedByURL,
4639
extensions: {
4740
codegenScalarType: 'string',
41+
jsonSchema: {
42+
title: 'Cuid',
43+
type: 'string',
44+
pattern: CUID_REGEX.source,
45+
},
4846
},
4947
} as GraphQLScalarTypeConfig<string, string>;
5048

51-
export const GraphQLCuid: GraphQLScalarType =
52-
/*#__PURE__*/ new GraphQLScalarType(GraphQLCuidConfig);
49+
export const GraphQLCuid: GraphQLScalarType = /*#__PURE__*/ new GraphQLScalarType(GraphQLCuidConfig);

src/scalars/Currency.ts

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1-
import {
2-
Kind,
3-
GraphQLError,
4-
GraphQLScalarType,
5-
GraphQLScalarTypeConfig,
6-
} from 'graphql';
1+
import { Kind, GraphQLError, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql';
72

8-
const validate = (value: any) => {
9-
const CURRENCY_REGEX =
10-
/^(AED|AFN|ALL|AMD|ANG|AOA|ARS|AUD|AWG|AZN|BAM|BBD|BDT|BGN|BHD|BIF|BMD|BND|BOB|BOV|BRL|BSD|BTN|BWP|BYN|BZD|CAD|CDF|CHE|CHF|CHW|CLF|CLP|CNY|COP|COU|CRC|CUC|CUP|CVE|CZK|DJF|DKK|DOP|DZD|EGP|ERN|ETB|EUR|FJD|FKP|GBP|GEL|GHS|GIP|GMD|GNF|GTQ|GYD|HKD|HNL|HRK|HTG|HUF|IDR|ILS|INR|IQD|IRR|ISK|JMD|JOD|JPY|KES|KGS|KHR|KMF|KPW|KRW|KWD|KYD|KZT|LAK|LBP|LKR|LRD|LSL|LYD|MAD|MDL|MGA|MKD|MMK|MNT|MOP|MRU|MUR|MVR|MWK|MXN|MXV|MYR|MZN|NAD|NGN|NIO|NOK|NPR|NZD|OMR|PAB|PEN|PGK|PHP|PKR|PLN|PYG|QAR|RON|RSD|RUB|RWF|SAR|SBD|SCR|SDG|SEK|SGD|SHP|SLL|SOS|SRD|SSP|STN|SVC|SYP|SZL|THB|TJS|TMT|TND|TOP|TRY|TTD|TWD|TZS|UAH|UGX|USD|USN|UYI|UYU|UYW|UZS|VES|VND|VUV|WST|XAF|XAG|XAU|XBA|XBB|XBC|XBD|XCD|XDR|XOF|XPD|XPF|XPT|XSU|XTS|XUA|XXX|YER|ZAR|ZMW|ZWL)$/i;
3+
const CURRENCY_REGEX =
4+
/^(AED|AFN|ALL|AMD|ANG|AOA|ARS|AUD|AWG|AZN|BAM|BBD|BDT|BGN|BHD|BIF|BMD|BND|BOB|BOV|BRL|BSD|BTN|BWP|BYN|BZD|CAD|CDF|CHE|CHF|CHW|CLF|CLP|CNY|COP|COU|CRC|CUC|CUP|CVE|CZK|DJF|DKK|DOP|DZD|EGP|ERN|ETB|EUR|FJD|FKP|GBP|GEL|GHS|GIP|GMD|GNF|GTQ|GYD|HKD|HNL|HRK|HTG|HUF|IDR|ILS|INR|IQD|IRR|ISK|JMD|JOD|JPY|KES|KGS|KHR|KMF|KPW|KRW|KWD|KYD|KZT|LAK|LBP|LKR|LRD|LSL|LYD|MAD|MDL|MGA|MKD|MMK|MNT|MOP|MRU|MUR|MVR|MWK|MXN|MXV|MYR|MZN|NAD|NGN|NIO|NOK|NPR|NZD|OMR|PAB|PEN|PGK|PHP|PKR|PLN|PYG|QAR|RON|RSD|RUB|RWF|SAR|SBD|SCR|SDG|SEK|SGD|SHP|SLL|SOS|SRD|SSP|STN|SVC|SYP|SZL|THB|TJS|TMT|TND|TOP|TRY|TTD|TWD|TZS|UAH|UGX|USD|USN|UYI|UYU|UYW|UZS|VES|VND|VUV|WST|XAF|XAG|XAU|XBA|XBB|XBC|XBD|XCD|XDR|XOF|XPD|XPF|XPT|XSU|XTS|XUA|XXX|YER|ZAR|ZMW|ZWL)$/i;
115

6+
const validate = (value: any) => {
127
if (typeof value !== 'string') {
138
throw new TypeError(`Value is not string: ${value}`);
149
}
@@ -37,9 +32,7 @@ export const GraphQLCurrencyConfig = /*#__PURE__*/ {
3732

3833
parseLiteral(ast) {
3934
if (ast.kind !== Kind.STRING) {
40-
throw new GraphQLError(
41-
`Can only validate strings as a currency but got a: ${ast.kind}`,
42-
);
35+
throw new GraphQLError(`Can only validate strings as a currency but got a: ${ast.kind}`);
4336
}
4437

4538
return validate(ast.value);
@@ -49,8 +42,12 @@ export const GraphQLCurrencyConfig = /*#__PURE__*/ {
4942
specifiedByUrl: specifiedByURL,
5043
extensions: {
5144
codegenScalarType: 'string',
45+
jsonSchema: {
46+
title: 'Currency',
47+
type: 'string',
48+
pattern: CURRENCY_REGEX.source,
49+
},
5250
},
5351
} as GraphQLScalarTypeConfig<string, string>;
5452

55-
export const GraphQLCurrency: GraphQLScalarType =
56-
/*#__PURE__*/ new GraphQLScalarType(GraphQLCurrencyConfig);
53+
export const GraphQLCurrency: GraphQLScalarType = /*#__PURE__*/ new GraphQLScalarType(GraphQLCurrencyConfig);

src/scalars/DID.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
import {
2-
GraphQLScalarType,
3-
Kind,
4-
GraphQLError,
5-
GraphQLScalarTypeConfig,
6-
} from 'graphql';
1+
import { GraphQLScalarType, Kind, GraphQLError, GraphQLScalarTypeConfig } from 'graphql';
72

83
// See: https://www.w3.org/TR/2021/PR-did-core-20210803/#did-syntax
9-
const DID_REGEX =
10-
/^did:[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+:[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+$/;
4+
const DID_REGEX = /^did:[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+:[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+$/;
115

126
const validate = (value: any) => {
137
if (typeof value !== 'string') {
@@ -35,9 +29,7 @@ export const GraphQLDIDConfig = {
3529

3630
parseLiteral(ast) {
3731
if (ast.kind !== Kind.STRING) {
38-
throw new GraphQLError(
39-
`Can only validate strings as DID but got a: ${ast.kind}`,
40-
);
32+
throw new GraphQLError(`Can only validate strings as DID but got a: ${ast.kind}`);
4133
}
4234

4335
return validate(ast.value);
@@ -47,8 +39,12 @@ export const GraphQLDIDConfig = {
4739
specifiedByUrl: specifiedByURL,
4840
extensions: {
4941
codegenScalarType: 'string',
42+
jsonSchema: {
43+
title: 'DID',
44+
type: 'string',
45+
pattern: DID_REGEX.source,
46+
},
5047
},
5148
} as GraphQLScalarTypeConfig<string, string>;
5249

53-
export const GraphQLDID: GraphQLScalarType =
54-
/*#__PURE__*/ new GraphQLScalarType(GraphQLDIDConfig);
50+
export const GraphQLDID: GraphQLScalarType = /*#__PURE__*/ new GraphQLScalarType(GraphQLDIDConfig);

0 commit comments

Comments
 (0)