Skip to content

Commit 655af91

Browse files
committed
refactor: tighten typing constraints
1 parent 8abdea8 commit 655af91

File tree

4 files changed

+77
-31
lines changed

4 files changed

+77
-31
lines changed

packages/core/src/shared/clients/clientWrapper.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import globals from '../extensionGlobals'
77
import { AwsClient, AwsClientConstructor, AwsCommand, AwsCommandConstructor } from '../awsClientBuilderV3'
88
import { PaginationConfiguration, Paginator } from '@aws-sdk/types'
99
import { AsyncCollection, toCollection } from '../utilities/asyncCollection'
10+
import { isDefined } from '../utilities/tsUtils'
1011

1112
type SDKPaginator<C, CommandInput extends object, CommandOutput extends object> = (
1213
config: Omit<PaginationConfiguration, 'client'> & { client: C },
@@ -49,10 +50,6 @@ export abstract class ClientWrapper<C extends AwsClient> implements vscode.Dispo
4950
.map((o) => o.filter(isDefined))
5051

5152
return collection
52-
53-
function isDefined<T>(i: T | undefined): i is T {
54-
return i !== undefined
55-
}
5653
}
5754

5855
protected async getFirst<CommandInput extends object, CommandOutput extends object, Output extends object>(

packages/core/src/shared/clients/codecatalystClient.ts

+70-25
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,14 @@ import { ServiceConfigurationOptions } from 'aws-sdk/lib/service'
1616
import { CancellationError, Timeout, waitTimeout, waitUntil } from '../utilities/timeoutUtils'
1717
import { isUserCancelledError } from '../../shared/errors'
1818
import { showMessageWithCancel } from '../utilities/messages'
19-
import { assertHasProps, ClassToInterfaceType, hasProps, isNonNullable, RequiredProps } from '../utilities/tsUtils'
19+
import {
20+
assertHasProps,
21+
ClassToInterfaceType,
22+
hasProps,
23+
isDefined,
24+
isNonNullable,
25+
RequiredProps,
26+
} from '../utilities/tsUtils'
2027
import { AsyncCollection, toCollection } from '../utilities/asyncCollection'
2128
import { joinAll, pageableToCollection } from '../utilities/collectionUtils'
2229
import { CodeCatalyst } from 'aws-sdk'
@@ -33,6 +40,7 @@ import {
3340
CreateAccessTokenCommand,
3441
CreateAccessTokenRequest,
3542
CreateAccessTokenResponse,
43+
DevEnvironmentRepositorySummary,
3644
DevEnvironmentSummary,
3745
GetDevEnvironmentCommand,
3846
GetDevEnvironmentRequest,
@@ -49,7 +57,6 @@ import {
4957
GetUserDetailsCommandOutput,
5058
GetUserDetailsRequest,
5159
ListDevEnvironmentsCommand,
52-
ListDevEnvironmentSessionsResponse,
5360
ListDevEnvironmentsRequest,
5461
ListDevEnvironmentsResponse,
5562
ListProjectsCommand,
@@ -58,6 +65,8 @@ import {
5865
ListSpacesCommand,
5966
ListSpacesRequest,
6067
ListSpacesResponse,
68+
PersistentStorage,
69+
ProjectSummary,
6170
SpaceSummary,
6271
} from '@aws-sdk/client-codecatalyst'
6372
import { truncateProps } from '../utilities/textUtilities'
@@ -68,6 +77,16 @@ import { AwsCommand } from '../awsClientBuilderV3'
6877
import { ClientWrapper } from './clientWrapper'
6978
import { StandardRetryStrategy } from '@smithy/util-retry'
7079

80+
const requiredDevEnvProps = [
81+
'id',
82+
'status',
83+
'inactivityTimeoutMinutes',
84+
'repositories',
85+
'instanceType',
86+
'lastUpdatedTime',
87+
] as const
88+
type RequiredDevEnvProps = (typeof requiredDevEnvProps)[number]
89+
7190
export interface CodeCatalystConfig {
7291
readonly region: string
7392
readonly endpoint: string
@@ -91,11 +110,17 @@ export function getCodeCatalystConfig(): CodeCatalystConfig {
91110
}
92111
}
93112

94-
export interface DevEnvironment extends DevEnvironmentSummary {
113+
interface CodeCatalystDevEnvironmentSummary extends RequiredProps<DevEnvironmentSummary, RequiredDevEnvProps> {
114+
readonly persistentStorage: RequiredProps<PersistentStorage, 'sizeInGiB'>
115+
readonly repositories: RequiredProps<DevEnvironmentRepositorySummary, 'repositoryName'>[]
116+
}
117+
118+
export interface DevEnvironment extends CodeCatalystDevEnvironmentSummary {
95119
readonly type: 'devEnvironment'
96120
readonly id: string
97121
readonly org: Pick<CodeCatalystOrg, 'name'>
98122
readonly project: Pick<CodeCatalystProject, 'name'>
123+
readonly repositories: RequiredProps<DevEnvironmentRepositorySummary, 'repositoryName'>[]
99124
}
100125

101126
/** CodeCatalyst developer environment session. */
@@ -138,8 +163,8 @@ export type CodeCatalystResource =
138163
function toDevEnv(
139164
spaceName: string,
140165
projectName: string,
141-
summary: RequiredProps<DevEnvironmentSummary, 'id' | 'status'>
142-
): RequiredProps<DevEnvironment, 'status'> {
166+
summary: CodeCatalystDevEnvironmentSummary
167+
): RequiredProps<DevEnvironment, RequiredDevEnvProps> {
143168
return {
144169
type: 'devEnvironment',
145170
org: { name: spaceName },
@@ -525,11 +550,15 @@ class CodeCatalystClientInternal extends ClientWrapper<CodeCatalystSDKClient> {
525550
public listSpaces(request: ListSpacesRequest = {}): AsyncCollection<CodeCatalystOrg[]> {
526551
const requester: (request: ListSpacesRequest) => Promise<ListSpacesResponse> = async (request) =>
527552
this.callV3(ListSpacesCommand, request, true, { items: [] })
528-
const collection = pageableToCollection(requester, request, 'nextToken', 'items').filter(
529-
(summaries) => summaries !== undefined
530-
)
553+
const collection = pageableToCollection(requester, request, 'nextToken', 'items').filter(isDefined)
554+
// ts doesn't recognize nested assertion, so we add cast.This is safe because we assert it in the same line.
531555
return collection.map((summaries) =>
532-
summaries.filter((s) => hasProps(s, 'name')).map((s) => ({ type: 'org', ...s }))
556+
(summaries.filter((s) => hasProps(s, 'name')) as Readonly<RequiredProps<SpaceSummary, 'name'>>[]).map(
557+
(s) => ({
558+
type: 'org',
559+
...s,
560+
})
561+
)
533562
)
534563
}
535564

@@ -551,34 +580,36 @@ class CodeCatalystClientInternal extends ClientWrapper<CodeCatalystSDKClient> {
551580
const requester: (request: ListProjectsRequest) => Promise<ListProjectsResponse> = (request) =>
552581
this.callV3(ListProjectsCommand, request, true, { items: [] })
553582

554-
const collection = pageableToCollection(requester, request, 'nextToken', 'items').filter(
555-
(summaries) => summaries !== undefined
556-
)
557-
583+
const collection = pageableToCollection(requester, request, 'nextToken', 'items').filter(isDefined)
584+
// ts doesn't recognize nested assertion, so we add cast. This is safe because we assert it in the same line.
558585
return collection.map((summaries) =>
559-
summaries
560-
.filter((s) => hasProps(s, 'name'))
561-
.map((s) => ({
586+
(summaries.filter((s) => hasProps(s, 'name')) as Readonly<RequiredProps<ProjectSummary, 'name'>>[]).map(
587+
(s) => ({
562588
type: 'project',
563589
org: { name: request.spaceName },
564590
...s,
565-
}))
591+
})
592+
)
566593
)
567594
}
568595

569596
/**
570597
* Gets a flat list of all devenvs for the given CodeCatalyst project.
571598
*/
572-
public listDevEnvironments(proj: CodeCatalystProject): AsyncCollection<DevEnvironment[]> {
599+
public listDevEnvironments(
600+
proj: CodeCatalystProject
601+
): AsyncCollection<RequiredProps<DevEnvironment, RequiredDevEnvProps>[]> {
573602
const initRequest = { spaceName: proj.org.name, projectName: proj.name }
574603
const requester: (request: ListDevEnvironmentsRequest) => Promise<ListDevEnvironmentsResponse> = (request) =>
575604
this.callV3(ListDevEnvironmentsCommand, request, true, { items: [] })
576-
const collection = pageableToCollection(requester, initRequest, 'nextToken', 'items').filter(
577-
(c) => c !== undefined
578-
)
579-
605+
const collection = pageableToCollection(requester, initRequest, 'nextToken' as never, 'items').filter(isDefined)
606+
// ts unable to recognize nested assertion here, so we need to cast. This is safe because we assert it in the same line.
580607
return collection.map((envs) =>
581-
envs.filter((s) => hasProps(s, 'id', 'status')).map((s) => toDevEnv(proj.org.name, proj.name, s))
608+
(
609+
envs.filter(
610+
(s) => hasProps(s, ...requiredDevEnvProps) && hasPersistentStorage(s) && hasRepositories(s)
611+
) as CodeCatalystDevEnvironmentSummary[]
612+
).map((s) => toDevEnv(proj.org.name, proj.name, s))
582613
)
583614
}
584615

@@ -730,14 +761,16 @@ class CodeCatalystClientInternal extends ClientWrapper<CodeCatalystSDKClient> {
730761

731762
public async getDevEnvironment(
732763
args: RequiredProps<GetDevEnvironmentRequest, 'spaceName' | 'projectName'>
733-
): Promise<RequiredProps<DevEnvironment, 'status'>> {
764+
): Promise<RequiredProps<DevEnvironment, RequiredDevEnvProps>> {
734765
const a = { ...args }
735766
delete (a as any).ides
736767
delete (a as any).repositories
737768

738769
const r: GetDevEnvironmentResponse = await this.callV3(GetDevEnvironmentCommand, a, false)
739770
const summary = { ...args, ...r }
740-
assertHasProps(summary, 'id', 'status')
771+
if (!hasProps(summary, ...requiredDevEnvProps) || !hasPersistentStorage(summary) || !hasRepositories(summary)) {
772+
throw new ToolkitError(`GetDevEnvironment failed due to response missing required properties`)
773+
}
741774

742775
return toDevEnv(args.spaceName, args.projectName, summary)
743776
}
@@ -974,3 +1007,15 @@ export async function isThirdPartyRepo(
9741007
!Uri.parse(url).authority.endsWith('caws.dev-tools.aws.dev')
9751008
)
9761009
}
1010+
1011+
function hasPersistentStorage<T extends DevEnvironmentSummary>(
1012+
s: T
1013+
): s is T & { persistentStorage: { sizeInGiB: number } } {
1014+
return hasProps(s, 'persistentStorage') && hasProps(s.persistentStorage, 'sizeInGiB')
1015+
}
1016+
1017+
function hasRepositories<T extends DevEnvironmentSummary>(
1018+
s: T
1019+
): s is T & { repositories: RequiredProps<DevEnvironmentRepositorySummary, 'repositoryName'>[] } {
1020+
return hasProps(s, 'repositories') && s.repositories.every((r) => hasProps(r, 'repositoryName'))
1021+
}

packages/core/src/shared/utilities/tsUtils.ts

+4
Original file line numberDiff line numberDiff line change
@@ -161,3 +161,7 @@ export function omitIfPresent<T extends Record<string, unknown>>(obj: T, keys: s
161161
}
162162
return objCopy
163163
}
164+
165+
export function isDefined<T>(i: T | undefined): i is T {
166+
return i !== undefined
167+
}

packages/core/src/testE2E/codecatalyst/client.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ describe.only('Test how this codebase uses the CodeCatalyst API', function () {
191191
assert.strictEqual(actualDevEnv.org.name, spaceName)
192192
assert.strictEqual(actualDevEnv.alias, differentDevEnvSettings.alias)
193193
assert.strictEqual(actualDevEnv.instanceType, 'dev.standard1.medium')
194-
assert.strictEqual(actualDevEnv.persistentStorage.sizeInGiB, 32)
194+
assert.strictEqual(actualDevEnv.persistentStorage && actualDevEnv.persistentStorage.sizeInGiB, 32)
195195
})
196196

197197
it.skip('creates a Dev Environment using an existing branch', async function () {
@@ -533,7 +533,7 @@ describe.only('Test how this codebase uses the CodeCatalyst API', function () {
533533
)
534534
}
535535

536-
async function getAllDevEnvs(projectName: CodeCatalystProject['name']): Promise<DevEnvironment[]> {
536+
async function getAllDevEnvs(projectName: CodeCatalystProject['name']) {
537537
const currentDevEnvs = await client
538538
.listDevEnvironments({ name: projectName, org: { name: spaceName }, type: 'project' })
539539
.flatten()

0 commit comments

Comments
 (0)