Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
222 changes: 112 additions & 110 deletions src/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,160 +10,162 @@ import { normalizeTypeDescriptors, actionWith } from './util';
* @access public
*/
function apiMiddleware({ getState }) {
return next => async action => {
return next => action => {
// Do not process actions without an [RSAA] property
if (!isRSAA(action)) {
return next(action);
}

// Try to dispatch an error request FSA for invalid RSAAs
const validationErrors = validateRSAA(action);
if (validationErrors.length) {
const callAPI = action[RSAA];
if (callAPI.types && Array.isArray(callAPI.types)) {
let requestType = callAPI.types[0];
if (requestType && requestType.type) {
requestType = requestType.type;
return (async () => {
// Try to dispatch an error request FSA for invalid RSAAs
const validationErrors = validateRSAA(action);
if (validationErrors.length) {
const callAPI = action[RSAA];
if (callAPI.types && Array.isArray(callAPI.types)) {
let requestType = callAPI.types[0];
if (requestType && requestType.type) {
requestType = requestType.type;
}
next({
type: requestType,
payload: new InvalidRSAA(validationErrors),
error: true
});
}
next({
type: requestType,
payload: new InvalidRSAA(validationErrors),
error: true
});
}
return;
}

// Parse the validated RSAA action
const callAPI = action[RSAA];
var { endpoint, headers, options = {} } = callAPI;
const { method, body, credentials, bailout, types } = callAPI;
const [requestType, successType, failureType] = normalizeTypeDescriptors(
types
);

// Should we bail out?
try {
if (
(typeof bailout === 'boolean' && bailout) ||
(typeof bailout === 'function' && bailout(getState()))
) {
return;
}
} catch (e) {
return next(
await actionWith(
{
...requestType,
payload: new RequestError('[RSAA].bailout function failed'),
error: true
},
[action, getState()]
)

// Parse the validated RSAA action
const callAPI = action[RSAA];
var { endpoint, headers, options = {} } = callAPI;
const { method, body, credentials, bailout, types } = callAPI;
const [requestType, successType, failureType] = normalizeTypeDescriptors(
types
);
}

// Process [RSAA].endpoint function
if (typeof endpoint === 'function') {
// Should we bail out?
try {
endpoint = endpoint(getState());
if (
(typeof bailout === 'boolean' && bailout) ||
(typeof bailout === 'function' && bailout(getState()))
) {
return;
}
} catch (e) {
return next(
await actionWith(
{
...requestType,
payload: new RequestError('[RSAA].endpoint function failed'),
payload: new RequestError('[RSAA].bailout function failed'),
error: true
},
[action, getState()]
)
);
}
}

// Process [RSAA].headers function
if (typeof headers === 'function') {
// Process [RSAA].endpoint function
if (typeof endpoint === 'function') {
try {
endpoint = endpoint(getState());
} catch (e) {
return next(
await actionWith(
{
...requestType,
payload: new RequestError('[RSAA].endpoint function failed'),
error: true
},
[action, getState()]
)
);
}
}

// Process [RSAA].headers function
if (typeof headers === 'function') {
try {
headers = headers(getState());
} catch (e) {
return next(
await actionWith(
{
...requestType,
payload: new RequestError('[RSAA].headers function failed'),
error: true
},
[action, getState()]
)
);
}
}

// Process [RSAA].options function
if (typeof options === 'function') {
try {
options = options(getState());
} catch (e) {
return next(
await actionWith(
{
...requestType,
payload: new RequestError('[RSAA].options function failed'),
error: true
},
[action, getState()]
)
);
}
}

// We can now dispatch the request FSA
if (
typeof requestType.payload === 'function' ||
typeof requestType.meta === 'function'
) {
next(await actionWith(requestType, [action, getState()]));
} else {
next(requestType);
}

try {
headers = headers(getState());
// Make the API call
var res = await fetch(endpoint, {
...options,
method,
body,
credentials,
headers: headers || {}
});
} catch (e) {
// The request was malformed, or there was a network error
return next(
await actionWith(
{
...requestType,
payload: new RequestError('[RSAA].headers function failed'),
payload: new RequestError(e.message),
error: true
},
[action, getState()]
)
);
}
}

// Process [RSAA].options function
if (typeof options === 'function') {
try {
options = options(getState());
} catch (e) {
// Process the server response
if (res.ok) {
return next(await actionWith(successType, [action, getState(), res]));
} else {
return next(
await actionWith(
{
...requestType,
payload: new RequestError('[RSAA].options function failed'),
...failureType,
error: true
},
[action, getState()]
[action, getState(), res]
)
);
}
}

// We can now dispatch the request FSA
if (
typeof requestType.payload === 'function' ||
typeof requestType.meta === 'function'
) {
next(await actionWith(requestType, [action, getState()]));
} else {
next(requestType);
}

try {
// Make the API call
var res = await fetch(endpoint, {
...options,
method,
body,
credentials,
headers: headers || {}
});
} catch (e) {
// The request was malformed, or there was a network error
return next(
await actionWith(
{
...requestType,
payload: new RequestError(e.message),
error: true
},
[action, getState()]
)
);
}

// Process the server response
if (res.ok) {
return next(await actionWith(successType, [action, getState(), res]));
} else {
return next(
await actionWith(
{
...failureType,
error: true
},
[action, getState(), res]
)
);
}
})();
};
}

Expand Down
24 changes: 24 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -846,6 +846,30 @@ test('apiMiddleware must pass actions without an [RSAA] property to the next han
actionHandler(anAction);
});

test("apiMiddleware mustn't return a promise on actions without a [RSAA] property", t => {
const anAction = {};
const doGetState = () => {};
const nextHandler = apiMiddleware({ getState: doGetState });
const doNext = action => action;
const actionHandler = nextHandler(doNext);

t.plan(1);
const noProm = actionHandler(anAction);
t.notEqual(typeof noProm.then, 'function', 'no promise returned');
});

test('apiMiddleware must return a promise on actions with a [RSAA] property', t => {
const anAction = { [RSAA]: {} };
const doGetState = () => {};
const nextHandler = apiMiddleware({ getState: doGetState });
const doNext = action => action;
const actionHandler = nextHandler(doNext);

t.plan(1);
const yesProm = actionHandler(anAction);
t.equal(typeof yesProm.then, 'function', 'promise returned');
});

test('apiMiddleware must dispatch an error request FSA for an invalid RSAA with a string request type', t => {
const anAction = {
[RSAA]: {
Expand Down