Skip to content

Commit

Permalink
Merge pull request #953 from MoralisWeb3/fix/serialisation-nextjs-req…
Browse files Browse the repository at this point in the history
…uests

fix: serialisation nextjs requests
  • Loading branch information
ErnoW authored Jan 12, 2023
2 parents c26cb16 + 56d4751 commit e00f50f
Show file tree
Hide file tree
Showing 13 changed files with 113 additions and 70 deletions.
5 changes: 5 additions & 0 deletions .changeset/big-mangos-mix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@moralisweb3/next': patch
---

Fix requests that can return null response
5 changes: 5 additions & 0 deletions .changeset/five-moles-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@moralisweb3/next': patch
---

Fix issues related to several NextJS hooks, due to the serialisation of the request
5 changes: 2 additions & 3 deletions demos/parse-server-migration/src/cloud/upgradeRequest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Operation, toCamelCase } from '@moralisweb3/common-core';

type UnknownOperation = Operation<unknown, unknown, unknown, unknown>;
import { UnknownOperation } from '@moralisweb3/common-core';
import { toCamelCase } from '@moralisweb3/common-core';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function upgradeRequest(params: any, operation: UnknownOperation): any {
Expand Down
1 change: 1 addition & 0 deletions packages/apiUtils/src/resolvers2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './OperationResolver';
export * from './NullableOperationResolver';
export * from './PaginatedOperationResolver';
export * from './NullableOperationResolver';
export * from './types';
12 changes: 12 additions & 0 deletions packages/apiUtils/src/resolvers2/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { NullableOperationResolver } from './NullableOperationResolver';
import { OperationResolver } from './OperationResolver';
import { PaginatedOperationResolver } from './PaginatedOperationResolver';

export type UnknownNullableOperationResolver = NullableOperationResolver<unknown, unknown, unknown, unknown>;
export type UnknownPaginatedOperationResolver = PaginatedOperationResolver<any, unknown, unknown, unknown>;
export type UnknownDefaultOperationResolver = OperationResolver<unknown, unknown, unknown, unknown>;

export type UnknownOperationResolver =
| UnknownNullableOperationResolver
| UnknownPaginatedOperationResolver
| UnknownDefaultOperationResolver;
3 changes: 1 addition & 2 deletions packages/codegen/src/next/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Operation } from '@moralisweb3/common-core';
import { UnknownOperation } from '@moralisweb3/common-core';

export type UnknownOperation = Operation<unknown, unknown, unknown, unknown>;
export type OperationAction = Pick<UnknownOperation, 'name' | 'groupName' | 'method' | 'id' | 'urlSearchParamNames'>;

export type Module = 'evmApi' | 'solApi' | 'auth';
2 changes: 2 additions & 0 deletions packages/common/core/src/operations/Operation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ export type OperationRequestUrlParams = Record<string, string | string[] | boole
export type OperationRequestBody = OperationRequestPropertiesBody | OperationRequestRawBody;
export type OperationRequestPropertiesBody = Record<string, unknown>;
export type OperationRequestRawBody = string | number | boolean | object;

export type UnknownOperation = Operation<unknown, unknown, unknown, unknown>;
59 changes: 59 additions & 0 deletions packages/next/src/MoralisNextApi/Modules.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { EvmApi } from '@moralisweb3/evm-api';
import Core, { UnknownOperation } from '@moralisweb3/common-core';
import { operations as evmOperations } from 'moralis/common-evm-utils';
import { operations as solOperations } from 'moralis/common-sol-utils';
import { operations as authOperations } from '@moralisweb3/common-auth-utils';
import { Auth } from '@moralisweb3/auth';
import { SolApi } from '@moralisweb3/sol-api';
import { UnknownOperationResolver } from '@moralisweb3/api-utils';

export class Module {
constructor(public readonly moduleName: string, public readonly operations: UnknownOperation[]) {}

getOperationByName(operationName: string) {
const operation = this.operations.find((op) => op.name === operationName);

if (!operation) {
throw new Error(`Operation ${operationName} not found`);
}

return operation;
}

getRequestHandler(operation: UnknownOperation, core: Core) {
const apiModule = core.getModule(this.moduleName) as unknown as Record<
string,
Record<string, UnknownOperationResolver['fetch']>
>;

const apiGroup = apiModule[operation.groupName];

if (!apiGroup) {
throw new Error(`Operation ${operation.name} has no group name in ${this.moduleName}`);
}

const requestHandler = apiGroup[operation.name];

if (!requestHandler) {
throw new Error(`Operation ${operation.name} has no requestHandler in ${this.moduleName}.${apiGroup}`);
}

return requestHandler;
}
}

const modules: Module[] = [
new Module(EvmApi.moduleName, evmOperations as UnknownOperation[]),
new Module(SolApi.moduleName, solOperations as UnknownOperation[]),
new Module(Auth.moduleName, authOperations as UnknownOperation[]),
];

export function getModuleByName(moduleName: string) {
const module = modules.find((currentModule) => currentModule.moduleName === moduleName);

if (!module) {
throw new Error(`Module ${moduleName} not found`);
}

return module;
}
17 changes: 11 additions & 6 deletions packages/next/src/MoralisNextApi/MoralisNextApi.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,32 @@
import { MoralisNextApiParams, MoralisNextHandlerParams } from './types';
import { RequestHandlerResolver } from './RequestHandlerResolver';
import Moralis from 'moralis';
import type { NextApiRequest, NextApiResponse } from 'next';
import { authOperationNames, moralisNextAuthHandler } from '../auth/moralisNextAuthHandler';
import { getModuleByName } from './Modules';
import { isMoralisError } from '@moralisweb3/common-core';
import { serverLogger } from '../serverLogger';

