This repository has been archived by the owner on Aug 3, 2022. It is now read-only.
forked from hyperledger-labs/convector
/
invokable.decorator.ts
152 lines (131 loc) · 4.21 KB
/
invokable.decorator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
/** @module convector-core-controller */
import { Schema } from 'yup';
import { ClientIdentity } from 'fabric-shim';
import { StubHelper, ChaincodeError } from '@theledger/fabric-chaincode-utils';
import {
ControllerNamespaceMissingError,
ControllerInstantiationError,
ControllerInvokablesMissingError,
ControllerInvalidInvokeError,
ControllerInvalidArgumentError,
ControllerArgumentParseError,
ControllerInvalidFunctionError
} from '@worldsibu/convector-core-errors';
import 'reflect-metadata';
import { paramMetadataKey } from './param.decorator';
import { controllerMetadataKey } from './controller.decorator';
/** @hidden */
const invokableMetadataKey = Symbol('invokable');
/**
* Used to expose a function inside a controller
* to be called from the outside world.
*
* The logic behind this decorators involves validating the amount of parameters
* expected against the ones received.
*
* It also injects the [[ConvectorController.sender]] information.
*
* @decorator
*/
export function Invokable() {
return (
target: any,
key: string,
descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<any>>
) => {
const fn = descriptor.value!;
if (typeof fn !== 'function') {
throw new ControllerInvalidFunctionError();
}
const invokables = Reflect.getMetadata(invokableMetadataKey, target.constructor) || {};
Reflect.defineMetadata(invokableMetadataKey, {
...invokables,
[key]: true
}, target.constructor);
// The use of `function` here is necessary to keep the context of `this`
descriptor.value = async function internalFn(
this: any,
stubHelper: StubHelper,
args: string[]
) {
const schemas: [Schema<any>, any, { new(...args: any[]): any }][] =
Reflect.getOwnMetadata(paramMetadataKey, target, key);
if (schemas) {
if (schemas.length !== args.length) {
throw new ControllerInvalidInvokeError(key, args.length, schemas.length);
}
args = await schemas.reduce(async (result, [schema, opts, model], index) => {
let paramResult;
try {
if (opts.update) {
paramResult = schema.cast(args[index], opts);
} else {
paramResult = await schema.validate(args[index], opts);
}
} catch (e) {
throw new ControllerInvalidArgumentError(e, index, args[index]);
}
if (model) {
try {
paramResult = new model(JSON.parse(args[index]));
} catch (e) {
throw new ControllerArgumentParseError(e, index, args[index]);
}
}
return [...await result, paramResult];
}, Promise.resolve([]));
}
const identity = new ClientIdentity(stubHelper.getStub());
const namespace = Reflect.getMetadata(controllerMetadataKey, target.constructor);
const ctx = Object.create(this[namespace], { sender: { value: identity.getX509Certificate().fingerPrint } });
try {
return await fn.call(ctx, ...args);
} catch (e) {
const error = new ChaincodeError(e.message);
error.stack = e.stack;
throw error;
}
};
};
}
/**
* Return all the invokable methods of a controller
*
* The controller gets registered in the chaincode using its namespace,
* just for further references.
*
* @hidden
*
* @param controller
*/
export function getInvokables(controller: { new(...args: any[]): any }): any {
let obj: any;
let namespace: string;
let invokables: any[];
try {
namespace = Reflect.getMetadata(controllerMetadataKey, controller);
if (!namespace) {
throw new TypeError();
}
} catch (e) {
throw new ControllerNamespaceMissingError(e, controller.name);
}
try {
obj = new controller();
} catch (e) {
throw new ControllerInstantiationError(e, namespace);
}
try {
invokables = Reflect.getMetadata(invokableMetadataKey, controller);
if (!invokables) {
throw new TypeError();
}
} catch (e) {
throw new ControllerInvokablesMissingError(e, namespace);
}
return Object.keys(invokables)
.reduce((result, k) => ({
...result,
[`${namespace}_${k}`]: obj[k]
}), { [namespace]: obj });
}