├── .gitattributes ├── .github ├── CODEOWNERS ├── release-drafter.yml └── workflows │ ├── auto-approve.yml │ ├── ci.yml │ ├── release-drafter.yml │ └── site.yml ├── .gitignore ├── .nvmrc ├── .scalafmt.conf ├── LICENSE ├── README.md ├── bootstrap ├── build.sbt ├── docs ├── index.md ├── package.json └── sidebars.js ├── lambda-event └── src │ ├── main │ └── scala │ │ └── zio │ │ └── lambda │ │ └── event │ │ ├── APIGatewayCustomAuthorizerEvent.scala │ │ ├── APIGatewayProxyRequestEvent.scala │ │ ├── APIGatewayV2CustomAuthorizerEvent.scala │ │ ├── APIGatewayV2HttpEvent.scala │ │ ├── APIGatewayV2WebSocket.scala │ │ ├── ActiveMQEvent.scala │ │ ├── ApplicationLoadBalancerRequestEvent.scala │ │ ├── CloudFormationCustomResourceEvent.scala │ │ ├── CloudFrontEvent.scala │ │ ├── CloudWatchLogsEvent.scala │ │ ├── CodeCommitEvent.scala │ │ ├── CognitoEvent.scala │ │ ├── CognitoUserPoolCreateAuthChallengeEvent.scala │ │ ├── CognitoUserPoolCustomMessageEvent.scala │ │ ├── CognitoUserPoolDefineAuthChallengeEvent.scala │ │ ├── CognitoUserPoolEvent.scala │ │ ├── CognitoUserPoolMigrateUserEvent.scala │ │ ├── CognitoUserPoolPostAuthenticationEvent.scala │ │ ├── CognitoUserPoolPostConfirmationEvent.scala │ │ ├── CognitoUserPoolPreAuthenticationEvent.scala │ │ ├── CognitoUserPoolPreSignUpEvent.scala │ │ ├── CognitoUserPoolPreTokenGenerationEvent.scala │ │ ├── CognitoUserPoolVerifyAuthChallengeResponseEvent.scala │ │ ├── ConfigEvent.scala │ │ ├── ConnectEvent.scala │ │ ├── DynamoDBEvent.scala │ │ ├── IoTButtonEvent.scala │ │ ├── KInesisFirehoseEvent.scala │ │ ├── KafkaEvent.scala │ │ ├── KinesisAnalyticsFirehoseInputProcessingEvent.scala │ │ ├── KinesisAnalyticsOutputDeliveryEvent.scala │ │ ├── KinesisAnalyticsStreamsInputPreprocessingEvent.scala │ │ ├── KinesisEvent.scala │ │ ├── LambdaDestinationEvent.scala │ │ ├── LexEvent.scala │ │ ├── S3BatchEvent.scala │ │ ├── S3Event.scala │ │ ├── SNSEvent.scala │ │ ├── SQSEvent.scala │ │ ├── ScheduledEvent.scala │ │ └── SecretsManagerRotationEvent.scala │ └── test │ └── scala │ └── zio │ └── lambda │ └── event │ ├── JavaLambdaEventJsonEncoder.scala │ ├── JavaLambdaEventsGen.scala │ ├── KafkaEventSpec.scala │ ├── KinesisEventSpec.scala │ ├── SQSEventSpec.scala │ └── ScheduledEventSpec.scala ├── lambda-example └── src │ └── main │ └── scala │ └── zio │ └── lambda │ └── example │ ├── CustomEvent.scala │ ├── CustomResponse.scala │ └── SimpleHandler.scala ├── lambda-response └── src │ └── main │ └── scala │ └── zio │ └── lambda │ └── response │ ├── APIGatewayProxyResponse.scala │ ├── APIGatewayV2HTTPResponse.scala │ ├── APIGatewayV2WebSocketResponse.scala │ ├── ApplicationLoadBalancerResponse.scala │ ├── KinesisAnalyticsInputPreprocessingResponse.scala │ ├── KinesisAnalyticsOutputDeliveryResponse.scala │ ├── S3BatchResponse.scala │ └── SimpleIAMPolicyResponse.scala ├── lambda └── src │ ├── main │ └── scala │ │ └── zio │ │ └── lambda │ │ ├── ClientContext.scala │ │ ├── CognitoIdentity.scala │ │ ├── Context.scala │ │ ├── ZLambda.scala │ │ ├── ZLambdaRunner.scala │ │ └── internal │ │ ├── CustomClassLoader.scala │ │ ├── InvocationError.scala │ │ ├── InvocationErrorResponse.scala │ │ ├── InvocationRequest.scala │ │ ├── InvocationResponse.scala │ │ ├── LambdaEnvironment.scala │ │ ├── LambdaLoader.scala │ │ ├── LambdaLoaderLive.scala │ │ ├── LoopProcessor.scala │ │ ├── RuntimeApi.scala │ │ ├── RuntimeApiLive.scala │ │ ├── ZLambdaAppReflective.scala │ │ └── ZLambdaReflectiveAppOld.scala │ └── test │ └── scala │ └── zio │ └── lambda │ └── internal │ ├── InvocationErrorGen.scala │ ├── InvocationRequestGen.scala │ ├── InvocationRequestImplicits.scala │ ├── InvocationRequestSpec.scala │ ├── LambdaEnvironmentGen.scala │ ├── LambdaLoaderLiveSpec.scala │ ├── RuntimeApiLiveSpec.scala │ ├── TestCustomClassLoader.scala │ ├── TestRuntimeApi.scala │ ├── TestZLambda.scala │ └── ZRuntimeSpec.scala ├── project ├── BuildHelper.scala ├── build.properties └── plugins.sbt └── sbt /.gitattributes: -------------------------------------------------------------------------------- 1 | sbt linguist-vendored 2 | website/* linguist-vendored 3 | docs/* linguist-vendored 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @zio/zio-lambda 2 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$NEXT_PATCH_VERSION' 2 | tag-template: 'v$NEXT_PATCH_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - 'feature' 7 | - title: '🐛 Bug Fixes' 8 | labels: 9 | - 'bug' 10 | - title: '🧰 Maintenance' 11 | labels: 12 | - 'build' 13 | - title: '🌱 Dependency Updates' 14 | labels: 15 | - 'dependency-update' 16 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 17 | template: | 18 | ## Changes 19 | $CHANGES 20 | -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | name: Auto approve 2 | 3 | on: 4 | pull_request_target 5 | 6 | jobs: 7 | auto-approve: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: hmarr/auto-approve-action@v4 11 | if: github.actor == 'scala-steward' 12 | with: 13 | github-token: "${{ secrets.GITHUB_TOKEN }}" 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: [ 'master' ] 7 | release: 8 | types: 9 | - published 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | java: [ '17' ] 18 | scala: [ '2.12.19', '2.13.16', '3.3.5' ] 19 | steps: 20 | - uses: actions/checkout@v4 21 | - name: Setup Java 22 | uses: actions/setup-java@v4.7 23 | with: 24 | distribution: temurin 25 | java-version: ${{ matrix.java }} 26 | - name: Setup sbt 27 | uses: sbt/setup-sbt@v1 28 | - name: Cache scala dependencies 29 | uses: coursier/cache-action@v6 30 | - name: Lint code 31 | run: sbt check 32 | - name: Run tests 33 | run: sbt ++${{ matrix.scala }}! test 34 | 35 | website: 36 | runs-on: ubuntu-latest 37 | timeout-minutes: 60 38 | steps: 39 | - name: Checkout current branch 40 | uses: actions/checkout@v4 41 | - name: Setup Java 42 | uses: actions/setup-java@v4.7 43 | with: 44 | distribution: temurin 45 | java-version: 17 46 | - name: Setup sbt 47 | uses: sbt/setup-sbt@v1 48 | - name: Cache scala dependencies 49 | uses: coursier/cache-action@v6 50 | - name: Check Website Generation 51 | run: sbt compileDocs 52 | 53 | publish: 54 | runs-on: ubuntu-latest 55 | needs: [ build, website ] 56 | if: github.event_name != 'pull_request' 57 | steps: 58 | - uses: actions/checkout@v4 59 | with: 60 | fetch-depth: 0 61 | - name: Setup Java 62 | uses: actions/setup-java@v4.7 63 | with: 64 | distribution: temurin 65 | java-version: 17 66 | - name: Setup sbt 67 | uses: sbt/setup-sbt@v1 68 | - run: sbt ci-release 69 | env: 70 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 71 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 72 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 73 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 74 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v5 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /.github/workflows/site.yml: -------------------------------------------------------------------------------- 1 | # This file was autogenerated using `zio-sbt-website` via `sbt generateGithubWorkflow` 2 | # task and should be included in the git repository. Please do not edit it manually. 3 | 4 | name: Website 5 | 'on': 6 | workflow_dispatch: {} 7 | release: 8 | types: 9 | - published 10 | push: 11 | branches: 12 | - master 13 | pull_request: {} 14 | jobs: 15 | build: 16 | name: Build and Test 17 | runs-on: ubuntu-latest 18 | if: ${{ github.event_name == 'pull_request' }} 19 | steps: 20 | - name: Git Checkout 21 | uses: actions/checkout@v3.3.0 22 | with: 23 | fetch-depth: '0' 24 | - name: Setup Scala 25 | uses: actions/setup-java@v3.9.0 26 | with: 27 | distribution: temurin 28 | java-version: 17 29 | check-latest: true 30 | - name: Check if the README file is up to date 31 | run: sbt docs/checkReadme 32 | - name: Check if the site workflow is up to date 33 | run: sbt docs/checkGithubWorkflow 34 | - name: Check artifacts build process 35 | run: sbt +publishLocal 36 | - name: Check website build process 37 | run: sbt docs/clean; sbt docs/buildWebsite 38 | publish-docs: 39 | name: Publish Docs 40 | runs-on: ubuntu-latest 41 | if: ${{ ((github.event_name == 'release') && (github.event.action == 'published')) || (github.event_name == 'workflow_dispatch') }} 42 | steps: 43 | - name: Git Checkout 44 | uses: actions/checkout@v3.3.0 45 | with: 46 | fetch-depth: '0' 47 | - name: Setup Scala 48 | uses: actions/setup-java@v3.9.0 49 | with: 50 | distribution: temurin 51 | java-version: 17 52 | check-latest: true 53 | - name: Setup NodeJs 54 | uses: actions/setup-node@v3 55 | with: 56 | node-version: 16.x 57 | registry-url: https://registry.npmjs.org 58 | - name: Publish Docs to NPM Registry 59 | run: sbt docs/publishToNpm 60 | env: 61 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 62 | generate-readme: 63 | name: Generate README 64 | runs-on: ubuntu-latest 65 | if: ${{ (github.event_name == 'push') || ((github.event_name == 'release') && (github.event.action == 'published')) }} 66 | steps: 67 | - name: Git Checkout 68 | uses: actions/checkout@v3.3.0 69 | with: 70 | ref: ${{ github.head_ref }} 71 | fetch-depth: '0' 72 | - name: Setup Scala 73 | uses: actions/setup-java@v3.9.0 74 | with: 75 | distribution: temurin 76 | java-version: 17 77 | check-latest: true 78 | - name: Generate Readme 79 | run: sbt docs/generateReadme 80 | - name: Commit Changes 81 | run: | 82 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 83 | git config --local user.name "github-actions[bot]" 84 | git add README.md 85 | git commit -m "Update README.md" || echo "No changes to commit" 86 | - name: Create Pull Request 87 | uses: peter-evans/create-pull-request@v4.2.3 88 | with: 89 | body: |- 90 | Autogenerated changes after running the `sbt docs/generateReadme` command of the [zio-sbt-website](https://zio.dev/zio-sbt) plugin. 91 | 92 | I will automatically update the README.md file whenever there is new change for README.md, e.g. 93 | - After each release, I will update the version in the installation section. 94 | - After any changes to the "docs/index.md" file, I will update the README.md file accordingly. 95 | branch: zio-sbt-website/update-readme 96 | commit-message: Update README.md 97 | delete-branch: true 98 | title: Update README.md 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dropbox settings and caches 2 | .dropbox 3 | .dropbox.attr 4 | .dropbox.cache 5 | # -*- mode: gitignore; -*- 6 | *~ 7 | \#*\# 8 | /.emacs.desktop 9 | /.emacs.desktop.lock 10 | *.elc 11 | auto-save-list 12 | tramp 13 | .\#* 14 | 15 | # Org-mode 16 | .org-id-locations 17 | *_archive 18 | 19 | # flymake-mode 20 | *_flymake.* 21 | 22 | # eshell files 23 | /eshell/history 24 | /eshell/lastdir 25 | 26 | # elpa packages 27 | /elpa/ 28 | 29 | # reftex files 30 | *.rel 31 | 32 | # AUCTeX auto folder 33 | /auto/ 34 | 35 | # cask packages 36 | .cask/ 37 | dist/ 38 | 39 | # Flycheck 40 | flycheck_*.el 41 | 42 | # server auth directory 43 | /server/ 44 | 45 | # projectiles files 46 | .projectile 47 | 48 | # directory configuration 49 | .dir-locals.el 50 | *~ 51 | 52 | # temporary files which can be created if a process still has a handle open of a deleted file 53 | .fuse_hidden* 54 | 55 | # KDE directory preferences 56 | .directory 57 | 58 | # Linux trash folder which might appear on any partition or disk 59 | .Trash-* 60 | 61 | # .nfs files are created when an open file is removed but is still being accessed 62 | .nfs* 63 | # General 64 | .DS_Store 65 | .AppleDouble 66 | .LSOverride 67 | 68 | # Icon must end with two \r 69 | Icon 70 | 71 | # Thumbnails 72 | ._* 73 | 74 | # Files that might appear in the root of a volume 75 | .DocumentRevisions-V100 76 | .fseventsd 77 | .Spotlight-V100 78 | .TemporaryItems 79 | .Trashes 80 | .VolumeIcon.icns 81 | .com.apple.timemachine.donotpresent 82 | 83 | # Directories potentially created on remote AFP share 84 | .AppleDB 85 | .AppleDesktop 86 | Network Trash Folder 87 | Temporary Items 88 | .apdisk 89 | # Cache files for Sublime Text 90 | *.tmlanguage.cache 91 | *.tmPreferences.cache 92 | *.stTheme.cache 93 | 94 | # Workspace files are user-specific 95 | *.sublime-workspace 96 | 97 | # Project files should be checked into the repository, unless a significant 98 | # proportion of contributors will probably not be using Sublime Text 99 | # *.sublime-project 100 | 101 | # SFTP configuration file 102 | sftp-config.json 103 | 104 | # Package control specific files 105 | Package Control.last-run 106 | Package Control.ca-list 107 | Package Control.ca-bundle 108 | Package Control.system-ca-bundle 109 | Package Control.cache/ 110 | Package Control.ca-certs/ 111 | Package Control.merged-ca-bundle 112 | Package Control.user-ca-bundle 113 | oscrypto-ca-bundle.crt 114 | bh_unicode_properties.cache 115 | 116 | # Sublime-github package stores a github token in this file 117 | # https://packagecontrol.io/packages/sublime-github 118 | GitHub.sublime-settings 119 | # Ignore tags created by etags, ctags, gtags (GNU global) and cscope 120 | TAGS 121 | .TAGS 122 | !TAGS/ 123 | tags 124 | .tags 125 | !tags/ 126 | gtags.files 127 | GTAGS 128 | GRTAGS 129 | GPATH 130 | GSYMS 131 | cscope.files 132 | cscope.out 133 | cscope.in.out 134 | cscope.po.out 135 | 136 | *.tmproj 137 | *.tmproject 138 | tmtags 139 | # Swap 140 | [._]*.s[a-v][a-z] 141 | [._]*.sw[a-p] 142 | [._]s[a-rt-v][a-z] 143 | [._]ss[a-gi-z] 144 | [._]sw[a-p] 145 | 146 | # Session 147 | Session.vim 148 | 149 | # Temporary 150 | .netrwhist 151 | *~ 152 | # Auto-generated tag files 153 | tags 154 | # Persistent undo 155 | [._]*.un~ 156 | # Windows thumbnail cache files 157 | Thumbs.db 158 | ehthumbs.db 159 | ehthumbs_vista.db 160 | 161 | # Dump file 162 | *.stackdump 163 | 164 | # Folder config file 165 | [Dd]esktop.ini 166 | 167 | # Recycle Bin used on file shares 168 | $RECYCLE.BIN/ 169 | 170 | # Windows Installer files 171 | *.cab 172 | *.msi 173 | *.msix 174 | *.msm 175 | *.msp 176 | 177 | # Windows shortcuts 178 | *.lnk 179 | 180 | .metadata 181 | bin/ 182 | tmp/ 183 | *.tmp 184 | *.bak 185 | *.swp 186 | *~.nib 187 | local.properties 188 | .settings/ 189 | .loadpath 190 | .recommenders 191 | 192 | # External tool builders 193 | .externalToolBuilders/ 194 | 195 | # Locally stored "Eclipse launch configurations" 196 | *.launch 197 | 198 | # PyDev specific (Python IDE for Eclipse) 199 | *.pydevproject 200 | 201 | # CDT-specific (C/C++ Development Tooling) 202 | .cproject 203 | 204 | # CDT- autotools 205 | .autotools 206 | 207 | # Java annotation processor (APT) 208 | .factorypath 209 | 210 | # PDT-specific (PHP Development Tools) 211 | .buildpath 212 | 213 | # sbteclipse plugin 214 | .target 215 | 216 | # Tern plugin 217 | .tern-project 218 | 219 | # TeXlipse plugin 220 | .texlipse 221 | 222 | # STS (Spring Tool Suite) 223 | .springBeans 224 | 225 | # Code Recommenders 226 | .recommenders/ 227 | 228 | # Annotation Processing 229 | .apt_generated/ 230 | 231 | # Scala IDE specific (Scala & Java development for Eclipse) 232 | .cache-main 233 | .scala_dependencies 234 | .worksheet 235 | # Ensime specific 236 | .ensime 237 | .ensime_cache/ 238 | .ensime_lucene/ 239 | # default application storage directory used by the IDE Performance Cache feature 240 | .data/ 241 | 242 | # used for ADF styles caching 243 | temp/ 244 | 245 | # default output directories 246 | classes/ 247 | deploy/ 248 | javadoc/ 249 | 250 | # lock file, a part of Oracle Credential Store Framework 251 | cwallet.sso.lck# JEnv local Java version configuration file 252 | .java-version 253 | 254 | # Used by previous versions of JEnv 255 | .jenv-version 256 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 257 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 258 | 259 | # User-specific stuff 260 | .idea/**/workspace.xml 261 | .idea/**/tasks.xml 262 | .idea/**/usage.statistics.xml 263 | .idea/**/dictionaries 264 | .idea/**/shelf 265 | 266 | # Sensitive or high-churn files 267 | .idea/**/dataSources/ 268 | .idea/**/dataSources.ids 269 | .idea/**/dataSources.local.xml 270 | .idea/**/sqlDataSources.xml 271 | .idea/**/dynamic.xml 272 | .idea/**/uiDesigner.xml 273 | .idea/**/dbnavigator.xml 274 | 275 | # Gradle 276 | .idea/**/gradle.xml 277 | .idea/**/libraries 278 | 279 | # Gradle and Maven with auto-import 280 | # When using Gradle or Maven with auto-import, you should exclude module files, 281 | # since they will be recreated, and may cause churn. Uncomment if using 282 | # auto-import. 283 | # .idea/modules.xml 284 | # .idea/*.iml 285 | # .idea/modules 286 | 287 | # CMake 288 | cmake-build-*/ 289 | 290 | # Mongo Explorer plugin 291 | .idea/**/mongoSettings.xml 292 | 293 | # File-based project format 294 | *.iws 295 | 296 | # IntelliJ 297 | out/ 298 | 299 | # mpeltonen/sbt-idea plugin 300 | .idea_modules/ 301 | 302 | # JIRA plugin 303 | atlassian-ide-plugin.xml 304 | 305 | # Cursive Clojure plugin 306 | .idea/replstate.xml 307 | 308 | # Crashlytics plugin (for Android Studio and IntelliJ) 309 | com_crashlytics_export_strings.xml 310 | crashlytics.properties 311 | crashlytics-build.properties 312 | fabric.properties 313 | 314 | # Editor-based Rest Client 315 | .idea/httpRequests 316 | nbproject/private/ 317 | build/ 318 | nbbuild/ 319 | dist/ 320 | nbdist/ 321 | .nb-gradle/ 322 | # Built application files 323 | *.apk 324 | *.ap_ 325 | 326 | # Files for the ART/Dalvik VM 327 | *.dex 328 | 329 | # Java class files 330 | *.class 331 | 332 | # Generated files 333 | bin/ 334 | gen/ 335 | out/ 336 | 337 | # Gradle files 338 | .gradle/ 339 | build/ 340 | 341 | # Local configuration file (sdk path, etc) 342 | local.properties 343 | 344 | # Proguard folder generated by Eclipse 345 | proguard/ 346 | 347 | # Log Files 348 | *.log 349 | 350 | # Android Studio Navigation editor temp files 351 | .navigation/ 352 | 353 | # Android Studio captures folder 354 | captures/ 355 | 356 | # IntelliJ 357 | *.iml 358 | .idea/workspace.xml 359 | .idea/tasks.xml 360 | .idea/gradle.xml 361 | .idea/assetWizardSettings.xml 362 | .idea/dictionaries 363 | .idea/libraries 364 | .idea/caches 365 | .idea/compiler.xml 366 | .idea/encodings.xml 367 | .idea/misc.xml 368 | .idea/modules.xml 369 | .idea/sbt.xml 370 | .idea/scala_compiler.xml 371 | .idea/codeStyles/codeStyleConfig.xml 372 | .idea/codeStyles/Project.xml 373 | .idea/hydra.xml 374 | 375 | # Keystore files 376 | # Uncomment the following line if you do not want to check your keystore files in. 377 | #*.jks 378 | 379 | # External native build folder generated in Android Studio 2.2 and later 380 | .externalNativeBuild 381 | 382 | # Google Services (e.g. APIs or Firebase) 383 | google-services.json 384 | 385 | # Freeline 386 | freeline.py 387 | freeline/ 388 | freeline_project_description.json 389 | 390 | # fastlane 391 | fastlane/report.xml 392 | fastlane/Preview.html 393 | fastlane/screenshots 394 | fastlane/test_output 395 | fastlane/readme.md 396 | .gradle 397 | /build/ 398 | 399 | # Ignore Gradle GUI config 400 | gradle-app.setting 401 | 402 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 403 | !gradle-wrapper.jar 404 | 405 | # Cache of project 406 | .gradletasknamecache 407 | 408 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 409 | # gradle/wrapper/gradle-wrapper.properties 410 | # Compiled class file 411 | *.class 412 | 413 | # Log file 414 | *.log 415 | 416 | # BlueJ files 417 | *.ctxt 418 | 419 | # Mobile Tools for Java (J2ME) 420 | .mtj.tmp/ 421 | 422 | # Package Files # 423 | *.jar 424 | *.war 425 | *.nar 426 | *.ear 427 | *.zip 428 | *.tar.gz 429 | *.rar 430 | 431 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 432 | hs_err_pid* 433 | target/ 434 | pom.xml.tag 435 | pom.xml.releaseBackup 436 | pom.xml.versionsBackup 437 | pom.xml.next 438 | release.properties 439 | dependency-reduced-pom.xml 440 | buildNumber.properties 441 | .mvn/timing.properties 442 | .mvn/wrapper/maven-wrapper.jar 443 | # Simple Build Tool 444 | # http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control 445 | 446 | dist/* 447 | target/ 448 | lib_managed/ 449 | src_managed/ 450 | project/boot/ 451 | project/plugins/project/ 452 | .history 453 | .cache 454 | .lib/ 455 | *.class 456 | *.log 457 | 458 | .metals/ 459 | .bloop/ 460 | .bsp/ 461 | .vscode/ 462 | project/secret 463 | project/metals.sbt 464 | project/project/ 465 | 466 | # mdoc 467 | website/node_modules 468 | website/build 469 | website/i18n/en.json 470 | website/static/api 471 | 472 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "2.7.5" 2 | maxColumn = 120 3 | align.preset = most 4 | align.multiline = false 5 | continuationIndent.defnSite = 2 6 | assumeStandardLibraryStripMargin = true 7 | docstrings = JavaDoc 8 | lineEndings = preserve 9 | includeCurlyBraceInSelectChains = false 10 | danglingParentheses.preset = true 11 | optIn.annotationNewlines = true 12 | newlines.alwaysBeforeMultilineDef = false 13 | 14 | rewrite.rules = [RedundantBraces] 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [//]: # (This file was autogenerated using `zio-sbt-website` plugin via `sbt generateReadme` command.) 2 | [//]: # (So please do not edit it manually. Instead, change "docs/index.md" file or sbt setting keys) 3 | [//]: # (e.g. "readmeDocumentation" and "readmeSupport".) 4 | 5 | # ZIO Lambda 6 | 7 | A ZIO-based AWS Custom Runtime compatible with GraalVM Native Image. 8 | 9 | [![Development](https://img.shields.io/badge/Project%20Stage-Development-green.svg)](https://github.com/zio/zio/wiki/Project-Stages) ![CI Badge](https://github.com/zio/zio-lambda/workflows/CI/badge.svg) [![Sonatype Releases](https://img.shields.io/nexus/r/https/oss.sonatype.org/dev.zio/zio-lambda_2.13.svg?label=Sonatype%20Release)](https://oss.sonatype.org/content/repositories/releases/dev/zio/zio-lambda_2.13/) [![Sonatype Snapshots](https://img.shields.io/nexus/s/https/oss.sonatype.org/dev.zio/zio-lambda_2.13.svg?label=Sonatype%20Snapshot)](https://oss.sonatype.org/content/repositories/snapshots/dev/zio/zio-lambda_2.13/) [![javadoc](https://javadoc.io/badge2/dev.zio/zio-lambda-docs_2.13/javadoc.svg)](https://javadoc.io/doc/dev.zio/zio-lambda-docs_2.13) [![ZIO Lambda](https://img.shields.io/github/stars/zio/zio-lambda?style=social)](https://github.com/zio/zio-lambda) 10 | 11 | ## Installation 12 | 13 | ```scala 14 | libraryDependencies += "dev.zio" %% "zio-json" % "0.6.2" 15 | libraryDependencies += "dev.zio" %% "zio-lambda" % "1.0.5" 16 | 17 | // Optional dependencies 18 | libraryDependencies += "dev.zio" %% "zio-lambda-event" % "1.0.5" 19 | libraryDependencies += "dev.zio" %% "zio-lambda-response" % "1.0.5" 20 | ``` 21 | 22 | ## Usage 23 | 24 | Create your Lambda function by providing it to `ZLambdaRunner.serve(...)` method. 25 | 26 | ```scala 27 | import zio.Console._ 28 | import zio._ 29 | import zio.lambda._ 30 | 31 | object SimpleHandler extends ZIOAppDefault { 32 | 33 | def app(request: KinesisEvent, context: Context) = for { 34 | _ <- printLine(event.message) 35 | } yield "Handler ran successfully" 36 | 37 | override val run = 38 | ZLambdaRunner.serve(app) 39 | } 40 | ``` 41 | 42 | zio-lambda depends on [**zio-json**](https://github.com/zio/zio-json) for decoding any event you send to it and enconding any response you send back to the Lambda service. You can either create your own data types or use the ones that are included in **zio-lambda-event** and **zio-lambda-response**. 43 | 44 | The last step is to define the way your function will be invoked. There are three ways, detailed below: 45 | 46 | ## Lambda layer 47 | 48 | Upload zio-lambda as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html) 49 | Each release will contain a zip file ready to be used as a lambda layer) and your function. Instructions coming soon! 50 | 51 | ## Direct deployment of native image binary 52 | 53 | 1. Create an AWS Lambda function and choose the runtime where you provide your own bootstrap on Amazon Linux 2 54 | 55 | ![create-lambda](https://user-images.githubusercontent.com/14280155/164102664-3686e415-20be-4dd9-8979-ea6098a7a4b9.png) 56 | 2. Run `sbt GraalVMNativeImage/packageBin`, we'll find the binary present under the `graalvm-native-image` folder: 57 | 58 | ![binary-located](https://user-images.githubusercontent.com/14280155/164103337-6645dfeb-7fc4-4f7f-9b13-8005b0cddead.png) 59 | 60 | 3. Create the following bootstap file (which calls out to the binary) and place it in the same directory alongside the binary: 61 | ```bash 62 | #!/usr/bin/env bash 63 | 64 | set -euo pipefail 65 | 66 | ./zio-lambda-example 67 | ``` 68 | 69 | ![bootstrap-alongside-native-binary](https://user-images.githubusercontent.com/14280155/164103935-0bf7a6cb-814d-4de1-8fa1-4d0d54fb6e88.png) 70 | 71 | 4. Now we can zip both these files up: 72 | ```log 73 | > pwd 74 | /home/cal/IdeaProjects/zio-lambda/lambda-example/target/graalvm-native-image 75 | > zip upload.zip bootstrap zio-lambda-example 76 | ``` 77 | 78 | 5. Take `upload.zip` and upload it to AWS Lambda and test your function: 79 | 80 | ![lambda-ui](https://user-images.githubusercontent.com/14280155/164104747-039ec584-d3e2-4b47-884d-ff88977e2b53.png) 81 | 82 | 6. Test everything out to make sure everything works: 83 | 84 | ![test-ui](https://user-images.githubusercontent.com/14280155/164104858-a720ac55-b9bb-47ec-af70-c4bd5eb5bed3.png) 85 | 86 | ## Deployment of native image binary in a Docker container 87 | 88 | Following the steps from `Direct deployment of native image binary` to produce your native image binary, we can package 89 | up the native binary into a Docker image and deploy it like that to AWS Lambda. 90 | 91 | ```Dockerfile 92 | FROM gcr.io/distroless/base-debian12 93 | COPY lambda-example/target/graalvm-native-image/zio-lambda-example /app/zio-lambda-example 94 | CMD ["/app/zio-lambda-example"] 95 | ``` 96 | 97 | **NOTE:** This Dockerfile is meant to build the lambda-example located in the zio-lambda project and the Dockerfile is 98 | placed in the zio-lambda-repository. You will need to adjust this Dockerfile to match your project needs. 99 | 100 | Now we can build and tag the Docker image: 101 | 102 | ```shell 103 | docker build -t native-image-binary . 104 | ``` 105 | 106 | Take this image and push it to AWS ECR: 107 | 108 | ```bash 109 | pass=$(aws ecr get-login-password --region us-east-1) 110 | docker login --username AWS --password $pass 111 | docker tag native-image-binary : 112 | docker push : 113 | ``` 114 | 115 | Here is an example: 116 | 117 | ![image-uploaded](https://user-images.githubusercontent.com/14280155/164120591-68a78d19-c56b-4793-96b8-cfe567443063.png) 118 | 119 | Create a Lambda function and choose container image: 120 | 121 | ![lambda-create-container-image](https://user-images.githubusercontent.com/14280155/164120637-9c827736-26a8-4c65-92d4-2919157bbda6.png) 122 | 123 | ![image](https://user-images.githubusercontent.com/14280155/164120764-2c736a46-29e3-488c-ba6a-e2b69ef51792.png) 124 | 125 | Please note that because you incur the overhead of your native binary residing within a Docker container, there is more overhead than the other approach of deploying the binary straight to AWS Lambda 126 | 127 | ## Documentation 128 | 129 | Learn more on the [ZIO Lambda homepage](https://zio.dev/zio-lambda/)! 130 | 131 | ## Contributing 132 | 133 | For the general guidelines, see ZIO [contributor's guide](https://zio.dev/about/contributing). 134 | 135 | ## Code of Conduct 136 | 137 | See the [Code of Conduct](https://zio.dev/about/code-of-conduct) 138 | 139 | ## Support 140 | 141 | Come chat with us on [![Badge-Discord]][Link-Discord]. 142 | 143 | [Badge-Discord]: https://img.shields.io/discord/629491597070827530?logo=discord "chat on discord" 144 | [Link-Discord]: https://discord.gg/2ccFBr4 "Discord" 145 | 146 | ## License 147 | 148 | [License](LICENSE) 149 | -------------------------------------------------------------------------------- /bootstrap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | /opt/bin/./zio-lambda 6 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import BuildHelper.* 2 | 3 | inThisBuild( 4 | List( 5 | organization := "dev.zio", 6 | homepage := Some(url("https://zio.dev/zio-lambda/")), 7 | licenses := List("Apache-2.0" -> url("http://www.apache.org/licenses/LICENSE-2.0")), 8 | developers := List( 9 | Developer( 10 | "jrmsamson", 11 | "Jerome Samson", 12 | "jeromess.88@gmail.com", 13 | url("https://github.com/jrmsamson") 14 | ), 15 | Developer( 16 | "jdegoes", 17 | "John De Goes", 18 | "john@degoes.net", 19 | url("http://degoes.net") 20 | ) 21 | ), 22 | pgpPassphrase := sys.env.get("PGP_PASSWORD").map(_.toArray), 23 | pgpPublicRing := file("/tmp/public.asc"), 24 | pgpSecretRing := file("/tmp/secret.asc"), 25 | scmInfo := Some( 26 | ScmInfo(url("https://github.com/zio/zio-lambda/"), "scm:git:git@github.com:zio/zio-lambda.git") 27 | ) 28 | ) 29 | ) 30 | 31 | val zioVersion = "2.1.17" 32 | val zioJsonVersion = "0.7.3" 33 | val awsLambdaJavaTests = "1.1.1" 34 | 35 | lazy val root = 36 | project 37 | .in(file(".")) 38 | .settings(publish / skip := true) 39 | .aggregate( 40 | docs, 41 | zioLambda, 42 | zioLambdaExample, 43 | zioLambdaEvent, 44 | zioLambdaResponse 45 | ) 46 | 47 | lazy val zioLambda = module("zio-lambda", "lambda") 48 | .enablePlugins(BuildInfoPlugin) 49 | .enablePlugins(JavaAppPackaging) 50 | .settings(buildInfoSettings("zio.lambda")) 51 | .settings( 52 | stdSettings("zio-lambda"), 53 | scalacOptions -= "-Xfatal-warnings", //temporary disable fatal errors on depricated method calls. 54 | testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), 55 | libraryDependencies ++= Seq( 56 | "com.amazonaws" % "aws-lambda-java-tests" % awsLambdaJavaTests % "test" 57 | ) 58 | ) 59 | .settings( 60 | topLevelDirectory := None, 61 | Universal / mappings ++= Seq(file("bootstrap") -> "bootstrap"), 62 | Compile / mainClass := Some("zio.lambda.internal.ZLambdaAppReflective") 63 | ) 64 | 65 | lazy val zioLambdaEvent = module("zio-lambda-event", "lambda-event") 66 | .enablePlugins(BuildInfoPlugin) 67 | .settings(buildInfoSettings("zio.lambda.event")) 68 | .settings( 69 | stdSettings("zio-lambda-event"), 70 | scalacOptions -= "-Yretain-trees", 71 | testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), 72 | libraryDependencies ++= Seq( 73 | "com.amazonaws" % "aws-lambda-java-tests" % awsLambdaJavaTests % "test" 74 | ) 75 | ) 76 | 77 | lazy val zioLambdaResponse = module("zio-lambda-response", "lambda-response") 78 | .enablePlugins(BuildInfoPlugin) 79 | .settings(buildInfoSettings("zio.lambda.response")) 80 | .settings( 81 | stdSettings("zio-lambda-response"), 82 | scalacOptions -= "-Yretain-trees", 83 | testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework")), 84 | libraryDependencies ++= Seq( 85 | "com.amazonaws" % "aws-lambda-java-tests" % awsLambdaJavaTests % "test" 86 | ) 87 | ) 88 | 89 | lazy val zioLambdaExample = module("zio-lambda-example", "lambda-example") 90 | .enablePlugins(BuildInfoPlugin) 91 | .enablePlugins(GraalVMNativeImagePlugin) 92 | .settings(buildInfoSettings("zio.lambda.example")) 93 | .settings( 94 | publish / skip := true, 95 | name := "zio-lambda-example", 96 | stdSettings("zio-lambda-example"), 97 | assembly / assemblyJarName := "zio-lambda-example.jar", 98 | GraalVMNativeImage / mainClass := Some("zio.lambda.example.SimpleHandler"), 99 | GraalVMNativeImage / containerBuildImage := Some("ghcr.io/graalvm/native-image-community:21.0.2"), 100 | graalVMNativeImageOptions := Seq( 101 | "--verbose", 102 | "--no-fallback", 103 | "--install-exit-handlers", 104 | "--enable-http", 105 | "--link-at-build-time", 106 | "--report-unsupported-elements-at-runtime", 107 | "-H:+UnlockExperimentalVMOptions", 108 | "-H:+StaticExecutableWithDynamicLibC", 109 | "-H:+RemoveSaturatedTypeFlows" 110 | ) 111 | ) 112 | .dependsOn(zioLambda) 113 | 114 | def module(moduleName: String, fileName: String): Project = 115 | Project(moduleName, file(fileName)) 116 | .settings(stdSettings(moduleName)) 117 | .settings( 118 | libraryDependencies ++= Seq( 119 | "dev.zio" %% "zio" % zioVersion, 120 | "dev.zio" %% "zio-test" % zioVersion % "test", 121 | "dev.zio" %% "zio-test-sbt" % zioVersion % "test", 122 | "dev.zio" %% "zio-json" % zioJsonVersion 123 | ) 124 | ) 125 | 126 | lazy val docs = project 127 | .in(file("zio-lambda-docs")) 128 | .settings( 129 | moduleName := "zio-lambda-docs", 130 | projectName := "ZIO Lambda", 131 | mainModuleName := (zioLambda / moduleName).value, 132 | projectStage := ProjectStage.Development, 133 | ScalaUnidoc / unidoc / unidocProjectFilter := inProjects(zioLambda, zioLambdaEvent, zioLambdaResponse), 134 | docsPublishBranch := "master", 135 | excludeDependencies += "org.scala-lang.modules" % "scala-collection-compat_2.13" 136 | ) 137 | .dependsOn(zioLambda, zioLambdaEvent, zioLambdaResponse) 138 | .enablePlugins(WebsitePlugin) 139 | 140 | addCommandAlias("fmt", "all scalafmtSbt scalafmt test:scalafmt") 141 | addCommandAlias("check", "all scalafmtSbtCheck scalafmtCheck test:scalafmtCheck") 142 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: index 3 | title: "Introduction to ZIO Lambda" 4 | sidebar_label: "ZIO Lambda" 5 | --- 6 | 7 | A ZIO-based AWS Custom Runtime compatible with GraalVM Native Image. 8 | 9 | @PROJECT_BADGES@ 10 | 11 | ## Installation 12 | 13 | ```scala 14 | libraryDependencies += "dev.zio" %% "zio-json" % "0.6.2" 15 | libraryDependencies += "dev.zio" %% "zio-lambda" % "@VERSION@" 16 | 17 | // Optional dependencies 18 | libraryDependencies += "dev.zio" %% "zio-lambda-event" % "@VERSION@" 19 | libraryDependencies += "dev.zio" %% "zio-lambda-response" % "@VERSION@" 20 | ``` 21 | 22 | ## Usage 23 | 24 | Create your Lambda function by providing it to `ZLambdaRunner.serve(...)` method. 25 | 26 | ```scala 27 | import zio.Console._ 28 | import zio._ 29 | import zio.lambda._ 30 | 31 | object SimpleHandler extends ZIOAppDefault { 32 | 33 | def app(request: KinesisEvent, context: Context) = for { 34 | _ <- printLine(event.message) 35 | } yield "Handler ran successfully" 36 | 37 | override val run = 38 | ZLambdaRunner.serve(app) 39 | } 40 | ``` 41 | 42 | zio-lambda depends on [**zio-json**](https://github.com/zio/zio-json) for decoding any event you send to it and enconding any response you send back to the Lambda service. You can either create your own data types or use the ones that are included in **zio-lambda-event** and **zio-lambda-response**. 43 | 44 | The last step is to define the way your function will be invoked. There are three ways, detailed below: 45 | 46 | ## Lambda layer 47 | 48 | Upload zio-lambda as a [Lambda Layer](https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html) 49 | Each release will contain a zip file ready to be used as a lambda layer) and your function. Instructions coming soon! 50 | 51 | ## Direct deployment of native image binary 52 | 53 | 1. Create an AWS Lambda function and choose the runtime where you provide your own bootstrap on Amazon Linux 2 54 | 55 | ![create-lambda](https://user-images.githubusercontent.com/14280155/164102664-3686e415-20be-4dd9-8979-ea6098a7a4b9.png) 56 | 2. Run `sbt GraalVMNativeImage/packageBin`, we'll find the binary present under the `graalvm-native-image` folder: 57 | 58 | ![binary-located](https://user-images.githubusercontent.com/14280155/164103337-6645dfeb-7fc4-4f7f-9b13-8005b0cddead.png) 59 | 60 | 3. Create the following bootstap file (which calls out to the binary) and place it in the same directory alongside the binary: 61 | ```bash 62 | #!/usr/bin/env bash 63 | 64 | set -euo pipefail 65 | 66 | ./zio-lambda-example 67 | ``` 68 | 69 | ![bootstrap-alongside-native-binary](https://user-images.githubusercontent.com/14280155/164103935-0bf7a6cb-814d-4de1-8fa1-4d0d54fb6e88.png) 70 | 71 | 4. Now we can zip both these files up: 72 | ```log 73 | > pwd 74 | /home/cal/IdeaProjects/zio-lambda/lambda-example/target/graalvm-native-image 75 | > zip upload.zip bootstrap zio-lambda-example 76 | ``` 77 | 78 | 5. Take `upload.zip` and upload it to AWS Lambda and test your function: 79 | 80 | ![lambda-ui](https://user-images.githubusercontent.com/14280155/164104747-039ec584-d3e2-4b47-884d-ff88977e2b53.png) 81 | 82 | 6. Test everything out to make sure everything works: 83 | 84 | ![test-ui](https://user-images.githubusercontent.com/14280155/164104858-a720ac55-b9bb-47ec-af70-c4bd5eb5bed3.png) 85 | 86 | ## Deployment of native image binary in a Docker container 87 | 88 | Following the steps from `Direct deployment of native image binary` to produce your native image binary, we can package 89 | up the native binary into a Docker image and deploy it like that to AWS Lambda. 90 | 91 | ```Dockerfile 92 | FROM gcr.io/distroless/base-debian12 93 | COPY lambda-example/target/graalvm-native-image/zio-lambda-example /app/zio-lambda-example 94 | CMD ["/app/zio-lambda-example"] 95 | ``` 96 | 97 | **NOTE:** This Dockerfile is meant to build the lambda-example located in the zio-lambda project and the Dockerfile is 98 | placed in the zio-lambda-repository. You will need to adjust this Dockerfile to match your project needs. 99 | 100 | Now we can build and tag the Docker image: 101 | 102 | ```shell 103 | docker build -t native-image-binary . 104 | ``` 105 | 106 | Take this image and push it to AWS ECR: 107 | 108 | ```bash 109 | pass=$(aws ecr get-login-password --region us-east-1) 110 | docker login --username AWS --password $pass 111 | docker tag native-image-binary : 112 | docker push : 113 | ``` 114 | 115 | Here is an example: 116 | 117 | ![image-uploaded](https://user-images.githubusercontent.com/14280155/164120591-68a78d19-c56b-4793-96b8-cfe567443063.png) 118 | 119 | Create a Lambda function and choose container image: 120 | 121 | ![lambda-create-container-image](https://user-images.githubusercontent.com/14280155/164120637-9c827736-26a8-4c65-92d4-2919157bbda6.png) 122 | 123 | ![image](https://user-images.githubusercontent.com/14280155/164120764-2c736a46-29e3-488c-ba6a-e2b69ef51792.png) 124 | 125 | Please note that because you incur the overhead of your native binary residing within a Docker container, there is more overhead than the other approach of deploying the binary straight to AWS Lambda 126 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@zio.dev/zio-lambda", 3 | "description": "ZIO Lambda Documentation", 4 | "license": "Apache-2.0" 5 | } 6 | -------------------------------------------------------------------------------- /docs/sidebars.js: -------------------------------------------------------------------------------- 1 | const sidebars = { 2 | sidebar: [ 3 | { 4 | type: "category", 5 | label: "ZIO Lambda", 6 | collapsed: false, 7 | link: { type: "doc", id: "index" }, 8 | items: [ ] 9 | } 10 | ] 11 | }; 12 | 13 | module.exports = sidebars; 14 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/APIGatewayCustomAuthorizerEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class APIGatewayCustomAuthorizerEvent( 6 | version: String, 7 | `type`: String, 8 | methodArn: String, 9 | identitySource: String, 10 | authorizationToken: String, 11 | resource: String, 12 | path: String, 13 | httpMethod: String, 14 | headers: Map[String, String], 15 | queryStringParameters: Map[String, String], 16 | pathParameters: Map[String, String], 17 | stageVariables: Map[String, String], 18 | requestContext: APIGatewayCustomAuthorizerRequestContext 19 | ) 20 | 21 | object APIGatewayCustomAuthorizerEvent { 22 | implicit val decoder: JsonDecoder[APIGatewayCustomAuthorizerEvent] = 23 | DeriveJsonDecoder.gen[APIGatewayCustomAuthorizerEvent] 24 | } 25 | 26 | final case class APIGatewayCustomAuthorizerRequestContext( 27 | path: String, 28 | accountId: String, 29 | resourceId: String, 30 | stage: String, 31 | requestId: String, 32 | identity: APIGatewayCustomAuthorizerRequestContextIdentity, 33 | resourcePath: String, 34 | httpMethod: String, 35 | apiId: String 36 | ) 37 | 38 | object APIGatewayCustomAuthorizerRequestContext { 39 | implicit val decoder: JsonDecoder[APIGatewayCustomAuthorizerRequestContext] = 40 | DeriveJsonDecoder.gen[APIGatewayCustomAuthorizerRequestContext] 41 | } 42 | 43 | final case class APIGatewayCustomAuthorizerRequestContextIdentity(apiKey: String, sourceIp: String) 44 | 45 | object APIGatewayCustomAuthorizerRequestContextIdentity { 46 | implicit val decoder: JsonDecoder[APIGatewayCustomAuthorizerRequestContextIdentity] = 47 | DeriveJsonDecoder.gen[APIGatewayCustomAuthorizerRequestContextIdentity] 48 | } 49 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/APIGatewayProxyRequestEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class APIGatewayProxyRequestEvent( 6 | resource: String, 7 | path: String, 8 | httpMethod: String, 9 | headers: Map[String, String], 10 | multiValueHeaders: Map[String, List[String]], 11 | queryStringParameters: Map[String, String], 12 | multiValueQueryStringParameters: Map[String, List[String]], 13 | pathParameters: Map[String, String], 14 | stageVariables: Map[String, String], 15 | requestContext: APIGatewayProxyRequestRequestContext, 16 | body: String, 17 | isBase64Encoded: Boolean 18 | ) 19 | 20 | object APIGatewayProxyRequest { 21 | implicit val decoder: JsonDecoder[APIGatewayProxyRequestEvent] = DeriveJsonDecoder.gen[APIGatewayProxyRequestEvent] 22 | } 23 | 24 | final case class APIGatewayProxyRequestRequestContext( 25 | accountId: String, 26 | stage: String, 27 | resourceId: String, 28 | requestId: String, 29 | operationName: String, 30 | identity: APIGatewayProxyRequestRequestContextIdentity, 31 | resourcePath: String, 32 | httpMethod: String, 33 | apiId: String, 34 | path: String, 35 | authorizer: Map[String, String] 36 | ) 37 | 38 | object APIGatewayProxyRequestRequestContext { 39 | implicit val decoder: JsonDecoder[APIGatewayProxyRequestRequestContext] = 40 | DeriveJsonDecoder.gen[APIGatewayProxyRequestRequestContext] 41 | } 42 | 43 | final case class APIGatewayProxyRequestRequestContextIdentity( 44 | cognitoIdentityPoolId: String, 45 | accountId: String, 46 | cognitoIdentityId: String, 47 | caller: String, 48 | apiKey: String, 49 | principalOrgId: String, 50 | sourceIp: String, 51 | cognitoAuthenticationType: String, 52 | cognitoAuthenticationProvider: String, 53 | userArn: String, 54 | userAgent: String, 55 | user: String, 56 | accessKey: String 57 | ) 58 | 59 | object APIGatewayProxyRequestRequestContextIdentity { 60 | implicit val decoder: JsonDecoder[APIGatewayProxyRequestRequestContextIdentity] = 61 | DeriveJsonDecoder.gen[APIGatewayProxyRequestRequestContextIdentity] 62 | } 63 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/APIGatewayV2CustomAuthorizerEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | import java.time.format.DateTimeFormatter 5 | import java.time.OffsetDateTime 6 | 7 | final case class APIGatewayV2CustomAuthorizerEvent( 8 | version: String, 9 | `type`: String, 10 | routeArn: String, 11 | identitySource: List[String], 12 | routeKey: String, 13 | rawPath: String, 14 | rawQueryString: String, 15 | cookies: List[String], 16 | headers: Map[String, String], 17 | queryStringParameters: Map[String, String], 18 | requestContext: APIGatewayV2CustomAuthorizerRequestContext, 19 | pathParameters: Map[String, String], 20 | stageVariables: Map[String, String] 21 | ) 22 | 23 | object APIGatewayV2CustomAuthorizer { 24 | implicit val decoder: JsonDecoder[APIGatewayV2CustomAuthorizerEvent] = 25 | DeriveJsonDecoder.gen[APIGatewayV2CustomAuthorizerEvent] 26 | } 27 | 28 | final case class APIGatewayV2CustomAuthorizerRequestContext( 29 | accountId: String, 30 | apiId: String, 31 | domainName: String, 32 | domainPrefix: String, 33 | http: Http, 34 | requestId: String, 35 | routeKey: String, 36 | stage: String, 37 | time: java.time.OffsetDateTime, 38 | timeEpoch: Long 39 | ) 40 | 41 | object APIGatewayV2CustomAuthorizerRequestContext { 42 | lazy val timeDateTimeFormatter = DateTimeFormatter.ofPattern("dd/MMM/yyyy:HH:mm:ss Z") 43 | 44 | implicit val timeDecoder: JsonDecoder[OffsetDateTime] = 45 | JsonDecoder[String].map(OffsetDateTime.parse(_, timeDateTimeFormatter)) 46 | 47 | implicit val decoder: JsonDecoder[APIGatewayV2CustomAuthorizerRequestContext] = 48 | DeriveJsonDecoder.gen[APIGatewayV2CustomAuthorizerRequestContext] 49 | } 50 | 51 | final case class Http( 52 | method: String, 53 | path: String, 54 | protocol: String, 55 | sourceIp: String, 56 | userAgent: String 57 | ) 58 | 59 | object Http { 60 | implicit val decoder: JsonDecoder[Http] = DeriveJsonDecoder.gen[Http] 61 | } 62 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/APIGatewayV2HttpEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class APIGatewayV2HttpEvent( 6 | version: String, 7 | routeKey: String, 8 | rawPath: String, 9 | rawQueryString: String, 10 | cookies: Option[List[String]], 11 | headers: Map[String, String], 12 | queryStringParameters: Option[Map[String, String]], 13 | pathParameters: Option[Map[String, String]], 14 | stageVariables: Option[Map[String, String]], 15 | body: String, 16 | isBase64Encoded: Boolean, 17 | requestContext: APIGatewayV2HTTPRequestContext 18 | ) 19 | 20 | object APIGatewayV2HttpEvent { 21 | implicit val decoder: JsonDecoder[APIGatewayV2HttpEvent] = DeriveJsonDecoder.gen[APIGatewayV2HttpEvent] 22 | } 23 | 24 | final case class APIGatewayV2HTTPRequestContext( 25 | routeKey: String, 26 | accountId: String, 27 | stage: String, 28 | apiId: String, 29 | domainName: String, 30 | domainPrefix: String, 31 | time: String, 32 | timeEpoch: Long, 33 | http: APIGatewayV2HTTPRequestContextHttp, 34 | authorizer: Option[APIGatewayV2HTTPRequestContextAuthorizer], 35 | requestId: String 36 | ) 37 | 38 | object APIGatewayV2HTTPRequestContext { 39 | implicit val decoder: JsonDecoder[APIGatewayV2HTTPRequestContext] = 40 | DeriveJsonDecoder.gen[APIGatewayV2HTTPRequestContext] 41 | } 42 | 43 | final case class APIGatewayV2HTTPRequestContextHttp( 44 | method: String, 45 | path: String, 46 | protocol: String, 47 | sourceIp: String, 48 | userAgent: String 49 | ) 50 | 51 | object APIGatewayV2HTTPRequestContextHttp { 52 | implicit val decoder: JsonDecoder[APIGatewayV2HTTPRequestContextHttp] = 53 | DeriveJsonDecoder.gen[APIGatewayV2HTTPRequestContextHttp] 54 | } 55 | 56 | final case class APIGatewayV2HTTPRequestContextAuthorizer( 57 | jwt: APIGatewayV2HTTPRequestContextAuthorizerJWT, 58 | lambda: Map[String, String], 59 | iam: APIGatewayV2HTTPRequestContextAuthorizerIAM 60 | ) 61 | 62 | object APIGatewayV2HTTPRequestContextAuthorizer { 63 | implicit val decoder: JsonDecoder[APIGatewayV2HTTPRequestContextAuthorizer] = 64 | DeriveJsonDecoder.gen[APIGatewayV2HTTPRequestContextAuthorizer] 65 | } 66 | 67 | final case class APIGatewayV2HTTPRequestContextAuthorizerJWT(claims: Map[String, String], scopes: List[String]) 68 | 69 | object APIGatewayV2HTTPRequestContextAuthorizerJWT { 70 | implicit val decoder: JsonDecoder[APIGatewayV2HTTPRequestContextAuthorizerJWT] = 71 | DeriveJsonDecoder.gen[APIGatewayV2HTTPRequestContextAuthorizerJWT] 72 | } 73 | 74 | final case class APIGatewayV2HTTPRequestContextAuthorizerIAM( 75 | accessKey: String, 76 | accountId: String, 77 | callerId: String, 78 | cognitoIdentity: APIGatewayV2HTTPRequestContextAuthorizerIAMCognitoIdentity, 79 | principalOrgId: String, 80 | userArn: String, 81 | userId: String 82 | ) 83 | 84 | object APIGatewayV2HTTPRequestContextAuthorizerIAM { 85 | implicit val decoder: JsonDecoder[APIGatewayV2HTTPRequestContextAuthorizerIAM] = 86 | DeriveJsonDecoder.gen[APIGatewayV2HTTPRequestContextAuthorizerIAM] 87 | } 88 | 89 | final case class APIGatewayV2HTTPRequestContextAuthorizerIAMCognitoIdentity( 90 | amr: List[String], 91 | identityId: String, 92 | identityPoolId: String 93 | ) 94 | 95 | object APIGatewayV2HTTPRequestContextAuthorizerIAMCognitoIdentity { 96 | implicit val decoder: JsonDecoder[APIGatewayV2HTTPRequestContextAuthorizerIAMCognitoIdentity] = 97 | DeriveJsonDecoder.gen[APIGatewayV2HTTPRequestContextAuthorizerIAMCognitoIdentity] 98 | } 99 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/APIGatewayV2WebSocket.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class APIGatewayV2WebSocketEvent( 6 | resource: String, 7 | path: String, 8 | httpMethod: String, 9 | headers: Map[String, String], 10 | multiValueHeaders: Map[String, List[String]], 11 | queryStringParameters: Map[String, String], 12 | multiValueQueryStringParameters: Map[String, List[String]], 13 | pathParameters: Map[String, String], 14 | stageVariables: Map[String, String], 15 | requestContext: APIGatewayV2WebSocketRequestContext, 16 | body: String, 17 | isBase64Encoded: Boolean 18 | ) 19 | 20 | object APIGatewayV2WebSocketEvent { 21 | implicit val decoder: JsonDecoder[APIGatewayV2WebSocketEvent] = DeriveJsonDecoder.gen[APIGatewayV2WebSocketEvent] 22 | } 23 | 24 | final case class APIGatewayV2WebSocketRequestContext( 25 | accountId: String, 26 | resourceId: String, 27 | stage: String, 28 | requestId: String, 29 | identity: APIGatewayV2WebSocketRequestContextIdentity, 30 | resourcePath: String, 31 | authorizer: Map[String, String], 32 | httpMethod: String, 33 | apiId: String, 34 | connectedAt: Long, 35 | connectionId: String, 36 | domainName: String, 37 | error: String, 38 | eventType: String, 39 | extendedRequestId: String, 40 | integrationLatency: String, 41 | messageDirection: String, 42 | messageId: String, 43 | requestTime: String, 44 | requestTimeEpoch: Long, 45 | routeKey: String, 46 | status: String 47 | ) 48 | object APIGatewayV2WebSocketRequestContext { 49 | implicit val decoder: JsonDecoder[APIGatewayV2WebSocketRequestContext] = 50 | DeriveJsonDecoder.gen[APIGatewayV2WebSocketRequestContext] 51 | } 52 | 53 | final case class APIGatewayV2WebSocketRequestContextIdentity( 54 | cognitoIdentityPoolId: String, 55 | accountId: String, 56 | cognitoIdentityId: String, 57 | caller: String, 58 | apiKey: String, 59 | sourceIp: String, 60 | cognitoAuthenticationType: String, 61 | cognitoAuthenticationProvider: String, 62 | userArn: String, 63 | userAgent: String, 64 | user: String, 65 | accessKey: String 66 | ) 67 | object APIGatewayV2WebSocketRequestContextIdentity { 68 | implicit val decoder: JsonDecoder[APIGatewayV2WebSocketRequestContextIdentity] = 69 | DeriveJsonDecoder.gen[APIGatewayV2WebSocketRequestContextIdentity] 70 | } 71 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/ActiveMQEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class ActiveMQEvent( 6 | eventSource: String, 7 | eventSourceArn: String, 8 | messages: List[ActiveMQMessage] 9 | ) 10 | 11 | object ActiveMQEvent { 12 | implicit val decoder: JsonDecoder[ActiveMQEvent] = DeriveJsonDecoder.gen[ActiveMQEvent] 13 | } 14 | 15 | final case class ActiveMQMessage( 16 | messageID: String, 17 | messageType: String, 18 | timestamp: Long, 19 | deliveryMode: Int, 20 | correlationID: String, 21 | replyTo: String, 22 | destination: ActiveMQMessageDestination, 23 | redelivered: Boolean, 24 | `type`: String, 25 | expiration: Long, 26 | priority: Int, 27 | data: String, 28 | brokerInTime: Long, 29 | brokerOutTime: Long 30 | ) 31 | 32 | object ActiveMQMessage { 33 | implicit val decoder: JsonDecoder[ActiveMQMessage] = DeriveJsonDecoder.gen[ActiveMQMessage] 34 | } 35 | 36 | final case class ActiveMQMessageDestination(physicalName: String) 37 | 38 | object ActiveMQMessageDestination { 39 | implicit val decoder: JsonDecoder[ActiveMQMessageDestination] = DeriveJsonDecoder.gen[ActiveMQMessageDestination] 40 | } 41 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/ApplicationLoadBalancerRequestEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class ApplicationLoadBalancerRequestEvent( 6 | requestContext: ApplicationLoadBalancerRequestContext, 7 | httpMethod: String, 8 | path: String, 9 | queryStringParameters: Map[String, String], 10 | multiValueQueryStringParameters: Map[String, List[String]], 11 | headers: Map[String, String], 12 | multiValueHeaders: Map[String, List[String]], 13 | body: String, 14 | isBase64Encoded: Boolean 15 | ) 16 | 17 | object ApplicationLoadBalancerRequestEvent { 18 | implicit val decoder: JsonDecoder[ApplicationLoadBalancerRequestEvent] = 19 | DeriveJsonDecoder.gen[ApplicationLoadBalancerRequestEvent] 20 | } 21 | 22 | final case class ApplicationLoadBalancerRequestContext(elb: ApplicationLoadBalancerRequestContextElb) 23 | object ApplicationLoadBalancerRequestContext { 24 | implicit val decoder: JsonDecoder[ApplicationLoadBalancerRequestContext] = 25 | DeriveJsonDecoder.gen[ApplicationLoadBalancerRequestContext] 26 | } 27 | 28 | final case class ApplicationLoadBalancerRequestContextElb(targetGroupArn: String) 29 | object ApplicationLoadBalancerRequestContextElb { 30 | implicit val decoder: JsonDecoder[ApplicationLoadBalancerRequestContextElb] = 31 | DeriveJsonDecoder.gen[ApplicationLoadBalancerRequestContextElb] 32 | } 33 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CloudFormationCustomResourceEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CloudFormationCustomResourceEvent( 6 | requestType: String, 7 | serviceToken: String, 8 | responseUrl: String, 9 | stackId: String, 10 | requestId: String, 11 | logicalResourceId: String, 12 | physicalResourceId: String, 13 | resourceType: String, 14 | resourceProperties: Map[String, String], 15 | oldResourceProperties: Map[String, String] 16 | ) 17 | 18 | object CloudFormationCustomResourceEvent { 19 | implicit val decoder: JsonDecoder[CloudFormationCustomResourceEvent] = 20 | DeriveJsonDecoder.gen[CloudFormationCustomResourceEvent] 21 | } 22 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CloudFrontEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CloudFrontEvent(records: List[CloudFrontRecord]) 6 | 7 | object CloudFrontEvent { 8 | implicit val decoder: JsonDecoder[CloudFrontEvent] = DeriveJsonDecoder.gen[CloudFrontEvent] 9 | } 10 | 11 | final case class CloudFrontRecord(cf: CloudFrontRecordCF) 12 | object CloudFrontRecord { 13 | implicit val decoder: JsonDecoder[CloudFrontRecord] = DeriveJsonDecoder.gen[CloudFrontRecord] 14 | } 15 | 16 | final case class CloudFrontRecordCF( 17 | config: CloudFrontRecordCFConfig, 18 | request: CloudFrontRecordCFRequest, 19 | response: CloudFrontRecordCFResponse 20 | ) 21 | object CloudFrontRecordCF { 22 | implicit val decoder: JsonDecoder[CloudFrontRecordCF] = DeriveJsonDecoder.gen[CloudFrontRecordCF] 23 | } 24 | 25 | final case class CloudFrontRecordCFConfig(distributionId: String) 26 | object CloudFrontRecordCFConfig { 27 | implicit val decoder: JsonDecoder[CloudFrontRecordCFConfig] = DeriveJsonDecoder.gen[CloudFrontRecordCFConfig] 28 | } 29 | final case class CloudFrontRecordCFRequest( 30 | uri: String, 31 | method: String, 32 | httpVersion: String, 33 | clientIp: String, 34 | headers: Map[String, List[CloudFrontRecordCFHttpHeader]] 35 | ) 36 | object CloudFrontRecordCFRequest { 37 | implicit val decoder: JsonDecoder[CloudFrontRecordCFRequest] = DeriveJsonDecoder.gen[CloudFrontRecordCFRequest] 38 | } 39 | 40 | final case class CloudFrontRecordCFResponse( 41 | status: String, 42 | statusDescription: String, 43 | httpVersion: String, 44 | headers: Map[String, List[CloudFrontRecordCFHttpHeader]] 45 | ) 46 | object CloudFrontRecordCFResponse { 47 | implicit val decoder: JsonDecoder[CloudFrontRecordCFResponse] = DeriveJsonDecoder.gen[CloudFrontRecordCFResponse] 48 | } 49 | 50 | final case class CloudFrontRecordCFHttpHeader( 51 | key: String, 52 | value: String 53 | ) 54 | object CloudFrontRecordCFHttpHeader { 55 | implicit val decoder: JsonDecoder[CloudFrontRecordCFHttpHeader] = 56 | DeriveJsonDecoder.gen[CloudFrontRecordCFHttpHeader] 57 | } 58 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CloudWatchLogsEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CloudWatchLogsEvent(awsLogs: CloudWatchLogsAWSLogs) 6 | 7 | object CloudWatchLogsEvent { 8 | implicit val decoder: JsonDecoder[CloudWatchLogsEvent] = DeriveJsonDecoder.gen[CloudWatchLogsEvent] 9 | } 10 | 11 | final case class CloudWatchLogsAWSLogs(data: String) 12 | object CloudWatchLogsAWSLogs { 13 | implicit val decoder: JsonDecoder[CloudWatchLogsAWSLogs] = DeriveJsonDecoder.gen[CloudWatchLogsAWSLogs] 14 | } 15 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CodeCommitEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CodeCommitEvent(records: Seq[CodeCommitRecord]) 6 | 7 | object CodeCommitEvent { 8 | implicit val decoder: JsonDecoder[CodeCommitEvent] = DeriveJsonDecoder.gen[CodeCommitEvent] 9 | } 10 | 11 | final case class CodeCommitRecord( 12 | eventId: String, 13 | eventVersion: String, 14 | eventTime: java.time.Instant, 15 | eventTriggerName: String, 16 | eventPartNumber: Int, 17 | codeCommit: CodeCommitRecordCommit, 18 | eventName: String, 19 | eventTriggerConfigId: String, 20 | eventSourceArn: String, 21 | userIdentityArn: String, 22 | eventSource: String, 23 | awsRegion: String, 24 | customData: String, 25 | eventTotalParts: Int 26 | ) 27 | object CodeCommitRecord { 28 | implicit val decoder: JsonDecoder[CodeCommitRecord] = DeriveJsonDecoder.gen[CodeCommitRecord] 29 | } 30 | 31 | final case class CodeCommitRecordCommit(references: List[CodeCommitRecordCommitReference]) 32 | object CodeCommitRecordCommit { 33 | implicit val decoder: JsonDecoder[CodeCommitRecordCommit] = DeriveJsonDecoder.gen[CodeCommitRecordCommit] 34 | } 35 | 36 | final case class CodeCommitRecordCommitReference( 37 | commit: String, 38 | ref: String, 39 | created: Boolean 40 | ) 41 | object CodeCommitRecordCommitReference { 42 | implicit val decoder: JsonDecoder[CodeCommitRecordCommitReference] = 43 | DeriveJsonDecoder.gen[CodeCommitRecordCommitReference] 44 | } 45 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoEvent( 6 | region: String, 7 | datasetRecords: Map[String, CognitoDatasetRecord], 8 | identityPoolId: String, 9 | identityId: String, 10 | datasetName: String, 11 | eventType: String, 12 | version: Int 13 | ) 14 | 15 | object CognitoEvent { 16 | implicit val decoder: JsonDecoder[CognitoEvent] = DeriveJsonDecoder.gen[CognitoEvent] 17 | } 18 | 19 | final case class CognitoDatasetRecord( 20 | oldValue: String, 21 | newValue: String, 22 | op: String 23 | ) 24 | object CognitoDatasetRecord { 25 | implicit val decoder: JsonDecoder[CognitoDatasetRecord] = DeriveJsonDecoder.gen[CognitoDatasetRecord] 26 | } 27 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolCreateAuthChallengeEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolCreateAuthChallengeEvent( 6 | version: String, 7 | triggerSource: String, 8 | region: String, 9 | userPoolId: String, 10 | userName: String, 11 | callerContext: CognitoUserPoolCreateAuthChallengeCallerContext, 12 | request: CognitoUserPoolCreateAuthChallengeRequest, 13 | response: CognitoUserPoolCreateAuthChallengeResponse 14 | ) 15 | 16 | object CognitoUserPoolCreateAuthChallenge { 17 | implicit val decoder: JsonDecoder[CognitoUserPoolCreateAuthChallengeEvent] = 18 | DeriveJsonDecoder.gen[CognitoUserPoolCreateAuthChallengeEvent] 19 | } 20 | 21 | final case class CognitoUserPoolCreateAuthChallengeCallerContext(awsSdkVersion: String, clientId: String) 22 | object CognitoUserPoolCreateAuthChallengeCallerContext { 23 | implicit val decoder: JsonDecoder[CognitoUserPoolCreateAuthChallengeCallerContext] = 24 | DeriveJsonDecoder.gen[CognitoUserPoolCreateAuthChallengeCallerContext] 25 | } 26 | 27 | final case class CognitoUserPoolCreateAuthChallengeRequest( 28 | clientMetadata: Map[String, String], 29 | challengeName: String, 30 | // session: List[ChallengeResult], 31 | userNotFound: Boolean 32 | ) 33 | object CognitoUserPoolCreateAuthChallengeRequest { 34 | implicit val decoder: JsonDecoder[CognitoUserPoolCreateAuthChallengeRequest] = 35 | DeriveJsonDecoder.gen[CognitoUserPoolCreateAuthChallengeRequest] 36 | } 37 | 38 | final case class CognitoUserPoolCreateAuthChallengeResponse( 39 | publicChallengeParameters: Map[String, String], 40 | privateChallengeParameters: Map[String, String], 41 | challengeMetadata: String 42 | ) 43 | object CognitoUserPoolCreateAuthChallengeResponse { 44 | implicit val decoder: JsonDecoder[CognitoUserPoolCreateAuthChallengeResponse] = 45 | DeriveJsonDecoder.gen[CognitoUserPoolCreateAuthChallengeResponse] 46 | } 47 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolCustomMessageEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolCustomMessageEvent( 6 | version: String, 7 | triggerSource: String, 8 | region: String, 9 | userPoolId: String, 10 | userName: String, 11 | callerContext: CognitoUserPoolCustomMessageCallerContext, 12 | request: CognitoUserPoolCustomMessageRequest, 13 | response: CognitoUserPoolCustomMessageResponse 14 | ) 15 | 16 | object CognitoUserPoolCustomMessageEvent { 17 | implicit val decoder: JsonDecoder[CognitoUserPoolCustomMessageEvent] = 18 | DeriveJsonDecoder.gen[CognitoUserPoolCustomMessageEvent] 19 | } 20 | 21 | final case class CognitoUserPoolCustomMessageCallerContext(awsSdkVersion: String, clientId: String) 22 | object CognitoUserPoolCustomMessageCallerContext { 23 | implicit val decoder: JsonDecoder[CognitoUserPoolCustomMessageCallerContext] = 24 | DeriveJsonDecoder.gen[CognitoUserPoolCustomMessageCallerContext] 25 | } 26 | 27 | final case class CognitoUserPoolCustomMessageRequest() 28 | object CognitoUserPoolCustomMessageRequest { 29 | implicit val decoder: JsonDecoder[CognitoUserPoolCustomMessageRequest] = 30 | DeriveJsonDecoder.gen[CognitoUserPoolCustomMessageRequest] 31 | } 32 | 33 | final case class CognitoUserPoolCustomMessageResponse() 34 | object CognitoUserPoolCustomMessageResponse { 35 | implicit val decoder: JsonDecoder[CognitoUserPoolCustomMessageResponse] = 36 | DeriveJsonDecoder.gen[CognitoUserPoolCustomMessageResponse] 37 | } 38 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolDefineAuthChallengeEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolDefineAuthChallengeEvent() 6 | 7 | object CognitoUserPoolDefineAuthChallengeEvent { 8 | implicit val decoder: JsonDecoder[CognitoUserPoolDefineAuthChallengeEvent] = 9 | DeriveJsonDecoder.gen[CognitoUserPoolDefineAuthChallengeEvent] 10 | } 11 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolEvent() 6 | 7 | object CognitoUserPoolEvent { 8 | implicit val decoder: JsonDecoder[CognitoUserPoolEvent] = DeriveJsonDecoder.gen[CognitoUserPoolEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolMigrateUserEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolMigrateUserEvent() 6 | object CognitoUserPoolMigrateUserEvent { 7 | implicit val decoder: JsonDecoder[CognitoUserPoolMigrateUserEvent] = 8 | DeriveJsonDecoder.gen[CognitoUserPoolMigrateUserEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolPostAuthenticationEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolPostAuthenticationEvent() 6 | object CognitoUserPoolPostAuthenticationEvent { 7 | implicit val decoder: JsonDecoder[CognitoUserPoolPostAuthenticationEvent] = 8 | DeriveJsonDecoder.gen[CognitoUserPoolPostAuthenticationEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolPostConfirmationEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolPostConfirmationEvent() 6 | object CognitoUserPoolPostConfirmationEvent { 7 | implicit val decoder: JsonDecoder[CognitoUserPoolPostConfirmationEvent] = 8 | DeriveJsonDecoder.gen[CognitoUserPoolPostConfirmationEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolPreAuthenticationEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolPreAuthenticationEvent() 6 | object CognitoUserPoolPreAuthentication { 7 | implicit val decoder: JsonDecoder[CognitoUserPoolPreAuthenticationEvent] = 8 | DeriveJsonDecoder.gen[CognitoUserPoolPreAuthenticationEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolPreSignUpEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolPreSignUpEvent() 6 | object CognitoUserPoolPreSignUpEvent { 7 | implicit val decoder: JsonDecoder[CognitoUserPoolPreSignUpEvent] = 8 | DeriveJsonDecoder.gen[CognitoUserPoolPreSignUpEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolPreTokenGenerationEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolPreTokenGenerationEvent() 6 | object CognitoUserPoolPreTokenGenerationEvent { 7 | implicit val decoder: JsonDecoder[CognitoUserPoolPreTokenGenerationEvent] = 8 | DeriveJsonDecoder.gen[CognitoUserPoolPreTokenGenerationEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/CognitoUserPoolVerifyAuthChallengeResponseEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoUserPoolVerifyAuthChallengeResponseEvent() 6 | object CognitoUserPoolVerifyAuthChallengeResponseEvent { 7 | implicit val decoder: JsonDecoder[CognitoUserPoolVerifyAuthChallengeResponseEvent] = 8 | DeriveJsonDecoder.gen[CognitoUserPoolVerifyAuthChallengeResponseEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/ConfigEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class ConfigEvent( 6 | version: String, 7 | invokingEvent: String, 8 | ruleParameters: String, 9 | resultToken: String, 10 | configRuleArn: String, 11 | configRuleId: String, 12 | configRuleName: String, 13 | accountId: String, 14 | executionRoleArn: String, 15 | eventLeftScope: Boolean 16 | ) 17 | 18 | object Config { 19 | implicit val decoder: JsonDecoder[ConfigEvent] = DeriveJsonDecoder.gen[ConfigEvent] 20 | } 21 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/ConnectEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class ConnectEvent(details: ConnectDetails, name: String) 6 | 7 | object ConnectEvent { 8 | implicit val decoder: JsonDecoder[ConnectEvent] = DeriveJsonDecoder.gen[ConnectEvent] 9 | } 10 | 11 | final case class ConnectDetails(contactData: ConnectContactData, parameters: Map[String, String]) 12 | object ConnectDetails { 13 | implicit val decoder: JsonDecoder[ConnectDetails] = DeriveJsonDecoder.gen[ConnectDetails] 14 | } 15 | 16 | final case class ConnectContactData( 17 | attributes: Map[String, String], 18 | channel: String, 19 | contactId: String, 20 | customerEndpoint: Endpoint, 21 | initialContactId: String, 22 | initiationMethod: String, 23 | instanceArn: String, 24 | previousContactId: String, 25 | queue: String, 26 | systemEndpoint: Endpoint 27 | ) 28 | object ConnectContactData { 29 | implicit val decoder: JsonDecoder[ConnectContactData] = DeriveJsonDecoder.gen[ConnectContactData] 30 | } 31 | final case class Endpoint(address: String, `type`: String) 32 | object Endpoint { 33 | implicit val decoder: JsonDecoder[Endpoint] = DeriveJsonDecoder.gen[Endpoint] 34 | } 35 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/DynamoDBEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | final case class DynamoDBEvent( 4 | // records: List[Dynamodb.DynamodbStreamRecord] 5 | ) 6 | 7 | object DynamoDBEvent { 8 | // implicit val codec: JsonValueCodec[Dynamodb] = JsonCodecMaker.make 9 | // final case class DynamodbStreamRecord( 10 | // eventName: String, 11 | // eventVersion: String, 12 | // eventSource: String, 13 | // awsRegion: String, 14 | // eventSourceARN: String, 15 | // dynamodb: StreamRecord, 16 | // userIdentity: Identity 17 | // ) 18 | // object DynamodbStreamRecord { 19 | // implicit val codec: JsonValueCodec[DynamodbStreamRecord] = JsonCodecMaker.make 20 | // } 21 | // final case class StreamRecord( 22 | // approximateCreationDateTime: java.time.Instant, 23 | // keys: Map[String, AttributeValue], 24 | // newImage: Map[String, AttributeValue], 25 | // oldImage: Map[String, AttributeValue], 26 | // sequenceNumber: String, 27 | // sizeBytes: Long, 28 | // streamViewType: String 29 | // ) 30 | // object StreamRecord { 31 | // implicit val codec: JsonValueCodec[StreamRecord] = JsonCodecMaker.make 32 | // } 33 | 34 | // final case class Identity(principalId: String, `type`: String) 35 | // object Identity { 36 | // implicit val codec: JsonValueCodec[Identity] = JsonCodecMaker.make 37 | // } 38 | // final case class AttributeValue( 39 | // n: String, 40 | // b: String, // Revisit this as it was defined as java.nio.ByteBuffer, 41 | // sS: List[String], 42 | // nS: List[String], 43 | // bS: List[String], // Revisit this as it was defined as List[java.nio.ByteBuffer] 44 | // m: Map[String, AttributeValue], 45 | // l: List[AttributeValue], 46 | // nULLValue: Boolean, 47 | // bOOL: Boolean 48 | // ) 49 | // object AttributeValue { 50 | // implicit val codec: JsonValueCodec[AttributeValue] = JsonCodecMaker.make 51 | // } 52 | // } 53 | 54 | // final case class DynamodbTimeWindow(records: Dynamodb.DynamodbStreamRecord) 55 | // object DynamodbTimeWindow { 56 | // implicit val codec: JsonValueCodec[DynamodbTimeWindow] = JsonCodecMaker.make 57 | // } 58 | } 59 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/IoTButtonEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class IoTButtonEvent( 6 | serialNumber: String, 7 | clickType: String, 8 | batteryVoltage: String 9 | ) 10 | object IoTButtonEvent { 11 | implicit val decoder: JsonDecoder[IoTButtonEvent] = DeriveJsonDecoder.gen[IoTButtonEvent] 12 | } 13 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/KInesisFirehoseEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class KinesisFirehoseEvent() 6 | object KinesisFirehoseEvent { 7 | implicit val decoder: JsonDecoder[KinesisFirehoseEvent] = DeriveJsonDecoder.gen[KinesisFirehoseEvent] 8 | } 9 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/KafkaEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class KafkaEvent( 6 | records: Map[String, List[KafkaRecord]], 7 | eventSource: String, 8 | eventSourceArn: String, 9 | bootstrapServers: String 10 | ) 11 | 12 | object KafkaEvent { 13 | implicit val decoder: JsonDecoder[KafkaEvent] = DeriveJsonDecoder.gen[KafkaEvent] 14 | } 15 | 16 | final case class KafkaRecord( 17 | topic: String, 18 | partition: Int, 19 | offset: Long, 20 | timestamp: java.time.Instant, 21 | timestampType: String, 22 | key: String, 23 | value: String 24 | ) 25 | object KafkaRecord { 26 | implicit val instantDecoder: JsonDecoder[java.time.Instant] = JsonDecoder[Long] 27 | .map(java.time.Instant.ofEpochMilli) 28 | 29 | implicit val decoder: JsonDecoder[KafkaRecord] = DeriveJsonDecoder.gen[KafkaRecord] 30 | } 31 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/KinesisAnalyticsFirehoseInputProcessingEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class KinesisAnalyticsFirehoseInputPreprocessingEvent( 6 | invocationId: String, 7 | applicationArn: String, 8 | streamArn: String, 9 | records: List[KinesisAnalyticsFirehoseInputPreprocessingRecord] 10 | ) 11 | object KinesisAnalyticsFirehoseInputPreprocessingEvent { 12 | implicit val decoder: JsonDecoder[KinesisAnalyticsFirehoseInputPreprocessingEvent] = 13 | DeriveJsonDecoder.gen[KinesisAnalyticsFirehoseInputPreprocessingEvent] 14 | } 15 | 16 | final case class KinesisAnalyticsFirehoseInputPreprocessingRecord( 17 | recordId: String, 18 | kinesisFirehoseRecordMetadata: KinesisFirehoseRecordMetadata, 19 | data: String // Revisit this as it was defined as java.nio.ByteBuffer 20 | ) 21 | object KinesisAnalyticsFirehoseInputPreprocessingRecord { 22 | implicit val decoder: JsonDecoder[KinesisAnalyticsFirehoseInputPreprocessingRecord] = 23 | DeriveJsonDecoder.gen[KinesisAnalyticsFirehoseInputPreprocessingRecord] 24 | } 25 | final case class KinesisFirehoseRecordMetadata(approximateArrivalTimestamp: Long) 26 | object KinesisFirehoseRecordMetadata { 27 | implicit val decoder: JsonDecoder[KinesisFirehoseRecordMetadata] = 28 | DeriveJsonDecoder.gen[KinesisFirehoseRecordMetadata] 29 | } 30 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/KinesisAnalyticsOutputDeliveryEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class KinesisAnalyticsOutputDeliveryEvent( 6 | invocationId: String, 7 | applicationArn: String, 8 | records: List[KinesisAnalyticsOutputDeliveryRecord] 9 | ) 10 | 11 | object KinesisAnalyticsOutputDeliveryEvent { 12 | implicit val decoder: JsonDecoder[KinesisAnalyticsOutputDeliveryEvent] = 13 | DeriveJsonDecoder.gen[KinesisAnalyticsOutputDeliveryEvent] 14 | } 15 | 16 | final case class KinesisAnalyticsOutputDeliveryRecord( 17 | recordId: String, 18 | lambdaDeliveryRecordMetadata: LambdaDeliveryRecordMetadata, 19 | data: String // Revisit this as it was defined as java.nio.ByteBuffer 20 | ) 21 | object KinesisAnalyticsOutputDeliveryRecord { 22 | implicit val decoder: JsonDecoder[KinesisAnalyticsOutputDeliveryRecord] = 23 | DeriveJsonDecoder.gen[KinesisAnalyticsOutputDeliveryRecord] 24 | } 25 | final case class LambdaDeliveryRecordMetadata(retryHint: Long) 26 | object LambdaDeliveryRecordMetadata { 27 | implicit val decoder: JsonDecoder[LambdaDeliveryRecordMetadata] = 28 | DeriveJsonDecoder.gen[LambdaDeliveryRecordMetadata] 29 | } 30 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/KinesisAnalyticsStreamsInputPreprocessingEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class KinesisAnalyticsStreamsInputPreprocessingEvent() 6 | object KinesisAnalyticsStreamsInputPreprocessingEvent { 7 | implicit val decoder: JsonDecoder[KinesisAnalyticsStreamsInputPreprocessingEvent] = 8 | DeriveJsonDecoder.gen[KinesisAnalyticsStreamsInputPreprocessingEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/KinesisEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class KinesisEvent( 6 | @jsonField("Records") records: List[KinesisRecord] 7 | ) 8 | 9 | object KinesisEvent { 10 | implicit val decoder: JsonDecoder[KinesisEvent] = DeriveJsonDecoder.gen[KinesisEvent] 11 | } 12 | 13 | final case class KinesisRecord( 14 | kinesis: KinesisRecordUnit, 15 | eventSource: String, 16 | eventID: String, 17 | invokeIdentityArn: String, 18 | eventName: String, 19 | eventVersion: String, 20 | eventSourceARN: String, 21 | awsRegion: String 22 | ) 23 | 24 | object KinesisRecord { 25 | implicit val decoder: JsonDecoder[KinesisRecord] = DeriveJsonDecoder.gen[KinesisRecord] 26 | } 27 | 28 | final case class KinesisRecordUnit( 29 | kinesisSchemaVersion: String, 30 | partitionKey: String, 31 | sequenceNumber: String, 32 | data: String, 33 | approximateArrivalTimestamp: java.time.Instant, 34 | encryptionType: KinesisRecordUnitEncryptionType 35 | ) 36 | 37 | object KinesisRecordUnit { 38 | implicit val instantDecoder: JsonDecoder[java.time.Instant] = JsonDecoder[Double] 39 | .map(value => java.time.Instant.ofEpochMilli((BigDecimal(value) * 1000).toLong)) 40 | 41 | implicit val decoder: JsonDecoder[KinesisRecordUnit] = DeriveJsonDecoder.gen[KinesisRecordUnit] 42 | } 43 | 44 | sealed trait KinesisRecordUnitEncryptionType 45 | object KinesisRecordUnitEncryptionType { 46 | case object None extends KinesisRecordUnitEncryptionType 47 | case object Kms extends KinesisRecordUnitEncryptionType 48 | 49 | implicit val decoder: JsonDecoder[KinesisRecordUnitEncryptionType] = JsonDecoder[String].mapOrFail { 50 | _.toUpperCase() match { 51 | case "NONE" => Right(None) 52 | case "KMS" => Right(Kms) 53 | case unknown => Left(s"Unknown Kinesis event encryptionType: $unknown") 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/LambdaDestinationEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class LambdaDestinationEvent() 6 | 7 | object LambdaDestinationEvent { 8 | implicit val decoder: JsonDecoder[LambdaDestinationEvent] = DeriveJsonDecoder.gen[LambdaDestinationEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/LexEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class LexEvent() 6 | 7 | object LexEvent { 8 | implicit val decoder: JsonDecoder[LexEvent] = DeriveJsonDecoder.gen[LexEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/S3BatchEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class S3BatchEvent() 6 | object S3BatchEvent { 7 | implicit val decoder: JsonDecoder[S3BatchEvent] = DeriveJsonDecoder.gen[S3BatchEvent] 8 | } 9 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/S3Event.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class S3Event() 6 | object S3Event { 7 | implicit val decoder: JsonDecoder[S3Event] = DeriveJsonDecoder.gen[S3Event] 8 | } 9 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/SNSEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class SNSEvent() 6 | object SNSEvent { 7 | implicit val decoder: JsonDecoder[SNSEvent] = DeriveJsonDecoder.gen[SNSEvent] 8 | } 9 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/SQSEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class SQSEvent(@jsonField("Records") records: List[SQSRecord]) 6 | 7 | object SQSEvent { 8 | implicit val decoder: JsonDecoder[SQSEvent] = DeriveJsonDecoder.gen[SQSEvent] 9 | } 10 | 11 | final case class SQSRecord( 12 | messageId: String, 13 | receiptHandle: String, 14 | body: String, 15 | md5OfBody: String, 16 | md5OfMessageAttributes: String, 17 | eventSourceARN: String, 18 | eventSource: String, 19 | awsRegion: String, 20 | attributes: Map[String, String], 21 | messageAttributes: Map[String, SQSMessageAttribute] 22 | ) 23 | 24 | object SQSRecord { 25 | implicit val decoder: JsonDecoder[SQSRecord] = DeriveJsonDecoder.gen[SQSRecord] 26 | } 27 | 28 | final case class SQSMessageAttribute(stringValue: String, dataType: SQSMessageAttributeDataType) 29 | object SQSMessageAttribute { 30 | implicit val decoder: JsonDecoder[SQSMessageAttribute] = DeriveJsonDecoder.gen[SQSMessageAttribute] 31 | 32 | } 33 | 34 | sealed trait SQSMessageAttributeDataType 35 | object SQSMessageAttributeDataType { 36 | implicit val decoder: JsonDecoder[SQSMessageAttributeDataType] = JsonDecoder[String].mapOrFail { 37 | _.toUpperCase() match { 38 | case "STRING" => Right(String) 39 | case "NUMBER" => Right(Number) 40 | case "BINARY" => Right(Binary) 41 | case value => Left(s"Unknown SQSMessageAttributeDataType: $value") 42 | } 43 | } 44 | 45 | case object String extends SQSMessageAttributeDataType 46 | case object Number extends SQSMessageAttributeDataType 47 | case object Binary extends SQSMessageAttributeDataType 48 | } 49 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/ScheduledEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class ScheduledEvent( 6 | id: String, 7 | account: String, 8 | region: String, 9 | detail: Map[String, String], 10 | source: String, 11 | resources: List[String], 12 | time: java.time.ZonedDateTime, 13 | @jsonField("detail-type") detailType: String 14 | ) 15 | 16 | object ScheduledEvent { 17 | implicit val decoder: JsonDecoder[ScheduledEvent] = DeriveJsonDecoder.gen[ScheduledEvent] 18 | } 19 | -------------------------------------------------------------------------------- /lambda-event/src/main/scala/zio/lambda/event/SecretsManagerRotationEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | 5 | final case class SecretsManagerRotationEvent() 6 | object SecretsManagerRotationEvent { 7 | implicit val decoder: JsonDecoder[SecretsManagerRotationEvent] = DeriveJsonDecoder.gen[SecretsManagerRotationEvent] 8 | } 9 | -------------------------------------------------------------------------------- /lambda-event/src/test/scala/zio/lambda/event/JavaLambdaEventJsonEncoder.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import com.amazonaws.services.lambda.runtime.serialization.PojoSerializer 4 | import com.amazonaws.services.lambda.runtime.serialization.events.LambdaEventSerializers 5 | 6 | import java.io.ByteArrayOutputStream 7 | import scala.reflect.ClassTag 8 | 9 | object JavaLambdaEventJsonEncoder { 10 | def toJson[T](value: T)(implicit tag: ClassTag[T]): String = { 11 | val pojoSerializer: PojoSerializer[T] = 12 | LambdaEventSerializers.serializerFor( 13 | tag.runtimeClass.asInstanceOf[Class[T]], 14 | this.getClass().getClassLoader() 15 | ) 16 | val outputStream = new ByteArrayOutputStream(1024) 17 | pojoSerializer.toJson(value, outputStream) 18 | outputStream.toString() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lambda-event/src/test/scala/zio/lambda/event/JavaLambdaEventsGen.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import com.amazonaws.services.lambda.runtime.events.models.kinesis.EncryptionType 4 | import com.amazonaws.services.lambda.runtime.events.{KafkaEvent => JavaKafkaEvent} 5 | import com.amazonaws.services.lambda.runtime.events.{KinesisEvent => JavaKinesisEvent} 6 | import com.amazonaws.services.lambda.runtime.events.{SQSEvent => JavaSQSEvent} 7 | import com.amazonaws.services.lambda.runtime.events.{ScheduledEvent => JavaScheduledEvent} 8 | import org.joda.time.DateTime 9 | import org.joda.time.DateTimeZone 10 | import zio.test._ 11 | 12 | import java.nio.ByteBuffer 13 | import java.time.Instant 14 | import java.time.OffsetDateTime 15 | import java.time.temporal.ChronoUnit 16 | import java.util.Date 17 | import java.util.TimeZone 18 | import scala.jdk.CollectionConverters._ 19 | 20 | object JavaLambdaEventsGen { 21 | 22 | private val genKinesisEventRecordUnit = 23 | for { 24 | kinesisSchemaVersion <- Gen.string 25 | approximateArrivalTimestamp <- Gen.instant(Instant.now(), Instant.now().plus(365, ChronoUnit.DAYS)) 26 | encryptionType <- Gen.oneOf( 27 | Gen.const(EncryptionType.KMS.toString()), 28 | Gen.const(EncryptionType.NONE.toString()) 29 | ) 30 | partitionKey <- Gen.string 31 | sequenceNumber <- Gen.string 32 | data <- Gen.string 33 | } yield { 34 | val record = new JavaKinesisEvent.Record() 35 | record.setKinesisSchemaVersion(kinesisSchemaVersion) 36 | record.setApproximateArrivalTimestamp(Date.from(approximateArrivalTimestamp)) 37 | record.setData(ByteBuffer.wrap(data.getBytes("UTF-8"))) 38 | record.setEncryptionType(encryptionType) 39 | record.setPartitionKey(partitionKey) 40 | record.setSequenceNumber(sequenceNumber) 41 | record 42 | } 43 | 44 | private val genKinesisEventRecord = 45 | for { 46 | awsRegion <- Gen.string 47 | eventID <- Gen.string 48 | eventName <- Gen.string 49 | eventSource <- Gen.string 50 | eventSourceArn <- Gen.string 51 | eventVersion <- Gen.string 52 | invokeIdentityArn <- Gen.string 53 | kinesis <- genKinesisEventRecordUnit 54 | } yield { 55 | val record = new JavaKinesisEvent.KinesisEventRecord() 56 | record.setAwsRegion(awsRegion) 57 | record.setEventID(eventID) 58 | record.setEventName(eventName) 59 | record.setEventSource(eventSource) 60 | record.setEventSourceARN(eventSourceArn) 61 | record.setEventVersion(eventVersion) 62 | record.setInvokeIdentityArn(invokeIdentityArn) 63 | record.setKinesis(kinesis) 64 | record 65 | } 66 | 67 | val genKinesisEvent: Gen[Sized, JavaKinesisEvent] = 68 | Gen.listOf1(genKinesisEventRecord).map { records => 69 | val kinesisEvent = new JavaKinesisEvent() 70 | kinesisEvent.setRecords(records.asJava) 71 | kinesisEvent 72 | } 73 | 74 | val genScheduledEvent: Gen[Sized, JavaScheduledEvent] = 75 | for { 76 | account <- Gen.string 77 | region <- Gen.string 78 | detail <- Gen.mapOf(Gen.string, Gen.string) 79 | source <- Gen.string 80 | id <- Gen.string 81 | time <- Gen.offsetDateTime(OffsetDateTime.now(), OffsetDateTime.now().plusDays(365)) 82 | resources <- Gen.listOf(Gen.string) 83 | detailType <- Gen.string 84 | } yield { 85 | val scheduledEvent = new JavaScheduledEvent() 86 | scheduledEvent 87 | .withAccount(account) 88 | .withRegion(region) 89 | .withDetail(detail.asInstanceOf[Map[String, Object]].asJava) 90 | .withSource(source) 91 | .withId(id) 92 | .withTime( 93 | new DateTime( 94 | time.toInstant().toEpochMilli(), 95 | DateTimeZone.forTimeZone(TimeZone.getTimeZone(time.toZonedDateTime.getZone())) 96 | ) 97 | ) 98 | .withResources(resources.asJava) 99 | .withDetailType(detailType) 100 | 101 | scheduledEvent 102 | } 103 | 104 | private val genKafkaEventRecord = 105 | for { 106 | topic <- Gen.string 107 | partition <- Gen.int 108 | offset <- Gen.long 109 | timestamp <- Gen 110 | .instant( 111 | Instant.now(), 112 | Instant 113 | .now() 114 | .plus(365, ChronoUnit.DAYS) 115 | ) 116 | .map(_.toEpochMilli()) 117 | timestampType <- Gen.string 118 | key <- Gen.string 119 | value <- Gen.string 120 | } yield JavaKafkaEvent.KafkaEventRecord 121 | .builder() 122 | .withTopic(topic) 123 | .withPartition(partition) 124 | .withOffset(offset) 125 | .withTimestamp(timestamp) 126 | .withTimestampType(timestampType) 127 | .withKey(key) 128 | .withValue(value) 129 | .build() 130 | 131 | val genKafkaEvent: Gen[Sized, JavaKafkaEvent] = 132 | for { 133 | eventSource <- Gen.string 134 | eventSourceArn <- Gen.string 135 | bootstrapServers <- Gen.string 136 | records <- Gen.mapOf(Gen.string, Gen.listOf(genKafkaEventRecord).map(_.asJava)) 137 | } yield JavaKafkaEvent 138 | .builder() 139 | .withEventSource(eventSource) 140 | .withEventSourceArn(eventSourceArn) 141 | .withBootstrapServers(bootstrapServers) 142 | .withRecords(records.asJava) 143 | .build() 144 | 145 | private val genMessageAttribute = 146 | for { 147 | stringValue <- Gen.string 148 | dataType <- Gen.oneOf(Gen.const("String"), Gen.const("Number"), Gen.const("Binary")) 149 | } yield { 150 | val messageAttribute = new JavaSQSEvent.MessageAttribute() 151 | messageAttribute.setDataType(dataType) 152 | messageAttribute.setStringValue(stringValue) 153 | messageAttribute 154 | } 155 | 156 | private val genSQSEventRecord = 157 | for { 158 | messageId <- Gen.string 159 | receiptHandle <- Gen.string 160 | body <- Gen.string 161 | md5OfBody <- Gen.string 162 | md5OfMessageAttributes <- Gen.string 163 | eventSourceArn <- Gen.string 164 | eventSource <- Gen.string 165 | awsRegion <- Gen.string 166 | attributes <- Gen.mapOf(Gen.string, Gen.string) 167 | messageAttributes <- Gen.mapOf(Gen.string, genMessageAttribute) 168 | } yield { 169 | val sqsMessage = new JavaSQSEvent.SQSMessage() 170 | sqsMessage.setMessageId(messageId) 171 | sqsMessage.setReceiptHandle(receiptHandle) 172 | sqsMessage.setBody(body) 173 | sqsMessage.setMd5OfBody(md5OfBody) 174 | sqsMessage.setMd5OfMessageAttributes(md5OfMessageAttributes) 175 | sqsMessage.setEventSourceArn(eventSourceArn) 176 | sqsMessage.setEventSource(eventSource) 177 | sqsMessage.setAwsRegion(awsRegion) 178 | sqsMessage.setAttributes(attributes.asJava) 179 | sqsMessage.setMessageAttributes(messageAttributes.asJava) 180 | sqsMessage 181 | } 182 | 183 | val genSQSEvent: Gen[Sized, JavaSQSEvent] = 184 | Gen.listOf(genSQSEventRecord).map { records => 185 | val sqsEvent = new JavaSQSEvent() 186 | sqsEvent.setRecords(records.asJava) 187 | sqsEvent 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /lambda-event/src/test/scala/zio/lambda/event/KafkaEventSpec.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | import zio.test.Assertion._ 5 | import zio.test._ 6 | 7 | object KafkaEventSpec extends ZIOSpecDefault { 8 | 9 | override def spec = 10 | suite("KafkaEvent spec")( 11 | test("should decode Kafka JSON") { 12 | check(JavaLambdaEventsGen.genKafkaEvent) { kafkaEvent => 13 | assert(JavaLambdaEventJsonEncoder.toJson(kafkaEvent).fromJson[KafkaEvent])(isRight) 14 | } 15 | } 16 | ) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /lambda-event/src/test/scala/zio/lambda/event/KinesisEventSpec.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | import zio.test.Assertion._ 5 | import zio.test._ 6 | 7 | object KinesisEventSpec extends ZIOSpecDefault { 8 | 9 | override def spec = 10 | suite("KinesisEvent spec")( 11 | test("should decode Kinesis JSON") { 12 | check(JavaLambdaEventsGen.genKinesisEvent) { kinesisEvent => 13 | assert(JavaLambdaEventJsonEncoder.toJson(kinesisEvent).fromJson[KinesisEvent])(isRight) 14 | } 15 | } 16 | ) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /lambda-event/src/test/scala/zio/lambda/event/SQSEventSpec.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | import zio.test.Assertion._ 5 | import zio.test._ 6 | 7 | object SQSEventSpec extends ZIOSpecDefault { 8 | 9 | override def spec = 10 | suite("SQSEvent spec")( 11 | test("should decode SQS JSON") { 12 | check(JavaLambdaEventsGen.genSQSEvent) { sqsEvent => 13 | assert(JavaLambdaEventJsonEncoder.toJson(sqsEvent).fromJson[SQSEvent])(isRight) 14 | } 15 | } 16 | ) 17 | 18 | } 19 | -------------------------------------------------------------------------------- /lambda-event/src/test/scala/zio/lambda/event/ScheduledEventSpec.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.event 2 | 3 | import zio.json._ 4 | import zio.test.Assertion._ 5 | import zio.test._ 6 | 7 | object ScheduledEventSpec extends ZIOSpecDefault { 8 | 9 | override def spec = 10 | suite("ScheduledEvent spec")( 11 | test("should decode Scheduled JSON") { 12 | check(JavaLambdaEventsGen.genScheduledEvent) { scheduledEvent => 13 | assert(JavaLambdaEventJsonEncoder.toJson(scheduledEvent).fromJson[ScheduledEvent])(isRight) 14 | } 15 | } 16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /lambda-example/src/main/scala/zio/lambda/example/CustomEvent.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.example 2 | 3 | import zio.json._ 4 | 5 | final case class CustomEvent(message: String) 6 | 7 | object CustomEvent { 8 | implicit val decoder: JsonDecoder[CustomEvent] = DeriveJsonDecoder.gen[CustomEvent] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-example/src/main/scala/zio/lambda/example/CustomResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.example 2 | 3 | import zio.json._ 4 | 5 | final case class CustomResponse(message: String) 6 | 7 | object CustomResponse { 8 | implicit val encoder: JsonEncoder[CustomResponse] = DeriveJsonEncoder.gen[CustomResponse] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-example/src/main/scala/zio/lambda/example/SimpleHandler.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.example 2 | 3 | import zio.Console._ 4 | import zio._ 5 | import zio.lambda._ 6 | 7 | object SimpleHandler extends ZIOAppDefault { 8 | 9 | val app = (event: CustomEvent, _: Context) => 10 | for { 11 | _ <- printLine(event.message) 12 | } yield "Handler ran successfully" 13 | 14 | override val run = 15 | ZLambdaRunner.serve(app) 16 | } 17 | -------------------------------------------------------------------------------- /lambda-response/src/main/scala/zio/lambda/response/APIGatewayProxyResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.response 2 | 3 | import zio.json._ 4 | 5 | final case class APIGatewayProxyResponse( 6 | statusCode: Int, 7 | headers: Map[String, String], 8 | multiValueHeaders: Map[String, List[String]], 9 | body: String, 10 | isBase64Encoded: Boolean 11 | ) 12 | 13 | object APIGatewayProxyResponse { 14 | implicit val encoder: JsonEncoder[APIGatewayProxyResponse] = DeriveJsonEncoder.gen[APIGatewayProxyResponse] 15 | } 16 | -------------------------------------------------------------------------------- /lambda-response/src/main/scala/zio/lambda/response/APIGatewayV2HTTPResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.response 2 | 3 | import zio.json._ 4 | 5 | final case class APIGatewayV2HTTPResponse( 6 | body: String, 7 | statusCode: Int = 200, 8 | headers: Map[String, String] = Map(), 9 | multiValueHeaders: Map[String, List[String]] = Map(), 10 | cookies: List[String] = List(), 11 | isBase64Encoded: Boolean = false 12 | ) 13 | 14 | object APIGatewayV2HTTPResponse { 15 | implicit val encoder: JsonEncoder[APIGatewayV2HTTPResponse] = DeriveJsonEncoder.gen[APIGatewayV2HTTPResponse] 16 | } 17 | -------------------------------------------------------------------------------- /lambda-response/src/main/scala/zio/lambda/response/APIGatewayV2WebSocketResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.response 2 | 3 | import zio.json._ 4 | 5 | final case class APIGatewayV2WebSocketResponse( 6 | isBase64Encoded: Boolean, 7 | statusCode: Int, 8 | headers: Map[String, String], 9 | multiValueHeaders: Map[String, List[String]], 10 | body: String 11 | ) 12 | 13 | object APIGatewayV2WebSocketResponse { 14 | implicit val encoder: JsonEncoder[APIGatewayV2WebSocketResponse] = 15 | DeriveJsonEncoder.gen[APIGatewayV2WebSocketResponse] 16 | 17 | } 18 | -------------------------------------------------------------------------------- /lambda-response/src/main/scala/zio/lambda/response/ApplicationLoadBalancerResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.response 2 | 3 | import zio.json._ 4 | 5 | final case class ApplicationLoadBalancerResponse( 6 | statusCode: Int, 7 | statusDescription: String, 8 | isBase64Encoded: Boolean, 9 | headers: Map[String, String], 10 | multiValueHeaders: Map[String, List[String]], 11 | body: String 12 | ) 13 | 14 | object ApplicationLoadBalancerResponse { 15 | implicit val encoder: JsonEncoder[ApplicationLoadBalancerResponse] = 16 | DeriveJsonEncoder.gen[ApplicationLoadBalancerResponse] 17 | 18 | } 19 | -------------------------------------------------------------------------------- /lambda-response/src/main/scala/zio/lambda/response/KinesisAnalyticsInputPreprocessingResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.response 2 | 3 | import zio.json._ 4 | 5 | final case class KinesisAnalyticsInputPreprocessingResponse( 6 | records: List[KinesisAnalyticsInputPreprocessingResponseRecord] 7 | ) 8 | 9 | object KinesisAnalyticsInputPreprocessingResponse { 10 | implicit val encoder: JsonEncoder[KinesisAnalyticsInputPreprocessingResponse] = 11 | DeriveJsonEncoder.gen[KinesisAnalyticsInputPreprocessingResponse] 12 | 13 | } 14 | 15 | final case class KinesisAnalyticsInputPreprocessingResponseRecord( 16 | recordId: String, 17 | result: KinesisAnalyticsInputPreprocessingResponseRecordResult 18 | ) 19 | 20 | object KinesisAnalyticsInputPreprocessingResponseRecord { 21 | implicit val encoder: JsonEncoder[KinesisAnalyticsInputPreprocessingResponseRecord] = 22 | DeriveJsonEncoder.gen[KinesisAnalyticsInputPreprocessingResponseRecord] 23 | 24 | } 25 | 26 | sealed trait KinesisAnalyticsInputPreprocessingResponseRecordResult 27 | object KinesisAnalyticsInputPreprocessingResponseRecordResult { 28 | implicit val encoder: JsonEncoder[KinesisAnalyticsInputPreprocessingResponseRecordResult] = 29 | DeriveJsonEncoder.gen[KinesisAnalyticsInputPreprocessingResponseRecordResult] 30 | 31 | case object Ok extends KinesisAnalyticsInputPreprocessingResponseRecordResult 32 | case object ProcessingFailed extends KinesisAnalyticsInputPreprocessingResponseRecordResult 33 | case object Dropped extends KinesisAnalyticsInputPreprocessingResponseRecordResult 34 | } 35 | -------------------------------------------------------------------------------- /lambda-response/src/main/scala/zio/lambda/response/KinesisAnalyticsOutputDeliveryResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.response 2 | 3 | import zio.json._ 4 | 5 | final case class KinesisAnalyticsOutputDeliveryResponse(records: List[KinesisAnalyticsOutputDeliveryResponseRecord]) 6 | 7 | object KinesisAnalyticsOutputDeliveryResponse { 8 | implicit val encoder: JsonEncoder[KinesisAnalyticsOutputDeliveryResponse] = 9 | DeriveJsonEncoder.gen[KinesisAnalyticsOutputDeliveryResponse] 10 | 11 | } 12 | 13 | final case class KinesisAnalyticsOutputDeliveryResponseRecord( 14 | recordId: String, 15 | result: KinesisAnalyticsOutputDeliveryResponseRecordResult 16 | ) 17 | 18 | object KinesisAnalyticsOutputDeliveryResponseRecord { 19 | implicit val encoder: JsonEncoder[KinesisAnalyticsOutputDeliveryResponseRecord] = 20 | DeriveJsonEncoder.gen[KinesisAnalyticsOutputDeliveryResponseRecord] 21 | 22 | } 23 | 24 | sealed trait KinesisAnalyticsOutputDeliveryResponseRecordResult 25 | object KinesisAnalyticsOutputDeliveryResponseRecordResult { 26 | implicit val encoder: JsonEncoder[KinesisAnalyticsOutputDeliveryResponseRecordResult] = 27 | DeriveJsonEncoder.gen[KinesisAnalyticsOutputDeliveryResponseRecordResult] 28 | 29 | case object Ok extends KinesisAnalyticsOutputDeliveryResponseRecordResult 30 | case object DeliveryFailed extends KinesisAnalyticsOutputDeliveryResponseRecordResult 31 | } 32 | -------------------------------------------------------------------------------- /lambda-response/src/main/scala/zio/lambda/response/S3BatchResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.response 2 | 3 | import zio.json._ 4 | 5 | final case class S3BatchResponse() 6 | 7 | object S3BatchResponse { 8 | implicit val encoder: JsonEncoder[S3BatchResponse] = DeriveJsonEncoder.gen[S3BatchResponse] 9 | } 10 | -------------------------------------------------------------------------------- /lambda-response/src/main/scala/zio/lambda/response/SimpleIAMPolicyResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.response 2 | 3 | import zio.json._ 4 | 5 | final case class SimpleIAMPolicyResponse() 6 | 7 | object SimpleIAMPolicyResponse { 8 | implicit val encoder: JsonEncoder[SimpleIAMPolicyResponse] = DeriveJsonEncoder.gen[SimpleIAMPolicyResponse] 9 | } 10 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/ClientContext.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda 2 | 3 | import zio.json._ 4 | 5 | final case class ClientContext( 6 | client: Client, 7 | custom: Map[String, String], 8 | env: Map[String, String] 9 | ) 10 | 11 | object ClientContext { 12 | implicit val decoder: JsonDecoder[ClientContext] = DeriveJsonDecoder.gen[ClientContext] 13 | } 14 | 15 | final case class Client( 16 | installationId: String, 17 | appTitle: String, 18 | appVersionName: String, 19 | appVersionCode: String, 20 | appPackageName: String 21 | ) 22 | 23 | object Client { 24 | implicit val decoder: JsonDecoder[Client] = DeriveJsonDecoder.gen[Client] 25 | } 26 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/CognitoIdentity.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda 2 | 3 | import zio.json._ 4 | 5 | final case class CognitoIdentity(cognitoIdentityId: String, cognitoIdentityPoolId: String) 6 | 7 | object CognitoIdentity { 8 | implicit val decoder: JsonDecoder[CognitoIdentity] = DeriveJsonDecoder.gen[CognitoIdentity] 9 | } 10 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/Context.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda 2 | 3 | import zio.lambda.internal.InvocationRequest 4 | import zio.lambda.internal.LambdaEnvironment 5 | 6 | final case class Context( 7 | awsRequestId: String, 8 | logGroupName: String, 9 | logStreamName: String, 10 | functionName: String, 11 | functionVersion: String, 12 | invokedFunctionArn: String, 13 | remainingTimeInMillis: Long, 14 | memoryLimitInMB: Int, 15 | clientContext: Option[ClientContext], 16 | cognitoIdentity: Option[CognitoIdentity] 17 | ) 18 | 19 | object Context { 20 | private[lambda] def from(invocationRequest: InvocationRequest, environment: LambdaEnvironment): Context = 21 | Context( 22 | awsRequestId = invocationRequest.id, 23 | remainingTimeInMillis = invocationRequest.remainingTimeInMillis, 24 | clientContext = invocationRequest.clientContext, 25 | cognitoIdentity = invocationRequest.cognitoIdentity, 26 | invokedFunctionArn = invocationRequest.invokedFunctionArn, 27 | memoryLimitInMB = environment.memoryLimitInMB, 28 | logGroupName = environment.logGroupName, 29 | logStreamName = environment.logStreamName, 30 | functionName = environment.functionName, 31 | functionVersion = environment.functionVersion 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/ZLambda.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda 2 | 3 | import zio._ 4 | import zio.json._ 5 | import zio.lambda.internal.LambdaEnvironment 6 | import zio.lambda.internal.LoopProcessor 7 | import zio.lambda.internal.RuntimeApiLive 8 | 9 | @deprecated("Use ZLambdaRunner", "1.0.3") 10 | abstract class ZLambda[E: JsonDecoder, A: JsonEncoder] extends ZIOAppDefault { self => 11 | 12 | def apply(event: E, context: Context): Task[A] 13 | 14 | def applyJson(json: String, context: Context): Task[String] = 15 | JsonDecoder[E].decodeJson(json) match { 16 | case Left(errorMessage) => 17 | ZIO.fail(new Throwable(s"Error decoding json. Json=$json, Error$errorMessage")) 18 | 19 | case Right(event) => apply(event, context).map(_.toJson) 20 | } 21 | 22 | def run = 23 | LoopProcessor 24 | .loop(Right(self)) 25 | .provide( 26 | LambdaEnvironment.live, 27 | RuntimeApiLive.layer, 28 | LoopProcessor.live 29 | ) 30 | 31 | } 32 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/ZLambdaRunner.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda 2 | 3 | import zio.json.{JsonDecoder, JsonEncoder} 4 | import zio.json._ 5 | import zio._ 6 | import zio.lambda.internal.{LambdaEnvironment, LoopProcessor, RuntimeApiLive} 7 | 8 | object ZLambdaRunner { 9 | 10 | def serve[R, IN: JsonDecoder, OUT: JsonEncoder, ERR <: Throwable]( 11 | appFunction: (IN, Context) => ZIO[R, ERR, OUT] 12 | ): RIO[R, Unit] = 13 | LoopProcessor 14 | .loopZioApp(Right(defaultRaw[R, IN, OUT, ERR](_, _, appFunction))) 15 | .provideSomeLayer[R](ZLambdaRunner.default) 16 | 17 | def serveRaw[R](rawFunction: (String, Context) => ZIO[R, Throwable, String]): RIO[R, Unit] = 18 | LoopProcessor 19 | .loopZioApp(Right(rawFunction)) 20 | .provideSomeLayer[R](ZLambdaRunner.default) 21 | 22 | private def defaultRaw[R, IN: JsonDecoder, OUT: JsonEncoder, ERR <: Throwable]( 23 | json: String, 24 | context: Context, 25 | userFunction: (IN, Context) => ZIO[R, ERR, OUT] 26 | ): ZIO[R, Throwable, String] = 27 | JsonDecoder[IN].decodeJson(json) match { 28 | case Left(errorMessage) => 29 | ZIO.fail(new Throwable(s"Error decoding json. Json=$json, Error$errorMessage")) 30 | case Right(event) => 31 | userFunction(event, context).map(_.toJson) 32 | } 33 | 34 | private def default = 35 | LambdaEnvironment.live >>> (RuntimeApiLive.layer ++ LambdaEnvironment.live) >>> LoopProcessor.live 36 | 37 | } 38 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/CustomClassLoader.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | import zio.stream.ZStream 5 | 6 | import java.net.URLClassLoader 7 | import java.nio.file.Files 8 | import java.nio.file.Paths 9 | 10 | trait CustomClassLoader { 11 | def getClassLoader: Task[ClassLoader] 12 | } 13 | 14 | object CustomClassLoader { 15 | val live: URLayer[LambdaEnvironment, CustomClassLoader] = ZLayer.fromFunction { (environment: LambdaEnvironment) => 16 | new CustomClassLoader { 17 | override def getClassLoader: Task[ClassLoader] = 18 | ZStream 19 | .fromJavaStream(Files.list(Paths.get(environment.taskRoot))) 20 | .runCollect 21 | .map(stream => 22 | new URLClassLoader( 23 | stream 24 | .map(_.toUri().toURL()) 25 | .toArray 26 | ) 27 | ) 28 | } 29 | } 30 | 31 | def getClassLoader: ZIO[CustomClassLoader, Throwable, ClassLoader] = 32 | ZIO.serviceWithZIO(_.getClassLoader) 33 | } 34 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/InvocationError.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | final case class InvocationError(requestId: String, errorResponse: InvocationErrorResponse) 4 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/InvocationErrorResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio.json._ 4 | 5 | final case class InvocationErrorResponse( 6 | errorMessage: String, 7 | errorType: String, 8 | stackTrace: List[String] 9 | ) 10 | 11 | object InvocationErrorResponse { 12 | implicit val encoder: JsonEncoder[InvocationErrorResponse] = DeriveJsonEncoder.gen[InvocationErrorResponse] 13 | 14 | def fromThrowable(throwable: Throwable): InvocationErrorResponse = 15 | InvocationErrorResponse( 16 | throwable.getMessage(), 17 | throwable.getClass().getName(), 18 | throwable.getStackTrace().map(_.toString()).toList 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/InvocationRequest.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio.lambda.ClientContext 4 | import zio.lambda.CognitoIdentity 5 | import zio.json._ 6 | import zio.json.internal.FastStringReader 7 | 8 | /** 9 | * https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html 10 | * 11 | * @param requestId The request ID, which identifies the request that triggered the function invocation. 12 | * @param deadlineMs The date that the function times out in Unix time milliseconds. 13 | * @param invokedFunctionArn The ARN of the Lambda function, version, or alias that's specified in the invocation. 14 | * @param xrayTraceId The AWS X-Ray tracing header. 15 | * @param clientContext For invocations from the AWS Mobile SDK, data about the client application and device. 16 | * @param cognitoIdentity For invocations from the AWS Mobile SDK, data about the Amazon Cognito identity provider. 17 | * @param payload The payload from the invocation, which is a JSON document that contains event data from the function trigger. 18 | */ 19 | final case class InvocationRequest( 20 | id: String, 21 | remainingTimeInMillis: Long, 22 | invokedFunctionArn: String, 23 | xrayTraceId: Option[String], 24 | clientContext: Option[ClientContext], 25 | cognitoIdentity: Option[CognitoIdentity], 26 | payload: String 27 | ) 28 | 29 | object InvocationRequest { 30 | 31 | private[internal] def fromHttpResponse( 32 | headers: java.util.Map[String, java.util.List[String]], 33 | payload: String 34 | ): InvocationRequest = 35 | InvocationRequest( 36 | headers.get("Lambda-Runtime-Aws-Request-Id").get(0), 37 | headers.get("Lambda-Runtime-Deadline-Ms").get(0).toLong, 38 | headers.get("Lambda-Runtime-Invoked-Function-Arn").get(0), 39 | extractHeader("Lambda-Runtime-Trace-Id", headers), 40 | parseHeader[ClientContext]("Lambda-Runtime-Client-Context", headers), 41 | parseHeader[CognitoIdentity]("Lambda-Runtime-Cognito-Identity", headers), 42 | payload 43 | ) 44 | 45 | private def extractHeader( 46 | header: String, 47 | headers: java.util.Map[String, java.util.List[String]] 48 | ): Option[String] = { 49 | val values = headers.get(header) 50 | if (values != null && !values.isEmpty()) { 51 | Some(values.get(0)) 52 | } else None 53 | } 54 | 55 | private def parseHeader[A]( 56 | header: String, 57 | headers: java.util.Map[String, java.util.List[String]] 58 | )(implicit decoder: JsonDecoder[A]): Option[A] = 59 | extractHeader(header, headers).map(value => decoder.unsafeDecode(Nil, new FastStringReader(value))) 60 | } 61 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/InvocationResponse.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | final case class InvocationResponse(requestId: String, payload: String) 4 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/LambdaEnvironment.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | 5 | final case class LambdaEnvironment( 6 | runtimeApi: String, 7 | handler: String, 8 | taskRoot: String, 9 | memoryLimitInMB: Int, 10 | logGroupName: String, 11 | logStreamName: String, 12 | functionName: String, 13 | functionVersion: String 14 | ) 15 | 16 | object LambdaEnvironment { 17 | val live: TaskLayer[LambdaEnvironment] = 18 | ZLayer { 19 | for { 20 | runtimeApi <- ZIO.system 21 | .flatMap(_.env("AWS_LAMBDA_RUNTIME_API")) 22 | .someOrFail(new Throwable("AWS_LAMBDA_RUNTIME_API env variable not defined")) 23 | handler <- ZIO.system.flatMap(_.envOrElse("_HANDLER", "")) 24 | taskRoot <- ZIO.system.flatMap(_.envOrElse("LAMBDA_TASK_ROOT", "")) 25 | memoryLimitInMB <- ZIO.system.flatMap(_.envOrElse("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128")) 26 | logGroupName <- ZIO.system.flatMap(_.envOrElse("AWS_LAMBDA_LOG_GROUP_NAME", "")) 27 | logStreamName <- ZIO.system.flatMap(_.envOrElse("AWS_LAMBDA_LOG_STREAM_NAME", "")) 28 | functionName <- ZIO.system.flatMap(_.envOrElse("AWS_LAMBDA_FUNCTION_NAME", "")) 29 | functionVersion <- ZIO.system.flatMap(_.envOrElse("AWS_LAMBDA_FUNCTION_VERSION", "")) 30 | 31 | } yield LambdaEnvironment( 32 | runtimeApi, 33 | handler, 34 | taskRoot, 35 | memoryLimitInMB.toInt, 36 | logGroupName, 37 | logStreamName, 38 | functionName, 39 | functionVersion 40 | ) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/LambdaLoader.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | import zio.lambda.ZLambda 5 | 6 | trait LambdaLoader[T] { 7 | def loadLambda: UIO[Either[Throwable, T]] 8 | } 9 | 10 | object LambdaLoader { 11 | @deprecated("Use LambdaAppLoaderLive", "1.0.3") 12 | def loadLambda: URIO[LambdaLoader[ZLambda[_, _]], Either[Throwable, ZLambda[_, _]]] = 13 | ZIO.serviceWithZIO(_.loadLambda) 14 | 15 | def loadLambdaApp: URIO[LambdaLoader[ZIOAppDefault], Either[Throwable, ZIOAppDefault]] = 16 | ZIO.serviceWithZIO(_.loadLambda) 17 | } 18 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/LambdaLoaderLive.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | import zio.lambda.ZLambda 5 | 6 | @deprecated("Use LambdaAppLoaderLive", "1.0.3") 7 | final case class LambdaLoaderLive( 8 | customClassLoader: CustomClassLoader, 9 | environment: LambdaEnvironment 10 | ) extends LambdaLoaderLiveCommon[ZLambda[_, _]](customClassLoader, environment) 11 | 12 | final case class LambdaAppLoaderLive( 13 | customClassLoader: CustomClassLoader, 14 | environment: LambdaEnvironment 15 | ) extends LambdaLoaderLiveCommon[ZIOAppDefault](customClassLoader, environment) 16 | 17 | abstract class LambdaLoaderLiveCommon[T](customClassLoader: CustomClassLoader, environment: LambdaEnvironment) 18 | extends LambdaLoader[T] { 19 | 20 | override lazy val loadLambda: UIO[Either[Throwable, T]] = 21 | customClassLoader.getClassLoader 22 | .flatMap[Any, Throwable, T](classLoader => 23 | ZIO 24 | .attempt( 25 | Class 26 | .forName( 27 | environment.handler + "$", 28 | true, 29 | classLoader 30 | ) 31 | .getDeclaredField("MODULE$") 32 | .get(null) 33 | .asInstanceOf[T] 34 | ) 35 | .refineOrDie { case ex: ClassNotFoundException => ex } 36 | ) 37 | .either 38 | 39 | } 40 | 41 | @deprecated("Use LambdaAppLoaderLive", "1.0.3") 42 | object LambdaLoaderLive { 43 | val layer = 44 | ZLayer.fromFunction(LambdaLoaderLive.apply _) 45 | } 46 | 47 | object LambdaAppLoaderLive { 48 | val layer = 49 | ZLayer.fromFunction(LambdaAppLoaderLive.apply _) 50 | } 51 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/LoopProcessor.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | import zio.lambda.{Context, ZLambda} 5 | 6 | trait LoopProcessor { 7 | @deprecated("Use loopZioApp", "1.0.3") 8 | def loop(eitherZLambda: Either[Throwable, ZLambda[_, _]]): Task[Unit] 9 | def loopZioApp[R](rawFunction: Either[Throwable, (String, Context) => ZIO[R, Throwable, String]]): RIO[R, Unit] 10 | } 11 | 12 | object LoopProcessor { 13 | 14 | final case class Live(runtimeApi: RuntimeApi, environment: LambdaEnvironment) extends LoopProcessor { 15 | 16 | @deprecated("Use loopZioApp", "1.0.3") 17 | def loop(eitherZLambda: Either[Throwable, ZLambda[_, _]]): Task[Unit] = 18 | loopZioApp(eitherZLambda.map(_.applyJson)) 19 | 20 | def loopZioApp[R](rawFunction: Either[Throwable, (String, Context) => ZIO[R, Throwable, String]]): RIO[R, Unit] = 21 | rawFunction match { 22 | case Right(zLambda) => 23 | runtimeApi.getNextInvocation 24 | .flatMap[R, Throwable, Unit](request => 25 | zLambda(request.payload, Context.from(request, environment)) 26 | .foldZIO( 27 | throwable => 28 | runtimeApi.sendInvocationError( 29 | InvocationError(request.id, InvocationErrorResponse.fromThrowable(throwable)) 30 | ), 31 | payload => 32 | runtimeApi.sendInvocationResponse( 33 | InvocationResponse(request.id, payload) 34 | ) 35 | ) 36 | ) 37 | .forever 38 | 39 | case Left(throwable) => 40 | runtimeApi.getNextInvocation 41 | .flatMap[Any, Throwable, Unit](request => 42 | runtimeApi 43 | .sendInvocationError( 44 | InvocationError( 45 | request.id, 46 | InvocationErrorResponse.fromThrowable(throwable) 47 | ) 48 | ) 49 | ) 50 | } 51 | } 52 | 53 | @deprecated("Use loopZioApp", "1.0.3") 54 | def loop( 55 | eitherZLambda: Either[Throwable, ZLambda[_, _]] 56 | ): RIO[LoopProcessor, Unit] = 57 | ZIO.serviceWithZIO[LoopProcessor](_.loop(eitherZLambda)) 58 | 59 | def loopZioApp[R]( 60 | rawFunction: Either[Throwable, (String, Context) => ZIO[R, Throwable, String]] 61 | ): RIO[LoopProcessor & R, Unit] = 62 | ZIO.serviceWithZIO[LoopProcessor](_.loopZioApp(rawFunction)) 63 | 64 | val live: ZLayer[RuntimeApi with LambdaEnvironment, Throwable, LoopProcessor] = 65 | ZLayer { 66 | for { 67 | runtimeApi <- ZIO.service[RuntimeApi] 68 | environment <- ZIO.service[LambdaEnvironment] 69 | } yield Live(runtimeApi, environment) 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/RuntimeApi.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | 5 | trait RuntimeApi { 6 | def getNextInvocation: Task[InvocationRequest] 7 | def sendInvocationResponse(invocationResponse: InvocationResponse): Task[Unit] 8 | def sendInvocationError(invocationError: InvocationError): Task[Unit] 9 | def sendInitializationError(errorResponse: InvocationErrorResponse): Task[Unit] 10 | } 11 | 12 | object RuntimeApi { 13 | def getNextInvocation: RIO[RuntimeApi, InvocationRequest] = 14 | ZIO.serviceWithZIO(_.getNextInvocation) 15 | 16 | def sendInvocationResponse(invocationResponse: InvocationResponse): RIO[RuntimeApi, Unit] = 17 | ZIO.serviceWithZIO(_.sendInvocationResponse(invocationResponse)) 18 | 19 | def sendInvocationError(invocationError: InvocationError): RIO[RuntimeApi, Unit] = 20 | ZIO.serviceWithZIO(_.sendInvocationError(invocationError)) 21 | 22 | def sendInitializationError(errorResponse: InvocationErrorResponse): RIO[RuntimeApi, Unit] = 23 | ZIO.serviceWithZIO(_.sendInitializationError(errorResponse)) 24 | } 25 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/RuntimeApiLive.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | import zio.json._ 5 | 6 | import java.io.InputStream 7 | import java.net.HttpURLConnection 8 | import java.net.URL 9 | import java.io.ByteArrayOutputStream 10 | 11 | final case class RuntimeApiLive(environment: LambdaEnvironment) extends RuntimeApi { 12 | 13 | private val baseRuntimeUrl = s"http://${environment.runtimeApi}/2018-06-01/runtime" 14 | private val nextInvocationUrl = new URL(s"$baseRuntimeUrl/invocation/next") 15 | 16 | def getNextInvocation: Task[InvocationRequest] = { 17 | def readResponse(is: InputStream) = { 18 | val result = new ByteArrayOutputStream() 19 | val buffer = new Array[Byte](1024) 20 | var length = is.read(buffer) 21 | while (length != -1) { 22 | result.write(buffer, 0, length) 23 | length = is.read(buffer) 24 | } 25 | try is.close() // Reusing connection 26 | catch { case _: Throwable => () } 27 | 28 | result.toString("UTF-8") 29 | } 30 | 31 | ZIO.attempt { 32 | val con = nextInvocationUrl.openConnection().asInstanceOf[HttpURLConnection] 33 | con.setRequestMethod("GET") 34 | con.setConnectTimeout(0) 35 | con.setReadTimeout(0) 36 | val responseBody = readResponse(con.getInputStream()) 37 | InvocationRequest.fromHttpResponse(con.getHeaderFields(), responseBody) 38 | } 39 | } 40 | 41 | def sendInvocationResponse(invocationResponse: InvocationResponse): Task[Unit] = { 42 | val url = new URL(s"$baseRuntimeUrl/invocation/${invocationResponse.requestId}/response") 43 | val conn = url.openConnection().asInstanceOf[HttpURLConnection] 44 | conn.setRequestMethod("POST") 45 | conn.setRequestProperty("Content-Type", "application/json") 46 | val payload = invocationResponse.payload 47 | val payloadBytes = payload.getBytes("UTF-8") 48 | conn.setFixedLengthStreamingMode(payloadBytes.length) 49 | conn.setDoOutput(true) 50 | 51 | ZIO.attempt { 52 | val outputStream = conn.getOutputStream() 53 | outputStream.write(payloadBytes) 54 | try conn.getInputStream().close() 55 | catch { case _: Throwable => () } // Reusing connection 56 | } 57 | } 58 | 59 | def sendInvocationError(invocationError: InvocationError): Task[Unit] = 60 | postRequest( 61 | s"$baseRuntimeUrl/invocation/${invocationError.requestId}/error", 62 | invocationError.errorResponse 63 | ) 64 | 65 | def sendInitializationError(errorResponse: InvocationErrorResponse): Task[Unit] = 66 | postRequest( 67 | s"$baseRuntimeUrl/init/error", 68 | errorResponse, 69 | Map("Lambda-Runtime-Function-Error-Type" -> errorResponse.errorType) 70 | ) 71 | 72 | private def postRequest[A: JsonEncoder]( 73 | url: String, 74 | payload: A, 75 | headers: Map[String, String] = Map.empty 76 | ): Task[Unit] = { 77 | val conn = new URL(url).openConnection().asInstanceOf[HttpURLConnection] 78 | val body = payload.toJson 79 | conn.setRequestMethod("POST") 80 | conn.setRequestProperty("Content-Type", "application/json") 81 | val bodyBytes = body.getBytes("UTF-8") 82 | conn.setFixedLengthStreamingMode(bodyBytes.length) 83 | conn.setDoOutput(true) 84 | 85 | headers.foreach { case (header, value) => 86 | conn.setRequestProperty(header, value) 87 | } 88 | 89 | ZIO.attempt { 90 | val outputStream = conn.getOutputStream() 91 | outputStream.write(body.getBytes("UTF-8")) 92 | conn.getInputStream().close() 93 | } 94 | } 95 | } 96 | 97 | object RuntimeApiLive { 98 | val layer: ZLayer[LambdaEnvironment, Throwable, RuntimeApi] = 99 | ZLayer.fromFunction(RuntimeApiLive.apply _) 100 | 101 | } 102 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/ZLambdaAppReflective.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | 5 | /** 6 | * The main class to use ZIOAppDefault as a Layer 7 | * 8 | * https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html 9 | * https://docs.aws.amazon.com/lambda/latest/dg/configuration-layers.html 10 | */ 11 | object ZLambdaAppReflective extends ZIOAppDefault { self => 12 | 13 | def run = 14 | LambdaLoader.loadLambdaApp.flatMap { v => 15 | ZIO.fromEither(v).flatMap(_.run) 16 | }.tapError { 17 | case throwable: Throwable => 18 | RuntimeApi.sendInitializationError( 19 | InvocationErrorResponse.fromThrowable(throwable) 20 | ) 21 | case any => 22 | RuntimeApi.sendInitializationError( 23 | InvocationErrorResponse.fromThrowable(new IllegalStateException(any.toString)) 24 | ) 25 | }.provideSome[Scope with ZIOAppArgs]( 26 | LambdaEnvironment.live, 27 | CustomClassLoader.live, 28 | LambdaAppLoaderLive.layer, 29 | RuntimeApiLive.layer 30 | ) 31 | 32 | } 33 | -------------------------------------------------------------------------------- /lambda/src/main/scala/zio/lambda/internal/ZLambdaReflectiveAppOld.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | 5 | @deprecated("Use ZLambdaAppReflective", "1.0.3") 6 | object ZLambdaReflectiveAppOld extends ZIOAppDefault { 7 | 8 | def run = 9 | LambdaLoader.loadLambda 10 | .flatMap(LoopProcessor.loop(_).forever) 11 | .tapError(throwable => 12 | RuntimeApi.sendInitializationError( 13 | InvocationErrorResponse.fromThrowable(throwable) 14 | ) 15 | ) 16 | .provide( 17 | LambdaEnvironment.live, 18 | CustomClassLoader.live, 19 | LambdaLoaderLive.layer, 20 | LoopProcessor.live, 21 | RuntimeApiLive.layer 22 | ) 23 | 24 | } 25 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/InvocationErrorGen.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio.test._ 4 | 5 | object InvocationErrorGen { 6 | 7 | private val genInvocationErrorResponse = 8 | for { 9 | error <- Gen.string 10 | errorType <- Gen.string 11 | stackTrace <- Gen.listOf(Gen.string) 12 | } yield InvocationErrorResponse( 13 | error, 14 | errorType, 15 | stackTrace 16 | ) 17 | 18 | val gen: Gen[Sized, InvocationError] = 19 | for { 20 | requestId <- Gen.string 21 | invocationErrorResponse <- genInvocationErrorResponse 22 | } yield InvocationError( 23 | requestId, 24 | invocationErrorResponse 25 | ) 26 | 27 | } 28 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/InvocationRequestGen.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio.test._ 4 | import zio.lambda.ClientContext 5 | import zio.lambda.CognitoIdentity 6 | import zio.lambda.Client 7 | 8 | object InvocationRequestGen { 9 | 10 | private val genClient = 11 | for { 12 | installationId <- Gen.string 13 | appTitle <- Gen.string 14 | appVersionName <- Gen.string 15 | appVersionCode <- Gen.string 16 | appPackageName <- Gen.string 17 | } yield Client( 18 | installationId = installationId, 19 | appTitle = appTitle, 20 | appVersionName = appVersionName, 21 | appVersionCode = appVersionCode, 22 | appPackageName = appPackageName 23 | ) 24 | 25 | private val genClientContext = 26 | for { 27 | client <- genClient 28 | custom <- Gen.mapOf(Gen.string, Gen.string) 29 | env <- Gen.mapOf(Gen.string, Gen.string) 30 | } yield ClientContext( 31 | client = client, 32 | custom = custom, 33 | env = env 34 | ) 35 | 36 | private val genCognitoIdentity = 37 | for { 38 | cognitoIdentityId <- Gen.string 39 | cognitoIdentityPoolId <- Gen.string 40 | } yield CognitoIdentity( 41 | cognitoIdentityId = cognitoIdentityId, 42 | cognitoIdentityPoolId = cognitoIdentityPoolId 43 | ) 44 | 45 | val gen: Gen[Sized, InvocationRequest] = 46 | for { 47 | id <- Gen.string 48 | remainingTimeInMillis <- Gen.long 49 | invokedFunctionArn <- Gen.string 50 | xrayTraceId <- Gen.option(Gen.string) 51 | clientContext <- Gen.option(genClientContext) 52 | cognitoIdentity <- Gen.option(genCognitoIdentity) 53 | payload <- Gen.string 54 | } yield InvocationRequest( 55 | id = id, 56 | remainingTimeInMillis = remainingTimeInMillis, 57 | invokedFunctionArn = invokedFunctionArn, 58 | xrayTraceId = xrayTraceId, 59 | clientContext = clientContext, 60 | cognitoIdentity = cognitoIdentity, 61 | payload = payload 62 | ) 63 | 64 | } 65 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/InvocationRequestImplicits.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio.json._ 4 | import zio.lambda.ClientContext 5 | import zio.lambda.CognitoIdentity 6 | import zio.lambda.Client 7 | 8 | object InvocationRequestImplicits { 9 | implicit val clientEncoder: JsonEncoder[Client] = 10 | DeriveJsonEncoder.gen[Client] 11 | 12 | implicit val clientContextEncoder: JsonEncoder[ClientContext] = 13 | DeriveJsonEncoder.gen[ClientContext] 14 | 15 | implicit val cognitoIdentityEncoder: JsonEncoder[CognitoIdentity] = 16 | DeriveJsonEncoder.gen[CognitoIdentity] 17 | 18 | implicit val invocationRequestEncoder: JsonEncoder[InvocationRequest] = DeriveJsonEncoder.gen[InvocationRequest] 19 | } 20 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/InvocationRequestSpec.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio.json._ 4 | import zio.test.Assertion._ 5 | import zio.test._ 6 | 7 | import InvocationRequestImplicits._ 8 | 9 | object InvocationRequestSpec extends ZIOSpecDefault { 10 | 11 | override def spec = 12 | suite("InvocationRequest spec")( 13 | suite("fromHttpResponse")( 14 | test("should return InvocationRequest") { 15 | check(InvocationRequestGen.gen) { invocationRequest => 16 | val headers = new java.util.HashMap[String, java.util.List[String]]() 17 | headers.put("Lambda-Runtime-Aws-Request-Id", java.util.Collections.singletonList(invocationRequest.id)) 18 | headers.put( 19 | "Lambda-Runtime-Trace-Id", 20 | invocationRequest.xrayTraceId 21 | .map(java.util.Collections.singletonList[String]) 22 | .getOrElse(java.util.Collections.emptyList()) 23 | ) 24 | headers.put( 25 | "Lambda-Runtime-Invoked-Function-Arn", 26 | java.util.Collections.singletonList(invocationRequest.invokedFunctionArn) 27 | ) 28 | headers.put( 29 | "Lambda-Runtime-Deadline-Ms", 30 | java.util.Collections.singletonList(invocationRequest.remainingTimeInMillis.toString()) 31 | ) 32 | headers.put( 33 | "Lambda-Runtime-Client-Context", 34 | invocationRequest.clientContext match { 35 | case Some(value) => java.util.Collections.singletonList(value.toJson) 36 | case None => java.util.Collections.emptyList() 37 | } 38 | ) 39 | headers.put( 40 | "Lambda-Runtime-Cognito-Identity", 41 | invocationRequest.cognitoIdentity match { 42 | case Some(value) => java.util.Collections.singletonList(value.toJson) 43 | case None => java.util.Collections.emptyList() 44 | } 45 | ) 46 | 47 | assert( 48 | InvocationRequest.fromHttpResponse( 49 | headers, 50 | invocationRequest.payload 51 | ) 52 | )(equalTo(invocationRequest)) 53 | } 54 | } 55 | ) 56 | ) 57 | 58 | } 59 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/LambdaEnvironmentGen.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio.test._ 4 | 5 | object LambdaEnvironmentGen { 6 | 7 | val gen: Gen[Sized, LambdaEnvironment] = 8 | for { 9 | runtimeApi <- Gen.string(Gen.char) 10 | handler <- Gen.string 11 | taskRoot <- Gen.string 12 | memoryLimitInMB <- Gen.int 13 | logGroupName <- Gen.string 14 | logStreamName <- Gen.string 15 | functionName <- Gen.string 16 | functionVersion <- Gen.string 17 | } yield LambdaEnvironment( 18 | runtimeApi, 19 | handler, 20 | taskRoot, 21 | memoryLimitInMB, 22 | logGroupName, 23 | logStreamName, 24 | functionName, 25 | functionVersion 26 | ) 27 | } 28 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/LambdaLoaderLiveSpec.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | import zio.json._ 5 | import zio.lambda.Context 6 | import zio.test.Assertion._ 7 | import zio.test._ 8 | import zio.lambda.ClientContext 9 | import zio.lambda.Client 10 | import zio.lambda.CognitoIdentity 11 | 12 | object LambdaLoaderLiveSpec extends ZIOSpecDefault { 13 | 14 | override def spec = 15 | suite("LambdaLoaderLive spec")( 16 | test("should return an error if Function Handler is None") { 17 | val lambdaEnvironmentLayer = ZLayer.succeed( 18 | LambdaEnvironment("", "non_exists", "/opt", 128, "", "", "", "") 19 | ) 20 | 21 | LambdaLoader.loadLambdaApp 22 | .map(assert(_)(isLeft)) 23 | .provide( 24 | lambdaEnvironmentLayer, 25 | TestCustomClassLoader.test, 26 | LambdaAppLoaderLive.layer 27 | ) 28 | }, 29 | test("should load ZLambda") { 30 | val lambdaEnvironmentLayer = ZLayer.succeed( 31 | LambdaEnvironment( 32 | "", 33 | "zio.lambda.internal.SuccessZLambda", 34 | "", 35 | 0, 36 | "", 37 | "", 38 | "", 39 | "" 40 | ) 41 | ) 42 | 43 | val context = Context( 44 | "", 45 | "", 46 | "", 47 | "", 48 | "", 49 | "", 50 | 0, 51 | 1, 52 | Some(ClientContext(Client("", "", "", "", ""), Map.empty, Map.empty)), 53 | Some(CognitoIdentity("", "")) 54 | ) 55 | 56 | LambdaLoader.loadLambda.flatMap { 57 | case Right(zLambda) => 58 | zLambda.applyJson(CustomPayload("payload").toJson, context) 59 | case Left(error) => ZIO.fail(s"ZLambda not loaded. Error=$error") 60 | } 61 | .map(Function.const(assertCompletes)) 62 | .provide( 63 | lambdaEnvironmentLayer, 64 | TestCustomClassLoader.test, 65 | LambdaLoaderLive.layer 66 | ) 67 | } 68 | ) 69 | 70 | } 71 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/RuntimeApiLiveSpec.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | import zio.test.Assertion._ 5 | import zio.test._ 6 | 7 | import java.net.{ConnectException, ServerSocket} 8 | 9 | object RuntimeApiLiveSpec extends ZIOSpecDefault { 10 | 11 | val serverSocket = new ServerSocket(8085) 12 | 13 | override def spec = 14 | suite("RuntimeApiLiveSpec spec")( 15 | test("sendInvocationResponse should not throw any error when unicode string is provided") { 16 | 17 | check(Gen.string(Gen.unicodeChar)) { unicodeString => 18 | val env = LambdaEnvironment("localhost:8085", "", "", 0, "", "", "", "") 19 | val resp = InvocationResponse("id", unicodeString) 20 | val runtime = new RuntimeApiLive(env) 21 | for { 22 | _ <- ZIO.attempt { 23 | serverSocket.accept().close() 24 | }.fork 25 | res <- runtime.sendInvocationResponse(resp).retryWhile(_.isInstanceOf[ConnectException]) 26 | } yield assert(res)(isUnit) 27 | } 28 | 29 | } 30 | ) 31 | 32 | } 33 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/TestCustomClassLoader.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | 5 | object TestCustomClassLoader { 6 | lazy val test: ULayer[CustomClassLoader] = 7 | ZLayer.succeed(new CustomClassLoader { 8 | override lazy val getClassLoader: Task[ClassLoader] = 9 | ZIO.succeed( 10 | TestCustomClassLoader.getClass().getClassLoader() 11 | ) 12 | }) 13 | } 14 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/TestRuntimeApi.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | 5 | trait TestRuntimeApi { 6 | def addInvocationRequest(invocationRequest: InvocationRequest): Task[Unit] 7 | def getInvocationResponse(): Task[InvocationResponse] 8 | def getInvocationError(): Task[InvocationError] 9 | def getInitializationError(): Task[InvocationErrorResponse] 10 | } 11 | 12 | object TestRuntimeApi { 13 | 14 | final case class Test( 15 | invocationRequestQueue: Queue[InvocationRequest], 16 | invocationResponseQueue: Queue[InvocationResponse], 17 | invocationErrorQueue: Queue[InvocationError], 18 | initializationErrorQueue: Queue[InvocationErrorResponse] 19 | ) extends TestRuntimeApi 20 | with RuntimeApi { 21 | 22 | override def addInvocationRequest( 23 | invocationRequest: InvocationRequest 24 | ): Task[Unit] = 25 | invocationRequestQueue.offer(invocationRequest).unit 26 | 27 | override def getInvocationResponse(): Task[InvocationResponse] = 28 | invocationResponseQueue.take 29 | 30 | override def getInvocationError(): Task[InvocationError] = 31 | invocationErrorQueue.take 32 | 33 | override def getInitializationError(): Task[InvocationErrorResponse] = 34 | initializationErrorQueue.take 35 | 36 | override def getNextInvocation: Task[InvocationRequest] = 37 | invocationRequestQueue.take 38 | 39 | override def sendInvocationResponse(invocationResponse: InvocationResponse): Task[Unit] = 40 | invocationResponseQueue.offer(invocationResponse).unit 41 | 42 | override def sendInvocationError(invocationError: InvocationError): Task[Unit] = 43 | invocationErrorQueue.offer(invocationError).unit 44 | 45 | override def sendInitializationError(errorResponse: InvocationErrorResponse): Task[Unit] = 46 | initializationErrorQueue.offer(errorResponse).unit 47 | } 48 | 49 | val testLayer: ULayer[TestRuntimeApi with RuntimeApi] = 50 | ZLayer { 51 | for { 52 | invocationRequestQueue <- Queue.unbounded[InvocationRequest] 53 | invocationResponseQueue <- Queue.unbounded[InvocationResponse] 54 | invocationErrorQueue <- Queue.unbounded[InvocationError] 55 | initializationErrorQueue <- Queue.unbounded[InvocationErrorResponse] 56 | } yield Test( 57 | invocationRequestQueue, 58 | invocationResponseQueue, 59 | invocationErrorQueue, 60 | initializationErrorQueue 61 | ) 62 | } 63 | 64 | def addInvocationRequest(invocationRequest: InvocationRequest): RIO[TestRuntimeApi, Unit] = 65 | ZIO.serviceWithZIO(_.addInvocationRequest(invocationRequest)) 66 | 67 | def getInvocationResponse(): RIO[TestRuntimeApi, InvocationResponse] = 68 | ZIO.serviceWithZIO(_.getInvocationResponse()) 69 | 70 | def getInvocationError(): RIO[TestRuntimeApi, InvocationError] = 71 | ZIO.serviceWithZIO(_.getInvocationError()) 72 | 73 | } 74 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/TestZLambda.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | import zio.json._ 5 | import zio.lambda.ZLambda 6 | import zio.lambda.Context 7 | 8 | final case class CustomPayload(value: String) 9 | object CustomPayload { 10 | implicit val decoder: JsonDecoder[CustomPayload] = DeriveJsonDecoder.gen[CustomPayload] 11 | implicit val encoder: JsonEncoder[CustomPayload] = DeriveJsonEncoder.gen[CustomPayload] 12 | } 13 | 14 | final case class CustomResponse(value: String) 15 | object CustomResponse { 16 | implicit val encoder: JsonEncoder[CustomResponse] = DeriveJsonEncoder.gen[CustomResponse] 17 | } 18 | 19 | object SuccessZLambda extends ZLambda[CustomPayload, CustomResponse] { 20 | override def apply(event: CustomPayload, context: Context): Task[CustomResponse] = 21 | ZIO.succeed(CustomResponse(event.value)) 22 | } 23 | 24 | object ErrorZLambda extends ZLambda[CustomPayload, CustomResponse] { 25 | override def apply(event: CustomPayload, context: Context): Task[CustomResponse] = 26 | ZIO.fail(new Throwable("ZLambda error")) 27 | } 28 | -------------------------------------------------------------------------------- /lambda/src/test/scala/zio/lambda/internal/ZRuntimeSpec.scala: -------------------------------------------------------------------------------- 1 | package zio.lambda.internal 2 | 3 | import zio._ 4 | import zio.json._ 5 | import zio.test.Assertion._ 6 | import zio.test._ 7 | 8 | object ZRuntimeSpec extends ZIOSpecDefault { 9 | 10 | override def spec = suite("ZRuntimeLive spec")( 11 | test("should process invocation and send invocation response") { 12 | checkN(1)(InvocationRequestGen.gen.noShrink, LambdaEnvironmentGen.gen.noShrink) { 13 | (invocationRequest, lambdaEnvironment) => 14 | (for { 15 | _ <- LoopProcessor.loop(Right(SuccessZLambda)).fork 16 | 17 | _ <- TestRuntimeApi.addInvocationRequest( 18 | invocationRequest.copy( 19 | payload = CustomPayload(invocationRequest.payload).toJson 20 | ) 21 | ) 22 | 23 | invocationResponseSent <- TestRuntimeApi.getInvocationResponse() 24 | 25 | } yield assert(invocationResponseSent)( 26 | equalTo( 27 | InvocationResponse( 28 | invocationRequest.id, 29 | CustomResponse(invocationRequest.payload).toJson 30 | ) 31 | ) 32 | )).provide( 33 | TestRuntimeApi.testLayer, 34 | ZLayer.succeed(lambdaEnvironment), 35 | LoopProcessor.live 36 | ) 37 | } 38 | }, 39 | test("should send invocation error if ZLambda wasn't loaded successfully ") { 40 | check(InvocationRequestGen.gen, LambdaEnvironmentGen.gen) { (invocationRequest, lambdaEnvironment) => 41 | val loaderLambdaError = new Throwable("Error loading ZLambda") 42 | 43 | (for { 44 | _ <- LoopProcessor.loop(Left(loaderLambdaError)).fork 45 | 46 | _ <- TestRuntimeApi.addInvocationRequest( 47 | invocationRequest.copy( 48 | payload = CustomPayload(invocationRequest.payload).toJson 49 | ) 50 | ) 51 | 52 | invocationError <- TestRuntimeApi.getInvocationError() 53 | 54 | } yield assert(invocationError)( 55 | equalTo( 56 | InvocationError( 57 | invocationRequest.id, 58 | InvocationErrorResponse.fromThrowable(loaderLambdaError) 59 | ) 60 | ) 61 | )).provide( 62 | TestRuntimeApi.testLayer, 63 | ZLayer.succeed(lambdaEnvironment), 64 | LoopProcessor.live 65 | ) 66 | } 67 | }, 68 | test("should send invocation error if ZLambda fails") { 69 | check(InvocationRequestGen.gen, LambdaEnvironmentGen.gen) { (invocationRequest, lambdaEnvironment) => 70 | val loopProcessorLayer = 71 | (TestRuntimeApi.testLayer ++ 72 | ZLayer.succeed(lambdaEnvironment)) >>> LoopProcessor.live ++ TestRuntimeApi.testLayer 73 | 74 | (for { 75 | _ <- LoopProcessor.loop(Right(ErrorZLambda)).fork 76 | 77 | _ <- TestRuntimeApi.addInvocationRequest( 78 | invocationRequest.copy( 79 | payload = CustomPayload(invocationRequest.payload).toJson 80 | ) 81 | ) 82 | 83 | _ <- TestRuntimeApi.getInvocationError() 84 | 85 | } yield assertCompletes).provide(loopProcessorLayer) 86 | } 87 | } 88 | ) 89 | 90 | } 91 | -------------------------------------------------------------------------------- /project/BuildHelper.scala: -------------------------------------------------------------------------------- 1 | import sbt._ 2 | import sbt.Keys._ 3 | import sbtbuildinfo._ 4 | import BuildInfoKeys._ 5 | 6 | object BuildHelper { 7 | private val Scala212 = "2.12.19" 8 | private val Scala213 = "2.13.16" 9 | private val Scala3 = "3.3.5" 10 | private val KindProjectorVersion = "0.13.3" 11 | 12 | def buildInfoSettings(packageName: String) = 13 | Seq( 14 | buildInfoKeys := Seq[BuildInfoKey](name, version, scalaVersion, sbtVersion, isSnapshot), 15 | buildInfoPackage := packageName, 16 | buildInfoObject := "BuildInfo" 17 | ) 18 | 19 | def kindProjector(scalaVer: String) = 20 | if (scalaVer != Scala3) 21 | Seq( 22 | compilerPlugin("org.typelevel" %% "kind-projector" % KindProjectorVersion cross CrossVersion.full) 23 | ) 24 | else 25 | Seq.empty 26 | 27 | def stdSettings(prjName: String) = 28 | Seq( 29 | name := s"$prjName", 30 | crossScalaVersions := Seq(Scala212, Scala213, Scala3), 31 | ThisBuild / scalaVersion := Scala213, 32 | libraryDependencies ++= kindProjector(scalaVersion.value), 33 | incOptions ~= (_.withLogRecompileOnMacro(false)) 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version = 1.10.11 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.4") 2 | addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.7") 3 | addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.13.1") 4 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.11") 5 | addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.7.1") 6 | addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.18") 7 | addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.3") 8 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.3") 9 | addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6") 10 | addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.11.1") 11 | addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.4.4") 12 | addSbtPlugin("dev.zio" % "zio-sbt-website" % "0.3.9") 13 | 14 | resolvers += Resolver.sonatypeRepo("public") 15 | -------------------------------------------------------------------------------- /sbt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # A more capable sbt runner, coincidentally also called sbt. 4 | # Author: Paul Phillips 5 | # https://github.com/paulp/sbt-extras 6 | # 7 | # Generated from http://www.opensource.org/licenses/bsd-license.php 8 | # Copyright (c) 2011, Paul Phillips. All rights reserved. 9 | # 10 | # Redistribution and use in source and binary forms, with or without 11 | # modification, are permitted provided that the following conditions are 12 | # met: 13 | # 14 | # * Redistributions of source code must retain the above copyright 15 | # notice, this list of conditions and the following disclaimer. 16 | # * Redistributions in binary form must reproduce the above copyright 17 | # notice, this list of conditions and the following disclaimer in the 18 | # documentation and/or other materials provided with the distribution. 19 | # * Neither the name of the author nor the names of its contributors 20 | # may be used to endorse or promote products derived from this software 21 | # without specific prior written permission. 22 | # 23 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 | # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 27 | # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 29 | # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 | 35 | set -o pipefail 36 | 37 | declare -r sbt_release_version="1.5.4" 38 | declare -r sbt_unreleased_version="1.5.4" 39 | 40 | declare -r latest_213="2.13.5" 41 | declare -r latest_212="2.12.13" 42 | declare -r latest_211="2.11.12" 43 | declare -r latest_210="2.10.7" 44 | declare -r latest_29="2.9.3" 45 | declare -r latest_28="2.8.2" 46 | 47 | declare -r buildProps="project/build.properties" 48 | 49 | declare -r sbt_launch_ivy_release_repo="https://repo.typesafe.com/typesafe/ivy-releases" 50 | declare -r sbt_launch_ivy_snapshot_repo="https://repo.scala-sbt.org/scalasbt/ivy-snapshots" 51 | declare -r sbt_launch_mvn_release_repo="https://repo.scala-sbt.org/scalasbt/maven-releases" 52 | declare -r sbt_launch_mvn_snapshot_repo="https://repo.scala-sbt.org/scalasbt/maven-snapshots" 53 | 54 | declare -r default_jvm_opts_common="-Xms512m -Xss2m -XX:MaxInlineLevel=18" 55 | declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy -Dsbt.coursier.home=project/.coursier" 56 | 57 | declare sbt_jar sbt_dir sbt_create sbt_version sbt_script sbt_new 58 | declare sbt_explicit_version 59 | declare verbose noshare batch trace_level 60 | 61 | declare java_cmd="java" 62 | declare sbt_launch_dir="$HOME/.sbt/launchers" 63 | declare sbt_launch_repo 64 | 65 | # pull -J and -D options to give to java. 66 | declare -a java_args scalac_args sbt_commands residual_args 67 | 68 | # args to jvm/sbt via files or environment variables 69 | declare -a extra_jvm_opts extra_sbt_opts 70 | 71 | echoerr() { echo >&2 "$@"; } 72 | vlog() { [[ -n "$verbose" ]] && echoerr "$@"; } 73 | die() { 74 | echo "Aborting: $*" 75 | exit 1 76 | } 77 | 78 | setTrapExit() { 79 | # save stty and trap exit, to ensure echo is re-enabled if we are interrupted. 80 | SBT_STTY="$(stty -g 2>/dev/null)" 81 | export SBT_STTY 82 | 83 | # restore stty settings (echo in particular) 84 | onSbtRunnerExit() { 85 | [ -t 0 ] || return 86 | vlog "" 87 | vlog "restoring stty: $SBT_STTY" 88 | stty "$SBT_STTY" 89 | } 90 | 91 | vlog "saving stty: $SBT_STTY" 92 | trap onSbtRunnerExit EXIT 93 | } 94 | 95 | # this seems to cover the bases on OSX, and someone will 96 | # have to tell me about the others. 97 | get_script_path() { 98 | local path="$1" 99 | [[ -L "$path" ]] || { 100 | echo "$path" 101 | return 102 | } 103 | 104 | local -r target="$(readlink "$path")" 105 | if [[ "${target:0:1}" == "/" ]]; then 106 | echo "$target" 107 | else 108 | echo "${path%/*}/$target" 109 | fi 110 | } 111 | 112 | script_path="$(get_script_path "${BASH_SOURCE[0]}")" 113 | declare -r script_path 114 | script_name="${script_path##*/}" 115 | declare -r script_name 116 | 117 | init_default_option_file() { 118 | local overriding_var="${!1}" 119 | local default_file="$2" 120 | if [[ ! -r "$default_file" && "$overriding_var" =~ ^@(.*)$ ]]; then 121 | local envvar_file="${BASH_REMATCH[1]}" 122 | if [[ -r "$envvar_file" ]]; then 123 | default_file="$envvar_file" 124 | fi 125 | fi 126 | echo "$default_file" 127 | } 128 | 129 | sbt_opts_file="$(init_default_option_file SBT_OPTS .sbtopts)" 130 | sbtx_opts_file="$(init_default_option_file SBTX_OPTS .sbtxopts)" 131 | jvm_opts_file="$(init_default_option_file JVM_OPTS .jvmopts)" 132 | 133 | build_props_sbt() { 134 | [[ -r "$buildProps" ]] && 135 | grep '^sbt\.version' "$buildProps" | tr '=\r' ' ' | awk '{ print $2; }' 136 | } 137 | 138 | set_sbt_version() { 139 | sbt_version="${sbt_explicit_version:-$(build_props_sbt)}" 140 | [[ -n "$sbt_version" ]] || sbt_version=$sbt_release_version 141 | export sbt_version 142 | } 143 | 144 | url_base() { 145 | local version="$1" 146 | 147 | case "$version" in 148 | 0.7.*) echo "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/simple-build-tool" ;; 149 | 0.10.*) echo "$sbt_launch_ivy_release_repo" ;; 150 | 0.11.[12]) echo "$sbt_launch_ivy_release_repo" ;; 151 | 0.*-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) # ie "*-yyyymmdd-hhMMss" 152 | echo "$sbt_launch_ivy_snapshot_repo" ;; 153 | 0.*) echo "$sbt_launch_ivy_release_repo" ;; 154 | *-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]T[0-9][0-9][0-9][0-9][0-9][0-9]) # ie "*-yyyymmddThhMMss" 155 | echo "$sbt_launch_mvn_snapshot_repo" ;; 156 | *) echo "$sbt_launch_mvn_release_repo" ;; 157 | esac 158 | } 159 | 160 | make_url() { 161 | local version="$1" 162 | 163 | local base="${sbt_launch_repo:-$(url_base "$version")}" 164 | 165 | case "$version" in 166 | 0.7.*) echo "$base/sbt-launch-0.7.7.jar" ;; 167 | 0.10.*) echo "$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;; 168 | 0.11.[12]) echo "$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;; 169 | 0.*) echo "$base/org.scala-sbt/sbt-launch/$version/sbt-launch.jar" ;; 170 | *) echo "$base/org/scala-sbt/sbt-launch/$version/sbt-launch-${version}.jar" ;; 171 | esac 172 | } 173 | 174 | addJava() { 175 | vlog "[addJava] arg = '$1'" 176 | java_args+=("$1") 177 | } 178 | addSbt() { 179 | vlog "[addSbt] arg = '$1'" 180 | sbt_commands+=("$1") 181 | } 182 | addScalac() { 183 | vlog "[addScalac] arg = '$1'" 184 | scalac_args+=("$1") 185 | } 186 | addResidual() { 187 | vlog "[residual] arg = '$1'" 188 | residual_args+=("$1") 189 | } 190 | 191 | addResolver() { addSbt "set resolvers += $1"; } 192 | 193 | addDebugger() { addJava "-Xdebug" && addJava "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1"; } 194 | 195 | setThisBuild() { 196 | vlog "[addBuild] args = '$*'" 197 | local key="$1" && shift 198 | addSbt "set $key in ThisBuild := $*" 199 | } 200 | setScalaVersion() { 201 | [[ "$1" == *"-SNAPSHOT" ]] && addResolver 'Resolver.sonatypeRepo("snapshots")' 202 | addSbt "++ $1" 203 | } 204 | setJavaHome() { 205 | java_cmd="$1/bin/java" 206 | setThisBuild javaHome "_root_.scala.Some(file(\"$1\"))" 207 | export JAVA_HOME="$1" 208 | export JDK_HOME="$1" 209 | export PATH="$JAVA_HOME/bin:$PATH" 210 | } 211 | 212 | getJavaVersion() { 213 | local -r str=$("$1" -version 2>&1 | grep -E -e '(java|openjdk) version' | awk '{ print $3 }' | tr -d '"') 214 | 215 | # java -version on java8 says 1.8.x 216 | # but on 9 and 10 it's 9.x.y and 10.x.y. 217 | if [[ "$str" =~ ^1\.([0-9]+)(\..*)?$ ]]; then 218 | echo "${BASH_REMATCH[1]}" 219 | elif [[ "$str" =~ ^([0-9]+)(\..*)?$ ]]; then 220 | echo "${BASH_REMATCH[1]}" 221 | elif [[ -n "$str" ]]; then 222 | echoerr "Can't parse java version from: $str" 223 | fi 224 | } 225 | 226 | checkJava() { 227 | # Warn if there is a Java version mismatch between PATH and JAVA_HOME/JDK_HOME 228 | 229 | [[ -n "$JAVA_HOME" && -e "$JAVA_HOME/bin/java" ]] && java="$JAVA_HOME/bin/java" 230 | [[ -n "$JDK_HOME" && -e "$JDK_HOME/lib/tools.jar" ]] && java="$JDK_HOME/bin/java" 231 | 232 | if [[ -n "$java" ]]; then 233 | pathJavaVersion=$(getJavaVersion java) 234 | homeJavaVersion=$(getJavaVersion "$java") 235 | if [[ "$pathJavaVersion" != "$homeJavaVersion" ]]; then 236 | echoerr "Warning: Java version mismatch between PATH and JAVA_HOME/JDK_HOME, sbt will use the one in PATH" 237 | echoerr " Either: fix your PATH, remove JAVA_HOME/JDK_HOME or use -java-home" 238 | echoerr " java version from PATH: $pathJavaVersion" 239 | echoerr " java version from JAVA_HOME/JDK_HOME: $homeJavaVersion" 240 | fi 241 | fi 242 | } 243 | 244 | java_version() { 245 | local -r version=$(getJavaVersion "$java_cmd") 246 | vlog "Detected Java version: $version" 247 | echo "$version" 248 | } 249 | 250 | # MaxPermSize critical on pre-8 JVMs but incurs noisy warning on 8+ 251 | default_jvm_opts() { 252 | local -r v="$(java_version)" 253 | if [[ $v -ge 10 ]]; then 254 | echo "$default_jvm_opts_common -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler" 255 | elif [[ $v -ge 8 ]]; then 256 | echo "$default_jvm_opts_common" 257 | else 258 | echo "-XX:MaxPermSize=384m $default_jvm_opts_common" 259 | fi 260 | } 261 | 262 | execRunner() { 263 | # print the arguments one to a line, quoting any containing spaces 264 | vlog "# Executing command line:" && { 265 | for arg; do 266 | if [[ -n "$arg" ]]; then 267 | if printf "%s\n" "$arg" | grep -q ' '; then 268 | printf >&2 "\"%s\"\n" "$arg" 269 | else 270 | printf >&2 "%s\n" "$arg" 271 | fi 272 | fi 273 | done 274 | vlog "" 275 | } 276 | 277 | setTrapExit 278 | 279 | if [[ -n "$batch" ]]; then 280 | "$@" /dev/null 2>&1; then 302 | curl --fail --silent --location "$url" --output "$jar" 303 | elif command -v wget >/dev/null 2>&1; then 304 | wget -q -O "$jar" "$url" 305 | fi 306 | } && [[ -r "$jar" ]] 307 | } 308 | 309 | acquire_sbt_jar() { 310 | { 311 | sbt_jar="$(jar_file "$sbt_version")" 312 | [[ -r "$sbt_jar" ]] 313 | } || { 314 | sbt_jar="$HOME/.ivy2/local/org.scala-sbt/sbt-launch/$sbt_version/jars/sbt-launch.jar" 315 | [[ -r "$sbt_jar" ]] 316 | } || { 317 | sbt_jar="$(jar_file "$sbt_version")" 318 | jar_url="$(make_url "$sbt_version")" 319 | 320 | echoerr "Downloading sbt launcher for ${sbt_version}:" 321 | echoerr " From ${jar_url}" 322 | echoerr " To ${sbt_jar}" 323 | 324 | download_url "${jar_url}" "${sbt_jar}" 325 | 326 | case "${sbt_version}" in 327 | 0.*) 328 | vlog "SBT versions < 1.0 do not have published MD5 checksums, skipping check" 329 | echo "" 330 | ;; 331 | *) verify_sbt_jar "${sbt_jar}" ;; 332 | esac 333 | } 334 | } 335 | 336 | verify_sbt_jar() { 337 | local jar="${1}" 338 | local md5="${jar}.md5" 339 | md5url="$(make_url "${sbt_version}").md5" 340 | 341 | echoerr "Downloading sbt launcher ${sbt_version} md5 hash:" 342 | echoerr " From ${md5url}" 343 | echoerr " To ${md5}" 344 | 345 | download_url "${md5url}" "${md5}" >/dev/null 2>&1 346 | 347 | if command -v md5sum >/dev/null 2>&1; then 348 | if echo "$(cat "${md5}") ${jar}" | md5sum -c -; then 349 | rm -rf "${md5}" 350 | return 0 351 | else 352 | echoerr "Checksum does not match" 353 | return 1 354 | fi 355 | elif command -v md5 >/dev/null 2>&1; then 356 | if [ "$(md5 -q "${jar}")" == "$(cat "${md5}")" ]; then 357 | rm -rf "${md5}" 358 | return 0 359 | else 360 | echoerr "Checksum does not match" 361 | return 1 362 | fi 363 | elif command -v openssl >/dev/null 2>&1; then 364 | if [ "$(openssl md5 -r "${jar}" | awk '{print $1}')" == "$(cat "${md5}")" ]; then 365 | rm -rf "${md5}" 366 | return 0 367 | else 368 | echoerr "Checksum does not match" 369 | return 1 370 | fi 371 | else 372 | echoerr "Could not find an MD5 command" 373 | return 1 374 | fi 375 | } 376 | 377 | usage() { 378 | set_sbt_version 379 | cat < display stack traces with a max of frames (default: -1, traces suppressed) 392 | -debug-inc enable debugging log for the incremental compiler 393 | -no-colors disable ANSI color codes 394 | -sbt-create start sbt even if current directory contains no sbt project 395 | -sbt-dir path to global settings/plugins directory (default: ~/.sbt/) 396 | -sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11+) 397 | -ivy path to local Ivy repository (default: ~/.ivy2) 398 | -no-share use all local caches; no sharing 399 | -offline put sbt in offline mode 400 | -jvm-debug Turn on JVM debugging, open at the given port. 401 | -batch Disable interactive mode 402 | -prompt Set the sbt prompt; in expr, 's' is the State and 'e' is Extracted 403 | -script Run the specified file as a scala script 404 | 405 | # sbt version (default: sbt.version from $buildProps if present, otherwise $sbt_release_version) 406 | -sbt-version use the specified version of sbt (default: $sbt_release_version) 407 | -sbt-force-latest force the use of the latest release of sbt: $sbt_release_version 408 | -sbt-dev use the latest pre-release version of sbt: $sbt_unreleased_version 409 | -sbt-jar use the specified jar as the sbt launcher 410 | -sbt-launch-dir directory to hold sbt launchers (default: $sbt_launch_dir) 411 | -sbt-launch-repo repo url for downloading sbt launcher jar (default: $(url_base "$sbt_version")) 412 | 413 | # scala version (default: as chosen by sbt) 414 | -28 use $latest_28 415 | -29 use $latest_29 416 | -210 use $latest_210 417 | -211 use $latest_211 418 | -212 use $latest_212 419 | -213 use $latest_213 420 | -scala-home use the scala build at the specified directory 421 | -scala-version use the specified version of scala 422 | -binary-version use the specified scala version when searching for dependencies 423 | 424 | # java version (default: java from PATH, currently $(java -version 2>&1 | grep version)) 425 | -java-home alternate JAVA_HOME 426 | 427 | # passing options to the jvm - note it does NOT use JAVA_OPTS due to pollution 428 | # The default set is used if JVM_OPTS is unset and no -jvm-opts file is found 429 | $(default_jvm_opts) 430 | JVM_OPTS environment variable holding either the jvm args directly, or 431 | the reference to a file containing jvm args if given path is prepended by '@' (e.g. '@/etc/jvmopts') 432 | Note: "@"-file is overridden by local '.jvmopts' or '-jvm-opts' argument. 433 | -jvm-opts file containing jvm args (if not given, .jvmopts in project root is used if present) 434 | -Dkey=val pass -Dkey=val directly to the jvm 435 | -J-X pass option -X directly to the jvm (-J is stripped) 436 | 437 | # passing options to sbt, OR to this runner 438 | SBT_OPTS environment variable holding either the sbt args directly, or 439 | the reference to a file containing sbt args if given path is prepended by '@' (e.g. '@/etc/sbtopts') 440 | Note: "@"-file is overridden by local '.sbtopts' or '-sbt-opts' argument. 441 | -sbt-opts file containing sbt args (if not given, .sbtopts in project root is used if present) 442 | -S-X add -X to sbt's scalacOptions (-S is stripped) 443 | 444 | # passing options exclusively to this runner 445 | SBTX_OPTS environment variable holding either the sbt-extras args directly, or 446 | the reference to a file containing sbt-extras args if given path is prepended by '@' (e.g. '@/etc/sbtxopts') 447 | Note: "@"-file is overridden by local '.sbtxopts' or '-sbtx-opts' argument. 448 | -sbtx-opts file containing sbt-extras args (if not given, .sbtxopts in project root is used if present) 449 | EOM 450 | exit 0 451 | } 452 | 453 | process_args() { 454 | require_arg() { 455 | local type="$1" 456 | local opt="$2" 457 | local arg="$3" 458 | 459 | if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then 460 | die "$opt requires <$type> argument" 461 | fi 462 | } 463 | while [[ $# -gt 0 ]]; do 464 | case "$1" in 465 | -h | -help) usage ;; 466 | -v) verbose=true && shift ;; 467 | -d) addSbt "--debug" && shift ;; 468 | -w) addSbt "--warn" && shift ;; 469 | -q) addSbt "--error" && shift ;; 470 | -x) shift ;; # currently unused 471 | -trace) require_arg integer "$1" "$2" && trace_level="$2" && shift 2 ;; 472 | -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;; 473 | 474 | -no-colors) addJava "-Dsbt.log.noformat=true" && shift ;; 475 | -sbt-create) sbt_create=true && shift ;; 476 | -sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;; 477 | -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;; 478 | -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;; 479 | -no-share) noshare=true && shift ;; 480 | -offline) addSbt "set offline in Global := true" && shift ;; 481 | -jvm-debug) require_arg port "$1" "$2" && addDebugger "$2" && shift 2 ;; 482 | -batch) batch=true && shift ;; 483 | -prompt) require_arg "expr" "$1" "$2" && setThisBuild shellPrompt "(s => { val e = Project.extract(s) ; $2 })" && shift 2 ;; 484 | -script) require_arg file "$1" "$2" && sbt_script="$2" && addJava "-Dsbt.main.class=sbt.ScriptMain" && shift 2 ;; 485 | 486 | -sbt-version) require_arg version "$1" "$2" && sbt_explicit_version="$2" && shift 2 ;; 487 | -sbt-force-latest) sbt_explicit_version="$sbt_release_version" && shift ;; 488 | -sbt-dev) sbt_explicit_version="$sbt_unreleased_version" && shift ;; 489 | -sbt-jar) require_arg path "$1" "$2" && sbt_jar="$2" && shift 2 ;; 490 | -sbt-launch-dir) require_arg path "$1" "$2" && sbt_launch_dir="$2" && shift 2 ;; 491 | -sbt-launch-repo) require_arg path "$1" "$2" && sbt_launch_repo="$2" && shift 2 ;; 492 | 493 | -28) setScalaVersion "$latest_28" && shift ;; 494 | -29) setScalaVersion "$latest_29" && shift ;; 495 | -210) setScalaVersion "$latest_210" && shift ;; 496 | -211) setScalaVersion "$latest_211" && shift ;; 497 | -212) setScalaVersion "$latest_212" && shift ;; 498 | -213) setScalaVersion "$latest_213" && shift ;; 499 | 500 | -scala-version) require_arg version "$1" "$2" && setScalaVersion "$2" && shift 2 ;; 501 | -binary-version) require_arg version "$1" "$2" && setThisBuild scalaBinaryVersion "\"$2\"" && shift 2 ;; 502 | -scala-home) require_arg path "$1" "$2" && setThisBuild scalaHome "_root_.scala.Some(file(\"$2\"))" && shift 2 ;; 503 | -java-home) require_arg path "$1" "$2" && setJavaHome "$2" && shift 2 ;; 504 | -sbt-opts) require_arg path "$1" "$2" && sbt_opts_file="$2" && shift 2 ;; 505 | -sbtx-opts) require_arg path "$1" "$2" && sbtx_opts_file="$2" && shift 2 ;; 506 | -jvm-opts) require_arg path "$1" "$2" && jvm_opts_file="$2" && shift 2 ;; 507 | 508 | -D*) addJava "$1" && shift ;; 509 | -J*) addJava "${1:2}" && shift ;; 510 | -S*) addScalac "${1:2}" && shift ;; 511 | 512 | new) sbt_new=true && : ${sbt_explicit_version:=$sbt_release_version} && addResidual "$1" && shift ;; 513 | 514 | *) addResidual "$1" && shift ;; 515 | esac 516 | done 517 | } 518 | 519 | # process the direct command line arguments 520 | process_args "$@" 521 | 522 | # skip #-styled comments and blank lines 523 | readConfigFile() { 524 | local end=false 525 | until $end; do 526 | read -r || end=true 527 | [[ $REPLY =~ ^# ]] || [[ -z $REPLY ]] || echo "$REPLY" 528 | done <"$1" 529 | } 530 | 531 | # if there are file/environment sbt_opts, process again so we 532 | # can supply args to this runner 533 | if [[ -r "$sbt_opts_file" ]]; then 534 | vlog "Using sbt options defined in file $sbt_opts_file" 535 | while read -r opt; do extra_sbt_opts+=("$opt"); done < <(readConfigFile "$sbt_opts_file") 536 | elif [[ -n "$SBT_OPTS" && ! ("$SBT_OPTS" =~ ^@.*) ]]; then 537 | vlog "Using sbt options defined in variable \$SBT_OPTS" 538 | IFS=" " read -r -a extra_sbt_opts <<<"$SBT_OPTS" 539 | else 540 | vlog "No extra sbt options have been defined" 541 | fi 542 | 543 | # if there are file/environment sbtx_opts, process again so we 544 | # can supply args to this runner 545 | if [[ -r "$sbtx_opts_file" ]]; then 546 | vlog "Using sbt options defined in file $sbtx_opts_file" 547 | while read -r opt; do extra_sbt_opts+=("$opt"); done < <(readConfigFile "$sbtx_opts_file") 548 | elif [[ -n "$SBTX_OPTS" && ! ("$SBTX_OPTS" =~ ^@.*) ]]; then 549 | vlog "Using sbt options defined in variable \$SBTX_OPTS" 550 | IFS=" " read -r -a extra_sbt_opts <<<"$SBTX_OPTS" 551 | else 552 | vlog "No extra sbt options have been defined" 553 | fi 554 | 555 | [[ -n "${extra_sbt_opts[*]}" ]] && process_args "${extra_sbt_opts[@]}" 556 | 557 | # reset "$@" to the residual args 558 | set -- "${residual_args[@]}" 559 | argumentCount=$# 560 | 561 | # set sbt version 562 | set_sbt_version 563 | 564 | checkJava 565 | 566 | # only exists in 0.12+ 567 | setTraceLevel() { 568 | case "$sbt_version" in 569 | "0.7."* | "0.10."* | "0.11."*) echoerr "Cannot set trace level in sbt version $sbt_version" ;; 570 | *) setThisBuild traceLevel "$trace_level" ;; 571 | esac 572 | } 573 | 574 | # set scalacOptions if we were given any -S opts 575 | [[ ${#scalac_args[@]} -eq 0 ]] || addSbt "set scalacOptions in ThisBuild += \"${scalac_args[*]}\"" 576 | 577 | [[ -n "$sbt_explicit_version" && -z "$sbt_new" ]] && addJava "-Dsbt.version=$sbt_explicit_version" 578 | vlog "Detected sbt version $sbt_version" 579 | 580 | if [[ -n "$sbt_script" ]]; then 581 | residual_args=("$sbt_script" "${residual_args[@]}") 582 | else 583 | # no args - alert them there's stuff in here 584 | ((argumentCount > 0)) || { 585 | vlog "Starting $script_name: invoke with -help for other options" 586 | residual_args=(shell) 587 | } 588 | fi 589 | 590 | # verify this is an sbt dir, -create was given or user attempts to run a scala script 591 | [[ -r ./build.sbt || -d ./project || -n "$sbt_create" || -n "$sbt_script" || -n "$sbt_new" ]] || { 592 | cat <