Skip to content

Commit

Permalink
Merge pull request #28 from thuoe/refactor/gql-enum-generating
Browse files Browse the repository at this point in the history
fix: use gql enums as directive arguments
  • Loading branch information
thuoe authored Apr 6, 2024
2 parents c409ed6 + bcfd478 commit 74170f0
Show file tree
Hide file tree
Showing 5 changed files with 32 additions and 50 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,10 @@ You can use the `@currency` directive to fetch the latest exchange rate of a giv
type Car {
make: String
model: String
price: String @currency(from: "GBP", to: "USD")
price: String @currency(from: GBP, to: USD)
}
```

The amount can either resolved with the scalar types `String` or `Float`
The field can either be resolved with scalar types `String` or `Float`

The valid currency codes to use as part of the directive's arguments can be found [here](./src/types.ts).
2 changes: 1 addition & 1 deletion server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const typeDefs = String.raw`#graphql
firstName: String @regex(pattern: "(Eddie|Sam)")
lastName: String @regex(pattern: "\\b[A-Z]\\w+\\b")
age: Int @cache(key: "user_age", ttl: 3000)
amount: String @currency(from: "GBP", to: "USD")
amount: String @currency(from: GBP, to: USD)
}
type Query {
Expand Down
23 changes: 3 additions & 20 deletions src/directives/currency.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fetchDirective } from '@src/utils'
import { fetchDirective, generateGraphQLEnum } from '@src/utils'
import { MapperKind, mapSchema } from '@graphql-tools/utils'
import { GraphQLError, GraphQLSchema, defaultFieldResolver } from 'graphql'
import { CurrencyCode } from '@src/types'
Expand All @@ -9,22 +9,6 @@ type CurrencyDirectiveArgs = {
to: CurrencyCode
}

const generateGraphQLEnum = (origin: Record<string, string>) => {
const formattedCodes = Object.keys(origin).map((code) => {
return `${code} \n`
}).join('')
const result = `enum CurrencyCode {\n${formattedCodes} }`
return result
}

const validateCodes = (...codes: string[]) => {
const validCodes = Object.keys(CurrencyCode)
const invalidCodes = codes.filter(code => !validCodes.includes(code))
if (invalidCodes.length > 0) {
throw new GraphQLError(`Currency codes: ${invalidCodes} are not valid!`)
}
}

export const fetchAmount = async ({ originalAmount, from, to }: { originalAmount: number, from: CurrencyCode, to: CurrencyCode }) => {
try {
const response = await fetch(`https://www.google.com/search?q=${originalAmount}+${from}+to+${to}+&hl=en`)
Expand All @@ -40,8 +24,8 @@ export const fetchAmount = async ({ originalAmount, from, to }: { originalAmount

const currencyDirective = (directiveName: string = 'currency') => {
return {
currencyDirectiveTypeDefs: `directive @${directiveName} (from: String!, to: String!) on FIELD_DEFINITION
${generateGraphQLEnum(CurrencyCode)}
currencyDirectiveTypeDefs: `directive @${directiveName} (from: CurrencyCode!, to: CurrencyCode!) on FIELD_DEFINITION
${generateGraphQLEnum('CurrencyCode', CurrencyCode)}
`,
currencyDirectiveTransformer: (schema: GraphQLSchema) => mapSchema(schema, {
[MapperKind.OBJECT_FIELD]: (fieldConfig) => {
Expand All @@ -52,7 +36,6 @@ const currencyDirective = (directiveName: string = 'currency') => {
return {
...fieldConfig,
resolve: async (source, args, context, info) => {
validateCodes(from, to)
const { fieldName, returnType } = info
const type = returnType.toString()
if (type !== 'String' && type !== 'Float') {
Expand Down
8 changes: 8 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,11 @@ export const fetchDirective = <T extends Record<string, unknown>>(schema: GraphQ
}
return null
}

export const generateGraphQLEnum = (enumName: string, origin: Record<string, string>) => {
const formattedValues = Object.keys(origin).map((key) => {
return `${key} \n`
}).join('')
const result = `enum ${enumName} {\n${formattedValues} }`
return result
}
45 changes: 18 additions & 27 deletions test/currency.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ describe('@currency directive', () => {
const schema = buildSchema({
typeDefs: [
`type User {
amount: String @currency(from: "${from}", to: "${to}")
amount: String @currency(from: ${from}, to: ${to})
}
type Query {
Expand All @@ -70,33 +70,24 @@ describe('@currency directive', () => {
})

it('will throw an error if currency code(s) are not recognized', async () => {
const from = 'BLAH'
const to = 'HMMMM'
const schema = buildSchema({
typeDefs: [
`type User {
amount: String @currency(from: "${from}", to: "${to}")
}
type Query {
user: User
}
const buildFaultySchema = () =>
buildSchema({
typeDefs: [
`
type User {
amount: String @currency(from: BOB, to: CAT)
}
type Query {
user: User
}
`,
currencyDirectiveTypeDefs
],
resolvers,
transformers: [currencyDirectiveTransformer]
})

testServer = new ApolloServer({ schema })

const response = await testServer.executeOperation<{ user: { amount: string } }>({
query: testQuery
})
currencyDirectiveTypeDefs
],
resolvers,
transformers: [currencyDirectiveTransformer]
})

assert(response.body.kind === 'single')
expect(response.body.singleResult.errors).toBeDefined();
expect(response.body.singleResult.errors[0].message).toBe(`Currency codes: ${from},${to} are not valid!`);
expect(directive.fetchAmount).not.toHaveBeenCalled()
expect(buildFaultySchema).toThrow()
})
})

0 comments on commit 74170f0

Please sign in to comment.