const FALLBACK_ERROR_MESSAGE = 'Internal Server Error';

async function MoralisNextHandler({ req, res, authentication }: MoralisNextHandlerParams) {
async function MoralisNextHandler({ req, res, authentication, core }: MoralisNextHandlerParams) {
const [moduleName, operationName] = req.query.moralis as string[];

try {
const requestHandler = RequestHandlerResolver.tryResolve(moduleName, operationName);
const module = getModuleByName(moduleName);
const operation = module.getOperationByName(operationName);
const deserialisedRequest = operation.deserializeRequest(req.body, core);
const requestHandler = module.getRequestHandler(operation, core);

if (!requestHandler) {
return res.status(400).json({ error: `Operation ${moduleName}/${operationName} is not supported` });
}

let response;

if (authOperationNames.includes(operationName)) {
response = await moralisNextAuthHandler({ req, res, authentication, requestHandler, operationName });
response = await moralisNextAuthHandler({ req, res, authentication, requestHandler, operation, core });
} else {
response = await requestHandler.fetch(req.body);
response = await requestHandler(deserialisedRequest);
}

return res.status(200).json(response);
Expand Down Expand Up @@ -50,7 +54,8 @@ const MoralisNextApi = ({ authentication, ...config }: MoralisNextApiParams) =>
Moralis.start(config);
}

return async (req: NextApiRequest, res: NextApiResponse) => MoralisNextHandler({ req, res, authentication });
return async (req: NextApiRequest, res: NextApiResponse) =>
MoralisNextHandler({ req, res, authentication, core: Moralis.Core });
};

export default MoralisNextApi;
51 changes: 0 additions & 51 deletions packages/next/src/MoralisNextApi/RequestHandlerResolver.ts

This file was deleted.

3 changes: 2 additions & 1 deletion packages/next/src/MoralisNextApi/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ApiUtilsConfigValues } from '@moralisweb3/api-utils';
import { RequestChallengeEvmRequest, RequestChallengeSolanaRequest } from '@moralisweb3/common-auth-utils';
import { MoralisCoreConfigValues } from 'moralis/common-core';
import { MoralisCoreConfigValues, Core } from 'moralis/common-core';
import type { NextApiRequest, NextApiResponse } from 'next';

export type MoralisConfigValues = MoralisCoreConfigValues | ApiUtilsConfigValues;
Expand All @@ -15,4 +15,5 @@ export interface MoralisNextHandlerParams {
req: NextApiRequest;
res: NextApiResponse;
authentication?: AuthConfig;
core: Core;
}
18 changes: 12 additions & 6 deletions packages/next/src/auth/moralisNextAuthHandler.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { OperationResolver } from '@moralisweb3/api-utils';
import { UnknownOperationResolver } from '@moralisweb3/api-utils';
import { UnknownOperation } from '@moralisweb3/common-core';
import { MoralisNextHandlerParams } from '../MoralisNextApi/types';

export const authOperationNames = [
Expand All @@ -9,28 +10,33 @@ export const authOperationNames = [
];

export interface MoralisNextAuthHandler extends MoralisNextHandlerParams {
requestHandler: OperationResolver<unknown, unknown, unknown, unknown>;
operationName: string;
requestHandler: UnknownOperationResolver['fetch'];
operation: UnknownOperation;
}

export const moralisNextAuthHandler = async ({
req,
authentication,
requestHandler,
operationName,
operation,
core,
}: MoralisNextAuthHandler) => {
const operationName = operation.name;
if (!authentication) {
throw new Error(
`Error running the '${operationName}' operation. No authentication config provided in 'pages/api/moralis/[...moralis].ts'`,
);
}

const deserlialisedRequest = operation.deserializeRequest(req.body, core) as Record<string, unknown>;

switch (operationName) {
case 'requestChallengeEvm':
case 'requestChallengeSolana':
return requestHandler.fetch({ ...req.body, ...authentication });
return requestHandler({ ...deserlialisedRequest, ...authentication });
case 'verifyChallengeEvm':
case 'verifyChallengeSolana':
return requestHandler.fetch(req.body);
return requestHandler(deserlialisedRequest);
default:
throw new Error(`${operationName} is not supported authentication operation`);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/utils/fetchers/fetcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async function fetcher<Request, Response, JSONResponse>(
endpoint = `/api/moralis/${endpoint}`;

try {
const { data } = await axios.post<JSONResponse>(endpoint, operation.serializeRequest(request, Moralis.Core));
const { data } = await axios.post<JSONResponse>(endpoint, request);
return operation.deserializeResponse(data, request, Moralis.Core);
} catch (error) {
// Overwrite error message if the nextjs api returns additional error details
Expand Down

1 comment on commit e00f50f

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test coverage

Title Lines Statements Branches Functions
api-utils Coverage: 25%
26.2% (49/187) 19.14% (9/47) 22.85% (8/35)
auth Coverage: 90%
92.77% (77/83) 81.81% (18/22) 90% (18/20)
evm-api Coverage: 100%
100% (81/81) 66.66% (6/9) 100% (49/49)
common-evm-utils Coverage: 64%
64.67% (974/1506) 19.44% (127/653) 35.25% (208/590)
sol-api Coverage: 96%
96.66% (29/30) 66.66% (6/9) 91.66% (11/12)
common-sol-utils Coverage: 74%
73.77% (135/183) 60% (12/20) 65.67% (44/67)
common-streams-utils Coverage: 93%
94% (1066/1134) 81.42% (399/490) 86.74% (373/430)
streams Coverage: 89%
89.35% (512/573) 68.05% (49/72) 88.98% (105/118)

Please sign in to comment.