Skip to content

Commit 2c777e9

Browse files
Merge pull request #25 from rynobax/master
Adding conditional (and/or) helpers
2 parents 47ce04a + 1a06a0e commit 2c777e9

File tree

10 files changed

+196
-6
lines changed

10 files changed

+196
-6
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,28 @@ const resolvers = combineResolvers([
168168
export default resolvers;
169169
```
170170

171+
Conditional resovlers:
172+
```javascript
173+
import { and, or } from 'apollo-resolvers';
174+
175+
import isFooResolver from './foo';
176+
import isBarResolver from './bar';
177+
178+
const banResolver = (root, { input }, { models: { UserModel } })=> UserModel.ban(input);
179+
180+
// Will execute banResolver if either isFooResolver or isBarResolver successfully resolve
181+
// If none of the resolvers succeed, the error from the last conditional resolver will
182+
// be returned
183+
const orBanResolver = or(isFooResolver, isBarResolver)(banResolver);
184+
185+
// Will execute banResolver if both isFooResolver and isBarResolver successfully resolve
186+
// If one of the condition resolvers throws an error, it will stop the execution and
187+
// return the error
188+
const andBanResolver = and(isFooResolver, isBarResolver)(banResolver);
189+
190+
// In both cases, conditions are evaluated from left to right
191+
```
192+
171193
## Resolver context
172194

173195
Resolvers are provided a mutable context object that is shared between all resolvers for a given request. A common pattern with GraphQL is inject request-specific model instances into the resolver context for each request. Models frequently reference one another, and unbinding circular references can be a pain. `apollo-resolvers` provides a request context factory that allows you to bind context disposal to server responses, calling a `dispose` method on each model instance attached to the context to do any sort of required reference cleanup necessary to avoid memory leaks:

dist/context.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/helper.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
export declare const combineResolvers: (resolvers?: any[]) => any;
2+
export declare const and: (...conditions: any[]) => (resolver: any) => any;
3+
export declare const or: (...conditions: any[]) => (resolver: any) => (...query: any[]) => Promise<{}>;

dist/helper.js

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/helper.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/promise.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/resolver.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/util.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/helper.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,27 @@ import * as merge from "deepmerge";
33
// Helper function to combine multiple resolver definition hashes into a single hash for consumption by Apollostack's graphql-server
44
export const combineResolvers = (resolvers = []) => resolvers
55
.reduce((combined, resolver) => merge(combined, resolver));
6+
7+
// Accepts multiple authentication resolvers and returns a function which will be called
8+
// if all of the authentication resolvers succeed, or throw an error if one of them fails
9+
export const and = (...conditions) => resolver => {
10+
return conditions.reduceRight((p, c) => {
11+
return c.createResolver(p);
12+
}, resolver)
13+
}
14+
15+
// Accepts multiple authentication resolvers and returns a function which will be called
16+
// if any of the authentication resolvers succeed, or throw an error if all of them fail
17+
export const or = (...conditions) => resolver => (...query) => {
18+
return new Promise((resolve, reject) => {
19+
let limit = conditions.length - 1;
20+
const attempt = (i) =>
21+
conditions[i].createResolver(resolver)(...query)
22+
.then(res => resolve(res))
23+
.catch(err => {
24+
if(i === limit) reject(err);
25+
else attempt(i + 1);
26+
});
27+
attempt(0);
28+
});
29+
}

test/unit/helper_spec.js

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { expect } from 'chai';
2+
import { stub } from 'sinon';
23

34
import {
4-
combineResolvers
5+
combineResolvers, and, or,
56
} from '../../dist/helper';
7+
import { createResolver } from '../../dist/resolver';
68

79
describe('(unit) src/helper.js', () => {
810
describe('combineResolvers', () => {
@@ -51,4 +53,125 @@ describe('(unit) src/helper.js', () => {
5153
});
5254
})
5355
});
56+
57+
describe('Conditional resolvers', () => {
58+
const conditionalErr = new Error('conditional error');
59+
const successResolver = createResolver(() => null, () => null);
60+
const failureResolver = createResolver(() => { throw conditionalErr; }, () => null);
61+
62+
describe('and', () => {
63+
it('(true, true) succeeds', () => {
64+
const resolver = and(successResolver, successResolver)(() => true);
65+
return resolver().then(res => expect(res).to.be.true);
66+
});
67+
68+
it('(false, true) throws', () => {
69+
const resolver = and(failureResolver, successResolver)(() => true);
70+
return resolver().catch(err => expect(err).to.equal(conditionalErr));
71+
});
72+
73+
it('(false, false) throws', () => {
74+
const resolver = and(failureResolver, failureResolver)(() => true);
75+
return resolver().catch(err => expect(err).to.equal(conditionalErr));
76+
});
77+
78+
it('stops evaluating resolvers after first failure', () => {
79+
const r1 = {
80+
handle: () => { throw conditionalErr },
81+
error: () => null,
82+
};
83+
84+
const r2 = {
85+
handle: () => null,
86+
error: () => null,
87+
};
88+
89+
stub(r1, 'handle', r1.handle);
90+
stub(r2, 'handle', r2.handle);
91+
92+
const resolver = or(
93+
createResolver(r1.handle, r1.error),
94+
createResolver(r2.handle, r2.error)
95+
)(() => true);
96+
return resolver().catch(err => {
97+
expect(err).to.equal(conditionalErr)
98+
expect(r1.handle.calledOnce).to.be.true;
99+
expect(r2.handle.notCalled).to.be.true;
100+
});
101+
});
102+
103+
it('only calls the result resolver once', () => {
104+
const r1 = {
105+
handle: () => true,
106+
error: () => null,
107+
};
108+
109+
stub(r1, 'handle', r1.handle);
110+
111+
const resolver = and(successResolver, successResolver)(createResolver(r1.handle, r1.error));
112+
return resolver().then(res => {
113+
expect(res).to.be.true;
114+
expect(r1.handle.calledOnce).to.be.true;
115+
});
116+
});
117+
});
118+
119+
describe('or', () => {
120+
it('(true, true) succeeds', () => {
121+
const resolver = or(successResolver, successResolver)(() => true);
122+
return resolver().then(res => expect(res).to.be.true);
123+
});
124+
125+
it('(false, true) succeeds', () => {
126+
const resolver = or(failureResolver, successResolver)(() => true);
127+
return resolver().then(res => expect(res).to.be.true);
128+
});
129+
130+
it('(false, false) throws', () => {
131+
const resolver = or(failureResolver, failureResolver)(() => true);
132+
return resolver().catch(err => expect(err).to.equal(conditionalErr));
133+
});
134+
135+
it('stops evaluating resolvers after first success', () => {
136+
const r1 = {
137+
handle: () => null,
138+
error: () => null,
139+
};
140+
141+
const r2 = {
142+
handle: () => null,
143+
error: () => null,
144+
};
145+
146+
stub(r1, 'handle', r1.handle);
147+
stub(r2, 'handle', r2.handle);
148+
149+
const resolver = or(
150+
createResolver(r1.handle, r1.error),
151+
createResolver(r2.handle, r2.error)
152+
)(() => true);
153+
return resolver().then(res => {
154+
expect(res).to.be.true;
155+
expect(r1.handle.calledOnce).to.be.true;
156+
expect(r2.handle.notCalled).to.be.true;
157+
});
158+
});
159+
160+
it('only calls the result resolver once', () => {
161+
const r1 = {
162+
handle: () => true,
163+
error: () => null,
164+
};
165+
166+
stub(r1, 'handle', r1.handle);
167+
168+
const resolver = or(failureResolver, successResolver)(createResolver(r1.handle, r1.error));
169+
return resolver().then(res => {
170+
expect(res).to.be.true;
171+
expect(r1.handle.calledOnce).to.be.true;
172+
});
173+
});
174+
});
175+
176+
});
54177
});

0 commit comments

Comments
 (0)