@kensio/yulin 1.21.10 → 1.21.11

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.
Files changed (52) hide show
  1. package/dist/serve/payload-2/sim-payload-2-endpoint.d.ts +9 -0
  2. package/dist/serve/payload-2/sim-payload-2-request-context.js +1 -1
  3. package/dist/service/apigatewayv2/api/sim-http-api-lambda-proxy.factory.d.ts +6 -0
  4. package/dist/service/apigatewayv2/api/sim-http-api-lambda-proxy.factory.js +2 -0
  5. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-format.d.ts +8 -0
  6. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-format.js +36 -0
  7. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-request.d.ts +53 -0
  8. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-request.js +1 -0
  9. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-settings.d.ts +26 -0
  10. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-settings.js +44 -0
  11. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-settings.type.d.ts +21 -0
  12. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-settings.type.js +1 -0
  13. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-stream-name.d.ts +13 -0
  14. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-stream-name.js +23 -0
  15. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-variables.d.ts +17 -0
  16. package/dist/service/apigatewayv2/api/stage/access-log/sim-http-api-access-log-variables.js +86 -0
  17. package/dist/service/apigatewayv2/api/stage/sim-http-api-stage.d.ts +9 -0
  18. package/dist/service/apigatewayv2/api/stage/sim-http-api-stage.js +9 -0
  19. package/dist/service/apigatewayv2/cfn/stage/sim-cfn-http-api-access-log-settings-properties.d.ts +24 -0
  20. package/dist/service/apigatewayv2/cfn/stage/sim-cfn-http-api-access-log-settings-properties.js +30 -0
  21. package/dist/service/apigatewayv2/cfn/stage/sim-cfn-http-api-stage-properties.d.ts +1 -0
  22. package/dist/service/apigatewayv2/cfn/stage/sim-cfn-http-api-stage-properties.js +10 -4
  23. package/dist/service/apigatewayv2/command/stage/sim-http-api-access-log-settings-input.d.ts +15 -0
  24. package/dist/service/apigatewayv2/command/stage/sim-http-api-access-log-settings-input.js +33 -0
  25. package/dist/service/apigatewayv2/command/stage/sim-http-api-stage-commands.js +4 -0
  26. package/dist/service/apigatewayv2/command/stage/stage.command.d.ts +2 -0
  27. package/dist/service/apigatewayv2/serve/auth/sim-http-api-route-authorize-input.d.ts +6 -0
  28. package/dist/service/apigatewayv2/serve/sim-api-gateway-v2-controller.d.ts +13 -15
  29. package/dist/service/apigatewayv2/serve/sim-api-gateway-v2-controller.js +26 -60
  30. package/dist/service/apigatewayv2/serve/sim-http-api-access-log-record.d.ts +31 -0
  31. package/dist/service/apigatewayv2/serve/sim-http-api-access-log-record.js +88 -0
  32. package/dist/service/apigatewayv2/serve/sim-http-api-access-log.d.ts +43 -0
  33. package/dist/service/apigatewayv2/serve/sim-http-api-access-log.js +72 -0
  34. package/dist/service/apigatewayv2/serve/sim-http-api-endpoint.js +1 -0
  35. package/dist/service/apigatewayv2/serve/sim-http-api-integration-input.d.ts +22 -0
  36. package/dist/service/apigatewayv2/serve/sim-http-api-integration-input.js +1 -0
  37. package/dist/service/apigatewayv2/serve/sim-http-api-integration-invocation.d.ts +5 -22
  38. package/dist/service/apigatewayv2/serve/sim-http-api-integration-invocation.js +17 -20
  39. package/dist/service/apigatewayv2/serve/sim-http-api-integration-outcome.d.ts +27 -0
  40. package/dist/service/apigatewayv2/serve/sim-http-api-integration-outcome.js +13 -0
  41. package/dist/service/apigatewayv2/serve/sim-http-api-may-invoke.d.ts +19 -0
  42. package/dist/service/apigatewayv2/serve/sim-http-api-may-invoke.js +19 -0
  43. package/dist/service/apigatewayv2/serve/sim-http-api-serve-pipeline.d.ts +27 -0
  44. package/dist/service/apigatewayv2/serve/sim-http-api-serve-pipeline.js +75 -0
  45. package/dist/service/apigatewayv2/serve/sim-http-api-served.d.ts +22 -0
  46. package/dist/service/apigatewayv2/serve/sim-http-api-served.js +1 -0
  47. package/dist/service/apigatewayv2/serve/sim-http-api-serving.d.ts +12 -0
  48. package/dist/service/apigatewayv2/serve/sim-http-api-serving.js +1 -0
  49. package/dist/service/logs/group/sim-logs-arn.d.ts +15 -0
  50. package/dist/service/logs/group/sim-logs-arn.js +33 -0
  51. package/docs/services/apigatewayv2/README.md +162 -6
  52. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import { SimHttpApiStage } from "../../api/stage/sim-http-api-stage.js";
