@gugananuvem/aws-local-simulator 1.0.2 → 1.0.5

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 (37) hide show
  1. package/bin/aws-local-simulator.js +1 -62
  2. package/package.json +8 -10
  3. package/src/index.js +1 -131
  4. package/src/config/config-loader.js +0 -113
  5. package/src/config/default-config.js +0 -65
  6. package/src/config/env-loader.js +0 -67
  7. package/src/index.mjs +0 -124
  8. package/src/server.js +0 -219
  9. package/src/services/apigateway/index.js +0 -67
  10. package/src/services/apigateway/server.js +0 -435
  11. package/src/services/apigateway/simulator.js +0 -1252
  12. package/src/services/cognito/index.js +0 -66
  13. package/src/services/cognito/server.js +0 -229
  14. package/src/services/cognito/simulator.js +0 -848
  15. package/src/services/dynamodb/index.js +0 -71
  16. package/src/services/dynamodb/server.js +0 -122
  17. package/src/services/dynamodb/simulator.js +0 -614
  18. package/src/services/eventbridge/index.js +0 -85
  19. package/src/services/index.js +0 -19
  20. package/src/services/lambda/handler-loader.js +0 -173
  21. package/src/services/lambda/index.js +0 -73
  22. package/src/services/lambda/route-registry.js +0 -275
  23. package/src/services/lambda/server.js +0 -153
  24. package/src/services/lambda/simulator.js +0 -278
  25. package/src/services/s3/index.js +0 -70
  26. package/src/services/s3/server.js +0 -239
  27. package/src/services/s3/simulator.js +0 -740
  28. package/src/services/sns/index.js +0 -76
  29. package/src/services/sqs/index.js +0 -96
  30. package/src/services/sqs/server.js +0 -274
  31. package/src/services/sqs/simulator.js +0 -660
  32. package/src/template/aws-config-template.js +0 -88
  33. package/src/template/aws-config-template.mjs +0 -91
  34. package/src/template/config-template.json +0 -165
  35. package/src/utils/aws-config.js +0 -92
  36. package/src/utils/local-store.js +0 -68
  37. package/src/utils/logger.js +0 -60
