diff --git a/README.md b/README.md index 31e2351bd..fad746049 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,8 @@ scalar RGBA scalar USCurrency +scalar Currency + scalar JSON scalar JSONObject @@ -127,6 +129,7 @@ import { RGBResolver, RGBAResolver, USCurrencyResolver, + CurrencyResolver, JSONResolver, JSONObjectResolver, ObjectIDResolver, @@ -176,6 +179,7 @@ const myResolverMap = { ISBN: ISBNResolver, USCurrency: USCurrencyResolver, + Currency: CurrencyResolver, JSON: JSONResolver, JSONObject: JSONObjectResolver, @@ -581,6 +585,10 @@ A US currency string, such as \$21.25. > Uses [graphql-currency-scalars](https://github.com/abhiaiyer91/graphql-currency-scalars) +### Currency + +A field whose value is an [ISO-4217 currency](https://en.wikipedia.org/wiki/ISO_4217). + ### JSON The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). diff --git a/src/index.ts b/src/index.ts index 6c8043c1d..a4effeeca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,6 +33,7 @@ export { RGB as RGBDefinition, RGBA as RGBADefinition, USCurrency as USCurrencyDefinition, + Currency as CurrencyDefinition, JSON as JSONDefinition, JSONObject as JSONObjectDefinition, IBAN as IBANTypeDefinition, @@ -74,6 +75,7 @@ export { RGB as RGBResolver, RGBA as RGBAResolver, USCurrency as USCurrencyResolver, + Currency as CurrencyResolver, JSON as JSONResolver, JSONObject as JSONObjectResolver, IBAN as IBANResolver, @@ -113,6 +115,7 @@ export { RGB as GraphQLRGB, RGBA as GraphQLRGBA, USCurrency as GraphQLUSCurrency, + Currency as GraphQLCurrency, JSON as GraphQLJSON, JSONObject as GraphQLJSONObject, IBAN as GraphQLIBAN, @@ -154,6 +157,7 @@ export { RGB as RGBMock, RGBA as RGBAMock, USCurrency as USCurrencyMock, + Currency as CurrencyMock, JSON as JSONMock, JSONObject as JSONObjectMock, IBAN as IBANMock, diff --git a/src/mocks.ts b/src/mocks.ts index a2d9e6bbc..320c3caab 100644 --- a/src/mocks.ts +++ b/src/mocks.ts @@ -75,6 +75,7 @@ export const RGBA = () => )}, ${Math.random()})`; export const ISBN = () => `978-3-16-148410-0`; export const USCurrency = () => 1000; +export const Currency = () => 'USD'; export const JSON = () => ({}); export const JSONObject = () => ({}); export const IBAN = () => 'NL55INGB4789170233'; diff --git a/src/resolvers/Currency.ts b/src/resolvers/Currency.ts new file mode 100644 index 000000000..725d461a2 --- /dev/null +++ b/src/resolvers/Currency.ts @@ -0,0 +1,39 @@ +import { Kind, GraphQLError, GraphQLScalarType } from 'graphql'; + +const validate = (value: any) => { + const CURRENCY_REGEX = /^(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; + + if (typeof value !== 'string') { + throw new TypeError(`Value is not string: ${value}`); + } + + if (!CURRENCY_REGEX.test(value)) { + throw new TypeError(`Value is not a valid currency value: ${value}`); + } + + return value; +}; + +export default new GraphQLScalarType({ + name: `Currency`, + + description: `A field whose value is a Currency: https://en.wikipedia.org/wiki/ISO_4217.`, + + serialize(value) { + return validate(value); + }, + + parseValue(value) { + return validate(value); + }, + + parseLiteral(ast) { + if (ast.kind !== Kind.STRING) { + throw new GraphQLError( + `Can only validate strings as a currency but got a: ${ast.kind}`, + ); + } + + return validate(ast.value); + }, +}); diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index f93e7471e..5d28605c0 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -26,6 +26,7 @@ import Port from './Port'; import RGB from './RGB'; import RGBA from './RGBA'; import USCurrency from './USCurrency'; +import Currency from './Currency'; import { JSON, JSONObject } from './JSON'; import IBAN from './IBAN'; import ObjectID from './ObjectID'; @@ -72,6 +73,7 @@ export { RGB, RGBA, USCurrency, + Currency, JSON, JSONObject, IBAN, diff --git a/src/typeDefs.ts b/src/typeDefs.ts index e959f2680..396c755c8 100644 --- a/src/typeDefs.ts +++ b/src/typeDefs.ts @@ -29,6 +29,7 @@ export const RGB = `scalar RGB`; export const RGBA = `scalar RGBA`; export const URL = 'scalar URL'; export const USCurrency = `scalar USCurrency`; +export const Currency = `scalar Currency`; export const UnsignedFloat = 'scalar UnsignedFloat'; export const UnsignedInt = 'scalar UnsignedInt'; @@ -69,6 +70,7 @@ export default [ RGB, RGBA, USCurrency, + Currency, JSON, JSONObject, IBAN, diff --git a/tests/Currency.test.ts b/tests/Currency.test.ts new file mode 100644 index 000000000..5daaaf9bd --- /dev/null +++ b/tests/Currency.test.ts @@ -0,0 +1,58 @@ +/* global describe, it, expect */ +import { Kind } from 'graphql/language'; +import Currency from '../src/resolvers/Currency'; + +describe(`Currency`, () => { + describe(`valid`, () => { + it(`serialize`, () => { + expect(Currency.serialize(`USD`)).toEqual(`USD`); + }); + + it(`parseValue`, () => { + expect(Currency.parseValue(`USD`)).toEqual(`USD`); + }); + + it(`parseLiteral`, () => { + expect( + Currency.parseLiteral( + { + value: `USD`, + kind: Kind.STRING, + }, + {}, + ), + ).toEqual(`USD`); + }); + }); + + describe(`invalid`, () => { + describe(`not a valid currency value`, () => { + it(`serialize`, () => { + expect(() => Currency.serialize(123)).toThrow(/Value is not string/); + expect(() => Currency.serialize(`this is not a currency`)).toThrow( + /Value is not a valid currency value/, + ); + }); + + it(`parseValue`, () => { + expect(() => Currency.serialize(123)).toThrow(/Value is not string/); + expect(() => Currency.parseValue(`this is not a currency`)).toThrow( + /Value is not a valid currency value/, + ); + }); + + it(`parseLiteral`, () => { + expect(() => + Currency.parseLiteral({ value: 123, kind: Kind.INT } as any, {}), + ).toThrow(/Can only validate strings as a currency but got a/); + + expect(() => + Currency.parseLiteral( + { value: `this is not a currency`, kind: Kind.STRING }, + {}, + ), + ).toThrow(/Value is not a valid currency value/); + }); + }); + }); +});