2
2
  import { SimApiGatewayV2NotFound } from "../../error/sim-api-gateway-v2.error.js";
3
3
  import { SimApiGatewayV2UnsimulatedInput } from "../sim-api-gateway-v2-unsimulated-input.js";
4
+ import { simHttpApiAccessLogSettings } from "./sim-http-api-access-log-settings-input.js";
4
5
  import { simHttpApiStageRouteSettings } from "./sim-http-api-stage-route-settings-input.js";
5
6
  import { SimHttpApiStageRules } from "./sim-http-api-stage-rules.js";
6
7
  const stagesPath = "/stages";
@@ -12,6 +13,7 @@ const acceptedCreateStageOptions = [
12
13
  "Description",
13
14
  "DefaultRouteSettings",
14
15
  "RouteSettings",
16
+ "AccessLogSettings",
15
17
  ];
16
18
  /**
17
19
  * The commands addressing the stages of an API.
@@ -38,6 +40,7 @@ export class SimHttpApiStageCommands {
38
40
  unsimulated,
39
41
  clock: this.clock,
40
42
  });
43
+ const accessLogSettings = simHttpApiAccessLogSettings(input, unsimulated);
41
44
  const httpApi = this.access.api({
42
45
  method: "POST",
43
46
  apiId,
@@ -52,6 +55,7 @@ export class SimHttpApiStageCommands {
52
55
  description: input.Description,
53
56
  createdDate: this.clock.now(),
54
57
  routeSettings,
58
+ accessLogSettings,
55
59
  });
56
60
  httpApi.stages.add(stage);
57
61
  return { ...stage.view(), $metadata: {} };
@@ -1,5 +1,6 @@
1
1
  import type { SimResponseMetadata } from "../../../aws/metadata/response-metadata.type.js";
2
2
  import type { SimHttpApiRouteSettings, SimHttpApiRouteSettingsMap } from "../../api/stage/settings/sim-http-api-route-settings.type.js";
3
+ import type { SimHttpApiAccessLogSettingsInput } from "../../api/stage/access-log/sim-http-api-access-log-settings.type.js";
3
4
  import type { SimHttpApiStageView } from "../../api/stage/sim-http-api-stage.js";
4
5
  /**
5
6
  * Minimal structural sim API Gateway v2 CreateStage command.
@@ -17,6 +18,7 @@ export interface SimCreateStageCommandInput {
17
18
  readonly Description?: string | undefined;
18
19
  readonly DefaultRouteSettings?: SimHttpApiRouteSettings | undefined;
19
20
  readonly RouteSettings?: SimHttpApiRouteSettingsMap | undefined;
21
+ readonly AccessLogSettings?: SimHttpApiAccessLogSettingsInput | undefined;
20
22
  }
21
23
  export interface SimCreateStageCommandOutput extends SimHttpApiStageView {
22
24
  readonly $metadata: SimResponseMetadata;
@@ -35,4 +35,10 @@ export interface SimHttpApiRouteAuthorizeInput {
35
35
  * integrated function's.
36
36
  */
37
37
  readonly iam: SimIamInterServiceAuthZ;
38
+ /**
39
+ * The id the endpoint stamped this request with, which a `REQUEST`
40
+ * authorizer's event reports as its own. The authorizer and the integration
41
+ * behind the same request name one id, as they do on real AWS.
42
+ */
43
+ readonly requestId?: string | undefined;
38
44
  }
