import { redactUnknown } from './redact.ts' import { accessKeySetupUrl, isDryRun, resolveAccessKeySecretName, resolveSecretKeySecretName, secretKeySetupUrl, type AwsAuthOptions, } from './validation.ts' export class AwsApiError extends Error { readonly status: number readonly code: string | null readonly nextStep: string readonly details: Record constructor( status: number, details: Record, nextStep: string, ) { const code = typeof details.code === 'string' ? details.code : null const message = typeof details.message === 'string' && details.message.length > 0 ? details.message : 'AWS API request failed' super( 'AWS API failed (' + status + (code ? '/' + code : '') + '): ' + message + '. Next step: ' + nextStep, ) this.name = 'AwsApiError' this.status = status this.code = code this.nextStep = nextStep this.details = details } } export function nextStepFor( status: number, message: string, accessKeySecret: string, secretKeySecret: string, host: string, ): string { const accessUrl = accessKeySetupUrl(accessKeySecret) const secretUrl = secretKeySetupUrl(secretKeySecret) const lower = message.toLowerCase() if ( status === 401 || status === 403 || lower.includes('invalidclienttokenid') || lower.includes('signaturedoesnotmatch') || lower.includes('incomplete') || lower.includes('expired') ) { return ( 'Save ' + accessKeySecret + ' and ' + secretKeySecret + ' (optional awsSessionToken for temporary credentials) and approve host ' + host + ' plus the regional service hosts you call. Access key: ' + accessUrl + ' Secret key: ' + secretUrl ) } if (status === 404) { return ( 'AWS returned 404. Confirm the region, bucket, and resource exist, and that host ' + host + ' is approved for both secrets.' ) } return ( 'Confirm both secrets are saved and host ' + host + ' is approved. Access key: ' + accessUrl + ' Secret key: ' + secretUrl ) } export type MutationPreview = { dryRun: true method: string service: string region: string host: string path: string body: Record | null accessKeySecret: string secretKeySecret: string } export function mutationPreview( input: AwsAuthOptions & { dryRun?: unknown; confirm?: unknown }, spec: { method: string service: string region: string host: string path: string body?: Record | null }, ): MutationPreview | null { if (!isDryRun(input)) return null return { dryRun: true, method: spec.method, service: spec.service, region: spec.region, host: spec.host, path: spec.path, body: spec.body ?? null, accessKeySecret: resolveAccessKeySecretName(input), secretKeySecret: resolveSecretKeySecretName(input), } } export function slimErrorBody(text: string): Record { const redacted = redactUnknown(text) if (typeof redacted !== 'string') return { message: 'AWS request failed' } const codeMatch = redacted.match(/([\s\S]*?)<\/Code>/i) const messageMatch = redacted.match(/([\s\S]*?)<\/Message>/i) try { const parsed = JSON.parse(redacted) as Record return { code: typeof parsed.__type === 'string' ? parsed.__type : typeof parsed.code === 'string' ? parsed.code : null, message: typeof parsed.message === 'string' ? parsed.message : redacted.slice(0, 300), } } catch { return { code: codeMatch?.[1] ?? null, message: messageMatch?.[1] ?? redacted.slice(0, 300), } } }