Skip to content

feat(appsync): support S3 mapping template locations for resolvers and pipeline functions#13669

Open
nitishagar wants to merge 1 commit into
serverless:mainfrom
nitishagar:fix/13249-appsync-s3-location
Open

feat(appsync): support S3 mapping template locations for resolvers and pipeline functions#13669
nitishagar wants to merge 1 commit into
serverless:mainfrom
nitishagar:fix/13249-appsync-s3-location

Conversation

@nitishagar

@nitishagar nitishagar commented Jul 2, 2026

Copy link
Copy Markdown

Summary

  • Add requestS3Location / responseS3Location options to AppSync resolver and pipeline function configuration so VTL templates can be referenced by S3 location instead of inlined into the CloudFormation template
  • Extend the isVTLResolver predicate to recognise S3-only resolvers as VTL, fixing silent misclassification of S3-only PIPELINE resolvers as JS
  • Add AJV schema definition (appSyncS3Location) and a post-AJV mutual-exclusivity pass with precise error messages

Motivation

Large AppSync APIs can exceed CloudFormation's 1 MB processed-template limit when VTL templates are inlined. Referencing templates by S3 location (RequestMappingTemplateS3Location / ResponseMappingTemplateS3Location) is the AWS-recommended workaround, but the Framework had no way to express this.

Changes

  • validation.js: new appSyncS3Location definition (bucket+key required, version optional, additionalProperties:false); refs in resolverConfig and pipelineFunctionConfig; post-AJV notBoth pass for mutual-exclusivity and code+S3 errors
  • resources/Resolver.js: extended isVTLResolver predicate; resolveS3Location helper (exported); compile-time guards; per-side S3/inline branches
  • resources/PipelineFunction.js: same guards and per-side branches; imports resolveS3Location from Resolver.js
  • Tests: new snapshot cases for UNIT S3, S3-only PIPELINE (regression for predicate fix), and pipeline-function S3; validation tests for shape, mutual exclusivity, and code+S3
  • Docs: resolvers.md and pipeline-functions.md updated with field descriptions and YAML example

Backward Compatibility

The new fields are optional. All existing configurations compile byte-for-byte identically (the predicate extension is purely additive).

Closes #13249

Summary by CodeRabbit

  • New Features
    • Added support for AppSync request/response mapping templates stored in S3 for both resolvers and pipeline functions.
    • Introduced requestS3Location / responseS3Location options (with { bucket, key }, plus optional versioning).
  • Documentation
    • Updated AppSync “Resolvers” and related guides with configuration guidance and examples for S3-based templates.
    • Clarified that S3 templates are used directly (no local file reads and no variable substitution).
  • Bug Fixes
    • Added validation to prevent invalid combinations (e.g., mixing inline templates or JavaScript code with S3 locations).

@Mmarzex

Mmarzex commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8cec4914-b90e-474c-b3c9-828af83be653

📥 Commits

Reviewing files that changed from the base of the PR and between 32edab5 and f1fefaa.

⛔ Files ignored due to path filters (1)
  • packages/serverless/test/unit/lib/plugins/aws/appsync/__snapshots__/resolvers.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • docs/sf/providers/aws/guide/appsync/pipeline-functions.md
  • docs/sf/providers/aws/guide/appsync/resolvers.md
  • packages/serverless/lib/plugins/aws/appsync/resources/PipelineFunction.js
  • packages/serverless/lib/plugins/aws/appsync/resources/Resolver.js
  • packages/serverless/lib/plugins/aws/appsync/validation.js
  • packages/serverless/test/unit/lib/plugins/aws/appsync/resolvers.test.js
  • packages/serverless/test/unit/lib/plugins/aws/appsync/validation/pipelineFunctions.test.js
  • packages/serverless/test/unit/lib/plugins/aws/appsync/validation/resolvers.test.js
✅ Files skipped from review due to trivial changes (1)
  • docs/sf/providers/aws/guide/appsync/resolvers.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/serverless/test/unit/lib/plugins/aws/appsync/validation/pipelineFunctions.test.js
  • packages/serverless/test/unit/lib/plugins/aws/appsync/validation/resolvers.test.js
  • packages/serverless/lib/plugins/aws/appsync/resources/PipelineFunction.js
  • docs/sf/providers/aws/guide/appsync/pipeline-functions.md
  • packages/serverless/test/unit/lib/plugins/aws/appsync/resolvers.test.js
  • packages/serverless/lib/plugins/aws/appsync/validation.js

📝 Walkthrough

Walkthrough

This PR adds requestS3Location and responseS3Location support for AppSync resolvers and pipeline functions, with schema validation, compilation updates, tests, and docs for S3-backed VTL templates.

