Skip to content

Commit 01e9dfd

Browse files
committed
Add ability to remove resource type
1 parent 20dd762 commit 01e9dfd

File tree

9 files changed

+121
-2
lines changed

9 files changed

+121
-2
lines changed

src/handlers/ResourceHandler.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { ServerRequestHandler } from 'vscode-languageserver';
33
import { RequestHandler } from 'vscode-languageserver/node';
44
import { TopLevelSection } from '../context/ContextType';
55
import { getEntityMap } from '../context/SectionContextBuilder';
6+
import { parseResourceTypeName } from '../resourceState/ResourceStateParser';
67
import {
78
ResourceTypesResult,
89
ListResourcesParams,
@@ -22,6 +23,7 @@ import { GetStackTemplateParams, GetStackTemplateResult } from '../stacks/StackR
2223
import { LoggerFactory } from '../telemetry/LoggerFactory';
2324
import { TelemetryService } from '../telemetry/TelemetryService';
2425
import { extractErrorMessage } from '../utils/Errors';
26+
import { parseWithPrettyError } from '../utils/ZodErrorWrapper';
2527

2628
const log = LoggerFactory.getLogger('ResourceHandler');
2729

@@ -39,6 +41,13 @@ export function getResourceTypesHandler(
3941
};
4042
}
4143

44+
export function removeResourceTypeHandler(components: ServerComponents): RequestHandler<string, void, void> {
45+
return (rawParams: string) => {
46+
const typeName = parseWithPrettyError(parseResourceTypeName, rawParams);
47+
components.resourceStateManager.removeResourceType(typeName);
48+
};
49+
}
50+
4251
export function listResourcesHandler(
4352
components: ServerComponents,
4453
): RequestHandler<ListResourcesParams, ListResourcesResult, void> {

src/protocol/LspResourceHandlers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
SearchResourceRequest,
1818
SearchResourceParams,
1919
SearchResourceResult,
20+
RemoveResourceTypeRequest,
2021
} from '../resourceState/ResourceStateTypes';
2122
import { ResourceStackManagementResult } from '../resourceState/StackManagementInfoProvider';
2223

@@ -35,6 +36,10 @@ export class LspResourceHandlers {
3536
this.connection.onRequest(ResourceTypesRequest.method, handler);
3637
}
3738

39+
onRemoveResourceType(handler: RequestHandler<string, void, void>) {
40+
this.connection.onRequest(RemoveResourceTypeRequest.method, handler);
41+
}
42+
3843
onResourceStateImport(handler: ServerRequestHandler<ResourceStateParams, ResourceStateResult, never, void>) {
3944
this.connection.onRequest(ResourceStateRequest.method, handler);
4045
}

src/resourceState/ResourceStateManager.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { ISettingsSubscriber, SettingsConfigurable, SettingsSubscription } from
1212
import { DefaultSettings, ProfileSettings } from '../settings/Settings';
1313
import { LoggerFactory } from '../telemetry/LoggerFactory';
1414
import { ScopedTelemetry } from '../telemetry/ScopedTelemetry';
15-
import { Telemetry, Measure } from '../telemetry/TelemetryDecorator';
15+
import { Telemetry, Measure, Count } from '../telemetry/TelemetryDecorator';
1616
import { Closeable } from '../utils/Closeable';
1717
import { handleLspError } from '../utils/Errors';
1818
import { NO_LIST_SUPPORT, REQUIRES_RESOURCE_MODEL } from './ListResourcesExclusionTypes';
@@ -170,6 +170,12 @@ export class ResourceStateManager implements SettingsConfigurable, Closeable {
170170
return [...listableTypes].filter((type) => !NO_LIST_SUPPORT.has(type) && !REQUIRES_RESOURCE_MODEL.has(type));
171171
}
172172

