Skip to content

Commit

Permalink
[E4E-0]: Allow $headers to be included in variables to queries
Browse files Browse the repository at this point in the history
  • Loading branch information
TheCleric committed Jan 26, 2022
1 parent 2e42d47 commit d5585ab
Show file tree
Hide file tree
Showing 3 changed files with 137 additions and 16 deletions.
2 changes: 2 additions & 0 deletions lib/types/restSchema/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export type RestResponse<T> = T extends IRestEndpoint<infer U, unknown> ? U : ne

export type RestRequest<T> = T extends IRestEndpoint<unknown, infer U> ? U : never;

export type WithHeaders<T> = T & { $headers?: Record<string, string> };

// Allow for either:
// 1. All fields to put into an input object
// or
Expand Down
111 changes: 111 additions & 0 deletions lib/useRestQuery/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,48 @@ describe('useRestQuery Library', () => {
expect(print(generatedNode)).toContain(dummyEndpoint.gql);
});

it('wrapRestMutation should create a function that allows $headers', async () => {
const testToken = 'TEST_TOKEN';
const mockResult = {
called: true,
client: new MockApolloClient() as unknown as ApolloClient<unknown>,
data: { refreshToken: { sessionToken: testToken } },
loading: false,
};

useMutationMock.mockReturnValue([async (): Promise<typeof mockResult> => Promise.resolve(mockResult), mockResult]);

const wrappedRestMutation = wrapRestMutation<'refreshToken'>();
const [refreshToken] = wrappedRestMutation(
gql`
mutation TestMutationB088D1CCBE7C($input: input) {
refreshToken(input: $input) {
sessionToken
}
}
`,
{ endpoint: dummyEndpoint },
);
const result = await refreshToken({
variables: {
$headers: {
'x-api-key': '12345',
},
input: {
testInput: 'test',
},
},
});

expect(result.data?.refreshToken.sessionToken).toBe(testToken);

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
const generatedNode = first(first(useMutationMock.mock.calls)) as DocumentNode;

// Make sure our @rest gql got injected
expect(print(generatedNode)).toContain(dummyEndpoint.gql);
});