Changes

AppSync S3 Mapping Template Locations

Layer / File(s) Summary
Schema and validation rules for S3 locations
packages/serverless/lib/plugins/aws/appsync/validation.js, packages/serverless/test/unit/lib/plugins/aws/appsync/validation/*
Adds the S3 location schema, extends resolver and pipeline function configs, and enforces mutual exclusivity and shape validation.
Resolver.js S3 location compilation
packages/serverless/lib/plugins/aws/appsync/resources/Resolver.js, packages/serverless/test/unit/lib/plugins/aws/appsync/resolvers.test.js
Adds S3 location resolution, treats S3-backed templates as VTL, and writes *MappingTemplateS3Location properties.
PipelineFunction.js S3 location compilation
packages/serverless/lib/plugins/aws/appsync/resources/PipelineFunction.js, packages/serverless/test/unit/lib/plugins/aws/appsync/resolvers.test.js
Adds S3-backed request/response template support for pipeline functions and rejects invalid JS/S3 combinations.
Documentation for S3 location options
docs/sf/providers/aws/guide/appsync/pipeline-functions.md, docs/sf/providers/aws/guide/appsync/resolvers.md
Documents the new S3 location options, exclusivity rules, and usage details.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Config as serverless.yml
  participant Validator as validateConfig
  participant Resolver as Resolver.compile
  participant PipelineFunction as PipelineFunction.compile
  participant CloudFormation as CFN template

  Config->>Validator: resolver/pipelineFunction config with requestS3Location
  Validator->>Validator: schema + exclusivity checks
  Validator->>Resolver: valid resolver config
  Resolver->>Resolver: resolveS3Location()
  Resolver->>CloudFormation: set RequestMappingTemplateS3Location / ResponseMappingTemplateS3Location
  Validator->>PipelineFunction: valid pipeline function config
  PipelineFunction->>PipelineFunction: resolveS3Location()
  PipelineFunction->>CloudFormation: set RequestMappingTemplateS3Location / ResponseMappingTemplateS3Location
Loading

Related issues: #13249

Suggested labels: enhancement, appsync

Suggested reviewers: none identified

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: S3 mapping template support for AppSync resolvers and pipeline functions.
Linked Issues check ✅ Passed The PR implements S3 template locations for resolvers and functions, matching the linked issue’s goal to avoid inlined mapping templates.
Out of Scope Changes check ✅ Passed The changes stay focused on AppSync S3 template support, related validation, tests, and docs with no obvious unrelated additions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
packages/serverless/lib/plugins/aws/appsync/validation.js (1)

898-927: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider consolidating duplicate mutual-exclusion logic.

The same "request/requestS3Location", "response/responseS3Location", and "code + S3Location" exclusivity rules are re-implemented independently in Resolver.js and PipelineFunction.js (compile()), in addition to here. This is likely intentional (compile() can be invoked directly in tests without a prior validateConfig() call, so it needs its own guard), but the error-message text and exact truthiness semantics can drift between the three copies over time (see related comment on Resolver.js, lines 35-44, about a code truthiness/in-operator discrepancy that has already crept in).

Extracting a single shared helper (used by both validateConfig and the two compile() methods) would remove this class of drift risk going forward.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serverless/lib/plugins/aws/appsync/validation.js` around lines 898 -
927, The mutual-exclusion checks for request/requestS3Location,
response/responseS3Location, and code+S3Location are duplicated across
validation.js, Resolver.js, and PipelineFunction.js, which risks message and
truthiness drift. Extract the shared exclusivity logic into one helper and have
both validateConfig and the compile() paths in Resolver and PipelineFunction
call it so all three places enforce the same rules and error text.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/sf/providers/aws/guide/appsync/pipeline-functions.md`:
- Around line 39-40: Update the `requestS3Location` and `responseS3Location`
docs in the AppSync pipeline functions guide to also mention the
JavaScript-runtime restriction enforced by `PipelineFunction.js`: these S3
template locations are not allowed when `code` is set. Keep the existing
mutual-exclusion note with `request`/`response`, and add the `code`
incompatibility so the `PipelineFunction` configuration options are described
consistently with the validator.

In `@docs/sf/providers/aws/guide/appsync/resolvers.md`:
- Around line 48-49: Update the AppSync resolver config reference to state that
requestS3Location and responseS3Location are VTL-only and cannot be used when
code is set. In the resolver docs section that describes these fields, add the
code incompatibility alongside the existing request/response mutual exclusivity,
matching the behavior enforced by the resolver configuration handling. Use the
existing requestS3Location, responseS3Location, and code terminology so readers
can see the constraint clearly.

In `@packages/serverless/lib/plugins/aws/appsync/resources/Resolver.js`:
- Around line 17-26: The resolveS3Location helper currently returns an object
for AppSync template locations, but RequestMappingTemplateS3Location and
ResponseMappingTemplateS3Location must be plain S3 URI strings. Update
resolveS3Location in Resolver.js to return a single string (bucket/key style)
and stop including Version, then adjust any resolver/function template assembly
that uses requestS3Location or responseS3Location to consume the string value.
Also update the associated tests and snapshots to reflect the string output and
remove expectations around the object shape.

In `@packages/serverless/lib/plugins/aws/appsync/validation.js`:
- Around line 943-963: `validateConfig()` is not using the same merge behavior
as `getAppSyncConfig()`, so split resolver and pipeline-function entries may be
validated incompletely. Update the resolver and pipeline function checks in
`validation.js` to validate the normalized deep-merged config (or reuse the same
`lodash.merge`-based merging used by `getAppSyncConfig()`), and keep the
`notBoth()` checks in the `resolversMap` and `pipelineFunctionsMap` loops so
`request`/`requestS3Location` and `code`/S3 conflicts are caught correctly.

In `@packages/serverless/test/unit/lib/plugins/aws/appsync/resolvers.test.js`:
- Around line 57-121: The resolver tests are asserting the wrong shape for
RequestMappingTemplateS3Location, so update the expectations in the AppSync
resolver test cases to match the CloudFormation schema once resolveS3Location
and Resolver.js are corrected. Focus on the compileResolver coverage in this
spec and replace the object-based assertions around
RequestMappingTemplateS3Location/ResponseMappingTemplateS3Location with the
proper string-based contract, while keeping the existing checks that code cannot
be combined with requestS3Location.

---

Nitpick comments:
In `@packages/serverless/lib/plugins/aws/appsync/validation.js`:
- Around line 898-927: The mutual-exclusion checks for
request/requestS3Location, response/responseS3Location, and code+S3Location are
duplicated across validation.js, Resolver.js, and PipelineFunction.js, which
risks message and truthiness drift. Extract the shared exclusivity logic into
one helper and have both validateConfig and the compile() paths in Resolver and
PipelineFunction call it so all three places enforce the same rules and error
text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 296e47ff-b790-4e21-b2a1-4f88154f23d0

📥 Commits

Reviewing files that changed from the base of the PR and between b5ff282 and 32edab5.

⛔ Files ignored due to path filters (1)
  • packages/serverless/test/unit/lib/plugins/aws/appsync/__snapshots__/resolvers.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (8)
  • docs/sf/providers/aws/guide/appsync/pipeline-functions.md
  • docs/sf/providers/aws/guide/appsync/resolvers.md
  • packages/serverless/lib/plugins/aws/appsync/resources/PipelineFunction.js
  • packages/serverless/lib/plugins/aws/appsync/resources/Resolver.js
  • packages/serverless/lib/plugins/aws/appsync/validation.js
  • packages/serverless/test/unit/lib/plugins/aws/appsync/resolvers.test.js
  • packages/serverless/test/unit/lib/plugins/aws/appsync/validation/pipelineFunctions.test.js
  • packages/serverless/test/unit/lib/plugins/aws/appsync/validation/resolvers.test.js

Comment thread docs/sf/providers/aws/guide/appsync/pipeline-functions.md Outdated
Comment thread docs/sf/providers/aws/guide/appsync/resolvers.md Outdated
Comment thread packages/serverless/lib/plugins/aws/appsync/resources/Resolver.js
Comment on lines +943 to +963
const resolversMap = flattenMerge(data.resolvers)
for (const [key, resolver] of Object.entries(resolversMap)) {
if (typeof resolver === 'object' && resolver !== null) {
notBoth(resolver, `resolver '${key}'`)
if (Array.isArray(resolver.functions)) {
for (const f of resolver.functions) {
if (typeof f === 'object' && f !== null) {
notBoth(f, `inline pipeline function in resolver '${key}'`)
}
}
}
}
}

const pipelineFunctionsMap = flattenMerge(data.pipelineFunctions)
for (const [name, func] of Object.entries(pipelineFunctionsMap)) {
if (typeof func === 'object' && func !== null) {
notBoth(func, `pipeline function '${name}'`)
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Look for any existing config merge logic for resolvers/pipelineFunctions
# arrays-of-maps (deep vs shallow) elsewhere in the plugin.
rg -n -C3 'resolvers|pipelineFunctions' packages/serverless/lib/plugins/aws/appsync/index.js 2>/dev/null
fd -e js . packages/serverless/lib/plugins/aws/appsync --exec rg -l 'merge\(' {} \;

Repository: serverless/serverless

Length of output: 675


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## file list\n'
git ls-files 'packages/serverless/lib/plugins/aws/appsync/*.js'

printf '\n## outlines\n'
ast-grep outline packages/serverless/lib/plugins/aws/appsync/get-appsync-config.js --view expanded || true
ast-grep outline packages/serverless/lib/plugins/aws/appsync/validation.js --view expanded || true

printf '\n## merge/search hits\n'
rg -n -C 3 'merge\(|Object\.assign|flattenMerge|resolvers|pipelineFunctions' \
  packages/serverless/lib/plugins/aws/appsync/get-appsync-config.js \
  packages/serverless/lib/plugins/aws/appsync/validation.js \
  packages/serverless/lib/plugins/aws/appsync/index.js

printf '\n## focused reads\n'
sed -n '1,220p' packages/serverless/lib/plugins/aws/appsync/get-appsync-config.js
printf '\n---\n'
sed -n '900,990p' packages/serverless/lib/plugins/aws/appsync/validation.js

Repository: serverless/serverless

Length of output: 18534


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## validateConfig call sites\n'
rg -n -C 3 'validateConfig\(' packages/serverless/lib/plugins/aws/appsync

printf '\n## config flow around getAppSyncConfig and validateConfig\n'
sed -n '1,220p' packages/serverless/lib/plugins/aws/appsync/index.js
printf '\n---\n'
sed -n '1,220p' packages/serverless/lib/plugins/aws/appsync/get-appsync-config.js

printf '\n## semantic probe: shallow Object.assign vs deep merge on split keys\n'
node - <<'JS'
const merge = require('lodash/merge')

const raw = {
  resolvers: [
    { 'Query.user': { request: 'inline.vtl' } },
    { 'Query.user': { requestS3Location: 's3://bucket/request.vtl' } },
  ],
  pipelineFunctions: [
    { 'FnA': { code: 'exports.handler = () => {}' } },
    { 'FnA': { requestS3Location: 's3://bucket/request.vtl' } },
  ],
}

const shallow = Object.assign({}, ...raw.resolvers)
const deep = merge({}, ...raw.resolvers)
console.log(JSON.stringify({ shallow, deep }, null, 2))
console.log('shallow resolver keys:', Object.keys(shallow['Query.user'] || {}))
console.log('deep resolver keys:', Object.keys(deep['Query.user'] || {}))
JS

Repository: serverless/serverless

Length of output: 12738


Use the same deep merge as getAppSyncConfig()

getAppSyncConfig() deep-merges array-of-map inputs with lodash.merge, but validateConfig() flattens them with Object.assign. For split resolver/pipeline-function entries, a later partial entry can overwrite earlier fields and let notBoth() miss request/requestS3Location or code/S3 conflicts. Reuse the same merge behavior here, or validate the normalized config.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/serverless/lib/plugins/aws/appsync/validation.js` around lines 943 -
963, `validateConfig()` is not using the same merge behavior as
`getAppSyncConfig()`, so split resolver and pipeline-function entries may be
validated incompletely. Update the resolver and pipeline function checks in
`validation.js` to validate the normalized deep-merged config (or reuse the same
`lodash.merge`-based merging used by `getAppSyncConfig()`), and keep the
`notBoth()` checks in the `resolversMap` and `pipelineFunctionsMap` loops so
`request`/`requestS3Location` and `code`/S3 conflicts are caught correctly.

…d pipeline functions

Add requestS3Location / responseS3Location options to AppSync resolver and
pipeline function configuration. When set, CloudFormation emits
RequestMappingTemplateS3Location / ResponseMappingTemplateS3Location instead
of inlining the template body, avoiding the 1 MB CloudFormation template limit.

- Extend isVTLResolver predicate to recognise S3-only resolvers as VTL
- Add resolveS3Location helper (lowerCamelCase in, PascalCase CFN out)
- Guard against code + *S3Location (VTL-only scope)
- Guard against request + requestS3Location (per-side mutual exclusivity)
- Add appSyncS3Location AJV definition with required bucket/key, optional version
- Add post-AJV notBoth pass in validateConfig for clear error messages
- Unit tests for UNIT resolver, S3-only PIPELINE resolver, and pipeline function
- Validation tests for schema shape and mutual-exclusivity errors
- Docs updated for resolvers.md and pipeline-functions.md

Closes serverless#13249
@nitishagar nitishagar force-pushed the fix/13249-appsync-s3-location branch from 32edab5 to f1fefaa Compare July 2, 2026 03:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable usage of RequestMappingTemplateS3Location for resolver and function configurations in AppSync APIs.

2 participants