fix(v2): validate timeoutSeconds per trigger type#1874
fix(v2): validate timeoutSeconds per trigger type#1874kyungseopk1m wants to merge 3 commits intofirebase:masterfrom
Conversation
The v2 SDK accepts any timeoutSeconds value at function-definition
time and leaves the rejection to the Cloud Functions control plane
at deploy time. v1 has had assertRuntimeOptionsValid for a long time
but v2 was missing the equivalent.
Add per-trigger-type validation so misconfigured values fail locally
instead of mid-deploy:
- 540s for event-handling functions (firestore, database, pubsub,
storage, scheduler, eventarc, testLab, remoteConfig, alerts,
dataconnect)
- 3600s for HTTPS and callable functions (onRequest, onCall,
onCallGenkit, identity, dataconnect/graphql, ai)
- 1800s for task queue functions (onTaskDispatched)
Implementation notes:
- New internal helper assertTimeoutSecondsValid(opts, kind) and
MAX_{EVENT,HTTPS,TASK}_TIMEOUT_SECONDS constants in
src/v2/options.ts.
- optionsToEndpoint and optionsToTriggerAnnotations gained an
optional kind argument. When omitted they behave exactly as
before, so existing callers are unaffected.
- Expression<number> and RESET_VALUE are passed through untouched;
the SDK cannot know the concrete value in those cases.
- For providers whose __endpoint is a lazy getter (storage) the
throw happens the first time the manifest is read instead of at
function-definition time; a regression test covers this path.
Fixes firebase#1737
There was a problem hiding this comment.
Code Review
This pull request introduces validation for the timeoutSeconds parameter based on the specific Cloud Functions v2 trigger type (event-handling, HTTPS/callable, or task queue). The validation is performed at function-definition or manifest-extraction time to prevent deployment failures due to misconfigured timeout values. The changes include the addition of a centralized validation function, assertTimeoutSecondsValid, and updates across all v2 providers to pass the correct trigger category during endpoint and trigger annotation generation. Extensive unit tests were added to verify the validation logic for each trigger type and ensure correct fallback behavior with global options. I have no feedback to provide.
IzaakGough
left a comment
There was a problem hiding this comment.
Hi @kyungseopk1m, thank you for contributing! This looks good overall. The only issue I found is the failing lint check from CHANGELOG.md formatting. Could you please fix that?
|
@IzaakGough Thanks for the review! Fixed in 750b2e9 — formatted CHANGELOG.md with prettier. |
| kind: TimeoutKind | ||
| ): void { | ||
| const timeoutSeconds = opts?.timeoutSeconds ?? getGlobalOptions().timeoutSeconds; | ||
| if (typeof timeoutSeconds !== "number") { |
There was a problem hiding this comment.
if timeoutSeconds is NaN here, i think we might have an issue, i think it might slip through?
| expect(hello).to.equal("world"); | ||
| }); | ||
|
|
||
| it("rejects timeoutSeconds above the 3600s HTTPS limit", () => { |
There was a problem hiding this comment.
I like that we're testing some of the providers, but I think we need more direct test of the wiring across providers
a parameterized suite like "each v2 provider rejects out-of-range timeout" would be a cheap way to get coverage
| * @internal | ||
| */ | ||
| export function assertTimeoutSecondsValid( | ||
| opts: GlobalOptions | EventHandlerOptions | HttpsOptions | undefined, |
There was a problem hiding this comment.
nit: I don't know if we need the | undefined
cabljac
left a comment
There was a problem hiding this comment.
Thanks for this PR! I've added some comments. @IzaakGough we may be able to pick this up
Fixes #1737
Description
The v2 SDK currently accepts any
timeoutSecondsvalue and only the Cloud Functions control plane rejects invalid ones at deploy time with errors likecannot exceed 540 seconds. v1 has hadassertRuntimeOptionsValidfor a long time but v2 was missing the equivalent, so users only find out they configured the wrong limit after a multi-minute deploy round-trip.This PR adds per-trigger-type validation that throws at function-definition time (or at manifest-extraction time for providers whose
__endpointis a lazy getter, such asstorage).Limits enforced:
onRequest,onCall,onCallGenkit, identity, dataconnect/graphql, ai)onTaskDispatched)Implementation:
assertTimeoutSecondsValid(opts, kind)andMAX_{EVENT,HTTPS,TASK}_TIMEOUT_SECONDSconstants insrc/v2/options.ts.optionsToEndpointandoptionsToTriggerAnnotationsgained an optionalkind: TimeoutKindargument. When omitted they behave exactly as before — validation is opt-in at the call site, so existing consumers of these helpers are unaffected.kindwhen converting the function-specific options. Global-option calls stay unannotated so they keep working exactly as before (no regression for a project that setstimeoutSecondsglobally on a function type that can tolerate it).Expression<number>,RESET_VALUE, andundefinedare deliberately skipped. The SDK cannot know the concrete value for parametrized expressions, andRESET_VALUEis the user explicitly asking us to clear the global.onCallGenkitdelegates toonCall(opts, …)and is therefore covered transitively.Error messages follow the v1 shape but include the per-kind label:
Tests
spec/v2/options.spec.ts— 13 unit tests forassertTimeoutSecondsValidcovering each kind's limit, negative values,Expression,RESET_VALUE, global fallback, function-level override, and function-levelRESET_VALUEclearing an out-of-range global. Plus 6 integration tests foroptionsToEndpoint/optionsToTriggerAnnotations(omitted kind = no-op for backwards compatibility, provided kind = enforced).spec/v2/providers/https.spec.ts—onRequestandonCallboth reject 3601s.spec/v2/providers/pubsub.spec.ts— function-level 3600s and a global 3600s (viasetGlobalOptions) are both rejected.spec/v2/providers/tasks.spec.ts— 1801s rejected.spec/v2/providers/storage.spec.ts— validation fires when__endpointis accessed (lazy-getter path).Full suite: 900 / 900 passing,
npm run buildclean,npm run lint0 errors on the touched files,prettier --checkclean.Backwards compatibility
kindargument is optional. Calls tooptionsToEndpoint/optionsToTriggerAnnotationsthat don't pass it keep their current behavior, so downstream code that reaches into these helpers is unaffected.0is still accepted, matching v1'sassertRuntimeOptionsValid. Enforcing the documented 1s minimum can be a separate follow-up if desired.Code sample
Before: this deploys, hits
gcloud beta functions deploy, and fails after ~30 seconds:After: fails immediately when the function module is loaded with: