Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC] feat: Middlewares addition #263

Merged
merged 3 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
17 changes: 17 additions & 0 deletions lib/near-bindgen.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions lib/near-bindgen.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions src/near-bindgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,50 @@ export function call({
};
}

/**
* The interface that a middleware has to implement in order to be used as a middleware function/class.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface Middleware<Arguments extends Array<any>> {
ailisp marked this conversation as resolved.
Show resolved Hide resolved
/**
* The method that gets called with the same arguments that are passed to the function it is wrapping.
*
* @param args - Arguments that will be passed to the function - immutable.
*/
call(...args: Arguments): void;
}

/**
* Tells the SDK to apply an array of passed in middleware to the function execution.
*
* @param middlewares - The middlewares to be executed.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function middleware<Arguments extends Array<any>>(
...middlewares: Middleware<Arguments>[]
): DecoratorFunction {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return function <AnyFunction extends (...args: Arguments) => any>(
_target: object,
_key: string | symbol,
descriptor: TypedPropertyDescriptor<AnyFunction>
): void {
const originalMethod = descriptor.value;

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
descriptor.value = function (...args: Arguments): ReturnType<AnyFunction> {
try {
middlewares.forEach((middleware) => middleware.call(...args));
} catch (error) {
throw new Error(error);
}

return originalMethod.apply(this, args);
};
};
}

/**
* Extends this class with the methods needed to make the contract storable/serializable and readable/deserializable to and from the blockchain.
* Also tells the SDK to capture and expose all view, call and initialize functions.
Expand Down
84 changes: 84 additions & 0 deletions tests/__tests__/test-middlewares.ava.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { Worker } from "near-workspaces";
import test from "ava";

test.beforeEach(async (t) => {
// Init the worker and start a Sandbox server
const worker = await Worker.init();

// Prepare sandbox for tests, create accounts, deploy contracts, etx.
const root = worker.rootAccount;

// Deploy the contract.
const middlewares = await root.devDeploy("build/middlewares.wasm");

// Create the init args.
const args = JSON.stringify({ randomData: "anything" });
// Capture the result of the init function call.
const result = await middlewares.callRaw(middlewares, "init", args);

// Extract the logs.
const { logs } = result.result.receipts_outcome[0].outcome;
// Create the expected logs.
const expectedLogs = [`Log from middleware: ${args}`];

// Check for correct logs.
t.deepEqual(logs, expectedLogs);

// Create test users
const ali = await root.createSubAccount("ali");

// Save state for test runs
t.context.worker = worker;
t.context.accounts = { root, middlewares, ali };
});

test.afterEach.always(async (t) => {
await t.context.worker.tearDown().catch((error) => {
console.log("Failed to tear down the worker:", error);
});
});

test("The middleware logs with call functions", async (t) => {
const { ali, middlewares } = t.context.accounts;

// Create the arguments which will be passed to the function.
const args = JSON.stringify({ id: "1", text: "hello" });
// Call the function.
const result = await ali.callRaw(middlewares, "add", args);
// Extract the logs.
const { logs } = result.result.receipts_outcome[0].outcome;
// Create the expected logs.
const expectedLogs = [`Log from middleware: ${args}`];

t.deepEqual(logs, expectedLogs);
});

test("The middleware logs with view functions", async (t) => {
const { ali, middlewares } = t.context.accounts;

// Create the arguments which will be passed to the function.
const args = JSON.stringify({ id: "1", accountId: "hello" });
// Call the function.
const result = await ali.callRaw(middlewares, "get", args);
// Extract the logs.
const { logs } = result.result.receipts_outcome[0].outcome;
// Create the expected logs.
const expectedLogs = [`Log from middleware: ${args}`];

t.deepEqual(logs, expectedLogs);
});

test("The middleware logs with private functions", async (t) => {
const { ali, middlewares } = t.context.accounts;

// Create the arguments which will be passed to the function.
const args = { id: "test", accountId: "tset" };
// Call the function.
const result = await ali.callRaw(middlewares, "get_private", "");
// Extract the logs.
const { logs } = result.result.receipts_outcome[0].outcome;
// Create the expected logs.
const expectedLogs = [`Log from middleware: ${args}`];

t.deepEqual(logs, expectedLogs);
});
ailisp marked this conversation as resolved.
Show resolved Hide resolved
4 changes: 3 additions & 1 deletion tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"build:private": "near-sdk-js build src/decorators/private.ts build/private.wasm",
"build:bigint-serialization": "near-sdk-js build src/bigint-serialization.ts build/bigint-serialization.wasm",
"build:date-serialization": "near-sdk-js build src/date-serialization.ts build/date-serialization.wasm",
"build:middlewares": "near-sdk-js build src/middlewares.ts build/middlewares.wasm",
"test": "ava",
"test:context-api": "ava __tests__/test_context_api.ava.js",
"test:math-api": "ava __tests__/test_math_api.ava.js",
Expand All @@ -49,7 +50,8 @@
"test:private": "ava __tests__/decorators/private.ava.js",
"test:bigint-serialization": "ava __tests__/test-bigint-serialization.ava.js",
"test:date-serialization": "ava __tests__/test-date-serialization.ava.js",
"test:serialization": "ava __tests__/test-serialization.ava.js"
"test:serialization": "ava __tests__/test-serialization.ava.js",
"test:middlewares": "ava __tests__/test-middlewares.ava.js"
},
"author": "Near Inc <hello@nearprotocol.com>",
"license": "Apache-2.0",
Expand Down
45 changes: 45 additions & 0 deletions tests/src/middlewares.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NearBindgen, near, call, view } from "near-sdk-js";
import { initialize, middleware } from "../../lib/near-bindgen";

@NearBindgen({ requireInit: true })
export class Contract {
@initialize({})
@middleware({
call: (args) => near.log(`Log from middleware: ${args}`),
})
// eslint-disable-next-line @typescript-eslint/no-empty-function
init({ randomData: _ }: { randomData: string }) {}

@call({})
@middleware({
call: (args) => near.log(`Log from middleware: ${args}`),
})
// eslint-disable-next-line @typescript-eslint/no-empty-function
add({ id: _, text: _t }: { id: string; text: string }) {}

@view({})
@middleware({
call: (args) => near.log(`Log from middleware: ${args}`),
})
get({ id, accountId }: { id: string; accountId: string }): {
id: string;
accountId: string;
} {
return { id: accountId, accountId: id };
}

@view({})
get_private(): { id: string; accountId: string } {
return this.getFromPrivate({ id: "test", accountId: "tset" });
}

@middleware({
call: (args) => near.log(`Log from middleware: ${args}`),
})
getFromPrivate({ id, accountId }: { id: string; accountId: string }): {
id: string;
accountId: string;
} {
return { id, accountId };
}
}