Skip to content

Commit 8ebf821

Browse files
authored
refactor(core-api): pagination configuration through joi context (#3955)
1 parent 39894a4 commit 8ebf821

File tree

3 files changed

+135
-151
lines changed

3 files changed

+135
-151
lines changed

__tests__/integration/core-api/handlers/wallets.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ describe("API 2.0 - Wallets", () => {
106106

107107
it("should fail to GET a wallet by the given invalid identifier", async () => {
108108
for (const value of invalidIdentifiers) {
109-
api.expectError(await api.request("GET", `wallets/${value}`), 400);
109+
api.expectError(await api.request("GET", `wallets/${value}`), 422);
110110
}
111111
});
112112

packages/core-api/src/schemas.ts

Lines changed: 102 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,138 +1,113 @@
11
import Joi from "@hapi/joi";
22

3-
type SchemaSettings = {
4-
pagination: {
5-
limit: number;
6-
};
3+
export const pagination = {
4+
page: Joi.number().integer().positive().default(1),
5+
offset: Joi.number().integer().min(0),
6+
limit: Joi.number().integer().min(1).default(100).max(Joi.ref("$configuration.plugins.pagination.limit")),
77
};
88

9-
export const createSchemas = (settings: SchemaSettings) => {
10-
const pagination = {
11-
page: Joi.number().integer().positive().default(1),
12-
offset: Joi.number().integer().min(0),
13-
limit: Joi.number().integer().min(1).default(100).max(settings.pagination.limit),
14-
};
15-
16-
const blockId = Joi.alternatives().try(
17-
Joi.string()
18-
.min(1)
19-
.max(20)
20-
.regex(/^[0-9]+$/, "decimal non-negative integer"),
21-
Joi.string().length(64).hex(),
22-
);
23-
24-
const address = Joi.string().alphanum().length(34);
25-
26-
const delegateIdentifier = Joi.string()
27-
.regex(/^[a-zA-Z0-9!@$&_.]+$/)
28-
.min(1)
29-
.max(66);
30-
31-
const username = Joi.string()
32-
.regex(/^[a-z0-9!@$&_.]+$/)
9+
export const blockId = Joi.alternatives().try(
10+
Joi.string()
3311
.min(1)
34-
.max(20);
35-
36-
const integerBetween = Joi.object().keys({
12+
.max(20)
13+
.regex(/^[0-9]+$/, "decimal non-negative integer"),
14+
Joi.string().length(64).hex(),
15+
);
16+
17+
export const address = Joi.string().alphanum().length(34);
18+
19+
export const delegateIdentifier = Joi.string()
20+
.regex(/^[a-zA-Z0-9!@$&_.]+$/)
21+
.min(1)
22+
.max(66);
23+
24+
export const username = Joi.string()
25+
.regex(/^[a-z0-9!@$&_.]+$/)
26+
.min(1)
27+
.max(20);
28+
29+
export const integerBetween = Joi.object().keys({
30+
from: Joi.number().integer().min(0),
31+
to: Joi.number().integer().min(0),
32+
});
33+
34+
export const percentage = Joi.object().keys({
35+
from: Joi.number().precision(2).min(0).max(100),
36+
to: Joi.number().precision(2).min(0).max(100),
37+
});
38+
39+
export const numberFixedOrBetween = Joi.alternatives().try(
40+
Joi.number().integer().min(0),
41+
Joi.object().keys({
3742
from: Joi.number().integer().min(0),
3843
to: Joi.number().integer().min(0),
39-
});
40-
41-
const percentage = Joi.object().keys({
42-
from: Joi.number().precision(2).min(0).max(100),
43-
to: Joi.number().precision(2).min(0).max(100),
44-
});
45-
46-
const numberFixedOrBetween = Joi.alternatives().try(
47-
Joi.number().integer().min(0),
48-
Joi.object().keys({
49-
from: Joi.number().integer().min(0),
50-
to: Joi.number().integer().min(0),
51-
}),
52-
);
53-
54-
const walletId = Joi.alternatives().try(
55-
Joi.string()
56-
.regex(/^[a-z0-9!@$&_.]+$/)
57-
.min(1)
58-
.max(20),
59-
Joi.string().alphanum().length(34),
60-
Joi.string().hex().length(66),
61-
);
44+
}),
45+
);
6246

63-
const orderBy = Joi.string().regex(
64-
/^[a-z._]{1,40}:(asc|desc)$/i,
65-
"orderBy query parameter (<iteratee>:<direction>)",
47+
export const walletId = Joi.alternatives().try(
48+
Joi.string()
49+
.regex(/^[a-z0-9!@$&_.]+$/)
50+
.min(1)
51+
.max(20),
52+
Joi.string().alphanum().length(34),
53+
Joi.string().hex().length(66),
54+
);
55+
56+
export const orderBy = Joi.string().regex(
57+
/^[a-z._]{1,40}:(asc|desc)$/i,
58+
"orderBy query parameter (<iteratee>:<direction>)",
59+
);
60+
61+
export const blocksOrderBy = orderBy.default("height:desc");
62+
export const transactionsOrderBy = orderBy.default("timestamp:desc,sequence:desc");
63+
64+
const equalCriteria = (value: any) => value;
65+
const numericCriteria = (value: any) =>
66+
Joi.alternatives().try(
67+
value,
68+
Joi.object().keys({ from: value }),
69+
Joi.object().keys({ to: value }),
70+
Joi.object().keys({ from: value, to: value }),
6671
);
72+
const likeCriteria = (value: any) => value;
73+
const containsCriteria = (value: any) => value;
74+
const orCriteria = (criteria: any) => Joi.alternatives().try(criteria, Joi.array().items(criteria));
75+
const orEqualCriteria = (value: any) => orCriteria(equalCriteria(value));
76+
const orNumericCriteria = (value: any) => orCriteria(numericCriteria(value));
77+
const orLikeCriteria = (value: any) => orCriteria(likeCriteria(value));
78+
const orContainsCriteria = (value: any) => orCriteria(containsCriteria(value));
79+
80+
export const blockCriteriaSchemas = {
81+
id: orEqualCriteria(blockId),
82+
version: orEqualCriteria(Joi.number().integer().min(0)),
83+
timestamp: orNumericCriteria(Joi.number().integer().min(0)),
84+
previousBlock: orEqualCriteria(blockId),
85+
height: orNumericCriteria(Joi.number().integer().min(0)),
86+
numberOfTransactions: orNumericCriteria(Joi.number().integer().min(0)),
87+
totalAmount: orNumericCriteria(Joi.number().integer().min(0)),
88+
totalFee: orNumericCriteria(Joi.number().integer().min(0)),
89+
reward: orNumericCriteria(Joi.number().integer().min(0)),
90+
payloadLength: orNumericCriteria(Joi.number().integer().min(0)),
91+
payloadHash: orEqualCriteria(Joi.string().hex()),
92+
generatorPublicKey: orEqualCriteria(Joi.string().hex().length(66)),
93+
blockSignature: orEqualCriteria(Joi.string().hex()),
94+
};
6795

68-
const blocksOrderBy = orderBy.default("height:desc");
69-
const transactionsOrderBy = orderBy.default("timestamp:desc,sequence:desc");
70-
71-
const equalCriteria = (value: any) => value;
72-
const numericCriteria = (value: any) =>
73-
Joi.alternatives().try(
74-
value,
75-
Joi.object().keys({ from: value }),
76-
Joi.object().keys({ to: value }),
77-
Joi.object().keys({ from: value, to: value }),
78-
);
79-
const likeCriteria = (value: any) => value;
80-
const containsCriteria = (value: any) => value;
81-
const orCriteria = (criteria: any) => Joi.alternatives().try(criteria, Joi.array().items(criteria));
82-
const orEqualCriteria = (value: any) => orCriteria(equalCriteria(value));
83-
const orNumericCriteria = (value: any) => orCriteria(numericCriteria(value));
84-
const orLikeCriteria = (value: any) => orCriteria(likeCriteria(value));
85-
const orContainsCriteria = (value: any) => orCriteria(containsCriteria(value));
86-
87-
const blockCriteriaSchemas = {
88-
id: orEqualCriteria(blockId),
89-
version: orEqualCriteria(Joi.number().integer().min(0)),
90-
timestamp: orNumericCriteria(Joi.number().integer().min(0)),
91-
previousBlock: orEqualCriteria(blockId),
92-
height: orNumericCriteria(Joi.number().integer().min(0)),
93-
numberOfTransactions: orNumericCriteria(Joi.number().integer().min(0)),
94-
totalAmount: orNumericCriteria(Joi.number().integer().min(0)),
95-
totalFee: orNumericCriteria(Joi.number().integer().min(0)),
96-
reward: orNumericCriteria(Joi.number().integer().min(0)),
97-
payloadLength: orNumericCriteria(Joi.number().integer().min(0)),
98-
payloadHash: orEqualCriteria(Joi.string().hex()),
99-
generatorPublicKey: orEqualCriteria(Joi.string().hex().length(66)),
100-
blockSignature: orEqualCriteria(Joi.string().hex()),
101-
};
102-
103-
const transactionCriteriaSchemas = {
104-
address: orEqualCriteria(address),
105-
senderId: orEqualCriteria(address),
106-
recipientId: orEqualCriteria(address),
107-
id: orEqualCriteria(Joi.string().hex().length(64)),
108-
version: orEqualCriteria(Joi.number().integer().positive()),
109-
blockId: orEqualCriteria(blockId),
110-
sequence: orNumericCriteria(Joi.number().integer().positive()),
111-
timestamp: orNumericCriteria(Joi.number().integer().min(0)),
112-
nonce: orNumericCriteria(Joi.number().integer().positive()),
113-
senderPublicKey: orEqualCriteria(Joi.string().hex().length(66)),
114-
type: orEqualCriteria(Joi.number().integer().min(0)),
115-
typeGroup: orEqualCriteria(Joi.number().integer().min(0)),
116-
vendorField: orLikeCriteria(Joi.string().max(255, "utf8")),
117-
amount: orNumericCriteria(Joi.number().integer().min(0)),
118-
fee: orNumericCriteria(Joi.number().integer().min(0)),
119-
asset: orContainsCriteria(Joi.object()),
120-
};
121-
122-
return {
123-
pagination,
124-
blockId,
125-
address,
126-
delegateIdentifier,
127-
username,
128-
integerBetween,
129-
percentage,
130-
numberFixedOrBetween,
131-
walletId,
132-
orderBy,
133-
blocksOrderBy,
134-
transactionsOrderBy,
135-
blockCriteriaSchemas,
136-
transactionCriteriaSchemas,
137-
};
96+
export const transactionCriteriaSchemas = {
97+
address: orEqualCriteria(address),
98+
senderId: orEqualCriteria(address),
99+
recipientId: orEqualCriteria(address),
100+
id: orEqualCriteria(Joi.string().hex().length(64)),
101+
version: orEqualCriteria(Joi.number().integer().positive()),
102+
blockId: orEqualCriteria(blockId),
103+
sequence: orNumericCriteria(Joi.number().integer().positive()),
104+
timestamp: orNumericCriteria(Joi.number().integer().min(0)),
105+
nonce: orNumericCriteria(Joi.number().integer().positive()),
106+
senderPublicKey: orEqualCriteria(Joi.string().hex().length(66)),
107+
type: orEqualCriteria(Joi.number().integer().min(0)),
108+
typeGroup: orEqualCriteria(Joi.number().integer().min(0)),
109+
vendorField: orLikeCriteria(Joi.string().max(255, "utf8")),
110+
amount: orNumericCriteria(Joi.number().integer().min(0)),
111+
fee: orNumericCriteria(Joi.number().integer().min(0)),
112+
asset: orContainsCriteria(Joi.object()),
138113
};

packages/core-api/src/server.ts

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { Container, Contracts, Providers, Types } from "@arkecosystem/core-kernel";
1+
import { Container, Contracts, Providers, Types, Utils } from "@arkecosystem/core-kernel";
22
import { badData } from "@hapi/boom";
33
import { Server as HapiServer, ServerInjectOptions, ServerInjectResponse, ServerRoute } from "@hapi/hapi";
44
import { readFileSync } from "fs";
55

6-
import { createSchemas } from "./schemas";
6+
import * as Schemas from "./schemas";
77

88
// todo: review the implementation
99
@Container.injectable()
@@ -63,11 +63,7 @@ export class Server {
6363
this.server.listener.headersTimeout = timeout;
6464

6565
this.server.app.app = this.app;
66-
this.server.app.schemas = createSchemas({
67-
pagination: {
68-
limit: this.configuration.getRequired<number>("plugins.pagination.limit"),
69-
},
70-
});
66+
this.server.app.schemas = Schemas;
7167

7268
this.server.ext("onPreHandler", (request, h) => {
7369
request.headers["content-type"] = "application/json";
@@ -166,27 +162,40 @@ export class Server {
166162
options.tls.cert = readFileSync(options.tls.cert).toString();
167163
}
168164

169-
return {
170-
...{
171-
router: {
172-
stripTrailingSlash: true,
165+
const validateContext = {
166+
configuration: {
167+
plugins: {
168+
pagination: {
169+
limit: this.configuration.getRequired<number>("plugins.pagination.limit"),
170+
},
173171
},
174-
routes: {
175-
payload: {
176-
/* istanbul ignore next */
177-
async failAction(request, h, err) {
178-
return badData(err.message);
179-
},
172+
},
173+
};
174+
175+
const defaultOptions = {
176+
router: {
177+
stripTrailingSlash: true,
178+
},
179+
routes: {
180+
payload: {
181+
/* istanbul ignore next */
182+
async failAction(request, h, err) {
183+
return badData(err.message);
180184
},
181-
validate: {
182-
/* istanbul ignore next */
183-
async failAction(request, h, err) {
184-
return badData(err.message);
185-
},
185+
},
186+
validate: {
187+
options: {
188+
context: validateContext,
189+
},
190+
191+
/* istanbul ignore next */
192+
async failAction(request, h, err) {
193+
return badData(err.message);
186194
},
187195
},
188196
},
189-
...options,
190197
};
198+
199+
return Utils.merge(defaultOptions, options);
191200
}
192201
}

0 commit comments

Comments
 (0)