├── .gitignore ├── MAINTAINERS.md ├── .github ├── workflows │ ├── check-wip.yaml │ ├── ps-ce-sdk-unit-tests.yaml │ ├── ps-ce-sdk-integration-tests.yaml │ ├── ps-sdk-stale.yaml │ └── ps-ce-sdk-release.yaml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── .chglog ├── config.yml └── CHANGELOG.tpl.md ├── test ├── integration │ ├── ProtocolConstants.ps1 │ ├── HttpServer.ps1 │ └── HttpIntegration.Tests.ps1 ├── unit │ ├── Read-CloudEventData.Tests.ps1 │ ├── New-CloudEvent.Tests.ps1 │ ├── Set-CloudEventJsonData.Tests.ps1 │ ├── Read-CloudEventJsonData.Tests.ps1 │ ├── Set-CloudEventXmlData.Tests.ps1 │ ├── Read-CloudEventXmlData.Tests.ps1 │ ├── Set-CloudEventData.Tests.ps1 │ ├── ConvertTo-HttpMessage.Tests.ps1 │ └── ConvertFrom-HttpMessage.Tests.ps1 └── RunTests.ps1 ├── src └── CloudEventsPowerShell │ ├── CloudEventsPowerShell.csproj │ ├── CloudEvents.Sdk.psd1 │ ├── dataserialization │ ├── xml.ps1 │ └── xml.Tests.ps1 │ └── CloudEvents.Sdk.psm1 ├── publish.ps1 ├── RELEASING.md ├── CONTRIBUTING.md ├── CHANGELOG.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | **/.vs 2 | **/bin 3 | **/obj 4 | /CloudEvents 5 | /CloudEvents.Sdk -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers 2 | 3 | Current active maintainers of this SDK: 4 | 5 | - [William Lam](https://github.com/lamw) 6 | - [Mark Peek](https://github.com/markpeek 7 | - [Dimitar Milov](https://github.com/dmilov) 8 | - [Michael Gasch](https://github.com/embano1) 9 | - [Simeon Gerginov](https://github.com/SimeonGerginov) 10 | -------------------------------------------------------------------------------- /.github/workflows/check-wip.yaml: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | name: Check "WIP" in PR Title 7 | 8 | on: 9 | pull_request: 10 | types: [opened, synchronize, reopened, edited] 11 | 12 | jobs: 13 | wip: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Check WIP in PR Title 17 | uses: embano1/wip@v2 18 | -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/cloudevents/sdk-powershell 6 | options: 7 | commits: 8 | filters: 9 | Type: 10 | - fix 11 | - feat 12 | - chore 13 | commit_groups: 14 | title_maps: 15 | fix: 🐞 Fix 16 | feat: 💫 Feature 17 | chore: 🧹 Chore 18 | header: 19 | pattern: "^(\\w*)\\:\\s(.*)$" 20 | pattern_maps: 21 | - Type 22 | - Subject 23 | refs: 24 | actions: 25 | - Closes 26 | - Fixes 27 | notes: 28 | keywords: 29 | - "BREAKING" -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 16 | 1. 17 | 1. 18 | 1. 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Affected version** 24 | Please provide details on the version used, e.g. release tag, commit, module version, etc. 25 | 26 | **Screenshots** 27 | If applicable, add screenshots to help explain your problem. 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /test/integration/ProtocolConstants.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | New-Variable -Option Constant -Scope 'script' -Name 'ClientSource' -Value 'ps:test:client' 7 | New-Variable -Option Constant -Scope 'script' -Name 'ServerSource' -Value 'ps:test:server' 8 | New-Variable -Option Constant -Scope 'script' -Name 'EchoBinaryType' -Value 'echo-binary' 9 | New-Variable -Option Constant -Scope 'script' -Name 'EchoStructuredType' -Value 'echo-structured' 10 | New-Variable -Option Constant -Scope 'script' -Name 'ServerStopType' -Value 'server-stop' 11 | New-Variable -Option Constant -Scope 'script' -Name 'ServerPingType' -Value 'server-ping' -------------------------------------------------------------------------------- /.github/workflows/ps-ce-sdk-unit-tests.yaml: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | name: Unit Tests 7 | 8 | on: 9 | push: 10 | branches: ["main"] 11 | 12 | pull_request: 13 | branches: ["main"] 14 | 15 | jobs: 16 | unit-tests: 17 | name: Run Unit Tests 18 | strategy: 19 | matrix: 20 | platform: ["ubuntu-latest", "windows-latest"] 21 | 22 | runs-on: ${{ matrix.platform }} 23 | timeout-minutes: 10 24 | 25 | steps: 26 | - name: Check out code 27 | uses: actions/checkout@v2 28 | 29 | - name: Run Tests 30 | shell: pwsh 31 | run: ./build.ps1 -TestsType unit -ExitProcess 32 | -------------------------------------------------------------------------------- /.github/workflows/ps-ce-sdk-integration-tests.yaml: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | name: Integration Tests 7 | 8 | on: 9 | push: 10 | branches: ["main"] 11 | 12 | pull_request: 13 | branches: ["main"] 14 | 15 | jobs: 16 | integration-tests: 17 | name: Run Integration Tests 18 | strategy: 19 | matrix: 20 | platform: ["ubuntu-latest", "windows-latest"] 21 | 22 | runs-on: ${{ matrix.platform }} 23 | timeout-minutes: 10 24 | 25 | steps: 26 | - name: Check out code 27 | uses: actions/checkout@v2 28 | 29 | - name: Run Tests 30 | shell: pwsh 31 | run: ./build.ps1 -TestsType integration -ExitProcess 32 | -------------------------------------------------------------------------------- /src/CloudEventsPowerShell/CloudEventsPowerShell.csproj: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | CloudEventsPowerShell 10 | CloudEventsPowerShell 11 | CloudEvents PowerShell SDK. 12 | netstandard2.0 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/unit/Read-CloudEventData.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | Describe "Read-CloudEventData Function Tests" { 7 | Context "Extracts Data from CloudEvent" { 8 | It 'Reads xml text data' { 9 | # Arrange 10 | $cloudEvent = New-CloudEvent ` 11 | -Id ([Guid]::NewGuid()) ` 12 | -Type test ` 13 | -Source 'urn:test' 14 | 15 | 16 | $expectedData = '' 17 | $expectedDataContentType = 'text/xml' 18 | 19 | $cloudEvent = $cloudEvent | Set-CloudEventData -Data $expectedData -DataContentType $expectedDataContentType 20 | 21 | # Act 22 | $actual = $cloudEvent | Read-CloudEventData 23 | 24 | # Assert 25 | $actual | Should -Not -Be $null 26 | $actual | Should -Be $expectedData 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /.chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | {{ range .Versions }} 2 | 3 | ## {{ if .Tag.Previous }}[Release {{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} 4 | 5 | > Release Date: {{ datetime "2006-01-02" .Tag.Date }} 6 | 7 | {{ range .CommitGroups -}} 8 | ### {{ .Title }} 9 | 10 | {{ range .Commits -}} 11 | - [{{ .Hash.Short }}]{{"\t"}}{{ .Subject }}{{ range .Refs }} (#{{ .Ref }}) {{ end }} 12 | {{ end }} 13 | {{ end -}} 14 | 15 | {{- if .RevertCommits -}} 16 | ### ⏮ Reverts 17 | 18 | {{ range .RevertCommits -}} 19 | - [{{ .Hash.Short }}]{{"\t"}}{{ .Revert.Header }}{{ range .Refs }} (#{{ .Ref }}) {{ end }} 20 | {{ end }} 21 | {{ end -}} 22 | 23 | ### ⚠️ BREAKING 24 | 25 | {{ range .Commits -}} 26 | {{ if .Notes -}} 27 | {{ if not .Merge -}} 28 | {{ if not (contains .Header "Update CHANGELOG for" ) -}} 29 | {{ .Subject }} [{{ .Hash.Short }}]:{{"\n"}}{{ range .Notes }}{{ .Body }} 30 | {{ end }} 31 | {{ end -}} 32 | {{ end -}} 33 | {{ end -}} 34 | {{ end -}} 35 | 36 | ### 📖 Commits 37 | 38 | {{ range .Commits -}} 39 | {{ if not .Merge -}} 40 | {{ if not (contains .Header "Update CHANGELOG for" ) -}} 41 | - [{{ .Hash.Short }}]{{"\t"}}{{ .Header }}{{ range .Refs }} (#{{ .Ref }}) {{ end }} 42 | {{ end -}} 43 | {{ end -}} 44 | {{ end -}} 45 | 46 | {{ end -}} -------------------------------------------------------------------------------- /.github/workflows/ps-sdk-stale.yaml: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | name: Close Stale 7 | 8 | on: 9 | schedule: 10 | - cron: "0 1 * * *" # daily 11 | 12 | jobs: 13 | stale: 14 | runs-on: "ubuntu-latest" 15 | 16 | steps: 17 | - uses: "actions/stale@v3" 18 | with: 19 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 20 | 21 | stale-issue-message: |- 22 | This issue is stale because it has been open for 90 days with no 23 | activity. It will automatically close after 30 more days of 24 | inactivity. Mark as fresh by adding the comment `/remove-lifecycle stale`. 25 | stale-issue-label: "lifecycle/stale" 26 | exempt-issue-labels: "lifecycle/frozen" 27 | 28 | stale-pr-message: |- 29 | This Pull Request is stale because it has been open for 90 days with 30 | no activity. It will automatically close after 30 more days of 31 | inactivity. Mark as fresh by adding the comment `/remove-lifecycle stale`. 32 | stale-pr-label: "lifecycle/stale" 33 | exempt-pr-labels: "lifecycle/frozen" 34 | 35 | days-before-stale: 90 36 | days-before-close: 30 -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also 4 | include relevant motivation and context. List any dependencies that are required 5 | for this change. 6 | 7 | Closes: #(issue-number) 8 | 9 | ## Type of change 10 | 11 | Please mark options that are relevant: 12 | 13 | - [ ] Bug fix (non-breaking change which fixes an issue) 14 | - [ ] New feature (non-breaking change which adds functionality) 15 | - [ ] Breaking change (fix or feature that would cause existing functionality to 16 | not work as expected) 17 | - [ ] This change requires a documentation update 18 | 19 | ## How Has This Been Tested? 20 | 21 | Please describe the tests that you ran to verify your changes. Provide 22 | instructions so we can reproduce. If applicable, please also list any relevant 23 | details for your test configuration. 24 | 25 | - [ ] Test Description 1 26 | - [ ] Test Description 2 27 | 28 | **Test Configuration**: 29 | * Toolchain: 30 | * SDK: 31 | * (add more if needed) 32 | 33 | ## Checklist: 34 | 35 | - [ ] My code follows the contribution 36 | [guidelines](https://github.com/cloudevents/sdk-powershell/blob/main/CONTRIBUTING.md) 37 | of this project 38 | - [ ] I have commented my code, particularly in hard-to-understand areas 39 | - [ ] I have made corresponding changes to the documentation 40 | - [ ] I have added tests that prove my fix is effective or that my feature works 41 | - [ ] New and existing unit tests pass locally with my changes 42 | - [ ] Any dependent changes have been merged and published in downstream modules -------------------------------------------------------------------------------- /test/RunTests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | param( 7 | [Parameter()] 8 | [ValidateScript({Test-Path $_})] 9 | [string] 10 | $CloudEventsModulePath, 11 | 12 | [Parameter()] 13 | [ValidateSet('unit', 'integration', 'all')] 14 | [string] 15 | $TestsType, 16 | 17 | [Parameter()] 18 | [Switch] 19 | $EnableProcessExit 20 | ) 21 | 22 | Import-Module $CloudEventsModulePath 23 | 24 | if ($TestsType -eq 'unit' -or $TestsType -eq 'all') { 25 | $pesterContainer = New-PesterContainer -Path (Join-Path $PSScriptRoot 'unit') 26 | $pesterConfiguration = [PesterConfiguration]::Default 27 | 28 | $pesterConfiguration.Run.Path = (Join-Path $PSScriptRoot 'unit') 29 | $pesterConfiguration.Run.Container = $pesterContainer 30 | $pesterConfiguration.Run.Exit = $EnableProcessExit.IsPresent 31 | 32 | Invoke-Pester -Configuration $pesterConfiguration 33 | } 34 | 35 | if ($TestsType -eq 'integration' -or $TestsType -eq 'all') { 36 | 37 | $testsData = @{ 38 | CloudEventsModulePath = $CloudEventsModulePath 39 | } 40 | 41 | $pesterContainer = New-PesterContainer -Path (Join-Path $PSScriptRoot 'integration') -Data $testsData 42 | $pesterConfiguration = [PesterConfiguration]::Default 43 | 44 | $pesterConfiguration.Run.Path = (Join-Path $PSScriptRoot 'integration') 45 | $pesterConfiguration.Run.Container = $pesterContainer 46 | $pesterConfiguration.Run.Exit = $EnableProcessExit.IsPresent 47 | 48 | Invoke-Pester -Configuration $pesterConfiguration 49 | } 50 | -------------------------------------------------------------------------------- /test/unit/New-CloudEvent.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | Describe "New-CloudEvent Function Tests" { 7 | Context "Create CloudEvent Object" { 8 | It 'Create CloudEvent with required parameters only' { 9 | # Arrange 10 | $expectedType = 'test' 11 | $expectedSource = 'urn:test' 12 | $expectedId = ([Guid]::NewGuid().ToString()) 13 | 14 | # Act 15 | $actual = New-CloudEvent ` 16 | -Id $expectedId ` 17 | -Type $expectedType ` 18 | -Source $expectedSource 19 | 20 | # Assert 21 | $actual | Should -Not -Be $null 22 | $actual.Type | Should -Be $expectedType 23 | $actual.Source | Should -Be $expectedSource 24 | $actual.Id | Should -Be $expectedId 25 | } 26 | 27 | It 'Create CloudEvent with all possible parameters' { 28 | # Arrange 29 | $expectedType = 'test' 30 | $expectedSource = 'urn:test' 31 | $expectedId = 'test-id-1' 32 | $expectedTime = Get-Date -Year 2021 -Month 1 -Day 18 -Hour 12 -Minute 30 -Second 0 33 | 34 | # Act 35 | $actual = New-CloudEvent ` 36 | -Type $expectedType ` 37 | -Source $expectedSource ` 38 | -Id $expectedId ` 39 | -Time $expectedTime 40 | 41 | # Assert 42 | $actual | Should -Not -Be $null 43 | $actual.Type | Should -Be $expectedType 44 | $actual.Source | Should -Be $expectedSource 45 | $actual.Id | Should -Be $expectedId 46 | $actual.Time | Should -Be $expectedTime 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /publish.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | <# 7 | .SYNOPSIS 8 | Publish the CloudEvents.Sdk module to PSGallery 9 | 10 | .PARAMETER NuGetApiKey 11 | PowerShell Gallery API Key to be used to publish the module 12 | 13 | .PARAMETER ModuleReleaseDir 14 | Parent directory of the 'CloudEvents.Sdk' module that will be published 15 | #> 16 | 17 | param( 18 | [Parameter(Mandatory = $true)] 19 | [string] 20 | $NuGetApiKey, 21 | 22 | [Parameter(Mandatory = $true)] 23 | [ValidateScript({ Test-Path $_ })] 24 | [string] 25 | $ModuleReleaseDir 26 | ) 27 | 28 | # Work with full path in case relative path is provided 29 | $ModuleReleaseDir = (Resolve-Path $ModuleReleaseDir).Path 30 | 31 | $moduleName = 'CloudEvents.Sdk' 32 | 33 | # Build is successful and all tests pass 34 | $env:PSModulePath += [IO.Path]::PathSeparator + $ModuleReleaseDir 35 | 36 | $localModule = Get-Module $moduleName -ListAvailable 37 | $psGalleryModule = Find-Module -Name $moduleName -Repository PSGallery 38 | 39 | # Throw an exception if module with the same version is availble on PSGallery 40 | if ( $null -ne $psGalleryModule -and ` 41 | $null -ne $localModule -and ` 42 | $psGalleryModule.Version -eq $localModule.Version ) { 43 | throw "'$moduleName' module with version '$($localModule.Version)' is already available on PSGallery" 44 | } 45 | 46 | Write-Host "Performing operation: Publish-Module -Name $moduleName -RequiredVersion $($localModule.Version) -NuGetApiKey *** -Repository PSGallery -Confirm:`$false" 47 | Publish-Module -Name $moduleName -RequiredVersion $localModule.Version -NuGetApiKey $NuGetApiKey -Repository PSGallery -Confirm:$false -ErrorAction Stop 48 | -------------------------------------------------------------------------------- /test/unit/Set-CloudEventJsonData.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | Describe "Set-CloudEventJsonData Function Tests" { 7 | Context "Sets Json Data" { 8 | It 'Sets json data with depth 1' { 9 | # Arrange 10 | $cloudEvent = New-CloudEvent ` 11 | -Id ([Guid]::NewGuid()) ` 12 | -Type test ` 13 | -Source 'urn:test' 14 | 15 | $expectedJson = '{ 16 | "a": "b" 17 | }' 18 | 19 | $htData = @{'a' = 'b'} 20 | 21 | # Act 22 | $actual = $cloudEvent | Set-CloudEventJsonData -Data $htData -Depth 1 23 | 24 | # Assert 25 | $actual | Should -Not -Be $null 26 | $actual.DataContentType.ToString() | Should -Be 'application/json' 27 | $actual.Data | Should -Be $expectedJson 28 | } 29 | 30 | It 'Sets json data with depth 4' { 31 | # Arrange 32 | $cloudEvent = New-CloudEvent ` 33 | -Id ([Guid]::NewGuid()) ` 34 | -Type test ` 35 | -Source 'urn:test' 36 | 37 | $expectedJson = '{ 38 | "1": { 39 | "2": { 40 | "3": { 41 | "4": "wow" 42 | } 43 | } 44 | } 45 | }' 46 | 47 | $htData = @{ 48 | '1' = @{ 49 | '2' = @{ 50 | '3' = @{ 51 | '4' = 'wow' 52 | } 53 | } 54 | } 55 | } 56 | 57 | # Act 58 | $actual = $cloudEvent | Set-CloudEventJsonData -Data $htData -Depth 4 59 | 60 | # Assert 61 | $actual | Should -Not -Be $null 62 | $actual.DataContentType.ToString() | Should -Be 'application/json' 63 | $actual.Data | Should -Be $expectedJson 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # How to create a Release 2 | 3 | We use `tags` to drive the creation of the releases. This is handled by the 4 | Github Actions release workflow in 5 | [`ps-ce-sdk-release.yaml`](.github/workflows/ps-ce-sdk-release.yaml). 6 | 7 | A new release will upload the `PowerShell` module artifacts to the `PowerShell` 8 | [gallery](https://www.powershellgallery.com/packages/CloudEvents.Sdk). 9 | 10 | ## Step 1 - Bump Module Version 11 | 12 | Bump the `ModuleVersion` in 13 | [`src/CloudEventsPowerShell/CloudEvents.Sdk.psd1`](./src/CloudEventsPowerShell/CloudEvents.Sdk.psd1) 14 | to the next semantic release version (without `"v"` prefix). 15 | 16 | ```powershell 17 | # Version number of this module. 18 | ModuleVersion = '0.3.0' 19 | ``` 20 | 21 | Create a pull request with this change, review and approve it **after** all checks 22 | have passed. 23 | 24 | 25 | ## Step 2 - Update local `main` branch 26 | 27 | Pull in the latest changes, incl. the merged PR above, into your local `main` 28 | branch of this repository **before** creating a `tag` via the `git` CLI. 29 | 30 | ```console 31 | git checkout main 32 | git fetch -avp 33 | git pull upstream main 34 | ``` 35 | 36 | **Note:** the above commands assume `upstream` pointing to the remote 37 | `https://github.com/cloudevents/sdk-powershell.git` 38 | 39 | ## Step 3 - Create and push a Tag 40 | 41 | 42 | ```console 43 | RELEASE=v0.3.0 44 | git tag -a $RELEASE -m "Release ${RELEASE}" 45 | git push upstream refs/tags/${RELEASE} 46 | ``` 47 | 48 | 49 | This will trigger the release 50 | [workflow](https://github.com/cloudevents/sdk-powershell/actions/workflows/ps-ce-sdk-release.yaml). 51 | **Verify** that it executed successfully and that a new Github 52 | [release](https://github.com/cloudevents/sdk-powershell/releases) was created. 53 | 54 | The release workflow also creates a pull request with the updated 55 | [`CHANGELOG.md`](CHANGELOG.md). **Verify**, approve and merge accordingly. 56 | 57 | If you need to make changes to the Github release notes, you can edit them on the [release](https://github.com/cloudevents/sdk-powershell/releases) page. 58 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to CloudEvents sdk-powershell 2 | 3 | We welcome and appreciate contributions from the community. Please read this document for different ways of getting involved with the sdk-powershell. 4 | 5 | ## Contributing to Issues 6 | 7 | - Ensure that the issue you are about to file is not already open. If someone has already opened a similar issue, please leave a comment or add a GitHub reaction to the top comment to express your interest. 8 | - If you can't find your issue already, [open a new issue](https://github.com/cloudevents/sdk-powershell/issues/new). 9 | 10 | ## Contributing to Code 11 | CloudEvents.SDK PowerShell consists of a .NET project that resolves the [CloudEvents sdk-csharp](https://github.com/cloudevents/sdk-csharp) dependency, PowerShell script module with the sdk advanced functions, and Pester tests. 12 | 13 | ### Required Toolchain 14 | - [.NET 5.0](https://dotnet.microsoft.com/download/dotnet/5.0) SDK 15 | - [PowerShell 7.0](https://github.com/PowerShell/PowerShell#get-powershell) or higher 16 | - [Pester 5.1.1](https://www.powershellgallery.com/packages/Pester/5.1.1) or higher 17 | 18 | ### Building and testing 19 | The CloudEvents.Sdk module source code is in the `src` directory. We have unit tests and localhost integration tests available in the `test` directory.
20 |
21 | The `build.ps1` script is the entry point to build the module and run the tests. It has two parameters
22 |    1. `OutputDir` - The destination directory for the `CloudEvents.Sdk` module
23 |    2. `TestsType` - Specifies which tests (`none` | `unit` | `integration` | `all`) to run on successful module build.

24 | Running the `build.ps1` without specifying parameters produce the module in a `CloudEvents.Sdk` directory under the repository root directory, and runs all tests. 25 | 26 | ### Forks and Pull Requests 27 | 28 | Anyone can [fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the repository into their own user account, where they can make private changes. To contribute your changes back create a [pull request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests). A pull request should relate to an existing issue.
29 |
30 | Adding new features or fixing bugs might require adding or updating tests. Before creating a pull request make sure all tests pass locally. -------------------------------------------------------------------------------- /test/unit/Read-CloudEventJsonData.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | Describe "Read-CloudEventJsonData Function Tests" { 7 | Context "Extracts Json Data from CloudEvent" { 8 | It 'Extracts hashtable from CloudEvent with json data' { 9 | # Arrange 10 | $cloudEvent = New-CloudEvent ` 11 | -Id ([Guid]::NewGuid()) ` 12 | -Type test ` 13 | -Source 'urn:test' 14 | 15 | $expectedHtData = @{'a' = 'b'} 16 | 17 | $cloudEvent = $cloudEvent | Set-CloudEventJsonData -Data $expectedHtData -Depth 1 18 | 19 | # Act 20 | $actual = $cloudEvent | Read-CloudEventJsonData 21 | 22 | # Assert 23 | $actual | Should -Not -Be $null 24 | $actual -is [hashtable] | Should -Be $true 25 | $actual.a | Should -Be 'b' 26 | } 27 | 28 | It 'Expects error when CloudEvent data is not json' { 29 | # Arrange 30 | $cloudEvent = New-CloudEvent ` 31 | -Id ([Guid]::NewGuid()) ` 32 | -Type test ` 33 | -Source 'urn:test' 34 | 35 | $cloudEvent = $cloudEvent | Set-CloudEventData -Data "test" -DataContentType 'application/text' 36 | $pre 37 | 38 | # Act 39 | { $cloudEvent | Read-CloudEventJsonData -ErrorAction Stop } | ` 40 | Should -Throw "*Cloud Event '$($cloudEvent.Id)' has no json data*" 41 | 42 | } 43 | 44 | It 'Extracts hashtable from CloudEvent with json data with depth 4' { 45 | # Arrange 46 | $cloudEvent = New-CloudEvent ` 47 | -Id ([Guid]::NewGuid()) ` 48 | -Type test ` 49 | -Source 'urn:test' 50 | 51 | $expectedHtData = @{ 52 | 'l1' = @{ 53 | 'l2' = @{ 54 | 'l3' = @{ 55 | 'l4' = 'wow' 56 | } 57 | } 58 | } 59 | } 60 | 61 | $cloudEvent = $cloudEvent | Set-CloudEventJsonData -Data $expectedHtData -Depth 4 62 | 63 | # Act 64 | $actual = $cloudEvent | Read-CloudEventJsonData -Depth 4 65 | 66 | # Assert 67 | $actual | Should -Not -Be $null 68 | $actual -is [hashtable] | Should -Be $true 69 | $actual.l1.l2.l3.l4 | Should -Be 'wow' 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /test/unit/Set-CloudEventXmlData.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | Describe "Set-CloudEventXmlData Function Tests" { 7 | Context "Sets Xml Data" { 8 | It 'Sets xml data with depth 1' { 9 | # Arrange 10 | $cloudEvent = New-CloudEvent ` 11 | -Id ([Guid]::NewGuid()) ` 12 | -Type test ` 13 | -Source 'urn:test' 14 | 15 | $expectedXml = 'b' 16 | 17 | $htData = @{'a' = 'b'} 18 | 19 | # Act 20 | $actual = $cloudEvent | Set-CloudEventXmlData -Data $htData -AttributesKeysInElementAttributes $false 21 | 22 | # Assert 23 | $actual | Should -Not -Be $null 24 | $actual.DataContentType.ToString() | Should -Be 'application/xml' 25 | $actual.Data | Should -Be $expectedXml 26 | } 27 | 28 | It 'Sets xml data with depth 4' { 29 | # Arrange 30 | $cloudEvent = New-CloudEvent ` 31 | -Id ([Guid]::NewGuid()) ` 32 | -Type test ` 33 | -Source 'urn:test' 34 | 35 | $expectedXml = '<1><2><3><4>wow' 36 | 37 | $htData = @{ 38 | '1' = @{ 39 | '2' = @{ 40 | '3' = @{ 41 | '4' = 'wow' 42 | } 43 | } 44 | } 45 | } 46 | 47 | # Act 48 | $actual = $cloudEvent | Set-CloudEventXmlData -Data $htData -AttributesKeysInElementAttributes $false 49 | 50 | # Assert 51 | $actual | Should -Not -Be $null 52 | $actual.DataContentType.ToString() | Should -Be 'application/xml' 53 | $actual.Data | Should -Be $expectedXml 54 | } 55 | 56 | It 'Should throw when no single root key hashtable is passed to the Set-CloudEventXmlData Data parameter' { 57 | # Arrange 58 | $cloudEvent = New-CloudEvent ` 59 | -Id ([Guid]::NewGuid()) ` 60 | -Type test ` 61 | -Source 'urn:test' 62 | 63 | $htData = @{ 64 | '1' = '2' 65 | '3' = '4' 66 | } 67 | 68 | # Act & Assert 69 | { $cloudEvent | Set-CloudEventXmlData -Data $htData -AttributesKeysInElementAttributes $false} | ` 70 | Should -Throw '*Input Hashtable must have single root key*' 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /test/unit/Read-CloudEventXmlData.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | Describe "Read-CloudEventXmlData Function Tests" { 7 | Context "Extracts Xml Data from CloudEvent" { 8 | It 'Extracts hashtable from CloudEvent with xml data' { 9 | # Arrange 10 | $cloudEvent = New-CloudEvent ` 11 | -Id ([Guid]::NewGuid()) ` 12 | -Type test ` 13 | -Source 'urn:test' 14 | 15 | $xmlData = "b" 16 | $expectedHtData = @{'a' = 'b'} 17 | 18 | $cloudEvent = $cloudEvent | Set-CloudEventData -Data $xmlData -DataContentType 'application/xml' 19 | 20 | # Act 21 | $actual = $cloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes' 22 | 23 | # Assert 24 | $actual | Should -Not -Be $null 25 | $actual -is [hashtable] | Should -Be $true 26 | $actual.a | Should -Be 'b' 27 | } 28 | 29 | It 'Expects error when CloudEvent data is not xml' { 30 | # Arrange 31 | $cloudEvent = New-CloudEvent ` 32 | -Id ([Guid]::NewGuid()) ` 33 | -Type test ` 34 | -Source 'urn:test' 35 | 36 | $cloudEvent = $cloudEvent | Set-CloudEventData -Data "test" -DataContentType 'application/text' 37 | $pre 38 | 39 | # Act 40 | { $cloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes' -ErrorAction Stop } | ` 41 | Should -Throw "*Cloud Event '$($cloudEvent.Id)' has no xml data*" 42 | 43 | } 44 | 45 | It 'Extracts hashtable from CloudEvent with xml data with depth 4' { 46 | # Arrange 47 | $cloudEvent = New-CloudEvent ` 48 | -Id ([Guid]::NewGuid()) ` 49 | -Type test ` 50 | -Source 'urn:test' 51 | 52 | $xmlData = 'wow' 53 | $expectedHtData = @{ 54 | 'l1' = @{ 55 | 'l2' = @{ 56 | 'l3' = @{ 57 | 'l4' = 'wow' 58 | } 59 | } 60 | } 61 | } 62 | 63 | $cloudEvent = $cloudEvent | Set-CloudEventData -Data $xmlData -DataContentType 'application/xml' 64 | 65 | # Act 66 | $actual = $cloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes' 67 | 68 | # Assert 69 | $actual | Should -Not -Be $null 70 | $actual -is [hashtable] | Should -Be $true 71 | $actual.l1.l2.l3.l4 | Should -Be 'wow' 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /.github/workflows/ps-ce-sdk-release.yaml: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | name: Release 7 | 8 | on: 9 | push: 10 | tags: 11 | - v* 12 | workflow_dispatch: 13 | 14 | jobs: 15 | psgallery-publish: 16 | name: Release CloudEvents.Sdk Module 17 | runs-on: "ubuntu-latest" 18 | env: 19 | OUTPUT_DIR: release 20 | CHANGE_LOG_FILE_NAME: RELEASE_CHANGELOG.md 21 | 22 | timeout-minutes: 10 23 | 24 | steps: 25 | - name: Check out code 26 | uses: actions/checkout@v2 27 | 28 | - name: Build and Test 29 | shell: pwsh 30 | run: ./build.ps1 -OutputDir $env:OUTPUT_DIR -TestsType all -ExitProcess 31 | 32 | - name: Publish to PSGallery 33 | shell: pwsh 34 | run: ./publish.ps1 -ModuleReleaseDir $env:OUTPUT_DIR -NuGetApiKey ${{ secrets.CLOUDEVENTS_SDK_PUBLISHER_API_KEY }} 35 | 36 | - name: Create CHANGELOG 37 | env: 38 | IMAGE: quay.io/git-chglog/git-chglog 39 | # https://quay.io/repository/git-chglog/git-chglog from tag v0.14.2 40 | IMAGE_SHA: 998e89dab8dd8284cfff5f8cfb9e9af41fe3fcd4671f2e86a180e453c20959e3 41 | run: | 42 | # generate CHANGELOG for this Github release tag only 43 | docker run --rm -v $PWD:/workdir ${IMAGE}@sha256:${IMAGE_SHA} -o ${CHANGE_LOG_FILE_NAME} $(basename "${{ github.ref }}" ) 44 | 45 | - name: Create Github Release 46 | env: 47 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 48 | run: gh release create $(basename "${{ github.ref }}") -F ${CHANGE_LOG_FILE_NAME} 49 | 50 | changelog-pull-request: 51 | needs: psgallery-publish 52 | name: Create CHANGELOG PR 53 | runs-on: ubuntu-latest 54 | continue-on-error: true 55 | env: 56 | CHANGE_LOG_FILE_NAME: CHANGELOG.md 57 | 58 | steps: 59 | - name: Checkout 60 | uses: actions/checkout@v2 61 | with: 62 | # for changelog 63 | fetch-depth: 0 64 | ref: "main" 65 | 66 | - name: Create CHANGELOG commit 67 | env: 68 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | IMAGE: quay.io/git-chglog/git-chglog 70 | # https://quay.io/repository/git-chglog/git-chglog from tag v0.14.2 71 | IMAGE_SHA: 998e89dab8dd8284cfff5f8cfb9e9af41fe3fcd4671f2e86a180e453c20959e3 72 | run: | 73 | # update CHANGELOG.md 74 | docker run --rm -v $PWD:/workdir ${IMAGE}@sha256:${IMAGE_SHA} -o ${CHANGE_LOG_FILE_NAME} 75 | 76 | git config user.email "${{ github.actor }}@users.noreply.github.com" 77 | git config user.name "${{ github.actor }}" 78 | git add ${CHANGE_LOG_FILE_NAME} 79 | git commit -m "Update CHANGELOG for $(basename ${{ github.ref }})" 80 | 81 | - name: Create Pull Request 82 | uses: peter-evans/create-pull-request@v3 83 | with: 84 | delete-branch: true 85 | title: "Update CHANGELOG" 86 | body: | 87 | Update CHANGELOG.md for new release 88 | 89 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## [Release v0.3.3](https://github.com/cloudevents/sdk-powershell/compare/v0.3.2...v0.3.3) 4 | 5 | > Release Date: 2021-09-08 6 | 7 | ### 🧹 Chore 8 | 9 | - [32f333b] Bump module version for new release 10 | - [5e2e221] Trigger release on tag push (#47) 11 | 12 | ### ⚠️ BREAKING 13 | 14 | ### 📖 Commits 15 | 16 | - [32f333b] chore: Bump module version for new release 17 | - [5e2e221] chore: Trigger release on tag push (#47) 18 | 19 | 20 | ## [Release v0.3.2](https://github.com/cloudevents/sdk-powershell/compare/v0.3.1...v0.3.2) 21 | 22 | > Release Date: 2021-09-08 23 | 24 | ### 🧹 Chore 25 | 26 | - [c8b5958] Add Project URI in the module manifest 27 | 28 | ### ⚠️ BREAKING 29 | 30 | ### 📖 Commits 31 | 32 | - [c8b5958] chore: Add Project URI in the module manifest 33 | 34 | 35 | ## [Release v0.3.1](https://github.com/cloudevents/sdk-powershell/compare/v0.3.0...v0.3.1) 36 | 37 | > Release Date: 2021-08-30 38 | 39 | ### 🐞 Fix 40 | 41 | - [7eb0f9b] Incorrect parsing of Binary Content Mode cloud events 42 | 43 | ### 🧹 Chore 44 | 45 | - [941816a] Add RELEASE instructions (#40) 46 | 47 | ### ⚠️ BREAKING 48 | 49 | ### 📖 Commits 50 | 51 | - [7eb0f9b] fix: Incorrect parsing of Binary Content Mode cloud events 52 | - [941816a] chore: Add RELEASE instructions (#40) 53 | - [7876104] Update README with the new version output 54 | 55 | 56 | ## v0.3.0 57 | 58 | > Release Date: 2021-06-21 59 | 60 | ### 🧹 Chore 61 | 62 | - [5b14f75] Include commit details in CHANGELOG (#35) 63 | - [2c4d0ed] Add check WIP action (#31) 64 | - [5764180] Add CHANGELOG configuration (#29) 65 | - [0bd4192] Verify PRs and push to main (#6) 66 | - [e08a275] Add stale action (#7) 67 | 68 | ### ⚠️ BREAKING 69 | 70 | ### 📖 Commits 71 | 72 | - [2144695] Bump module version 0.3.0 73 | - [5b14f75] chore: Include commit details in CHANGELOG (#35) 74 | - [78b7e9e] Implement publish script 75 | - [2c4d0ed] chore: Add check WIP action (#31) 76 | - [5764180] chore: Add CHANGELOG configuration (#29) 77 | - [a0e1078] Add PR template (#3) 78 | - [ae6f848] Update issue templates 79 | - [3a9010a] Improve examples. Remove unnecessary details. 80 | - [401a475] Implement wait server to start in the integrations tests BeforeAll Fix Binary Mode HTTP Header DateTime format. It is UTC time formatted in ISO 8601 format including milliseconds. Bump revision of the module version 81 | - [0bd4192] chore: Verify PRs and push to main (#6) 82 | - [e08a275] chore: Add stale action (#7) 83 | - [3629c77] Add Install Module Instructions to README ([#17](https://github.com/cloudevents/sdk-powershell/issues/17)) (#17) (#16) 84 | - [aac584a] Enable process exit in build.ps1 85 | - [84606b0] Create CONTRIBUTING.md ([#15](https://github.com/cloudevents/sdk-powershell/issues/15)) (#15) 86 | - [d305f16] Renames Add-CloudEvent*Data to Set-CloudEvent*Data ([#12](https://github.com/cloudevents/sdk-powershell/issues/12)) (#12) (#9) 87 | - [f23df54] Adds license headers in source code files ([#11](https://github.com/cloudevents/sdk-powershell/issues/11)) (#11) 88 | - [62f14b6] Remove install instructions from an internal PS repository ([#10](https://github.com/cloudevents/sdk-powershell/issues/10)) (#10) (#1) 89 | - [b2afcf8] Initial commit 90 | -------------------------------------------------------------------------------- /test/unit/Set-CloudEventData.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | Describe "Set-CloudEventData Function Tests" { 7 | Context "Sets Data" { 8 | It 'Sets byte[] data' { 9 | # Arrange 10 | $cloudEvent = New-CloudEvent ` 11 | -Id ([Guid]::NewGuid()) ` 12 | -Type test ` 13 | -Source 'urn:test' 14 | 15 | $expectedData = [Text.Encoding]::UTF8.GetBytes("test") 16 | $expectedDataContentType = 'application/octet-stream' 17 | 18 | 19 | # Act 20 | $actual = $cloudEvent | ` 21 | Set-CloudEventData ` 22 | -Data $expectedData ` 23 | -DataContentType $expectedDataContentType 24 | 25 | # Assert 26 | $actual | Should -Not -Be $null 27 | $actual.DataContentType.ToString() | Should -Be $expectedDataContentType 28 | $actual.Data | Should -Be $expectedData 29 | } 30 | 31 | It 'Sets xml text data' { 32 | # Arrange 33 | $cloudEvent = New-CloudEvent ` 34 | -Id ([Guid]::NewGuid()) ` 35 | -Type test ` 36 | -Source 'urn:test' 37 | 38 | $expectedData = '' 39 | $expectedDataContentType = 'application/xml' 40 | 41 | 42 | # Act 43 | $actual = $cloudEvent | ` 44 | Set-CloudEventData ` 45 | -Data $expectedData ` 46 | -DataContentType $expectedDataContentType 47 | 48 | # Assert 49 | $actual | Should -Not -Be $null 50 | $actual.DataContentType.ToString() | Should -Be $expectedDataContentType 51 | $actual.Data | Should -Be $expectedData 52 | } 53 | } 54 | 55 | 56 | Context "Errors on invalid data content type" { 57 | It 'Throws error on invalid MIME content type' { 58 | # Arrange 59 | $invalidContentType = 'invalid' 60 | 61 | $cloudEvent = New-CloudEvent ` 62 | -Id ([Guid]::NewGuid()) ` 63 | -Type test ` 64 | -Source 'urn:test' 65 | 66 | # Act & Assert 67 | { Set-CloudEventData ` 68 | -CloudEvent $cloudEvent ` 69 | -Data '1' ` 70 | -DataContentType $invalidContentType } | ` 71 | Should -Throw "*The specified content type is invalid*" 72 | } 73 | 74 | It 'Throws error on empty content type' { 75 | # Arrange 76 | $invalidContentType = [string]::Empty 77 | 78 | $cloudEvent = New-CloudEvent ` 79 | -Id ([Guid]::NewGuid()) ` 80 | -Type test ` 81 | -Source 'urn:test' 82 | 83 | # Act & Assert 84 | { Set-CloudEventData ` 85 | -CloudEvent $cloudEvent ` 86 | -Data '1' ` 87 | -DataContentType $invalidContentType } | ` 88 | Should -Throw "*The parameter 'contentType' cannot be an empty string*" 89 | } 90 | 91 | It 'Throws error on null content type' { 92 | # Arrange 93 | $invalidContentType = $null 94 | 95 | $cloudEvent = New-CloudEvent ` 96 | -Id ([Guid]::NewGuid()) ` 97 | -Type test ` 98 | -Source 'urn:test' 99 | 100 | # Act & Assert 101 | { Set-CloudEventData ` 102 | -CloudEvent $cloudEvent ` 103 | -Data '1' ` 104 | -DataContentType $invalidContentType } | ` 105 | Should -Throw "*The parameter 'contentType' cannot be an empty string*" 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /src/CloudEventsPowerShell/CloudEvents.Sdk.psd1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | @{ 7 | 8 | # Script module or binary module file associated with this manifest. 9 | RootModule = 'CloudEvents.Sdk.psm1' 10 | 11 | # Version number of this module. 12 | ModuleVersion = '0.3.3' 13 | 14 | # Supported PSEditions 15 | CompatiblePSEditions = @('Core') 16 | 17 | # ID used to uniquely identify this module 18 | GUID = 'd0d7d392-0eab-40a8-8a3f-78ba41ef2f02' 19 | 20 | # Author of this module 21 | Author = 'dmilov' 22 | 23 | # Company or vendor of this module 24 | CompanyName = 'The CloudEvents Authors' 25 | 26 | # Copyright statement for this module 27 | Copyright = '(c) The CloudEvents Authors' 28 | 29 | # Description of the functionality provided by this module 30 | Description = 'PowerShell CloudEvents SDK' 31 | 32 | # Minimum version of the PowerShell engine required by this module 33 | PowerShellVersion = '7.0' 34 | 35 | # Name of the PowerShell host required by this module 36 | # PowerShellHostName = '' 37 | 38 | # Minimum version of the PowerShell host required by this module 39 | # PowerShellHostVersion = '' 40 | 41 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 42 | # DotNetFrameworkVersion = '' 43 | 44 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # ClrVersion = '' 46 | 47 | # Processor architecture (None, X86, Amd64) required by this module 48 | # ProcessorArchitecture = '' 49 | 50 | # Modules that must be imported into the global environment prior to importing this module 51 | # RequiredModules = @() 52 | 53 | # Assemblies that must be loaded prior to importing this module 54 | RequiredAssemblies = @('CloudNative.CloudEvents.dll') 55 | 56 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 57 | # ScriptsToProcess = @() 58 | 59 | # Type files (.ps1xml) to be loaded when importing this module 60 | # TypesToProcess = @() 61 | 62 | # Format files (.ps1xml) to be loaded when importing this module 63 | # FormatsToProcess = @() 64 | 65 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 66 | # NestedModules = @() 67 | 68 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 69 | FunctionsToExport = @( 70 | 'New-CloudEvent', 'Set-CloudEventData', 'Set-CloudEventJsonData', 'Set-CloudEventXmlData', 'Read-CloudEventData', 'Read-CloudEventJsonData', 'Read-CloudEventXmlData', # CloudEvent Object Functions 71 | 'ConvertTo-HttpMessage', 'ConvertFrom-HttpMessage' # Http Binding Functions 72 | ) 73 | 74 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 75 | CmdletsToExport = @() 76 | 77 | # Variables to export from this module 78 | VariablesToExport = '*' 79 | 80 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 81 | AliasesToExport = @() 82 | 83 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 84 | PrivateData = @{ 85 | 86 | PSData = @{ 87 | 88 | # Tags applied to this module. These help with module discovery in online galleries. 89 | Tags = 'CloudEvents','SDK' 90 | 91 | # A URL to the license for this module. 92 | # LicenseUri = '' 93 | 94 | # A URL to the main website for this project. 95 | ProjectUri = 'https://github.com/cloudevents/sdk-powershell' 96 | 97 | # A URL to an icon representing this module. 98 | # IconUri = '' 99 | 100 | # ReleaseNotes of this module 101 | # ReleaseNotes = '' 102 | 103 | # Prerelease string of this module 104 | # Prerelease = '' 105 | 106 | # Flag to indicate whether the module requires explicit user acceptance for install/update 107 | # RequireLicenseAcceptance = $false 108 | 109 | # External dependent modules of this module 110 | # ExternalModuleDependencies = @() 111 | 112 | } # End of PSData hashtable 113 | 114 | } # End of PrivateData hashtable 115 | 116 | } 117 | 118 | -------------------------------------------------------------------------------- /test/integration/HttpServer.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | param( 7 | [Parameter(Mandatory = $true)] 8 | [ValidateScript( { Test-Path $_ })] 9 | [string] 10 | $CloudEventsModulePath, 11 | 12 | [Parameter( 13 | Mandatory = $true, 14 | ValueFromPipeline = $false, 15 | ValueFromPipelineByPropertyName = $false)] 16 | [ValidateNotNull()] 17 | [string] 18 | $ServerUrl 19 | ) 20 | 21 | . (Join-Path $PSScriptRoot 'ProtocolConstants.ps1') 22 | 23 | # Import SDK Module 24 | Import-Module $CloudEventsModulePath 25 | 26 | function Start-HttpCloudEventListener { 27 | <# 28 | .DESCRIPTION 29 | Starts a HTTP CloudEvent Listener on specified Url 30 | #> 31 | 32 | [CmdletBinding()] 33 | param( 34 | [Parameter( 35 | Mandatory = $true, 36 | ValueFromPipeline = $false, 37 | ValueFromPipelineByPropertyName = $false)] 38 | [ValidateNotNull()] 39 | [string] 40 | $Url, 41 | 42 | [Parameter( 43 | Mandatory = $false, 44 | ValueFromPipeline = $false, 45 | ValueFromPipelineByPropertyName = $false)] 46 | [scriptblock] 47 | $Handler 48 | ) 49 | 50 | $listener = New-Object -Type 'System.Net.HttpListener' 51 | $listener.AuthenticationSchemes = [System.Net.AuthenticationSchemes]::Anonymous 52 | $listener.Prefixes.Add($Url) 53 | 54 | try { 55 | $listener.Start() 56 | 57 | $context = $listener.GetContext() 58 | 59 | # Read Input Stream 60 | $buffer = New-Object 'byte[]' -ArgumentList 1024 61 | $ms = New-Object 'IO.MemoryStream' 62 | $read = 0 63 | while (($read = $context.Request.InputStream.Read($buffer, 0, 1024)) -gt 0) { 64 | $ms.Write($buffer, 0, $read); 65 | } 66 | $bodyData = $ms.ToArray() 67 | $ms.Dispose() 68 | 69 | # Read Headers 70 | $headers = @{} 71 | for ($i = 0; $i -lt $context.Request.Headers.Count; $i++) { 72 | $headers[$context.Request.Headers.GetKey($i)] = $context.Request.Headers.GetValues($i) 73 | } 74 | 75 | $cloudEvent = ConvertFrom-HttpMessage -Headers $headers -Body $bodyData 76 | 77 | if ( $cloudEvent -ne $null ) { 78 | $Handler.Invoke($cloudEvent, $context.Response) 79 | $context.Response.Close(); 80 | } 81 | else { 82 | $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::BadRequest) 83 | $context.Response.Close(); 84 | } 85 | 86 | } 87 | catch { 88 | Write-Error $_ 89 | $context.Response.StatusCode = [int]([System.Net.HttpStatusCode]::InternalServerError) 90 | $context.Response.Close(); 91 | } 92 | finally { 93 | $listener.Stop() 94 | } 95 | } 96 | 97 | $global:serverStopRequested = $false 98 | while ( -not $global:serverStopRequested ) { 99 | try { 100 | Start-HttpCloudEventListener -Url $ServerUrl -Handler { 101 | $requestCloudEvent = $args[0] 102 | $response = $args[1] 103 | 104 | # When CloudEvent Type is 'echo-structured' or 'echo-binary' the Server responds 105 | # with CloudEvent in corresponding content mode 106 | if ( $requestCloudEvent.Type -eq $script:EchoBinaryType -or ` 107 | $requestCloudEvent.Type -eq $script:EchoStructuredType ) { 108 | 109 | # Create Cloud Event for the response 110 | $cloudEvent = New-CloudEvent ` 111 | -Type $requestCloudEvent.Type ` 112 | -Source $script:ServerSource ` 113 | -Time (Get-Date) ` 114 | -Id $requestCloudEvent.Id 115 | 116 | # Add Data to the new Cloud Event 117 | $requestCloudEventJsonData = $requestCloudEvent | Read-CloudEventJsonData 118 | $requestCloudEventXmlData = $requestCloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes' 119 | if ($requestCloudEventJsonData) { 120 | $cloudEvent = $cloudEvent | Set-CloudEventJsonData ` 121 | -Data $requestCloudEventJsonData 122 | } 123 | elseif ($requestCloudEventXmlData) { 124 | $cloudEvent = $cloudEvent | Set-CloudEventXmlData ` 125 | -Data $requestCloudEventXmlData ` 126 | -AttributesKeysInElementAttributes $false 127 | } 128 | else { 129 | $requestCloudEventData = $requestCloudEvent | Read-CloudEventData 130 | $cloudEvent = $cloudEvent | Set-CloudEventData ` 131 | -Data $requestCloudEventData ` 132 | -DataContentType $requestCloudEvent.DataContentType 133 | } 134 | 135 | # Convert Cloud Event to HTTP Response 136 | $contentMode = $requestCloudEvent.Type.TrimStart('echo-') 137 | $httpMessage = $cloudEvent | ConvertTo-HttpMessage -ContentMode $contentMode 138 | 139 | $response.Headers = New-Object 'System.Net.WebHeaderCollection' 140 | foreach ($keyValue in $httpMessage.Headers.GetEnumerator()) { 141 | $response.Headers.Add($keyValue.Key, $keyValue.Value) 142 | } 143 | $response.ContentLength64 = $httpMessage.Body.Length 144 | $response.OutputStream.Write($httpMessage.Body, 0, $httpMessage.Body.Length) 145 | $response.StatusCode = [int]([System.Net.HttpStatusCode]::OK) 146 | 147 | } 148 | else { 149 | # No Content in all other cases 150 | $response.StatusCode = [int]([System.Net.HttpStatusCode]::NoContent) 151 | } 152 | 153 | if ( $requestCloudEvent.Type -eq $script:ServerStopType ) { 154 | # Server Stop is requested 155 | $global:serverStopRequested = $true 156 | } 157 | } 158 | } catch { 159 | Write-Error $_ 160 | break 161 | } 162 | } -------------------------------------------------------------------------------- /src/CloudEventsPowerShell/dataserialization/xml.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | $SKIPATTR = "SkipAttributes" 7 | $ALWAYSATTRVALUE = "AlwaysAttrValue" 8 | $ATTRVALUEFORELEMENTSWITHATTR = "AttrValueWhenAttributes" 9 | function ConvertFrom-XmlPropertyValue { 10 | param( 11 | [Parameter(Mandatory = $true, 12 | ValueFromPipeline = $false)] 13 | $InputObject, 14 | 15 | [Parameter(Mandatory = $true)] 16 | [ValidateSet("SkipAttributes", "AlwaysAttrValue", "AttrValueWhenAttributes")] 17 | [string] 18 | $ConvertMode 19 | ) 20 | 21 | $value = $InputObject 22 | $Attributes = $null 23 | 24 | if ($InputObject -is [Xml.XmlElement]) { 25 | $hasAttributes = (($InputObject | Get-Member -MemberType Properties) | Where-Object {$_.Name -eq '#text'}) -ne $null 26 | if ($hasAttributes) { 27 | $Attributes = @{} 28 | $arrProperties = $InputObject | Get-Member -MemberType Properties 29 | foreach ($p in $arrProperties) { 30 | if ($p.Name -eq '#text') { 31 | $value = $InputObject.'#text' 32 | } else { 33 | $Attributes[$p.Name] = $InputObject.$($p.Name) 34 | } 35 | } 36 | 37 | } else { 38 | $value = ConvertFrom-CEDataXml -InputXmlElement $InputObject -ConvertMode $ConvertMode 39 | } 40 | } 41 | 42 | if ($InputObject -is [array]) { 43 | $value = @() 44 | foreach ($obj in $InputObject) { 45 | $value += ConvertFrom-XmlPropertyValue -InputObject $obj -ConvertMode $ConvertMode 46 | } 47 | } 48 | 49 | if (($ConvertMode -eq $SKIPATTR) -or 50 | ($Attributes -eq $null -and $ConvertMode -eq $ATTRVALUEFORELEMENTSWITHATTR)) { 51 | 52 | $value 53 | } 54 | 55 | if (($ConvertMode -eq $ALWAYSATTRVALUE) -or 56 | ($Attributes -ne $null -and $ConvertMode -eq $ATTRVALUEFORELEMENTSWITHATTR)) { 57 | @{ 58 | "Attributes" = $Attributes 59 | "Value" = $value 60 | } 61 | } 62 | } 63 | 64 | function ConvertFrom-CEDataXml { 65 | param( 66 | [Parameter(Mandatory = $true, 67 | ValueFromPipeline = $true, 68 | ParameterSetName="Text")] 69 | [ValidateNotNull()] 70 | [string] 71 | $InputString, 72 | 73 | [Parameter(Mandatory = $true, 74 | ValueFromPipeline = $false, 75 | ParameterSetName="XmlElement")] 76 | [ValidateNotNull()] 77 | [Xml.XmlElement] 78 | $InputXmlElement, 79 | 80 | [Parameter(Mandatory = $true)] 81 | [ValidateSet("SkipAttributes", "AlwaysAttrValue", "AttrValueWhenAttributes")] 82 | [string] 83 | $ConvertMode 84 | ) 85 | $result = $null 86 | if ($InputString -ne $null) { 87 | $xmlDocument = [xml]$InputString 88 | } 89 | if ($InputXmlElement -ne $null) { 90 | $xmlDocument = $InputXmlElement 91 | } 92 | 93 | if ($xmlDocument -ne $null) { 94 | $xmlProperties = $xmlDocument | Get-Member -MemberType Properties 95 | 96 | $result = @{} 97 | 98 | 99 | foreach ($property in $xmlProperties) { 100 | $propertyName = $property.Name 101 | $value = ConvertFrom-XmlPropertyValue -InputObject $xmlDocument.$propertyName -ConvertMode $ConvertMode 102 | $result[$propertyName] = $value 103 | } 104 | } 105 | 106 | $result 107 | } 108 | 109 | function New-XmlElement { 110 | param( 111 | [Parameter(Mandatory = $true, 112 | ValueFromPipeline = $false)] 113 | [ValidateNotNull()] 114 | $DictionaryEntry, 115 | 116 | [Parameter(Mandatory = $true, 117 | ValueFromPipeline = $false)] 118 | $XmlDocument, 119 | 120 | [Parameter(Mandatory = $false)] 121 | [Switch] 122 | $AttributesKeysInElementAttributes 123 | 124 | ) 125 | $result = $XmlDocument.CreateElement($DictionaryEntry.Name) 126 | 127 | $value = "" 128 | 129 | if ($DictionaryEntry.Value -is [hashtable]) { 130 | if($DictionaryEntry.Value.Keys.Count -eq 2 -and ` 131 | $DictionaryEntry.Value['Attributes'] -is [hashtable] -and ` 132 | $DictionaryEntry.Value['Value'] -ne $null -and ` 133 | $AttributesKeysInElementAttributes) { 134 | foreach ($attKv in $DictionaryEntry.Value['Attributes'].GetEnumerator()) { 135 | $result.SetAttribute($attKv.Name, $attKv.Value) 136 | } 137 | $result.InnerText = $DictionaryEntry.Value['Value'].ToString() 138 | 139 | } else { 140 | foreach ($nameValue in $DictionaryEntry.Value.GetEnumerator()) { 141 | $xmlElement = New-XmlElement -DictionaryEntry $nameValue -XmlDocument $XmlDocument -AttributesKeysInElementAttributes:$AttributesKeysInElementAttributes 142 | $xmlElement | Foreach-Object { 143 | $result.AppendChild($_) | Out-Null 144 | } 145 | } 146 | } 147 | } elseif ($DictionaryEntry.Value -is [array]) { 148 | $result = @() 149 | foreach ($item in $DictionaryEntry.Value) { 150 | $result += (New-XmlElement ` 151 | -DictionaryEntry ` 152 | (New-Object System.Collections.DictionaryEntry ` 153 | -ArgumentList @($DictionaryEntry.Name, $item)) ` 154 | -XmlDocument $XmlDocument ` 155 | -AttributesKeysInElementAttributes:$AttributesKeysInElementAttributes) 156 | } 157 | } else { 158 | $value = $DictionaryEntry.Value.ToString() 159 | $result.InnerText = $value 160 | } 161 | 162 | $result 163 | } 164 | 165 | function ConvertTo-CEDataXml { 166 | param( 167 | [Parameter(Mandatory = $true, 168 | ValueFromPipeline = $true)] 169 | [ValidateNotNull()] 170 | [Hashtable] 171 | $InputObject, 172 | 173 | [Parameter(Mandatory = $true)] 174 | [bool] 175 | $AttributesKeysInElementAttributes 176 | ) 177 | if ($InputObject.Keys.Count -ne 1) { 178 | throw "Input Hashtable must have single root key" 179 | } 180 | 181 | [xml]$resultDocument = New-Object System.Xml.XmlDocument 182 | 183 | foreach ($nameValue in $InputObject.GetEnumerator()) { 184 | $element = New-XmlElement -DictionaryEntry $nameValue -XmlDocument $resultDocument -AttributesKeysInElementAttributes:$AttributesKeysInElementAttributes 185 | 186 | $element | Foreach-Object { 187 | $resultDocument.AppendChild($_) | Out-Null 188 | } 189 | } 190 | 191 | $resultDocument.OuterXml 192 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PowerShell 7.0 SDK for CloudEvents based on [.NET SDK for CloudEvents](https://github.com/cloudevents/sdk-csharp) 2 | 3 | Official CloudEvents SDK to integrate your application with CloudEvents. 4 | 5 | ## Status 6 | 7 | Supported CloudEvents versions: 8 | - v1.0 9 | 10 | Supported Protocols: 11 | - HTTP 12 | 13 | ## **`CloudEvents.Sdk`** Module 14 | The module contains functions to 15 | - Create CloudEvent objects 16 | - Add data to a CloudEvent object 17 | - Read data from a CloudEvent object 18 | - Convert a CloudEvent object to an HTTP Message 19 | - Convert an HTTP Message to a CloudEvent object 20 | 21 | ## Install **`CloudEvents.Sdk`** Module 22 | 23 | ### Prerequisites 24 | - [PowerShell 7.0](https://github.com/PowerShell/PowerShell/releases/tag/v7.0.4) 25 | 26 | ```powershell 27 | Install-Module CloudEvents.Sdk 28 | Import-Module CloudEvents.Sdk 29 | Get-Command -Module CloudEvents.Sdk 30 | 31 | CommandType Name Version Source 32 | ----------- ---- ------- ------ 33 | Function ConvertFrom-HttpMessage 0.3.0 CloudEvents.Sdk 34 | Function ConvertTo-HttpMessage 0.3.0 CloudEvents.Sdk 35 | Function New-CloudEvent 0.3.0 CloudEvents.Sdk 36 | Function Read-CloudEventData 0.3.0 CloudEvents.Sdk 37 | Function Read-CloudEventJsonData 0.3.0 CloudEvents.Sdk 38 | Function Read-CloudEventXmlData 0.3.0 CloudEvents.Sdk 39 | Function Set-CloudEventData 0.3.0 CloudEvents.Sdk 40 | Function Set-CloudEventJsonData 0.3.0 CloudEvents.Sdk 41 | Function Set-CloudEventXmlData 0.3.0 CloudEvents.Sdk 42 | ``` 43 | 44 | ## Using **`CloudEvents.Sdk`** Module 45 | ## 1. Event Producer 46 | ### Create a CloudEvent object 47 | ```powershell 48 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id (New-Guid).Guid -Time (Get-Date) 49 | ``` 50 | 51 | ### Set **JSON Data** to a CloudEvent object 52 | ```powershell 53 | $cloudEvent | Set-CloudEventJsonData -Data @{ 54 | 'Foo' = 'Hello' 55 | 'Bar' = 'World' 56 | } 57 | 58 | 59 | DataContentType : application/json 60 | Data : { 61 | "Bar": "World", 62 | "Foo": "Hello" 63 | } 64 | Id : ac9b12d9-ae45-4654-a4d7-42bbf0d9816d 65 | DataSchema : 66 | Source : mailto:cncf-wg-serverless@lists.cncf.io 67 | SpecVersion : V1_0 68 | Subject : 69 | Time : 4/26/2021 9:00:45 AM 70 | Type : com.example.object.deleted.v2 71 | ``` 72 | 73 | ### Set **XML Data** to a CloudEvent object 74 | ```powershell 75 | $cloudEvent | Set-CloudEventXmlData -Data @{ 76 | 'xml' = @{ 77 | 'Foo' = 'Hello' 78 | 'Bar' = 'World' 79 | } 80 | } ` 81 | -AttributesKeysInElementAttributes $true 82 | 83 | 84 | DataContentType : application/xml 85 | Data : WorldHello 86 | Id : ac9b12d9-ae45-4654-a4d7-42bbf0d9816d 87 | DataSchema : 88 | Source : mailto:cncf-wg-serverless@lists.cncf.io 89 | SpecVersion : V1_0 90 | Subject : 91 | Time : 4/26/2021 9:00:45 AM 92 | Type : com.example.object.deleted.v2 93 | ``` 94 | ### Set Custom Format Data to a CloudEvent object 95 | ```powershell 96 | $cloudEvent | Set-CloudEventData -DataContentType 'application/text' -Data 'Hello World!' 97 | 98 | DataContentType : application/text 99 | Data : Hello World! 100 | Id : b1b748cd-e98d-4f5f-80ea-76dea71a53a5 101 | DataSchema : 102 | Source : mailto:cncf-wg-serverless@lists.cncf.io 103 | SpecVersion : V1_0 104 | Subject : 105 | Time : 4/27/2021 7:00:44 PM 106 | Type : com.example.object.deleted.v2 107 | ``` 108 | 109 | ### Convert a CloudEvent object to an HTTP message in **Binary** or **Structured** content mode 110 | ```powershell 111 | # Format structured cloud event HTTP message 112 | $cloudEventStructuredHttpMessage = $cloudEvent | ConvertTo-HttpMessage -ContentMode Structured 113 | ``` 114 | 115 | ### Send CloudEvent object to HTTP server 116 | ```powershell 117 | Invoke-WebRequest -Method POST -Uri 'http://my.cloudevents.server/' -Headers $cloudEventStructuredHttpMessage.Headers -Body $cloudEventStructuredHttpMessage.Body 118 | ``` 119 | 120 | ## 2. Event Consumer 121 | ### Convert an HTTP message to a CloudEvent object 122 | ```powershell 123 | $cloudEvent = ConvertFrom-HttpMessage -Headers -Body 124 | ``` 125 | 126 | ### Read CloudEvent **JSON Data** as a **PowerShell Hashtable** 127 | ```powershell 128 | Read-CloudEventJsonData -CloudEvent $cloudEvent 129 | 130 | 131 | Name Value 132 | ---- ----- 133 | Foo Hello 134 | Bar World 135 | ``` 136 | 137 | ### Read CloudEvent **XML Data** as a **PowerShell Hashtable** 138 | ```powershell 139 | Read-CloudEventXmlData -CloudEvent $cloudEvent -ConvertMode SkipAttributes 140 | 141 | Name Value 142 | ---- ----- 143 | xml {Bar, Foo} 144 | ``` 145 | 146 | The `ConvertMode` parameter specifies how the xml should be converted to a PowerShell Hashtable. `SkipAttributes` mode skips reading the xml attributes. There are three different modes of conversion. For more details check the help of the `Read-CloudEventXmlData` cmdlet. 147 | 148 | ### Read CloudEvent Custom Format **Data** as a **byte[]** 149 | ```powershell 150 | Read-CloudEventData -CloudEvent $cloudEvent 151 | 152 | 72 153 | 101 154 | 108 155 | 108 156 | 111 157 | 32 158 | 87 159 | 111 160 | 114 161 | 108 162 | 100 163 | 33 164 | ``` 165 | 166 | ## Community 167 | 168 | - There are bi-weekly calls immediately following the 169 | [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time) 170 | at 9am PT (US Pacific). Which means they will typically start at 10am PT, but 171 | if the other call ends early then the SDK call will start early as well. See 172 | the 173 | [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#) 174 | to determine which week will have the call. 175 | - Slack: #cloudeventssdk channel under 176 | [CNCF's Slack workspace](https://slack.cncf.io/). 177 | - Email: https://lists.cncf.io/g/cncf-cloudevents-sdk 178 | - Contact for additional information: Michael Gasch (`@Michael Gasch` 179 | on slack). 180 | 181 | Each SDK may have its own unique processes, tooling and guidelines, common 182 | governance related material can be found in the 183 | [CloudEvents `community`](https://github.com/cloudevents/spec/tree/master/community) 184 | directory. In particular, in there you will find information concerning 185 | how SDK projects are 186 | [managed](https://github.com/cloudevents/spec/blob/master/community/SDK-GOVERNANCE.md), 187 | [guidelines](https://github.com/cloudevents/spec/blob/master/community/SDK-maintainer-guidelines.md) 188 | for how PR reviews and approval, and our 189 | [Code of Conduct](https://github.com/cloudevents/spec/blob/master/community/GOVERNANCE.md#additional-information) 190 | information. 191 | 192 | If there is a security concern with one of the CloudEvents specifications, or 193 | with one of the project's SDKs, please send an email to 194 | [cncf-cloudevents-security@lists.cncf.io](mailto:cncf-cloudevents-security@lists.cncf.io). 195 | 196 | ## Additional SDK Resources 197 | 198 | - [List of current active maintainers](MAINTAINERS.md) 199 | - [How to contribute to the project](CONTRIBUTING.md) 200 | - [SDK's License](LICENSE) 201 | - [SDK's Release process](RELEASING.md) 202 | -------------------------------------------------------------------------------- /test/unit/ConvertTo-HttpMessage.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | Describe "ConvertTo-HttpMessage Function Tests" { 7 | BeforeAll { 8 | $expectedSpecVersion = '1.0' 9 | $expectedStructuredContentType = 'application/cloudevents+json' 10 | } 11 | 12 | Context "Converts CloudEvent in Binary Content Mode" { 13 | It 'Converts a CloudEvent with all properties and json format data' { 14 | 15 | # Arrange 16 | $expectedType = 'test' 17 | $expectedSource = 'urn:test' 18 | $expectedId = 'test-id-1' 19 | $expectedTime = Get-Date -Year 2021 -Month 1 -Day 18 -Hour 12 -Minute 30 -Second 0 -Millisecond 0 20 | $expectedDataContentType = 'application/json' 21 | 22 | $cloudEvent = New-CloudEvent ` 23 | -Type $expectedType ` 24 | -Source $expectedSource ` 25 | -Id $expectedId ` 26 | -Time $expectedTime 27 | 28 | $expectedData = @{ 'key1' = 'value2'; 'key3' = 'value4' } 29 | $cloudEvent = Set-CloudEventJsonData ` 30 | -CloudEvent $cloudEvent ` 31 | -Data $expectedData 32 | 33 | 34 | # Act 35 | $actual = $cloudEvent | ConvertTo-HttpMessage -ContentMode Binary 36 | 37 | # Assert 38 | $actual | Should -Not -Be $null 39 | $actual.Headers | Should -Not -Be $null 40 | $actual.Body | Should -Not -Be $null 41 | 42 | ## Assert Headers 43 | $actual.Headers['Content-Type'] | Should -Be $expectedDataContentType 44 | $actual.Headers['ce-source'] | Should -Be $expectedSource 45 | $actual.Headers['ce-specversion'] | Should -Be $expectedSpecVersion 46 | $actual.Headers['ce-type'] | Should -Be $expectedType 47 | $actual.Headers['ce-time'] | Should -Be ($expectedTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')) 48 | $actual.Headers['ce-id'] | Should -Be $expectedId 49 | 50 | ## Assert Body 51 | 52 | ## Expected Body is 53 | ## { 54 | ## "key1": "value2", 55 | ## "key3": "value4" 56 | ## } 57 | 58 | 59 | $actualDecodedBody = [Text.Encoding]::UTF8.GetString($actual.Body) | ConvertFrom-Json -AsHashtable 60 | $actualDecodedBody.Keys.Count | Should -Be 2 61 | $actualDecodedBody.key1 | Should -Be $expectedData.key1 62 | $actualDecodedBody.key3 | Should -Be $expectedData.key3 63 | } 64 | 65 | It 'Converts a CloudEvent with required properties and application/xml format data' { 66 | # Arrange 67 | $expectedType = 'test' 68 | $expectedId = 'test-id-1' 69 | $expectedSource = 'urn:test' 70 | $expectedDataContentType = 'application/xml' 71 | 72 | $cloudEvent = New-CloudEvent ` 73 | -Id $expectedId ` 74 | -Type $expectedType ` 75 | -Source $expectedSource 76 | 77 | $expectedData = '' 78 | $cloudEvent = Set-CloudEventData ` 79 | -CloudEvent $cloudEvent ` 80 | -Data $expectedData ` 81 | -DataContentType $expectedDataContentType 82 | 83 | # Act 84 | $actual = $cloudEvent | ConvertTo-HttpMessage -ContentMode Binary 85 | 86 | # Assert 87 | $actual | Should -Not -Be $null 88 | $actual.Headers | Should -Not -Be $null 89 | $actual.Body | Should -Not -Be $null 90 | 91 | ## Assert Headers 92 | $actual.Headers['Content-Type'] | Should -Be $expectedDataContentType 93 | $actual.Headers['ce-source'] | Should -Be $expectedSource 94 | $actual.Headers['ce-specversion'] | Should -Be $expectedSpecVersion 95 | $actual.Headers['ce-type'] | Should -Be $expectedType 96 | $actual.Headers['ce-id'] | Should -Be $expectedId 97 | 98 | ## Assert Body 99 | 100 | ## Expected Body is 101 | ## 102 | $actualDecodedBody =[Text.Encoding]::UTF8.GetString($actual.Body) 103 | $actualDecodedBody | Should -Be $expectedData 104 | } 105 | } 106 | 107 | Context "Converts CloudEvent in Structured Content Mode" { 108 | It 'Converts a CloudEvent with all properties and json format data' { 109 | # Arrange 110 | $expectedType = 'test' 111 | $expectedSource = 'urn:test' 112 | $expectedId = 'test-id-1' 113 | $expectedTime = Get-Date -Year 2021 -Month 1 -Day 18 -Hour 12 -Minute 30 -Second 0 -Millisecond 0 114 | $expectedDataContentType = 'application/json' 115 | 116 | $cloudEvent = New-CloudEvent ` 117 | -Type $expectedType ` 118 | -Source $expectedSource ` 119 | -Id $expectedId ` 120 | -Time $expectedTime 121 | 122 | $expectedData = @{ 'key1' = 'value2'; 'key3' = 'value4' } 123 | $cloudEvent = Set-CloudEventJsonData ` 124 | -CloudEvent $cloudEvent ` 125 | -Data $expectedData 126 | 127 | 128 | # Act 129 | $actual = $cloudEvent | ConvertTo-HttpMessage -ContentMode Structured 130 | 131 | # Assert 132 | $actual | Should -Not -Be $null 133 | $actual.Headers | Should -Not -Be $null 134 | $actual.Body | Should -Not -Be $null 135 | 136 | ## Assert Headers 137 | $headerContentTypes = $actual.Headers['Content-Type'].ToString().Split(';') 138 | $headerContentTypes[0].Trim() | Should -Be $expectedStructuredContentType 139 | $headerContentTypes[1].Trim() | Should -Be 'charset=utf-8' 140 | 141 | $actual.Headers['ce-source'] | Should -Be $expectedSource 142 | $actual.Headers['ce-specversion'] | Should -Be $expectedSpecVersion 143 | $actual.Headers['ce-type'] | Should -Be $expectedType 144 | $actual.Headers['ce-time'] | Should -Be ($expectedTime.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')) 145 | $actual.Headers['ce-id'] | Should -Be $expectedId 146 | 147 | ## Assert Body 148 | 149 | ## Expected Body is 150 | ## { 151 | ## "specversion": "1.0", 152 | ## "type": "test", 153 | ## "source": "urn:test", 154 | ## "id": "test-id-1", 155 | ## "time": "2021-01-18T12:30:00.9785466+02:00", 156 | ## "datacontenttype": "application/json", 157 | ## "data": "{ 158 | ## "key1": "value2", 159 | ## "key3": "value4" 160 | ## }" 161 | ## } 162 | $actualDecodedBody = [Text.Encoding]::UTF8.GetString($actual.Body) | ConvertFrom-Json -Depth 1 163 | 164 | $actualDecodedBody.specversion | Should -Be $expectedSpecVersion 165 | $actualDecodedBody.type | Should -Be $expectedType 166 | $actualDecodedBody.source | Should -Be $expectedSource 167 | Get-Date $actualDecodedBody.time | Should -Be $expectedTime 168 | $actualDecodedBody.datacontenttype | Should -Be $expectedDataContentType 169 | $actualDecodedData = $actualDecodedBody.data | ConvertFrom-Json -AsHashtable 170 | $actualDecodedData.Keys.Count | Should -Be 2 171 | $actualDecodedData.key1 | Should -Be $expectedData.key1 172 | $actualDecodedData.key3 | Should -Be $expectedData.key3 173 | } 174 | 175 | It 'Converts a CloudEvent with required properties and application/xml format data' { 176 | # Arrange 177 | $expectedId = ([Guid]::NewGuid().ToString()) 178 | $expectedType = 'test' 179 | $expectedSource = 'urn:test' 180 | $expectedDataContentType = 'application/xml' 181 | 182 | $cloudEvent = New-CloudEvent ` 183 | -Id $expectedId ` 184 | -Type $expectedType ` 185 | -Source $expectedSource 186 | 187 | $expectedData = '' 188 | $cloudEvent = Set-CloudEventData ` 189 | -CloudEvent $cloudEvent ` 190 | -Data $expectedData ` 191 | -DataContentType $expectedDataContentType 192 | 193 | # Act 194 | $actual = $cloudEvent | ConvertTo-HttpMessage -ContentMode Structured 195 | 196 | # Assert 197 | $actual | Should -Not -Be $null 198 | $actual.Headers | Should -Not -Be $null 199 | $actual.Body | Should -Not -Be $null 200 | 201 | ## Assert Headers 202 | $headerContentTypes = $actual.Headers['Content-Type'].ToString().Split(';') 203 | $headerContentTypes[0].Trim() | Should -Be $expectedStructuredContentType 204 | $headerContentTypes[1].Trim() | Should -Be 'charset=utf-8' 205 | $actual.Headers['ce-source'] | Should -Be $expectedSource 206 | $actual.Headers['ce-specversion'] | Should -Be $expectedSpecVersion 207 | $actual.Headers['ce-type'] | Should -Be $expectedType 208 | $actual.Headers['ce-id'] | Should -Be $expectedId 209 | 210 | ## Assert Body 211 | 212 | ## Expected Body is 213 | ## { 214 | ## "specversion": "1.0", 215 | ## "type": "test", 216 | ## "source": "urn:test", 217 | ## "datacontenttype": "application/xml", 218 | ## "data": "" 219 | ## } 220 | $actualDecodedBody = [Text.Encoding]::UTF8.GetString($actual.Body) | ConvertFrom-Json -Depth 1 221 | $actualDecodedBody.specversion | Should -Be $expectedSpecVersion 222 | $actualDecodedBody.type | Should -Be $expectedType 223 | $actualDecodedBody.source | Should -Be $expectedSource 224 | $actualDecodedBody.datacontenttype | Should -Be $expectedDataContentType 225 | $actualDecodedBody.data | Should -Be $expectedData 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021-Present The CloudEvents Authors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/integration/HttpIntegration.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | param( 7 | [Parameter()] 8 | [ValidateScript( { Test-Path $_ })] 9 | [string] 10 | $CloudEventsModulePath) 11 | 12 | Describe "Client-Server Integration Tests" { 13 | Context "Send And Receive CloudEvents over Http" { 14 | BeforeAll { 15 | $testServerUrl = 'http://localhost:52673/' 16 | 17 | $serverProcess = $null 18 | 19 | . (Join-Path $PSScriptRoot 'ProtocolConstants.ps1') 20 | 21 | # Starts CloudEvent Test Server 22 | $usePowerShell = (Get-Process -Id $pid).ProcessName 23 | $serverScript = Join-Path $PSScriptRoot 'HttpServer.ps1' 24 | $serverProcessArguments = "-Command $serverScript -CloudEventsModulePath '$CloudEventsModulePath' -ServerUrl '$testServerUrl'" 25 | 26 | $serverProcess = Start-Process ` 27 | -FilePath $usePowerShell ` 28 | -ArgumentList $serverProcessArguments ` 29 | -PassThru ` 30 | -NoNewWindow 31 | 32 | # Wait Server to Start 33 | $serverPingRequest = ` 34 | New-CloudEvent ` 35 | -Id ([Guid]::NewGuid()) ` 36 | -Type $script:ServerPingType ` 37 | -Source $script:ClientSource | ` 38 | ConvertTo-HttpMessage ` 39 | -ContentMode Structured 40 | 41 | $serverReady = $false 42 | $maxRetries = 10 43 | do { 44 | try { 45 | Invoke-WebRequest ` 46 | -Uri $testServerUrl ` 47 | -Headers $serverPingRequest.Headers ` 48 | -Body $serverPingRequest.Body | Out-Null 49 | $serverReady = $true 50 | } catch { 51 | Write-Verbose "Wait CloudEvents HTTP Test Server to start" 52 | Start-Sleep -Seconds 1 53 | $maxRetries-- 54 | } 55 | } while (-not $serverReady -and $maxRetries -gt 0) 56 | 57 | if ($maxRetries -eq 0) { 58 | throw "CloudEvents HTTP Test Server failed to start." 59 | } 60 | } 61 | 62 | AfterAll { 63 | # Requests Stop CloudEvent Test Server 64 | $serverStopRequest = ` 65 | New-CloudEvent ` 66 | -Id ([Guid]::NewGuid()) ` 67 | -Type $script:ServerStopType ` 68 | -Source $script:ClientSource | ` 69 | ConvertTo-HttpMessage ` 70 | -ContentMode Structured 71 | 72 | Invoke-WebRequest ` 73 | -Uri $testServerUrl ` 74 | -Headers $serverStopRequest.Headers ` 75 | -Body $serverStopRequest.Body | Out-Null 76 | 77 | if ($serverProcess -ne $null -and ` 78 | -not $serverProcess.HasExited) { 79 | $serverProcess | Wait-Process 80 | } 81 | } 82 | 83 | It 'Echo binary content mode cloud events' { 84 | # Arrange 85 | $cloudEvent = New-CloudEvent ` 86 | -Type $script:EchoBinaryType ` 87 | -Source $script:ClientSource ` 88 | -Id 'integration-test-1' ` 89 | -Time (Get-Date) | ` 90 | Set-CloudEventJsonData -Data @{ 91 | 'a1' = 'b' 92 | 'a2' = 'c' 93 | 'a3' = 'd' 94 | } 95 | 96 | # Act 97 | 98 | ## Convert CloudEvent to HTTP Message 99 | $httpRequest = ConvertTo-HttpMessage ` 100 | -CloudEvent $cloudEvent ` 101 | -ContentMode Binary 102 | 103 | ## Invoke WebRequest with the HTTP Message 104 | $httpResponse = Invoke-WebRequest ` 105 | -Uri $testServerUrl ` 106 | -Headers $httpRequest.Headers ` 107 | -Body $httpRequest.Body 108 | 109 | ## Convert HTTP Response to CloudEvent 110 | $resultCloudEvent = ConvertFrom-HttpMessage ` 111 | -Headers $httpResponse.Headers ` 112 | -Body $httpResponse.Content 113 | 114 | # Assert 115 | 116 | ## Assert echoed CloudEvent 117 | $resultCloudEvent | Should -Not -Be $null 118 | $resultCloudEvent.Source | Should -Be $script:ServerSource 119 | $resultCloudEvent.Type | Should -Be $script:EchoBinaryType 120 | $resultCloudEvent.Id | Should -Be $cloudEvent.Id 121 | $resultCloudEvent.Time | Should -BeGreaterThan $cloudEvent.Time 122 | 123 | ## Assert Result CloudEvent Data 124 | ## Read Data as Json 125 | $resultData = $resultCloudEvent | Read-CloudEventJsonData 126 | $resultData.a1 | Should -Be 'b' 127 | $resultData.a2 | Should -Be 'c' 128 | $resultData.a3 | Should -Be 'd' 129 | } 130 | 131 | It 'Echo binary content mode cloud events with XML data' { 132 | # Arrange 133 | $cloudEvent = New-CloudEvent ` 134 | -Type $script:EchoBinaryType ` 135 | -Source $script:ClientSource ` 136 | -Id 'integration-test-2' ` 137 | -Time (Get-Date) | ` 138 | Set-CloudEventXmlData -Data @{ 139 | 'a1' = @{ 140 | 'a2' = 'c' 141 | 'a3' = 'd' 142 | } 143 | } ` 144 | -AttributesKeysInElementAttributes $false 145 | 146 | # Act 147 | 148 | ## Convert CloudEvent to HTTP Message 149 | $httpRequest = ConvertTo-HttpMessage ` 150 | -CloudEvent $cloudEvent ` 151 | -ContentMode Binary 152 | 153 | ## Invoke WebRequest with the HTTP Message 154 | $httpResponse = Invoke-WebRequest ` 155 | -Uri $testServerUrl ` 156 | -Headers $httpRequest.Headers ` 157 | -Body $httpRequest.Body 158 | 159 | ## Convert HTTP Response to CloudEvent 160 | $resultCloudEvent = ConvertFrom-HttpMessage ` 161 | -Headers $httpResponse.Headers ` 162 | -Body $httpResponse.Content 163 | 164 | # Assert 165 | 166 | ## Assert echoed CloudEvent 167 | $resultCloudEvent | Should -Not -Be $null 168 | $resultCloudEvent.Source | Should -Be $script:ServerSource 169 | $resultCloudEvent.Type | Should -Be $script:EchoBinaryType 170 | $resultCloudEvent.Id | Should -Be $cloudEvent.Id 171 | $resultCloudEvent.Time | Should -BeGreaterThan $cloudEvent.Time 172 | 173 | ## Assert Result CloudEvent Data 174 | ## Read Data as Xml 175 | $resultData = $resultCloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes' 176 | $resultData -is [hashtable] | Should -Be $true 177 | $resultData.a1 -is [hashtable] | Should -Be $true 178 | $resultData.a1.a2 | Should -Be 'c' 179 | $resultData.a1.a3 | Should -Be 'd' 180 | } 181 | 182 | It 'Echo structured content mode cloud events' { 183 | # Arrange 184 | $cloudEvent = New-CloudEvent ` 185 | -Type $script:EchoStructuredType ` 186 | -Source $script:ClientSource ` 187 | -Id 'integration-test-3' ` 188 | -Time (Get-Date) | ` 189 | Set-CloudEventJsonData -Data @{ 190 | 'b1' = 'd' 191 | 'b2' = 'e' 192 | 'b3' = 'f' 193 | } 194 | 195 | # Act 196 | 197 | ## Convert CloudEvent to HTTP Message 198 | $httpRequest = ConvertTo-HttpMessage ` 199 | -CloudEvent $cloudEvent ` 200 | -ContentMode Structured 201 | 202 | ## Invoke WebRequest with the HTTP Message 203 | $httpResponse = Invoke-WebRequest ` 204 | -Uri $testServerUrl ` 205 | -Headers $httpRequest.Headers ` 206 | -Body $httpRequest.Body 207 | 208 | ## Convert HTTP Response to CloudEvent 209 | $resultCloudEvent = ConvertFrom-HttpMessage ` 210 | -Headers $httpResponse.Headers ` 211 | -Body $httpResponse.Content 212 | 213 | # Assert 214 | 215 | ## Assert echoed CloudEvent 216 | $resultCloudEvent | Should -Not -Be $null 217 | $resultCloudEvent.Source | Should -Be $script:ServerSource 218 | $resultCloudEvent.Type | Should -Be $script:EchoStructuredType 219 | $resultCloudEvent.Id | Should -Be $cloudEvent.Id 220 | $resultCloudEvent.Time | Should -BeGreaterThan $cloudEvent.Time 221 | 222 | ## Assert Result CloudEvent Data 223 | ## Read Data as Json 224 | $resultData = $resultCloudEvent | Read-CloudEventJsonData 225 | $resultData.b1 | Should -Be 'd' 226 | $resultData.b2 | Should -Be 'e' 227 | $resultData.b3 | Should -Be 'f' 228 | } 229 | 230 | It 'Echo structured content mode cloud events with XML data' { 231 | # Arrange 232 | $cloudEvent = New-CloudEvent ` 233 | -Type $script:EchoStructuredType ` 234 | -Source $script:ClientSource ` 235 | -Id 'integration-test-4' ` 236 | -Time (Get-Date) | ` 237 | Set-CloudEventXmlData -Data @{ 238 | 'b1' = @{ 239 | 'b2' = 'e' 240 | 'b3' = 'f' 241 | } 242 | } ` 243 | -AttributesKeysInElementAttributes $false 244 | 245 | # Act 246 | 247 | ## Convert CloudEvent to HTTP Message 248 | $httpRequest = ConvertTo-HttpMessage ` 249 | -CloudEvent $cloudEvent ` 250 | -ContentMode Structured 251 | 252 | ## Invoke WebRequest with the HTTP Message 253 | $httpResponse = Invoke-WebRequest ` 254 | -Uri $testServerUrl ` 255 | -Headers $httpRequest.Headers ` 256 | -Body $httpRequest.Body 257 | 258 | ## Convert HTTP Response to CloudEvent 259 | $resultCloudEvent = ConvertFrom-HttpMessage ` 260 | -Headers $httpResponse.Headers ` 261 | -Body $httpResponse.Content 262 | 263 | # Assert 264 | 265 | ## Assert echoed CloudEvent 266 | $resultCloudEvent | Should -Not -Be $null 267 | $resultCloudEvent.Source | Should -Be $script:ServerSource 268 | $resultCloudEvent.Type | Should -Be $script:EchoStructuredType 269 | $resultCloudEvent.Id | Should -Be $cloudEvent.Id 270 | $resultCloudEvent.Time | Should -BeGreaterThan $cloudEvent.Time 271 | 272 | ## Assert Result CloudEvent Data 273 | ## Read Data as Xml 274 | $resultData = $resultCloudEvent | Read-CloudEventXmlData -ConvertMode 'SkipAttributes' 275 | $resultData -is [hashtable] | Should -Be $true 276 | $resultData.b1 -is [hashtable] | Should -Be $true 277 | $resultData.b1.b2 | Should -Be 'e' 278 | $resultData.b1.b3 | Should -Be 'f' 279 | } 280 | 281 | It 'Send cloud event expecting no result' { 282 | # Arrange 283 | $cloudEvent = New-CloudEvent ` 284 | -Type 'no-content' ` 285 | -Source $script:ClientSource ` 286 | -Id 'integration-test-5' ` 287 | -Time (Get-Date) | ` 288 | Set-CloudEventData ` 289 | -Data 'This is text data' ` 290 | -DataContentType 'application/text' 291 | 292 | # Act 293 | 294 | ## Convert CloudEvent to HTTP Message 295 | $httpRequest = ConvertTo-HttpMessage ` 296 | -CloudEvent $cloudEvent ` 297 | -ContentMode Structured 298 | 299 | ## Invoke WebRequest with the HTTP Message 300 | $httpResponse = Invoke-WebRequest ` 301 | -Uri $testServerUrl ` 302 | -Headers $httpRequest.Headers ` 303 | -Body $httpRequest.Body 304 | 305 | # Assert 306 | $httpResponse.StatusCode | Should -Be ([int]([System.Net.HttpStatusCode]::NoContent)) 307 | } 308 | } 309 | } -------------------------------------------------------------------------------- /test/unit/ConvertFrom-HttpMessage.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | Describe "ConvertFrom-HttpMessage Function Tests" { 7 | BeforeEach { 8 | $script:expectedSpecVersion = '1.0' 9 | $script:expectedStructuredContentType = 'application/cloudevents+json' 10 | } 11 | 12 | Context "Converts CloudEvent in Binary Content Mode" { 13 | It 'Converts a CloudEvent with all properties and json format data' { 14 | 15 | # Arrange 16 | $expectedType = 'test' 17 | $expectedSource = 'urn:test' 18 | $expectedId = 'test-id-1' 19 | $expectedTime = Get-Date ` 20 | -Year 2021 ` 21 | -Month 1 ` 22 | -Day 18 ` 23 | -Hour 12 ` 24 | -Minute 30 ` 25 | -Second 23 ` 26 | -MilliSecond 134 27 | $expectedDataContentType = 'application/json' 28 | 29 | $headers = @{ 30 | 'Content-Type' = @($expectedDataContentType, 'charset=utf-8') 31 | 'ce-specversion' = $expectedSpecVersion 32 | 'ce-type' = $expectedType 33 | 'ce-time' = $expectedTime.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') 34 | 'ce-id' = $expectedId 35 | 'ce-source' = $expectedSource 36 | } 37 | 38 | $body =[Text.Encoding]::UTF8.GetBytes('{ 39 | "l10": { 40 | "l2": { 41 | "l3": "wow" 42 | } 43 | }, 44 | "l11": "mhm" 45 | }') 46 | 47 | # Act 48 | $actual = ConvertFrom-HttpMessage ` 49 | -Headers $headers ` 50 | -Body $body 51 | 52 | # Assert 53 | $actual | Should -Not -Be $null 54 | $actual.Type | Should -Be $expectedType 55 | $actual.Source | Should -Be $expectedSource 56 | $actual.Id | Should -Be $expectedId 57 | $actual.Time.Year | Should -Be $expectedTime.Year 58 | $actual.Time.Month | Should -Be $expectedTime.Month 59 | $actual.Time.Day | Should -Be $expectedTime.Day 60 | $actual.Time.Hours | Should -Be $expectedTime.Hours 61 | $actual.Time.Minutes | Should -Be $expectedTime.Minutes 62 | $actual.Time.Seconds | Should -Be $expectedTime.Seconds 63 | $actual.Time.MilliSeconds | Should -Be $expectedTime.MilliSeconds 64 | $actual.DataContentType | Should -Be $expectedDataContentType 65 | 66 | ## Assert Data 67 | $actualHTData = $actual | Read-CloudEventJsonData -Depth 3 68 | 69 | $actualHTData | Should -Not -Be $null 70 | $actualHTData.l10.l2.l3 | Should -Be 'wow' 71 | $actualHTData.l11 | Should -Be 'mhm' 72 | 73 | } 74 | 75 | It 'Converts a CloudEvent with required properties and application/xml format data' { 76 | # Arrange 77 | $expectedType = 'test' 78 | $expectedSource = 'urn:test' 79 | $expectedDataContentType = 'application/xml' 80 | $expectedId = 'test-id-2' 81 | $expectedData = [Text.Encoding]::UTF8.GetBytes('') 82 | 83 | $headers = @{ 84 | 'Content-Type' = @($expectedDataContentType, 'charset=utf-8') 85 | 'ce-specversion' = $expectedSpecVersion 86 | 'ce-type' = $expectedType 87 | 'ce-source' = $expectedSource 88 | 'ce-id' = $expectedId 89 | } 90 | 91 | $body = $expectedData 92 | 93 | # Act 94 | $actual = ConvertFrom-HttpMessage ` 95 | -Headers $headers ` 96 | -Body $body 97 | 98 | # Assert 99 | $actual | Should -Not -Be $null 100 | $actual.Id | Should -Be $expectedId 101 | $actual.Type | Should -Be $expectedType 102 | $actual.Source | Should -Be $expectedSource 103 | $actual.DataContentType | Should -Be $expectedDataContentType 104 | $actual.Data | Should -Be $expectedData 105 | 106 | ## Assert Data obtained by Read-CloudEventData 107 | $actualData = $actual | Read-CloudEventData 108 | 109 | $actualData | Should -Be $expectedData 110 | } 111 | 112 | It 'Returns null when ce-id is not specified' { 113 | # Arrange 114 | $expectedType = 'test' 115 | $expectedSource = 'urn:test' 116 | $expectedDataContentType = 'application/xml' 117 | $expectedData = [Text.Encoding]::UTF8.GetBytes('') 118 | 119 | $headers = @{ 120 | 'Content-Type' = @($expectedDataContentType, 'charset=utf-8') 121 | 'ce-specversion' = $expectedSpecVersion 122 | 'ce-type' = $expectedType 123 | 'ce-source' = $expectedSource 124 | } 125 | 126 | $body = $expectedData 127 | 128 | # Act 129 | $actual = ConvertFrom-HttpMessage ` 130 | -Headers $headers ` 131 | -Body $body 132 | 133 | # Assert 134 | $actual | Should -Be $null 135 | } 136 | 137 | It 'Returns null when ce-source is not specified' { 138 | # Arrange 139 | $expectedType = 'test' 140 | $expectedDataContentType = 'application/xml' 141 | $expectedId = 'test-id-3' 142 | $expectedData = [Text.Encoding]::UTF8.GetBytes('') 143 | 144 | $headers = @{ 145 | 'Content-Type' = @($expectedDataContentType, 'charset=utf-8') 146 | 'ce-specversion' = $expectedSpecVersion 147 | 'ce-type' = $expectedType 148 | 'ce-id' = $expectedId 149 | } 150 | 151 | $body = $expectedData 152 | 153 | # Act 154 | $actual = ConvertFrom-HttpMessage ` 155 | -Headers $headers ` 156 | -Body $body 157 | 158 | # Assert 159 | $actual | Should -Be $null 160 | } 161 | 162 | It 'Returns null when ce-type is not specified' { 163 | # Arrange 164 | $expectedSource = 'urn:test' 165 | $expectedDataContentType = 'application/xml' 166 | $expectedId = 'test-id-4' 167 | $expectedData = [Text.Encoding]::UTF8.GetBytes('') 168 | 169 | $headers = @{ 170 | 'Content-Type' = @($expectedDataContentType, 'charset=utf-8') 171 | 'ce-specversion' = $expectedSpecVersion 172 | 'ce-source' = $expectedSource 173 | 'ce-id' = $expectedId 174 | } 175 | 176 | $body = $expectedData 177 | 178 | # Act 179 | $actual = ConvertFrom-HttpMessage ` 180 | -Headers $headers ` 181 | -Body $body 182 | 183 | # Assert 184 | $actual | Should -Be $null 185 | } 186 | } 187 | 188 | Context "Converts CloudEvent in Structured Content Mode" { 189 | It 'Converts a CloudEvent with all properties and json format data' { 190 | # Arrange 191 | $expectedType = 'test' 192 | $expectedSource = 'urn:test' 193 | $expectedId = 'test-id-1' 194 | $expectedTime = Get-Date ` 195 | -Year 2021 ` 196 | -Month 1 ` 197 | -Day 18 ` 198 | -Hour 12 ` 199 | -Minute 30 ` 200 | -Second 23 ` 201 | -MilliSecond 134 202 | $expectedDataContentType = 'application/json' 203 | 204 | $headers = @{ 205 | 'Content-Type' = $expectedStructuredContentType 206 | } 207 | 208 | $eventData = @{ 209 | 'l10' = @{ 210 | 'l2' = @{ 211 | 'l3' = 'wow' 212 | } 213 | } 214 | 'l11' = 'mhm' 215 | } 216 | 217 | $structuredJsonBody = @{ 218 | 'specversion' = $expectedSpecVersion 219 | 'type' = $expectedType 220 | 'time' = $expectedTime.ToString('yyyy-MM-ddTHH:mm:ss.fffZ') 221 | 'id' = $expectedId 222 | 'source' = $expectedSource 223 | 'datacontenttype' = $expectedDataContentType 224 | } 225 | 226 | $structuredJsonBody['data_base64'] = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes(($eventData | ConvertTo-Json -Depth 3))) 227 | 228 | $body = [Text.Encoding]::UTF8.GetBytes(($structuredJsonBody | ConvertTo-Json)) 229 | 230 | # Act 231 | $actual = ConvertFrom-HttpMessage ` 232 | -Headers $headers ` 233 | -Body $body 234 | 235 | # Assert 236 | $actual | Should -Not -Be $null 237 | $actual.Type | Should -Be $expectedType 238 | $actual.Source | Should -Be $expectedSource 239 | $actual.Id | Should -Be $expectedId 240 | $actual.Time.Year | Should -Be $expectedTime.Year 241 | $actual.Time.Month | Should -Be $expectedTime.Month 242 | $actual.Time.Day | Should -Be $expectedTime.Day 243 | $actual.Time.Hours | Should -Be $expectedTime.Hours 244 | $actual.Time.Minutes | Should -Be $expectedTime.Minutes 245 | $actual.Time.Seconds | Should -Be $expectedTime.Seconds 246 | $actual.Time.MilliSeconds | Should -Be $expectedTime.MilliSeconds 247 | $actual.DataContentType | Should -Be $expectedDataContentType 248 | 249 | ## Assert Data 250 | $actualHTData = $actual | Read-CloudEventJsonData -Depth 3 251 | 252 | $actualHTData | Should -Not -Be $null 253 | $actualHTData -is [hashtable] | Should -Be $true 254 | $actualHTData.l10.l2.l3 | Should -Be 'wow' 255 | $actualHTData.l11 | Should -Be 'mhm' 256 | } 257 | 258 | It 'Converts a CloudEvent with required properties and application/xml format data' { 259 | # Arrange 260 | $expectedType = 'test' 261 | $expectedSource = 'urn:test' 262 | $expectedDataContentType = 'application/xml' 263 | $expectedData = [Text.Encoding]::UTF8.GetBytes('') 264 | 265 | $headers = @{ 266 | 'Content-Type' = $expectedStructuredContentType 267 | } 268 | 269 | $structuredJsonBody = @{ 270 | 'specversion' = $expectedSpecVersion 271 | 'type' = $expectedType 272 | 'source' = $expectedSource 273 | 'datacontenttype' = $expectedDataContentType 274 | 'data' = $expectedData 275 | } 276 | 277 | $body = [Text.Encoding]::UTF8.GetBytes(($structuredJsonBody | ConvertTo-Json)) 278 | 279 | # Act 280 | $actual = ConvertFrom-HttpMessage ` 281 | -Headers $headers ` 282 | -Body $body 283 | 284 | # Assert 285 | $actual | Should -Not -Be $null 286 | $actual.Type | Should -Be $expectedType 287 | $actual.Source | Should -Be $expectedSource 288 | $actual.DataContentType | Should -Be $expectedDataContentType 289 | $actual.Data | Should -Be $expectedData 290 | 291 | ## Assert Data obtained by Read-CloudEventData 292 | $actualData = $actual | Read-CloudEventData 293 | 294 | $actualData | Should -Be $expectedData 295 | } 296 | 297 | It 'Throws error when CloudEvent encoding is not non-batching JSON' { 298 | # Arrange 299 | $unsupportedContentFormat = 'application/cloudevents-batch+json' 300 | 301 | $expectedType = 'test' 302 | $expectedSource = 'urn:test' 303 | $expectedDataContentType = 'application/xml' 304 | $expectedData = [Text.Encoding]::UTF8.GetBytes('') 305 | 306 | $headers = @{ 307 | 'Content-Type' = $unsupportedContentFormat 308 | } 309 | 310 | $structuredJsonBody = @{ 311 | 'specversion' = $expectedSpecVersion 312 | 'type' = $expectedType 313 | 'source' = $expectedSource 314 | 'datacontenttype' = $expectedDataContentType 315 | 'data' = $expectedData 316 | } 317 | 318 | $body = [Text.Encoding]::UTF8.GetBytes(($structuredJsonBody | ConvertTo-Json)) 319 | 320 | # Act & Assert 321 | {ConvertFrom-HttpMessage ` 322 | -Headers $headers ` 323 | -Body $body } | ` 324 | Should -Throw "*Unsupported CloudEvents encoding*" 325 | } 326 | 327 | It 'Returns null when no Content-Type header' { 328 | # Arrange 329 | 330 | $expectedType = 'test' 331 | $expectedSource = 'urn:test' 332 | $expectedDataContentType = 'application/xml' 333 | $expectedData = [Text.Encoding]::UTF8.GetBytes('') 334 | $structuredJsonBody = @{ 335 | 'specversion' = $expectedSpecVersion 336 | 'type' = $expectedType 337 | 'source' = $expectedSource 338 | 'datacontenttype' = $expectedDataContentType 339 | 'data' = $expectedData 340 | } 341 | 342 | $body = [Text.Encoding]::UTF8.GetBytes(($structuredJsonBody | ConvertTo-Json)) 343 | 344 | # Act & Assert 345 | $ce = ConvertFrom-HttpMessage ` 346 | -Headers @{} ` 347 | -Body $body 348 | $ce | Should -Be $null 349 | } 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/CloudEventsPowerShell/dataserialization/xml.Tests.ps1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | BeforeAll { 7 | . $PSCommandPath.Replace('.Tests.ps1', '.ps1') 8 | } 9 | 10 | Describe "CloudEvent Xml Serializers Unit Tests" { 11 | Context "ConvertFrom-CEDataXml" { 12 | It "Should convert single element XML text to a hashtable" { 13 | # Arrange 14 | $inputXml = "value" 15 | $expected = @{'key' = 'value'} 16 | 17 | # Act 18 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode SkipAttributes 19 | 20 | # Assert 21 | $actual | Should -Not -Be $null 22 | $actual -is [hashtable] | Should -Be $true 23 | $actual.Keys.Count | Should -Be $expected.Keys.Count 24 | $actual.key | Should -Be $expected.key 25 | } 26 | 27 | It "Should convert XML with array of nodes to a hashtable" { 28 | # Arrange 29 | $inputXml = "value1value2value3" 30 | $expected = @{'keys' = @{'key' = @('value1', 'value2', 'value3')}} 31 | 32 | # Act 33 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode SkipAttributes 34 | 35 | # Assert 36 | $actual | Should -Not -Be $null 37 | $actual -is [hashtable] | Should -Be $true 38 | $actual.keys.key[0] | Should -Be $expected.keys.key[0] 39 | $actual.keys.key[1] | Should -Be $expected.keys.key[1] 40 | $actual.keys.key[2] | Should -Be $expected.keys.key[2] 41 | } 42 | 43 | It "Should convert XML with array of nodes to a hashtable and ConvertMode 'AlwaysAttrValue'" { 44 | # Arrange 45 | $inputXml = "value1value2value3" 46 | $expected = @{ 47 | 'keys' = @{ 48 | 'Attributes' = $null 49 | 'Value' = @{ 50 | 'key' = @{ 51 | 'Attributes' = $null 52 | 'Value' = @( 53 | @{ 54 | 'Attributes' = $null 55 | 'Value' = 'value1' 56 | }, 57 | @{ 58 | 'Attributes' = $null 59 | 'Value' = 'value2' 60 | }, 61 | @{ 62 | 'Attributes' = $null 63 | 'Value' = 'value3' 64 | } 65 | ) 66 | } 67 | } 68 | } 69 | 70 | } 71 | 72 | # Act 73 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode AlwaysAttrValue 74 | 75 | # Assert 76 | $actual | Should -Not -Be $null 77 | $actual -is [hashtable] | Should -Be $true 78 | $actual.keys.Attributes | Should -Be $null 79 | $actual.keys.Value.key.Value[0].Value | Should -Be $expected.keys.Value.key.Value[0].Value 80 | $actual.keys.Value.key.Attributes | Should -Be $null 81 | $actual.keys.Value.key.Value[0].Attributes | Should -Be $null 82 | $actual.keys.Value.key.Value[1].Value | Should -Be $expected.keys.Value.key.Value[1].Value 83 | $actual.keys.Value.key.Value[2].Value | Should -Be $expected.keys.Value.key.Value[2].Value 84 | } 85 | 86 | It "Should convert single element XML with ConvertMode = 'AlwaysAttrValue'" { 87 | # Arrange 88 | $inputXml = "value-1value-2" 89 | $expected = @{ 90 | 'root' = @{ 91 | 'Attributes' = $null 92 | 'Value' = @{ 93 | 'key1' = @{ 94 | 'Attributes' = @{ 95 | 'att1' = 'true' 96 | } 97 | 'Value' = 'value-1' 98 | } 99 | 'key2' = @{ 100 | 'Attributes' = $null 101 | 'Value' = 'value-2' 102 | } 103 | } 104 | } 105 | } 106 | 107 | # Act 108 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode AlwaysAttrValue 109 | 110 | # Assert 111 | $actual | Should -Not -Be $null 112 | $actual -is [hashtable] | Should -Be $true 113 | $actual.root.Value.key1.Attributes.att1 | Should -Be $expected.root.Value.key1.Attributes.att1 114 | $actual.root.Value.key1.Value | Should -Be $expected.root.Value.key1.Value 115 | $actual.root.Value.key2.Attributes | Should -Be $expected.root.Value.key2.Attributes 116 | $actual.root.Value.key2.Value | Should -Be $expected.root.Value.key2.Value 117 | } 118 | 119 | It "Should convert elements with attributes with ConvertMode = 'AttrValueWhenAttributes'" { 120 | # Arrange 121 | $inputXml = "valuevalue-1" 122 | $expected = @{ 123 | 'root' = @{ 124 | 'key' = 'value' 125 | 'withattr' = @{ 126 | 'Attributes' = @{ 127 | 'att1' = 'true' 128 | 'att2' = 'false' 129 | } 130 | 'Value' = 'value-1' 131 | } 132 | } 133 | } 134 | 135 | # Act 136 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode AttrValueWhenAttributes 137 | 138 | # Assert 139 | $actual | Should -Not -Be $null 140 | $actual -is [hashtable] | Should -Be $true 141 | $actual.root.key | Should -Be $expected.root.key 142 | $actual.root.withattr.Attributes.att1 | Should -Be $expected.root.withattr.Attributes.att1 143 | $actual.root.withattr.Attributes.att2 | Should -Be $expected.root.withattr.Attributes.att2 144 | $actual.root.withattr.Value | Should -Be $expected.root.withattr.Value 145 | } 146 | 147 | It "Should convert XML with nested elements to a hashtable skipping attribute properties" { 148 | # Arrange 149 | $inputXml = '857085702021-02-04T08:51:53.723999ZdcuivcqaDCdatacenter-2clsdomain-c710.161.140.163host-21User dcui@127.0.0.1 logged in as VMware-client/6.5.0127.0.0.1VMware-client/6.4.0en52b910cf-661f-f72d-9f86-fb82113404b7' 150 | $expected = @{ 151 | 'UserLoginSessionEvent' = @{ 152 | 'key' = '8570' 153 | 'createdTime' = '2021-02-04T08:51:53.723999Z' 154 | 'userName' = 'dcui' 155 | 'datacenter' = @{ 156 | 'name' = 'vcqaDC' 157 | 'datacenter' = 'datacenter-2' 158 | } 159 | 'computeResource' = @{ 160 | 'name' = 'cls' 161 | 'computeResource' = 'domain-c7' 162 | } 163 | 'host' = @{ 164 | 'name' = '10.161.140.163' 165 | 'host' = 'host-21' 166 | } 167 | 'fullFormattedMessage' = 'User dcui@127.0.0.1 logged in as VMware-client/6.5.0' 168 | 'ipAddress' = '127.0.0.1' 169 | 'userAgent' = 'VMware-client/6.4.0' 170 | 'locale' = 'en' 171 | 'sessionId' = '52b910cf-661f-f72d-9f86-fb82113404b7' 172 | } 173 | } 174 | 175 | # Act 176 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode SkipAttributes 177 | 178 | # Assert 179 | $actual | Should -Not -Be $null 180 | $actual -is [hashtable] | Should -Be $true 181 | $actual.UserLoginSessionEvent -is [hashtable] | Should -Be $true 182 | $actual.UserLoginSessionEvent.key | Should -Be $expected.UserLoginSessionEvent.key 183 | $actual.UserLoginSessionEvent.createdTime | Should -Be $expected.UserLoginSessionEvent.createdTime 184 | $actual.UserLoginSessionEvent.userName | Should -Be $expected.UserLoginSessionEvent.userName 185 | $actual.UserLoginSessionEvent.datacenter -is [hashtable] | Should -Be $true 186 | $actual.UserLoginSessionEvent.datacenter.name | Should -Be $expected.UserLoginSessionEvent.datacenter.name 187 | $actual.UserLoginSessionEvent.datacenter.datacenter | Should -Be $expected.UserLoginSessionEvent.datacenter.datacenter 188 | $actual.UserLoginSessionEvent.host -is [hashtable] | Should -Be $true 189 | $actual.UserLoginSessionEvent.host.name | Should -Be $expected.UserLoginSessionEvent.host.name 190 | $actual.UserLoginSessionEvent.host.host | Should -Be $expected.UserLoginSessionEvent.host.host 191 | $actual.UserLoginSessionEvent.fullFormattedMessage | Should -Be $expected.UserLoginSessionEvent.fullFormattedMessage 192 | } 193 | 194 | It "Should convert XML with nested elements to a hashtable with ConvertMode = 'AttrValueWhenAttributes'" { 195 | # Arrange 196 | $inputXml = '857085702021-02-04T08:51:53.723999ZdcuivcqaDCdatacenter-2clsdomain-c710.161.140.163host-21User dcui@127.0.0.1 logged in as VMware-client/6.5.0127.0.0.1VMware-client/6.4.0en52b910cf-661f-f72d-9f86-fb82113404b7' 197 | $expected = @{ 198 | 'UserLoginSessionEvent' = @{ 199 | 'key' = '8570' 200 | 'createdTime' = '2021-02-04T08:51:53.723999Z' 201 | 'userName' = 'dcui' 202 | 'datacenter' = @{ 203 | 'name' = 'vcqaDC' 204 | 'datacenter' = @{ 205 | 'Attributes' = @{ 206 | 'type' = 'Datacenter' 207 | } 208 | 'Value' = 'datacenter-2' 209 | } 210 | } 211 | 'computeResource' = @{ 212 | 'name' = 'cls' 213 | 'computeResource' = @{ 214 | 'Attributes' = @{ 215 | 'type' = 'ClusterComputeResource' 216 | } 217 | 'Value' = 'domain-c7' 218 | } 219 | } 220 | 'host' = @{ 221 | 'name' = '10.161.140.163' 222 | 'host' = @{ 223 | 'Attributes' = @{ 224 | 'type' = 'HostSystem' 225 | } 226 | 'Value' = 'host-21' 227 | } 228 | } 229 | 'fullFormattedMessage' = 'User dcui@127.0.0.1 logged in as VMware-client/6.5.0' 230 | 'ipAddress' = '127.0.0.1' 231 | 'userAgent' = 'VMware-client/6.4.0' 232 | 'locale' = 'en' 233 | 'sessionId' = '52b910cf-661f-f72d-9f86-fb82113404b7' 234 | } 235 | } 236 | 237 | # Act 238 | $actual = ConvertFrom-CEDataXml -InputString $inputXml -ConvertMode AttrValueWhenAttributes 239 | 240 | # Assert 241 | $actual | Should -Not -Be $null 242 | $actual -is [hashtable] | Should -Be $true 243 | $actual.UserLoginSessionEvent -is [hashtable] | Should -Be $true 244 | $actual.UserLoginSessionEvent.key | Should -Be $expected.UserLoginSessionEvent.key 245 | $actual.UserLoginSessionEvent.createdTime | Should -Be $expected.UserLoginSessionEvent.createdTime 246 | $actual.UserLoginSessionEvent.userName | Should -Be $expected.UserLoginSessionEvent.userName 247 | $actual.UserLoginSessionEvent.datacenter -is [hashtable] | Should -Be $true 248 | $actual.UserLoginSessionEvent.datacenter.name | Should -Be $expected.UserLoginSessionEvent.datacenter.name 249 | $actual.UserLoginSessionEvent.datacenter.datacenter.Attributes.type | Should -Be $expected.UserLoginSessionEvent.datacenter.datacenter.Attributes.type 250 | $actual.UserLoginSessionEvent.datacenter.datacenter.Value | Should -Be $expected.UserLoginSessionEvent.datacenter.datacenter.Value 251 | $actual.UserLoginSessionEvent.computeResource.computeResource.Attributes.type | Should -Be $expected.UserLoginSessionEvent.computeResource.computeResource.Attributes.type 252 | $actual.UserLoginSessionEvent.computeResource.computeResource.Value | Should -Be $expected.UserLoginSessionEvent.computeResource.computeResource.Value 253 | $actual.UserLoginSessionEvent.host.host.Attributes.type | Should -Be $expected.UserLoginSessionEvent.host.host.Attributes.type 254 | $actual.UserLoginSessionEvent.host.host.Value | Should -Be $expected.UserLoginSessionEvent.host.host.Value 255 | } 256 | } 257 | 258 | Context "ConvertTo-CEDataXml" { 259 | It "Should convert single hashtable to XML" { 260 | # Arrange 261 | $inputHashtable = @{'key' = 'value'} 262 | $expected = "value" 263 | 264 | # Act 265 | $actual = ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $false 266 | 267 | # Assert 268 | $actual | Should -Be $expected 269 | } 270 | 271 | It "Should convert hashtable with array values to XML " { 272 | # Arrange 273 | $inputHashtable = @{'keys' = @{'key' = @('value1', 'value2', 'value3')}} 274 | $expected = "value1value2value3" 275 | 276 | # Act 277 | $actual = ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $false 278 | 279 | # Assert 280 | $actual | Should -Be $expected 281 | } 282 | 283 | It "Should convert hashtable with hashtable values to XML " { 284 | # Arrange 285 | $inputHashtable = @{ 286 | 'UserLoginSessionEvent' = @{ 287 | 'datacenter' = @{ 288 | 'datacenter' = 'datacenter-2' 289 | } 290 | } 291 | } 292 | $expected = 'datacenter-2' 293 | 294 | # Act 295 | $actual = ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $false 296 | 297 | # Assert 298 | $actual | Should -Be $expected 299 | } 300 | 301 | It "Should convert hashtable with Attributes keys to XML elements without attributes" { 302 | # Arrange 303 | $inputHashtable = @{ 304 | 'UserLoginSessionEvent' = @{ 305 | 'computeResource' = @{ 306 | 'name' = 'cls' 307 | 'computeResource' = @{ 308 | 'Attributes' = @{ 309 | 'type' = 'ClusterComputeResource' 310 | } 311 | 'Value' = 'domain-c7' 312 | } 313 | } 314 | } 315 | } 316 | $expected = 'ClusterComputeResourcedomain-c7cls' 317 | 318 | # Act 319 | $actual = ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $false 320 | 321 | # Assert 322 | ## We can not expected the xml elements to be ordered as in the expected string, 323 | ## so test Xml ignoring the elements order 324 | $actual.Contains('ClusterComputeResource') | Should -Be $true 325 | $actual.Contains('domain-c7') | Should -Be $true 326 | $actual.Contains('cls') | Should -Be $true 327 | $actual.IndexOf('') | Should -BeGreaterThan $actual.IndexOf('') 328 | $actual.IndexOf('') | Should -BeLessThan $actual.IndexOf('') 329 | } 330 | 331 | 332 | It "Should convert hashtable with Attributes keys to XML elements with attributes" { 333 | # Arrange 334 | $inputHashtable = @{ 335 | 'UserLoginSessionEvent' = @{ 336 | 'computeResource' = @{ 337 | 'name' = 'cls' 338 | 'computeResource' = @{ 339 | 'Attributes' = @{ 340 | 'type' = 'ClusterComputeResource' 341 | } 342 | 'Value' = 'domain-c7' 343 | } 344 | } 345 | } 346 | } 347 | $expected = 'domain-c7cls' 348 | 349 | # Act 350 | $actual = ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $true 351 | 352 | # Assert 353 | ## We can not expected the xml elements to be ordered as in the expected string, 354 | ## so test Xml ignoring the elements order 355 | $actual.StartsWith('') | Should -Be $true 356 | $actual.Contains('domain-c7') | Should -Be $true 357 | $actual.Contains('cls') | Should -Be $true 358 | } 359 | 360 | It "Should throw when input hashtable has more than one root" { 361 | # Arrange 362 | $inputHashtable = @{'key1' = 'val1'; 'key2' = 'val2'} 363 | 364 | # Act & Assert 365 | { ConvertTo-CEDataXml -InputObject $inputHashtable -AttributesKeysInElementAttributes $false } | Should -Throw 'Input Hashtable must have single root key' 366 | } 367 | } 368 | } -------------------------------------------------------------------------------- /src/CloudEventsPowerShell/CloudEvents.Sdk.psm1: -------------------------------------------------------------------------------- 1 | # ************************************************************************** 2 | # Copyright (c) Cloud Native Foundation. 3 | # SPDX-License-Identifier: Apache-2.0 4 | # ************************************************************************** 5 | 6 | $xmlDataSerilizationLibPath = Join-Path (Join-Path $PSScriptRoot 'dataserialization') 'xml.ps1' 7 | . $xmlDataSerilizationLibPath 8 | 9 | function New-CloudEvent { 10 | <# 11 | .SYNOPSIS 12 | This function creates a new cloud event. 13 | 14 | 15 | .DESCRIPTION 16 | This function creates a new cloud event object with the provided parameters. 17 | The result cloud event object has no data. Use Add-CloudEvent* functions to 18 | add data to the cloud event object. 19 | 20 | .PARAMETER Type 21 | Specifies the 'type' attribute of the cloud event. 22 | 23 | .PARAMETER Source 24 | Specifies the 'source' attribute of the cloud event. 25 | 26 | .PARAMETER Id 27 | Specifies the 'id' attribute of the cloud event. 28 | 29 | .PARAMETER Time 30 | Specifies the 'time' attribute of the cloud event. 31 | 32 | .EXAMPLE 33 | New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date) 34 | 35 | Creates a cloud event with Type, Source, Id, and Time 36 | #> 37 | 38 | [CmdletBinding()] 39 | param( 40 | [Parameter(Mandatory = $true)] 41 | [ValidateNotNullOrEmpty()] 42 | [string] 43 | $Type, 44 | 45 | [Parameter(Mandatory = $true)] 46 | [ValidateNotNullOrEmpty()] 47 | [System.Uri] 48 | $Source, 49 | 50 | [Parameter(Mandatory = $true)] 51 | [ValidateNotNullOrEmpty()] 52 | [string] 53 | $Id, 54 | 55 | [Parameter(Mandatory = $false)] 56 | [ValidateNotNullOrEmpty()] 57 | [DateTime] 58 | $Time 59 | ) 60 | 61 | PROCESS { 62 | $cloudEvent = New-Object ` 63 | -TypeName 'CloudNative.CloudEvents.CloudEvent' ` 64 | -ArgumentList @( 65 | $Type, 66 | $Source, 67 | $Id, 68 | $Time, 69 | @()) 70 | 71 | Write-Output $cloudEvent 72 | } 73 | } 74 | 75 | #region Set Data Functions 76 | function Set-CloudEventData { 77 | <# 78 | .SYNOPSIS 79 | This function sets data in a cloud event. 80 | 81 | .DESCRIPTION 82 | This function sets data in a cloud event object with the provided parameters. 83 | 84 | .PARAMETER CloudEvent 85 | Specifies the cloud event object that receives the data. 86 | 87 | .PARAMETER Data 88 | Specifies the data object for the cloud event 'data' attribute. 89 | 90 | .PARAMETER DataContentType 91 | Specifies the 'datacontenttype' attribute of the cloud event. 92 | 93 | 94 | .EXAMPLE 95 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date) 96 | $cloudEvent | Set-CloudEventData -Data '' -DataContentType 'application/xml' 97 | 98 | Sets xml data to the cloud event 99 | #> 100 | 101 | [CmdletBinding()] 102 | param( 103 | [Parameter(Mandatory = $true, 104 | ValueFromPipeline = $true)] 105 | [ValidateNotNullOrEmpty()] 106 | [CloudNative.CloudEvents.CloudEvent] 107 | $CloudEvent, 108 | 109 | [Parameter(Mandatory = $true, 110 | ValueFromPipeline = $false)] 111 | [ValidateNotNullOrEmpty()] 112 | [object] 113 | $Data, 114 | 115 | # CloudEvent 'datacontenttype' attribute. Content type of the 'data' attribute value. 116 | # This attribute enables the data attribute to carry any type of content, whereby 117 | # format and encoding might differ from that of the chosen event format. 118 | [Parameter(Mandatory = $false, 119 | ValueFromPipeline = $false)] 120 | [string] 121 | $DataContentType) 122 | 123 | PROCESS { 124 | 125 | # https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype 126 | $contentType = New-Object ` 127 | -TypeName 'System.Net.Mime.ContentType' ` 128 | -ArgumentList ($DataContentType) 129 | 130 | $cloudEvent.Data = $Data 131 | $cloudEvent.DataContentType = $dataContentType 132 | 133 | Write-Output $CloudEvent 134 | } 135 | 136 | } 137 | 138 | function Set-CloudEventJsonData { 139 | <# 140 | .SYNOPSIS 141 | This function sets JSON format data in a cloud event. 142 | 143 | .DESCRIPTION 144 | This function converts a PowerShell hashtable to JSON format data and sets it in a cloud event. 145 | 146 | .PARAMETER CloudEvent 147 | Specifies the cloud event object that receives the data. 148 | 149 | .PARAMETER Data 150 | Specifies the PowerShell hashtable object that is set as JSON on the cloud event 'data' attribute. 151 | The 'datacontenttype' attribute is set to 'application/json' 152 | 153 | .PARAMETER Depth 154 | The maximum depth of the input hashtable specified on the `Data` parameter that will be converted to JSON. 155 | This parameter is passed on the `-Depth` parameter of the `ConvertTo-Json` cmdlet. 156 | The default value is 3 157 | 158 | 159 | .EXAMPLE 160 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date) 161 | $cloudEvent | Set-CloudEventJsonData -Data @{ 'key1' = 'value1'; 'key2' = 'value2'; } 162 | 163 | Sets JSON data to the cloud event 164 | #> 165 | 166 | [CmdletBinding()] 167 | param( 168 | [Parameter(Mandatory = $true, 169 | ValueFromPipeline = $true)] 170 | [ValidateNotNullOrEmpty()] 171 | [CloudNative.CloudEvents.CloudEvent] 172 | $CloudEvent, 173 | 174 | [Parameter(Mandatory = $true, 175 | ValueFromPipeline = $false)] 176 | [ValidateNotNull()] 177 | [Hashtable] 178 | $Data, 179 | 180 | [Parameter(Mandatory = $false, 181 | ValueFromPipeline = $false)] 182 | [int] 183 | $Depth = 3) 184 | 185 | PROCESS { 186 | 187 | # DataContentType is set to 'application/json' 188 | # https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype 189 | $dataContentType = New-Object ` 190 | -TypeName 'System.Net.Mime.ContentType' ` 191 | -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Json) 192 | 193 | $cloudEvent.DataContentType = $dataContentType 194 | $cloudEvent.Data = ConvertTo-Json -InputObject $Data -Depth $Depth 195 | 196 | Write-Output $CloudEvent 197 | } 198 | 199 | } 200 | 201 | function Set-CloudEventXmlData { 202 | <# 203 | .SYNOPSIS 204 | This function sets XML format data in a cloud event. 205 | 206 | .DESCRIPTION 207 | This function converts a PowerShell hashtable to XML format data and sets it in a cloud event. 208 | 209 | .PARAMETER CloudEvent 210 | Specifies the cloud event object that receives the data. 211 | 212 | .PARAMETER Data 213 | Specifies the PowerShell hashtable object that is set as XML on the cloud event 'data' attribute. 214 | The 'datacontenttype' attribute is set to 'application/xml' 215 | 216 | .PARAMETER AttributesKeysInElementAttributes 217 | Specifies how to format the XML. If specified and the input Data hashtable has pairs of 'Attributes', 'Value' keys 218 | creates XML element with attributes, otherwise each key is formatted as XML element. 219 | If true 220 | @{'root' = @{'Attributes' = @{'att1' = 'true'}; 'Value' = 'val-1'}} would be 'val-1' 221 | Otherwise 222 | @{'root' = @{'Attributes' = @{'att1' = 'true'}; 'Value' = 'val-1'}} would be 'trueval-1' 223 | 224 | 225 | .EXAMPLE 226 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date) 227 | $cloudEvent | Set-CloudEventXmlData -Data @{ 'key1' = 'value1'; 'key2' = 'value2'; } -AttributesKeysInElementAttributes $true 228 | 229 | Sets XML data in the cloud event 230 | #> 231 | 232 | [CmdletBinding()] 233 | param( 234 | [Parameter(Mandatory = $true, 235 | ValueFromPipeline = $true)] 236 | [ValidateNotNullOrEmpty()] 237 | [CloudNative.CloudEvents.CloudEvent] 238 | $CloudEvent, 239 | 240 | [Parameter(Mandatory = $true, 241 | ValueFromPipeline = $false)] 242 | [ValidateNotNull()] 243 | [Hashtable] 244 | $Data, 245 | 246 | [Parameter(Mandatory = $true)] 247 | [bool] 248 | $AttributesKeysInElementAttributes) 249 | 250 | PROCESS { 251 | 252 | # DataContentType is set to 'application/xml' 253 | $dataContentType = New-Object ` 254 | -TypeName 'System.Net.Mime.ContentType' ` 255 | -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Xml) 256 | 257 | $cloudEvent.DataContentType = $dataContentType 258 | $cloudEvent.Data = ConvertTo-CEDataXml -InputObject $Data -AttributesKeysInElementAttributes $AttributesKeysInElementAttributes 259 | 260 | Write-Output $CloudEvent 261 | } 262 | 263 | } 264 | #endregion Set Data Functions 265 | 266 | #region Read Data Functions 267 | function Read-CloudEventData { 268 | <# 269 | .SYNOPSIS 270 | This function gets the data from a cloud event. 271 | 272 | .DESCRIPTION 273 | This function gets the data as-is from a cloud event. It is equiualent of accessing the Data property of a CloudEvent object 274 | 275 | .PARAMETER CloudEvent 276 | Specifies the cloud event object to get data from. 277 | 278 | .EXAMPLE 279 | $cloudEvent = ConvertFrom-HttpMessage -Headers $httpResponse.Headers -Body $httpResponse.Content 280 | $cloudEvent | Read-CloudEventData 281 | 282 | Reads data from a cloud event received on the http response 283 | #> 284 | 285 | [CmdletBinding()] 286 | param( 287 | [Parameter(Mandatory = $true, 288 | ValueFromPipeline = $true)] 289 | [ValidateNotNullOrEmpty()] 290 | [CloudNative.CloudEvents.CloudEvent] 291 | $CloudEvent 292 | ) 293 | 294 | PROCESS { 295 | Write-Output $CloudEvent.Data 296 | } 297 | 298 | } 299 | 300 | function Read-CloudEventJsonData { 301 | <# 302 | .SYNOPSIS 303 | This function gets JSON fromat data from a cloud event as a PowerShell hashtable. 304 | 305 | .DESCRIPTION 306 | This function gets the data from a cloud event and converts it to a PowerShell hashtable. 307 | If the cloud event datacontenttype is not 'application/json' nothing is returned. 308 | 309 | .PARAMETER CloudEvent 310 | Specifies the cloud event object to get data from. 311 | 312 | .PARAMETER Depth 313 | Specifies how many levels of contained objects are included in the JSON representation. The default value is 3. 314 | 315 | .EXAMPLE 316 | $cloudEvent = ConvertFrom-HttpMessage -Headers $httpResponse.Headers -Body $httpResponse.Content 317 | $hashtable = $cloudEvent | Read-CloudEventJsonData 318 | 319 | Reads JSON data as a hashtable from a cloud event received on the http response 320 | #> 321 | 322 | 323 | <# 324 | .DESCRIPTION 325 | Returns PowerShell hashtable that represents the CloudEvent Json Data 326 | if the data content type is 'application/json', otherwise otherwise non-terminating error and no result 327 | #> 328 | 329 | [CmdletBinding()] 330 | param( 331 | [Parameter(Mandatory = $true, 332 | ValueFromPipeline = $true)] 333 | [ValidateNotNullOrEmpty()] 334 | [CloudNative.CloudEvents.CloudEvent] 335 | $CloudEvent, 336 | 337 | [Parameter(Mandatory = $false, 338 | ValueFromPipeline = $false)] 339 | [int] 340 | $Depth = 3 341 | ) 342 | 343 | PROCESS { 344 | 345 | # DataContentType is expected to be 'application/json' 346 | # https://github.com/cloudevents/spec/blob/master/spec.md#datacontenttype 347 | $dataContentType = New-Object ` 348 | -TypeName 'System.Net.Mime.ContentType' ` 349 | -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Json) 350 | 351 | if ($CloudEvent.DataContentType -eq $dataContentType -or ` 352 | ($CloudEvent.DataContentType -eq $null -and # Datacontent Type is Optional, if it is not specified we assume it is JSON as per https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#datacontenttype 353 | $cloudEvent.Data -is [Newtonsoft.Json.Linq.JObject])) { 354 | 355 | $data = $cloudEvent.Data 356 | 357 | if ($cloudEvent.Data -is [byte[]]) { 358 | $data = [System.Text.Encoding]::UTF8.GetString($data) 359 | } 360 | 361 | $result = $data.ToString() | ConvertFrom-Json -AsHashtable -Depth $Depth 362 | 363 | Write-Output $result 364 | } 365 | else { 366 | Write-Error "Cloud Event '$($cloudEvent.Id)' has no json data" 367 | } 368 | } 369 | 370 | } 371 | 372 | function Read-CloudEventXmlData { 373 | <# 374 | .SYNOPSIS 375 | This function gets XML fromat data from a cloud event as a PowerShell hashtable. 376 | 377 | .DESCRIPTION 378 | This function gets the data from a cloud event and converts it to a PowerShell hashtable. 379 | If the cloud event datacontenttype is not 'application/xml' nothing is returned. 380 | 381 | .PARAMETER CloudEvent 382 | Specifies the cloud event object to get data from. 383 | 384 | .PARAMETER ConvertMode 385 | Specifies the how to convert the xml data to a hashtable 386 | 'SkipAttributes' - Skips attributes of the XML elements. XmlElement is represented as a 387 | Key-Value pair where key is the xml element name, and the value is the xml element inner text 388 | 389 | Example: 390 | "value1" is converted to 391 | @{'key' = 'value-1'} 392 | 393 | 'AlwaysAttrValue' - Each element is represented as a hashtable with two keys 394 | 'Attributes' - key-value pair of the cml element attributes if any, otherwise null 395 | 'Value' - string value represinting the xml element inner text 396 | 397 | Example: 398 | "value1value2" is converted to 399 | @{ 400 | 'key1' = @{ 401 | 'Attributes' = @{ 402 | 'att' = 'true' 403 | } 404 | 'Value' = 'value1' 405 | } 406 | 'key2' = @{ 407 | 'Attributes' = $null 408 | 'Value' = 'value2' 409 | } 410 | 411 | } 412 | 'AttrValueWhenAttributes' - Uses 'SkipAttributes' for xml elements without attributes and 413 | 'AlwaysAttrValue' for xml elements with attributes 414 | Example: 415 | "value1value2" is converted to 416 | @{ 417 | 'key1' = @{ 418 | 'Attributes' = @{ 419 | 'att' = 'true' 420 | } 421 | 'Value' = 'value1' 422 | } 423 | 'key2' = 'value2' 424 | } 425 | 426 | .EXAMPLE 427 | $cloudEvent = ConvertFrom-HttpMessage -Headers $httpResponse.Headers -Body $httpResponse.Content 428 | $hashtable = $cloudEvent | Read-CloudEventXmlData -ConvertMode AttrValueWhenAttributes 429 | 430 | Reads XML data as a hashtable from a cloud event received on the http response 431 | #> 432 | 433 | 434 | <# 435 | .DESCRIPTION 436 | Returns PowerShell hashtable that represents the CloudEvent Xml Data 437 | if the data content type is 'application/xml', otherwise non-terminating error and no result 438 | #> 439 | 440 | [CmdletBinding()] 441 | param( 442 | [Parameter(Mandatory = $true, 443 | ValueFromPipeline = $true)] 444 | [ValidateNotNullOrEmpty()] 445 | [CloudNative.CloudEvents.CloudEvent] 446 | $CloudEvent, 447 | 448 | [Parameter(Mandatory = $true)] 449 | [ValidateSet("SkipAttributes", "AlwaysAttrValue", "AttrValueWhenAttributes")] 450 | [string] 451 | $ConvertMode 452 | ) 453 | 454 | PROCESS { 455 | 456 | # DataContentType is expected to be 'application/xml' 457 | $dataContentType = New-Object ` 458 | -TypeName 'System.Net.Mime.ContentType' ` 459 | -ArgumentList ([System.Net.Mime.MediaTypeNames+Application]::Xml) 460 | 461 | if ($CloudEvent.DataContentType -eq $dataContentType) { 462 | 463 | $data = $cloudEvent.Data 464 | 465 | if ($cloudEvent.Data -is [byte[]]) { 466 | $data = [System.Text.Encoding]::UTF8.GetString($data) 467 | } 468 | 469 | $result = $data.ToString() | ConvertFrom-CEDataXml -ConvertMode $ConvertMode 470 | 471 | Write-Output $result 472 | } 473 | else { 474 | Write-Error "Cloud Event '$($cloudEvent.Id)' has no xml data" 475 | } 476 | } 477 | 478 | } 479 | #endregion Read Data Functions 480 | 481 | #region HTTP Protocol Binding Conversion Functions 482 | function ConvertTo-HttpMessage { 483 | <# 484 | .SYNOPSIS 485 | This function converts a cloud event object to a Http Message. 486 | 487 | .DESCRIPTION 488 | This function converts a cloud event object to a PSObject with Headers and Body properties. 489 | The 'Headers' propery is a hashtable that can pe provided to the 'Headers' parameter of the Inveok-WebRequest cmdlet. 490 | The 'Body' propery is byte[] that can pe provided to the 'Body' parameter of the Inveok-WebRequest cmdlet. 491 | 492 | .PARAMETER CloudEvent 493 | Specifies the cloud event object to convert. 494 | 495 | .PARAMETER ContentMode 496 | Specifies the cloud event content mode. Structured and Binary content modes are supporterd. 497 | 498 | .EXAMPLE 499 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date) 500 | $cloudEvent | Add-CloudEventJsonData -Data @{ 'key1' = 'value1'; 'key2' = 'value2'; } 501 | 502 | $cloudEvent | ConvertTo-HttpMessage -ContentMode Binary 503 | 504 | Converts a cloud event object to Headers and Body formatted in Binary content mode. 505 | 506 | .EXAMPLE 507 | $cloudEvent = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date) 508 | $cloudEvent | Add-CloudEventJsonData -Data @{ 'key1' = 'value1'; 'key2' = 'value2'; } 509 | 510 | $cloudEvent | ConvertTo-HttpMessage -ContentMode Structured 511 | 512 | Converts a cloud event object to Headers and Body formatted in Structured content mode. 513 | 514 | .EXAMPLE 515 | $httpMessage = New-CloudEvent -Type 'com.example.object.deleted.v2' -Source 'mailto:cncf-wg-serverless@lists.cncf.io' -Id '6e8bc430-9c3a-11d9-9669-0800200c9a66' -Time (Get-Date) | ` 516 | Add-CloudEventJsonData -Data @{ 'key1' = 'value1'; 'key2' = 'value2'; } | ` 517 | ConvertTo-HttpMessage -ContentMode Structured 518 | 519 | Invoke-WebRequest -Uri 'http://localhost:52673/' -Headers $httpMessage.Headers -Body $httpMessage.Body 520 | 521 | Sends a cloud event http requests to a server 522 | #> 523 | 524 | [CmdletBinding()] 525 | param( 526 | [Parameter( 527 | Mandatory = $true, 528 | ValueFromPipeline = $true, 529 | ValueFromPipelineByPropertyName = $false)] 530 | [ValidateNotNull()] 531 | [CloudNative.CloudEvents.CloudEvent] 532 | $CloudEvent, 533 | 534 | [Parameter( 535 | Mandatory = $true, 536 | ValueFromPipeline = $false, 537 | ValueFromPipelineByPropertyName = $false)] 538 | [CloudNative.CloudEvents.ContentMode] 539 | $ContentMode) 540 | 541 | PROCESS { 542 | # Output Object 543 | $result = New-Object -TypeName PSCustomObject 544 | 545 | $cloudEventFormatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter' 546 | 547 | $HttpHeaderPrefix = "ce-"; 548 | $SpecVersionHttpHeader1 = $HttpHeaderPrefix + "cloudEventsVersion"; 549 | $SpecVersionHttpHeader2 = $HttpHeaderPrefix + "specversion"; 550 | 551 | $headers = @{} 552 | 553 | # Build HTTP headers 554 | foreach ($attribute in $cloudEvent.GetAttributes()) { 555 | if (-not $attribute.Key.Equals([CloudNative.CloudEvents.CloudEventAttributes]::DataAttributeName($cloudEvent.SpecVersion)) -and ` 556 | -not $attribute.Key.Equals([CloudNative.CloudEvents.CloudEventAttributes]::DataContentTypeAttributeName($cloudEvent.SpecVersion))) { 557 | if ($attribute.Value -is [string]) { 558 | $headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToString()) 559 | } 560 | elseif ($attribute.Value -is [DateTime]) { 561 | $headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ss.fffZ')) 562 | } 563 | elseif ($attribute.Value -is [Uri] -or $attribute.Value -is [int]) { 564 | $headers.Add(($HttpHeaderPrefix + $attribute.Key), $attribute.Value.ToString()) 565 | } 566 | else { 567 | $headers.Add(($HttpHeaderPrefix + $attribute.Key), 568 | [System.Text.Encoding]::UTF8.GetString($cloudEventFormatter.EncodeAttribute($cloudEvent.SpecVersion, $attribute.Key, 569 | $attribute.Value, 570 | $cloudEvent.Extensions.Values))); 571 | } 572 | } 573 | } 574 | 575 | # Add Headers property to the output object 576 | $result | Add-Member -MemberType NoteProperty -Name 'Headers' -Value $headers 577 | 578 | # Process Structured Mode 579 | # Structured Mode supports non-batching JSON format only 580 | # https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#14-event-formats 581 | if ($ContentMode -eq [CloudNative.CloudEvents.ContentMode]::Structured) { 582 | # Format Body as byte[] 583 | $contentType = $null 584 | 585 | # CloudEventFormatter is instance of 'CloudNative.CloudEvents.JsonEventFormatter' from the 586 | # .NET CloudEvents SDK for the purpose of fomatting structured mode 587 | $buffer = $cloudEventFormatter.EncodeStructuredEvent($cloudEvent, [ref] $contentType) 588 | $result | Add-Member -MemberType NoteProperty -Name 'Body' -Value $buffer 589 | $result.Headers.Add('Content-Type', $contentType) 590 | } 591 | 592 | # Process Binary Mode 593 | if ($ContentMode -eq [CloudNative.CloudEvents.ContentMode]::Binary) { 594 | $bodyData = $null 595 | 596 | if ($cloudEvent.DataContentType -ne $null) { 597 | $result.Headers.Add('Content-Type', $cloudEvent.DataContentType) 598 | } 599 | 600 | if ($cloudEvent.Data -is [byte[]]) { 601 | $bodyData = $cloudEvent.Data 602 | } 603 | elseif ($cloudEvent.Data -is [string]) { 604 | $bodyData = [System.Text.Encoding]::UTF8.GetBytes($cloudEvent.Data.ToString()) 605 | } 606 | elseif ($cloudEvent.Data -is [IO.Stream]) { 607 | $buffer = New-Object 'byte[]' -ArgumentList 1024 608 | 609 | $ms = New-Object 'IO.MemoryStream' 610 | 611 | try { 612 | $read = 0 613 | while (($read = $cloudEvent.Data.Read($buffer, 0, 1024)) -gt 0) { 614 | $ms.Write($buffer, 0, $read); 615 | } 616 | $bodyData = $ms.ToArray() 617 | } 618 | finally { 619 | $ms.Dispose() 620 | } 621 | 622 | } 623 | else { 624 | $bodyData = $cloudEventFormatter.EncodeAttribute($cloudEvent.SpecVersion, 625 | [CloudNative.CloudEvents.CloudEventAttributes]::DataAttributeName($cloudEvent.SpecVersion), 626 | $cloudEvent.Data, $cloudEvent.Extensions.Values) 627 | } 628 | 629 | # Add Body property to the output object 630 | $result | Add-Member -MemberType NoteProperty -Name 'Body' -Value $bodyData 631 | } 632 | 633 | Write-Output $result 634 | } 635 | } 636 | 637 | function ConvertFrom-HttpMessage { 638 | <# 639 | .SYNOPSIS 640 | This function converts a Http Message to a cloud event object 641 | 642 | .DESCRIPTION 643 | This function converts a Http Message (Headers and Body) to a cloud event object. 644 | Result of Invoke-WebRequest that contains a cloud event can be passed as input to this 645 | function binding the the 'Headers' and 'Content' properties to the 'Headers' and 'Body' paramters. 646 | 647 | .PARAMETER Headers 648 | Specifies the Http Headers as a PowerShell hashtable. 649 | 650 | .PARAMETER Body 651 | Specifies the Http body as string or byte[]. 652 | 653 | .EXAMPLE 654 | $httpReponse = Invoke-WebRequest -Uri 'http://localhost:52673/' -Headers $httpMessage.Headers -Body $httpMessage.Body 655 | $cloudEvent = ConvertFrom-HttpMessage -Headers $httpResponse.Headers -Body $httpResponse.Content 656 | 657 | Converts a http response to a cloud event object 658 | #> 659 | 660 | [CmdletBinding()] 661 | param( 662 | [Parameter( 663 | Mandatory = $true, 664 | ValueFromPipeline = $false, 665 | ValueFromPipelineByPropertyName = $false)] 666 | [ValidateNotNull()] 667 | [hashtable] 668 | $Headers, 669 | 670 | [Parameter( 671 | Mandatory = $false, 672 | ValueFromPipeline = $false, 673 | ValueFromPipelineByPropertyName = $false)] 674 | [ValidateNotNull()] 675 | $Body) 676 | 677 | PROCESS { 678 | $HttpHeaderPrefix = "ce-"; 679 | $SpecVersionHttpHeader = $HttpHeaderPrefix + "specversion"; 680 | 681 | $result = $null 682 | 683 | # Always Convert Body to byte[] 684 | # Conversion works with byte[] while 685 | # body can be string in HTTP responses 686 | # for text content type 687 | if ($Body -is [string]) { 688 | $Body = [System.Text.Encoding]::UTF8.GetBytes($Body) 689 | } 690 | 691 | if ($null -ne $Headers['Content-Type']) { 692 | $ContentType = $Headers['Content-Type'] 693 | if ($ContentType -is [array]) { 694 | # Get the first content-type value 695 | $ContentType = $ContentType[0] 696 | } 697 | 698 | if ($ContentType.StartsWith([CloudNative.CloudEvents.CloudEvent]::MediaType, 699 | [StringComparison]::InvariantCultureIgnoreCase)) { 700 | 701 | # Handle Structured Mode 702 | $ctParts = $ContentType.Split(';') 703 | if ($ctParts[0].Trim().StartsWith(([CloudNative.CloudEvents.CloudEvent]::MediaType) + ([CloudNative.CloudEvents.JsonEventFormatter]::MediaTypeSuffix), 704 | [StringComparison]::InvariantCultureIgnoreCase)) { 705 | 706 | # Structured Mode supports non-batching JSON format only 707 | # https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#14-event-formats 708 | 709 | # .NET SDK 'CloudNative.CloudEvents.JsonEventFormatter' type is used 710 | # to decode the Structured Mode CloudEvents 711 | 712 | $json = [System.Text.Encoding]::UTF8.GetString($Body) 713 | $jObject = [Newtonsoft.Json.Linq.JObject]::Parse($json) 714 | $formatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter' 715 | $result = $formatter.DecodeJObject($jObject, $null) 716 | 717 | $result.Data = $result.Data 718 | } 719 | else { 720 | # Throw error for unsupported encoding 721 | throw "Unsupported CloudEvents encoding" 722 | } 723 | } 724 | else { 725 | # Handle Binary Mode 726 | $version = $null 727 | if ($Headers.Contains($SpecVersionHttpHeader) -and ` 728 | $null -ne $Headers[$SpecVersionHttpHeader] -and ` 729 | ($Headers[$SpecVersionHttpHeader] | Select-Object -First 1).StartsWith('1.0')) { 730 | # We do support the 1.0 cloud event version 731 | $version = [CloudNative.CloudEvents.CloudEventsSpecVersion]::V1_0 732 | } 733 | 734 | if ($null -ne $version) { 735 | # SpecVersion is REQUIRED attribute, it it is not specified this is not a CloudEvent 736 | # https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#specversion 737 | $cloudEvent = New-Object ` 738 | -TypeName 'CloudNative.CloudEvents.CloudEvent' ` 739 | -ArgumentList @($version, $null); 740 | 741 | $attributes = $cloudEvent.GetAttributes(); 742 | 743 | # Get attributes from HTTP Headers 744 | foreach ($httpHeader in $Headers.GetEnumerator()) { 745 | if ($httpHeader.Key.Equals($SpecVersionHttpHeader, [StringComparison]::InvariantCultureIgnoreCase)) { 746 | continue 747 | } 748 | 749 | if ($httpHeader.Key.StartsWith($HttpHeaderPrefix, [StringComparison]::InvariantCultureIgnoreCase)) { 750 | $headerValue = $httpHeader.Value 751 | if ($headerValue -is [array]) { 752 | # Get the first object 753 | $headerValue = $headerValue[0] 754 | } 755 | $name = $httpHeader.Key.Substring(3); 756 | 757 | # Abolished structures in headers in 1.0 758 | if ( $null -ne $headerValue -and ` 759 | $headerValue.StartsWith('"') -and ` 760 | $headerValue.EndsWith('"') -or ` 761 | $headerValue.StartsWith("'") -and $headerValue.EndsWith("'") -or ` 762 | $headerValue.StartsWith("{") -and $headerValue.EndsWith("}") -or ` 763 | $headerValue.StartsWith("[") -and $headerValue.EndsWith("]")) { 764 | 765 | $jsonFormatter = New-Object 'CloudNative.CloudEvents.JsonEventFormatter' 766 | 767 | $attributes[$name] = $jsonFormatter.DecodeAttribute($version, $name, 768 | [System.Text.Encoding]::UTF8.GetBytes($headerValue), $null); 769 | } 770 | else { 771 | $attributes[$name] = $headerValue 772 | } 773 | } 774 | } 775 | 776 | # Verify parsed attributes from HTTP Headers 777 | if ($null -ne $attributes['datacontenttype']) { 778 | # ce-datatype is prohibitted by the protocol -> throw error 779 | # https://github.com/cloudevents/spec/blob/v1.0.1/http-protocol-binding.md#311-http-content-type 780 | throw "'ce-datacontenttype' HTTP header is prohibited for Binary ContentMode CloudEvent" 781 | } 782 | 783 | if ($Headers['Content-Type'] -is [string]) { 784 | $cloudEvent.DataContentType = New-Object 'System.Net.Mime.ContentType' -ArgumentList @($Headers['Content-Type']) 785 | } elseif ($Headers['Content-Type'][0] -is [string]) { 786 | $cloudEvent.DataContentType = New-Object 'System.Net.Mime.ContentType' -ArgumentList @($Headers['Content-Type'][0]) 787 | } 788 | 789 | # Id, Type, and Source are reuiqred to be non-empty strings otherwise consider this is not a CloudEvent 790 | # https://github.com/cloudevents/spec/blob/v1.0.1/spec.md#required-attributes 791 | if ( -not [string]::IsNullOrEmpty($cloudEvent.Id) -and ` 792 | -not [string]::IsNullOrEmpty($cloudEvent.Source) -and ` 793 | -not [string]::IsNullOrEmpty($cloudEvent.Type)) { 794 | # Get Data from HTTP Body 795 | $cloudEvent.Data = $Body 796 | 797 | $result = $cloudEvent 798 | } 799 | } 800 | } 801 | } 802 | 803 | Write-Output $result 804 | } 805 | } 806 | #endregion HTTP Protocol Binding Conversion Functions --------------------------------------------------------------------------------