-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(decimalcurrency): DecimalCurrency class as value for graphql scalar
BREAKING CHANGE: DecimalCurrency is completely reimplemented
- Loading branch information
Showing
5 changed files
with
358 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { DecimalCurrency } from '..'; | ||
|
||
const validStrRepresentations: string[] = ['0 USD', '-5 CZK', '20 EUR', '0.456 BTC']; | ||
|
||
describe('DecimalCurrency', () => { | ||
describe('from', () => { | ||
it('accepts string value', () => { | ||
validStrRepresentations.forEach((str) => { | ||
const obj = DecimalCurrency.from(str); | ||
expect(obj).toBeInstanceOf(DecimalCurrency); | ||
expect(obj && obj.toString()).toEqual(str); | ||
}); | ||
}); | ||
|
||
it('accepts object value', () => { | ||
const obj = DecimalCurrency.from({ | ||
decimal: 123, | ||
currency: 'CZK', | ||
}); | ||
expect(obj).toBeInstanceOf(DecimalCurrency); | ||
// expect(obj && obj.toString()).toEqual(str); | ||
}); | ||
}); | ||
}); |
158 changes: 158 additions & 0 deletions
158
src/decimalcurrency/__tests__/graphql-decimalcurrency-type.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
// tslint:disable:no-unsafe-any | ||
import { GraphQLBoolean, GraphQLInt, GraphQLObjectType, GraphQLSchema, graphqlSync } from 'graphql'; | ||
|
||
import { DecimalCurrency, GraphQLDecimalCurrencyType } from '..'; | ||
|
||
describe('GraphQLDecimalCurrencyType', () => { | ||
let schema: GraphQLSchema; | ||
|
||
beforeEach(() => { | ||
schema = new GraphQLSchema({ | ||
query: new GraphQLObjectType({ | ||
name: 'Query', | ||
fields: { | ||
value: { | ||
type: GraphQLDecimalCurrencyType, | ||
args: { | ||
arg: { | ||
type: GraphQLDecimalCurrencyType, | ||
}, | ||
}, | ||
resolve: (_, { arg }) => arg, | ||
}, | ||
argIsDecimalCurrency: { | ||
type: GraphQLBoolean, | ||
args: { | ||
arg: { | ||
type: GraphQLDecimalCurrencyType, | ||
}, | ||
}, | ||
resolve: (_, { arg }) => arg instanceof DecimalCurrency, | ||
}, | ||
}, | ||
}), | ||
types: [GraphQLInt], | ||
}); | ||
}); | ||
|
||
describe('serialize', () => { | ||
it('should support serialization', () => { | ||
['0 USD', '-1 CZK', '19.95 USD'].forEach((value) => { | ||
expect(GraphQLDecimalCurrencyType.serialize(DecimalCurrency.from(value))).toEqual(value); | ||
}); | ||
}); | ||
|
||
it('should support serialization', () => { | ||
['0 USD', '-1 CZK', '10.01 USD'].forEach((value) => { | ||
expect(GraphQLDecimalCurrencyType.serialize(value)).toEqual(value); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('parseValue', () => { | ||
it('should support parsing string values', () => { | ||
const { data } = graphqlSync( | ||
schema, | ||
'query ($arg: DecimalCurrency!) { value(arg: $arg) argIsDecimalCurrency(arg: $arg) }', | ||
null, | ||
null, | ||
{ | ||
arg: '123.40 EUR', | ||
}, | ||
); | ||
|
||
expect(data && data.value).toEqual('123.40 EUR'); | ||
expect(data && data.argIsDecimalCurrency).toStrictEqual(true); | ||
}); | ||
|
||
it('should support parsing object values', () => { | ||
const { data, errors } = graphqlSync( | ||
schema, | ||
`query ( | ||
$arg: DecimalCurrency! | ||
# $int: Int, $float: Float, $str: String, $strCode:String | ||
) { | ||
val1: value(arg: $arg) | ||
# val2: value(arg: { decimal: $str currency: $strCode } ) # NOT WORK! | ||
# val3: value(arg: { decimal: $int currency: CZK } ) # NOT WORK! | ||
# val4: value(arg: { decimal: $float currency: USD } ) # NOT WORK! | ||
argIsDecimalCurrency(arg: $arg) | ||
}`, | ||
null, | ||
null, | ||
{ | ||
arg: { decimal: '123.40', currency: 'EUR' }, | ||
// int: 123, float: 19.95, str: '5678.90', strCode: 'USD', | ||
}, | ||
); | ||
|
||
expect(errors).toBeFalsy(); | ||
expect(data && data.val1).toEqual('123.40 EUR'); | ||
expect(data && data.argIsDecimalCurrency).toStrictEqual(true); | ||
}); | ||
}); | ||
|
||
describe('parseLiteral', () => { | ||
it('should handle string literals', () => { | ||
const { data } = graphqlSync( | ||
schema, | ||
` | ||
{ | ||
value(arg: "3.14 USD") | ||
} | ||
`, | ||
); | ||
expect(data).toEqual({ | ||
value: '3.14 USD', | ||
}); | ||
}); | ||
|
||
it('should handle object literals', () => { | ||
const { data, errors } = graphqlSync( | ||
schema, | ||
`query { | ||
val1: value(arg: { decimal: "0.00000001" currency: "BTC" }) | ||
val2: value(arg: { decimal: 0.00000002 currency: BTC }) | ||
val3: value(arg: { decimal: 0.00000003 currency: BTC }) | ||
val4: value(arg: { decimal: 4 currency: CZK }) | ||
val5: value(arg: { decimal: 3.14 currency: EUR }) | ||
}`, | ||
); | ||
|
||
expect(errors).toBeFalsy(); | ||
expect(data).toEqual({ | ||
val1: '0.00000001 BTC', | ||
val2: '0.00000002 BTC', | ||
val3: '0.00000003 BTC', | ||
val4: '4 CZK', | ||
val5: '3.14 EUR', | ||
}); | ||
}); | ||
|
||
it('should handle null literals', () => { | ||
const { data } = graphqlSync( | ||
schema, | ||
` | ||
{ | ||
value(arg: null) | ||
} | ||
`, | ||
); | ||
expect(data).toEqual({ | ||
value: null, | ||
}); | ||
}); | ||
|
||
it('should reject invalid literals', () => { | ||
const { data } = graphqlSync( | ||
schema, | ||
` | ||
{ | ||
value(arg: INVALID) | ||
} | ||
`, | ||
); | ||
expect(data).toBeUndefined(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
type Decimal = string & { __tag: 'Decimal' }; | ||
type Currency = string & { __tag: 'Currency' }; | ||
|
||
interface DecimalCurrencyValue { | ||
decimal: Decimal; | ||
currency: Currency; | ||
} | ||
|
||
export const DecimalRegExp = /^[+-]?\d+(?:\.\d+)?$/; | ||
export const CurrencyRegExp = /^[A-Z]+$/; | ||
export const DecimalCurrencyRegExp = /^([+-]?\d+(?:\.\d+)?)\s+([A-Z]+)$/; | ||
|
||
function isDecimal(value: string): value is Decimal { | ||
return DecimalRegExp.test(value); | ||
} | ||
|
||
function isCurrency(value: string): value is Currency { | ||
return CurrencyRegExp.test(value); | ||
} | ||
|
||
export default class DecimalCurrency implements DecimalCurrencyValue { | ||
static from(value: { decimal: Decimal; currency: Currency } | DecimalCurrency): DecimalCurrency; | ||
static from( | ||
value: string | { decimal?: string | number | BigInteger; currency?: string }, | ||
): DecimalCurrency | undefined; | ||
static from(value: null | undefined): undefined; | ||
static from(value: unknown): DecimalCurrency | undefined { | ||
if (value === null || typeof value === 'undefined') { | ||
return; | ||
} | ||
if (value instanceof DecimalCurrency) { | ||
return new DecimalCurrency(value.decimal, value.currency, true); | ||
} | ||
if (typeof value === 'string') { | ||
return DecimalCurrency.parse(value); | ||
} | ||
if (typeof value === 'object' && value) { | ||
const { decimal, currency } = value as DecimalCurrencyValue; | ||
if (typeof decimal !== 'undefined' && currency) { | ||
return new DecimalCurrency(decimal, currency); | ||
} | ||
|
||
return; | ||
} | ||
} | ||
|
||
static parse(value: string): DecimalCurrency | undefined { | ||
if (typeof value === 'string') { | ||
const match = value.match(DecimalCurrencyRegExp); | ||
if (match) { | ||
return new DecimalCurrency(match[1] as Decimal, match[2] as Currency, true); | ||
} | ||
} | ||
} | ||
|
||
decimal: Decimal; | ||
currency: Currency; | ||
|
||
constructor(decimalCurrency: string); | ||
constructor(decimal: string, currency: string); // tslint:disable-line:unified-signatures | ||
constructor(decimal: Decimal, currency: Currency, trust: true); // tslint:disable-line:unified-signatures | ||
constructor( | ||
decimal: Decimal | string | number | any, | ||
currency?: Currency | string, | ||
trust?: boolean, | ||
) { | ||
const strDecimal = String(decimal); | ||
if (currency) { | ||
if (trust) { | ||
this.decimal = strDecimal as Decimal; | ||
this.currency = currency as Currency; | ||
} else { | ||
if (isDecimal(strDecimal) && isCurrency(currency)) { | ||
this.decimal = strDecimal; | ||
this.currency = currency; | ||
} else { | ||
throw new TypeError(`Invalid Decimal format`); | ||
} | ||
} | ||
} else { | ||
const match = strDecimal.match(DecimalCurrencyRegExp); | ||
if (!match) { | ||
throw new TypeError(`Cannot create DecimalCurrency from value '${decimal}'`); | ||
} | ||
this.decimal = match[1] as Decimal; | ||
this.currency = match[2] as Currency; | ||
} | ||
} | ||
|
||
serialize(): string { | ||
return `${this.decimal} ${this.currency}`; | ||
} | ||
|
||
toString(): string { | ||
return this.serialize(); | ||
} | ||
|
||
getDecimal(): Decimal { | ||
return this.decimal; | ||
} | ||
|
||
getCurrency(): Currency { | ||
return this.currency; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export { default as GraphQLDecimalCurrencyType } from './type'; | ||
export { default as DecimalCurrency } from './decimalcurrency'; |
Oops, something went wrong.