Skip to content

Commit

Permalink
Add pathBuilder argument to @rest directive
Browse files Browse the repository at this point in the history
pathBuilder is a user provided function via a graphql variable to build up the path string. A use case it enables is having one or more optional query string parameters e.g:
"/items/filter?minPrice=5&maxPrice=20"
Here minPrice and maxPrice could be omitted

See proposal in #69 and the problem it solves in #67
  • Loading branch information
HeyHugo committed Feb 18, 2018
1 parent 114c4cf commit 606d511
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 10 deletions.
79 changes: 79 additions & 0 deletions src/__tests__/restLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,85 @@ describe('Query single call', () => {
post: { ...postWithNest, __typename: 'Post' },
});
});

it('can build the path using pathBuilder', async () => {
expect.assertions(1);

const link = new RestLink({ uri: '/api' });
const posts = [{ id: '1', title: 'Love apollo' }];
fetchMock.get('/api/posts?status=published', posts);

const postTitleQuery = gql`
query postTitle($pathFunction: any, $status: String) {
posts(status: $status) @rest(type: "Post", pathBuilder: $pathFunction) {
id
title
}
}
`;

function createPostsPath(variables) {
const qs = Object.keys(
variables,
).reduce((acc: string, key: string): string => {
if (variables[key] === null || variables[key] === undefined) {
return acc;
}
if (acc === '') {
return '?' + key + '=' + encodeURIComponent(String(variables[key]));
}
return (
acc + '&' + key + '=' + encodeURIComponent(String(variables[key]))
);
}, '');
return '/posts' + qs;
}

const { data } = await makePromise<Result>(
execute(link, {
operationName: 'postTitle',
query: postTitleQuery,
variables: {
status: 'published',
pathFunction: createPostsPath,
},
}),
);

expect(data).toMatchObject({
posts: [{ ...posts[0], __typename: 'Post' }],
});
});

it('throws if missing both path and pathBuilder', async () => {
expect.assertions(1);

const link = new RestLink({ uri: '/api' });
const post = { id: '1', title: 'Love apollo' };
fetchMock.get('/api/post/1', post);

const postTitleQuery = gql`
query postTitle {
post @rest(type: "Post") {
id
title
}
}
`;

try {
await makePromise<Result>(
execute(link, {
operationName: 'postTitle',
query: postTitleQuery,
}),
);
} catch (error) {
expect(error.message).toBe(
'Parmeter "path" or "pathBuilder" must be set in @rest directive',
);
}
});
});

describe('Query multiple calls', () => {
Expand Down
43 changes: 33 additions & 10 deletions src/restLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,18 @@ export namespace RestLink {
/** What GraphQL type to name the response */
type?: string;
/** What path to use */
path: string;
path?: string;
/**
* What endpoint to select from the map of endpoints available to this link.
* @default `RestLink.endpoints[DEFAULT_ENDPOINT_KEY]`
*/
endpoint?: string;
/**
* Optional function that constructs a request path out of the Environmental state
* when processing this @rest(...) call.
* @default function that produces a request path string from the args.
*/
pathBuilder?: (args: object) => string;
/**
* Optional method that constructs a RequestBody out of the Environmental state
* when processing this @rest(...) call.
Expand Down Expand Up @@ -387,19 +393,36 @@ const resolver: Resolver = async (
typePatcher,
fieldNameDenormalizer: linkLevelNameDenormalizer,
} = context;
const { path, endpoint } = directives.rest as RestLink.DirectiveOptions;
let {
path,
endpoint,
pathBuilder,
} = directives.rest as RestLink.DirectiveOptions;
const uri = getURIFromEndpoints(endpoints, endpoint);
try {
const argsWithExport = { ...args, ...exportVariables };
let pathWithParams = Object.keys(argsWithExport).reduce(
(acc, e) => replaceParam(acc, e, argsWithExport[e]),
path,
);
if (pathWithParams.includes(':')) {
throw new Error(
'Missing params to run query, specify it in the query params or use an export directive',
);

if (!pathBuilder) {
if (!path) {
throw new Error(
'Parmeter "path" or "pathBuilder" must be set in @rest directive',
);
}
pathBuilder = (args: object): string => {
const pathWithParams = Object.keys(argsWithExport).reduce(
(acc, e) => replaceParam(acc, e, argsWithExport[e]),
path,
);
if (pathWithParams.includes(':')) {
throw new Error(
'Missing params to run query, specify it in the query params or use an export directive',
);
}
return pathWithParams;
};
}
const pathWithParams = pathBuilder(argsWithExport);

let {
method,
type,
Expand Down

0 comments on commit 606d511

Please sign in to comment.