Skip to content

Commit 9cd84a8

Browse files
feat: Add endpoint.response.actionAttemptType (#153)
Co-authored-by: Seam Bot <seambot@getseam.com>
1 parent b85a318 commit 9cd84a8

File tree

7 files changed

+154
-4
lines changed

7 files changed

+154
-4
lines changed

src/lib/blueprint.ts

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ interface ResourceResponse extends BaseResponse {
193193
responseType: 'resource'
194194
responseKey: string
195195
resourceType: string
196+
actionAttemptType?: string
196197
}
197198

198199
interface ResourceListResponse extends BaseResponse {
@@ -276,6 +277,7 @@ export type Method = 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'
276277

277278
interface Context extends Required<BlueprintOptions> {
278279
codeSampleDefinitions: CodeSampleDefinition[]
280+
actionAttempts: ActionAttempt[]
279281
}
280282

281283
export const TypesModuleSchema = z.object({
@@ -301,19 +303,21 @@ export const createBlueprint = async (
301303
// TODO: Move openapi to TypesModuleSchema
302304
const openapi = typesModule.openapi as Openapi
303305

306+
const resources = createResources(openapi.components.schemas)
307+
const actionAttempts = createActionAttempts(openapi.components.schemas)
308+
304309
const context = {
305310
codeSampleDefinitions,
306311
formatCode,
312+
actionAttempts,
307313
}
308314

309-
const resources = createResources(openapi.components.schemas)
310-
311315
return {
312316
title: openapi.info.title,
313317
routes: await createRoutes(openapi.paths, context),
314318
resources,
315319
events: createEvents(openapi.components.schemas, resources),
316-
actionAttempts: createActionAttempts(openapi.components.schemas),
320+
actionAttempts,
317321
}
318322
}
319323

@@ -523,7 +527,7 @@ const createEndpointFromOperation = async (
523527
const draftMessage = parsedOperation['x-draft']
524528

525529
const request = createRequest(methods, operation, path)
526-
const response = createResponse(operation, path)
530+
const response = createResponse(operation, path, context)
527531

528532
const operationAuthMethods = parsedOperation.security.map(
529533
(securitySchema) => {
@@ -843,6 +847,7 @@ const createResource = (
843847
const createResponse = (
844848
operation: OpenapiOperation,
845849
path: string,
850+
context: Context,
846851
): Response => {
847852
if (!('responses' in operation) || operation.responses == null) {
848853
throw new Error(
@@ -920,6 +925,12 @@ const createResponse = (
920925
)
921926
}
922927

928+
const actionAttemptType = validateActionAttemptType(
929+
parsedOperation['x-action-attempt-type'],
930+
responseKey,
931+
path,
932+
context,
933+
)
923934
const refKey = responseKey
924935

925936
if (refKey != null && properties[refKey] != null) {
@@ -931,6 +942,7 @@ const createResponse = (
931942
responseKey: refKey,
932943
resourceType: refString?.split('/').at(-1) ?? 'unknown',
933944
description,
945+
...(actionAttemptType != null && { actionAttemptType }),
934946
}
935947
}
936948
}
@@ -941,6 +953,37 @@ const createResponse = (
941953
}
942954
}
943955

956+
const validateActionAttemptType = (
957+
actionAttemptType: string | undefined,
958+
responseKey: string,
959+
path: string,
960+
context: Context,
961+
): string | undefined => {
962+
const excludedPaths = ['/action_attempts']
963+
const isPathExcluded = excludedPaths.some((p) => path.startsWith(p))
964+
965+
if (
966+
actionAttemptType == null &&
967+
responseKey === 'action_attempt' &&
968+
!isPathExcluded
969+
) {
970+
throw new Error(`Missing action_attempt_type for path ${path}`)
971+
}
972+
973+
if (
974+
actionAttemptType != null &&
975+
!context.actionAttempts.some(
976+
(attempt) => attempt.actionAttemptType === actionAttemptType,
977+
)
978+
) {
979+
throw new Error(
980+
`Invalid action_attempt_type '${actionAttemptType}' for path ${path}`,
981+
)
982+
}
983+
984+
return actionAttemptType
985+
}
986+
944987
export const createProperties = (
945988
properties: Record<string, OpenapiSchema>,
946989
parentPaths: string[],

src/lib/openapi/schemas.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ export const OpenapiOperationSchema = z.object({
8181
'x-undocumented': z.string().default(''),
8282
'x-deprecated': z.string().default(''),
8383
'x-draft': z.string().default(''),
84+
'x-action-attempt-type': z.string().optional(),
8485
})
8586

8687
export const EnumValueSchema = z.object({

test/fixtures/types/openapi.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,38 @@ export default {
318318
'x-title': 'Get a foo',
319319
},
320320
},
321+
'/foos/create': {
322+
post: {
323+
operationId: 'foosCreatePost',
324+
responses: {
325+
200: {
326+
content: {
327+
'application/json': {
328+
schema: {
329+
properties: {
330+
ok: { type: 'boolean' },
331+
action_attempt: {
332+
$ref: '#/components/schemas/action_attempt',
333+
},
334+
},
335+
required: ['action_attempt', 'ok'],
336+
type: 'object',
337+
},
338+
},
339+
},
340+
description: 'Create a foo.',
341+
},
342+
400: { description: 'Bad Request' },
343+
401: { description: 'Unauthorized' },
344+
},
345+
security: [],
346+
summary: '/foos/create',
347+
tags: ['/foos'],
348+
'x-response-key': 'action_attempt',
349+
'x-action-attempt-type': 'CREATE_FOO',
350+
'x-title': 'Create a foo',
351+
},
352+
},
321353
'/foos/list': {
322354
get: {
323355
operationId: 'foosListGet',

test/snapshots/blueprint.test.ts.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,36 @@ Generated by [AVA](https://avajs.dev).
601601
undocumentedMessage: '',
602602
workspaceScope: 'required',
603603
},
604+
{
605+
authMethods: [],
606+
codeSamples: [],
607+
deprecationMessage: '',
608+
description: '',
609+
draftMessage: '',
610+
isDeprecated: false,
611+
isDraft: false,
612+
isUndocumented: false,
613+
name: 'create',
614+
path: '/foos/create',
615+
request: {
616+
methods: [
617+
'POST',
618+
],
619+
parameters: [],
620+
preferredMethod: 'POST',
621+
semanticMethod: 'POST',
622+
},
623+
response: {
624+
actionAttemptType: 'CREATE_FOO',
625+
description: 'Create a foo.',
626+
resourceType: 'action_attempt',
627+
responseKey: 'action_attempt',
628+
responseType: 'resource',
629+
},
630+
title: 'Create a foo',
631+
undocumentedMessage: '',
632+
workspaceScope: 'none',
633+
},
604634
{
605635
authMethods: [
606636
'api_key',
@@ -1727,6 +1757,36 @@ Generated by [AVA](https://avajs.dev).
17271757
undocumentedMessage: '',
17281758
workspaceScope: 'required',
17291759
},
1760+
{
1761+
authMethods: [],
1762+
codeSamples: [],
1763+
deprecationMessage: '',
1764+
description: '',
1765+
draftMessage: '',
1766+
isDeprecated: false,
1767+
isDraft: false,
1768+
isUndocumented: false,
1769+
name: 'create',
1770+
path: '/foos/create',
1771+
request: {
1772+
methods: [
1773+
'POST',
1774+
],
1775+
parameters: [],
1776+
preferredMethod: 'POST',
1777+
semanticMethod: 'POST',
1778+
},
1779+
response: {
1780+
actionAttemptType: 'CREATE_FOO',
1781+
description: 'Create a foo.',
1782+
resourceType: 'action_attempt',
1783+
responseKey: 'action_attempt',
1784+
responseType: 'resource',
1785+
},
1786+
title: 'Create a foo',
1787+
undocumentedMessage: '',
1788+
workspaceScope: 'none',
1789+
},
17301790
{
17311791
authMethods: [
17321792
'api_key',
415 Bytes
Binary file not shown.

test/snapshots/seam-blueprint.test.ts.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18871,6 +18871,7 @@ Generated by [AVA](https://avajs.dev).
1887118871
semanticMethod: 'POST',
1887218872
},
1887318873
response: {
18874+
actionAttemptType: 'CREATE_ACCESS_CODE',
1887418875
description: 'OK',
1887518876
resourceType: 'access_code',
1887618877
responseKey: 'access_code',
@@ -22073,6 +22074,7 @@ Generated by [AVA](https://avajs.dev).
2207322074
semanticMethod: 'POST',
2207422075
},
2207522076
response: {
22077+
actionAttemptType: 'ENCODE_CREDENTIAL',
2207622078
description: 'OK',
2207722079
resourceType: 'action_attempt',
2207822080
responseKey: 'action_attempt',
@@ -22151,6 +22153,7 @@ Generated by [AVA](https://avajs.dev).
2215122153
semanticMethod: 'POST',
2215222154
},
2215322155
response: {
22156+
actionAttemptType: 'SCAN_CREDENTIAL',
2215422157
description: 'OK',
2215522158
resourceType: 'action_attempt',
2215622159
responseKey: 'action_attempt',
@@ -28454,6 +28457,7 @@ Generated by [AVA](https://avajs.dev).
2845428457
semanticMethod: 'POST',
2845528458
},
2845628459
response: {
28460+
actionAttemptType: 'LOCK_DOOR',
2845728461
description: 'OK',
2845828462
resourceType: 'action_attempt',
2845928463
responseKey: 'action_attempt',
@@ -28514,6 +28518,7 @@ Generated by [AVA](https://avajs.dev).
2851428518
semanticMethod: 'POST',
2851528519
},
2851628520
response: {
28521+
actionAttemptType: 'UNLOCK_DOOR',
2851728522
description: 'OK',
2851828523
resourceType: 'action_attempt',
2851928524
responseKey: 'action_attempt',
@@ -29301,6 +29306,7 @@ Generated by [AVA](https://avajs.dev).
2930129306
semanticMethod: 'POST',
2930229307
},
2930329308
response: {
29309+
actionAttemptType: 'CREATE_NOISE_THRESHOLD',
2930429310
description: 'OK',
2930529311
resourceType: 'noise_threshold',
2930629312
responseKey: 'noise_threshold',
@@ -30106,6 +30112,7 @@ Generated by [AVA](https://avajs.dev).
3010630112
semanticMethod: 'POST',
3010730113
},
3010830114
response: {
30115+
actionAttemptType: 'ACTIVATE_CLIMATE_PRESET',
3010930116
description: 'OK',
3011030117
resourceType: 'action_attempt',
3011130118
responseKey: 'action_attempt',
@@ -30192,6 +30199,7 @@ Generated by [AVA](https://avajs.dev).
3019230199
semanticMethod: 'POST',
3019330200
},
3019430201
response: {
30202+
actionAttemptType: 'SET_HVAC_MODE',
3019530203
description: 'OK',
3019630204
resourceType: 'action_attempt',
3019730205
responseKey: 'action_attempt',
@@ -30630,6 +30638,7 @@ Generated by [AVA](https://avajs.dev).
3063030638
semanticMethod: 'POST',
3063130639
},
3063230640
response: {
30641+
actionAttemptType: 'SET_HVAC_MODE',
3063330642
description: 'OK',
3063430643
resourceType: 'action_attempt',
3063530644
responseKey: 'action_attempt',
@@ -30742,6 +30751,7 @@ Generated by [AVA](https://avajs.dev).
3074230751
semanticMethod: 'POST',
3074330752
},
3074430753
response: {
30754+
actionAttemptType: 'SET_HVAC_MODE',
3074530755
description: 'OK',
3074630756
resourceType: 'action_attempt',
3074730757
responseKey: 'action_attempt',
@@ -31354,6 +31364,7 @@ Generated by [AVA](https://avajs.dev).
3135431364
semanticMethod: 'POST',
3135531365
},
3135631366
response: {
31367+
actionAttemptType: 'SET_HVAC_MODE',
3135731368
description: 'OK',
3135831369
resourceType: 'action_attempt',
3135931370
responseKey: 'action_attempt',
@@ -31561,6 +31572,7 @@ Generated by [AVA](https://avajs.dev).
3156131572
semanticMethod: 'POST',
3156231573
},
3156331574
response: {
31575+
actionAttemptType: 'SET_FAN_MODE',
3156431576
description: 'OK',
3156531577
resourceType: 'action_attempt',
3156631578
responseKey: 'action_attempt',
@@ -31594,6 +31606,7 @@ Generated by [AVA](https://avajs.dev).
3159431606
semanticMethod: 'POST',
3159531607
},
3159631608
response: {
31609+
actionAttemptType: 'SET_HVAC_MODE',
3159731610
description: 'OK',
3159831611
resourceType: 'action_attempt',
3159931612
responseKey: 'action_attempt',
@@ -33890,6 +33903,7 @@ Generated by [AVA](https://avajs.dev).
3389033903
semanticMethod: 'POST',
3389133904
},
3389233905
response: {
33906+
actionAttemptType: 'RESET_SANDBOX_WORKSPACE',
3389333907
description: 'OK',
3389433908
resourceType: 'action_attempt',
3389533909
responseKey: 'action_attempt',
214 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)