fix: address PR review comments (#4042)

* fix: address PR review comments on staging release

- Add try/catch around clipboard.writeText() in CopyCodeButton
- Add missing folder and past_chat cases in resolveResourceFromContext
- Return 400 for ZodError instead of 500 in all 8 Athena API routes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(api): return 400 for Zod validation errors across 27 API routes

Routes using z.parse() were returning 500 for ZodError (client input
validation failures). Added instanceof z.ZodError check to return 400
before the generic 500 handler, matching the established pattern used
by 115+ other routes.

Affected services: CloudWatch (7), CloudFormation (7), DynamoDB (6),
Slack (3), Outlook (2), OneDrive (1), Google Drive (1).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(api): add success:false to ZodError responses for consistency

7 routes used { success: false, error: ... } in their generic error
handler but our ZodError handler only returned { error: ... }. Aligned
the ZodError response shape to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Waleed
2026-04-08 00:26:33 -07:00
committed by GitHub
parent 9282d1bf54
commit d0d35dd406
36 changed files with 218 additions and 4 deletions

View File

@@ -55,6 +55,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to create Athena named query'
logger.error('CreateNamedQuery failed', { error: errorMessage })

View File

@@ -53,6 +53,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Failed to get Athena named query'
logger.error('GetNamedQuery failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })

View File

@@ -63,6 +63,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to get Athena query execution'
logger.error('GetQueryExecution failed', { error: errorMessage })

View File

@@ -74,6 +74,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to get Athena query results'
logger.error('GetQueryResults failed', { error: errorMessage })

View File

@@ -51,6 +51,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to list Athena named queries'
logger.error('ListNamedQueries failed', { error: errorMessage })

View File

@@ -51,6 +51,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to list Athena query executions'
logger.error('ListQueryExecutions failed', { error: errorMessage })

View File

@@ -67,6 +67,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Failed to start Athena query'
logger.error('StartQuery failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })

View File

@@ -43,6 +43,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'Failed to stop Athena query'
logger.error('StopQuery failed', { error: errorMessage })
return NextResponse.json({ error: errorMessage }, { status: 500 })

View File

@@ -53,6 +53,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe stack drift detection status'
logger.error('DescribeStackDriftDetectionStatus failed', { error: errorMessage })

View File

@@ -70,6 +70,12 @@ export async function POST(request: NextRequest) {
output: { events },
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudFormation stack events'
logger.error('DescribeStackEvents failed', { error: errorMessage })

View File

@@ -78,6 +78,12 @@ export async function POST(request: NextRequest) {
output: { stacks },
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudFormation stacks'
logger.error('DescribeStacks failed', { error: errorMessage })

View File

@@ -48,6 +48,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to detect CloudFormation stack drift'
logger.error('DetectStackDrift failed', { error: errorMessage })

View File

@@ -45,6 +45,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to get CloudFormation template'
logger.error('GetTemplate failed', { error: errorMessage })

View File

@@ -67,6 +67,12 @@ export async function POST(request: NextRequest) {
output: { resources },
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to list CloudFormation stack resources'
logger.error('ListStackResources failed', { error: errorMessage })

View File

@@ -53,6 +53,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to validate CloudFormation template'
logger.error('ValidateTemplate failed', { error: errorMessage })

View File

@@ -88,6 +88,12 @@ export async function POST(request: NextRequest) {
output: { alarms: [...metricAlarms, ...compositeAlarms] },
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudWatch alarms'
logger.error('DescribeAlarms failed', { error: errorMessage })

View File

@@ -54,6 +54,12 @@ export async function POST(request: NextRequest) {
output: { logGroups },
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudWatch log groups'
logger.error('DescribeLogGroups failed', { error: errorMessage })

View File

@@ -44,6 +44,12 @@ export async function POST(request: NextRequest) {
output: { logStreams: result.logStreams },
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to describe CloudWatch log streams'
logger.error('DescribeLogStreams failed', { error: errorMessage })

View File

@@ -52,6 +52,12 @@ export async function POST(request: NextRequest) {
output: { events: result.events },
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to get CloudWatch log events'
logger.error('GetLogEvents failed', { error: errorMessage })

View File

@@ -89,6 +89,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to get CloudWatch metric statistics'
logger.error('GetMetricStatistics failed', { error: errorMessage })

View File

@@ -62,6 +62,12 @@ export async function POST(request: NextRequest) {
output: { metrics },
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'Failed to list CloudWatch metrics'
logger.error('ListMetrics failed', { error: errorMessage })

View File

@@ -63,6 +63,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage =
error instanceof Error ? error.message : 'CloudWatch Log Insights query failed'
logger.error('QueryLogs failed', { error: errorMessage })

View File

@@ -41,6 +41,12 @@ export async function POST(request: NextRequest) {
message: 'Item deleted successfully',
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'DynamoDB delete failed'
return NextResponse.json({ error: errorMessage }, { status: 500 })
}

View File

@@ -48,6 +48,12 @@ export async function POST(request: NextRequest) {
item: result.item,
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'DynamoDB get failed'
return NextResponse.json({ error: errorMessage }, { status: 500 })
}

View File

@@ -36,6 +36,12 @@ export async function POST(request: NextRequest) {
item: validatedData.item,
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'DynamoDB put failed'
return NextResponse.json({ error: errorMessage }, { status: 500 })
}

View File

@@ -51,6 +51,12 @@ export async function POST(request: NextRequest) {
count: result.count,
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'DynamoDB query failed'
return NextResponse.json({ error: errorMessage }, { status: 500 })
}

View File

@@ -45,6 +45,12 @@ export async function POST(request: NextRequest) {
count: result.count,
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'DynamoDB scan failed'
return NextResponse.json({ error: errorMessage }, { status: 500 })
}

View File

@@ -50,6 +50,12 @@ export async function POST(request: NextRequest) {
item: result.attributes,
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
const errorMessage = error instanceof Error ? error.message : 'DynamoDB update failed'
return NextResponse.json({ error: errorMessage }, { status: 500 })
}

View File

@@ -240,6 +240,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ success: false, error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
logger.error(`[${requestId}] Error downloading Google Drive file:`, error)
return NextResponse.json(
{

View File

@@ -165,6 +165,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ success: false, error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
logger.error(`[${requestId}] Error downloading OneDrive file:`, error)
return NextResponse.json(
{

View File

@@ -176,6 +176,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ success: false, error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
logger.error(`[${requestId}] Error creating Outlook draft:`, error)
return NextResponse.json(
{

View File

@@ -189,6 +189,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ success: false, error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
logger.error(`[${requestId}] Error sending Outlook email:`, error)
return NextResponse.json(
{

View File

@@ -158,6 +158,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ success: false, error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
logger.error(`[${requestId}] Error downloading Slack file:`, error)
return NextResponse.json(
{

View File

@@ -84,6 +84,12 @@ export async function POST(request: NextRequest) {
},
})
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ success: false, error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
logger.error(`[${requestId}] Error sending ephemeral message:`, error)
return NextResponse.json(
{

View File

@@ -77,6 +77,12 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ success: true, output: result.output })
} catch (error) {
if (error instanceof z.ZodError) {
return NextResponse.json(
{ success: false, error: error.errors[0]?.message ?? 'Invalid request' },
{ status: 400 }
)
}
logger.error(`[${requestId}] Error sending Slack message:`, error)
return NextResponse.json(
{

View File

@@ -14,10 +14,14 @@ export function CopyCodeButton({ code, className }: CopyCodeButtonProps) {
const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const handleCopy = useCallback(async () => {
await navigator.clipboard.writeText(code)
setCopied(true)
if (timerRef.current) clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => setCopied(false), 2000)
try {
await navigator.clipboard.writeText(code)
setCopied(true)
if (timerRef.current) clearTimeout(timerRef.current)
timerRef.current = setTimeout(() => setCopied(false), 2000)
} catch {
// Clipboard write can fail when document lacks focus or permission is denied
}
}, [code])
useEffect(