@grest-ts/http 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +618 -613
- package/dist/tsconfig.publish.tsbuildinfo +1 -1
- package/package.json +11 -11
- package/src/client/GGHttpSchema.createClient.ts +106 -106
- package/src/index-browser.ts +12 -12
- package/src/index-node.ts +38 -38
- package/src/rpc/GGHttpRouteRPC.ts +42 -42
- package/src/rpc/RpcRequest/GGRpcRequestBuilder.ts +91 -91
- package/src/rpc/RpcRequest/GGRpcRequestParser.ts +100 -100
- package/src/rpc/RpcResponse/GGRpcResponseBuilder.ts +84 -84
- package/src/rpc/RpcResponse/GGRpcResponseParser.ts +23 -23
- package/src/schema/GGHttpSchema.ts +115 -115
- package/src/schema/httpSchema.ts +99 -99
- package/src/server/GGHttp.ts +27 -27
- package/src/server/GGHttpMetrics.ts +31 -31
- package/src/server/GGHttpSchema.startServer.ts +161 -161
- package/src/server/GGHttpServer.ts +133 -133
- package/src/server/GG_HTTP_REQUEST.ts +12 -12
- package/src/server/GG_HTTP_SERVER.ts +4 -4
|
@@ -1,100 +1,100 @@
|
|
|
1
|
-
import type http from "http";
|
|
2
|
-
import type {HttpMethod} from "@grest-ts/common";
|
|
3
|
-
import {GGContractExecutor, GGContractMethod} from "@grest-ts/schema";
|
|
4
|
-
import {ClientHttpRouteToRpcTransformServerConfig, GGHttpRequest, GGHttpTransportMiddleware} from "../../schema/GGHttpSchema";
|
|
5
|
-
import type {GGHttpServerMiddleware} from "../../server/GGHttpSchema.startServer";
|
|
6
|
-
|
|
7
|
-
export class GGRpcRequestParser {
|
|
8
|
-
|
|
9
|
-
private readonly pathTemplate: string;
|
|
10
|
-
private readonly pathParams: string[];
|
|
11
|
-
private readonly hasBody: boolean;
|
|
12
|
-
protected readonly contract: GGContractMethod
|
|
13
|
-
private readonly apiMiddlewares: readonly GGHttpTransportMiddleware[]
|
|
14
|
-
private readonly serverMiddlewares: readonly GGHttpServerMiddleware[]
|
|
15
|
-
|
|
16
|
-
constructor(
|
|
17
|
-
method: HttpMethod,
|
|
18
|
-
pathTemplate: string,
|
|
19
|
-
config: ClientHttpRouteToRpcTransformServerConfig
|
|
20
|
-
) {
|
|
21
|
-
this.pathTemplate = pathTemplate
|
|
22
|
-
this.pathParams = (pathTemplate.match(/:(\w+)/g) || []).map(m => m.slice(1))
|
|
23
|
-
this.hasBody = method === "POST" || method === "PUT" || method === "PATCH"
|
|
24
|
-
this.contract = config.contract
|
|
25
|
-
this.apiMiddlewares = config.apiMiddlewares
|
|
26
|
-
this.serverMiddlewares = config.serverMiddlewares
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
public parseRequest = async (req: http.IncomingMessage): Promise<unknown> => {
|
|
30
|
-
const url = req.url || '/'
|
|
31
|
-
const qIndex = url.indexOf('?')
|
|
32
|
-
const queryArgs = this.parseQueryString(qIndex === -1 ? '' : url.substring(qIndex + 1))
|
|
33
|
-
if (this.apiMiddlewares?.length > 0) {
|
|
34
|
-
const mwQuery: GGHttpRequest = {headers: req.headers, queryArgs: queryArgs}
|
|
35
|
-
this.apiMiddlewares?.forEach(mw => mw.parseRequest?.(mwQuery))
|
|
36
|
-
}
|
|
37
|
-
for (const mw of this.serverMiddlewares ?? []) await mw.process?.();
|
|
38
|
-
|
|
39
|
-
let input: unknown;
|
|
40
|
-
if (this.hasBody) {
|
|
41
|
-
input = await this.parseBody(req);
|
|
42
|
-
} else if (this.pathParams.length > 0) {
|
|
43
|
-
input = {
|
|
44
|
-
...this.extractPathParams(qIndex === -1 ? url : url.substring(0, qIndex)),
|
|
45
|
-
...queryArgs
|
|
46
|
-
};
|
|
47
|
-
} else {
|
|
48
|
-
input = queryArgs;
|
|
49
|
-
}
|
|
50
|
-
return GGContractExecutor.parseInput(this.contract.input, input);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
private parseQueryString(rawQuery: string): Record<string, string | string[]> {
|
|
54
|
-
const result: Record<string, string | string[]> = {}
|
|
55
|
-
if (rawQuery) {
|
|
56
|
-
const params = new URLSearchParams(rawQuery)
|
|
57
|
-
for (const [key, value] of params.entries()) {
|
|
58
|
-
result[key] = value
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return result;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
private extractPathParams(actualPath: string): Record<string, string> {
|
|
65
|
-
const result: Record<string, string> = {}
|
|
66
|
-
const templateParts = this.pathTemplate.split('/').filter(p => p)
|
|
67
|
-
const actualParts = actualPath.split('/').filter(p => p)
|
|
68
|
-
const offset = actualParts.length - templateParts.length
|
|
69
|
-
if (offset >= 0) {
|
|
70
|
-
for (let i = 0; i < templateParts.length; i++) {
|
|
71
|
-
const templatePart = templateParts[i]
|
|
72
|
-
if (templatePart.startsWith(':')) {
|
|
73
|
-
const paramName = templatePart.slice(1)
|
|
74
|
-
result[paramName] = decodeURIComponent(actualParts[offset + i] || '')
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return result;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
private async parseBody(req: http.IncomingMessage): Promise<unknown> {
|
|
82
|
-
const contentType = req.headers['content-type'] || ''
|
|
83
|
-
const isMultipart = contentType.toLowerCase().startsWith('multipart/form-data')
|
|
84
|
-
if (isMultipart) {
|
|
85
|
-
throw new Error("Received multipart request on a JSON-only route. Use GGRpc.MULTIPART_POST for routes with file uploads.")
|
|
86
|
-
} else {
|
|
87
|
-
const rawBody: Buffer = await new Promise((resolve, reject) => {
|
|
88
|
-
const chunks: Buffer[] = []
|
|
89
|
-
req.on('data', (chunk: Buffer) => chunks.push(chunk))
|
|
90
|
-
req.on('end', () => resolve(Buffer.concat(chunks)))
|
|
91
|
-
req.on('error', reject)
|
|
92
|
-
});
|
|
93
|
-
if (rawBody && rawBody.length > 0) {
|
|
94
|
-
return JSON.parse(rawBody.toString('utf-8'))
|
|
95
|
-
} else {
|
|
96
|
-
return undefined
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
1
|
+
import type http from "http";
|
|
2
|
+
import type {HttpMethod} from "@grest-ts/common";
|
|
3
|
+
import {GGContractExecutor, GGContractMethod} from "@grest-ts/schema";
|
|
4
|
+
import {ClientHttpRouteToRpcTransformServerConfig, GGHttpRequest, GGHttpTransportMiddleware} from "../../schema/GGHttpSchema";
|
|
5
|
+
import type {GGHttpServerMiddleware} from "../../server/GGHttpSchema.startServer";
|
|
6
|
+
|
|
7
|
+
export class GGRpcRequestParser {
|
|
8
|
+
|
|
9
|
+
private readonly pathTemplate: string;
|
|
10
|
+
private readonly pathParams: string[];
|
|
11
|
+
private readonly hasBody: boolean;
|
|
12
|
+
protected readonly contract: GGContractMethod
|
|
13
|
+
private readonly apiMiddlewares: readonly GGHttpTransportMiddleware[]
|
|
14
|
+
private readonly serverMiddlewares: readonly GGHttpServerMiddleware[]
|
|
15
|
+
|
|
16
|
+
constructor(
|
|
17
|
+
method: HttpMethod,
|
|
18
|
+
pathTemplate: string,
|
|
19
|
+
config: ClientHttpRouteToRpcTransformServerConfig
|
|
20
|
+
) {
|
|
21
|
+
this.pathTemplate = pathTemplate
|
|
22
|
+
this.pathParams = (pathTemplate.match(/:(\w+)/g) || []).map(m => m.slice(1))
|
|
23
|
+
this.hasBody = method === "POST" || method === "PUT" || method === "PATCH"
|
|
24
|
+
this.contract = config.contract
|
|
25
|
+
this.apiMiddlewares = config.apiMiddlewares
|
|
26
|
+
this.serverMiddlewares = config.serverMiddlewares
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public parseRequest = async (req: http.IncomingMessage): Promise<unknown> => {
|
|
30
|
+
const url = req.url || '/'
|
|
31
|
+
const qIndex = url.indexOf('?')
|
|
32
|
+
const queryArgs = this.parseQueryString(qIndex === -1 ? '' : url.substring(qIndex + 1))
|
|
33
|
+
if (this.apiMiddlewares?.length > 0) {
|
|
34
|
+
const mwQuery: GGHttpRequest = {headers: req.headers, queryArgs: queryArgs}
|
|
35
|
+
this.apiMiddlewares?.forEach(mw => mw.parseRequest?.(mwQuery))
|
|
36
|
+
}
|
|
37
|
+
for (const mw of this.serverMiddlewares ?? []) await mw.process?.();
|
|
38
|
+
|
|
39
|
+
let input: unknown;
|
|
40
|
+
if (this.hasBody) {
|
|
41
|
+
input = await this.parseBody(req);
|
|
42
|
+
} else if (this.pathParams.length > 0) {
|
|
43
|
+
input = {
|
|
44
|
+
...this.extractPathParams(qIndex === -1 ? url : url.substring(0, qIndex)),
|
|
45
|
+
...queryArgs
|
|
46
|
+
};
|
|
47
|
+
} else {
|
|
48
|
+
input = queryArgs;
|
|
49
|
+
}
|
|
50
|
+
return GGContractExecutor.parseInput(this.contract.input, input);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
private parseQueryString(rawQuery: string): Record<string, string | string[]> {
|
|
54
|
+
const result: Record<string, string | string[]> = {}
|
|
55
|
+
if (rawQuery) {
|
|
56
|
+
const params = new URLSearchParams(rawQuery)
|
|
57
|
+
for (const [key, value] of params.entries()) {
|
|
58
|
+
result[key] = value
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private extractPathParams(actualPath: string): Record<string, string> {
|
|
65
|
+
const result: Record<string, string> = {}
|
|
66
|
+
const templateParts = this.pathTemplate.split('/').filter(p => p)
|
|
67
|
+
const actualParts = actualPath.split('/').filter(p => p)
|
|
68
|
+
const offset = actualParts.length - templateParts.length
|
|
69
|
+
if (offset >= 0) {
|
|
70
|
+
for (let i = 0; i < templateParts.length; i++) {
|
|
71
|
+
const templatePart = templateParts[i]
|
|
72
|
+
if (templatePart.startsWith(':')) {
|
|
73
|
+
const paramName = templatePart.slice(1)
|
|
74
|
+
result[paramName] = decodeURIComponent(actualParts[offset + i] || '')
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private async parseBody(req: http.IncomingMessage): Promise<unknown> {
|
|
82
|
+
const contentType = req.headers['content-type'] || ''
|
|
83
|
+
const isMultipart = contentType.toLowerCase().startsWith('multipart/form-data')
|
|
84
|
+
if (isMultipart) {
|
|
85
|
+
throw new Error("Received multipart request on a JSON-only route. Use GGRpc.MULTIPART_POST for routes with file uploads.")
|
|
86
|
+
} else {
|
|
87
|
+
const rawBody: Buffer = await new Promise((resolve, reject) => {
|
|
88
|
+
const chunks: Buffer[] = []
|
|
89
|
+
req.on('data', (chunk: Buffer) => chunks.push(chunk))
|
|
90
|
+
req.on('end', () => resolve(Buffer.concat(chunks)))
|
|
91
|
+
req.on('error', reject)
|
|
92
|
+
});
|
|
93
|
+
if (rawBody && rawBody.length > 0) {
|
|
94
|
+
return JSON.parse(rawBody.toString('utf-8'))
|
|
95
|
+
} else {
|
|
96
|
+
return undefined
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
import type http from "http";
|
|
2
|
-
import {ANY_ERROR, ERROR, GGContractExecutor, GGContractMethod, GGDebugData, GGErrorData, GGSchema, OK} from "@grest-ts/schema";
|
|
3
|
-
import {ClientHttpRouteToRpcTransformServerConfig} from "../../schema/GGHttpSchema";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export class GGRpcResponseBuilder {
|
|
7
|
-
|
|
8
|
-
protected readonly contract: GGContractMethod
|
|
9
|
-
|
|
10
|
-
constructor(config: ClientHttpRouteToRpcTransformServerConfig) {
|
|
11
|
-
this.contract = config.contract;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
public sendResponse = async (res: http.ServerResponse, rpcResult: ERROR<string, unknown> | OK<unknown>): Promise<void> => {
|
|
15
|
-
let json: string;
|
|
16
|
-
let statusCode: number;
|
|
17
|
-
if (rpcResult.success === true) {
|
|
18
|
-
statusCode = 200;
|
|
19
|
-
json = '{' +
|
|
20
|
-
'"success":true,' +
|
|
21
|
-
'"type":"OK"' +
|
|
22
|
-
this.makeDataStr(this.contract.success, rpcResult) +
|
|
23
|
-
"}"
|
|
24
|
-
} else {
|
|
25
|
-
json = this.makeError(GGContractExecutor.getResponseSchema(this.contract, rpcResult), rpcResult);
|
|
26
|
-
statusCode = rpcResult.statusCode;
|
|
27
|
-
}
|
|
28
|
-
res.writeHead(statusCode, {
|
|
29
|
-
'Content-Type': 'application/json',
|
|
30
|
-
'Content-Length': Buffer.byteLength(json)
|
|
31
|
-
});
|
|
32
|
-
res.end(json)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private makeError(schema: GGSchema<any> | undefined, rpcResult: ERROR<string, unknown>) {
|
|
36
|
-
return '{' +
|
|
37
|
-
'"success":false,' +
|
|
38
|
-
'"type":' + JSON.stringify(rpcResult.type) + ',' +
|
|
39
|
-
'"statusCode":' + Number(rpcResult.statusCode) +
|
|
40
|
-
this.makeErrorCtx(rpcResult.context, rpcResult.getDebugContext()) +
|
|
41
|
-
this.makeDataStr(schema, rpcResult) +
|
|
42
|
-
"}";
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private makeDataStr(schema: GGSchema<any>, data: OK<any> | ANY_ERROR): string {
|
|
46
|
-
if (schema) {
|
|
47
|
-
GGContractExecutor.assertResponse(schema, data);
|
|
48
|
-
const dataStr = schema.unsafeStringify(data.data)
|
|
49
|
-
return dataStr ? ',"data":' + dataStr + "" : "";
|
|
50
|
-
} else {
|
|
51
|
-
return "";
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
private makeErrorCtx(ctx: GGErrorData, debugContext?: GGDebugData): string {
|
|
56
|
-
if (ctx) {
|
|
57
|
-
let str = "";
|
|
58
|
-
str += ctx?.displayMessage ? '"displayMessage":' + JSON.stringify(ctx.displayMessage) + '' : "";
|
|
59
|
-
str += ctx?.timestamp ? (str ? "," : "") + '"timestamp":' + Number(ctx.timestamp) : "";
|
|
60
|
-
str += ctx?.ref ? (str ? "," : "") + '"ref":' + JSON.stringify(ctx.ref) + '' : "";
|
|
61
|
-
if (process.env.NODE_ENV !== "production" && debugContext) {
|
|
62
|
-
if (debugContext?.debugMessage) {
|
|
63
|
-
str += (str ? "," : "") + '"debugMessage":' + JSON.stringify(debugContext.debugMessage);
|
|
64
|
-
}
|
|
65
|
-
if (debugContext?.debugData !== undefined) {
|
|
66
|
-
str += (str ? "," : "") + '"debugData":' + JSON.stringify(debugContext.debugData);
|
|
67
|
-
}
|
|
68
|
-
if (debugContext?.originalError) {
|
|
69
|
-
const origErr = debugContext.originalError;
|
|
70
|
-
const errInfo = origErr instanceof ERROR
|
|
71
|
-
? origErr.toJSON()
|
|
72
|
-
: origErr instanceof Error
|
|
73
|
-
? {message: origErr.message, stack: origErr.stack?.split("\n")}
|
|
74
|
-
: {message: String(origErr)};
|
|
75
|
-
str += (str ? "," : "") + '"originalError":' + JSON.stringify(errInfo);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (str) {
|
|
79
|
-
return ',"context":{' + str + '}';
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return "";
|
|
83
|
-
}
|
|
84
|
-
}
|
|
1
|
+
import type http from "http";
|
|
2
|
+
import {ANY_ERROR, ERROR, GGContractExecutor, GGContractMethod, GGDebugData, GGErrorData, GGSchema, OK} from "@grest-ts/schema";
|
|
3
|
+
import {ClientHttpRouteToRpcTransformServerConfig} from "../../schema/GGHttpSchema";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export class GGRpcResponseBuilder {
|
|
7
|
+
|
|
8
|
+
protected readonly contract: GGContractMethod
|
|
9
|
+
|
|
10
|
+
constructor(config: ClientHttpRouteToRpcTransformServerConfig) {
|
|
11
|
+
this.contract = config.contract;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public sendResponse = async (res: http.ServerResponse, rpcResult: ERROR<string, unknown> | OK<unknown>): Promise<void> => {
|
|
15
|
+
let json: string;
|
|
16
|
+
let statusCode: number;
|
|
17
|
+
if (rpcResult.success === true) {
|
|
18
|
+
statusCode = 200;
|
|
19
|
+
json = '{' +
|
|
20
|
+
'"success":true,' +
|
|
21
|
+
'"type":"OK"' +
|
|
22
|
+
this.makeDataStr(this.contract.success, rpcResult) +
|
|
23
|
+
"}"
|
|
24
|
+
} else {
|
|
25
|
+
json = this.makeError(GGContractExecutor.getResponseSchema(this.contract, rpcResult), rpcResult);
|
|
26
|
+
statusCode = rpcResult.statusCode;
|
|
27
|
+
}
|
|
28
|
+
res.writeHead(statusCode, {
|
|
29
|
+
'Content-Type': 'application/json',
|
|
30
|
+
'Content-Length': Buffer.byteLength(json)
|
|
31
|
+
});
|
|
32
|
+
res.end(json)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private makeError(schema: GGSchema<any> | undefined, rpcResult: ERROR<string, unknown>) {
|
|
36
|
+
return '{' +
|
|
37
|
+
'"success":false,' +
|
|
38
|
+
'"type":' + JSON.stringify(rpcResult.type) + ',' +
|
|
39
|
+
'"statusCode":' + Number(rpcResult.statusCode) +
|
|
40
|
+
this.makeErrorCtx(rpcResult.context, rpcResult.getDebugContext()) +
|
|
41
|
+
this.makeDataStr(schema, rpcResult) +
|
|
42
|
+
"}";
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private makeDataStr(schema: GGSchema<any>, data: OK<any> | ANY_ERROR): string {
|
|
46
|
+
if (schema) {
|
|
47
|
+
GGContractExecutor.assertResponse(schema, data);
|
|
48
|
+
const dataStr = schema.unsafeStringify(data.data)
|
|
49
|
+
return dataStr ? ',"data":' + dataStr + "" : "";
|
|
50
|
+
} else {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private makeErrorCtx(ctx: GGErrorData, debugContext?: GGDebugData): string {
|
|
56
|
+
if (ctx) {
|
|
57
|
+
let str = "";
|
|
58
|
+
str += ctx?.displayMessage ? '"displayMessage":' + JSON.stringify(ctx.displayMessage) + '' : "";
|
|
59
|
+
str += ctx?.timestamp ? (str ? "," : "") + '"timestamp":' + Number(ctx.timestamp) : "";
|
|
60
|
+
str += ctx?.ref ? (str ? "," : "") + '"ref":' + JSON.stringify(ctx.ref) + '' : "";
|
|
61
|
+
if (process.env.NODE_ENV !== "production" && debugContext) {
|
|
62
|
+
if (debugContext?.debugMessage) {
|
|
63
|
+
str += (str ? "," : "") + '"debugMessage":' + JSON.stringify(debugContext.debugMessage);
|
|
64
|
+
}
|
|
65
|
+
if (debugContext?.debugData !== undefined) {
|
|
66
|
+
str += (str ? "," : "") + '"debugData":' + JSON.stringify(debugContext.debugData);
|
|
67
|
+
}
|
|
68
|
+
if (debugContext?.originalError) {
|
|
69
|
+
const origErr = debugContext.originalError;
|
|
70
|
+
const errInfo = origErr instanceof ERROR
|
|
71
|
+
? origErr.toJSON()
|
|
72
|
+
: origErr instanceof Error
|
|
73
|
+
? {message: origErr.message, stack: origErr.stack?.split("\n")}
|
|
74
|
+
: {message: String(origErr)};
|
|
75
|
+
str += (str ? "," : "") + '"originalError":' + JSON.stringify(errInfo);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (str) {
|
|
79
|
+
return ',"context":{' + str + '}';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import {ERROR_JSON, OK, SERVER_ERROR} from "@grest-ts/schema";
|
|
2
|
-
import {ClientHttpRouteToRpcTransformClientConfig} from "../../schema/GGHttpSchema";
|
|
3
|
-
|
|
4
|
-
export class GGRpcResponseParser {
|
|
5
|
-
|
|
6
|
-
constructor(config: ClientHttpRouteToRpcTransformClientConfig) {
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
parseResponse = async (response: Response): Promise<OK<unknown> | ERROR_JSON<string, unknown>> => {
|
|
11
|
-
const txt = await response.text();
|
|
12
|
-
try {
|
|
13
|
-
const json = txt ? JSON.parse(txt) : {};
|
|
14
|
-
if (typeof json === "object" && "success" in json && "type" in json) {
|
|
15
|
-
return json
|
|
16
|
-
} else {
|
|
17
|
-
return new SERVER_ERROR({displayMessage: "Invalid response format!", debugData: {json: json}});
|
|
18
|
-
}
|
|
19
|
-
} catch (err) {
|
|
20
|
-
return new SERVER_ERROR({displayMessage: "Failed to parse JSON", originalError: err, debugData: {text: txt}});
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
1
|
+
import {ERROR_JSON, OK, SERVER_ERROR} from "@grest-ts/schema";
|
|
2
|
+
import {ClientHttpRouteToRpcTransformClientConfig} from "../../schema/GGHttpSchema";
|
|
3
|
+
|
|
4
|
+
export class GGRpcResponseParser {
|
|
5
|
+
|
|
6
|
+
constructor(config: ClientHttpRouteToRpcTransformClientConfig) {
|
|
7
|
+
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
parseResponse = async (response: Response): Promise<OK<unknown> | ERROR_JSON<string, unknown>> => {
|
|
11
|
+
const txt = await response.text();
|
|
12
|
+
try {
|
|
13
|
+
const json = txt ? JSON.parse(txt) : {};
|
|
14
|
+
if (typeof json === "object" && "success" in json && "type" in json) {
|
|
15
|
+
return json
|
|
16
|
+
} else {
|
|
17
|
+
return new SERVER_ERROR({displayMessage: "Invalid response format!", debugData: {json: json}});
|
|
18
|
+
}
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return new SERVER_ERROR({displayMessage: "Failed to parse JSON", originalError: err, debugData: {text: txt}});
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -1,115 +1,115 @@
|
|
|
1
|
-
import {ERROR, ERROR_JSON, GGContractApiDefinition, GGContractClass, GGContractMethod, OK} from "@grest-ts/schema";
|
|
2
|
-
import type {HttpMethod} from "@grest-ts/common";
|
|
3
|
-
import type http from "http";
|
|
4
|
-
import type {GGHttpServerMiddleware} from "../server/GGHttpSchema.startServer";
|
|
5
|
-
|
|
6
|
-
export class GGHttpSchema<TContract extends GGContractApiDefinition, TContext> {
|
|
7
|
-
|
|
8
|
-
public readonly name: string
|
|
9
|
-
public readonly pathPrefix: string
|
|
10
|
-
public readonly apiMiddlewares: readonly GGHttpTransportMiddleware[]
|
|
11
|
-
public readonly codec: Record<keyof TContract, GGHttpCodec>
|
|
12
|
-
public readonly contract: GGContractClass<TContract> | null = null
|
|
13
|
-
|
|
14
|
-
constructor(
|
|
15
|
-
pathPrefix: string,
|
|
16
|
-
contract: GGContractClass<TContract>,
|
|
17
|
-
wireCodec: Record<keyof TContract, GGHttpCodec>,
|
|
18
|
-
middlewares: readonly GGHttpTransportMiddleware[] = []
|
|
19
|
-
) {
|
|
20
|
-
this.name = contract.name
|
|
21
|
-
this.pathPrefix = pathPrefix
|
|
22
|
-
this.apiMiddlewares = middlewares
|
|
23
|
-
this.codec = wireCodec
|
|
24
|
-
this.contract = contract
|
|
25
|
-
Object.freeze(this.apiMiddlewares)
|
|
26
|
-
Object.freeze(this.codec)
|
|
27
|
-
Object.freeze(this)
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// --------------------------------------------------------------------------------------------------------
|
|
32
|
-
// Client codec
|
|
33
|
-
// --------------------------------------------------------------------------------------------------------
|
|
34
|
-
|
|
35
|
-
export interface ClientHttpRouteToRpcTransformClientConfig {
|
|
36
|
-
pathPrefix: string,
|
|
37
|
-
contract: GGContractMethod,
|
|
38
|
-
middlewares: readonly GGHttpTransportMiddleware[]
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface ClientHttpRouteToRpcTransformClientCodec {
|
|
42
|
-
createRequest: (data: unknown) => GGHttpFetchRequest | Promise<GGHttpFetchRequest>
|
|
43
|
-
parseResponse: (response: Response) => Promise<OK<unknown> | ERROR_JSON<string, unknown>>
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface GGHttpFetchRequest {
|
|
47
|
-
url: string;
|
|
48
|
-
method: HttpMethod;
|
|
49
|
-
headers: Record<string, string>;
|
|
50
|
-
body: string | FormData | undefined;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// --------------------------------------------------------------------------------------------------------
|
|
54
|
-
// Server codec
|
|
55
|
-
// --------------------------------------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
export interface ClientHttpRouteToRpcTransformServerConfig {
|
|
58
|
-
contract: GGContractMethod,
|
|
59
|
-
apiMiddlewares: readonly GGHttpTransportMiddleware[],
|
|
60
|
-
serverMiddlewares: readonly GGHttpServerMiddleware[]
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface ClientHttpRouteToRpcTransformServerCodec {
|
|
64
|
-
parseRequest: (req: http.IncomingMessage) => Promise<unknown>,
|
|
65
|
-
sendResponse: (res: http.ServerResponse, rpcResult: ERROR<string, unknown> | OK<unknown>) => Promise<void>
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// --------------------------------------------------------------------------------------------------------
|
|
69
|
-
// Codec
|
|
70
|
-
// --------------------------------------------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
export interface GGHttpCodec {
|
|
73
|
-
readonly method: HttpMethod;
|
|
74
|
-
readonly path: string;
|
|
75
|
-
|
|
76
|
-
createForClient(config: ClientHttpRouteToRpcTransformClientConfig): ClientHttpRouteToRpcTransformClientCodec
|
|
77
|
-
|
|
78
|
-
createForServer(config: ClientHttpRouteToRpcTransformServerConfig): ClientHttpRouteToRpcTransformServerCodec
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// --------------------------------------------------------------------------------------------------------
|
|
83
|
-
// Middleware
|
|
84
|
-
// --------------------------------------------------------------------------------------------------------
|
|
85
|
-
|
|
86
|
-
export interface GGHttpTransportMiddleware {
|
|
87
|
-
/**
|
|
88
|
-
* Client-side: modify outgoing request (add headers, etc.)
|
|
89
|
-
*/
|
|
90
|
-
updateRequest?(req: GGHttpRequest): void;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Server-side: parse incoming request (extract context from headers, etc.)
|
|
94
|
-
*/
|
|
95
|
-
parseRequest?(req: GGHttpRequest): void;
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Server-side: modify outgoing response
|
|
99
|
-
*/
|
|
100
|
-
updateResponse?(res: GGHttpResponse): void;
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Client-side: parse incoming response
|
|
104
|
-
*/
|
|
105
|
-
parseResponse?(res: GGHttpResponse): void;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export interface GGHttpRequest {
|
|
109
|
-
headers?: Record<string, string | string[]>;
|
|
110
|
-
queryArgs?: Record<string, string | string[]>;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export interface GGHttpResponse {
|
|
114
|
-
headers: Record<string, string | string[]>;
|
|
115
|
-
}
|
|
1
|
+
import {ERROR, ERROR_JSON, GGContractApiDefinition, GGContractClass, GGContractMethod, OK} from "@grest-ts/schema";
|
|
2
|
+
import type {HttpMethod} from "@grest-ts/common";
|
|
3
|
+
import type http from "http";
|
|
4
|
+
import type {GGHttpServerMiddleware} from "../server/GGHttpSchema.startServer";
|
|
5
|
+
|
|
6
|
+
export class GGHttpSchema<TContract extends GGContractApiDefinition, TContext> {
|
|
7
|
+
|
|
8
|
+
public readonly name: string
|
|
9
|
+
public readonly pathPrefix: string
|
|
10
|
+
public readonly apiMiddlewares: readonly GGHttpTransportMiddleware[]
|
|
11
|
+
public readonly codec: Record<keyof TContract, GGHttpCodec>
|
|
12
|
+
public readonly contract: GGContractClass<TContract> | null = null
|
|
13
|
+
|
|
14
|
+
constructor(
|
|
15
|
+
pathPrefix: string,
|
|
16
|
+
contract: GGContractClass<TContract>,
|
|
17
|
+
wireCodec: Record<keyof TContract, GGHttpCodec>,
|
|
18
|
+
middlewares: readonly GGHttpTransportMiddleware[] = []
|
|
19
|
+
) {
|
|
20
|
+
this.name = contract.name
|
|
21
|
+
this.pathPrefix = pathPrefix
|
|
22
|
+
this.apiMiddlewares = middlewares
|
|
23
|
+
this.codec = wireCodec
|
|
24
|
+
this.contract = contract
|
|
25
|
+
Object.freeze(this.apiMiddlewares)
|
|
26
|
+
Object.freeze(this.codec)
|
|
27
|
+
Object.freeze(this)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// --------------------------------------------------------------------------------------------------------
|
|
32
|
+
// Client codec
|
|
33
|
+
// --------------------------------------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
export interface ClientHttpRouteToRpcTransformClientConfig {
|
|
36
|
+
pathPrefix: string,
|
|
37
|
+
contract: GGContractMethod,
|
|
38
|
+
middlewares: readonly GGHttpTransportMiddleware[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ClientHttpRouteToRpcTransformClientCodec {
|
|
42
|
+
createRequest: (data: unknown) => GGHttpFetchRequest | Promise<GGHttpFetchRequest>
|
|
43
|
+
parseResponse: (response: Response) => Promise<OK<unknown> | ERROR_JSON<string, unknown>>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface GGHttpFetchRequest {
|
|
47
|
+
url: string;
|
|
48
|
+
method: HttpMethod;
|
|
49
|
+
headers: Record<string, string>;
|
|
50
|
+
body: string | FormData | undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --------------------------------------------------------------------------------------------------------
|
|
54
|
+
// Server codec
|
|
55
|
+
// --------------------------------------------------------------------------------------------------------
|
|
56
|
+
|
|
57
|
+
export interface ClientHttpRouteToRpcTransformServerConfig {
|
|
58
|
+
contract: GGContractMethod,
|
|
59
|
+
apiMiddlewares: readonly GGHttpTransportMiddleware[],
|
|
60
|
+
serverMiddlewares: readonly GGHttpServerMiddleware[]
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ClientHttpRouteToRpcTransformServerCodec {
|
|
64
|
+
parseRequest: (req: http.IncomingMessage) => Promise<unknown>,
|
|
65
|
+
sendResponse: (res: http.ServerResponse, rpcResult: ERROR<string, unknown> | OK<unknown>) => Promise<void>
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --------------------------------------------------------------------------------------------------------
|
|
69
|
+
// Codec
|
|
70
|
+
// --------------------------------------------------------------------------------------------------------
|
|
71
|
+
|
|
72
|
+
export interface GGHttpCodec {
|
|
73
|
+
readonly method: HttpMethod;
|
|
74
|
+
readonly path: string;
|
|
75
|
+
|
|
76
|
+
createForClient(config: ClientHttpRouteToRpcTransformClientConfig): ClientHttpRouteToRpcTransformClientCodec
|
|
77
|
+
|
|
78
|
+
createForServer(config: ClientHttpRouteToRpcTransformServerConfig): ClientHttpRouteToRpcTransformServerCodec
|
|
79
|
+
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// --------------------------------------------------------------------------------------------------------
|
|
83
|
+
// Middleware
|
|
84
|
+
// --------------------------------------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
export interface GGHttpTransportMiddleware {
|
|
87
|
+
/**
|
|
88
|
+
* Client-side: modify outgoing request (add headers, etc.)
|
|
89
|
+
*/
|
|
90
|
+
updateRequest?(req: GGHttpRequest): void;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Server-side: parse incoming request (extract context from headers, etc.)
|
|
94
|
+
*/
|
|
95
|
+
parseRequest?(req: GGHttpRequest): void;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Server-side: modify outgoing response
|
|
99
|
+
*/
|
|
100
|
+
updateResponse?(res: GGHttpResponse): void;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Client-side: parse incoming response
|
|
104
|
+
*/
|
|
105
|
+
parseResponse?(res: GGHttpResponse): void;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface GGHttpRequest {
|
|
109
|
+
headers?: Record<string, string | string[]>;
|
|
110
|
+
queryArgs?: Record<string, string | string[]>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface GGHttpResponse {
|
|
114
|
+
headers: Record<string, string | string[]>;
|
|
115
|
+
}
|