Skip to content

Commit 3fc8b0a

Browse files
authored
Merge pull request #2009 from aeternity/middleware-next-prev
feat(middleware): allow navigate to next/prev pages
2 parents 6b804e2 + b89cf5b commit 3fc8b0a

File tree

6 files changed

+287
-44
lines changed

6 files changed

+287
-44
lines changed

src/Middleware.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
import { OperationOptions } from '@azure/core-client';
1+
import { OperationArguments, OperationOptions, OperationSpec } from '@azure/core-client';
22
import { userAgentPolicyName, setClientRequestIdPolicyName } from '@azure/core-rest-pipeline';
33
import {
44
genRequestQueuesPolicy, genCombineGetRequestsPolicy, genErrorFormatterPolicy,
55
parseBigIntPolicy, genVersionCheckPolicy, genRetryOnFailurePolicy,
66
} from './utils/autorest';
77
import { Middleware as MiddlewareApi, MiddlewareOptionalParams, ErrorResponse } from './apis/middleware';
8+
import { operationSpecs } from './apis/middleware/middleware';
9+
import { IllegalArgumentError, InternalError } from './utils/errors';
10+
import { MiddlewarePage, isMiddlewareRawPage } from './utils/MiddlewarePage';
811

912
export default class Middleware extends MiddlewareApi {
1013
/**
@@ -51,4 +54,57 @@ export default class Middleware extends MiddlewareApi {
5154
// TODO: use instead our retry policy
5255
this.pipeline.removePolicy({ name: 'defaultRetryPolicy' });
5356
}
57+
58+
/**
59+
* Get a middleware response by path instead of a method name and arguments.
60+
* @param pathWithQuery - a path to request starting with `/v3/`
61+
*/
62+
async requestByPath<Response = unknown>(pathWithQuery: string): Promise<Response> {
63+
const queryPos = pathWithQuery.indexOf('?');
64+
const path = pathWithQuery.slice(0, queryPos === -1 ? pathWithQuery.length : queryPos);
65+
const query = pathWithQuery.slice(queryPos === -1 ? pathWithQuery.length : queryPos + 1);
66+
67+
const operationSpec = operationSpecs.find((os) => {
68+
let p = path;
69+
if (os.path == null) return false;
70+
const groups = os.path.replace(/{\w+}/g, '{param}').split('{param}');
71+
while (groups.length > 0) {
72+
const part = groups.shift();
73+
if (part == null) throw new InternalError(`Unexpected operation spec path: ${os.path}`);
74+
if (!p.startsWith(part)) return false;
75+
p = p.replace(part, '');
76+
if (groups.length > 0) p = p.replace(/^[\w.]+/, '');
77+
}
78+
return p === '';
79+
});
80+
if (operationSpec == null) {
81+
throw new IllegalArgumentError(`Can't find operation spec corresponding to ${path}`);
82+
}
83+
84+
return this.sendOperationRequest({}, {
85+
...operationSpec,
86+
path,
87+
urlParameters: operationSpec.urlParameters
88+
?.filter(({ parameterPath }) => parameterPath === '$host'),
89+
queryParameters: Array.from(new URLSearchParams(query)).map(([key, value]) => ({
90+
parameterPath: ['options', key],
91+
mapper: {
92+
defaultValue: value.toString(),
93+
serializedName: key,
94+
type: {
95+
name: 'String',
96+
},
97+
},
98+
})),
99+
});
100+
}
101+
102+
override async sendOperationRequest<T>(
103+
operationArguments: OperationArguments,
104+
operationSpec: OperationSpec,
105+
): Promise<T> {
106+
const response = await super.sendOperationRequest(operationArguments, operationSpec);
107+
if (!isMiddlewareRawPage(response)) return response as T;
108+
return new MiddlewarePage(response, this) as T;
109+
}
54110
}

src/index-browser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export {
7272
default as MiddlewareSubscriber, MiddlewareSubscriberError, MiddlewareSubscriberDisconnected,
7373
} from './MiddlewareSubscriber';
7474
export { default as Middleware } from './Middleware';
75+
export { MiddlewarePageMissed } from './utils/MiddlewarePage';
7576

7677
export { default as connectionProxy } from './aepp-wallet-communication/connection-proxy';
7778
export {

src/utils/MiddlewarePage.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/* eslint-disable max-classes-per-file */
2+
import type Middleware from '../Middleware';
3+
import { BaseError } from './errors';
4+
5+
export interface MiddlewareRawPage {
6+
data: unknown[];
7+
next: string | null;
8+
prev: string | null;
9+
}
10+
11+
export function isMiddlewareRawPage(maybePage: unknown): maybePage is MiddlewareRawPage {
12+
const testPage = maybePage as MiddlewareRawPage;
13+
return testPage?.data != null && Array.isArray(testPage.data)
14+
&& 'next' in testPage
15+
&& 'prev' in testPage;
16+
}
17+
18+
/**
19+
* @category exception
20+
*/
21+
export class MiddlewarePageMissed extends BaseError {
22+
constructor(isNext: boolean) {
23+
super(`There is no ${isNext ? 'next' : 'previous'} page`);
24+
this.name = 'MiddlewarePageMissed';
25+
}
26+
}
27+
28+
/**
29+
* A wrapper around the middleware's page allowing to get the next/previous pages.
30+
*/
31+
export class MiddlewarePage<Item> {
32+
readonly data: Item[];
33+
34+
readonly nextPath: null | string;
35+
36+
readonly prevPath: null | string;
37+
38+
readonly #middleware: Middleware;
39+
40+
constructor(rawPage: MiddlewareRawPage, middleware: Middleware) {
41+
this.data = rawPage.data as Item[];
42+
this.nextPath = rawPage.next;
43+
this.prevPath = rawPage.prev;
44+
this.#middleware = middleware;
45+
}
46+
47+
/**
48+
* Get the next page.
49+
* Check the presence of `nextPath` to not fall outside existing pages.
50+
* @throws MiddlewarePageMissed
51+
*/
52+
async next(): Promise<MiddlewarePage<Item>> {
53+
if (this.nextPath == null) throw new MiddlewarePageMissed(true);
54+
return this.#middleware.requestByPath(this.nextPath);
55+
}
56+
57+
/**
58+
* Get the previous page.
59+
* Check the presence of `prevPath` to not fall outside existing pages.
60+
* @throws MiddlewarePageMissed
61+
*/
62+
async prev(): Promise<MiddlewarePage<Item>> {
63+
if (this.prevPath == null) throw new MiddlewarePageMissed(false);
64+
return this.#middleware.requestByPath(this.prevPath);
65+
}
66+
}

src/utils/autorest.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ export const createSerializer = (
4848
});
4949
};
5050

51-
// 16 === Number.MAX_SAFE_INTEGER.toString().length
52-
const bigIntPropertyRe = /("\w+":\s*)(\d{16,})(\s*[,}])/m;
53-
const bigIntArrayItemRe = /([[,]\s*)(\d{16,})\b/m;
51+
const safeLength = Number.MAX_SAFE_INTEGER.toString().length;
52+
const bigIntPropertyRe = new RegExp(String.raw`("\w+":\s*)(\d{${safeLength},})(\s*[,}])`, 'm');
53+
const bigIntArrayItemRe = new RegExp(String.raw`([[,]\s*)(\d{${safeLength},})\b`, 'm');
5454
export const parseBigIntPolicy: PipelinePolicy = {
5555
name: 'parse-big-int',
5656
async sendRequest(request, next) {

0 commit comments

Comments
 (0)