@@ -10,31 +10,29 @@ interface SimApiGatewayV2ServiceControllerProperties {
10
10
  *
11
11
  * A request reaching the generated endpoint, or a custom domain mapped to the
12
12
  * API, is matched to a route, and the route's integration invokes a simulated
13
- * Lambda function with a payload format 2.0 event. The function runs as its execution Role, as it does for
14
- * any other invocation.
13
+ * Lambda function with a payload format 2.0 event. The function runs as its
14
+ * execution Role, as it does for any other invocation. The throttle and the
15
+ * authorization standing in front of that are the pipeline's.
15
16
  *
16
- * The stage's throttle comes before any of that. A route whose bucket is empty
17
- * is answered 429, and neither its authorizer nor its integration runs.
18
- *
19
- * A route that authorizes anybody is checked next: a JWT route's
20
- * token has to verify and meet the route's scopes, an `AWS_IAM` route's caller
21
- * has to be allowed `execute-api:Invoke` on the route, and a `CUSTOM` route's
22
- * Lambda authorizer has to allow the request, or the request is refused and the
23
- * integration is never invoked. Whether the API may invoke a function is a
24
- * separate question, and the API's own rather than the client's.
17
+ * Every request the resolver matches is stamped with an id before anything
18
+ * runs, so the stage's access log line, the authorizer event and the
19
+ * integration event all name the same request, as they do on real AWS.
25
20
  */
26
21
  export declare class SimApiGatewayV2ServiceController implements SimAwsServiceController {
27
22
  private readonly router;
28
- private readonly routeAuthorizer;
29
- private readonly integration;
23
+ private readonly pipeline;
30
24
  private readonly serving;
25
+ private readonly accessLog;
31
26
  private readonly errorResponse;
32
- private readonly refusalResponse;
33
27
  constructor(properties?: SimApiGatewayV2ServiceControllerProperties);
34
28
  /**
35
29
  * Handle an HTTP request routed to a simulated HTTP API.
36
30
  */
37
31
  handleRequest(serviceRequest: SimAwsServiceRequest): Promise<Response>;
38
- private invoke;
32
+ /**
33
+ * Serve one request the resolver matched to a stage, and record it in that
34
+ * stage's access log.
35
+ */
36
+ private serve;
39
37
  }
40
38
  export {};
@@ -1,50 +1,38 @@
1
+ import { randomUUID } from "node:crypto";
1
2
  import { SimAws } from "../../aws/sim-aws.js";
2
- import { SimHttpApiRouteAuthorizer } from "./auth/sim-http-api-route-authorizer.js";
3
+ import { SimHttpApiAccessLog } from "./sim-http-api-access-log.js";
3
4
  import { SimApiGatewayV2ErrorResponse } from "./sim-api-gateway-v2-error-response.js";
4
5
  import { SimApiGatewayV2Router } from "./sim-api-gateway-v2-router.js";
5
- import { SimHttpApiIntegrationInvocation } from "./sim-http-api-integration-invocation.js";
6
- import { SimHttpApiRefusalResponse } from "./sim-http-api-refusal-response.js";
6
+ import { SimHttpApiServePipeline } from "./sim-http-api-serve-pipeline.js";
7
7
  import { SimHttpApiServingResolver, } from "./sim-http-api-serving.js";
8
8
  /**
9
9
  * Localhost HTTP controller for simulated API Gateway HTTP APIs.
10
10
  *
11
11
  * A request reaching the generated endpoint, or a custom domain mapped to the
12
12
  * API, is matched to a route, and the route's integration invokes a simulated
13
- * Lambda function with a payload format 2.0 event. The function runs as its execution Role, as it does for
14
- * any other invocation.
13
+ * Lambda function with a payload format 2.0 event. The function runs as its
14
+ * execution Role, as it does for any other invocation. The throttle and the
15
+ * authorization standing in front of that are the pipeline's.
15
16
  *
16
- * The stage's throttle comes before any of that. A route whose bucket is empty
17
- * is answered 429, and neither its authorizer nor its integration runs.
18
- *
19
- * A route that authorizes anybody is checked next: a JWT route's
20
- * token has to verify and meet the route's scopes, an `AWS_IAM` route's caller
21
- * has to be allowed `execute-api:Invoke` on the route, and a `CUSTOM` route's
22
- * Lambda authorizer has to allow the request, or the request is refused and the
23
- * integration is never invoked. Whether the API may invoke a function is a
24
- * separate question, and the API's own rather than the client's.
17
+ * Every request the resolver matches is stamped with an id before anything
18
+ * runs, so the stage's access log line, the authorizer event and the
19
+ * integration event all name the same request, as they do on real AWS.
25
20
  */
26
21
  export class SimApiGatewayV2ServiceController {
27
22
  router;
28
- routeAuthorizer;
29
- integration;
23
+ pipeline;
30
24
  serving;
25
+ accessLog;
31
26
  errorResponse = new SimApiGatewayV2ErrorResponse();
32
- refusalResponse = new SimHttpApiRefusalResponse();
33
27
  constructor(properties = {}) {
34
28
  const { simAws = new SimAws() } = properties;
35
29
  this.router = properties.router ?? new SimApiGatewayV2Router({ simAws });
36
- // The clock is taken from the router rather than from properties, so a
37
- // supplied router, the event timestamps and the token expiry checks all
38
- // belong to the same simulation.
39
- this.routeAuthorizer = new SimHttpApiRouteAuthorizer({
40
- clock: this.router.simAws,
41
- functions: this.router,
42
- });
43
- this.integration = new SimHttpApiIntegrationInvocation({
44
- router: this.router,
30
+ this.pipeline = new SimHttpApiServePipeline({ router: this.router });
31
+ this.serving = new SimHttpApiServingResolver({ router: this.router });
32
+ this.accessLog = new SimHttpApiAccessLog({
33
+ simAws: this.router.simAws,
45
34
  clock: this.router.simAws,
46
35
  });
47
- this.serving = new SimHttpApiServingResolver({ router: this.router });
48
36
  }
49
37
  /**
50
38
  * Handle an HTTP request routed to a simulated HTTP API.
@@ -59,41 +47,19 @@ export class SimApiGatewayV2ServiceController {
59
47
  return this.errorResponse.forbiddenHost();
60
48
  }
61
49
  case "served": {
62
- return await this.invoke(resolution.serving, serviceRequest);
50
+ return await this.serve(resolution.serving, serviceRequest);
63
51
  }
64
52
  }
65
53
  }
66
- async invoke(serving, serviceRequest) {
67
- const { request } = serviceRequest;
68
- const { api, match } = serving;
69
- // The stage's throttle is asked before the route's authorizer. A flood of
70
- // requests to a throttled route invokes neither. AWS publishes no order
71
- // between the two.
72
- if (!match.stage.admits(match.route.routeKey)) {
73
- return this.errorResponse.tooManyRequests();
74
- }
75
- // The client's own authorization comes next: a request with no
76
- // credentials is refused whether or not the integration behind the route
77
- // would work.
78
- const authorization = await this.routeAuthorizer.authorize({
79
- api,
80
- match,
81
- request,
82
- caller: serviceRequest.caller,
83
- iam: this.router.iamFor(api),
84
- domainName: serving.domainName,
85
- rawPath: serving.rawPath,
86
- });
87
- if (!authorization.admitted) {
88
- return this.refusalResponse.build(authorization);
89
- }
90
- return await this.integration.invoke({
91
- api,
92
- match,
93
- request,
94
- authorization,
95
- domainName: serving.domainName,
96
- rawPath: serving.rawPath,
97
- });
54
+ /**
55
+ * Serve one request the resolver matched to a stage, and record it in that
56
+ * stage's access log.
57
+ */
58
+ async serve(resolved, serviceRequest) {
59
+ const serving = { ...resolved, requestId: randomUUID() };
60
+ const at = this.router.simAws.now();
61
+ const served = await this.pipeline.run(serving, serviceRequest);
62
+ await this.accessLog.served(serving, serviceRequest.request, served, at);
63
+ return served.response;
98
64
  }
99
65
  }
@@ -0,0 +1,31 @@
1
+ import type { SimHttpApiAccessLogRequest } from "../api/stage/access-log/sim-http-api-access-log-request.js";
2
+ import type { SimHttpApiAuthorization } from "../api/authorizer/sim-http-api-authorization.js";
3
+ import type { SimHttpApiServing } from "./sim-http-api-serving.js";
4
+ import type { SimHttpApiIntegrationOutcome } from "./sim-http-api-integration-outcome.js";
5
+ interface SimHttpApiAccessLogRecordInput {
6
+ readonly serving: SimHttpApiServing;
7
+ readonly requestId: string;
8
+ readonly request: Request;
9
+ readonly response: Response;
10
+ /** Simulated milliseconds the whole request took. */
11
+ readonly responseLatency: number;
12
+ /** How long the response body is, measured from the response itself. */
13
+ readonly responseLength: number;
14
+ /** The simulated instant the request arrived. */
15
+ readonly at: Date;
16
+ /** Absent for a request refused before the route's authorizer ran. */
17
+ readonly authorization?: SimHttpApiAuthorization | undefined;
18
+ /** Absent for a request no integration was invoked for. */
19
+ readonly integration?: SimHttpApiIntegrationOutcome | undefined;
20
+ }
21
+ /**
22
+ * Describe one served request as its stage's access log records it.
23
+ *
24
+ * Every path through the endpoint arrives here, including the ones that
25
+ * refused the request. A throttled request has no authorization and no
26
+ * integration, and an authorizer that said no has an authorization and no
27
+ * integration. What each one knows is what its line carries, and the rest of
28
+ * the format's variables render as dashes.
29
+ */
30
+ export declare function simHttpApiAccessLogRecord(input: SimHttpApiAccessLogRecordInput): SimHttpApiAccessLogRequest;
31
+ export {};
@@ -0,0 +1,88 @@
1
+ import { simAwsProxiedSourceIp } from "../../../serve/http/sim-aws-proxied-connection.js";
2
+ /**
3
+ * The `$context.error.message` an endpoint's own refusal carries.
4
+ *
5
+ * These are the bodies `SimApiGatewayV2ErrorResponse` answers with, keyed by
6
+ * the status they answer with. A response an integration produced has no entry
7
+ * here, and leaves the variable empty as real API Gateway does.
8
+ */
9
+ const refusalMessages = new Map([
10
+ [401, "Unauthorized"],
11
+ [403, "Forbidden"],
12
+ [429, "Too Many Requests"],
13
+ [500, "Internal Server Error"],
14
+ ]);
15
+ /**
16
+ * Describe one served request as its stage's access log records it.
17
+ *
18
+ * Every path through the endpoint arrives here, including the ones that
19
+ * refused the request. A throttled request has no authorization and no
20
+ * integration, and an authorizer that said no has an authorization and no
21
+ * integration. What each one knows is what its line carries, and the rest of
22
+ * the format's variables render as dashes.
23
+ */
24
+ export function simHttpApiAccessLogRecord(input) {
25
+ const { serving, request, response, authorization, integration } = input;
26
+ const { api, match, domainName } = serving;
27
+ const admitted = authorization?.admitted === true ? authorization : undefined;
28
+ return {
29
+ requestId: input.requestId,
30
+ at: input.at,
31
+ accountId: api.accountRegionScope.accountId,
32
+ apiId: api.apiId,
33
+ domainName,
34
+ domainPrefix: domainName.split(".", 1).join(""),
35
+ stage: match.stage.stageName,
36
+ routeKey: match.route.routeKey,
37
+ method: request.method,
38
+ path: serving.rawPath,
39
+ protocol: "HTTP/1.1",
40
+ sourceIp: simAwsProxiedSourceIp,
41
+ userAgent: request.headers.get("user-agent") ?? "",
42
+ status: response.status,
43
+ responseLength: input.responseLength,
44
+ responseLatency: input.responseLatency,
45
+ ...(refusalMessage(response, integration) !== undefined && {
46
+ errorMessage: refusalMessage(response, integration),
47
+ }),
48
+ ...(authorization?.admitted === false &&
49
+ authorization.errorDescription !== undefined && {
50
+ authorizerError: authorization.errorDescription,
51
+ }),
52
+ ...integrationFields(integration),
53
+ ...(admitted?.jwt !== undefined && { jwt: admitted.jwt }),
54
+ ...(admitted?.lambda !== undefined && { lambda: admitted.lambda }),
55
+ ...(serving.basePathMatched !== undefined && {
56
+ basePathMatched: serving.basePathMatched,
57
+ }),
58
+ };
59
+ }
60
+ /**
61
+ * What the integration contributed, with the members it has no value for left
62
+ * out so that they render as dashes.
63
+ *
64
+ * A request no integration ran for contributes nothing at all, which is what a
65
+ * throttled or refused request does.
66
+ */
67
+ function integrationFields(integration) {
68
+ if (integration === undefined) {
69
+ return {};
70
+ }
71
+ const { integrationStatus, lambdaInvokeStatus, integrationErrorMessage, integrationLatency, } = integration;
72
+ return {
73
+ ...(integrationStatus !== undefined && { integrationStatus }),
74
+ ...(lambdaInvokeStatus !== undefined && { lambdaInvokeStatus }),
75
+ ...(integrationErrorMessage !== undefined && { integrationErrorMessage }),
76
+ ...(integrationLatency !== undefined && { integrationLatency }),
77
+ };
78
+ }
79
+ /**
80
+ * The message the endpoint answered with, where the endpoint answered rather
81
+ * than the integration.
82
+ */
83
+ function refusalMessage(response, integration) {
84
+ if (integration?.integrationStatus !== undefined) {
85
+ return undefined;
86
+ }
87
+ return refusalMessages.get(response.status);
88
+ }
@@ -0,0 +1,43 @@
1
+ import type { SimAws } from "../../aws/sim-aws.js";
2
+ import type { SimClock } from "../../../util/clock/sim-clock.js";
3
+ import type { SimHttpApiAccessLogRequest } from "../api/stage/access-log/sim-http-api-access-log-request.js";
4
+ import type { SimHttpApiStage } from "../api/stage/sim-http-api-stage.js";
5
+ import type { SimHttpApiServed } from "./sim-http-api-served.js";
6
+ import type { SimHttpApiServing } from "./sim-http-api-serving.js";
7
+ interface SimHttpApiAccessLogProperties {
8
+ readonly simAws: SimAws;
9
+ readonly clock: SimClock;
10
+ }
11
+ /**
12
+ * Writes a stage's access log line for one request.
13
+ *
14
+ * The line goes to the log group alone. Sim Lambda forwards a handler's output
15
+ * to the host console as well, and an API answering a few hundred requests in
16
+ * one test file would bury the suite's own output the same way. Writing
17
+ * through `SimLogsServiceWriter` keeps it out of the console, and a test reads
18
+ * the lines back with `FilterLogEvents`.
19
+ *
20
+ * Nothing here fails. A destination log group that was never declared is
21
+ * created by the write, and a stage with no access log settings writes
22
+ * nothing, which is how a request is served whether or not anybody is logging
23
+ * it.
24
+ */
25
+ export declare class SimHttpApiAccessLog {
26
+ #private;
27
+ constructor(properties: SimHttpApiAccessLogProperties);
28
+ /**
29
+ * Record one finished request against the stage that served it.
30
+ *
31
+ * A stage with no access log settings does nothing here, including none of
32
+ * the measuring a line needs. The response body is measured from a clone,
33
+ * since the one the client gets still has to be readable, and the latency is
34
+ * simulated milliseconds, so a test holding the clock logs a latency of zero
35
+ * rather than however long the process happened to take.
36
+ */
37
+ served(serving: SimHttpApiServing, request: Request, served: SimHttpApiServed, at: Date): Promise<void>;
38
+ /**
39
+ * Record one described request against the stage that served it.
40
+ */
41
+ record(stage: SimHttpApiStage, request: SimHttpApiAccessLogRequest): void;
42
+ }
43
+ export {};
@@ -0,0 +1,72 @@
1
+ import { simHttpApiAccessLogLine } from "../api/stage/access-log/sim-http-api-access-log-format.js";
2
+ import { simHttpApiAccessLogStreamName } from "../api/stage/access-log/sim-http-api-access-log-stream-name.js";
3
+ import { simHttpApiAccessLogVariables } from "../api/stage/access-log/sim-http-api-access-log-variables.js";
4
+ import { simHttpApiAccessLogRecord } from "./sim-http-api-access-log-record.js";
5
+ /**
6
+ * Writes a stage's access log line for one request.
7
+ *
8
+ * The line goes to the log group alone. Sim Lambda forwards a handler's output
9
+ * to the host console as well, and an API answering a few hundred requests in
10
+ * one test file would bury the suite's own output the same way. Writing
11
+ * through `SimLogsServiceWriter` keeps it out of the console, and a test reads
12
+ * the lines back with `FilterLogEvents`.
13
+ *
14
+ * Nothing here fails. A destination log group that was never declared is
15
+ * created by the write, and a stage with no access log settings writes
16
+ * nothing, which is how a request is served whether or not anybody is logging
17
+ * it.
18
+ */
19
+ export class SimHttpApiAccessLog {
20
+ #simAws;
21
+ #clock;
22
+ constructor(properties) {
23
+ this.#simAws = properties.simAws;
24
+ this.#clock = properties.clock;
25
+ }
26
+ /**
27
+ * Record one finished request against the stage that served it.
28
+ *
29
+ * A stage with no access log settings does nothing here, including none of
30
+ * the measuring a line needs. The response body is measured from a clone,
31
+ * since the one the client gets still has to be readable, and the latency is
32
+ * simulated milliseconds, so a test holding the clock logs a latency of zero
33
+ * rather than however long the process happened to take.
34
+ */
35
+ async served(serving, request, served, at) {
36
+ const { match, requestId = "" } = serving;
37
+ if (match.stage.accessLogSettings === undefined) {
38
+ return;
39
+ }
40
+ const body = await served.response.clone().arrayBuffer();
41
+ this.record(match.stage, simHttpApiAccessLogRecord({
42
+ serving,
43
+ requestId,
44
+ request,
45
+ response: served.response,
46
+ responseLength: body.byteLength,
47
+ responseLatency: this.#clock.now().getTime() - at.getTime(),
48
+ at,
49
+ ...(served.authorization !== undefined && {
50
+ authorization: served.authorization,
51
+ }),
52
+ ...(served.integration !== undefined && {
53
+ integration: served.integration,
54
+ }),
55
+ }));
56
+ }
57
+ /**
58
+ * Record one described request against the stage that served it.
59
+ */
60
+ record(stage, request) {
61
+ const settings = stage.accessLogSettings;
62
+ if (settings === undefined) {
63
+ return;
64
+ }
65
+ const line = simHttpApiAccessLogLine(settings.format, simHttpApiAccessLogVariables(request));
66
+ this.#simAws
67
+ .accountRegionScope(settings.accountId, settings.regionName)
68
+ .logs()
69
+ .serviceWriter()
70
+ .write(settings.logGroupName, simHttpApiAccessLogStreamName(this.#clock), [line]);
71
+ }
72
+ }
@@ -18,6 +18,7 @@
18
18
  export function simHttpApiEndpoint(serving) {
19
19
  const { api, match, domainName } = serving;
20
20
  return {
21
+ ...(serving.requestId !== undefined && { requestId: serving.requestId }),
21
22
  apiId: api.apiId,
22
23
  domainName,
23
24
  // The first label, which is the API id for a generated endpoint and the
@@ -0,0 +1,22 @@
1
+ import type { SimHttpApiAdmitted } from "../api/authorizer/sim-http-api-authorization.js";
2
+ import type { SimHttpApiMatch } from "../api/sim-http-api-match.js";
3
+ import type { SimHttpApi } from "../api/sim-http-api.js";
4
+ /**
5
+ * One request an integration is invoked for, as the endpoint resolved it.
6
+ */
7
+ export interface SimHttpApiIntegrationInvocationInput {
8
+ readonly api: SimHttpApi;
9
+ readonly match: SimHttpApiMatch;
10
+ readonly request: Request;
11
+ /** What the route's authorization knows about the caller. */
12
+ readonly authorization: SimHttpApiAdmitted;
13
+ /** The AWS-shaped hostname the request arrived on. */
14
+ readonly domainName: string;
15
+ /** The path the invocation event reports, which the resolver settled. */
16
+ readonly rawPath: string;
17
+ /**
18
+ * The id the endpoint stamped this request with, which the invocation event
19
+ * reports as its own.
20
+ */
21
+ readonly requestId?: string | undefined;
22
+ }
@@ -1,24 +1,12 @@
1
1
  import type { SimClock } from "../../../util/clock/sim-clock.js";
2
- import type { SimHttpApiAdmitted } from "../api/authorizer/sim-http-api-authorization.js";
3
- import type { SimHttpApiMatch } from "../api/sim-http-api-match.js";
4
- import type { SimHttpApi } from "../api/sim-http-api.js";
5
2
  import type { SimApiGatewayV2Router } from "./sim-api-gateway-v2-router.js";
3
+ import type { SimHttpApiIntegrationInvocationInput } from "./sim-http-api-integration-input.js";
4
+ import { type SimHttpApiIntegrationOutcome } from "./sim-http-api-integration-outcome.js";
6
5
  interface SimHttpApiIntegrationInvocationProperties {
7
6
  readonly router: SimApiGatewayV2Router;
8
7
  /** Clock the invocation event is stamped with. */
9
8
  readonly clock: SimClock;
10
9
  }
11
- interface SimHttpApiIntegrationInvocationInput {
12
- readonly api: SimHttpApi;
13
- readonly match: SimHttpApiMatch;
14
- readonly request: Request;
15
- /** What the route's authorization knows about the caller. */
16
- readonly authorization: SimHttpApiAdmitted;
17
- /** The AWS-shaped hostname the request arrived on. */
18
- readonly domainName: string;
19
- /** The path the invocation event reports, which the resolver settled. */
20
- readonly rawPath: string;
21
- }
22
10
  /**
23
11
  * Invokes the Lambda function behind the matched route's integration, and
24
12
  * turns what it returns into the HTTP response.
@@ -37,13 +25,8 @@ export declare class SimHttpApiIntegrationInvocation {
37
25
  /**
38
26
  * Invoke the integration for one authorized request.
39
27
  */
40
- invoke(input: SimHttpApiIntegrationInvocationInput): Promise<Response>;
41
- /**
42
- * Whether the API lacks permission to invoke the integrated function.
43
- *
44
- * The route the request matched is supplied as the source ARN, so a
45
- * permission granted for one route does not open another.
46
- */
47
- private mayNotInvoke;
28
+ invoke(input: SimHttpApiIntegrationInvocationInput): Promise<SimHttpApiIntegrationOutcome>;
29
+ /** The 500 answered whenever a handler was never reached. */
30
+ private failed;
48
31
  }
49
32
  export {};
@@ -1,9 +1,9 @@
1
1
  import { SimPayload2EventBuilder } from "../../../serve/payload-2/sim-payload-2-event-builder.js";
2
2
  import { SimPayload2ResponseBuilder } from "../../../serve/payload-2/sim-payload-2-response-builder.js";
3
- import { SimHttpApiExecuteApiArn } from "../api/sim-http-api-execute-api-arn.js";
4
- import { SimHttpApiInvokeAuthorizer } from "./auth/sim-http-api-invoke-authorizer.js";
5
3
  import { SimApiGatewayV2ErrorResponse } from "./sim-api-gateway-v2-error-response.js";
4
+ import { simHttpApiMayNotInvoke } from "./sim-http-api-may-invoke.js";
6
5
  import { simHttpApiEndpoint } from "./sim-http-api-endpoint.js";
6
+ import { simHttpApiIntegrationFailure, } from "./sim-http-api-integration-outcome.js";
7
7
  /**
8
8
  * Invokes the Lambda function behind the matched route's integration, and
9
9
  * turns what it returns into the HTTP response.
@@ -30,11 +30,11 @@ export class SimHttpApiIntegrationInvocation {
30
30
  async invoke(input) {
31
31
  const { match, request, authorization } = input;
32
32
  const target = this.router.targetFor(match.integration);
33
- if (target === undefined || this.mayNotInvoke(input, target)) {
33
+ if (target === undefined || simHttpApiMayNotInvoke(input, target)) {
34
34
  // The integration names a function that is not there, or one that
35
35
  // granted the API nothing. Real API Gateway discovers both only when it
36
36
  // tries to invoke, answers 500, and leaves the reason in its own logs.
37
- return this.errorResponse.internalServerError();
37
+ return this.failed("The integration could not be invoked");
38
38
  }
39
39
  try {
40
40
  const event = await this.eventBuilder.build(request, simHttpApiEndpoint(input), {
@@ -42,28 +42,25 @@ export class SimHttpApiIntegrationInvocation {
42
42
  caller: authorization.caller,
43
43
  lambda: authorization.lambda,
44
44
  });
45
- return this.responseBuilder.build(await target.simFunction.invoke(event));
45
+ const response = this.responseBuilder.build(await target.simFunction.invoke(event));
46
+ // Lambda answered the invocation, whatever the handler then returned.
47
+ // That is the distinction between the two statuses AWS logs.
48
+ return {
49
+ response,
50
+ integrationStatus: response.status,
51
+ lambdaInvokeStatus: 200,
52
+ };
46
53
  }
47
- catch {
54
+ catch (error) {
48
55
  // Real API Gateway reports an unhandled integration error as a 500, with
49
56
  // the error itself only visible in the function's logs. Reading the
50
57
  // request body can fail the same way, so building the event is inside
51
58
  // this too.
52
- return this.errorResponse.internalServerError();
59
+ return this.failed(error);
53
60
  }
54
61
  }
55
- /**
56
- * Whether the API lacks permission to invoke the integrated function.
57
- *
58
- * The route the request matched is supplied as the source ARN, so a
59
- * permission granted for one route does not open another.
60
- */
61
- mayNotInvoke(input, target) {
62
- const { api, match, request } = input;
63
- return new SimHttpApiInvokeAuthorizer({ iam: target.iam }).authorize({
64
- api,
65
- target,
66
- sourceArn: SimHttpApiExecuteApiArn.forMatchedRoute(api, match, request.method),
67
- }).isDenied;
62
+ /** The 500 answered whenever a handler was never reached. */
63
+ failed(error) {
64
+ return simHttpApiIntegrationFailure(this.errorResponse.internalServerError(), error);
68
65
  }
69
66
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * What invoking an integration produced, beyond the response the client gets.
3
+ *
4
+ * The two statuses are the pair AWS distinguishes in its access log variables.
5
+ * `lambdaInvokeStatus` is Lambda's own answer to the invocation, and
6
+ * `integrationStatus` the status the handler's code returned. An invocation
7
+ * that never reached a handler has neither.
8
+ */
9
+ export interface SimHttpApiIntegrationOutcome {
10
+ readonly response: Response;
11
+ readonly integrationStatus?: number | undefined;
12
+ readonly lambdaInvokeStatus?: number | undefined;
13
+ readonly integrationErrorMessage?: string | undefined;
14
+ /**
15
+ * Simulated milliseconds the invocation took. A test holding the clock still
16
+ * measures zero, the way the whole request's latency does.
17
+ */
18
+ readonly integrationLatency?: number | undefined;
19
+ }
20
+ /**
21
+ * The 500 every way of failing to reach a handler produces, carrying the
22
+ * reason the access log reports as `$context.integrationErrorMessage`.
23
+ *
24
+ * The message never reaches the client. Real API Gateway answers the same
25
+ * body whatever went wrong, and leaves the reason in its own logs.
26
+ */
27
+ export declare function simHttpApiIntegrationFailure(response: Response, error: unknown): SimHttpApiIntegrationOutcome;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The 500 every way of failing to reach a handler produces, carrying the
3
+ * reason the access log reports as `$context.integrationErrorMessage`.
4
+ *
5
+ * The message never reaches the client. Real API Gateway answers the same
6
+ * body whatever went wrong, and leaves the reason in its own logs.
7
+ */
8
+ export function simHttpApiIntegrationFailure(response, error) {
9
+ return {
10
+ response,
11
+ integrationErrorMessage: error instanceof Error ? error.message : String(error),
12
+ };
13
+ }