@@ -1,1252 +0,0 @@
1
- /**
2
- * API Gateway Simulator Core
3
- * Simula REST APIs, HTTP APIs, WebSocket APIs, Routes, Integrations
4
- */
5
-
6
- const crypto = require('crypto');
7
- const { v4: uuidv4 } = require('uuid');
8
- const logger = require('../../utils/logger');
9
- const LocalStore = require('../../utils/local-store');
10
- const path = require('path');
11
- const { URLPattern } = require('urlpattern-polyfill');
12
-
13
- class APIGatewaySimulator {
14
- constructor(config) {
15
- this.config = config;
16
- this.dataDir = path.join(process.env.AWS_LOCAL_SIMULATOR_DATA_DIR, 'apigateway');
17
- this.store = new LocalStore(this.dataDir);
18
- this.apis = new Map(); // REST/HTTP APIs
19
- this.websocketApis = new Map(); // WebSocket APIs
20
- this.deployments = new Map();
21
- this.stages = new Map();
22
- this.resources = new Map();
23
- this.methods = new Map();
24
- this.integrations = new Map();
25
- this.models = new Map();
26
- this.usagePlans = new Map();
27
- this.apiKeys = new Map();
28
- this.domainNames = new Map();
29
- }
30
-
31
- async initialize() {
32
- logger.debug('Inicializando API Gateway Simulator...');
33
- this.loadAPIs();
34
- this.loadWebSocketAPIs();
35
- this.loadDeployments();
36
- this.loadStages();
37
- this.loadResources();
38
- this.loadMethods();
39
- this.loadIntegrations();
40
- this.loadModels();
41
- this.loadUsagePlans();
42
- this.loadApiKeys();
43
- this.loadDomainNames();
44
-
45
- logger.debug(`✅ API Gateway Simulator inicializado com ${this.apis.size} APIs, ${this.resources.size} resources, ${this.methods.size} methods`);
46
- }
47
-
48
- // ============ REST API Operations ============
49
-
50
- createRestApi(params) {
51
- const { name, description, version, apiKeySource, endpointConfiguration, tags } = params;
52
-
53
- const apiId = `api_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
54
- const api = {
55
- id: apiId,
56
- name: name,
57
- description: description || '',
58
- version: version || '1.0',
59
- createdDate: new Date().toISOString(),
60
- apiKeySource: apiKeySource || 'HEADER',
61
- endpointConfiguration: endpointConfiguration || {
62
- types: ['REGIONAL']
63
- },
64
- tags: tags || {},
65
- resources: new Map(),
66
- stages: new Map(),
67
- deployments: new Map(),
68
- models: new Map(),
69
- authorizers: new Map(),
70
- gatewayResponses: new Map(),
71
- documentationParts: new Map()
72
- };
73
-
74
- // Cria recurso raiz
75
- const rootResource = {
76
- id: uuidv4(),
77
- path: '/',
78
- pathPart: '',
79
- parentId: null,
80
- resourceMethods: new Map()
81
- };
82
-
83
- api.resources.set('/', rootResource);
84
-
85
- this.apis.set(apiId, api);
86
- this.persistAPIs();
87
-
88
- logger.debug(`✅ REST API criada: ${name} (${apiId})`);
89
-
90
- return {
91
- id: apiId,
92
- name: api.name,
93
- createdDate: api.createdDate
94
- };
95
- }
96
-
97
- getRestApis() {
98
- return {
99
- items: Array.from(this.apis.values()).map(api => ({
100
- id: api.id,
101
- name: api.name,
102
- description: api.description,
103
- version: api.version,
104
- createdDate: api.createdDate,
105
- apiKeySource: api.apiKeySource
106
- }))
107
- };
108
- }
109
-
110
- getRestApi(params) {
111
- const { restApiId } = params;
112
- const api = this.apis.get(restApiId);
113
-
114
- if (!api) {
115
- throw new Error(`API ${restApiId} not found`);
116
- }
117
-
118
- return {
119
- id: api.id,
120
- name: api.name,
121
- description: api.description,
122
- version: api.version,
123
- createdDate: api.createdDate,
124
- apiKeySource: api.apiKeySource,
125
- endpointConfiguration: api.endpointConfiguration,
126
- tags: api.tags
127
- };
128
- }
129
-
130
- deleteRestApi(params) {
131
- const { restApiId } = params;
132
-
133
- if (!this.apis.has(restApiId)) {
134
- throw new Error(`API ${restApiId} not found`);
135
- }
136
-
137
- this.apis.delete(restApiId);
138
- this.persistAPIs();
139
-
140
- return {};
141
- }
142
-
143
- // ============ Resource Operations ============
144
-
145
- createResource(params) {
146
- const { restApiId, parentId, pathPart } = params;
147
- const api = this.apis.get(restApiId);
148
-
149
- if (!api) {
150
- throw new Error(`API ${restApiId} not found`);
151
- }
152
-
153
- const parentResource = api.resources.get(parentId);
154
- if (!parentResource) {
155
- throw new Error(`Parent resource ${parentId} not found`);
156
- }
157
-
158
- const resourceId = uuidv4();
159
- const fullPath = parentResource.path === '/'
160
- ? `/${pathPart}`
161
- : `${parentResource.path}/${pathPart}`;
162
-
163
- const resource = {
164
- id: resourceId,
165
- path: fullPath,
166
- pathPart: pathPart,
167
- parentId: parentId,
168
- resourceMethods: new Map()
169
- };
170
-
171
- api.resources.set(resourceId, resource);
172
- this.persistResources(restApiId);
173
-
174
- logger.debug(`📁 Recurso criado: ${fullPath} (${resourceId})`);
175
-
176
- return {
177
- id: resourceId,
178
- path: resource.path,
179
- parentId: resource.parentId
180
- };
181
- }
182
-
183
- getResources(params) {
184
- const { restApiId } = params;
185
- const api = this.apis.get(restApiId);
186
-
187
- if (!api) {
188
- throw new Error(`API ${restApiId} not found`);
189
- }
190
-
191
- const items = Array.from(api.resources.values()).map(resource => ({
192
- id: resource.id,
193
- path: resource.path,
194
- pathPart: resource.pathPart,
195
- parentId: resource.parentId,
196
- resourceMethods: Object.fromEntries(resource.resourceMethods)
197
- }));
198
-
199
- return { items };
200
- }
201
-
202
- deleteResource(params) {
203
- const { restApiId, resourceId } = params;
204
- const api = this.apis.get(restApiId);
205
-
206
- if (!api) {
207
- throw new Error(`API ${restApiId} not found`);
208
- }
209
-
210
- if (!api.resources.has(resourceId)) {
211
- throw new Error(`Resource ${resourceId} not found`);
212
- }
213
-
214
- // Verifica se tem filhos
215
- const hasChildren = Array.from(api.resources.values()).some(
216
- r => r.parentId === resourceId
217
- );
218
-
219
- if (hasChildren) {
220
- throw new Error('Resource has children');
221
- }
222
-
223
- api.resources.delete(resourceId);
224
- this.persistResources(restApiId);
225
-
226
- return {};
227
- }
228
-
229
- // ============ Method Operations ============
230
-
231
- putMethod(params) {
232
- const { restApiId, resourceId, httpMethod, authorizationType, apiKeyRequired, requestParameters, requestModels, authorizerId } = params;
233
- const api = this.apis.get(restApiId);
234
-
235
- if (!api) {
236
- throw new Error(`API ${restApiId} not found`);
237
- }
238
-
239
- const resource = api.resources.get(resourceId);
240
- if (!resource) {
241
- throw new Error(`Resource ${resourceId} not found`);
242
- }
243
-
244
- const method = {
245
- httpMethod: httpMethod,
246
- authorizationType: authorizationType || 'NONE',
247
- apiKeyRequired: apiKeyRequired || false,
248
- requestParameters: requestParameters || {},
249
- requestModels: requestModels || {},
250
- authorizerId: authorizerId || null,
251
- methodResponses: new Map(),
252
- integration: null
253
- };
254
-
255
- resource.resourceMethods.set(httpMethod, method);
256
- this.persistMethods(restApiId, resourceId);
257
-
258
- logger.debug(`🔧 Método criado: ${httpMethod} ${resource.path}`);
259
-
260
- return {
261
- httpMethod: method.httpMethod,
262
- authorizationType: method.authorizationType,
263
- apiKeyRequired: method.apiKeyRequired
264
- };
265
- }
266
-
267
- getMethod(params) {
268
- const { restApiId, resourceId, httpMethod } = params;
269
- const api = this.apis.get(restApiId);
270
-
271
- if (!api) {
272
- throw new Error(`API ${restApiId} not found`);
273
- }
274
-
275
- const resource = api.resources.get(resourceId);
276
- if (!resource) {
277
- throw new Error(`Resource ${resourceId} not found`);
278
- }
279
-
280
- const method = resource.resourceMethods.get(httpMethod);
281
- if (!method) {
282
- throw new Error(`Method ${httpMethod} not found`);
283
- }
284
-
285
- return {
286
- httpMethod: method.httpMethod,
287
- authorizationType: method.authorizationType,
288
- apiKeyRequired: method.apiKeyRequired,
289
- requestParameters: method.requestParameters,
290
- requestModels: method.requestModels
291
- };
292
- }
293
-
294
- deleteMethod(params) {
295
- const { restApiId, resourceId, httpMethod } = params;
296
- const api = this.apis.get(restApiId);
297
-
298
- if (!api) {
299
- throw new Error(`API ${restApiId} not found`);
300
- }
301
-
302
- const resource = api.resources.get(resourceId);
303
- if (!resource) {
304
- throw new Error(`Resource ${resourceId} not found`);
305
- }
306
-
307
- resource.resourceMethods.delete(httpMethod);
308
- this.persistMethods(restApiId, resourceId);
309
-
310
- return {};
311
- }
312
-
313
- // ============ Integration Operations ============
314
-
315
- putIntegration(params) {
316
- const { restApiId, resourceId, httpMethod, type, integrationHttpMethod, uri, credentials, requestParameters, requestTemplates, passthroughBehavior, timeoutInMillis, cacheNamespace, cacheKeyParameters, contentHandling } = params;
317
- const api = this.apis.get(restApiId);
318
-
319
- if (!api) {
320
- throw new Error(`API ${restApiId} not found`);
321
- }
322
-
323
- const resource = api.resources.get(resourceId);
324
- if (!resource) {
325
- throw new Error(`Resource ${resourceId} not found`);
326
- }
327
-
328
- const method = resource.resourceMethods.get(httpMethod);
329
- if (!method) {
330
- throw new Error(`Method ${httpMethod} not found`);
331
- }
332
-
333
- const integration = {
334
- type: type || 'HTTP',
335
- integrationHttpMethod: integrationHttpMethod,
336
- uri: uri,
337
- credentials: credentials || null,
338
- requestParameters: requestParameters || {},
339
- requestTemplates: requestTemplates || {},
340
- passthroughBehavior: passthroughBehavior || 'WHEN_NO_MATCH',
341
- timeoutInMillis: timeoutInMillis || 29000,
342
- cacheNamespace: cacheNamespace || '',
343
- cacheKeyParameters: cacheKeyParameters || [],
344
- contentHandling: contentHandling || null,
345
- integrationResponses: new Map()
346
- };
347
-
348
- method.integration = integration;
349
- this.persistIntegrations(restApiId, resourceId);
350
-
351
- logger.debug(`🔌 Integração criada: ${type} -> ${uri}`);
352
-
353
- return {
354
- type: integration.type,
355
- integrationHttpMethod: integration.integrationHttpMethod,
356
- uri: integration.uri
357
- };
358
- }
359
-
360
- getIntegration(params) {
361
- const { restApiId, resourceId, httpMethod } = params;
362
- const api = this.apis.get(restApiId);
363
-
364
- if (!api) {
365
- throw new Error(`API ${restApiId} not found`);
366
- }
367
-
368
- const resource = api.resources.get(resourceId);
369
- if (!resource) {
370
- throw new Error(`Resource ${resourceId} not found`);
371
- }
372
-
373
- const method = resource.resourceMethods.get(httpMethod);
374
- if (!method || !method.integration) {
375
- throw new Error(`Integration not found for ${httpMethod}`);
376
- }
377
-
378
- const integration = method.integration;
379
-
380
- return {
381
- type: integration.type,
382
- integrationHttpMethod: integration.integrationHttpMethod,
383
- uri: integration.uri,
384
- credentials: integration.credentials,
385
- requestParameters: integration.requestParameters,
386
- requestTemplates: integration.requestTemplates,
387
- passthroughBehavior: integration.passthroughBehavior,
388
- timeoutInMillis: integration.timeoutInMillis
389
- };
390
- }
391
-
392
- deleteIntegration(params) {
393
- const { restApiId, resourceId, httpMethod } = params;
394
- const api = this.apis.get(restApiId);
395
-
396
- if (!api) {
397
- throw new Error(`API ${restApiId} not found`);
398
- }
399
-
400
- const resource = api.resources.get(resourceId);
401
- if (!resource) {
402
- throw new Error(`Resource ${resourceId} not found`);
403
- }
404
-
405
- const method = resource.resourceMethods.get(httpMethod);
406
- if (method) {
407
- method.integration = null;
408
- this.persistIntegrations(restApiId, resourceId);
409
- }
410
-
411
- return {};
412
- }
413
-
414
- // ============ Integration Response Operations ============
415
-
416
- putIntegrationResponse(params) {
417
- const { restApiId, resourceId, httpMethod, statusCode, selectionPattern, responseParameters, responseTemplates, contentHandling } = params;
418
- const api = this.apis.get(restApiId);
419
-
420
- if (!api) {
421
- throw new Error(`API ${restApiId} not found`);
422
- }
423
-
424
- const resource = api.resources.get(resourceId);
425
- if (!resource) {
426
- throw new Error(`Resource ${resourceId} not found`);
427
- }
428
-
429
- const method = resource.resourceMethods.get(httpMethod);
430
- if (!method || !method.integration) {
431
- throw new Error(`Integration not found for ${httpMethod}`);
432
- }
433
-
434
- const integrationResponse = {
435
- statusCode: statusCode,
436
- selectionPattern: selectionPattern || '',
437
- responseParameters: responseParameters || {},
438
- responseTemplates: responseTemplates || {},
439
- contentHandling: contentHandling || null
440
- };
441
-
442
- method.integration.integrationResponses.set(statusCode, integrationResponse);
443
- this.persistIntegrations(restApiId, resourceId);
444
-
445
- return {
446
- statusCode: integrationResponse.statusCode,
447
- selectionPattern: integrationResponse.selectionPattern
448
- };
449
- }
450
-
451
- // ============ Deployment Operations ============
452
-
453
- createDeployment(params) {
454
- const { restApiId, stageName, stageDescription, description, variables } = params;
455
- const api = this.apis.get(restApiId);
456
-
457
- if (!api) {
458
- throw new Error(`API ${restApiId} not found`);
459
- }
460
-
461
- const deploymentId = uuidv4();
462
- const deployment = {
463
- id: deploymentId,
464
- description: description || '',
465
- createdDate: new Date().toISOString(),
466
- apiId: restApiId
467
- };
468
-
469
- api.deployments.set(deploymentId, deployment);
470
-
471
- if (stageName) {
472
- this.createStage({
473
- restApiId,
474
- stageName,
475
- description: stageDescription,
476
- variables
477
- });
478
- }
479
-
480
- this.persistDeployments(restApiId);
481
-
482
- logger.debug(`🚀 Deployment criado: ${deploymentId} para stage: ${stageName || 'N/A'}`);
483
-
484
- return {
485
- id: deploymentId,
486
- createdDate: deployment.createdDate
487
- };
488
- }
489
-
490
- // ============ Stage Operations ============
491
-
492
- createStage(params) {
493
- const { restApiId, stageName, description, variables, deploymentId, cacheClusterEnabled, cacheClusterSize, tracingEnabled } = params;
494
- const api = this.apis.get(restApiId);
495
-
496
- if (!api) {
497
- throw new Error(`API ${restApiId} not found`);
498
- }
499
-
500
- const stage = {
501
- stageName: stageName,
502
- description: description || '',
503
- createdDate: new Date().toISOString(),
504
- lastUpdatedDate: new Date().toISOString(),
505
- deploymentId: deploymentId,
506
- variables: variables || {},
507
- cacheClusterEnabled: cacheClusterEnabled || false,
508
- cacheClusterSize: cacheClusterSize || null,
509
- tracingEnabled: tracingEnabled || false,
510
- methodSettings: new Map()
511
- };
512
-
513
- api.stages.set(stageName, stage);
514
- this.persistStages(restApiId);
515
-
516
- // Cria endpoint URL
517
- const endpointUrl = `http://localhost:${this.config.ports.apigateway}/${restApiId}/${stageName}`;
518
-
519
- logger.debug(`📡 Stage criado: ${stageName} em ${endpointUrl}`);
520
-
521
- return {
522
- stageName: stage.stageName,
523
- createdDate: stage.createdDate,
524
- deploymentId: stage.deploymentId
525
- };
526
- }
527
-
528
- getStage(params) {
529
- const { restApiId, stageName } = params;
530
- const api = this.apis.get(restApiId);
531
-
532
- if (!api) {
533
- throw new Error(`API ${restApiId} not found`);
534
- }
535
-
536
- const stage = api.stages.get(stageName);
537
- if (!stage) {
538
- throw new Error(`Stage ${stageName} not found`);
539
- }
540
-
541
- return {
542
- stageName: stage.stageName,
543
- description: stage.description,
544
- createdDate: stage.createdDate,
545
- lastUpdatedDate: stage.lastUpdatedDate,
546
- deploymentId: stage.deploymentId,
547
- variables: stage.variables,
548
- methodSettings: Object.fromEntries(stage.methodSettings),
549
- cacheClusterEnabled: stage.cacheClusterEnabled,
550
- tracingEnabled: stage.tracingEnabled
551
- };
552
- }
553
-
554
- updateStage(params) {
555
- const { restApiId, stageName, description, variables, deploymentId, tracingEnabled } = params;
556
- const api = this.apis.get(restApiId);
557
-
558
- if (!api) {
559
- throw new Error(`API ${restApiId} not found`);
560
- }
561
-
562
- const stage = api.stages.get(stageName);
563
- if (!stage) {
564
- throw new Error(`Stage ${stageName} not found`);
565
- }
566
-
567
- if (description !== undefined) stage.description = description;
568
- if (variables !== undefined) stage.variables = { ...stage.variables, ...variables };
569
- if (deploymentId !== undefined) stage.deploymentId = deploymentId;
570
- if (tracingEnabled !== undefined) stage.tracingEnabled = tracingEnabled;
571
-
572
- stage.lastUpdatedDate = new Date().toISOString();
573
- this.persistStages(restApiId);
574
-
575
- return {
576
- stageName: stage.stageName,
577
- description: stage.description,
578
- lastUpdatedDate: stage.lastUpdatedDate
579
- };
580
- }
581
-
582
- deleteStage(params) {
583
- const { restApiId, stageName } = params;
584
- const api = this.apis.get(restApiId);
585
-
586
- if (!api) {
587
- throw new Error(`API ${restApiId} not found`);
588
- }
589
-
590
- api.stages.delete(stageName);
591
- this.persistStages(restApiId);
592
-
593
- return {};
594
- }
595
-
596
- // ============ API Key Operations ============
597
-
598
- createApiKey(params) {
599
- const { name, description, enabled, stageKeys, customerId } = params;
600
-
601
- const apiKeyId = uuidv4();
602
- const apiKey = {
603
- id: apiKeyId,
604
- name: name || '',
605
- description: description || '',
606
- enabled: enabled !== false,
607
- value: crypto.randomBytes(20).toString('hex'),
608
- stageKeys: stageKeys || [],
609
- customerId: customerId || null,
610
- createdDate: new Date().toISOString(),
611
- lastUpdatedDate: new Date().toISOString()
612
- };
613
-
614
- this.apiKeys.set(apiKeyId, apiKey);
615
- this.persistApiKeys();
616
-
617
- logger.debug(`🔑 API Key criada: ${name || apiKeyId}`);
618
-
619
- return {
620
- id: apiKey.id,
621
- name: apiKey.name,
622
- value: apiKey.value,
623
- enabled: apiKey.enabled
624
- };
625
- }
626
-
627
- getApiKeys(params) {
628
- const items = Array.from(this.apiKeys.values()).map(key => ({
629
- id: key.id,
630
- name: key.name,
631
- description: key.description,
632
- enabled: key.enabled,
633
- createdDate: key.createdDate,
634
- lastUpdatedDate: key.lastUpdatedDate
635
- }));
636
-
637
- return { items };
638
- }
639
-
640
- // ============ Usage Plan Operations ============
641
-
642
- createUsagePlan(params) {
643
- const { name, description, apiStages, throttle, quota } = params;
644
-
645
- const usagePlanId = uuidv4();
646
- const usagePlan = {
647
- id: usagePlanId,
648
- name: name,
649
- description: description || '',
650
- apiStages: apiStages || [],
651
- throttle: throttle || {
652
- burstLimit: 100,
653
- rateLimit: 10
654
- },
655
- quota: quota || {
656
- limit: 10000,
657
- period: 'DAY',
658
- offset: 0
659
- },
660
- createdDate: new Date().toISOString(),
661
- lastUpdatedDate: new Date().toISOString()
662
- };
663
-
664
- this.usagePlans.set(usagePlanId, usagePlan);
665
- this.persistUsagePlans();
666
-
667
- logger.debug(`📊 Usage Plan criado: ${name}`);
668
-
669
- return {
670
- id: usagePlan.id,
671
- name: usagePlan.name,
672
- createdDate: usagePlan.createdDate
673
- };
674
- }
675
-
676
- // ============ Request Execution ============
677
-
678
- async executeRequest(apiId, stageName, method, path, headers, body, queryString) {
679
- const api = this.apis.get(apiId);
680
- if (!api) {
681
- return this.createResponse(404, 'API not found');
682
- }
683
-
684
- const stage = api.stages.get(stageName);
685
- if (!stage) {
686
- return this.createResponse(404, 'Stage not found');
687
- }
688
-
689
- // Encontra o recurso e método correspondente
690
- const { resource, methodDef } = this.findMatchingResource(api, method, path);
691
-
692
- if (!resource || !methodDef) {
693
- return this.createResponse(404, 'Resource or method not found');
694
- }
695
-
696
- // Verifica API Key
697
- if (methodDef.apiKeyRequired) {
698
- const apiKey = this.extractApiKey(headers);
699
- if (!this.validateApiKey(apiKey)) {
700
- return this.createResponse(403, 'Forbidden: Invalid API Key');
701
- }
702
- }
703
-
704
- // Verifica throttling
705
- const usagePlan = this.getUsagePlanForApi(apiId, stageName);
706
- if (usagePlan && !this.checkThrottle(usagePlan)) {
707
- return this.createResponse(429, 'Too Many Requests');
708
- }
709
-
710
- // Processa integração
711
- const integration = methodDef.integration;
712
- if (!integration) {
713
- return this.createResponse(500, 'Integration not configured');
714
- }
715
-
716
- // Executa a integração baseada no tipo
717
- const response = await this.executeIntegration(integration, {
718
- method,
719
- path,
720
- headers,
721
- body,
722
- queryString,
723
- resource,
724
- stage
725
- });
726
-
727
- // Aplica resposta da integração
728
- const integrationResponse = this.getMatchingIntegrationResponse(
729
- integration.integrationResponses,
730
- response.statusCode
731
- );
732
-
733
- if (integrationResponse) {
734
- response.headers = { ...response.headers, ...integrationResponse.responseParameters };
735
- response.body = this.applyResponseTemplate(integrationResponse, response.body);
736
- }
737
-
738
- return response;
739
- }
740
-
741
- findMatchingResource(api, method, path) {
742
- // Busca recurso que corresponda ao path
743
- const resources = Array.from(api.resources.values());
744
-
745
- // Ordena por path mais específico primeiro
746
- resources.sort((a, b) => b.path.length - a.path.length);
747
-
748
- for (const resource of resources) {
749
- if (this.matchPath(resource.path, path)) {
750
- const methodDef = resource.resourceMethods.get(method);
751
- if (methodDef) {
752
- return { resource, methodDef };
753
- }
754
- }
755
- }
756
-
757
- return { resource: null, methodDef: null };
758
- }
759
-
760
- matchPath(pattern, path) {
761
- // Converte path com parâmetros para regex
762
- // Ex: /users/{userId}/posts/{postId}
763
- const regexPattern = pattern
764
- .replace(/\{([^}]+)\}/g, '([^/]+)')
765
- .replace(/\//g, '\\/');
766
-
767
- const regex = new RegExp(`^${regexPattern}$`);
768
- return regex.test(path);
769
- }
770
-
771
- extractPathParams(pattern, path) {
772
- const paramNames = [];
773
- const regexPattern = pattern
774
- .replace(/\{([^}]+)\}/g, (match, paramName) => {
775
- paramNames.push(paramName);
776
- return '([^/]+)';
777
- })
778
- .replace(/\//g, '\\/');
779
-
780
- const regex = new RegExp(`^${regexPattern}$`);
781
- const match = path.match(regex);
782
-
783
- if (match) {
784
- const params = {};
785
- paramNames.forEach((name, index) => {
786
- params[name] = match[index + 1];
787
- });
788
- return params;
789
- }
790
-
791
- return {};
792
- }
793
-
794
- async executeIntegration(integration, context) {
795
- const { type, uri, integrationHttpMethod, requestTemplates, requestParameters } = integration;
796
-
797
- // Prepara o request
798
- let requestBody = context.body;
799
- let requestHeaders = { ...context.headers };
800
-
801
- // Aplica templates de request
802
- if (requestTemplates && requestTemplates['application/json']) {
803
- const template = requestTemplates['application/json'];
804
- requestBody = this.applyRequestTemplate(template, context);
805
- }
806
-
807
- // Aplica mapeamento de parâmetros
808
- if (requestParameters) {
809
- for (const [key, value] of Object.entries(requestParameters)) {
810
- requestHeaders[key] = this.resolveParameter(value, context);
811
- }
812
- }
813
-
814
- switch(type) {
815
- case 'AWS':
816
- case 'AWS_PROXY':
817
- return this.executeAWSIntegration(integration, context, requestBody, requestHeaders);
818
- case 'HTTP':
819
- case 'HTTP_PROXY':
820
- return this.executeHTTPIntegration(integration, context, requestBody, requestHeaders);
821
- case 'MOCK':
822
- return this.executeMockIntegration(integration);
823
- default:
824
- return this.createResponse(501, `Integration type ${type} not supported`);
825
- }
826
- }
827
-
828
- async executeAWSIntegration(integration, context, body, headers) {
829
- // Simula integração com Lambda
830
- const uriParts = integration.uri.split(':');
831
- const functionName = uriParts[uriParts.length - 1];
832
-
833
- // Aqui poderia chamar o simulador Lambda
834
- logger.debug(`Invoking Lambda: ${functionName}`);
835
-
836
- return this.createResponse(200, {
837
- message: `Lambda ${functionName} invoked`,
838
- event: context
839
- });
840
- }
841
-
842
- async executeHTTPIntegration(integration, context, body, headers) {
843
- const { uri, integrationHttpMethod } = integration;
844
-
845
- logger.debug(`HTTP ${integrationHttpMethod} to ${uri}`);
846
-
847
- // Simula chamada HTTP
848
- try {
849
- const response = await this.makeHttpRequest(uri, integrationHttpMethod, headers, body);
850
- return {
851
- statusCode: response.status,
852
- headers: response.headers,
853
- body: response.data
854
- };
855
- } catch (error) {
856
- return this.createResponse(502, 'Bad Gateway');
857
- }
858
- }
859
-
860
- async makeHttpRequest(url, method, headers, body) {
861
- // Simulação - em implementação real, usaria axios ou fetch
862
- return {
863
- status: 200,
864
- headers: {},
865
- data: { message: 'Mock HTTP response' }
866
- };
867
- }
868
-
869
- executeMockIntegration(integration) {
870
- // Retorna resposta mock
871
- const mockResponse = integration.requestTemplates?.mock || {};
872
- return this.createResponse(200, mockResponse);
873
- }
874
-
875
- getMatchingIntegrationResponse(responses, statusCode) {
876
- // Busca response que corresponda ao status code
877
- if (responses.has(statusCode.toString())) {
878
- return responses.get(statusCode.toString());
879
- }
880
-
881
- // Busca default
882
- if (responses.has('default')) {
883
- return responses.get('default');
884
- }
885
-
886
- return null;
887
- }
888
-
889
- applyRequestTemplate(template, context) {
890
- // Implementação simples de template
891
- try {
892
- return JSON.stringify(context);
893
- } catch (error) {
894
- return template;
895
- }
896
- }
897
-
898
- applyResponseTemplate(response, body) {
899
- const templates = response.responseTemplates;
900
- if (templates && templates['application/json']) {
901
- // Aplica template de resposta
902
- try {
903
- return JSON.parse(templates['application/json'].replace(/\$\{([^}]+)\}/g, (match, path) => {
904
- return this.getNestedValue(body, path);
905
- }));
906
- } catch (error) {
907
- return body;
908
- }
909
- }
910
- return body;
911
- }
912
-
913
- resolveParameter(value, context) {
914
- // Resolve parâmetros como method.request.header.X-Header
915
- if (value.includes('method.request.')) {
916
- const parts = value.split('.');
917
- const type = parts[2]; // header, querystring, path
918
- const name = parts[3];
919
-
920
- switch(type) {
921
- case 'header':
922
- return context.headers[name];
923
- case 'querystring':
924
- return context.queryString[name];
925
- case 'path':
926
- return context.pathParams[name];
927
- default:
928
- return null;
929
- }
930
- }
931
- return value;
932
- }
933
-
934
- getNestedValue(obj, path) {
935
- return path.split('.').reduce((current, key) => current?.[key], obj);
936
- }
937
-
938
- extractApiKey(headers) {
939
- return headers['x-api-key'] || headers['X-Api-Key'];
940
- }
941
-
942
- validateApiKey(apiKeyValue) {
943
- if (!apiKeyValue) return false;
944
-
945
- for (const key of this.apiKeys.values()) {
946
- if (key.value === apiKeyValue && key.enabled) {
947
- return true;
948
- }
949
- }
950
- return false;
951
- }
952
-
953
- getUsagePlanForApi(apiId, stageName) {
954
- for (const plan of this.usagePlans.values()) {
955
- const apiStage = plan.apiStages.find(
956
- as => as.apiId === apiId && as.stage === stageName
957
- );
958
- if (apiStage) {
959
- return plan;
960
- }
961
- }
962
- return null;
963
- }
964
-
965
- checkThrottle(usagePlan) {
966
- // Implementação simplificada de throttling
967
- const { rateLimit, burstLimit } = usagePlan.throttle;
968
- // Aqui poderia implementar contagem de requests
969
- return true;
970
- }
971
-
972
- createResponse(statusCode, body) {
973
- return {
974
- statusCode: statusCode,
975
- headers: {
976
- 'Content-Type': 'application/json',
977
- 'Access-Control-Allow-Origin': '*'
978
- },
979
- body: typeof body === 'string' ? body : JSON.stringify(body)
980
- };
981
- }
982
-
983
- // ============ HTTP API Operations ============
984
-
985
- createHttpApi(params) {
986
- const { name, description, protocolType, routeSelectionExpression, corsConfiguration } = params;
987
-
988
- const apiId = `http_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
989
- const api = {
990
- id: apiId,
991
- name: name,
992
- description: description || '',
993
- protocolType: protocolType || 'HTTP',
994
- routeSelectionExpression: routeSelectionExpression || '$request.method $request.path',
995
- corsConfiguration: corsConfiguration || {
996
- allowOrigins: ['*'],
997
- allowMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
998
- allowHeaders: ['*'],
999
- maxAge: 300
1000
- },
1001
- routes: new Map(),
1002
- integrations: new Map(),
1003
- stages: new Map(),
1004
- createdDate: new Date().toISOString()
1005
- };
1006
-
1007
- this.apis.set(apiId, api);
1008
- this.persistAPIs();
1009
-
1010
- logger.debug(`✅ HTTP API criada: ${name} (${apiId})`);
1011
-
1012
- return {
1013
- ApiId: apiId,
1014
- Name: api.name,
1015
- ProtocolType: api.protocolType
1016
- };
1017
- }
1018
-
1019
- createRoute(params) {
1020
- const { apiId, routeKey, authorizationType, target } = params;
1021
- const api = this.apis.get(apiId);
1022
-
1023
- if (!api) {
1024
- throw new Error(`API ${apiId} not found`);
1025
- }
1026
-
1027
- const route = {
1028
- routeKey: routeKey,
1029
- authorizationType: authorizationType || 'NONE',
1030
- target: target,
1031
- createdAt: new Date().toISOString()
1032
- };
1033
-
1034
- api.routes.set(routeKey, route);
1035
- this.persistAPIs();
1036
-
1037
- return { route };
1038
- }
1039
-
1040
- // ============ Persistence ============
1041
-
1042
- loadAPIs() {
1043
- const saved = this.store.read('__apis__');
1044
- if (saved) {
1045
- for (const [id, data] of Object.entries(saved)) {
1046
- // Reconstitui Maps
1047
- data.resources = new Map(Object.entries(data.resources || {}));
1048
- data.stages = new Map(Object.entries(data.stages || {}));
1049
- data.deployments = new Map(Object.entries(data.deployments || {}));
1050
- data.models = new Map(Object.entries(data.models || {}));
1051
- data.authorizers = new Map(Object.entries(data.authorizers || {}));
1052
- this.apis.set(id, data);
1053
- }
1054
- }
1055
- }
1056
-
1057
- loadWebSocketAPIs() {
1058
- const saved = this.store.read('__websocket_apis__');
1059
- if (saved) {
1060
- for (const [id, data] of Object.entries(saved)) {
1061
- this.websocketApis.set(id, data);
1062
- }
1063
- }
1064
- }
1065
-
1066
- loadDeployments() {
1067
- const saved = this.store.read('__deployments__');
1068
- if (saved) {
1069
- for (const [id, data] of Object.entries(saved)) {
1070
- this.deployments.set(id, data);
1071
- }
1072
- }
1073
- }
1074
-
1075
- loadStages() {
1076
- const saved = this.store.read('__stages__');
1077
- if (saved) {
1078
- for (const [id, data] of Object.entries(saved)) {
1079
- this.stages.set(id, data);
1080
- }
1081
- }
1082
- }
1083
-
1084
- loadResources() {
1085
- const saved = this.store.read('__resources__');
1086
- if (saved) {
1087
- for (const [id, data] of Object.entries(saved)) {
1088
- this.resources.set(id, data);
1089
- }
1090
- }
1091
- }
1092
-
1093
- loadMethods() {
1094
- const saved = this.store.read('__methods__');
1095
- if (saved) {
1096
- for (const [id, data] of Object.entries(saved)) {
1097
- this.methods.set(id, data);
1098
- }
1099
- }
1100
- }
1101
-
1102
- loadIntegrations() {
1103
- const saved = this.store.read('__integrations__');
1104
- if (saved) {
1105
- for (const [id, data] of Object.entries(saved)) {
1106
- this.integrations.set(id, data);
1107
- }
1108
- }
1109
- }
1110
-
1111
- loadModels() {
1112
- const saved = this.store.read('__models__');
1113
- if (saved) {
1114
- for (const [id, data] of Object.entries(saved)) {
1115
- this.models.set(id, data);
1116
- }
1117
- }
1118
- }
1119
-
1120
- loadUsagePlans() {
1121
- const saved = this.store.read('__usage_plans__');
1122
- if (saved) {
1123
- for (const [id, data] of Object.entries(saved)) {
1124
- this.usagePlans.set(id, data);
1125
- }
1126
- }
1127
- }
1128
-
1129
- loadApiKeys() {
1130
- const saved = this.store.read('__api_keys__');
1131
- if (saved) {
1132
- for (const [id, data] of Object.entries(saved)) {
1133
- this.apiKeys.set(id, data);
1134
- }
1135
- }
1136
- }
1137
-
1138
- loadDomainNames() {
1139
- const saved = this.store.read('__domain_names__');
1140
- if (saved) {
1141
- for (const [id, data] of Object.entries(saved)) {
1142
- this.domainNames.set(id, data);
1143
- }
1144
- }
1145
- }
1146
-
1147
- persistAPIs() {
1148
- const apisObj = {};
1149
- for (const [id, api] of this.apis.entries()) {
1150
- apisObj[id] = {
1151
- ...api,
1152
- resources: Object.fromEntries(api.resources),
1153
- stages: Object.fromEntries(api.stages),
1154
- deployments: Object.fromEntries(api.deployments),
1155
- models: Object.fromEntries(api.models),
1156
- authorizers: Object.fromEntries(api.authorizers)
1157
- };
1158
- }
1159
- this.store.write('__apis__', apisObj);
1160
- }
1161
-
1162
- persistResources(apiId) {
1163
- const api = this.apis.get(apiId);
1164
- if (api) {
1165
- this.persistAPIs();
1166
- }
1167
- }
1168
-
1169
- persistMethods(apiId, resourceId) {
1170
- this.persistResources(apiId);
1171
- }
1172
-
1173
- persistIntegrations(apiId, resourceId) {
1174
- this.persistMethods(apiId, resourceId);
1175
- }
1176
-
1177
- persistStages(apiId) {
1178
- this.persistAPIs();
1179
- }
1180
-
1181
- persistDeployments(apiId) {
1182
- this.persistAPIs();
1183
- }
1184
-
1185
- persistApiKeys() {
1186
- const keysObj = {};
1187
- for (const [id, key] of this.apiKeys.entries()) {
1188
- keysObj[id] = key;
1189
- }
1190
- this.store.write('__api_keys__', keysObj);
1191
- }
1192
-
1193
- persistUsagePlans() {
1194
- const plansObj = {};
1195
- for (const [id, plan] of this.usagePlans.entries()) {
1196
- plansObj[id] = plan;
1197
- }
1198
- this.store.write('__usage_plans__', plansObj);
1199
- }
1200
-
1201
- async reset() {
1202
- this.apis.clear();
1203
- this.websocketApis.clear();
1204
- this.deployments.clear();
1205
- this.stages.clear();
1206
- this.resources.clear();
1207
- this.methods.clear();
1208
- this.integrations.clear();
1209
- this.models.clear();
1210
- this.usagePlans.clear();
1211
- this.apiKeys.clear();
1212
- this.domainNames.clear();
1213
-
1214
- this.persistAPIs();
1215
- this.persistApiKeys();
1216
- this.persistUsagePlans();
1217
-
1218
- logger.debug('API Gateway: Todos os dados resetados');
1219
- }
1220
-
1221
- // ============ Stats ============
1222
-
1223
- getAPIsCount() {
1224
- return this.apis.size;
1225
- }
1226
-
1227
- getDeploymentsCount() {
1228
- let count = 0;
1229
- for (const api of this.apis.values()) {
1230
- count += api.deployments.size;
1231
- }
1232
- return count;
1233
- }
1234
-
1235
- getStagesCount() {
1236
- let count = 0;
1237
- for (const api of this.apis.values()) {
1238
- count += api.stages.size;
1239
- }
1240
- return count;
1241
- }
1242
-
1243
- getResourcesCount() {
1244
- let count = 0;
1245
- for (const api of this.apis.values()) {
1246
- count += api.resources.size;
1247
- }
1248
- return count;
1249
- }
1250
- }
1251
-
1252
- module.exports = APIGatewaySimulator;