it('wrapRestQuery should create a function that returns results via useQuery', () => {
const testToken = 'TEST_TOKEN';
useQueryMock.mockReturnValue({ data: { refreshToken: { sessionToken: testToken } } });
Expand Down Expand Up @@ -242,6 +284,39 @@ describe('useRestQuery Library', () => {
expect(print(generatedNode)).toContain('?optional={args.optional}');
});

it('wrapRestQuery should create a function that allows $headers', () => {
const testToken = 'TEST_TOKEN';
useQueryMock.mockReturnValue({ data: { refreshToken: { sessionToken: testToken } } });

const wrappedRestQuery = wrapRestQuery<'refreshToken'>();
const { data } = wrappedRestQuery(
gql`
query TestQuery2067FE118166($input: input) {
refreshToken(input: $input) {
sessionToken
}
}
`,
{
endpoint: dummyEndpoint,
variables: {
$headers: {
'x-api-key': '12345',
},
testInput: 'test',
},
},
);

expect(data?.refreshToken.sessionToken).toBe(testToken);

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
const generatedNode = first(first(useQueryMock.mock.calls)) as DocumentNode;

// Make sure our @rest gql got injected
expect(print(generatedNode)).toContain(dummyEndpoint.gql);
});

it('wrapRestClientQuery should create a function that returns results via client.query', async () => {
const testToken = 'TEST_TOKEN';
const clientMock = new MockApolloClient();
Expand Down Expand Up @@ -375,6 +450,42 @@ describe('useRestQuery Library', () => {
// Make sure our @rest gql got injected without the optional
expect(print(generatedNode)).toContain('?optional={args.optional}');
});

it('wrapRestClientQuery should create a function that allows $headers', async () => {
const testToken = 'TEST_TOKEN';
const clientMock = new MockApolloClient();

clientMock.query.mockReturnValue({ data: { refreshToken: { sessionToken: testToken } } });

const wrappedRestQuery = wrapRestClientQuery<'refreshToken'>();

const { data } = await wrappedRestQuery({
client: clientMock as unknown as ApolloClient<object>,
endpoint: dummyEndpoint,
query: gql`
query TestClientQuery3F3C1DCCA1E2($input: input) {
refreshToken(input: $input) {
sessionToken
}
}
`,
variables: {
$headers: {
'x-api-key': '12345',
},
testInput: 'test',
},
});

expect(data?.refreshToken.sessionToken).toBe(testToken);

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-member-access
const generatedNode = (first(first(clientMock.query.mock.calls)) as Parameters<typeof wrappedRestQuery>[0])
?.query as DocumentNode;

// Make sure our @rest gql got injected
expect(print(generatedNode)).toContain(dummyEndpoint.gql);
});
});

describe('validateQueryAgainstEndpoint', () => {
Expand Down
40 changes: 24 additions & 16 deletions lib/useRestQuery/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@ import { DirectiveNode, FieldNode, OperationDefinitionNode } from 'graphql';
import { print } from 'graphql/language/printer';
import { get } from 'lodash';

import { IEndpointOptions, getSchemaField, Input, InvalidQueryError, IRestEndpoint, NamedGQLResult } from '../types';
import {
IEndpointOptions,
getSchemaField,
Input,
InvalidQueryError,
IRestEndpoint,
NamedGQLResult,
WithHeaders,
} from '../types';

function stripOptionals(gqlQuery: string, variables?: Record<string, unknown>): string {
const args = Array.from(gqlQuery.matchAll(/{args.([^}]*)}/g)).map(m => m[1]);
Expand All @@ -36,7 +44,7 @@ function stripOptionals(gqlQuery: string, variables?: Record<string, unknown>):

export function validateQueryAgainstEndpoint<TName extends string, TData = unknown, TVariables = OperationVariables>(
query: DocumentNode | TypedDocumentNode<NamedGQLResult<TName, TData>, TVariables>,
endpoint: IRestEndpoint<NamedGQLResult<TName, TData>, Input<TVariables>>,
endpoint: IRestEndpoint<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>>,
): void {
if (query.definitions.length !== 1) {
throw new InvalidQueryError('Query must contain exactly one definition', query, endpoint);
Expand Down Expand Up @@ -107,7 +115,7 @@ export function useRestMutation<
TCache extends ApolloCache<any> = ApolloCache<any>,
>(
mutation: DocumentNode | TypedDocumentNode<NamedGQLResult<TName, TData>, TVariables>,
options: IEndpointOptions<NamedGQLResult<TName, TData>, Input<TVariables>> &
options: IEndpointOptions<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>> &
MutationHookOptions<NamedGQLResult<TName, TData>, TVariables, TContext>,
): MutationTuple<NamedGQLResult<TName, TData>, Input<TVariables>, TContext, TCache> {
validateQueryAgainstEndpoint(mutation, options.endpoint);
Expand Down Expand Up @@ -166,19 +174,19 @@ export function wrapRestMutation<TName extends string>() {
>(
mutation: DocumentNode | TypedDocumentNode<TData, TVariables>,
options: IEndpointOptions<TData, TVariables> & MutationHookOptions<TData, TVariables, TContext>,
): MutationTuple<NamedGQLResult<TName, TData>, Input<TVariables>, TContext, TCache> =>
): MutationTuple<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>, TContext, TCache> =>
useRestMutation(
mutation,
options as unknown as IEndpointOptions<NamedGQLResult<TName, TData>, Input<TVariables>> &
MutationHookOptions<NamedGQLResult<TName, TData>, TVariables, TContext>,
options as unknown as IEndpointOptions<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>> &
MutationHookOptions<NamedGQLResult<TName, TData>, WithHeaders<TVariables>, TContext>,
);
}

export function useRestQuery<TName extends string, TData, TVariables>(
query: DocumentNode | TypedDocumentNode<NamedGQLResult<TName, TData>, TVariables>,
options: IEndpointOptions<NamedGQLResult<TName, TData>, Input<TVariables>> &
options: IEndpointOptions<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>> &
QueryHookOptions<NamedGQLResult<TName, TData>, TVariables>,
): QueryResult<NamedGQLResult<TName, TData>, Input<TVariables>> {
): QueryResult<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>> {
validateQueryAgainstEndpoint(query, options.endpoint);
const directives = (query.definitions[0] as OperationDefinitionNode).selectionSet.selections[0]
.directives as DirectiveNode[];
Expand All @@ -195,9 +203,9 @@ export function useRestQuery<TName extends string, TData, TVariables>(
// eslint-disable-next-line no-console, sort-keys
console.debug('useRestQuery', { query: print(query), options });
// eslint-disable-next-line react-hooks/rules-of-hooks
return useQuery<NamedGQLResult<TName, TData>, Input<TVariables>>(
return useQuery<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>>(
query,
options as QueryHookOptions<NamedGQLResult<TName, TData>, Input<TVariables>>,
options as QueryHookOptions<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>>,
);
}

Expand Down Expand Up @@ -231,17 +239,17 @@ export function useRestQuery<TName extends string, TData, TVariables>(
export function wrapRestQuery<TName extends string>() {
return <TData, TVariables>(
query: DocumentNode | TypedDocumentNode<TData, TVariables>,
options: IEndpointOptions<TData, TVariables> & QueryHookOptions<TData, Input<TVariables>>,
): QueryResult<NamedGQLResult<TName, TData>, Input<TVariables>> =>
options: IEndpointOptions<TData, TVariables> & QueryHookOptions<TData, WithHeaders<Input<TVariables>>>,
): QueryResult<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>> =>
useRestQuery(
query,
options as unknown as IEndpointOptions<NamedGQLResult<TName, TData>, Input<TVariables>> &
options as unknown as IEndpointOptions<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>> &
QueryHookOptions<NamedGQLResult<TName, TData>, TVariables>,
);
}

export function useRestClientQuery<TName extends string, TData, TVariables>(
options: IEndpointOptions<NamedGQLResult<TName, TData>, Input<TVariables>> &
options: IEndpointOptions<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>> &
QueryOptions<TVariables, NamedGQLResult<TName, TData>> & { client: ApolloClient<object> },
): Promise<ApolloQueryResult<NamedGQLResult<TName, TData>>> {
validateQueryAgainstEndpoint(options.query, options.endpoint);
Expand Down Expand Up @@ -294,10 +302,10 @@ export function wrapRestClientQuery<TName extends string>() {
return <TData, TVariables>(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
options: IEndpointOptions<TData, TVariables> &
QueryOptions<Input<TVariables>, TData> & { client: ApolloClient<object> },
QueryOptions<WithHeaders<Input<TVariables>>, TData> & { client: ApolloClient<object> },
): Promise<ApolloQueryResult<NamedGQLResult<TName, TData>>> =>
useRestClientQuery(
options as unknown as IEndpointOptions<NamedGQLResult<TName, TData>, Input<TVariables>> &
options as unknown as IEndpointOptions<NamedGQLResult<TName, TData>, WithHeaders<Input<TVariables>>> &
// eslint-disable-next-line @typescript-eslint/no-explicit-any
QueryOptions<TVariables, NamedGQLResult<TName, TData>> & { client: ApolloClient<object> },
);
Expand Down

0 comments on commit d5585ab

Please sign in to comment.