├── .editorconfig ├── .gcloudignore ├── .github └── workflows │ ├── conformance.yml │ ├── lint.yml │ └── unit.yml ├── .gitignore ├── .gitmodules ├── .kokoro ├── autorelease.bat ├── autorelease.sh └── populatesecrets.sh ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASING.md ├── build-release.sh ├── build.sh ├── create-release-commit.sh ├── docs ├── README.md ├── customization.md ├── deployment.md ├── examples.md ├── history.md ├── launch-settings.md ├── packages.md └── testing.md ├── examples ├── .gcloudignore ├── Directory.Build.props ├── Directory.Build.targets ├── OpenFunction.Examples.AdvancedDependencyInjection │ ├── Function.cs │ └── OpenFunction.Examples.AdvancedDependencyInjection.csproj ├── OpenFunction.Examples.Configuration │ ├── Function.cs │ ├── OpenFunction.Examples.Configuration.csproj │ ├── appsettings.Development.json │ ├── appsettings.Production.json │ └── appsettings.json ├── OpenFunction.Examples.CustomConfiguration │ ├── Function.cs │ └── OpenFunction.Examples.CustomConfiguration.csproj ├── OpenFunction.Examples.CustomEventDataFunction │ ├── Function.cs │ └── OpenFunction.Examples.CustomEventDataFunction.csproj ├── OpenFunction.Examples.FSharpEventFunction │ ├── Function.fs │ └── OpenFunction.Examples.FSharpEventFunction.fsproj ├── OpenFunction.Examples.FSharpHttpFunction │ ├── Function.fs │ └── OpenFunction.Examples.FSharpHttpFunction.fsproj ├── OpenFunction.Examples.FSharpUntypedEventFunction │ ├── Function.fs │ └── OpenFunction.Examples.FSharpUntypedEventFunction.fsproj ├── OpenFunction.Examples.IntegrationTests │ ├── AdvancedDependencyInjectionTest.cs │ ├── OpenFunction.Examples.IntegrationTests.csproj │ ├── SimpleDependencyInjectionTest.cs │ ├── SimpleDependencyInjectionUnitTest.cs │ ├── SimpleHttpFunctionTest.cs │ ├── SimpleHttpFunctionTest_WithFunctionTestBase.cs │ ├── SimpleHttpFunctionTest_WithTestServerFixture.cs │ ├── SimpleHttpFunctionTest_WithTestServerInCtor.cs │ ├── SimpleHttpFunctionTest_WithTestServerInTest.cs │ └── TestableDependenciesTest.cs ├── OpenFunction.Examples.Middleware │ ├── Function.cs │ └── OpenFunction.Examples.Middleware.csproj ├── OpenFunction.Examples.MultiProjectDependency │ ├── BusinessLogic.cs │ └── OpenFunction.Examples.MultiProjectDependency.csproj ├── OpenFunction.Examples.MultiProjectFunction │ ├── Function.cs │ └── OpenFunction.Examples.MultiProjectFunction.csproj ├── OpenFunction.Examples.SimpleDependencyInjection │ ├── Function.cs │ └── OpenFunction.Examples.SimpleDependencyInjection.csproj ├── OpenFunction.Examples.SimpleEventFunction │ ├── Function.cs │ └── OpenFunction.Examples.SimpleEventFunction.csproj ├── OpenFunction.Examples.SimpleHttpFunction │ ├── Function.cs │ └── OpenFunction.Examples.SimpleHttpFunction.csproj ├── OpenFunction.Examples.SimpleUntypedEventFunction │ ├── Function.cs │ └── OpenFunction.Examples.SimpleUntypedEventFunction.csproj ├── OpenFunction.Examples.StorageImageAnnotator │ ├── Function.cs │ ├── OpenFunction.Examples.StorageImageAnnotator.csproj │ └── README.md ├── OpenFunction.Examples.TestableDependencies │ ├── Function.cs │ └── OpenFunction.Examples.TestableDependencies.csproj ├── OpenFunction.Examples.TimeZoneConverter │ ├── ConversionResult.cs │ ├── ConversionType.cs │ ├── Function.cs │ ├── OpenFunction.Examples.TimeZoneConverter.csproj │ └── README.md ├── OpenFunction.Examples.VbEventFunction │ ├── CloudFunction.vb │ └── OpenFunction.Examples.VbEventFunction.vbproj ├── OpenFunction.Examples.VbHttpFunction │ ├── CloudFunction.vb │ └── OpenFunction.Examples.VbHttpFunction.vbproj ├── OpenFunction.Examples.VbUntypedEventFunction │ ├── CloudFunction.vb │ └── OpenFunction.Examples.VbUntypedEventFunction.vbproj ├── OpenFunction.Examples.sln ├── README.md ├── copyright.txt └── generate.sh ├── global.json ├── install-local-templates.sh ├── run-conformance-tests.sh ├── src ├── CommonProperties.xml ├── Directory.Build.targets ├── NuGetIcon.png ├── OpenFunction.ConformanceTests │ ├── HttpFunction.cs │ ├── OpenFunction.ConformanceTests.csproj │ └── UntypedCloudEventFunction.cs ├── OpenFunction.Framework.Tests │ ├── CloudEventAdapterTDataTest.cs │ ├── CloudEventAdapterTest.cs │ ├── GcfEvents │ │ ├── EventDeserializationTest.cs │ │ ├── GcfConvertersTest.cs │ │ ├── GcfEventResources.cs │ │ ├── emulator_pubsub.json │ │ ├── firebase-analytics-no-app-id.json │ │ ├── firebase-analytics-no-event-name.json │ │ ├── firebase-analytics.json │ │ ├── firebase-auth1.json │ │ ├── firebase-auth2.json │ │ ├── firebase-db1.json │ │ ├── firebase-db2.json │ │ ├── firebase-db3.json │ │ ├── firebase-db4.json │ │ ├── firebase-db5.json │ │ ├── firebase-db6.json │ │ ├── firebase-db7.json │ │ ├── firebase-db8.json │ │ ├── firebase-dbdelete1.json │ │ ├── firebase-dbdelete2.json │ │ ├── firebase-remote-config.json │ │ ├── firestore_complex.json │ │ ├── firestore_simple.json │ │ ├── legacy_pubsub.json │ │ ├── legacy_storage_change.json │ │ ├── pubsub_binary.json │ │ ├── pubsub_text.json │ │ ├── pubsub_text_microsecond_precision.json │ │ ├── raw_pubsub.json │ │ └── storage.json │ ├── OpenFunction.Framework.Tests.csproj │ └── TestResourceHelper.cs ├── OpenFunction.Framework │ ├── AssemblyInfo.cs │ ├── CloudEventAdapter.cs │ ├── CloudEventAdapterTData.cs │ ├── GcfEvents │ │ ├── Context.cs │ │ ├── GcfConverters.cs │ │ ├── Request.cs │ │ └── Resource.cs │ ├── ICloudEventFunction.cs │ ├── IHttpFunction.cs │ ├── OpenFunction.Framework.csproj │ └── Preconditions.cs ├── OpenFunction.Hosting.Tests │ ├── ApplicationConfigurationTest.cs │ ├── CommandLineArgumentsConfigurationTest.cs │ ├── CustomCloudEventDataTestBase.cs │ ├── FunctionTargetTest.cs │ ├── FunctionsEnvironmentVariablesConfigurationSourceTest.cs │ ├── FunctionsStartupAttributeTest.cs │ ├── FunctionsStartupTest.cs │ ├── Logging │ │ ├── JsonConsoleLoggerTest.cs │ │ └── SimpleConsoleLoggerTest.cs │ └── OpenFunction.Hosting.Tests.csproj ├── OpenFunction.Hosting │ ├── AssemblyInfo.cs │ ├── EntryPoint.cs │ ├── Extensions │ │ ├── ApplicationBuilderExtensions.cs │ │ ├── FunctionsFrameworkConfigurationExtensions.cs │ │ ├── FunctionsFrameworkLoggingExtensions.cs │ │ ├── FunctionsFrameworkServiceCollectionExtensions.cs │ │ └── FunctionsFrameworkWebHostBuilderExtensions.cs │ ├── FunctionsEnvironmentVariablesConfigurationSource.cs │ ├── FunctionsFrameworkOptions.cs │ ├── FunctionsStartup.cs │ ├── FunctionsStartupAttribute.cs │ ├── HostingInternals.cs │ ├── Logging │ │ ├── FactoryLoggerProvider.cs │ │ ├── JsonConsoleLogger.cs │ │ ├── LoggerBase.cs │ │ └── SimpleConsoleLogger.cs │ ├── OpenFunction.Hosting.csproj │ ├── Preconditions.cs │ └── targets │ │ ├── OpenFunction.Hosting.props │ │ └── OpenFunction.Hosting.targets ├── OpenFunction.Templates │ ├── OpenFunction.Templates.csproj │ └── templates │ │ ├── event-function-cs │ │ ├── .template.config │ │ │ ├── icon.png │ │ │ ├── ide.host.json │ │ │ └── template.json │ │ ├── Function.cs │ │ └── MyFunction.csproj │ │ ├── event-function-fs │ │ ├── .template.config │ │ │ ├── icon.png │ │ │ ├── ide.host.json │ │ │ └── template.json │ │ ├── Function.fs │ │ └── MyFunction.fsproj │ │ ├── event-function-vb │ │ ├── .template.config │ │ │ ├── icon.png │ │ │ ├── ide.host.json │ │ │ └── template.json │ │ ├── CloudFunction.vb │ │ └── MyFunction.vbproj │ │ ├── http-function-cs │ │ ├── .template.config │ │ │ ├── icon.png │ │ │ ├── ide.host.json │ │ │ └── template.json │ │ ├── Function.cs │ │ └── MyFunction.csproj │ │ ├── http-function-fs │ │ ├── .template.config │ │ │ ├── icon.png │ │ │ ├── ide.host.json │ │ │ └── template.json │ │ ├── Function.fs │ │ └── MyFunction.fsproj │ │ ├── http-function-vb │ │ ├── .template.config │ │ │ ├── icon.png │ │ │ ├── ide.host.json │ │ │ └── template.json │ │ ├── CloudFunction.vb │ │ └── MyFunction.vbproj │ │ ├── untyped-event-function-cs │ │ ├── .template.config │ │ │ ├── icon.png │ │ │ ├── ide.host.json │ │ │ └── template.json │ │ ├── Function.cs │ │ └── MyFunction.csproj │ │ ├── untyped-event-function-fs │ │ ├── .template.config │ │ │ ├── icon.png │ │ │ ├── ide.host.json │ │ │ └── template.json │ │ ├── Function.fs │ │ └── MyFunction.fsproj │ │ └── untyped-event-function-vb │ │ ├── .template.config │ │ ├── icon.png │ │ ├── ide.host.json │ │ └── template.json │ │ ├── CloudFunction.vb │ │ └── MyFunction.vbproj ├── OpenFunction.Testing.Tests │ ├── FunctionTestBaseTest.cs │ └── OpenFunction.Testing.Tests.csproj ├── OpenFunction.Testing │ ├── AssemblyInfo.cs │ ├── FunctionTestBase.cs │ ├── FunctionTestServer.cs │ ├── FunctionTestServerBuilder.cs │ ├── LoggerTypeNameHelper.cs │ ├── MemoryLogger.cs │ ├── MemoryLoggerProvider.cs │ ├── OpenFunction.Testing.csproj │ ├── Preconditions.cs │ └── TestLogEntry.cs ├── OpenFunction.sln └── OpenFunction.snk ├── update-googleevents-references.sh └── update-project-references.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cs] 4 | csharp_space_after_cast = true 5 | -------------------------------------------------------------------------------- /.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file specifies files that are *not* uploaded to Google Cloud Platform 2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of 3 | # "#!include" directives (which insert the entries of the given .gitignore-style 4 | # file at that point). 5 | # 6 | # For more information, run: 7 | # $ gcloud topic gcloudignore 8 | # 9 | .gcloudignore 10 | # If you would like to upload your .git directory, .gitignore file or files 11 | # from your .gitignore file, remove the corresponding line 12 | # below: 13 | .git 14 | .gitignore 15 | 16 | node_modules 17 | #!include:.gitignore 18 | 19 | # Ignore the submodule 20 | functions-framework-conformance/ 21 | -------------------------------------------------------------------------------- /.github/workflows/conformance.yml: -------------------------------------------------------------------------------- 1 | name: .NET Conformance CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | run-conformance: 9 | strategy: 10 | matrix: 11 | dotnet: [ "3.1.x", "6.0.x" ] 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | runs-on: ${{ matrix.os }} 14 | 15 | # Avoid unnecessary output 16 | env: 17 | DOTNET_NOLOGO: true 18 | DOTNET_CLI_TELEMETRY_OPTOUT: true 19 | 20 | steps: 21 | - name: Checkout repo 22 | uses: actions/checkout@v2 23 | with: 24 | submodules: true 25 | 26 | - name: Setup .NET Core 27 | uses: actions/setup-dotnet@v2 28 | with: 29 | dotnet-version: ${{ matrix.dotnet }} 30 | 31 | - name: Setup Go 32 | uses: actions/setup-go@v2 33 | with: 34 | go-version: '^1.15.2' 35 | 36 | - name: Clear NuGet cache 37 | run: dotnet nuget locals all --clear 38 | 39 | - name: Run conformance tests 40 | shell: bash 41 | run: ./run-conformance-tests.sh 42 | 43 | - name: Upload logs on failure 44 | uses: actions/upload-artifact@v2 45 | if: failure() 46 | with: 47 | name: conformance-logs-${{ matrix.os }}-${{ matrix.dotnet }} 48 | path: tmp/conformance-test-output 49 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: .NET Lint CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | lint: 9 | runs-on: ubuntu-18.04 10 | steps: 11 | - name: Checkout repo 12 | uses: actions/checkout@v2 13 | with: 14 | submodules: true 15 | - name: Setup .NET Core 16 | uses: actions/setup-dotnet@v2 17 | - run: dotnet tool install -g dotnet-format 18 | - run: dotnet-format src -------------------------------------------------------------------------------- /.github/workflows/unit.yml: -------------------------------------------------------------------------------- 1 | name: .NET Unit CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | test: 9 | strategy: 10 | matrix: 11 | dotnet: [ "3.1.x", "6.0.x" ] 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | runs-on: ${{ matrix.os }} 14 | 15 | # Avoid unnecessary output 16 | env: 17 | DOTNET_NOLOGO: true 18 | DOTNET_CLI_TELEMETRY_OPTOUT: true 19 | 20 | steps: 21 | - name: Checkout repo 22 | uses: actions/checkout@v2 23 | with: 24 | submodules: true 25 | 26 | - name: Setup .NET Core 27 | uses: actions/setup-dotnet@v2 28 | with: 29 | dotnet-version: ${{ matrix.dotnet }} 30 | 31 | - name: Clear NuGet cache 32 | run: dotnet nuget locals all --clear 33 | 34 | - name: Build and unit test 35 | shell: bash 36 | run: ./build.sh 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | project.lock.json 4 | .DS_Store 5 | *.pyc 6 | nupkg/ 7 | tmp/ 8 | 9 | # Visual Studio Code 10 | .vscode 11 | 12 | # Rider 13 | .idea 14 | 15 | # User-specific files 16 | *.suo 17 | *.user 18 | *.userosscache 19 | *.sln.docstates 20 | 21 | # Launch settings, e.g. from running samples 22 | launchSettings.json 23 | 24 | # Build results 25 | [Dd]ebug/ 26 | [Dd]ebugPublic/ 27 | [Rr]elease/ 28 | [Rr]eleases/ 29 | x64/ 30 | x86/ 31 | build/ 32 | bld/ 33 | [Bb]in/ 34 | [Oo]bj/ 35 | [Oo]ut/ 36 | msbuild.log 37 | msbuild.err 38 | msbuild.wrn 39 | 40 | # Visual Studio 2015 41 | .vs/ 42 | 43 | # CodeRush 44 | .cr/ 45 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "functions-framework-conformance"] 2 | path = functions-framework-conformance 3 | url = https://github.com/GoogleCloudPlatform/functions-framework-conformance 4 | -------------------------------------------------------------------------------- /.kokoro/autorelease.bat: -------------------------------------------------------------------------------- 1 | :: See documentation in type-shell-output.bat 2 | 3 | cd /d %~dp0 4 | "C:\Program Files\Git\bin\bash.exe" autorelease.sh 5 | -------------------------------------------------------------------------------- /.kokoro/autorelease.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Environment variables: 4 | # - COMMITTISH_OVERRIDE: The commit to actually build the release from, if not the one that has been checked out 5 | # - SKIP_NUGET_PUSH: If non-empty, the push to nuget.org is skipped 6 | 7 | set -e 8 | 9 | SCRIPT=$(readlink -f "$0") 10 | SCRIPT_DIR=$(dirname "$SCRIPT") 11 | 12 | cd $SCRIPT_DIR/.. 13 | 14 | # Make sure secrets are loaded in a well known location before running releasetool 15 | source $SCRIPT_DIR/populatesecrets.sh 16 | populate_all_secrets 17 | 18 | NUGET_API_KEY="$(cat "$SECRETS_LOCATION"/google-cloud-nuget-api-key)" 19 | 20 | # Make sure we have the most recent version of pip, then install the gcp-releasetool package 21 | python -m pip install --upgrade pip 22 | python -m pip install gcp-releasetool 23 | python -m releasetool publish-reporter-script > /tmp/publisher-script 24 | 25 | # The publish reporter script uses "python3" which doesn't exist on Windows. 26 | # Work out what we should use instead. 27 | # Try to detect Python 3. It's quite different between Windows and Linux. 28 | if which python > /dev/null && python --version 2>&1 | grep -q "Python 3"; then declare -r PYTHON3=python 29 | elif which py > /dev/null && py -3 --version 2>&1 | grep -q "Python 3"; then declare -r PYTHON3="py -3" 30 | elif which python3 > /dev/null && python3 --version 2>&1 | grep -q "Python 3"; then declare -r PYTHON3=python3 31 | else 32 | echo "Unable to detect Python 3 installation." 33 | exit 1 34 | fi 35 | 36 | # Fix up the publish reporter script using $PYTHON3. We assume this won't 37 | # be harmful within sed - at the moment it's always "python", "py -3" or "python3". 38 | sed -i "s/python3/$PYTHON3/g" /tmp/publisher-script 39 | 40 | source /tmp/publisher-script 41 | 42 | COMMITTISH=$COMMITTISH_OVERRIDE 43 | if [[ $COMMITTISH_OVERRIDE = "" ]] 44 | then 45 | COMMITTISH=HEAD 46 | fi 47 | 48 | TAG=$(git tag --points-at $COMMITTISH | head -n 1) 49 | 50 | if [[ $TAG = "" ]] 51 | then 52 | echo "Committish $COMMITTISH does not point at a tag. Aborting." 53 | exit 1 54 | fi 55 | 56 | echo "Building with tag $TAG" 57 | 58 | # Build the release and run the tests. 59 | ./build-release.sh $TAG 60 | 61 | if [[ $SKIP_NUGET_PUSH = "" ]] 62 | then 63 | echo "Pushing NuGet packages" 64 | # Push the changes to nuget. 65 | cd ./tmp/release/nupkg 66 | for pkg in *.nupkg 67 | do 68 | dotnet nuget push -s https://api.nuget.org/v3/index.json -k $NUGET_API_KEY $pkg 69 | done 70 | cd ../../.. 71 | else 72 | echo "Skipping NuGet push" 73 | fi 74 | -------------------------------------------------------------------------------- /.kokoro/populatesecrets.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -r SECRETS_LOCATION="${KOKORO_GFILE_DIR}/secret_manager" 4 | 5 | # Populates secrets from Secret Manager according 6 | # to what's specified on env variables. 7 | populate_all_secrets(){ 8 | echo "Creating folder on disk for secrets: ${SECRETS_LOCATION}" 9 | mkdir -p ${SECRETS_LOCATION} 10 | 11 | # Currently we don't have any COMMON_SECRETS 12 | # populate_secrets "${COMMON_SECRETS}" "cloud-sharp-jenkins" 13 | populate_secrets "${JOB_SECRETS}" "cloud-sharp-jenkins" 14 | populate_secrets "${EXTERNAL_SECRETS}" "cloud-devrel-kokoro-resources" 15 | } 16 | 17 | populate_secrets(){ 18 | local env_var=$1 19 | local project=$2 20 | for key in $(echo ${env_var} | sed "s/,/ /g") 21 | do 22 | echo "Retrieving secret ${key}" 23 | gcloud secrets versions access latest \ 24 | --project $project \ 25 | --secret $key > \ 26 | "${SECRETS_LOCATION}/$key" 27 | if [[ $? == 0 ]]; then 28 | echo "Secret written to ${SECRETS_LOCATION}/${key}" 29 | else 30 | echo "Error retrieving secret ${key}" 31 | exit 1 32 | fi 33 | done 34 | } 35 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing the .NET Functions Frameworks packages 2 | 3 | This is a largely automated process. Steps: 4 | 5 | - Decide on a new version number, following semantic versioning, but 6 | don't change any files. 7 | - Document the new version in [docs/history.md]() (but don't commit) 8 | - Run `./create-release-commit.sh {new-version}` from the root directory 9 | - Push the commit to GitHub and create a pull request 10 | - Add the "autorelease: pending" tag to the PR, and request a review 11 | - Once the PR is merged: 12 | - A GitHub release and tags will be created automatically 13 | - A Kokoro job will be launched to build and publish the NuGet packages 14 | 15 | The `create-release-commit.sh` script is responsible for: 16 | 17 | - Updating the `src/CommonProperties.xml` file which contains the version number 18 | - Updating all project references in templates and examples 19 | - Updating `README.md` to give instructions for installing the templates 20 | - Committing all changes with an appropriate message based on the 21 | version history 22 | 23 | Note that the script expects the version history file to follow the 24 | existing format. It assumes that the third line of the file is the 25 | header for the new release, and uses everything from the fourth line 26 | until the next "##" line, which is expected to be the previous 27 | release. 28 | -------------------------------------------------------------------------------- /build-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ -z "$1" ]] 4 | then 5 | echo "Please specify the release tag" 6 | exit 1 7 | fi 8 | 9 | set -e 10 | 11 | rm -rf tmp 12 | mkdir tmp 13 | 14 | git clone https://github.com/OpenFunction/functions-framework-dotnet.git \ 15 | --depth 1 -b $1 --recursive tmp/release 16 | 17 | cd tmp/release 18 | ./build.sh 19 | 20 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | export ContinuousIntegrationBuild=true 6 | export Configuration=Release 7 | # When building examples, build against the version in this 8 | # repo rather than against NuGet; this allows us to make breaking 9 | # changes. 10 | export LocalFunctionsFramework=true 11 | 12 | echo Building... 13 | dotnet build -nologo -clp:NoSummary -v quiet src 14 | dotnet build -nologo -clp:NoSummary -v quiet examples 15 | 16 | echo Testing... 17 | dotnet test -nologo --no-build -v quiet src 18 | dotnet test -nologo --no-build -v quiet examples 19 | 20 | echo Packing... 21 | rm -rf nupkg 22 | dotnet pack -nologo -v quiet src -o $PWD/nupkg 23 | 24 | echo Created packages: 25 | ls nupkg 26 | -------------------------------------------------------------------------------- /create-release-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Script to automate the release process. Assumptions: 6 | # - docs/history.md has already been updated (but not committed) 7 | # - any Google.Events references have already been updated 8 | # - the version number in *this* repo has not been updated 9 | 10 | # Pass in the new version number as the sole command line argument 11 | 12 | if [[ -z "$1" ]] 13 | then 14 | echo 'Please specify the new version number.' 15 | exit 1 16 | fi 17 | 18 | OLD_VERSION=$(grep '.*' src/CommonProperties.xml | sed -e 's/<\/\?Version>//g' | sed 's/ //g') 19 | NEW_VERSION=$1 20 | 21 | if [[ $OLD_VERSION == $NEW_VERSION ]] 22 | then 23 | echo "Error: already at $OLD_VERSION. Either finish manually, or revert version changes." 24 | exit 1 25 | fi 26 | 27 | # Update CommonProperties.xml 28 | sed -i -e "s/.*<\/Version>/$NEW_VERSION<\/Version>/g" src/CommonProperties.xml 29 | 30 | # Update all references/templates 31 | ./update-project-references.sh 32 | 33 | # Update the README instructions for template installation. 34 | # The binary mode (-b) here is to preserve existing line endings. 35 | # (At some point we should probably make all line endings consistent...) 36 | sed -i -b -e "s/Templates::$OLD_VERSION/Templates::$NEW_VERSION/g" README.md 37 | 38 | # Build the commit message up as a file for simplicity 39 | mkdir -p tmp 40 | 41 | echo "Release Functions Framework .NET packages version $NEW_VERSION" > tmp/commit.txt 42 | echo "" >> tmp/commit.txt 43 | echo "Changes since $OLD_VERSION:" >> tmp/commit.txt 44 | # Skip the first three lines of docs/history.md (header, blank line, subheader for this release) 45 | # Then take lines until we get to the next release, skipping that line with "head". 46 | tail -n +4 docs/history.md | sed '/##/q' | head -n -1 >> tmp/commit.txt 47 | 48 | # TODO: Automate finding the packages we're releasing 49 | echo "Packages in this release:" >> tmp/commit.txt 50 | echo "- Release OpenFunction.Framework version $NEW_VERSION" >> tmp/commit.txt 51 | echo "- Release OpenFunction.Hosting version $NEW_VERSION" >> tmp/commit.txt 52 | echo "- Release OpenFunction.Templates version $NEW_VERSION" >> tmp/commit.txt 53 | echo "- Release OpenFunction.Testing version $NEW_VERSION" >> tmp/commit.txt 54 | 55 | # Commit! 56 | git commit -a -F tmp/commit.txt 57 | 58 | echo "Created commit:" 59 | git show -s 60 | 61 | echo "" 62 | echo "Changes committed updating from $OLD_VERSION to $NEW_VERSION. Please push to GitHub." 63 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Functions Framework for .NET documentation 2 | 3 | This directory provides documentation for those who need more detail 4 | than is given in the [top-level README](../README.md). This 5 | documentation is not published separately; the Markdown files should 6 | be readable in GitHub. 7 | 8 | ## Packages 9 | 10 | This repository contains the source code for the following NuGet packages: 11 | 12 | - [OpenFunction.Framework](https://www.nuget.org/packages/OpenFunction.Framework) 13 | is a very small package, primarily containing interfaces for Cloud 14 | Functions to implement, as well as adapters between function types. 15 | - [OpenFunction.Hosting](https://www.nuget.org/packages/OpenFunction.Hosting) 16 | contains code to start up an ASP.NET Core webserver based on 17 | conventional environment variables etc. 18 | - [OpenFunction.Testing](https://www.nuget.org/packages/OpenFunction.Testing) 19 | contains code to help simplify testing functions. 20 | - [OpenFunction.Templates](https://www.nuget.org/packages/OpenFunction.Templates) 21 | contains templates for the `dotnet` command line to create a very 22 | simple getting-started experience. 23 | 24 | See [the packages guide](packages.md) for more details about each 25 | package. 26 | 27 | ## Repository layout 28 | 29 | The repository is split into the following directories: 30 | 31 | - [src](../src): Source code for the production packages and tests 32 | - [examples](../examples): Source code for example functions 33 | - [docs](.): Documentation 34 | 35 | The example functions are split into their own directory (with their 36 | own solution file) so that we can have many examples, each of which 37 | is a complete, standlone project. The example functions refer to the 38 | projects in `src` to allow for easy development. However, the expectation 39 | is that unless they use unreleased features, each example could be 40 | extracted from the repo, project references changed to package 41 | references, additional MSBuild imports removed, and the example should still work. 42 | 43 | ## Additional documentation in this directory: 44 | 45 | - [Version History](history.md) 46 | - [Package Details](packages.md) 47 | - [Deployment](deployment.md) 48 | - [Testing Functions](testing.md) 49 | - [Examples](examples.md) 50 | - [Customization using Functions Startup classes](customization.md) 51 | - [Microsoft.NET.Sdk.Web and launchSettings.json](launch-settings.md) 52 | -------------------------------------------------------------------------------- /docs/launch-settings.md: -------------------------------------------------------------------------------- 1 | # Microsoft.NET.Sdk.Web and launchSettings.json 2 | 3 | You may well have reached this page due to a link in an exception, 4 | while trying to start a server using the Functions Framework Hosting 5 | package. 6 | 7 | If you start a project with an `Sdk` attribute of 8 | "Microsoft.NET.Sdk.Web" in Visual Studio, it will create a 9 | `launchSettings.json` file if one doesn't already exist, and pass a 10 | single string, `%LAUNCHER_ARGS%` to the `Main` method. 11 | 12 | That's useful for a regular ASP.NET Core application, but isn't 13 | appropriate for a Functions Framework application using the 14 | OpenFunction.Framework.Hosting package which *always* uses 15 | Kestrel, gets its port from the `PORT` environment variable etc. 16 | 17 | There are three simple solutions to this issue. In order of 18 | simplicity and preference: 19 | 20 | - Change the `Sdk` attribute in the root element of your project 21 | file to "Microsoft.NET.Sdk", and delete any existing 22 | `launchSettings.json` file. At that point Visual Studio will 23 | treat it as a regular console application project. Using the 24 | Debugging property tab in Visual Studio will create a new 25 | `launchSettings.json`, but the way that file is used does not 26 | interfere with the Functions Framework Hosting package. 27 | - Add an element 28 | `true` 29 | within a `` in your project file, and delete any 30 | existing `launchSettings.json` file. That will stop Visual Studio 31 | from creating a `launchSettings.json` file, so you won't experience 32 | this problem. 33 | - Stop using the Functions Framework Hosting package. If you want to run your 34 | function as part of a regular ASP.NET Core app, you can do so - and 35 | you *may* find it useful to still refer to the 36 | OpenFunction.Framework package, just not the hosting package. 37 | 38 | There may be more complex options you wish to investigate if none 39 | of the above options work in your situation. It would be useful if 40 | you could [file an issue](https://github.com/OpenFunction/functions-framework-dotnet/issues/new) 41 | to provide some context, so we can help you find those options and 42 | perhaps work around the problem within the framework in the future. 43 | -------------------------------------------------------------------------------- /examples/.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file tells "gcloud functions deploy" which files and folders 2 | # to ignore. This is important when deploying multi-project functions, 3 | # as otherwise a lot of unnecessary files (particularly in the bin and 4 | # obj directories) may be uploaded. 5 | 6 | .gcloudignore 7 | bin/ 8 | obj/ 9 | -------------------------------------------------------------------------------- /examples/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 7 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /examples/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.AdvancedDependencyInjection/OpenFunction.Examples.AdvancedDependencyInjection.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.Configuration/OpenFunction.Examples.Configuration.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.Configuration/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DbConnection": { 3 | "Instance": "TestInstance", 4 | "Database": "Development Test Database" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.Configuration/appsettings.Production.json: -------------------------------------------------------------------------------- 1 | { 2 | "DbConnection": { 3 | "Instance": "ProductionInstance" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.Configuration/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "DbConnection": { 3 | "Instance": "DefaultInstance", 4 | "Database": "DefaultDatabase" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.CustomConfiguration/Function.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Framework; 16 | using OpenFunction.Hosting; 17 | using Microsoft.AspNetCore.Hosting; 18 | using Microsoft.AspNetCore.Http; 19 | using Microsoft.Extensions.Configuration; 20 | using Steeltoe.Extensions.Configuration.RandomValue; 21 | using System.Threading.Tasks; 22 | 23 | namespace OpenFunction.Examples.CustomConfiguration 24 | { 25 | 26 | /// 27 | /// The startup class can be used to perform additional configuration, including 28 | /// adding application configuration sources, reconfiguring logging, providing services 29 | /// for dependency injection, and adding middleware to the eventual application pipeline. 30 | /// In this case, we add the "random value" provider from Steeltoe. 31 | /// See https://steeltoe.io/docs/2/configuration/random-value-provider for more details. 32 | /// 33 | public class Startup : FunctionsStartup 34 | { 35 | public override void ConfigureAppConfiguration(WebHostBuilderContext context, IConfigurationBuilder configuration) => 36 | configuration.AddRandomValueSource(); 37 | } 38 | 39 | [FunctionsStartup(typeof(Startup))] 40 | public class Function : IHttpFunction 41 | { 42 | private readonly IConfiguration _configuration; 43 | 44 | public Function(IConfiguration configuration) => 45 | _configuration = configuration; 46 | 47 | public async Task HandleAsync(HttpContext context) 48 | { 49 | int randomValue = _configuration.GetValue("random:int"); 50 | await context.Response.WriteAsync($"Here's a random integer: {randomValue}"); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.CustomConfiguration/OpenFunction.Examples.CustomConfiguration.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.CustomEventDataFunction/Function.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2021, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using CloudNative.CloudEvents; 16 | using CloudNative.CloudEvents.NewtonsoftJson; 17 | using OpenFunction.Framework; 18 | using OpenFunction.Hosting; 19 | using Microsoft.AspNetCore.Hosting; 20 | using Microsoft.Extensions.DependencyInjection; 21 | using Microsoft.Extensions.Logging; 22 | using Newtonsoft.Json; 23 | using System.Threading; 24 | using System.Threading.Tasks; 25 | 26 | namespace OpenFunction.Examples.CustomEventDataFunction 27 | { 28 | /// 29 | /// Startup class to inject a suitable CloudEventFormatter. Our CustomData type 30 | /// has attributes for Json.NET, but no CloudEventFormatterAttribute, so we inject 31 | /// a suitable JsonEventFormatter. 32 | /// 33 | public class Startup : FunctionsStartup 34 | { 35 | public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) => 36 | services.AddSingleton(new JsonEventFormatter()); 37 | } 38 | 39 | /// 40 | /// A function that can be triggered by a CloudEvent containing data of type 41 | /// CustomData in a JSON event format. 42 | /// 43 | [FunctionsStartup(typeof(Startup))] 44 | public class Function : ICloudEventFunction 45 | { 46 | private readonly ILogger _logger; 47 | 48 | public Function(ILogger logger) => 49 | _logger = logger; 50 | 51 | public Task HandleAsync(CloudEvent cloudEvent, CustomData data, CancellationToken cancellationToken) 52 | { 53 | _logger.LogInformation("Data received. TextValue={value}", data.TextValue); 54 | return Task.CompletedTask; 55 | } 56 | } 57 | 58 | public class CustomData 59 | { 60 | [JsonProperty("text")] 61 | public string TextValue { get; set; } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.CustomEventDataFunction/OpenFunction.Examples.CustomEventDataFunction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.FSharpEventFunction/OpenFunction.Examples.FSharpEventFunction.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.FSharpHttpFunction/Function.fs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace OpenFunction.Examples.FSharpHttpFunction 16 | 17 | open OpenFunction.Framework 18 | open Microsoft.AspNetCore.Http 19 | 20 | type Function() = 21 | interface IHttpFunction with 22 | /// 23 | /// Logic for your function goes here. 24 | /// 25 | /// The HTTP context, containing the request and the response. 26 | /// A task representing the asynchronous operation. 27 | member this.HandleAsync context = 28 | async { 29 | do! context.Response.WriteAsync "Hello, Functions Framework." |> Async.AwaitTask 30 | } |> Async.StartAsTask :> _ 31 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.FSharpHttpFunction/OpenFunction.Examples.FSharpHttpFunction.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.FSharpUntypedEventFunction/Function.fs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace OpenFunction.Examples.FSharpUntypedEventFunction 16 | 17 | open OpenFunction.Framework 18 | open System.Threading.Tasks 19 | 20 | type Function() = 21 | interface ICloudEventFunction with 22 | /// 23 | /// Logic for your function goes here. Note that a CloudEvent function just consumes an event; 24 | /// it doesn't provide any response. 25 | /// 26 | /// The CloudEvent your function should consume. 27 | /// A cancellation token that is notified if the request is aborted. 28 | /// A task representing the asynchronous operation. 29 | member this.HandleAsync(cloudEvent, cancellationToken) = 30 | printfn "CloudEvent information:" 31 | printfn "ID: %s" cloudEvent.Id 32 | printfn "Source: %A" cloudEvent.Source 33 | printfn "Type: %s" cloudEvent.Type 34 | printfn "Subject: %s" cloudEvent.Subject 35 | printfn "DataSchema: %A" cloudEvent.DataSchema 36 | printfn "DataContentType: %O" cloudEvent.DataContentType 37 | printfn "Time: %s" (match Option.ofNullable cloudEvent.Time with 38 | | Some time -> time.ToUniversalTime().ToString "yyyy-MM-dd'T'HH:mm:ss.fff'Z'" 39 | | None -> "") 40 | printfn "SpecVersion: %O" cloudEvent.SpecVersion 41 | printfn "Data: %A" cloudEvent.Data 42 | 43 | // In this example, we don't need to perform any asynchronous operations, so we 44 | // just return a completed Task to conform to the interface. 45 | Task.CompletedTask 46 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.FSharpUntypedEventFunction/OpenFunction.Examples.FSharpUntypedEventFunction.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.IntegrationTests/AdvancedDependencyInjectionTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Testing; 16 | using System; 17 | using System.Text.Json; 18 | using System.Threading.Tasks; 19 | using Xunit; 20 | 21 | namespace OpenFunction.Examples.IntegrationTests 22 | { 23 | public class AdvancedDependencyInjectionTest : FunctionTestBase 24 | { 25 | [Fact] 26 | public async Task SingletonOperationIdRemainsStable() 27 | { 28 | // Make two requests to the function. They should both return the same SingletonOperationId value. 29 | var response1 = await CallFunctionAsync(); 30 | var response2 = await CallFunctionAsync(); 31 | 32 | Assert.Equal(response1.SingletonOperationId, response2.SingletonOperationId); 33 | } 34 | 35 | [Fact] 36 | public async Task ScopedOperationIdChangesPerRequest() 37 | { 38 | // Make two requests to the function. They should provide different ScopedOperationId values. 39 | var response1 = await CallFunctionAsync(); 40 | var response2 = await CallFunctionAsync(); 41 | 42 | Assert.NotEqual(response1.ScopedOperationId, response2.ScopedOperationId); 43 | } 44 | 45 | private async Task CallFunctionAsync() 46 | { 47 | var content = await ExecuteHttpGetRequestAsync(); 48 | return JsonSerializer.Deserialize(content); 49 | } 50 | 51 | private class ResponseModel 52 | { 53 | public Guid SingletonOperationId { get; set; } 54 | public Guid ScopedOperationId { get; set; } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.IntegrationTests/OpenFunction.Examples.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | false 6 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.IntegrationTests/SimpleDependencyInjectionTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Testing; 16 | using Microsoft.Extensions.Logging; 17 | using System.Threading.Tasks; 18 | using Xunit; 19 | 20 | namespace OpenFunction.Examples.IntegrationTests 21 | { 22 | public class SimpleDependencyInjectionTest 23 | { 24 | [Fact] 25 | public async Task LogEntryIsRecorded() 26 | { 27 | using (var server = new FunctionTestServer()) 28 | { 29 | // We shouldn't have any log entries (for the function's category) at the start of the test. 30 | Assert.Empty(server.GetFunctionLogEntries()); 31 | // Note: server.GetFunctionLogEntries() is equivalent to 32 | // server.GetLogEntries(typeof(SimpleDependencyInjection.Function) 33 | 34 | var client = server.CreateClient(); 35 | 36 | // Make a request to the function. 37 | var response = await client.GetAsync("sample-path"); 38 | response.EnsureSuccessStatusCode(); 39 | 40 | // Check that we got the expected log entry. 41 | var logs = server.GetFunctionLogEntries(); 42 | var entry = Assert.Single(logs); 43 | 44 | Assert.Equal(LogLevel.Information, entry.Level); 45 | Assert.Equal("Function called with path /sample-path", entry.Message); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.IntegrationTests/SimpleDependencyInjectionUnitTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | using OpenFunction.Testing; 15 | using Microsoft.AspNetCore.Http; 16 | using Microsoft.Extensions.Logging; 17 | using System.Threading.Tasks; 18 | using Xunit; 19 | 20 | namespace OpenFunction.Examples.IntegrationTests 21 | { 22 | /// 23 | /// Example of a unit test using a MemoryLogger. Often integration tests are 24 | /// the simplest form of testing for functions, but MemoryLogger allows log entries 25 | /// to be tested in unit tests by implementing ILogger. 26 | /// 27 | public class SimpleDependencyInjectionUnitTest 28 | { 29 | [Fact] 30 | public async Task LogEntryIsRecorded() 31 | { 32 | var logger = new MemoryLogger(); 33 | var function = new SimpleDependencyInjection.Function(logger); 34 | 35 | // Constructing the function does not create any log entries. 36 | Assert.Empty(logger.ListLogEntries()); 37 | 38 | // Make a request to the function. 39 | var context = new DefaultHttpContext 40 | { 41 | Request = { Path = "/sample-path" } 42 | }; 43 | await function.HandleAsync(context); 44 | 45 | var logs = logger.ListLogEntries(); 46 | var entry = Assert.Single(logs); 47 | 48 | Assert.Equal(LogLevel.Information, entry.Level); 49 | Assert.Equal("Function called with path /sample-path", entry.Message); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.IntegrationTests/SimpleHttpFunctionTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Microsoft.AspNetCore.Hosting; 16 | using Microsoft.AspNetCore.TestHost; 17 | using Microsoft.Extensions.DependencyInjection; 18 | using Microsoft.Extensions.Hosting; 19 | using System.Threading.Tasks; 20 | using Xunit; 21 | 22 | namespace OpenFunction.Examples.IntegrationTests 23 | { 24 | /// 25 | /// Simple example of an integration test against a Cloud Function, without using 26 | /// the OpenFunction.Testing package. 27 | /// 28 | public class SimpleHttpFunctionTest 29 | { 30 | [Fact] 31 | public async Task FunctionWritesHelloFunctionsFramework() 32 | { 33 | // Various other extension methods are available to configure logging, 34 | // startups, application configurers, along with reading configuration 35 | // from the command line and environment variables - but those aren't 36 | // required for this test. 37 | var builder = Host.CreateDefaultBuilder() 38 | .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder 39 | .ConfigureServices(services => services.AddFunctionTarget()) 40 | .Configure((context, app) => app.UseFunctionsFramework(context)) 41 | .UseTestServer()); 42 | using var server = await builder.StartAsync(); 43 | using var client = server.GetTestServer().CreateClient(); 44 | 45 | // Make a request to the function, and test that the response looks how we expect it to. 46 | using var response = await client.GetAsync("request-uri"); 47 | response.EnsureSuccessStatusCode(); 48 | var content = await response.Content.ReadAsStringAsync(); 49 | Assert.Equal("Hello, Functions Framework.", content); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.IntegrationTests/SimpleHttpFunctionTest_WithFunctionTestBase.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Testing; 16 | using System.Threading.Tasks; 17 | using Xunit; 18 | 19 | namespace OpenFunction.Examples.IntegrationTests 20 | { 21 | public class SimpleHttpFunctionTest_WithFunctionTestBase : FunctionTestBase 22 | { 23 | [Fact] 24 | public async Task FunctionWritesHelloFunctionsFramework() 25 | { 26 | string content = await ExecuteHttpGetRequestAsync(); 27 | Assert.Equal("Hello, Functions Framework.", content); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.IntegrationTests/SimpleHttpFunctionTest_WithTestServerFixture.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Testing; 16 | using System.Threading.Tasks; 17 | using Xunit; 18 | 19 | namespace OpenFunction.Examples.IntegrationTests 20 | { 21 | /// 22 | /// Simple example of an integration test creating a 23 | /// as a fixture. This uses the same test server across multiple tests. 24 | /// 25 | public class SimpleHttpFunctionTest_WithTestServerFixture : IClassFixture> 26 | { 27 | // The function test server created automatically by xUnit. This will be disposed after all tests have run. 28 | private readonly FunctionTestServer _server; 29 | 30 | public SimpleHttpFunctionTest_WithTestServerFixture(FunctionTestServer server) => 31 | _server = server; 32 | 33 | [Fact] 34 | public async Task FunctionWritesHelloFunctionsFramework() 35 | { 36 | var client = _server.CreateClient(); 37 | 38 | // Make a request to the function, and test that the response looks how we expect it to. 39 | var response = await client.GetAsync("request-uri"); 40 | response.EnsureSuccessStatusCode(); 41 | var content = await response.Content.ReadAsStringAsync(); 42 | 43 | Assert.Equal("Hello, Functions Framework.", content); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.IntegrationTests/SimpleHttpFunctionTest_WithTestServerInCtor.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Testing; 16 | using System; 17 | using System.Threading.Tasks; 18 | using Xunit; 19 | 20 | namespace OpenFunction.Examples.IntegrationTests 21 | { 22 | /// 23 | /// Simple example of an integration test creating a 24 | /// in the constructor. This uses a different test server for each test, but makes the setup common 25 | /// across all tests. 26 | /// 27 | public class SimpleHttpFunctionTest_WithTestServerInCtor : IDisposable 28 | { 29 | // The function test server created in the constructor. 30 | private readonly FunctionTestServer _server; 31 | 32 | public SimpleHttpFunctionTest_WithTestServerInCtor() => 33 | _server = new FunctionTestServer(); 34 | 35 | // Dispose of the function test server, which in turn disposes of the underlying test server. 36 | // xUnit calls this automatically when a test is complete. 37 | public void Dispose() => _server.Dispose(); 38 | 39 | [Fact] 40 | public async Task FunctionWritesHelloFunctionsFramework() 41 | { 42 | var client = _server.CreateClient(); 43 | 44 | // Make a request to the function, and test that the response looks how we expect it to. 45 | var response = await client.GetAsync("request-uri"); 46 | response.EnsureSuccessStatusCode(); 47 | var content = await response.Content.ReadAsStringAsync(); 48 | 49 | Assert.Equal("Hello, Functions Framework.", content); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.IntegrationTests/SimpleHttpFunctionTest_WithTestServerInTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Testing; 16 | using System.Threading.Tasks; 17 | using Xunit; 18 | 19 | namespace OpenFunction.Examples.IntegrationTests 20 | { 21 | /// 22 | /// Simple example of an integration test creating a 23 | /// inside a test. 24 | /// 25 | public class SimpleHttpFunctionTest_WithTestServerInTest 26 | { 27 | [Fact] 28 | public async Task FunctionWritesHelloFunctionsFramework() 29 | { 30 | using (var server = new FunctionTestServer()) 31 | { 32 | var client = server.CreateClient(); 33 | 34 | // Make a request to the function, and test that the response looks how we expect it to. 35 | var response = await client.GetAsync("request-uri"); 36 | response.EnsureSuccessStatusCode(); 37 | var content = await response.Content.ReadAsStringAsync(); 38 | 39 | Assert.Equal("Hello, Functions Framework.", content); 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.IntegrationTests/TestableDependenciesTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Hosting; 16 | using OpenFunction.Testing; 17 | using Microsoft.AspNetCore.Hosting; 18 | using Microsoft.Extensions.DependencyInjection; 19 | using System.Threading.Tasks; 20 | using Xunit; 21 | 22 | namespace OpenFunction.Examples.IntegrationTests 23 | { 24 | /// 25 | /// By default, the test server will use the same Functions Startup classes 26 | /// as normal. Applying the FunctionsStartup attribute to the test class (and/or the assembly) 27 | /// signals to FunctionTestBase which startups should be used to inject test dependencies 28 | /// instead of the production ones. An alternative is to declare a parameterless constructor 29 | /// that creates a FunctionTestServer to pass into the base class constructor. 30 | /// 31 | [FunctionsStartup(typeof(TestStartup))] 32 | public class TestableDependenciesTest : FunctionTestBase 33 | { 34 | [Fact] 35 | public async Task FunctionOutputDoesNotReferToProduction() 36 | { 37 | string text = await ExecuteHttpGetRequestAsync(); 38 | Assert.DoesNotContain("Production dependency", text); 39 | Assert.Contains("Test dependency", text); 40 | } 41 | 42 | // Functions Startup class specified in the FunctionTestStartup attribute on the test class, 43 | // so that test-only dependencies can be used. 44 | private class TestStartup : FunctionsStartup 45 | { 46 | public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) => 47 | services.AddSingleton(); 48 | } 49 | 50 | public class TestDependency : TestableDependencies.IDependency 51 | { 52 | public string Name => "Test dependency"; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.Middleware/Function.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Framework; 16 | using OpenFunction.Hosting; 17 | using Microsoft.AspNetCore.Builder; 18 | using Microsoft.AspNetCore.Hosting; 19 | using Microsoft.AspNetCore.Http; 20 | using Microsoft.Extensions.Logging; 21 | using System.Diagnostics; 22 | using System.Threading.Tasks; 23 | 24 | namespace OpenFunction.Examples.Middleware 25 | { 26 | /// 27 | /// The startup class can be used to perform additional configuration, including 28 | /// adding application configuration sources, reconfiguring logging, providing services 29 | /// for dependency injection, and adding middleware to the eventual application pipeline. 30 | /// In this case, we add a simple piece of middleware to the request pipeline. 31 | /// 32 | public class Startup : FunctionsStartup 33 | { 34 | public override void Configure(WebHostBuilderContext context, IApplicationBuilder app) => 35 | app.UseMiddleware(); 36 | } 37 | 38 | /// 39 | /// This middleware just provides a single log entry per successful request. 40 | /// (This is not terribly useful as middleware, but it demonstrates the concept simply.) 41 | /// 42 | public class SampleMiddleware 43 | { 44 | private readonly RequestDelegate _next; 45 | 46 | public SampleMiddleware(RequestDelegate next) => 47 | _next = next; 48 | 49 | public async Task InvokeAsync(HttpContext context, ILogger logger) 50 | { 51 | Stopwatch sw = Stopwatch.StartNew(); 52 | await _next(context); 53 | sw.Stop(); 54 | logger.LogInformation("Path: {path}; Status: {status}; Time: {time}ms", 55 | context.Request.Path, context.Response.StatusCode, sw.Elapsed.TotalMilliseconds); 56 | } 57 | } 58 | 59 | /// 60 | /// The actual Cloud Function. 61 | /// 62 | [FunctionsStartup(typeof(Startup))] 63 | public class Function : IHttpFunction 64 | { 65 | public async Task HandleAsync(HttpContext context) => 66 | await context.Response.WriteAsync("Response to be logged by middleware."); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.Middleware/OpenFunction.Examples.Middleware.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.MultiProjectDependency/BusinessLogic.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace OpenFunction.Examples.MultiProjectDependency 16 | { 17 | /// 18 | /// See the comments in MultiProjectFunction.Function for the purpose 19 | /// of this class. 20 | /// 21 | public class BusinessLogic 22 | { 23 | /// 24 | /// Performs some general business logic which might be needed from multiple 25 | /// applications, so belongs in a separate class library. 26 | /// 27 | public string PerformGeneralBusinessLogic() => "Profit!"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.MultiProjectDependency/OpenFunction.Examples.MultiProjectDependency.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.MultiProjectFunction/Function.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Examples.MultiProjectDependency; 16 | using OpenFunction.Framework; 17 | using Microsoft.AspNetCore.Http; 18 | using System.Threading.Tasks; 19 | 20 | namespace OpenFunction.Examples.MultiProjectFunction 21 | { 22 | /// 23 | /// Function demonstrating how to use a project dependency from the function 24 | /// project to a class library. When deploying to Google Cloud Functions, the 25 | /// class library code still needs to be uploaded as part of the build, and the 26 | /// GOOGLE_BUILDABLE build-time environment variable needs to be set to the 27 | /// path to the function project. 28 | /// 29 | public class Function : IHttpFunction 30 | { 31 | public async Task HandleAsync(HttpContext context) 32 | { 33 | var logic = new BusinessLogic(); 34 | var result = logic.PerformGeneralBusinessLogic(); 35 | await context.Response.WriteAsync($"Result: {result}"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.MultiProjectFunction/OpenFunction.Examples.MultiProjectFunction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.SimpleDependencyInjection/Function.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Framework; 16 | using Microsoft.AspNetCore.Http; 17 | using Microsoft.Extensions.Logging; 18 | using System.Threading.Tasks; 19 | 20 | namespace OpenFunction.Examples.SimpleDependencyInjection 21 | { 22 | /// 23 | /// Simple example of dependency injection, with no additional configuration. 24 | /// Loggers are provided out-of-the-box by ASP.NET Core, which means you can inject a logger 25 | /// into your function constructor. 26 | /// 27 | public class Function : IHttpFunction 28 | { 29 | private readonly ILogger _logger; 30 | 31 | public Function(ILogger logger) => 32 | _logger = logger; 33 | 34 | public async Task HandleAsync(HttpContext context) 35 | { 36 | _logger.LogInformation("Function called with path {path}", context.Request.Path); 37 | await context.Response.WriteAsync("Written the request path to the logger provided by dependency injection."); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.SimpleDependencyInjection/OpenFunction.Examples.SimpleDependencyInjection.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.SimpleEventFunction/OpenFunction.Examples.SimpleEventFunction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.SimpleHttpFunction/Function.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Microsoft.AspNetCore.Http; 16 | using System.Threading.Tasks; 17 | //using Google.Cloud.Functions.Framework; 18 | using OpenFunction.Framework; 19 | 20 | namespace OpenFunction.Examples.SimpleHttpFunction 21 | { 22 | public class Function : IHttpFunction 23 | { 24 | /// 25 | /// Logic for your function goes here. 26 | /// 27 | /// The HTTP context, containing the request and the response. 28 | /// A task representing the asynchronous operation. 29 | public async Task HandleAsync(HttpContext context) 30 | { 31 | await context.Response.WriteAsync("Hello, Functions Framework."); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.SimpleHttpFunction/OpenFunction.Examples.SimpleHttpFunction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.SimpleUntypedEventFunction/Function.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using CloudNative.CloudEvents; 16 | using OpenFunction.Framework; 17 | using System; 18 | using System.Threading; 19 | using System.Threading.Tasks; 20 | 21 | namespace OpenFunction.Examples.SimpleUntypedEventFunction 22 | { 23 | public class Function : ICloudEventFunction 24 | { 25 | /// 26 | /// Logic for your function goes here. Note that a CloudEvent function just consumes an event; 27 | /// it doesn't provide any response. 28 | /// 29 | /// The CloudEvent your function should consume. 30 | /// A cancellation token that is notified if the request is aborted. 31 | /// A task representing the asynchronous operation. 32 | public Task HandleAsync(CloudEvent cloudEvent, CancellationToken cancellationToken) 33 | { 34 | Console.WriteLine("CloudEvent information:"); 35 | Console.WriteLine($"ID: {cloudEvent.Id}"); 36 | Console.WriteLine($"Source: {cloudEvent.Source}"); 37 | Console.WriteLine($"Type: {cloudEvent.Type}"); 38 | Console.WriteLine($"Subject: {cloudEvent.Subject}"); 39 | Console.WriteLine($"DataSchema: {cloudEvent.DataSchema}"); 40 | Console.WriteLine($"DataContentType: {cloudEvent.DataContentType}"); 41 | Console.WriteLine($"Time: {cloudEvent.Time?.ToUniversalTime():yyyy-MM-dd'T'HH:mm:ss.fff'Z'}"); 42 | Console.WriteLine($"SpecVersion: {cloudEvent.SpecVersion}"); 43 | Console.WriteLine($"Data: {cloudEvent.Data}"); 44 | 45 | // In this example, we don't need to perform any asynchronous operations, so the 46 | // method doesn't need to be declared async. 47 | return Task.CompletedTask; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.SimpleUntypedEventFunction/OpenFunction.Examples.SimpleUntypedEventFunction.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.StorageImageAnnotator/OpenFunction.Examples.StorageImageAnnotator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.StorageImageAnnotator/README.md: -------------------------------------------------------------------------------- 1 | # StorageImageAnnotator example function 2 | 3 | This function demonstrates an event handler for Google Cloud Storage. 4 | 5 | It should be attached to a trigger type of 6 | "google.storage.object.finalize", so it will be called whenever an 7 | object finishes uploading in a Storage bucket. 8 | 9 | If the file is not a JPEG image (content type "image/jpeg", extension 10 | ".jpg"), it's skipped. 11 | 12 | Otherwise, the function uses the [Google Cloud Vision 13 | API](https://cloud.google.com/vision) to detect faces, text, 14 | landmarks, logos and objects within the image. Once the Vision API 15 | has returned a response, the function formats the response as a text 16 | file, and uploads it as a new Google Cloud Storage object with the 17 | same name as the original one, but an extension of ".txt". 18 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.TestableDependencies/Function.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Framework; 16 | using OpenFunction.Hosting; 17 | using Microsoft.AspNetCore.Hosting; 18 | using Microsoft.AspNetCore.Http; 19 | using Microsoft.Extensions.DependencyInjection; 20 | using System.Threading.Tasks; 21 | 22 | namespace OpenFunction.Examples.TestableDependencies 23 | { 24 | // The dependency interface required by the function. 25 | public interface IDependency 26 | { 27 | public string Name { get; } 28 | } 29 | 30 | // The production dependency, configured in the Startup class. 31 | public class ProductionDependency : IDependency 32 | { 33 | public string Name => "Production dependency. Don't use me in tests!"; 34 | } 35 | 36 | /// 37 | /// Adds the production dependency to the service collection. 38 | /// 39 | public class Startup : FunctionsStartup 40 | { 41 | public override void ConfigureServices(WebHostBuilderContext context, IServiceCollection services) => 42 | services.AddSingleton(); 43 | } 44 | 45 | [FunctionsStartup(typeof(Startup))] 46 | public class Function : IHttpFunction 47 | { 48 | private readonly IDependency _dependency; 49 | 50 | public Function(IDependency dependency) => 51 | _dependency = dependency; 52 | 53 | public async Task HandleAsync(HttpContext context) 54 | { 55 | await context.Response.WriteAsync($"Dependency configured for function: {_dependency.Name}"); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.TestableDependencies/OpenFunction.Examples.TestableDependencies.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.TimeZoneConverter/ConversionResult.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Text.Json.Serialization; 16 | 17 | namespace OpenFunction.Examples.TimeZoneConverter 18 | { 19 | // Note: Input and Result could be LocalDateTime values, if we use NodaTime.Serialization.SystemTextJson. 20 | // But we've already got a LocalDateTimePattern available, so it's simplest just to format with that. 21 | 22 | /// 23 | /// The result of a zone-to-zone conversion. 24 | /// 25 | public sealed class ConversionResult 26 | { 27 | /// 28 | /// The version of the data used, e.g. "TZDB: 2019b (mapping: 14742)". 29 | /// 30 | [JsonPropertyName("data_version")] 31 | public string DataVersion { get; } 32 | 33 | /// 34 | /// The local date/time used as input and interpreted in . 35 | /// 36 | [JsonPropertyName("input")] 37 | public string Input { get; } 38 | 39 | /// 40 | /// The ID of the time zone to convert from. 41 | /// 42 | [JsonPropertyName("from_zone")] 43 | public string FromZone { get; } 44 | 45 | /// 46 | /// The ID of the time zone to convert to. 47 | /// 48 | [JsonPropertyName("to_zone")] 49 | public string ToZone { get; } 50 | 51 | /// 52 | /// The result date/time in . 53 | /// 54 | [JsonPropertyName("result")] 55 | public string Result { get; } 56 | 57 | /// 58 | /// The conversion type. While most local date/time values map 1:1 with instants in time, 59 | /// daylight saving transitions and other offset changes mean that some local date/time values 60 | /// are skipped entirely or occur twice. 61 | /// 62 | [JsonPropertyName("conversion_type")] 63 | public ConversionType ConversionType { get; } 64 | 65 | public ConversionResult(string dataVersion, string input, string fromZone, string toZone, string result, ConversionType conversionType) => 66 | (DataVersion, Input, FromZone, ToZone, Result, ConversionType) = (dataVersion, input, fromZone, toZone, result, conversionType); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.TimeZoneConverter/ConversionType.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | namespace OpenFunction.Examples.TimeZoneConverter 16 | { 17 | /// 18 | /// The type of conversion that was performed as part of a . 19 | /// 20 | public enum ConversionType 21 | { 22 | /// 23 | /// The input was unambiguous, mapping to a single instant in time. 24 | /// 25 | Unambiguous, 26 | 27 | /// 28 | /// The input was ambiguous, usually due to occurring within a "fall back" daylight saving transition. 29 | /// The conversion to the target time zone of each value did not resolve this ambiguity. 30 | /// The result is the earlier of the results. 31 | /// 32 | AmbiguousInputAmbiguousResult, 33 | 34 | /// 35 | /// The input was ambiguous, usually due to occurring within a "fall back" daylight saving transition. 36 | /// However, after converting both possible instants to the target time zone, the results are the same. 37 | /// This is usually due to converting between time zones which observe the same daylight saving transitions. 38 | /// 39 | AmbiguousInputUnambiguousResult, 40 | 41 | /// 42 | /// The input was skipped, usually due to occurring within a "spring forward" daylight saving transition. 43 | /// The result is provided by shifting the input value by the length of the "gap" in local time (usually one hour). 44 | /// 45 | SkippedInputForwardShiftedResult 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.TimeZoneConverter/OpenFunction.Examples.TimeZoneConverter.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | net6.0 5 | Enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.VbEventFunction/OpenFunction.Examples.VbEventFunction.vbproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | OpenFunction.Examples.VbEventFunction 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.VbHttpFunction/CloudFunction.vb: -------------------------------------------------------------------------------- 1 | ' Copyright 2020, Google LLC 2 | ' 3 | ' Licensed under the Apache License, Version 2.0 (the "License"); 4 | ' you may not use this file except in compliance with the License. 5 | ' You may obtain a copy of the License at 6 | ' 7 | ' https://www.apache.org/licenses/LICENSE-2.0 8 | ' 9 | ' Unless required by applicable law or agreed to in writing, software 10 | ' distributed under the License is distributed on an "AS IS" BASIS, 11 | ' WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ' See the License for the specific language governing permissions and 13 | ' limitations under the License. 14 | 15 | Imports OpenFunction.Framework 16 | Imports Microsoft.AspNetCore.Http 17 | 18 | Public Class CloudFunction 19 | Implements IHttpFunction 20 | 21 | ''' 22 | ''' Logic for your function goes here. 23 | ''' 24 | ''' The HTTP context, containing the request and the response. 25 | ''' A task representing the asynchronous operation. 26 | Public Async Function HandleAsync(context As HttpContext) As Task Implements IHttpFunction.HandleAsync 27 | Await context.Response.WriteAsync("Hello, Functions Framework.") 28 | End Function 29 | End Class 30 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.VbHttpFunction/OpenFunction.Examples.VbHttpFunction.vbproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | OpenFunction.Examples.VbHttpFunction 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.VbUntypedEventFunction/CloudFunction.vb: -------------------------------------------------------------------------------- 1 | ' Copyright 2020, Google LLC 2 | ' 3 | ' Licensed under the Apache License, Version 2.0 (the "License"); 4 | ' you may not use this file except in compliance with the License. 5 | ' You may obtain a copy of the License at 6 | ' 7 | ' https://www.apache.org/licenses/LICENSE-2.0 8 | ' 9 | ' Unless required by applicable law or agreed to in writing, software 10 | ' distributed under the License is distributed on an "AS IS" BASIS, 11 | ' WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | ' See the License for the specific language governing permissions and 13 | ' limitations under the License. 14 | 15 | Imports CloudNative.CloudEvents 16 | Imports OpenFunction.Framework 17 | Imports System.Threading 18 | 19 | Public Class CloudFunction 20 | Implements ICloudEventFunction 21 | 22 | ''' 23 | ''' Logic for your function goes here. Note that a CloudEvent function just consumes an event; 24 | ''' it doesn't provide any response. 25 | ''' 26 | ''' The CloudEvent your function should consume. 27 | ''' A cancellation token that is notified if the request is aborted. 28 | ''' A task representing the asynchronous operation. 29 | Public Function HandleAsync(cloudEvent As CloudEvent, cancellationToken As CancellationToken) As Task _ 30 | Implements ICloudEventFunction.HandleAsync 31 | Console.WriteLine("CloudEvent information:") 32 | Console.WriteLine($"ID: {cloudEvent.Id}") 33 | Console.WriteLine($"Source: {cloudEvent.Source}") 34 | Console.WriteLine($"Type: {cloudEvent.Type}") 35 | Console.WriteLine($"Subject: {cloudEvent.Subject}") 36 | Console.WriteLine($"DataSchema: {cloudEvent.DataSchema}") 37 | Console.WriteLine($"DataContentType: {cloudEvent.DataContentType}") 38 | Console.WriteLine($"Time: {cloudEvent.Time?.ToUniversalTime():yyyy-MM-dd'T'HH:mm:ss.fff'Z'}") 39 | Console.WriteLine($"SpecVersion: {cloudEvent.SpecVersion}") 40 | Console.WriteLine($"Data: {cloudEvent.Data}") 41 | 42 | ' In this example, we don't need to perform any asynchronous operations, so the 43 | ' function doesn't need to be declared as Async. 44 | Return Task.CompletedTask 45 | End Function 46 | End Class 47 | -------------------------------------------------------------------------------- /examples/OpenFunction.Examples.VbUntypedEventFunction/OpenFunction.Examples.VbUntypedEventFunction.vbproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | OpenFunction.Examples.VbUntypedEventFunction 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Example source code 2 | 3 | ## Documentation 4 | 5 | For information about running these samples, along with a brief 6 | description of what each example demonstrates, see the 7 | [examples documentation page](../docs/examples.md). 8 | 9 | ## Generating 10 | 11 | The following projects are the result of creating new projects from 12 | the templates: 13 | 14 | - OpenFunction.Examples.SimpleHttpFunction 15 | - OpenFunction.Examples.SimpleEventFunction 16 | - OpenFunction.Examples.SimpleUntypedEventFunction 17 | - OpenFunction.Examples.FSharpHttpFunction 18 | - OpenFunction.Examples.FSharpEventFunction 19 | - OpenFunction.Examples.FSharpUntypedEventFunction 20 | - OpenFunction.Examples.VbHttpFunction 21 | - OpenFunction.Examples.VbEventFunction 22 | - OpenFunction.Examples.VbUntypedEventFunction 23 | 24 | In each case, after creating the project, a copyright notice is 25 | added to the code. 26 | 27 | When built as they are, the example projects refer to packages on 28 | nuget.org, which makes deployment to Google Cloud Functions simpler, 29 | and is closer to the regular developer experience. 30 | 31 | To build against the local version of the Functions Framework (in 32 | the `src` directory), set an MSBuild property of 33 | `LocalFunctionsFramework` to any non-empty value. This is often most 34 | simply done from the command line. You can then either run the 35 | example with `dotnet run`, or start Visual Studio with the 36 | environment variable set. 37 | 38 | To deploy an example using the local version of the Functions 39 | Framework, you can use `--set-build-env-vars` to set the 40 | `LOCALFUNCTIONSFRAMEWORK` environment variable (which must be in 41 | all-caps for the Buildpack to work) to true, along with 42 | `GOOGLE_BUILDABLE` to select the project. This has to be run from 43 | the root directory, in order to pick up `src` as well as `examples`. 44 | So for example, to run the `SimpleHttpFunction` example using a 45 | modified Functions Framework, you could run: 46 | 47 | ```sh 48 | gcloud functions deploy ff-test \ 49 | --runtime=dotnet3 \ 50 | --trigger-http \ 51 | --set-build-env-vars=GOOGLE_BUILDABLE=examples/OpenFunction.Examples.SimpleHttpFunction,LOCALFUNCTIONSFRAMEWORK=true \ 52 | --entry-point=OpenFunction.Examples.SimpleHttpFunction.Function 53 | ``` 54 | -------------------------------------------------------------------------------- /examples/copyright.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /examples/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | generate() { 6 | declare -r DIRECTORY=$1 7 | declare -r TEMPLATE_NAME=$2 8 | declare -r LANG=$3 9 | 10 | echo "Generating $DIRECTORY" 11 | 12 | rm -rf $DIRECTORY 13 | mkdir $DIRECTORY 14 | cd $DIRECTORY 15 | 16 | # Create the project and source files 17 | dotnet new $TEMPLATE_NAME -lang $LANG > /dev/null 18 | 19 | # Add copyright notices 20 | for source in *.?? 21 | do 22 | mv $source $source.tmp 23 | cat ../copyright.txt $source.tmp > $source 24 | # Convert comment format in VB 25 | if [[ "$LANG" == "vb" ]] 26 | then 27 | sed -i -e "s/^\/\//\'/g" $source 28 | fi 29 | rm $source.tmp 30 | done 31 | 32 | cd .. 33 | } 34 | 35 | generate OpenFunction.Examples.SimpleHttpFunction of-http 'c#' 36 | generate OpenFunction.Examples.SimpleEventFunction of-event 'c#' 37 | generate OpenFunction.Examples.SimpleUntypedEventFunction of-untyped-event 'c#' 38 | generate OpenFunction.Examples.FSharpHttpFunction of-http 'f#' 39 | generate OpenFunction.Examples.FSharpEventFunction of-event 'f#' 40 | generate OpenFunction.Examples.FSharpUntypedEventFunction of-untyped-event 'f#' 41 | generate OpenFunction.Examples.VbHttpFunction of-http 'vb' 42 | generate OpenFunction.Examples.VbEventFunction of-event 'vb' 43 | generate OpenFunction.Examples.VbUntypedEventFunction of-untyped-event 'vb' 44 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "6.0.201", 4 | "rollForward": "minor" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /install-local-templates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This script builds a templates package using a date-based version, and installs it via "dotnet new -i" 4 | 5 | REAL_VERSION=$(grep '.*' src/CommonProperties.xml | sed -e 's/<\/\?Version>//g' | sed 's/ //g') 6 | 7 | VERSION=$REAL_VERSION-$(date -u +%Y%m%d%H%M) 8 | 9 | export Configuration=Release 10 | dotnet pack -p:Version=$VERSION src/OpenFunction.Templates 11 | 12 | NUGET_SOURCE=$PWD/src/OpenFunction.Templates/bin/Release/ 13 | 14 | # Come out of the functions-framework-dotnet directory to use the default SDK, 15 | # which is what VS uses. 16 | (cd .. && dotnet new -i OpenFunction.Templates::$VERSION --nuget-source=$NUGET_SOURCE) 17 | 18 | # Also install within functions-framework-dotnet directory so that it will 19 | # be installed for use in examples/generate.sh 20 | dotnet new -i OpenFunction.Templates::$VERSION --nuget-source=$NUGET_SOURCE 21 | 22 | echo "Installed local templates as version $VERSION" 23 | -------------------------------------------------------------------------------- /run-conformance-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Runs the conformance tests from 4 | # https://github.com/GoogleCloudPlatform/functions-framework-conformance 5 | 6 | # That repository is included as a submodule already, under 7 | # functions-framework-conformance 8 | 9 | # This script checks that the submodule is already present, but 10 | # does not assume it has already been built. 11 | # It assumes that "go" is already in the path. 12 | 13 | set -e 14 | 15 | # Path to the root of the conformance repository. By default this 16 | # is the submodule under functions-framework-dotnet, but it can be 17 | # easily tweaked to simplify testing of local changes to the conformance 18 | # tests. 19 | CONFORMANCE_REPO=functions-framework-conformance 20 | 21 | rm -rf tmp/conformance-test-output 22 | mkdir -p tmp/conformance-test-output 23 | 24 | if [[ ! -f $CONFORMANCE_REPO/README.md ]] 25 | then 26 | echo "Conformance test repo not found. Init and update submodules." 27 | exit 1 28 | fi 29 | 30 | # Build the conformance test framework 31 | echo "Building conformance test framework" 32 | # Regenerate the events - normally a no-op, but it makes it 33 | # simpler to update the events. 34 | (cd $CONFORMANCE_REPO && go generate ./...) 35 | # Build the conformance test client itself 36 | (cd $CONFORMANCE_REPO/client && go build) 37 | 38 | # Build the conformance functions up-front 39 | echo "Building conformance functions" 40 | dotnet build -nologo -clp:NoSummary -v quiet -c Release src/OpenFunction.ConformanceTests 41 | 42 | CLIENT_BINARY=$CONFORMANCE_REPO/client/client 43 | if [[ $OSTYPE =~ ^win* || $OSTYPE =~ ^msys* || $OSTYPE =~ ^cygwin* ]] 44 | then 45 | CLIENT_BINARY=${CLIENT_BINARY}.exe 46 | fi 47 | 48 | # Run the Functions Framework once as a "warm-up", killing it after 5 seconds. 49 | # This is necessary on MacOS for non-obvious reasons; 50 | # it's possible that the conformance test runner just expects it to be 51 | # ready a little bit earlier than it is. 52 | # TODO: Remove this when we can. 53 | echo "Running Functions Framework for 5 seconds as a warm-up step." 54 | dotnet src/OpenFunction.ConformanceTests/bin/Release/net6.0/OpenFunction.ConformanceTests.dll HttpFunction & 55 | DOTNETPID=$! 56 | sleep 5 57 | kill $DOTNETPID 58 | 59 | # Note: we run the DLL directly rather than using "dotnet run" as this 60 | # responds more correctly to being killed by the conformance test runner 61 | # on Linux. 62 | DOTNET_DLL=src/OpenFunction.ConformanceTests/bin/Release/net6.0/OpenFunction.ConformanceTests.dll 63 | 64 | echo "Using conformance test runner binary: $CLIENT_BINARY" 65 | echo "Using Functions Framework test binary: $DOTNET_DLL" 66 | 67 | # Run the tests 68 | run_test() { 69 | TEST_TYPE=$1 70 | TEST_FUNCTION=$2 71 | echo "Running '$TEST_TYPE' test with function '$TEST_FUNCTION'" 72 | (cd tmp/conformance-test-output && \ 73 | mkdir $TEST_TYPE && \ 74 | cd $TEST_TYPE && \ 75 | ../../../$CLIENT_BINARY \ 76 | -buildpacks=false \ 77 | -type=$TEST_TYPE \ 78 | -cmd="dotnet ../../../$DOTNET_DLL $TEST_FUNCTION" \ 79 | ) 80 | } 81 | 82 | run_test http HttpFunction 83 | run_test cloudevent UntypedCloudEventFunction 84 | 85 | echo "Tests completed successfully" 86 | -------------------------------------------------------------------------------- /src/CommonProperties.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 0.1.0 7 | 8 | 9 | 10 | 11 | NET6.0 12 | true 13 | ../OpenFunction.snk 14 | true 15 | true 16 | true 17 | Enable 18 | Library 19 | 20 | 21 | true 22 | 23 | 24 | 25 | 26 | True 27 | Copyright 2022 OpenFunction 28 | OpenFunction 29 | 30 | https://github.com/OpenFunction/OpenFunction/blob/main/docs/images/openfunction.png 31 | NuGetIcon.png 32 | LICENSE 33 | https://github.com/OpenFunction/functions-framework-dotnet 34 | git 35 | https://github.com/OpenFunction/functions-framework-dotnet 36 | 37 | 38 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb 39 | true 40 | true 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/Directory.Build.targets: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/NuGetIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/NuGetIcon.png -------------------------------------------------------------------------------- /src/OpenFunction.ConformanceTests/HttpFunction.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Framework; 16 | using Microsoft.AspNetCore.Http; 17 | using System.IO; 18 | using System.Threading.Tasks; 19 | 20 | // Note: no namespace, to make it simpler to run as part of conformance testing 21 | public class HttpFunction : IHttpFunction 22 | { 23 | public async Task HandleAsync(HttpContext context) 24 | { 25 | using var output = File.OpenWrite("function_output.json"); 26 | await context.Request.Body.CopyToAsync(output); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/OpenFunction.ConformanceTests/OpenFunction.ConformanceTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Exe 8 | net6.0 9 | False 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/OpenFunction.ConformanceTests/UntypedCloudEventFunction.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using CloudNative.CloudEvents; 16 | using CloudNative.CloudEvents.SystemTextJson; 17 | using OpenFunction.Framework; 18 | using System.IO; 19 | using System.Threading; 20 | using System.Threading.Tasks; 21 | 22 | // Note: no namespace, to make it simpler to run as part of conformance testing 23 | public class UntypedCloudEventFunction : ICloudEventFunction 24 | { 25 | public async Task HandleAsync(CloudEvent cloudEvent, CancellationToken cancellationToken) 26 | { 27 | // Write out a structured JSON representation of the CloudEvent 28 | var formatter = new JsonEventFormatter(); 29 | var bytes = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType); 30 | await File.WriteAllBytesAsync("function_output.json", bytes.ToArray()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/GcfEventResources.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Microsoft.AspNetCore.Http; 16 | 17 | namespace OpenFunction.Framework.Tests.GcfEvents 18 | { 19 | /// 20 | /// Helpers for resources accessed cross multiple tests. 21 | /// 22 | internal static class GcfEventResources 23 | { 24 | internal static HttpContext CreateHttpContext(string resourceName, string? path = null) => 25 | new DefaultHttpContext 26 | { 27 | Request = 28 | { 29 | Body = TestResourceHelper.LoadResource(typeof(GcfEventResources), resourceName), 30 | ContentType = "application/json", 31 | Path = path is null ? PathString.Empty : (PathString) path 32 | } 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/emulator_pubsub.json: -------------------------------------------------------------------------------- 1 | { 2 | "subscription": "projects\/emulator-project\/subscriptions\/test-subscription", 3 | "message": { 4 | "data": "VGVzdCBtZXNzYWdlIGZyb20gZW11bGF0b3I=", 5 | "messageId": "1", 6 | "attributes": { 7 | "attr1": "attr-value1" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-analytics-no-app-id.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventId": "analytics-1606955191246909-14239195990770746-5322866668684751502", 3 | "eventType": "providers/google.firebase.analytics/eventTypes/event.log", 4 | "resource": "projects/my-project-id/events/session_start", 5 | "timestamp": "2020-12-03T00:26:41.321623382Z", 6 | "data": { 7 | "eventDim": [ 8 | { 9 | "date": "20201202", 10 | "name": "session_start", 11 | "params": { 12 | "engaged_session_event": { "intValue": "1" }, 13 | "firebase_conversion": { "intValue": "1" }, 14 | "firebase_event_origin": { "stringValue": "auto" }, 15 | "firebase_screen": { "stringValue": "app_menu" }, 16 | "firebase_screen_class": { "stringValue": "AppActivity" }, 17 | "firebase_screen_id": { "intValue": "-2415111648950109400" }, 18 | "ga_session_id": { "intValue": "1606965190" }, 19 | "ga_session_number": { "intValue": "7" }, 20 | "session_engaged": { "intValue": "1" } 21 | }, 22 | "previousTimestampMicros": "1606951997533000", 23 | "timestampMicros": "1606955191246909" 24 | } 25 | ], 26 | "userDim": { 27 | "bundleInfo": { 28 | "bundleSequenceId": 58, 29 | "serverTimestampOffsetMicros": "875910" 30 | }, 31 | "deviceInfo": { 32 | "deviceCategory": "mobile", 33 | "deviceModel": "SM-A307G", 34 | "deviceTimeZoneOffsetSeconds": -10800, 35 | "mobileBrandName": "Samsung", 36 | "mobileMarketingName": "Galaxy A30s", 37 | "mobileModelName": "SM-A307G", 38 | "platformVersion": "10", 39 | "resettableDeviceId": "aaaaaa-1111-bbbb-2222-dddddddddddd", 40 | "userDefaultLanguage": "es-us" 41 | }, 42 | "firstOpenTimestampMicros": "1606882687506000", 43 | "geoInfo": { 44 | "city": "Burzaco", 45 | "continent": "005", 46 | "country": "Argentina", 47 | "region": "Buenos Aires Province" 48 | }, 49 | "userId": "0123456789abcdef0123456789abcdef", 50 | "userProperties": { 51 | "completed_tutorial": { 52 | "setTimestampUsec": "1606948068187909", 53 | "value": { "stringValue": "true" } 54 | }, 55 | "first_open_time": { 56 | "setTimestampUsec": "1606882688381909", 57 | "value": { "intValue": "1606885200000" } 58 | }, 59 | "last_level": { 60 | "index": 10, 61 | "setTimestampUsec": "1606952210498909", 62 | "value": { "stringValue": "school" } 63 | }, 64 | "user_id": { 65 | "setTimestampUsec": "1606955180040909", 66 | "value": { "stringValue": "abcdef0123456789abcdef0123456789" } 67 | } 68 | } 69 | } 70 | } 71 | } -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-analytics-no-event-name.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventId": "analytics-1606955191246909-14239195990770746-5322866668684751502", 3 | "eventType": "providers/google.firebase.analytics/eventTypes/event.log", 4 | "resource": "projects/my-project-id/events", 5 | "timestamp": "2020-12-03T00:26:41.321623382Z", 6 | "data": { 7 | "eventDim": [ 8 | { 9 | "date": "20201202", 10 | "name": "session_start", 11 | "params": { 12 | "engaged_session_event": { "intValue": "1" }, 13 | "firebase_conversion": { "intValue": "1" }, 14 | "firebase_event_origin": { "stringValue": "auto" }, 15 | "firebase_screen": { "stringValue": "app_menu" }, 16 | "firebase_screen_class": { "stringValue": "AppActivity" }, 17 | "firebase_screen_id": { "intValue": "-2415111648950109400" }, 18 | "ga_session_id": { "intValue": "1606965190" }, 19 | "ga_session_number": { "intValue": "7" }, 20 | "session_engaged": { "intValue": "1" } 21 | }, 22 | "previousTimestampMicros": "1606951997533000", 23 | "timestampMicros": "1606955191246909" 24 | } 25 | ], 26 | "userDim": { 27 | "appInfo": { 28 | "appId": "com.example.exampleapp", 29 | "appInstanceId": "aaabbb11122233344455566677788899", 30 | "appPlatform": "ANDROID", 31 | "appStore": "com.android.vending", 32 | "appVersion": "1.67" 33 | }, 34 | "bundleInfo": { 35 | "bundleSequenceId": 58, 36 | "serverTimestampOffsetMicros": "875910" 37 | }, 38 | "deviceInfo": { 39 | "deviceCategory": "mobile", 40 | "deviceModel": "SM-A307G", 41 | "deviceTimeZoneOffsetSeconds": -10800, 42 | "mobileBrandName": "Samsung", 43 | "mobileMarketingName": "Galaxy A30s", 44 | "mobileModelName": "SM-A307G", 45 | "platformVersion": "10", 46 | "resettableDeviceId": "aaaaaa-1111-bbbb-2222-dddddddddddd", 47 | "userDefaultLanguage": "es-us" 48 | }, 49 | "firstOpenTimestampMicros": "1606882687506000", 50 | "geoInfo": { 51 | "city": "Burzaco", 52 | "continent": "005", 53 | "country": "Argentina", 54 | "region": "Buenos Aires Province" 55 | }, 56 | "userId": "0123456789abcdef0123456789abcdef", 57 | "userProperties": { 58 | "completed_tutorial": { 59 | "setTimestampUsec": "1606948068187909", 60 | "value": { "stringValue": "true" } 61 | }, 62 | "first_open_time": { 63 | "setTimestampUsec": "1606882688381909", 64 | "value": { "intValue": "1606885200000" } 65 | }, 66 | "last_level": { 67 | "index": 10, 68 | "setTimestampUsec": "1606952210498909", 69 | "value": { "stringValue": "school" } 70 | }, 71 | "user_id": { 72 | "setTimestampUsec": "1606955180040909", 73 | "value": { "stringValue": "abcdef0123456789abcdef0123456789" } 74 | } 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-analytics.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventId": "analytics-1606955191246909-14239195990770746-5322866668684751502", 3 | "eventType": "providers/google.firebase.analytics/eventTypes/event.log", 4 | "resource": "projects/my-project-id/events/session_start", 5 | "timestamp": "2020-12-03T00:26:41.321623382Z", 6 | "data": { 7 | "eventDim": [ 8 | { 9 | "date": "20201202", 10 | "name": "session_start", 11 | "params": { 12 | "engaged_session_event": { "intValue": "1" }, 13 | "firebase_conversion": { "intValue": "1" }, 14 | "firebase_event_origin": { "stringValue": "auto" }, 15 | "firebase_screen": { "stringValue": "app_menu" }, 16 | "firebase_screen_class": { "stringValue": "AppActivity" }, 17 | "firebase_screen_id": { "intValue": "-2415111648950109400" }, 18 | "ga_session_id": { "intValue": "1606965190" }, 19 | "ga_session_number": { "intValue": "7" }, 20 | "session_engaged": { "intValue": "1" } 21 | }, 22 | "previousTimestampMicros": "1606951997533000", 23 | "timestampMicros": "1606955191246909" 24 | } 25 | ], 26 | "userDim": { 27 | "appInfo": { 28 | "appId": "com.example.exampleapp", 29 | "appInstanceId": "aaabbb11122233344455566677788899", 30 | "appPlatform": "ANDROID", 31 | "appStore": "com.android.vending", 32 | "appVersion": "1.67" 33 | }, 34 | "bundleInfo": { 35 | "bundleSequenceId": 58, 36 | "serverTimestampOffsetMicros": "875910" 37 | }, 38 | "deviceInfo": { 39 | "deviceCategory": "mobile", 40 | "deviceModel": "SM-A307G", 41 | "deviceTimeZoneOffsetSeconds": -10800, 42 | "mobileBrandName": "Samsung", 43 | "mobileMarketingName": "Galaxy A30s", 44 | "mobileModelName": "SM-A307G", 45 | "platformVersion": "10", 46 | "resettableDeviceId": "aaaaaa-1111-bbbb-2222-dddddddddddd", 47 | "userDefaultLanguage": "es-us" 48 | }, 49 | "firstOpenTimestampMicros": "1606882687506000", 50 | "geoInfo": { 51 | "city": "Burzaco", 52 | "continent": "005", 53 | "country": "Argentina", 54 | "region": "Buenos Aires Province" 55 | }, 56 | "userId": "0123456789abcdef0123456789abcdef", 57 | "userProperties": { 58 | "completed_tutorial": { 59 | "setTimestampUsec": "1606948068187909", 60 | "value": { "stringValue": "true" } 61 | }, 62 | "first_open_time": { 63 | "setTimestampUsec": "1606882688381909", 64 | "value": { "intValue": "1606885200000" } 65 | }, 66 | "last_level": { 67 | "index": 10, 68 | "setTimestampUsec": "1606952210498909", 69 | "value": { "stringValue": "school" } 70 | }, 71 | "user_id": { 72 | "setTimestampUsec": "1606955180040909", 73 | "value": { "stringValue": "abcdef0123456789abcdef0123456789" } 74 | } 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-auth1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "email": "test@nowhere.com", 4 | "metadata": { 5 | "createdAt": "2020-05-26T10:42:27Z", 6 | "lastSignedInAt": "2020-05-29T11:00:00Z" 7 | }, 8 | "providerData": [ 9 | { 10 | "email": "test@nowhere.com", 11 | "providerId": "password", 12 | "uid": "test@nowhere.com" 13 | } 14 | ], 15 | "uid": "UUpby3s4spZre6kHsgVSPetzQ8l2" 16 | }, 17 | "eventId": "4423b4fa-c39b-4f79-b338-977a018e9b55", 18 | "eventType": "providers/firebase.auth/eventTypes/user.create", 19 | "notSupported": { 20 | }, 21 | "resource": "projects/my-project-id", 22 | "timestamp": "2020-05-26T10:42:27.088Z" 23 | } 24 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-auth2.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "email": "test@nowhere.com", 4 | "metadata": { 5 | "createdAt": "2020-05-26T10:42:27Z" 6 | }, 7 | "providerData": [ 8 | { 9 | "email": "test@nowhere.com", 10 | "providerId": "password", 11 | "uid": "test@nowhere.com" 12 | } 13 | ], 14 | "uid": "UUpby3s4spZre6kHsgVSPetzQ8l2" 15 | }, 16 | "eventId": "5fd71bdc-4955-421f-9fc3-552ac3abead8", 17 | "eventType": "providers/firebase.auth/eventTypes/user.delete", 18 | "notSupported": { 19 | }, 20 | "resource": "projects/my-project-id", 21 | "timestamp": "2020-05-26T10:47:14.205Z" 22 | } 23 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-db1.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventType": "providers/google.firebase.database/eventTypes/ref.write", 3 | "params": { 4 | "child": "xyz" 5 | }, 6 | "auth": { 7 | "admin": true 8 | }, 9 | "domain":"firebaseio.com", 10 | "data": { 11 | "data": null, 12 | "delta": { 13 | "grandchild": "other" 14 | } 15 | }, 16 | "resource": "projects/_/instances/my-project-id/refs/gcf-test/xyz", 17 | "timestamp": "2020-05-21T11:15:34.178Z", 18 | "eventId": "/SnHth9OSlzK1Puj85kk4tDbF90=" 19 | } 20 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-db2.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventType": "providers/google.firebase.database/eventTypes/ref.write", 3 | "params": { 4 | "child": "xyz" 5 | }, 6 | "auth": { 7 | "admin": true 8 | }, 9 | "domain":"europe-west1.firebasedatabase.app", 10 | "data": { 11 | "data": { 12 | "grandchild": "other" 13 | }, 14 | "delta": { 15 | "grandchild": "other changed" 16 | } 17 | }, 18 | "resource": "projects/_/instances/my-project-id/refs/gcf-test/xyz", 19 | "timestamp": "2020-05-21T11:17:09.217Z", 20 | "eventId": "xaU5mk9HaahKU/T/XlPo/1ansu8=" 21 | } 22 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-db3.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventType": "providers/google.firebase.database/eventTypes/ref.write", 3 | "params": { 4 | "child": "abc" 5 | }, 6 | "auth": { 7 | "admin": true 8 | }, 9 | "domain":"firebaseio.com", 10 | "data": { 11 | "data": null, 12 | "delta": 10 13 | }, 14 | "resource": "projects/_/instances/my-project-id/refs/gcf-test/abc", 15 | "timestamp": "2020-05-21T11:17:17.809Z", 16 | "eventId": "kbAmkXNSQhmARA0JB5lcCt1Zpsg=" 17 | } 18 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-db4.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventType": "providers/google.firebase.database/eventTypes/ref.write", 3 | "params": { 4 | "child": "xyz" 5 | }, 6 | "auth": { 7 | "admin": true 8 | }, 9 | "domain":"firebaseio.com", 10 | "data": { 11 | "data": { 12 | "grandchild": "other changed" 13 | }, 14 | "delta": { 15 | "deeply": { 16 | "nested": { 17 | "text": "This is deeply nested", 18 | "text2": "Second value" 19 | } 20 | } 21 | } 22 | }, 23 | "resource": "projects/_/instances/my-project-id/refs/gcf-test/xyz", 24 | "timestamp": "2020-05-21T11:19:22.677Z", 25 | "eventId": "AlEKy6dsGnn48ubD7Y5QOoROj4U=" 26 | } 27 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-db5.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventType": "providers/google.firebase.database/eventTypes/ref.write", 3 | "params": { 4 | "child": "xyz" 5 | }, 6 | "auth": { 7 | "admin": true 8 | }, 9 | "domain":"firebaseio.com", 10 | "data": { 11 | "data": { 12 | "deeply": { 13 | "nested": { 14 | "text": "This is deeply nested", 15 | "text2": "Second value" 16 | } 17 | }, 18 | "grandchild": "other changed" 19 | }, 20 | "delta": { 21 | "deeply": { 22 | "abc": "def" 23 | } 24 | } 25 | }, 26 | "resource": "projects/_/instances/my-project-id/refs/gcf-test/xyz", 27 | "timestamp": "2020-05-21T11:21:07.809Z", 28 | "eventId": "9WBpn32ssA33ympcsuq/5JJmnDM=" 29 | } -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-db6.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventType": "providers/google.firebase.database/eventTypes/ref.write", 3 | "params": { 4 | "child": "xyz" 5 | }, 6 | "auth": { 7 | "admin": true 8 | }, 9 | "domain":"firebaseio.com", 10 | "data": { 11 | "data": { 12 | "deeply": { 13 | "abc": "def", 14 | "nested": { 15 | "text": "This is deeply nested", 16 | "text2": "Second value" 17 | } 18 | }, 19 | "grandchild": "other changed" 20 | }, 21 | "delta": { 22 | "deeply": { 23 | "nested": null 24 | } 25 | } 26 | }, 27 | "resource": "projects/_/instances/my-project-id/refs/gcf-test/xyz", 28 | "timestamp": "2020-05-21T11:22:36.728Z", 29 | "eventId": "mtM1+41X04VNxshbrbl7ua3wnTI=" 30 | } 31 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-db7.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventType": "providers/google.firebase.database/eventTypes/ref.write", 3 | "params": { 4 | "child": "xyz" 5 | }, 6 | "auth": { 7 | "admin": true 8 | }, 9 | "domain":"firebaseio.com", 10 | "data": { 11 | "data": { 12 | "deeply": { 13 | "abc": "def" 14 | }, 15 | "grandchild": "other changed" 16 | }, 17 | "delta": { 18 | "deeply": { 19 | "abc": 10 20 | } 21 | } 22 | }, 23 | "resource": "projects/_/instances/my-project-id/refs/gcf-test/xyz", 24 | "timestamp": "2020-05-21T11:23:46.225Z", 25 | "eventId": "fvnCmPWm8q4PPEKFRfrjuNxbQ00=" 26 | } -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-db8.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventType": "providers/google.firebase.database/eventTypes/ref.write", 3 | "params": { 4 | }, 5 | "auth": { 6 | "admin": true 7 | }, 8 | "domain":"firebaseio.com", 9 | "data": { 10 | "data": { 11 | "gcf-test": { 12 | "abc": 10, 13 | "child1": "value1", 14 | "xyz": { 15 | "deeply": { 16 | "abc": 11 17 | }, 18 | "grandchild": "other changed" 19 | } 20 | }, 21 | "not-gcf-test": "Foo" 22 | }, 23 | "delta": { 24 | "gcf-test": { 25 | "xyz": { 26 | "deeply": { 27 | "abc": 12 28 | } 29 | } 30 | } 31 | } 32 | }, 33 | "resource": "projects/_/instances/my-project-id/refs/", 34 | "timestamp": "2020-05-21T11:33:01.789Z", 35 | "eventId": "MazIvLoUshV35XHwjH+2rfP7uvk=" 36 | } -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-dbdelete1.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventType": "providers/google.firebase.database/eventTypes/ref.delete", 3 | "params": { 4 | "child": "xyz" 5 | }, 6 | "auth": { 7 | "admin": true 8 | }, 9 | "domain":"firebaseio.com", 10 | "data": { 11 | "data": { 12 | "grandchild": "other changed" 13 | }, 14 | "delta": null 15 | }, 16 | "resource": "projects/_/instances/my-project-id/refs/gcf-test/xyz", 17 | "timestamp": "2020-05-21T11:53:45.337Z", 18 | "eventId": "oIcVXHEMZfhQMNs/yD4nwpuKE0s=" 19 | } -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-dbdelete2.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventType": "providers/google.firebase.database/eventTypes/ref.delete", 3 | "params": { 4 | "child": "abc" 5 | }, 6 | "auth": { 7 | "admin": true 8 | }, 9 | "domain":"firebaseio.com", 10 | "data": { 11 | "data": 10, 12 | "delta": null 13 | }, 14 | "resource": "projects/_/instances/my-project-id/refs/gcf-test/abc", 15 | "timestamp": "2020-05-21T11:56:12.833Z", 16 | "eventId": "KVLKeFKjFP2jepddr+EPGC0ZQ20=" 17 | } 18 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firebase-remote-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "context":{ 3 | "eventId":"4087a7d9-4c27-42f1-8a06-1f17b2d5fe49", 4 | "eventType":"google.firebase.remoteconfig.update", 5 | "resource":{ 6 | "name":"projects/sample-project", 7 | "service":"firebaseremoteconfig.googleapis.com" 8 | }, 9 | "timestamp":"2020-11-16T16:35:33.592Z" 10 | }, 11 | "data":{ 12 | "updateOrigin":"CONSOLE", 13 | "updateTime":"2020-11-16T16:35:33.569229Z", 14 | "updateType":"INCREMENTAL_UPDATE", 15 | "updateUser":{ 16 | "email":"test@nowhere.com" 17 | }, 18 | "versionNumber":"5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firestore_complex.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "oldValue": {}, 4 | "updateMask": {}, 5 | "value": { 6 | "createTime": "2020-04-23T14:25:05.349632Z", 7 | "fields": { 8 | "arrayValue": { 9 | "arrayValue": { 10 | "values": [ 11 | { 12 | "integerValue": "1" 13 | }, 14 | { 15 | "integerValue": "2" 16 | } 17 | ] 18 | } 19 | }, 20 | "booleanValue": { 21 | "booleanValue": true 22 | }, 23 | "doubleValue": { 24 | "doubleValue": 5.5 25 | }, 26 | "geoPointValue": { 27 | "geoPointValue": { 28 | "latitude": 51.4543, 29 | "longitude": -0.9781 30 | } 31 | }, 32 | "intValue": { 33 | "integerValue": "50" 34 | }, 35 | "mapValue": { 36 | "mapValue": { 37 | "fields": { 38 | "field1": { 39 | "stringValue": "x" 40 | }, 41 | "field2": { 42 | "arrayValue": { 43 | "values": [ 44 | { 45 | "stringValue": "x" 46 | }, 47 | { 48 | "integerValue": "1" 49 | } 50 | ] 51 | } 52 | } 53 | } 54 | } 55 | }, 56 | "nullValue": { 57 | "nullValue": null 58 | }, 59 | "referenceValue": { 60 | "referenceValue": "projects/project-id/databases/(default)/documents/foo/bar/baz/qux" 61 | }, 62 | "stringValue": { 63 | "stringValue": "text" 64 | }, 65 | "timestampValue": { 66 | "timestampValue": "2020-04-23T14:23:53.241Z" 67 | } 68 | }, 69 | "name": "projects/project-id/databases/(default)/documents/gcf-test/IH75dRdeYJKd4uuQiqch", 70 | "updateTime": "2020-04-23T14:25:05.349632Z" 71 | } 72 | }, 73 | "eventId": "9babded5-e5f2-41af-a46a-06ba6bd84739-0", 74 | "eventType": "providers/cloud.firestore/eventTypes/document.write", 75 | "notSupported": {}, 76 | "params": { 77 | "doc": "IH75dRdeYJKd4uuQiqch" 78 | }, 79 | "resource": "projects/project-id/databases/(default)/documents/gcf-test/IH75dRdeYJKd4uuQiqch", 80 | "timestamp": "2020-04-23T14:25:05.349632Z" 81 | } -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/firestore_simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "oldValue":{ 4 | "createTime":"2020-04-23T09:58:53.211035Z", 5 | "fields":{ 6 | "another test":{ 7 | "stringValue":"asd" 8 | }, 9 | "count":{ 10 | "integerValue":"3" 11 | }, 12 | "foo":{ 13 | "stringValue":"bar" 14 | } 15 | }, 16 | "name":"projects/project-id/databases/(default)/documents/gcf-test/2Vm2mI1d0wIaK2Waj5to", 17 | "updateTime":"2020-04-23T12:00:27.247187Z" 18 | }, 19 | "updateMask":{ 20 | "fieldPaths":[ 21 | "count" 22 | ] 23 | }, 24 | "value":{ 25 | "createTime":"2020-04-23T09:58:53.211035Z", 26 | "fields":{ 27 | "another test":{ 28 | "stringValue":"asd" 29 | }, 30 | "count":{ 31 | "integerValue":"4" 32 | }, 33 | "foo":{ 34 | "stringValue":"bar" 35 | } 36 | }, 37 | "name":"projects/project-id/databases/(default)/documents/gcf-test/2Vm2mI1d0wIaK2Waj5to", 38 | "updateTime":"2020-04-23T12:00:27.247187Z" 39 | } 40 | }, 41 | "eventId":"7b8f1804-d38b-4b68-b37d-e2fb5d12d5a0-0", 42 | "eventType":"providers/cloud.firestore/eventTypes/document.write", 43 | "notSupported":{ 44 | 45 | }, 46 | "params":{ 47 | "doc":"2Vm2mI1d0wIaK2Waj5to" 48 | }, 49 | "resource":"projects/project-id/databases/(default)/documents/gcf-test/2Vm2mI1d0wIaK2Waj5to", 50 | "timestamp":"2020-04-23T12:00:27.247187Z" 51 | } 52 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/legacy_pubsub.json: -------------------------------------------------------------------------------- 1 | { 2 | "eventId": "1215011316659232", 3 | "timestamp": "2020-05-18T12:13:19.209Z", 4 | "eventType": "providers/cloud.pubsub/eventTypes/topic.publish", 5 | "resource": "projects/sample-project/topics/gcf-test", 6 | "data": { 7 | "@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage", 8 | "attributes": { 9 | "attribute1": "value1" 10 | }, 11 | "data": "VGhpcyBpcyBhIHNhbXBsZSBtZXNzYWdl" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/legacy_storage_change.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "bucket": "sample-bucket", 4 | "crc32c": "AAAAAA==", 5 | "etag": "COu8mb3Dn+kCEAE=", 6 | "generation": "1588778055917163", 7 | "id": "sample-bucket/MyFile/1588778055917163", 8 | "kind": "storage#object", 9 | "md5Hash": "ZDQxZDhjZDk4ZjAwYjIwNGU5ODAwOTk4ZWNmODQyN2U=", 10 | "mediaLink": "https://www.googleapis.com/download/storage/v1/b/projectid-sample-bucket/o/MyFile?generation=1588778055917163\u0026alt=media", 11 | "metageneration": "1", 12 | "name": "MyFile", 13 | "resourceState": "not_exists", 14 | "selfLink": "https://www.googleapis.com/storage/v1/b/projectid-sample-bucket/o/MyFile", 15 | "size": "0", 16 | "storageClass": "MULTI_REGIONAL", 17 | "timeCreated": "2020-05-06T15:14:15.917Z", 18 | "timeDeleted": "2020-05-18T09:07:51.799Z", 19 | "timeStorageClassUpdated": "2020-05-06T15:14:15.917Z", 20 | "updated": "2020-05-06T15:14:15.917Z" 21 | }, 22 | "eventId": "1200401551653202", 23 | "eventType": "providers/cloud.storage/eventTypes/object.change", 24 | "resource": "projects/_/buckets/sample-bucket/objects/MyFile#1588778055917163", 25 | "timestamp": "2020-05-18T09:07:51.799Z" 26 | } 27 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/pubsub_binary.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "eventId":"1144231683168617", 4 | "timestamp":"2020-05-06T07:33:34.556Z", 5 | "eventType":"google.pubsub.topic.publish", 6 | "resource":{ 7 | "service":"pubsub.googleapis.com", 8 | "name":"projects/sample-project/topics/gcf-test", 9 | "type":"type.googleapis.com/google.pubsub.v1.PubsubMessage" 10 | } 11 | }, 12 | "data": { 13 | "@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage", 14 | "data": "AQIDBA==" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/pubsub_text.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "eventId":"1144231683168617", 4 | "timestamp":"2020-05-06T07:33:34.556Z", 5 | "eventType":"google.pubsub.topic.publish", 6 | "resource":{ 7 | "service":"pubsub.googleapis.com", 8 | "name":"projects/sample-project/topics/gcf-test", 9 | "type":"type.googleapis.com/google.pubsub.v1.PubsubMessage" 10 | } 11 | }, 12 | "data": { 13 | "@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage", 14 | "attributes": { 15 | "attr1":"attr1-value" 16 | }, 17 | "data": "dGVzdCBtZXNzYWdlIDM=" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/pubsub_text_microsecond_precision.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "eventId":"1144231683168617", 4 | "timestamp":"2020-05-06T07:33:34.556123Z", 5 | "eventType":"google.pubsub.topic.publish", 6 | "resource":{ 7 | "service":"pubsub.googleapis.com", 8 | "name":"projects/sample-project/topics/gcf-test", 9 | "type":"type.googleapis.com/google.pubsub.v1.PubsubMessage" 10 | } 11 | }, 12 | "data": { 13 | "@type": "type.googleapis.com/google.pubsub.v1.PubsubMessage", 14 | "attributes": { 15 | "attr1":"attr1-value" 16 | }, 17 | "data": "dGVzdCBtZXNzYWdlIDM=" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/raw_pubsub.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": { 3 | "attributes": { "attr1":"value1" }, 4 | "data": "VGV4dCBtZXNzYWdl", 5 | "messageId":"4102184774039362", 6 | "message_id":"4102184774039362", 7 | "orderingKey":"orderxyz", 8 | "publishTime":"2022-02-15T11:28:32.942Z", 9 | "publish_time":"2022-02-15T11:28:32.942Z" 10 | }, 11 | "subscription":"projects/sample-project/subscriptions/sample-subscription" 12 | } -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/GcfEvents/storage.json: -------------------------------------------------------------------------------- 1 | { 2 | "context": { 3 | "eventId": "1147091835525187", 4 | "timestamp": "2020-04-23T07:38:57.772Z", 5 | "eventType": "google.storage.object.finalize", 6 | "resource": { 7 | "service": "storage.googleapis.com", 8 | "name": "projects/_/buckets/some-bucket/objects/folder/Test.cs", 9 | "type": "storage#object" 10 | } 11 | }, 12 | "data": { 13 | "bucket": "some-bucket", 14 | "contentType": "text/plain", 15 | "crc32c": "rTVTeQ==", 16 | "etag": "CNHZkbuF/ugCEAE=", 17 | "generation": "1587627537231057", 18 | "id": "some-bucket/folder/Test.cs/1587627537231057", 19 | "kind": "storage#object", 20 | "md5Hash": "kF8MuJ5+CTJxvyhHS1xzRg==", 21 | "mediaLink": "https://www.googleapis.com/download/storage/v1/b/some-bucket/o/folder%2FTest.cs?generation=1587627537231057\u0026alt=media", 22 | "metageneration": "1", 23 | "name": "folder/Test.cs", 24 | "selfLink": "https://www.googleapis.com/storage/v1/b/some-bucket/o/folder/Test.cs", 25 | "size": "352", 26 | "storageClass": "MULTI_REGIONAL", 27 | "timeCreated": "2020-04-23T07:38:57.230Z", 28 | "timeStorageClassUpdated": "2020-04-23T07:38:57.230Z", 29 | "updated": "2020-04-23T07:38:57.230Z" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/OpenFunction.Framework.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0 4 | false 5 | true 6 | ../OpenFunction.snk 7 | true 8 | Enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework.Tests/TestResourceHelper.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.IO; 17 | 18 | namespace OpenFunction.Framework.Tests 19 | { 20 | /// 21 | /// Helper methods to make it simpler to load embedded resources. 22 | /// 23 | internal class TestResourceHelper 24 | { 25 | public static Stream LoadResource(System.Type type, string relativeResourceName) 26 | { 27 | // In order to make diagnostics simpler, we load the whole resource into a MemoryStream 28 | // and return that. 29 | MemoryStream ret = new MemoryStream(); 30 | using (var resource = type.Assembly.GetManifestResourceStream(type, relativeResourceName)) 31 | { 32 | if (resource is null) 33 | { 34 | throw new ArgumentException($"Resource {relativeResourceName} not found"); 35 | } 36 | resource.CopyTo(ret); 37 | } 38 | ret.Position = 0; 39 | return ret; 40 | } 41 | 42 | public static Stream LoadResource(string relativeResourceName) => 43 | LoadResource(typeof(T), relativeResourceName); 44 | 45 | public static Stream LoadResource(object caller, string relativeResourceName) => 46 | LoadResource(caller.GetType(), relativeResourceName); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Runtime.CompilerServices; 16 | 17 | [assembly: InternalsVisibleTo("OpenFunction.Framework.Tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100afab79952ee22215f12b4e09337e65509c943fbc22d7006bc371d581d0f0ebf0da5d8039aab2607fb68a138a5d80a71bc02b7ebf586dbe1f2493c0ab20423ababfd15ce74d2264a6b37745f3658f016abaad662182aaef634a60f1346fcc45343acab5b6781535a3134818e13fac895a6c106c0480e34bbb06cb123e5583d8d2")] 18 | 19 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework/GcfEvents/Context.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Text.Json.Serialization; 17 | 18 | namespace OpenFunction.Framework.GcfEvents 19 | { 20 | /// 21 | /// The context of a GCF event. 22 | /// 23 | internal sealed class Context 24 | { 25 | // Note: maps to CloudEvents ID 26 | /// 27 | /// A unique ID for the event. 28 | /// 29 | [JsonPropertyName("eventId")] 30 | public string? Id { get; set; } 31 | 32 | // Note: maps to CloudEvents time (optional) 33 | /// 34 | /// The date/time this event was created. 35 | /// 36 | [JsonPropertyName("timestamp")] 37 | public DateTimeOffset? Timestamp { get; set; } 38 | 39 | // Note: maps to CloudEvents type (possibly via additional mapping) 40 | /// 41 | /// The type of the event. For example: "google.pubsub.topic.publish". 42 | /// 43 | [JsonPropertyName("eventType")] 44 | public string? Type { get; set; } 45 | 46 | // Maps somewhat to the CloudEvents subject (optional) and subject (required) 47 | /// 48 | /// The resource associated with the event. 49 | /// 50 | [JsonPropertyName("resource")] 51 | public Resource Resource { get; set; } = new Resource(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework/GcfEvents/Request.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | using System.Collections.Generic; 17 | using System.Text.Json.Serialization; 18 | 19 | namespace OpenFunction.Framework.GcfEvents 20 | { 21 | internal sealed class Request 22 | { 23 | [JsonPropertyName("context")] 24 | public Context Context { get; set; } = new Context(); 25 | 26 | [JsonPropertyName("data")] 27 | public Dictionary Data { get; set; } 28 | 29 | [JsonPropertyName("resource")] 30 | public string? Resource { get; set; } 31 | 32 | [JsonPropertyName("timestamp")] 33 | public DateTimeOffset? Timestamp { get; set; } 34 | 35 | [JsonPropertyName("eventType")] 36 | public string? EventType { get; set; } 37 | 38 | [JsonPropertyName("eventId")] 39 | public string? EventId { get; set; } 40 | 41 | [JsonPropertyName("params")] 42 | public dynamic? Params { get; set; } 43 | 44 | [JsonPropertyName("domain")] 45 | public string? Domain { get; set; } 46 | 47 | /// 48 | /// Raw PubSub only: the subscription triggering the request. 49 | /// 50 | [JsonPropertyName("subscription")] 51 | public string? RawPubSubSubscription { get; set; } 52 | 53 | /// 54 | /// Raw PubSub only: the PubSub message. 55 | /// 56 | [JsonPropertyName("message")] 57 | public Dictionary? RawPubSubMessage { get; set; } 58 | 59 | public Request() 60 | { 61 | Context = null!; 62 | Data = null!; 63 | } 64 | 65 | /// 66 | /// Copies any top-level data into the context. Data 67 | /// already present in the context "wins". 68 | /// 69 | public void NormalizeContext() 70 | { 71 | Context ??= new Context(); 72 | Context.Resource ??= new Resource(); 73 | Context.Resource.Name ??= Resource; 74 | Context.Id ??= EventId; 75 | Context.Timestamp ??= Timestamp; 76 | Context.Type ??= EventType; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework/GcfEvents/Resource.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Text.Json.Serialization; 16 | 17 | namespace OpenFunction.Framework.GcfEvents 18 | { 19 | /// 20 | /// The resource that an event applies to. 21 | /// 22 | public sealed class Resource 23 | { 24 | /// 25 | /// The service that triggered the event. 26 | /// 27 | [JsonPropertyName("service")] 28 | public string? Service { get; set; } 29 | 30 | /// 31 | /// The name associated with the event. 32 | /// 33 | [JsonPropertyName("name")] 34 | public string? Name { get; set; } 35 | 36 | /// 37 | /// The type of the resource. 38 | /// 39 | [JsonPropertyName("type")] 40 | public string? Type { get; set; } 41 | 42 | // TODO: Do we want raw path? It looks like it's deprecated. 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework/ICloudEventFunction.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using CloudNative.CloudEvents; 16 | using System.Threading; 17 | using System.Threading.Tasks; 18 | 19 | namespace OpenFunction.Framework 20 | { 21 | /// 22 | /// Function accepting a CloudEvent without performing any type-specific data deserialization. 23 | /// 24 | public interface ICloudEventFunction 25 | { 26 | /// 27 | /// Asynchronously handles the specified CloudEvent. 28 | /// 29 | /// The CloudEvent extracted from the request. 30 | /// A cancellation token which indicates if the request is aborted. 31 | /// A task representing the potentially-asynchronous handling of the event. 32 | /// If the task completes, the function is deemed to be successful. 33 | Task HandleAsync(CloudEvent cloudEvent, CancellationToken cancellationToken); 34 | } 35 | 36 | /// 37 | /// Function accepting a CloudEvent expecting a particular data type, which is automatically deserialized 38 | /// before function invocation. 39 | /// 40 | /// The expected type of the CloudEvent data. This type must be decorated 41 | /// with to indicate how to deserialize the CloudEvent. 42 | public interface ICloudEventFunction where TData : class 43 | { 44 | /// 45 | /// Asynchronously handles the specified CloudEvent. 46 | /// 47 | /// The original CloudEvent extracted from the request. 48 | /// The deserialized object constructed from the data. 49 | /// A cancellation token which indicates if the request is aborted. 50 | /// A task representing the potentially-asynchronous handling of the event. 51 | /// If the task completes, the function is deemed to be successful. 52 | Task HandleAsync(CloudEvent cloudEvent, TData data, CancellationToken cancellationToken); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework/IHttpFunction.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Microsoft.AspNetCore.Http; 16 | using System.Threading.Tasks; 17 | 18 | namespace OpenFunction.Framework 19 | { 20 | /// 21 | /// A simple HTTP function, which populates the . 22 | /// 23 | public interface IHttpFunction 24 | { 25 | /// 26 | /// Asynchronously handles the request in , 27 | /// populating to indicate the result. 28 | /// 29 | /// The HTTP context containing the request and response. 30 | /// A task to indicate when the request is complete. 31 | Task HandleAsync(HttpContext context); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework/OpenFunction.Framework.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | OpenFunction Framework 6 | enable 7 | OpenFunction Framework 8 | Openfunction 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/OpenFunction.Framework/Preconditions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | 17 | namespace OpenFunction.Framework 18 | { 19 | /// 20 | /// Simple preconditions class, internal to avoid any conflicts and compatibility issues. 21 | /// 22 | internal static class Preconditions 23 | { 24 | internal static T CheckNotNull(T value, string paramName) => 25 | value ?? throw new ArgumentNullException(paramName); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting.Tests/ApplicationConfigurationTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Framework; 16 | using Microsoft.AspNetCore.Hosting; 17 | using Microsoft.AspNetCore.Http; 18 | using Microsoft.AspNetCore.TestHost; 19 | using Microsoft.Extensions.DependencyInjection; 20 | using Microsoft.Extensions.Hosting; 21 | using System.Net; 22 | using System.Threading.Tasks; 23 | using Xunit; 24 | 25 | namespace OpenFunction.Hosting.Tests 26 | { 27 | /// 28 | /// Tests for the final part of configuring the "application". 29 | /// 30 | public partial class ApplicationConfigurationTest 31 | { 32 | [Theory] 33 | [InlineData("robots.txt", HttpStatusCode.NotFound, "")] 34 | [InlineData("favicon.ico", HttpStatusCode.NotFound, "")] 35 | [InlineData("foo.txt", HttpStatusCode.OK, "Test message")] 36 | public async Task PathHandling(string path, HttpStatusCode expectedStatus, string expectedText) 37 | { 38 | var builder = Host.CreateDefaultBuilder() 39 | .ConfigureWebHostDefaults(webHostBuilder => webHostBuilder 40 | .ConfigureServices(services => services.AddFunctionTarget()) 41 | .Configure((context, app) => app.UseFunctionsFramework(context)) 42 | .UseTestServer()); 43 | using var server = await builder.StartAsync(); 44 | using var client = server.GetTestServer().CreateClient(); 45 | using var response = await client.GetAsync(path); 46 | Assert.Equal(expectedStatus, response.StatusCode); 47 | var content = await response.Content.ReadAsStringAsync(); 48 | Assert.Equal(expectedText, content); 49 | } 50 | 51 | public class SimpleFunction : IHttpFunction 52 | { 53 | public async Task HandleAsync(HttpContext context) 54 | { 55 | await context.Response.WriteAsync("Test message"); 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting.Tests/FunctionsStartupAttributeTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Hosting; 16 | using System; 17 | using System.Collections.Generic; 18 | using System.Text; 19 | using Xunit; 20 | 21 | [assembly: FunctionsStartup(typeof(OpenFunction.Hosting.Tests.FunctionsStartupAttributeTest.TestStartup1), Order = 4)] 22 | [assembly: FunctionsStartup(typeof(OpenFunction.Hosting.Tests.FunctionsStartupAttributeTest.TestStartup2), Order = 2)] 23 | 24 | namespace OpenFunction.Hosting.Tests 25 | { 26 | public class FunctionsStartupAttributeTest 27 | { 28 | // Startups used for attribute detection. 29 | public class TestStartup1 : FunctionsStartup { } 30 | public class TestStartup2 : FunctionsStartup { } 31 | public class TestStartup3 : FunctionsStartup { } 32 | 33 | [FunctionsStartup(typeof(TestStartup3), Order = 3)] 34 | public class TargetTypeWithAttribute 35 | { 36 | } 37 | 38 | // This is the only test that uses the assembly attribute detection. 39 | // Everything else specifies the startups explicitly. 40 | [Fact] 41 | public void GetStartupTypes_NoType() 42 | { 43 | var actualTypes = FunctionsStartupAttribute.GetStartupTypes(typeof(FunctionsStartupAttributeTest).Assembly, null); 44 | // TestStartup2 comes before TestStartup1 due to the Order properties. 45 | var expectedTypes = new[] { typeof(TestStartup2), typeof(TestStartup1) }; 46 | Assert.Equal(expectedTypes, actualTypes); 47 | } 48 | 49 | [Fact] 50 | public void GetStartupTypes_WithType() 51 | { 52 | var actualTypes = FunctionsStartupAttribute.GetStartupTypes(typeof(FunctionsStartupAttributeTest).Assembly, typeof(TargetTypeWithAttribute)); 53 | // Ordering is based on the Order properties 54 | var expectedTypes = new[] { typeof(TestStartup2), typeof(TestStartup3), typeof(TestStartup1) }; 55 | Assert.Equal(expectedTypes, actualTypes); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting.Tests/OpenFunction.Hosting.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0 4 | false 5 | true 6 | ../OpenFunction.snk 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Runtime.CompilerServices; 16 | 17 | [assembly: InternalsVisibleTo("OpenFunction.Hosting.Tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100afab79952ee22215f12b4e09337e65509c943fbc22d7006bc371d581d0f0ebf0da5d8039aab2607fb68a138a5d80a71bc02b7ebf586dbe1f2493c0ab20423ababfd15ce74d2264a6b37745f3658f016abaad662182aaef634a60f1346fcc45343acab5b6781535a3134818e13fac895a6c106c0480e34bbb06cb123e5583d8d2")] 18 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting/Extensions/FunctionsFrameworkConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | using OpenFunction.Hosting; 15 | 16 | // Namespace by convention 17 | namespace Microsoft.Extensions.Configuration 18 | { 19 | /// 20 | /// Extensions for adding configuration for the Functions Framework. 21 | /// 22 | public static class FunctionsFrameworkConfigurationExtensions 23 | { 24 | /// 25 | /// Adds a configuration source for the Functions Framework based on environment variables (e.g. PORT and FUNCTION_TARGET). 26 | /// 27 | /// The configuration builder to add the source to. 28 | /// The original builder, for method chaining. 29 | public static IConfigurationBuilder AddFunctionsEnvironment(this IConfigurationBuilder builder) => 30 | builder.Add(new FunctionsEnvironmentVariablesConfigurationSource()); 31 | 32 | /// 33 | /// Adds a configuration source for the Functions Framework based on command line arguments. 34 | /// 35 | /// The configuration builder to add the source to. 36 | /// The command line arguments to use for configuration. 37 | /// The original builder, for method chaining. 38 | public static IConfigurationBuilder AddFunctionsCommandLine(this IConfigurationBuilder builder, string[] args) => 39 | HostingInternals.AddCommandLineArguments(builder, args); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting/Extensions/FunctionsFrameworkLoggingExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using OpenFunction.Hosting; 16 | using OpenFunction.Hosting.Logging; 17 | using Microsoft.AspNetCore.Hosting; 18 | 19 | // Namespace by convention 20 | namespace Microsoft.Extensions.Logging 21 | { 22 | /// 23 | /// Extensions for configuration logging for the Functions Framework. 24 | /// 25 | public static class FunctionsFrameworkLoggingExtensions 26 | { 27 | /// 28 | /// Adds a Functions Framework console logger, either using a "single line per log entry" 29 | /// plain text format (with separate lines for scopes and exceptions, when present) or a JSON format, 30 | /// depending on the execution environment. 31 | /// 32 | /// The logging builder to add the logger to. 33 | /// The context of the web host builder being configured. 34 | /// The original builder, for method chaining. 35 | public static ILoggingBuilder AddFunctionsConsoleLogging(this ILoggingBuilder builder, WebHostBuilderContext context) 36 | { 37 | var options = FunctionsFrameworkOptions.FromConfiguration(context.Configuration); 38 | ILoggerProvider provider = options.JsonLogging 39 | ? new FactoryLoggerProvider(category => new JsonConsoleLogger(category, System.Console.Out)) 40 | : new FactoryLoggerProvider(category => new SimpleConsoleLogger(category, System.Console.Out)); 41 | builder.AddProvider(provider); 42 | return builder; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting/FunctionsFrameworkOptions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Microsoft.AspNetCore.Hosting; 16 | using Microsoft.Extensions.Configuration; 17 | using System.Net; 18 | 19 | namespace OpenFunction.Hosting 20 | { 21 | /// 22 | /// Convenience type to allow options to be bound within . 23 | /// 24 | internal sealed class FunctionsFrameworkOptions 25 | { 26 | internal const string ConfigSection = "FunctionsFramework"; 27 | 28 | public int Port { get; set; } = 8080; 29 | public string? FunctionTarget { get; set; } 30 | public string? Address { get; set; } 31 | public bool JsonLogging { get; set; } 32 | 33 | internal IPAddress GetIPAddress() => 34 | Address switch 35 | { 36 | "Any" => IPAddress.Any, 37 | "Loopback" => IPAddress.Loopback, 38 | null => IPAddress.Loopback, 39 | string addr => IPAddress.Parse(addr) 40 | }; 41 | 42 | internal static FunctionsFrameworkOptions FromConfiguration(IConfiguration configuration) => 43 | configuration.GetSection(ConfigSection).Get() ?? new FunctionsFrameworkOptions(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting/Logging/FactoryLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Microsoft.Extensions.Logging; 16 | using System; 17 | 18 | namespace OpenFunction.Hosting.Logging 19 | { 20 | /// 21 | /// Simple logger provider that just calls a factory method each time it's asked for logger. 22 | /// 23 | internal class FactoryLoggerProvider : ILoggerProvider 24 | { 25 | private readonly Func _factory; 26 | 27 | internal FactoryLoggerProvider(Func factory) => 28 | _factory = factory; 29 | 30 | public ILogger CreateLogger(string categoryName) => _factory(categoryName); 31 | 32 | public void Dispose() 33 | { 34 | // No-op 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting/OpenFunction.Hosting.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | OpenFunction Framework Hosting 6 | The OpenFunction Framework Hosting package makes it easy to run a web server to execute functions. 7 | OpenFunction 8 | enable 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting/Preconditions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | 17 | namespace OpenFunction.Hosting 18 | { 19 | /// 20 | /// Simple preconditions class, internal to avoid any conflicts and compatibility issues. 21 | /// 22 | internal static class Preconditions 23 | { 24 | internal static T CheckNotNull(T value, string paramName) => 25 | value ?? throw new ArgumentNullException(paramName); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/OpenFunction.Hosting/targets/OpenFunction.Hosting.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/OpenFunction.Templates.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Template 7 | $(VERSION) 8 | Templates for OpenFunction Framework 9 | Templates for OpenFunction Framework 10 | dotnet-new;templates;openfunction 11 | 12 | true 13 | false 14 | content 15 | 16 | 17 | $(NoWarn);NU5128 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-cs/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/OpenFunction.Templates/templates/event-function-cs/.template.config/icon.png -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-cs/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "icon": "icon.png" 4 | } 5 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-cs/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "OpenFunction", 4 | "classifications": [ "OpenFunction" ], 5 | "identity": "OpenFunction.Templates.CloudEventFunction.CSharp", 6 | "groupIdentity": "OpenFunction.CloudEventFunction", 7 | "name": "OpenFunction CloudEvent Function", 8 | "description": "A project template for creating an OpenFunction to respond to a specific CloudEvent type.", 9 | "shortName": "of-event", 10 | "sourceName": "MyFunction", 11 | "defaultName": "CloudEventFunction", 12 | "tags": { 13 | "language": "C#", 14 | "type": "project" 15 | }, 16 | "preferNameDirectory": true 17 | } -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-cs/Function.cs: -------------------------------------------------------------------------------- 1 | using CloudNative.CloudEvents; 2 | using OpenFunction.Framework; 3 | using Google.Events.Protobuf.Cloud.Storage.V1; 4 | using System; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace MyFunction 9 | { 10 | /// 11 | /// A function that can be triggered in responses to changes in Google Cloud Storage. 12 | /// The type argument (StorageObjectData in this case) determines how the event payload is deserialized. 13 | /// The function must be deployed so that the trigger matches the expected payload type. (For example, 14 | /// deploying a function expecting a StorageObject payload will not work for a trigger that provides 15 | /// a FirestoreEvent.) 16 | /// 17 | public class Function : ICloudEventFunction 18 | { 19 | /// 20 | /// Logic for your function goes here. Note that a CloudEvent function just consumes an event; 21 | /// it doesn't provide any response. 22 | /// 23 | /// The CloudEvent your function should consume. 24 | /// The deserialized data within the CloudEvent. 25 | /// A cancellation token that is notified if the request is aborted. 26 | /// A task representing the asynchronous operation. 27 | public Task HandleAsync(CloudEvent cloudEvent, StorageObjectData data, CancellationToken cancellationToken) 28 | { 29 | Console.WriteLine("Storage object information:"); 30 | Console.WriteLine($" Name: {data.Name}"); 31 | Console.WriteLine($" Bucket: {data.Bucket}"); 32 | Console.WriteLine($" Size: {data.Size}"); 33 | Console.WriteLine($" Content type: {data.ContentType}"); 34 | Console.WriteLine("CloudEvent information:"); 35 | Console.WriteLine($" ID: {cloudEvent.Id}"); 36 | Console.WriteLine($" Source: {cloudEvent.Source}"); 37 | Console.WriteLine($" Type: {cloudEvent.Type}"); 38 | Console.WriteLine($" Subject: {cloudEvent.Subject}"); 39 | Console.WriteLine($" DataSchema: {cloudEvent.DataSchema}"); 40 | Console.WriteLine($" DataContentType: {cloudEvent.DataContentType}"); 41 | Console.WriteLine($" Time: {cloudEvent.Time?.ToUniversalTime():yyyy-MM-dd'T'HH:mm:ss.fff'Z'}"); 42 | Console.WriteLine($" SpecVersion: {cloudEvent.SpecVersion}"); 43 | 44 | // In this example, we don't need to perform any asynchronous operations, so the 45 | // method doesn't need to be declared async. 46 | return Task.CompletedTask; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-cs/MyFunction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-fs/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/OpenFunction.Templates/templates/event-function-fs/.template.config/icon.png -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-fs/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "icon": "icon.png" 4 | } 5 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-fs/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "OpenFunction", 4 | "classifications": [ "OpenFunction" ], 5 | "identity": "OpenFunction.Templates.CloudEventFunction.FSharp", 6 | "groupIdentity": "OpenFunction.CloudEventFunction", 7 | "name": "OpenFunction CloudEvent Function", 8 | "description": "A project template for creating an OpenFunction to respond to a specific CloudEvent type.", 9 | "shortName": "of-event", 10 | "sourceName": "MyFunction", 11 | "defaultName": "CloudEventFunction", 12 | "tags": { 13 | "language": "F#", 14 | "type": "project" 15 | }, 16 | "preferNameDirectory": true 17 | } -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-fs/Function.fs: -------------------------------------------------------------------------------- 1 | namespace MyFunction 2 | 3 | open CloudNative.CloudEvents 4 | open OpenFunction.Framework 5 | open Google.Events.Protobuf.Cloud.Storage.V1 6 | open System.Threading.Tasks 7 | 8 | /// 9 | /// A function that can be triggered in responses to changes in Google Cloud Storage. 10 | /// The type argument (StorageObjectData in this case) determines how the event payload is deserialized. 11 | /// The function must be deployed so that the trigger matches the expected payload type. (For example, 12 | /// deploying a function expecting a StorageObject payload will not work for a trigger that provides 13 | /// a FirestoreEvent.) 14 | /// 15 | type Function() = 16 | interface ICloudEventFunction with 17 | /// 18 | /// Logic for your function goes here. Note that a CloudEvent function just consumes an event; 19 | /// it doesn't provide any response. 20 | /// 21 | /// The CloudEvent your function should consume. 22 | /// The deserialized data within the CloudEvent. 23 | /// A cancellation token that is notified if the request is aborted. 24 | /// A task representing the asynchronous operation. 25 | member this.HandleAsync(cloudEvent, data, cancellationToken) = 26 | printfn "Storage object information:" 27 | printfn " Name: %s" data.Name 28 | printfn " Bucket: %s" data.Bucket 29 | printfn " Size: %i" data.Size 30 | printfn " Content type: %s" data.ContentType 31 | printfn "CloudEvent information:" 32 | printfn " ID: %s" cloudEvent.Id 33 | printfn " Source: %O" cloudEvent.Source 34 | printfn " Type: %s" cloudEvent.Type 35 | printfn " Subject: %s" cloudEvent.Subject 36 | printfn " DataSchema: %O" cloudEvent.DataSchema 37 | printfn " DataContentType: %O" cloudEvent.DataContentType 38 | printfn " Time: %s" (match Option.ofNullable cloudEvent.Time with 39 | | Some time -> time.ToUniversalTime().ToString "yyyy-MM-dd'T'HH:mm:ss.fff'Z'" 40 | | None -> "") 41 | printfn " SpecVersion: %O" cloudEvent.SpecVersion 42 | 43 | // In this example, we don't need to perform any asynchronous operations, so we 44 | // just return an completed Task to conform to the interface. 45 | Task.CompletedTask 46 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-fs/MyFunction.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-vb/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/OpenFunction.Templates/templates/event-function-vb/.template.config/icon.png -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-vb/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "icon": "icon.png" 4 | } 5 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-vb/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "OpenFunction", 4 | "classifications": [ "OpenFunction" ], 5 | "identity": "OpenFunction.Templates.CloudEventFunction.VisualBasic", 6 | "groupIdentity": "OpenFunction.CloudEventFunction", 7 | "name": "OpenFunction CloudEvent Function", 8 | "description": "A project template for creating an OpenFunction to respond to a specific CloudEvent type.", 9 | "shortName": "of-event", 10 | "sourceName": "MyFunction", 11 | "defaultName": "CloudEventFunction", 12 | "tags": { 13 | "language": "VB", 14 | "type": "project" 15 | }, 16 | "preferNameDirectory": true 17 | } -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-vb/CloudFunction.vb: -------------------------------------------------------------------------------- 1 | Imports CloudNative.CloudEvents 2 | Imports OpenFunction.Framework 3 | Imports Google.Events.Protobuf.Cloud.Storage.V1 4 | Imports System.Threading 5 | 6 | ''' 7 | ''' A function that can be triggered in responses to changes in Google Cloud Storage. 8 | ''' The type argument (StorageObjectData in this case) determines how the event payload is deserialized. 9 | ''' The function must be deployed so that the trigger matches the expected payload type. (For example, 10 | ''' deploying a function expecting a StorageObject payload will not work for a trigger that provides 11 | ''' a FirestoreEvent.) 12 | ''' 13 | Public Class CloudFunction 14 | Implements ICloudEventFunction(Of StorageObjectData) 15 | 16 | ''' 17 | ''' Logic for your function goes here. Note that a CloudEvent function just consumes an event; 18 | ''' it doesn't provide any response. 19 | ''' 20 | ''' The CloudEvent your function should consume. 21 | ''' The deserialized data within the CloudEvent. 22 | ''' A cancellation token that is notified if the request is aborted. 23 | ''' A task representing the asynchronous operation. 24 | Public Function HandleAsync(cloudEvent As CloudEvent, data As StorageObjectData, cancellationToken As CancellationToken) As Task _ 25 | Implements ICloudEventFunction(Of StorageObjectData).HandleAsync 26 | Console.WriteLine("Storage object information:") 27 | Console.WriteLine($" Name: {data.Name}") 28 | Console.WriteLine($" Bucket: {data.Bucket}") 29 | Console.WriteLine($" Size: {data.Size}") 30 | Console.WriteLine($" Content type: {data.ContentType}") 31 | Console.WriteLine("CloudEvent information:") 32 | Console.WriteLine($" ID: {cloudEvent.Id}") 33 | Console.WriteLine($" Source: {cloudEvent.Source}") 34 | Console.WriteLine($" Type: {cloudEvent.Type}") 35 | Console.WriteLine($" Subject: {cloudEvent.Subject}") 36 | Console.WriteLine($" DataSchema: {cloudEvent.DataSchema}") 37 | Console.WriteLine($" DataContentType: {cloudEvent.DataContentType}") 38 | Console.WriteLine($" Time: {cloudEvent.Time?.ToUniversalTime():yyyy-MM-dd'T'HH:mm:ss.fff'Z'}") 39 | Console.WriteLine($" SpecVersion: {cloudEvent.SpecVersion}") 40 | ' In this example, we don't need to perform any asynchronous operations, so the 41 | ' function doesn't need to be declared as Async. 42 | Return Task.CompletedTask 43 | End Function 44 | End Class 45 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/event-function-vb/MyFunction.vbproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | MyFunction 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-cs/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/OpenFunction.Templates/templates/http-function-cs/.template.config/icon.png -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-cs/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "icon": "icon.png" 4 | } 5 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-cs/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "OpenFunction", 4 | "classifications": [ "OpenFunction" ], 5 | "identity": "OpenFunction.Templates.HttpFunction.CSharp", 6 | "groupIdentity": "OpenFunction.HttpFunction", 7 | "name": "OpenFunction HttpFunction", 8 | "description": "A project template for creating an OpenFunction to respond to HTTP requests.", 9 | "shortName": "of-http", 10 | "sourceName": "MyFunction", 11 | "defaultName": "CloudHttpFunction", 12 | "tags": { 13 | "language": "C#", 14 | "type": "project" 15 | }, 16 | "preferNameDirectory": true 17 | } -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-cs/Function.cs: -------------------------------------------------------------------------------- 1 | using OpenFunction.Framework; 2 | using Microsoft.AspNetCore.Http; 3 | using System.Threading.Tasks; 4 | 5 | namespace MyFunction 6 | { 7 | public class Function : IHttpFunction 8 | { 9 | /// 10 | /// Logic for your function goes here. 11 | /// 12 | /// The HTTP context, containing the request and the response. 13 | /// A task representing the asynchronous operation. 14 | public async Task HandleAsync(HttpContext context) 15 | { 16 | await context.Response.WriteAsync("Hello, Functions Framework."); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-cs/MyFunction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-fs/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/OpenFunction.Templates/templates/http-function-fs/.template.config/icon.png -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-fs/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "icon": "icon.png" 4 | } 5 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-fs/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "OpenFunction", 4 | "classifications": [ "OpenFunction" ], 5 | "identity": "OpenFunction.Templates.HttpFunction.FSharp", 6 | "groupIdentity": "OpenFunction.HttpFunction", 7 | "name": "Google Cloud Functions HttpFunction", 8 | "description": "A project template for creating an OpenFunction to respond to HTTP requests.", 9 | "shortName": "of-http", 10 | "sourceName": "MyFunction", 11 | "defaultName": "CloudHttpFunction", 12 | "tags": { 13 | "language": "F#", 14 | "type": "project" 15 | }, 16 | "preferNameDirectory": true 17 | } -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-fs/Function.fs: -------------------------------------------------------------------------------- 1 | namespace MyFunction 2 | 3 | open OpenFunction.Framework 4 | open Microsoft.AspNetCore.Http 5 | 6 | type Function() = 7 | interface IHttpFunction with 8 | /// 9 | /// Logic for your function goes here. 10 | /// 11 | /// The HTTP context, containing the request and the response. 12 | /// A task representing the asynchronous operation. 13 | member this.HandleAsync context = 14 | async { 15 | do! context.Response.WriteAsync "Hello, Functions Framework." |> Async.AwaitTask 16 | } |> Async.StartAsTask :> _ 17 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-fs/MyFunction.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-vb/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/OpenFunction.Templates/templates/http-function-vb/.template.config/icon.png -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-vb/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "icon": "icon.png" 4 | } 5 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-vb/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "OpenFunction", 4 | "classifications": [ "OpenFunction" ], 5 | "identity": "OpenFunction.Templates.HttpFunction.VisualBasic", 6 | "groupIdentity": "OpenFunction.HttpFunction", 7 | "name": "OpenFunction HttpFunction", 8 | "description": "A project template for creating an OpenFunction to respond to HTTP requests.", 9 | "shortName": "of-http", 10 | "sourceName": "MyFunction", 11 | "defaultName": "CloudHttpFunction", 12 | "tags": { 13 | "language": "VB", 14 | "type": "project" 15 | }, 16 | "preferNameDirectory": true 17 | } -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-vb/CloudFunction.vb: -------------------------------------------------------------------------------- 1 | Imports OpenFunction.Framework 2 | Imports Microsoft.AspNetCore.Http 3 | 4 | Public Class CloudFunction 5 | Implements IHttpFunction 6 | 7 | ''' 8 | ''' Logic for your function goes here. 9 | ''' 10 | ''' The HTTP context, containing the request and the response. 11 | ''' A task representing the asynchronous operation. 12 | Public Async Function HandleAsync(context As HttpContext) As Task Implements IHttpFunction.HandleAsync 13 | Await context.Response.WriteAsync("Hello, Functions Framework.") 14 | End Function 15 | End Class 16 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/http-function-vb/MyFunction.vbproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | MyFunction 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-cs/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/OpenFunction.Templates/templates/untyped-event-function-cs/.template.config/icon.png -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-cs/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "icon": "icon.png" 4 | } 5 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-cs/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "OpenFunction", 4 | "classifications": [ "OpenFunction" ], 5 | "identity": "OpenFunction.Templates.UntypedCloudEventFunction.CSharp", 6 | "groupIdentity": "OpenFunction.UntypedCloudEventFunction", 7 | "name": "OpenFunction CloudEvent Function (Untyped)", 8 | "description": "A project template for creating an OpenFunction to respond to any CloudEvent.", 9 | "shortName": "of-untyped-event", 10 | "sourceName": "MyFunction", 11 | "defaultName": "CloudEventFunction", 12 | "tags": { 13 | "language": "C#", 14 | "type": "project" 15 | }, 16 | "preferNameDirectory": true 17 | } -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-cs/Function.cs: -------------------------------------------------------------------------------- 1 | using CloudNative.CloudEvents; 2 | using OpenFunction.Framework; 3 | using System; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace MyFunction 8 | { 9 | public class Function : ICloudEventFunction 10 | { 11 | /// 12 | /// Logic for your function goes here. Note that a CloudEvent function just consumes an event; 13 | /// it doesn't provide any response. 14 | /// 15 | /// The CloudEvent your function should consume. 16 | /// A cancellation token that is notified if the request is aborted. 17 | /// A task representing the asynchronous operation. 18 | public Task HandleAsync(CloudEvent cloudEvent, CancellationToken cancellationToken) 19 | { 20 | Console.WriteLine("CloudEvent information:"); 21 | Console.WriteLine($"ID: {cloudEvent.Id}"); 22 | Console.WriteLine($"Source: {cloudEvent.Source}"); 23 | Console.WriteLine($"Type: {cloudEvent.Type}"); 24 | Console.WriteLine($"Subject: {cloudEvent.Subject}"); 25 | Console.WriteLine($"DataSchema: {cloudEvent.DataSchema}"); 26 | Console.WriteLine($"DataContentType: {cloudEvent.DataContentType}"); 27 | Console.WriteLine($"Time: {cloudEvent.Time?.ToUniversalTime():yyyy-MM-dd'T'HH:mm:ss.fff'Z'}"); 28 | Console.WriteLine($"SpecVersion: {cloudEvent.SpecVersion}"); 29 | Console.WriteLine($"Data: {cloudEvent.Data}"); 30 | 31 | // In this example, we don't need to perform any asynchronous operations, so the 32 | // method doesn't need to be declared async. 33 | return Task.CompletedTask; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-cs/MyFunction.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-fs/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/OpenFunction.Templates/templates/untyped-event-function-fs/.template.config/icon.png -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-fs/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "icon": "icon.png" 4 | } 5 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-fs/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "OpenFunction", 4 | "classifications": [ "OpenFunction" ], 5 | "identity": "OpenFunction.Templates.UntypedCloudEventFunction.FSharp", 6 | "groupIdentity": "OpenFunction.UntypedCloudEventFunction", 7 | "name": "OpenFunction CloudEvent Function (Untyped)", 8 | "description": "A project template for creating an OpenFunction to respond to any CloudEvent.", 9 | "shortName": "of-untyped-event", 10 | "sourceName": "MyFunction", 11 | "defaultName": "CloudEventFunction", 12 | "tags": { 13 | "language": "F#", 14 | "type": "project" 15 | }, 16 | "preferNameDirectory": true 17 | } -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-fs/Function.fs: -------------------------------------------------------------------------------- 1 | namespace MyFunction 2 | 3 | open OpenFunction.Framework 4 | open System.Threading.Tasks 5 | 6 | type Function() = 7 | interface ICloudEventFunction with 8 | /// 9 | /// Logic for your function goes here. Note that a CloudEvent function just consumes an event; 10 | /// it doesn't provide any response. 11 | /// 12 | /// The CloudEvent your function should consume. 13 | /// A cancellation token that is notified if the request is aborted. 14 | /// A task representing the asynchronous operation. 15 | member this.HandleAsync(cloudEvent, cancellationToken) = 16 | printfn "CloudEvent information:" 17 | printfn "ID: %s" cloudEvent.Id 18 | printfn "Source: %A" cloudEvent.Source 19 | printfn "Type: %s" cloudEvent.Type 20 | printfn "Subject: %s" cloudEvent.Subject 21 | printfn "DataSchema: %A" cloudEvent.DataSchema 22 | printfn "DataContentType: %O" cloudEvent.DataContentType 23 | printfn "Time: %s" (match Option.ofNullable cloudEvent.Time with 24 | | Some time -> time.ToUniversalTime().ToString "yyyy-MM-dd'T'HH:mm:ss.fff'Z'" 25 | | None -> "") 26 | printfn "SpecVersion: %O" cloudEvent.SpecVersion 27 | printfn "Data: %A" cloudEvent.Data 28 | 29 | // In this example, we don't need to perform any asynchronous operations, so we 30 | // just return a completed Task to conform to the interface. 31 | Task.CompletedTask 32 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-fs/MyFunction.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-vb/.template.config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/OpenFunction.Templates/templates/untyped-event-function-vb/.template.config/icon.png -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-vb/.template.config/ide.host.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vs-2017.3.host", 3 | "icon": "icon.png" 4 | } 5 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-vb/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/template", 3 | "author": "OpenFunction", 4 | "classifications": [ "OpenFunction" ], 5 | "identity": "OpenFunction.Templates.UntypedCloudEventFunction.VisualBasic", 6 | "groupIdentity": "OpenFunction.UntypedCloudEventFunction", 7 | "name": "OpenFunction CloudEvent Function (Untyped)", 8 | "description": "A project template for creating an OpenFunction to respond to any CloudEvent.", 9 | "shortName": "of-untyped-event", 10 | "sourceName": "MyFunction", 11 | "defaultName": "CloudEventFunction", 12 | "tags": { 13 | "language": "VB", 14 | "type": "project" 15 | }, 16 | "preferNameDirectory": true 17 | } -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-vb/CloudFunction.vb: -------------------------------------------------------------------------------- 1 | Imports CloudNative.CloudEvents 2 | Imports OpenFunction.Framework 3 | Imports System.Threading 4 | 5 | Public Class CloudFunction 6 | Implements ICloudEventFunction 7 | 8 | ''' 9 | ''' Logic for your function goes here. Note that a CloudEvent function just consumes an event; 10 | ''' it doesn't provide any response. 11 | ''' 12 | ''' The CloudEvent your function should consume. 13 | ''' A cancellation token that is notified if the request is aborted. 14 | ''' A task representing the asynchronous operation. 15 | Public Function HandleAsync(cloudEvent As CloudEvent, cancellationToken As CancellationToken) As Task _ 16 | Implements ICloudEventFunction.HandleAsync 17 | Console.WriteLine("CloudEvent information:") 18 | Console.WriteLine($"ID: {cloudEvent.Id}") 19 | Console.WriteLine($"Source: {cloudEvent.Source}") 20 | Console.WriteLine($"Type: {cloudEvent.Type}") 21 | Console.WriteLine($"Subject: {cloudEvent.Subject}") 22 | Console.WriteLine($"DataSchema: {cloudEvent.DataSchema}") 23 | Console.WriteLine($"DataContentType: {cloudEvent.DataContentType}") 24 | Console.WriteLine($"Time: {cloudEvent.Time?.ToUniversalTime():yyyy-MM-dd'T'HH:mm:ss.fff'Z'}") 25 | Console.WriteLine($"SpecVersion: {cloudEvent.SpecVersion}") 26 | Console.WriteLine($"Data: {cloudEvent.Data}") 27 | 28 | ' In this example, we don't need to perform any asynchronous operations, so the 29 | ' function doesn't need to be declared as Async. 30 | Return Task.CompletedTask 31 | End Function 32 | End Class 33 | -------------------------------------------------------------------------------- /src/OpenFunction.Templates/templates/untyped-event-function-vb/MyFunction.vbproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | MyFunction 5 | net6.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/OpenFunction.Testing.Tests/FunctionTestBaseTest.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using CloudNative.CloudEvents; 16 | using OpenFunction.Framework; 17 | using Google.Events; 18 | using Google.Events.Protobuf.Cloud.Storage.V1; 19 | using Microsoft.Extensions.Logging; 20 | using System; 21 | using System.Threading; 22 | using System.Threading.Tasks; 23 | using Xunit; 24 | 25 | namespace OpenFunction.Testing.Tests 26 | { 27 | public class FunctionTestBaseTest 28 | { 29 | [Fact] 30 | public async Task ExecuteCloudEventAsync_CloudEvent() 31 | { 32 | var cloudEvent = new CloudEvent 33 | { 34 | Id = "event-id", 35 | Type = StorageObjectData.FinalizedCloudEventType, 36 | Source = new Uri("//storage", UriKind.RelativeOrAbsolute), 37 | Data = new StorageObjectData { Name = "test1" } 38 | }; 39 | 40 | using var test = new Test(); 41 | await test.ExecuteCloudEventRequestAsync(cloudEvent); 42 | var log = Assert.Single(test.GetFunctionLogEntries()); 43 | Assert.Equal("Name: test1", log.Message); 44 | } 45 | 46 | [Fact] 47 | public async Task ExecuteCloudEventAsync_Components() 48 | { 49 | using var test = new Test(); 50 | await test.ExecuteCloudEventRequestAsync( 51 | StorageObjectData.FinalizedCloudEventType, 52 | new StorageObjectData { Name = "test2" }); 53 | var log = Assert.Single(test.GetFunctionLogEntries()); 54 | Assert.Equal("Name: test2", log.Message); 55 | } 56 | 57 | // FunctionTestBase is abstract; this class just allows us to test multiple functions 58 | // easily in one class. 59 | private class Test : FunctionTestBase 60 | { 61 | } 62 | 63 | private class StorageObjectFunction : ICloudEventFunction 64 | { 65 | private ILogger _logger; 66 | 67 | public StorageObjectFunction(ILogger logger) => 68 | _logger = logger; 69 | 70 | public Task HandleAsync(CloudEvent cloudEvent, StorageObjectData data, CancellationToken cancellationToken) 71 | { 72 | _logger.LogInformation($"Name: {data.Name}"); 73 | return Task.CompletedTask; 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/OpenFunction.Testing.Tests/OpenFunction.Testing.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net6.0 4 | false 5 | true 6 | ../OpenFunction.snk 7 | true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/OpenFunction.Testing/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System.Runtime.CompilerServices; 16 | 17 | [assembly: InternalsVisibleTo("OpenFunction.Hosting.Tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100afab79952ee22215f12b4e09337e65509c943fbc22d7006bc371d581d0f0ebf0da5d8039aab2607fb68a138a5d80a71bc02b7ebf586dbe1f2493c0ab20423ababfd15ce74d2264a6b37745f3658f016abaad662182aaef634a60f1346fcc45343acab5b6781535a3134818e13fac895a6c106c0480e34bbb06cb123e5583d8d2")] 18 | -------------------------------------------------------------------------------- /src/OpenFunction.Testing/MemoryLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using Microsoft.Extensions.Logging; 16 | using System.Collections.Concurrent; 17 | using System.Collections.Generic; 18 | 19 | namespace OpenFunction.Testing 20 | { 21 | /// 22 | /// A logger provider that creates instances of . 23 | /// 24 | internal sealed class MemoryLoggerProvider : ILoggerProvider 25 | { 26 | private readonly ConcurrentDictionary _loggersByCategory = 27 | new ConcurrentDictionary(); 28 | 29 | public ILogger CreateLogger(string categoryName) => 30 | _loggersByCategory.GetOrAdd(categoryName, name => new MemoryLogger(name)); 31 | 32 | internal void Clear() => _loggersByCategory.Clear(); 33 | 34 | /// 35 | /// Returns a list of log entries for the given category name. If no logs have been 36 | /// written for the given category, an empty list is returned. 37 | /// 38 | /// The category name for which to get log entries. 39 | /// A list of log entries for the given category name. 40 | internal List GetLogEntries(string categoryName) => 41 | _loggersByCategory.TryGetValue(categoryName, out var logger) 42 | ? logger.ListLogEntries() : new List(); 43 | 44 | public void Dispose() 45 | { 46 | // No-op 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/OpenFunction.Testing/OpenFunction.Testing.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | OpenFunction Framework Testing 6 | Simplifies tests for OpenFunction Framework functions. 7 | openfunction 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/OpenFunction.Testing/Preconditions.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | using System; 16 | 17 | namespace OpenFunction.Testing 18 | { 19 | /// 20 | /// Simple preconditions class, internal to avoid any conflicts and compatibility issues. 21 | /// 22 | internal static class Preconditions 23 | { 24 | internal static T CheckNotNull(T value, string paramName) => 25 | value ?? throw new ArgumentNullException(paramName); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/OpenFunction.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenFunction/functions-framework-dotnet/89267fa0a726ed8fbebdcdf522e9c227347e3e53/src/OpenFunction.snk -------------------------------------------------------------------------------- /update-googleevents-references.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [[ -z "$1" ]] 6 | then 7 | echo 'Please specify the Google.Events.* version to update to.' 8 | exit 1 9 | fi 10 | 11 | VERSION=$1 12 | 13 | for proj in $(find src -name '*proj') $(find examples -name '*proj') 14 | do 15 | sed -i -e "s/Include=\"Google\.Events\" Version=\".*\"/Include=\"Google.Events\" Version=\"$VERSION\"/g" $proj 16 | sed -i -e "s/Include=\"Google\.Events\.SystemTextJson\" Version=\".*\"/Include=\"Google.Events.SystemTextJson\" Version=\"$VERSION\"/g" $proj 17 | sed -i -e "s/Include=\"Google\.Events\.Protobuf\" Version=\".*\"/Include=\"Google.Events.Protobuf\" Version=\"$VERSION\"/g" $proj 18 | done 19 | -------------------------------------------------------------------------------- /update-project-references.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | VERSION=$(grep '.*' src/CommonProperties.xml | sed -e 's/<\/\?Version>//g' | sed 's/ //g') 6 | 7 | for proj in $(find src/OpenFunction.Templates/templates -name '*proj') 8 | do 9 | sed -i -e "s/Include=\"OpenFunction.Hosting\" Version=\".*\"/Include=\"OpenFunction.Hosting\" Version=\"$VERSION\"/g" $proj 10 | done 11 | 12 | for proj in $(find examples -name '*proj') 13 | do 14 | sed -i -e "s/Include=\"OpenFunction.Hosting\" Version=\".*\"/Include=\"OpenFunction.Hosting\" Version=\"$VERSION\"/g" $proj 15 | sed -i -e "s/Include=\"OpenFunction.Testing\" Version=\".*\"/Include=\"OpenFunction.Testing\" Version=\"$VERSION\"/g" $proj 16 | done 17 | --------------------------------------------------------------------------------