173+
@Count({ name: 'removeResourceType' })
174+
public removeResourceType(typeName: string) {
175+
this.resourceListMap.delete(typeName);
176+
this.resourceStateMap.delete(typeName);
177+
}
178+
173179
private storeResourceState(typeName: ResourceType, id: ResourceId, state: ResourceState) {
174180
let resourceIdToStateMap = this.resourceStateMap.get(typeName);
175181
if (!resourceIdToStateMap) {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { z } from 'zod';
2+
3+
const ResourceTypeNameSchema = z.string().min(1);
4+
5+
export function parseResourceTypeName(input: unknown): string {
6+
return ResourceTypeNameSchema.parse(input);
7+
}

src/resourceState/ResourceStateTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ export const RefreshResourceListRequest = new RequestType<RefreshResourcesParams
9999
'aws/cfn/resources/refresh',
100100
);
101101

102+
export const RemoveResourceTypeRequest = new RequestType<string, void, void>('aws/cfn/resources/list/remove');
103+
102104
export const DeletionPolicyOnImport = 'Retain';
103105

104106
export interface ResourceTemplateFormat {

src/server/CfnServer.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
refreshResourceListHandler,
2525
searchResourceHandler,
2626
getStackMgmtInfo,
27+
removeResourceTypeHandler,
2728
} from '../handlers/ResourceHandler';
2829
import { uploadFileToS3Handler } from '../handlers/S3Handler';
2930
import {
@@ -212,6 +213,9 @@ export class CfnServer {
212213
this.lsp.resourceHandlers.onGetResourceTypes(
213214
withTelemetryContext('Resource.Get.Types', getResourceTypesHandler(this.components)),
214215
);
216+
this.lsp.resourceHandlers.onRemoveResourceType(
217+
withTelemetryContext('Resource.Remove.Type', removeResourceTypeHandler(this.components)),
218+
);
215219
this.lsp.resourceHandlers.onResourceStateImport(
216220
withTelemetryContext('Resource.State.Import', importResourceStateHandler(this.components)),
217221
);

tst/unit/handlers/ResourceHandler.test.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { describe, it, expect, beforeEach, vi } from 'vitest';
22
import { CancellationToken } from 'vscode-languageserver-protocol';
33
import { getEntityMap } from '../../../src/context/SectionContextBuilder';
4-
import { getManagedResourceStackTemplateHandler } from '../../../src/handlers/ResourceHandler';
4+
import {
5+
getManagedResourceStackTemplateHandler,
6+
removeResourceTypeHandler,
7+
} from '../../../src/handlers/ResourceHandler';
58
import { GetStackTemplateParams } from '../../../src/stacks/StackRequestType';
69
import { createMockComponents } from '../../utils/MockServerComponents';
710

@@ -121,3 +124,35 @@ describe('ResourceHandler - getManagedResourceStackTemplateHandler', () => {
121124
await expect(handler(params, CancellationToken.None)).rejects.toThrow('AWS API Error');
122125
});
123126
});
127+
128+
describe('ResourceHandler - removeResourceTypeHandler', () => {
129+
let mockComponents: ReturnType<typeof createMockComponents>;
130+
let handler: any;
131+
132+
beforeEach(() => {
133+
vi.clearAllMocks();
134+
mockComponents = createMockComponents();
135+
handler = removeResourceTypeHandler(mockComponents);
136+
});
137+
138+
it('should call resourceStateManager.removeResourceType with typeName', () => {
139+
handler('AWS::S3::Bucket');
140+
141+
expect(mockComponents.resourceStateManager.removeResourceType.calledOnceWith('AWS::S3::Bucket')).toBe(true);
142+
});
143+
144+
it('should handle multiple calls', () => {
145+
handler('AWS::S3::Bucket');
146+
handler('AWS::Lambda::Function');
147+
148+
expect(mockComponents.resourceStateManager.removeResourceType.callCount).toBe(2);
149+
expect(mockComponents.resourceStateManager.removeResourceType.calledWith('AWS::S3::Bucket')).toBe(true);
150+
expect(mockComponents.resourceStateManager.removeResourceType.calledWith('AWS::Lambda::Function')).toBe(true);
151+
});
152+
153+
it('should throw error for invalid input', () => {
154+
expect(() => handler('')).toThrow(TypeError);
155+
expect(() => handler(null as any)).toThrow();
156+
expect(() => handler(undefined as any)).toThrow();
157+
});
158+
});

tst/unit/resourceState/ResourceStateManager.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,4 +520,33 @@ describe('ResourceStateManager', () => {
520520
expect(result).not.toContain('MyOrg::Custom::Resource');
521521
});
522522
});
523+
524+
describe('removeResourceType()', () => {
525+
it('should remove resource type from both maps', async () => {
526+
const mockOutput: GetResourceCommandOutput = {
527+
TypeName: 'AWS::S3::Bucket',
528+
ResourceDescription: {
529+
Identifier: 'my-bucket',
530+
Properties: '{"BucketName": "my-bucket"}',
531+
},
532+
$metadata: {},
533+
};
534+
vi.mocked(mockCcapiService.getResource).mockResolvedValue(mockOutput);
535+
vi.mocked(mockCcapiService.listResources).mockResolvedValue({
536+
ResourceDescriptions: [{ Identifier: 'my-bucket' }],
537+
});
538+
539+
await manager.getResource('AWS::S3::Bucket', 'my-bucket');
540+
await manager.listResources('AWS::S3::Bucket');
541+
542+
manager.removeResourceType('AWS::S3::Bucket');
543+
544+
await manager.getResource('AWS::S3::Bucket', 'my-bucket');
545+
expect(mockCcapiService.getResource).toHaveBeenCalledTimes(2);
546+
});
547+
548+
it('should handle removing non-existent resource type', () => {
549+
expect(() => manager.removeResourceType('AWS::DynamoDB::Table')).not.toThrow();
550+
});
551+
});
523552
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { z } from 'zod';
3+
import { parseResourceTypeName } from '../../../src/resourceState/ResourceStateParser';
4+
5+
describe('ResourceStateParser', () => {
6+
describe('parseResourceTypeName', () => {
7+
it('should parse valid resource type name', () => {
8+
const result = parseResourceTypeName('AWS::S3::Bucket');
9+
expect(result).toBe('AWS::S3::Bucket');
10+
});
11+
12+
it('should throw error for empty string', () => {
13+
expect(() => parseResourceTypeName('')).toThrow(z.ZodError);
14+
});
15+
16+
it('should throw error for non-string input', () => {
17+
expect(() => parseResourceTypeName(123)).toThrow(z.ZodError);
18+
expect(() => parseResourceTypeName(null)).toThrow(z.ZodError);
19+
expect(() => parseResourceTypeName(undefined)).toThrow(z.ZodError);
20+
});
21+
});
22+
});

0 commit comments

Comments
 (0)