.github/workflows/static.yml000064400000002527146412130150012156 0ustar00name: Static Analysis on: push: branches: [ main ] pull_request: jobs: codesniffer: name: PHP CodeSniffer runs-on: ubuntu-22.04 steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '7.4' tools: composer:v2 coverage: none env: update: true - name: Install Dependencies uses: nick-invision/retry@v2 with: timeout_minutes: 5 max_attempts: 5 command: composer update --no-interaction --no-progress - name: Execute PHP CodeSniffer run: vendor/bin/phpcs -n -q --standard=PSR12 ./src ./tests psalm: name: Psalm runs-on: ubuntu-22.04 steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '7.4' tools: composer:v2 coverage: none env: update: true - name: Install Dependencies uses: nick-invision/retry@v2 with: timeout_minutes: 5 max_attempts: 5 command: composer update --no-interaction --no-progress - name: Execute Psalm run: vendor/bin/psalm.phar --no-progress --output-format=github .github/workflows/tests.yml000064400000001562146412130150012027 0ustar00name: Tests on: push: branches: [ main ] pull_request: jobs: tests: name: PHP ${{ matrix.php }} runs-on: ubuntu-22.04 strategy: matrix: php: ['7.4', '8.0', '8.1', '8.2', '8.3'] steps: - name: Checkout Code uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} tools: composer:v2 coverage: none env: update: true - name: Setup Problem Matchers run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Install Dependencies uses: nick-invision/retry@v2 with: timeout_minutes: 5 max_attempts: 5 command: composer update --no-interaction --no-progress - name: Execute PHPUnit run: vendor/bin/phpunit .gitignore000064400000000145146412130150006531 0ustar00.phpunit.cache .phpunit.result.cache composer.lock phpcs.xml phpunit.xml psalm.xml coverage vendor CONTRIBUTING.md000064400000012704146412130150006776 0ustar00# Contributing to CloudEvents' PHP SDK :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: We welcome contributions from the community! Please take some time to become acquainted with the process before submitting a pull request. There are just a few things to keep in mind. # Pull Requests Typically, a pull request should relate to an existing issue. If you have found a bug, want to add an improvement, or suggest an API change, please create an issue before proceeding with a pull request. For very minor changes such as typos in the documentation this isn't really necessary. ## Pull Request Guidelines Here you will find step by step guidance for creating, submitting and updating a pull request in this repository. We hope it will help you have an easy time managing your work and a positive, satisfying experience when contributing your code. Thanks for getting involved! :rocket: * [Getting Started](#getting-started) * [Branches](#branches) * [Commit Messages](#commit-messages) * [Staying current with main](#staying-current-with-main) * [Submitting and Updating a Pull Request](#submitting-and-updating-a-pull-request) * [Congratulations!](#congratulations) ## Getting Started When creating a pull request, first fork this repository and clone it to your local development environment. Then add this repository as the upstream. ```console git clone https://github.com/mygithuborg/sdk-php.git cd sdk-php git remote add upstream https://github.com/cloudevents/sdk-php.git ``` ## Branches The first thing you'll need to do is create a branch for your work. If you are submitting a pull request that fixes or relates to an existing GitHub issue, you can use the issue number in your branch name to keep things organized. ```console git fetch upstream git reset --hard upstream/main git checkout FETCH_HEAD git checkout -b 48-fix-http-agent-error ``` ## Commit Messages We prefer that contributors follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/#summary). The first line of your commit should be prefixed with a type, be a single sentence with no period, and succinctly indicate what this commit changes. All commit message lines should be kept to fewer than 80 characters if possible. An example of a good commit message. ```log docs: remove 0.1, 0.2 spec support from README ``` ### Signing your commits Each commit must be signed. Use the `--signoff` flag for your commits. ```console git commit --signoff ``` This will add a line to every git commit message: Signed-off-by: Joe Smith Use your real name (sorry, no pseudonyms or anonymous contributions.) The sign-off is a signature line at the end of your commit message. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as open-source code. See [developercertificate.org](http://developercertificate.org/) for the full text of the certification. Be sure to have your `user.name` and `user.email` set in your git config. If your git config information is set properly then viewing the `git log` information for your commit will look something like this: ``` Author: Joe Smith Date: Thu Feb 2 11:41:15 2018 -0800 Update README Signed-off-by: Joe Smith ``` Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will be rejected by the automated DCO check. ## Staying Current with `main` As you are working on your branch, changes may happen on `main`. Before submitting your pull request, be sure that your branch has been updated with the latest commits. ```console git fetch upstream git rebase upstream/main ``` This may cause conflicts if the files you are changing on your branch are also changed on main. Error messages from `git` will indicate if conflicts exist and what files need attention. Resolve the conflicts in each file, then continue with the rebase with `git rebase --continue`. If you've already pushed some changes to your `origin` fork, you'll need to force push these changes. ```console git push -f origin 48-fix-http-agent-error ``` ## Submitting and Updating Your Pull Request Before submitting a pull request, you should make sure that all of the tests successfully pass. Once you have sent your pull request, `main` may continue to evolve before your pull request has landed. If there are any commits on `main` that conflict with your changes, you may need to update your branch with these changes before the pull request can land. Resolve conflicts the same way as before. ```console git fetch upstream git rebase upstream/main # fix any potential conflicts git push -f origin 48-fix-http-agent-error ``` This will cause the pull request to be updated with your changes, and CI will rerun. A maintainer may ask you to make changes to your pull request. Sometimes these changes are minor and shouldn't appear in the commit log. For example, you may have a typo in one of your code comments that should be fixed before merge. You can prevent this from adding noise to the commit log with an interactive rebase. See the [git documentation](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History) for details. ```console git commit -m "fixup: fix typo" git rebase -i upstream/main # follow git instructions ``` Once you have rebased your commits, you can force push to your fork as before. ## Congratulations! Congratulations! You've done it! We really appreciate the time and energy you've given to the project. Thank you. LICENSE000064400000026135146412130150005555 0ustar00 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. MAINTAINERS.md000064400000000146146412130150006636 0ustar00# Maintainers Current active maintainers of this SDK: - [John Laswell](https://github.com/jlaswell) README.md000064400000010557146412130150006030 0ustar00# PHP SDK for [CloudEvents](https://github.com/cloudevents/spec) ## Status This SDK currently supports the following versions of CloudEvents: - [v1.0](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md) ## Installation Install the SDK using [Composer](https://getcomposer.org/): ```sh $ composer require cloudevents/sdk-php ``` ## Create a CloudEvent ```php use CloudEvents\V1\CloudEvent; use CloudEvents\V1\CloudEventImmutable; // Immutable CloudEvent $immutableEvent = new CloudEventImmutable( '1n6bFxDMHZFChlI4TVI9tdzphB9', '/examples/php-sdk', 'com.example.type', ['example' => 'first-event'], 'application/json' ); // Mutable CloudEvent $mutableEvent = new CloudEvent( '1n6bFxDMHZFChlI4TVI9tdzphB9', '/examples/php-sdk', 'com.example.type', ['example' => 'first-event'], 'application/json' ); // Create immutable from mutable or via versa $event = CloudEventImmutable::createFromInterface($mutableEvent); $event = CloudEvent::createFromInterface($immutableEvent); ``` ## Serialize/Deserialize a CloudEvent ```php use CloudEvents\Serializers\JsonDeserializer; use CloudEvents\Serializers\JsonSerializer; // JSON serialization $payload = JsonSerializer::create()->serializeStructured($event); $payload = JsonSerializer::create()->serializeBatch($events); // JSON deserialization $event = JsonDeserializer::create()->deserializeStructured($payload); $events = JsonDeserializer::create()->deserializeBatch($payload); ``` ## Marshal/Unmarshal a CloudEvent ```php use CloudEvents\Http\Marshaller; use CloudEvents\Http\Unmarshaller; // Marshal HTTP request $request = Marshaller::createJsonMarshaller()->marshalStructuredRequest($event); $request = Marshaller::createJsonMarshaller()->marshalBinaryRequest($event); $request = Marshaller::createJsonMarshaller()->marshalBatchRequest($events); // Marshal HTTP response $request = Marshaller::createJsonMarshaller()->marshalStructuredResponse($event); $request = Marshaller::createJsonMarshaller()->marshalBinaryResponse($event); $request = Marshaller::createJsonMarshaller()->marshalBatchResponse($events); // Unmarshal HTTP message $events = Unmarshaller::createJsonUnmarshaller()->unmarshal($message); ``` ## Testing You can use `composer` to build and run test environments when contributing. ``` $ composer run -l scripts: lint Show all current linting errors according to PSR12 lint-fix Show and fix all current linting errors according to PSR12 sa Run the static analyzer tests Run all tests locally tests-build Build containers to test against supported PHP versions tests-docker Run tests within supported PHP version containers ``` ## Community - There are bi-weekly calls immediately following the [Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time) at 9am PT (US Pacific). Which means they will typically start at 10am PT, but if the other call ends early then the SDK call will start early as well. See the [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#) to determine which week will have the call. - Slack: #cloudeventssdk channel under [CNCF's Slack workspace](https://slack.cncf.io/). - Email: https://lists.cncf.io/g/cncf-cloudevents-sdk - Contact for additional information: Denis Makogon (`@denysmakogon` on slack). Each SDK may have its own unique processes, tooling and guidelines, common governance related material can be found in the [CloudEvents `community`](https://github.com/cloudevents/spec#community-and-docs) directory. In particular, in there you will find information concerning how SDK projects are [managed](https://github.com/cloudevents/spec/blob/main/docs/SDK-GOVERNANCE.md), [guidelines](https://github.com/cloudevents/spec/blob/main/docs/SDK-maintainer-guidelines.md) for how PR reviews and approval, and our [Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md) information. If there is a security concern with one of the CloudEvents specifications, or with one of the project's SDKs, please send an email to [cncf-cloudevents-security@lists.cncf.io](mailto:cncf-cloudevents-security@lists.cncf.io). ## Additional SDK Resources - [List of current active maintainers](MAINTAINERS.md) - [How to contribute to the project](CONTRIBUTING.md) - [SDK's License](LICENSE) - [SDK's Release process](RELEASING.md) RELEASING.md000064400000000156146412130150006376 0ustar00To create a new release: - Create a new Github release via the Github UI, making sure to prefix it with `v` composer.json000064400000006554146412130150007275 0ustar00{ "name": "cloudevents/sdk-php", "description": "CloudEvents SDK", "type": "library", "license": "Apache-2.0", "support": { "issues": "https://github.com/cloudevents/sdk-php/issues", "source": "https://github.com/cloudevents/sdk-php" }, "autoload": { "psr-4": { "CloudEvents\\": "src/" } }, "autoload-dev": { "psr-4": { "Tests\\": "tests/" } }, "require": { "php": "^7.4.15 || ^8.0.2", "ext-json": "*", "ext-pcre": "*", "symfony/polyfill-php80": "^1.26" }, "require-dev": { "guzzlehttp/psr7": "^2.4.3", "php-http/discovery": "^1.15.2", "phpunit/phpunit": "^9.6.3 || ^10.0.12", "psalm/phar": "5.7.6", "psr/http-factory": "^1.0.1", "psr/http-message": "^1.0.1", "squizlabs/php_codesniffer": "3.7.2" }, "suggest": { "php-http/discovery": "Required for automatic discovery of HTTP message factories in the HTTP Marshaller.", "psr/http-factory": "Required for use of the HTTP Marshaller.", "psr/http-factory-implementation": "Required for use of the HTTP Marshaller.", "psr/http-message": "Required for use of the HTTP Marshaller and Unmarshaller.", "psr/http-message-implementation": "Required for use of the HTTP Marshaller and Unmarshaller." }, "scripts": { "lint": "./vendor/bin/phpcs -n --standard=PSR12 ./src ./tests", "lint-fix": "./vendor/bin/phpcbf -n --standard=PSR12 ./src ./tests", "tests": "./vendor/bin/phpunit", "sa": "./vendor/bin/psalm.phar", "tests-build": [ "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:7.4-tests -f hack/7.4.Dockerfile hack", "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.0-tests -f hack/8.0.Dockerfile hack", "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.1-tests -f hack/8.1.Dockerfile hack", "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.2-tests -f hack/8.2.Dockerfile hack", "DOCKER_BUILDKIT=1 docker build -t cloudevents/sdk-php:8.3-tests -f hack/8.3.Dockerfile hack" ], "tests-docker": [ "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:7.4-tests --coverage-html=coverage", "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:8.0-tests", "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:8.1-tests", "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:8.2-tests", "docker run -it -v $(pwd):/var/www cloudevents/sdk-php:8.3-tests" ] }, "scripts-descriptions": { "lint": "Show all current linting errors according to PSR12", "lint-fix": "Show and fix all current linting errors according to PSR12", "sa": "Run the static analyzer", "tests": "Run all tests locally", "tests-build": "Build containers to test against supported PHP versions", "tests-docker": "Run tests within supported PHP version containers" }, "config": { "preferred-install": "dist", "sort-packages": true, "allow-plugins": { "php-http/discovery": true } }, "extra": { "branch-alias": { "dev-main": "1.1-dev" } }, "minimum-stability": "dev", "prefer-stable": true } hack/7.4.Dockerfile000064400000002210146412130150007743 0ustar00FROM php:7.4-alpine LABEL org.opencontainers.image.url="https://github.com/cloudevents/sdk-php/tree/main/hack/7.4.Dockerfile" \ org.opencontainers.image.documentation="https://github.com/cloudevents/sdk-php/tree/main/hack/README.md" \ org.opencontainers.image.source="https://github.com/cloudevents/sdk-php" \ org.opencontainers.image.vendor="CloudEvent" \ org.opencontainers.image.title="PHP 7.4" \ org.opencontainers.image.description="PHP 7.4 test environment for cloudevents/sdk-php" COPY --chown=www-data:www-data install-composer /usr/local/bin/install-composer RUN chmod +x /usr/local/bin/install-composer \ && /usr/local/bin/install-composer \ && rm /usr/local/bin/install-composer RUN apk update \ && apk --no-cache upgrade \ && apk add --no-cache bash ca-certificates git libzip-dev \ && apk add --no-cache --virtual build-deps autoconf build-base g++ \ && pecl install pcov \ && docker-php-ext-enable pcov \ && apk del --purge build-deps \ && rm -rf /var/www/html /tmp/pear \ && chown -R www-data:www-data /var/www WORKDIR /var/www ENTRYPOINT ["/var/www/vendor/bin/phpunit"] hack/8.0.Dockerfile000064400000001734146412130150007752 0ustar00FROM php:8.0-alpine LABEL org.opencontainers.image.url="https://github.com/cloudevents/sdk-php/tree/main/hack/8.0.Dockerfile" \ org.opencontainers.image.documentation="https://github.com/cloudevents/sdk-php/tree/main/hack/README.md" \ org.opencontainers.image.source="https://github.com/cloudevents/sdk-php" \ org.opencontainers.image.vendor="CloudEvent" \ org.opencontainers.image.title="PHP 8.0" \ org.opencontainers.image.description="PHP 8.0 test environment for cloudevents/sdk-php" COPY --chown=www-data:www-data install-composer /usr/local/bin/install-composer RUN chmod +x /usr/local/bin/install-composer \ && /usr/local/bin/install-composer \ && rm /usr/local/bin/install-composer RUN apk update \ && apk --no-cache upgrade \ && apk add --no-cache bash ca-certificates git libzip-dev \ && rm -rf /var/www/html /tmp/pear \ && chown -R www-data:www-data /var/www WORKDIR /var/www ENTRYPOINT ["/var/www/vendor/bin/phpunit"] hack/8.1.Dockerfile000064400000001734146412130150007753 0ustar00FROM php:8.1-alpine LABEL org.opencontainers.image.url="https://github.com/cloudevents/sdk-php/tree/main/hack/8.1.Dockerfile" \ org.opencontainers.image.documentation="https://github.com/cloudevents/sdk-php/tree/main/hack/README.md" \ org.opencontainers.image.source="https://github.com/cloudevents/sdk-php" \ org.opencontainers.image.vendor="CloudEvent" \ org.opencontainers.image.title="PHP 8.1" \ org.opencontainers.image.description="PHP 8.1 test environment for cloudevents/sdk-php" COPY --chown=www-data:www-data install-composer /usr/local/bin/install-composer RUN chmod +x /usr/local/bin/install-composer \ && /usr/local/bin/install-composer \ && rm /usr/local/bin/install-composer RUN apk update \ && apk --no-cache upgrade \ && apk add --no-cache bash ca-certificates git libzip-dev \ && rm -rf /var/www/html /tmp/pear \ && chown -R www-data:www-data /var/www WORKDIR /var/www ENTRYPOINT ["/var/www/vendor/bin/phpunit"] hack/8.2.Dockerfile000064400000001734146412130150007754 0ustar00FROM php:8.2-alpine LABEL org.opencontainers.image.url="https://github.com/cloudevents/sdk-php/tree/main/hack/8.2.Dockerfile" \ org.opencontainers.image.documentation="https://github.com/cloudevents/sdk-php/tree/main/hack/README.md" \ org.opencontainers.image.source="https://github.com/cloudevents/sdk-php" \ org.opencontainers.image.vendor="CloudEvent" \ org.opencontainers.image.title="PHP 8.2" \ org.opencontainers.image.description="PHP 8.2 test environment for cloudevents/sdk-php" COPY --chown=www-data:www-data install-composer /usr/local/bin/install-composer RUN chmod +x /usr/local/bin/install-composer \ && /usr/local/bin/install-composer \ && rm /usr/local/bin/install-composer RUN apk update \ && apk --no-cache upgrade \ && apk add --no-cache bash ca-certificates git libzip-dev \ && rm -rf /var/www/html /tmp/pear \ && chown -R www-data:www-data /var/www WORKDIR /var/www ENTRYPOINT ["/var/www/vendor/bin/phpunit"] hack/8.3.Dockerfile000064400000001737146412130150007760 0ustar00FROM php:8.3-rc-alpine LABEL org.opencontainers.image.url="https://github.com/cloudevents/sdk-php/tree/main/hack/8.3.Dockerfile" \ org.opencontainers.image.documentation="https://github.com/cloudevents/sdk-php/tree/main/hack/README.md" \ org.opencontainers.image.source="https://github.com/cloudevents/sdk-php" \ org.opencontainers.image.vendor="CloudEvent" \ org.opencontainers.image.title="PHP 8.3" \ org.opencontainers.image.description="PHP 8.3 test environment for cloudevents/sdk-php" COPY --chown=www-data:www-data install-composer /usr/local/bin/install-composer RUN chmod +x /usr/local/bin/install-composer \ && /usr/local/bin/install-composer \ && rm /usr/local/bin/install-composer RUN apk update \ && apk --no-cache upgrade \ && apk add --no-cache bash ca-certificates git libzip-dev \ && rm -rf /var/www/html /tmp/pear \ && chown -R www-data:www-data /var/www WORKDIR /var/www ENTRYPOINT ["/var/www/vendor/bin/phpunit"] hack/install-composer000075500000000735146412130150010675 0ustar00#!/bin/sh php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('sha384', 'composer-setup.php') === '756890a4488ce9024fc62c56153228907f1545c228516cbf63f885e036d37e9a59d27d63f46af1d4d07ee0f76181c7d3') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \ php composer-setup.php --install-dir=/usr/local/bin --filename=composer --quiet php -r "unlink('composer-setup.php');" \ phpcs.xml.dist000064400000052267146412130150007356 0ustar00 The PSR-12 coding standard. 0 0 0 error Method name "%s" must not be prefixed with an underscore to indicate visibility 0 0 0 0 0 0 0 phpunit.xml.dist000064400000001104146412130150007710 0ustar00 tests/Unit src psalm.xml.dist000064400000000547146412130150007347 0ustar00 src/CloudEventInterface.php000064400000000211146412130150011724 0ustar00configuration = $configuration; $this->requestFactory = $requestFactory; $this->responseFactory = $responseFactory; $this->streamFactory = $streamFactory; } public static function createJsonMarshaller( RequestFactoryInterface $requestFactory = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null ): self { return new self( ['serializer' => JsonSerializer::create(), 'type' => 'json'], $requestFactory ?? Psr17FactoryDiscovery::findRequestFactory(), $responseFactory ?? Psr17FactoryDiscovery::findResponseFactory(), $streamFactory ?? Psr17FactoryDiscovery::findStreamFactory() ); } /** * @param UriInterface|string $uri * * @throws UnsupportedSpecVersionException */ public function marshalStructuredRequest( CloudEventInterface $cloudEvent, string $method, $uri ): RequestInterface { $serialized = $this->configuration['serializer']->serializeStructured($cloudEvent); return $this->requestFactory->createRequest($method, $uri) ->withBody($this->streamFactory->createStream($serialized)) ->withHeader('Content-Type', sprintf('application/cloudevents+%s', $this->configuration['type'])) ->withHeader('Content-Length', (string) strlen($serialized)); } /** * @param UriInterface|string $uri * * @throws UnsupportedSpecVersionException */ public function marshalBinaryRequest( CloudEventInterface $cloudEvent, string $method, $uri ): RequestInterface { $serialized = $this->configuration['serializer']->serializeBinary($cloudEvent); $request = $this->requestFactory->createRequest($method, $uri) ->withBody($this->streamFactory->createStream($serialized['data'])) ->withHeader('Content-Type', $serialized['contentType']) ->withHeader('Content-Length', (string) strlen($serialized['data'])); foreach ($serialized['attributes'] as $key => $value) { $request = $request->withHeader(sprintf('ce-%s', $key), $value); } return $request; } /** * @param list $cloudEvents * @param UriInterface|string $uri * * @throws UnsupportedSpecVersionException */ public function marshalBatchRequest( array $cloudEvents, string $method, $uri ): RequestInterface { $serialized = $this->configuration['serializer']->serializeBatch($cloudEvents); return $this->requestFactory->createRequest($method, $uri) ->withBody($this->streamFactory->createStream($serialized)) ->withHeader('Content-Type', sprintf('application/cloudevents-batch+%s', $this->configuration['type'])) ->withHeader('Content-Length', (string) strlen($serialized)); } /** * @throws UnsupportedSpecVersionException */ public function marshalStructuredResponse( CloudEventInterface $cloudEvent, int $code = 200, string $reasonPhrase = '' ): ResponseInterface { $serialized = $this->configuration['serializer']->serializeStructured($cloudEvent); return $this->responseFactory->createResponse($code, $reasonPhrase) ->withBody($this->streamFactory->createStream($serialized)) ->withHeader('Content-Type', sprintf('application/cloudevents+%s', $this->configuration['type'])) ->withHeader('Content-Length', (string) strlen($serialized)); } /** * @param UriInterface|string $uri * * @throws UnsupportedSpecVersionException */ public function marshalBinaryResponse( CloudEventInterface $cloudEvent, int $code = 200, string $reasonPhrase = '' ): ResponseInterface { $serialized = $this->configuration['serializer']->serializeBinary($cloudEvent); $response = $this->responseFactory->createResponse($code, $reasonPhrase) ->withBody($this->streamFactory->createStream($serialized['data'])) ->withHeader('Content-Type', $serialized['contentType']) ->withHeader('Content-Length', (string) strlen($serialized['data'])); foreach ($serialized['attributes'] as $key => $value) { $response = $response->withHeader(sprintf('ce-%s', $key), $value); } return $response; } /** * @param list $cloudEvents * * @throws UnsupportedSpecVersionException */ public function marshalBatchResponse( array $cloudEvents, int $code = 200, string $reasonPhrase = '' ): ResponseInterface { $serialized = $this->configuration['serializer']->serializeBatch($cloudEvents); return $this->responseFactory->createResponse($code, $reasonPhrase) ->withBody($this->streamFactory->createStream($serialized)) ->withHeader('Content-Type', sprintf('application/cloudevents-batch+%s', $this->configuration['type'])) ->withHeader('Content-Length', (string) strlen($serialized)); } } src/Http/MarshallerInterface.php000064400000003510146412130150012672 0ustar00 $cloudEvents * @param UriInterface|string $uri * * @throws UnsupportedSpecVersionException */ public function marshalBatchRequest( array $cloudEvents, string $method, $uri ): RequestInterface; /** * @throws UnsupportedSpecVersionException */ public function marshalStructuredResponse( CloudEventInterface $cloudEvent, int $code = 200, string $reasonPhrase = '' ): ResponseInterface; /** * @throws UnsupportedSpecVersionException */ public function marshalBinaryResponse( CloudEventInterface $cloudEvent, int $code = 200, string $reasonPhrase = '' ): ResponseInterface; /** * @param list $cloudEvents * * @throws UnsupportedSpecVersionException */ public function marshalBatchResponse( array $cloudEvents, int $code = 200, string $reasonPhrase = '' ): ResponseInterface; } src/Http/Unmarshaller.php000064400000010564146412130150011423 0ustar00}> */ private array $configuration; /** * @param array}> $configuration */ public function __construct(array $configuration) { $this->configuration = $configuration; } public static function createJsonUnmarshaller(): self { return new self([ 'json' => ['deserializer' => JsonDeserializer::create(), 'contentTypes' => ['application/json']], ]); } /** * @throws InvalidPayloadSyntaxException * @throws UnsupportedContentTypeException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException * * @return list */ public function unmarshal(MessageInterface $message): array { $contentType = strtolower(explode(';', $message->getHeader('Content-Type')[0] ?? '')[0]); foreach ($this->configuration as $type => $entry) { switch ($contentType) { case sprintf('application/cloudevents+%s', $type): return self::unmarshalStructured($message, $entry['deserializer']); case sprintf('application/cloudevents-batch+%s', $type): return self::unmarshalBatch($message, $entry['deserializer']); default: if (in_array($contentType, $entry['contentTypes'], true)) { return self::unmarshalBinary($message, $entry['deserializer']); } } } throw new UnsupportedContentTypeException(); } /** * @throws InvalidPayloadSyntaxException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException * * @return list */ private static function unmarshalStructured( MessageInterface $message, DeserializerInterface $deserializer ): array { $cloudEvent = $deserializer->deserializeStructured( (string) $message->getBody() ); return [$cloudEvent]; } /** * @throws InvalidPayloadSyntaxException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException * * @return list */ private static function unmarshalBinary( MessageInterface $message, DeserializerInterface $deserializer ): array { /** @var array> */ $headers = $message->getHeaders(); /** @psalm-suppress MixedArgumentTypeCoercion */ $cloudEvent = $deserializer->deserializeBinary( (string) $message->getBody(), implode(', ', $message->getHeader('Content-Type')), self::decodeAttributes($headers) ); return [$cloudEvent]; } /** * @param array> $headers * * @return array */ private static function decodeAttributes(array $headers): array { $attributes = []; foreach ($headers as $key => $values) { $key = (string) $key; /** @psalm-suppress UndefinedFunction */ if (\str_starts_with($key, 'ce-')) { $attributes[substr($key, 3)] = implode(', ', $values); } } return $attributes; } /** * @throws InvalidPayloadSyntaxException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException * * @return list */ private static function unmarshalBatch( MessageInterface $message, DeserializerInterface $deserializer ): array { return $deserializer->deserializeBatch( (string) $message->getBody() ); } } src/Http/UnmarshallerInterface.php000064400000001314146412130150013235 0ustar00 */ public function unmarshal(MessageInterface $message): array; } src/Serializers/DeserializerInterface.php000064400000002144146412130150014601 0ustar00 */ public function deserializeBatch(string $payload): array; /** * @param array $attributes * * @throws InvalidPayloadSyntaxException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException */ public function deserializeBinary(string $data, string $contentType, array $attributes): CloudEventInterface; } src/Serializers/JsonDeserializer.php000064400000006010146412130150013606 0ustar00denormalizer = $denormalizer; } public static function create(): self { return new self( new Denormalizer() ); } /** * @throws InvalidPayloadSyntaxException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException */ public function deserializeStructured(string $payload): CloudEventInterface { try { /** @var mixed */ $decoded = json_decode($payload, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { throw new InvalidPayloadSyntaxException(null, 0, $e); } if (!is_array($decoded)) { throw new InvalidPayloadSyntaxException(); } return $this->denormalizer->denormalize($decoded); } /** * @throws InvalidPayloadSyntaxException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException * * @return list */ public function deserializeBatch(string $payload): array { try { $decoded = json_decode($payload, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { throw new InvalidPayloadSyntaxException(null, 0, $e); } if (!is_array($decoded)) { throw new InvalidPayloadSyntaxException(); } $cloudEvents = []; foreach ($decoded as $value) { if (!is_array($value)) { throw new InvalidPayloadSyntaxException(); } $cloudEvents[] = $this->denormalizer->denormalize($value); } return $cloudEvents; } /** * @param array $attributes * * @throws InvalidPayloadSyntaxException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException */ public function deserializeBinary(string $data, string $contentType, array $attributes): CloudEventInterface { try { /** @var mixed */ $decoded = json_decode($data, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { throw new InvalidPayloadSyntaxException(null, 0, $e); } return $this->denormalizer->denormalize( array_merge( array_map('rawurldecode', $attributes), ['data' => $decoded, 'datacontenttype' => $contentType] ) ); } } src/Serializers/JsonSerializer.php000064400000005724146412130150013310 0ustar00normalizer = $normalizer; } public static function create(): self { return new self( new Normalizer() ); } /** * @throws UnsupportedSpecVersionException */ public function serializeStructured(CloudEventInterface $cloudEvent): string { if (! $cloudEvent instanceof V1CloudEventInterface) { throw new UnsupportedSpecVersionException(); } $normalized = $this->normalizer->normalize($cloudEvent, false); return json_encode($normalized); } /** * @param list $cloudEvents * * @throws UnsupportedSpecVersionException */ public function serializeBatch(array $cloudEvents): string { $normalized = []; foreach ($cloudEvents as $cloudEvent) { if (! $cloudEvent instanceof V1CloudEventInterface) { throw new UnsupportedSpecVersionException(); } $normalized[] = $this->normalizer->normalize($cloudEvent, false); } return json_encode($normalized); } /** * @throws UnsupportedSpecVersionException * * @return array{data: string, contentType: string, attributes: array} */ public function serializeBinary(CloudEventInterface $cloudEvent): array { if (! $cloudEvent instanceof V1CloudEventInterface) { throw new UnsupportedSpecVersionException(); } $normalized = $this->normalizer->normalize($cloudEvent, true); /** @var string */ $data = json_encode($normalized['data']); unset($normalized['data']); /** @var string */ $contentType = $normalized['datacontenttype'] ?? 'application/json'; unset($normalized['datacontenttype']); $attributes = []; /** @var mixed $value */ foreach ($normalized as $key => $value) { $attributes[$key] = self::encodeAttributeValue($value); } return [ 'data' => $data, 'contentType' => $contentType, 'attributes' => $attributes, ]; } /** * @param mixed $value * * @return string */ private static function encodeAttributeValue($value): string { if ($value === true) { return 'true'; } if ($value === false) { return 'false'; } return rawurlencode((string) $value); } } src/Serializers/Normalizers/V1/Denormalizer.php000064400000002621146412130150015564 0ustar00 $payload * * @throws InvalidAttributeException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException */ public function denormalize(array $payload): CloudEventInterface { try { if (($payload['specversion'] ?? null) !== CloudEventInterface::SPEC_VERSION) { throw new UnsupportedSpecVersionException(); } $cloudEvent = AttributeConverter::fromArray($payload); if ($cloudEvent === null) { throw new MissingAttributeException(); } return $cloudEvent->withData(DataFormatter::decode($payload)); } catch (Throwable $e) { if ($e instanceof UnsupportedSpecVersionException || $e instanceof MissingAttributeException) { throw $e; } throw new InvalidAttributeException(null, 0, $e); } } } src/Serializers/Normalizers/V1/DenormalizerInterface.php000064400000001126146412130150017404 0ustar00 $payload * * @throws InvalidAttributeException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException */ public function denormalize(array $payload): CloudEventInterface; } src/Serializers/Normalizers/V1/Normalizer.php000064400000001611146412130150015251 0ustar00} */ private array $configuration; /** * @param array{subsecondPrecision?: int<0, 6>} $configuration */ public function __construct(array $configuration = []) { $this->configuration = $configuration; } /** * @return array */ public function normalize(CloudEventInterface $cloudEvent, bool $rawData): array { return array_merge( AttributeConverter::toArray($cloudEvent, $this->configuration), DataFormatter::encode($cloudEvent->getData(), $rawData) ); } } src/Serializers/Normalizers/V1/NormalizerInterface.php000064400000000451146412130150017073 0ustar00 */ public function normalize(CloudEventInterface $cloudEvent, bool $rawData): array; } src/Serializers/SerializerInterface.php000064400000001405146412130150014267 0ustar00 $cloudEvents * * @throws UnsupportedSpecVersionException */ public function serializeBatch(array $cloudEvents): string; /** * @throws UnsupportedSpecVersionException * * @return array{data: string, contentType: string, attributes: array} */ public function serializeBinary(CloudEventInterface $cloudEvent): array; } src/Utilities/AttributeConverter.php000064400000005675146412130150013664 0ustar00} $configuration * * @return array */ public static function toArray(CloudEventInterface $cloudEvent, array $configuration): array { /** @var array */ $attributes = array_filter([ 'specversion' => $cloudEvent->getSpecVersion(), 'id' => $cloudEvent->getId(), 'source' => $cloudEvent->getSource(), 'type' => $cloudEvent->getType(), 'datacontenttype' => $cloudEvent->getDataContentType(), 'dataschema' => $cloudEvent->getDataSchema(), 'subject' => $cloudEvent->getSubject(), 'time' => TimeFormatter::encode($cloudEvent->getTime(), $configuration['subsecondPrecision'] ?? 0), ], fn ($attr) => $attr !== null); return array_merge($attributes, $cloudEvent->getExtensions()); } /** * @param array $attributes */ public static function fromArray(array $attributes): ?CloudEventImmutable { if (!isset($attributes['id']) || !isset($attributes['source']) || !isset($attributes['type'])) { return null; } /** @psalm-suppress MixedArgument */ $cloudEvent = new CloudEventImmutable( $attributes['id'], $attributes['source'], $attributes['type'] ); /** @var mixed $value */ foreach ($attributes as $attribute => $value) { switch ($attribute) { case 'specversion': case 'id': case 'source': case 'type': case 'data': case 'data_base64': break; case 'datacontenttype': /** @psalm-suppress MixedArgument */ $cloudEvent = $cloudEvent->withDataContentType($value); break; case 'dataschema': /** @psalm-suppress MixedArgument */ $cloudEvent = $cloudEvent->withDataSchema($value); break; case 'subject': /** @psalm-suppress MixedArgument */ $cloudEvent = $cloudEvent->withSubject($value); break; case 'time': /** @psalm-suppress MixedArgument */ $cloudEvent = $cloudEvent->withTime(TimeFormatter::decode($value)); break; default: /** @psalm-suppress MixedArgument */ $cloudEvent = $cloudEvent->withExtension((string) $attribute, $value); break; } } return $cloudEvent; } } src/Utilities/DataFormatter.php000064400000002742146412130150012556 0ustar00 base64_encode($data)]; } return $data !== null ? ['data' => $data] : []; } /** * @return mixed */ public static function decode(array $data) { if (isset($data['data_base64'])) { if (!is_string($data['data_base64'])) { throw new ValueError( \sprintf('%s(): Argument #1 ($data) contains bad data_base64 attribute content', __METHOD__) ); } $decoded = base64_decode($data['data_base64'], true); if ($decoded === false) { throw new ValueError( \sprintf('%s(): Argument #1 ($data) contains bad data_base64 attribute content', __METHOD__) ); } return $decoded; } if (isset($data['data'])) { return $data['data']; } return null; } /** * @param mixed $data */ private static function isBinary($data): bool { return is_string($data) && !preg_match('//u', $data); } } src/Utilities/TimeFormatter.php000064400000004726146412130150012607 0ustar00 $subsecondPrecision */ public static function encode(?DateTimeImmutable $time, int $subsecondPrecision): ?string { if ($time === null) { return null; } return sprintf('%sZ', self::encodeWithoutTimezone($time, $subsecondPrecision)); } /** * @param int<0, 6> $subsecondPrecision */ private static function encodeWithoutTimezone(DateTimeImmutable $time, int $subsecondPrecision): string { $utcTime = $time->setTimezone(new DateTimeZone(self::TIME_ZONE)); if ($subsecondPrecision <= 0) { return $utcTime->format(self::TIME_FORMAT); } if ($subsecondPrecision >= 6) { return $utcTime->format(self::TIME_FORMAT_EXTENDED); } return substr($utcTime->format(self::TIME_FORMAT_EXTENDED), 0, $subsecondPrecision - 6); } public static function decode(?string $time): ?DateTimeImmutable { if ($time === null) { return null; } $time = \strtoupper($time); /** @psalm-suppress UndefinedFunction */ $decoded = \str_contains($time, '.') ? DateTimeImmutable::createFromFormat(self::RFC3339_EXTENDED_FORMAT, self::truncateOverPrecision($time), new DateTimeZone(self::TIME_ZONE)) : DateTimeImmutable::createFromFormat(self::RFC3339_FORMAT, $time, new DateTimeZone(self::TIME_ZONE)); if ($decoded === false) { throw new ValueError( \sprintf('%s(): Argument #1 ($time) is not a valid RFC3339 timestamp', __METHOD__) ); } return $decoded; } private static function truncateOverPrecision(string $time): string { [$fst, $snd] = explode('.', $time); // match the first n digits at the start \preg_match('/^\d+/', $snd, $matches); $digits = $matches[0] ?? ''; // datetime portion + period + up to 6 digits + timezone string return $fst . '.' . substr($digits, 0, 6) . substr($snd, strlen($digits)); } } src/V1/CloudEvent.php000064400000000772146412130150010405 0ustar00setId($id); return $cloudEvent; } public function withSource(string $source): self { $cloudEvent = clone $this; $cloudEvent->setSource($source); return $cloudEvent; } public function withType(string $type): self { $cloudEvent = clone $this; $cloudEvent->setType($type); return $cloudEvent; } /** * @param mixed $data */ public function withData($data): self { $cloudEvent = clone $this; $cloudEvent->setData($data); return $cloudEvent; } public function withDataContentType(?string $dataContentType): self { $cloudEvent = clone $this; $cloudEvent->setDataContentType($dataContentType); return $cloudEvent; } public function withDataSchema(?string $dataSchema): self { $cloudEvent = clone $this; $cloudEvent->setDataSchema($dataSchema); return $cloudEvent; } public function withSubject(?string $subject): self { $cloudEvent = clone $this; $cloudEvent->setSubject($subject); return $cloudEvent; } public function withTime(?DateTimeImmutable $time): self { $cloudEvent = clone $this; $cloudEvent->setTime($time); return $cloudEvent; } /** * @param array $extensions */ public function withExtensions(array $extensions): self { $cloudEvent = clone $this; $cloudEvent->setExtensions($extensions); return $cloudEvent; } /** * @param bool|int|string|null $value */ public function withExtension(string $attribute, $value): self { $cloudEvent = clone $this; $cloudEvent->setExtension($attribute, $value); return $cloudEvent; } } src/V1/CloudEventInterface.php000064400000001432146412130150012220 0ustar00 */ public function getExtensions(): array; /** * @return bool|int|string|null */ public function getExtension(string $attribute); } src/V1/CloudEventTrait.php000064400000015541146412130150011411 0ustar00 */ private array $extensions = []; /** * @param mixed $data * @param array $extensions */ public function __construct( string $id, string $source, string $type, $data = null, ?string $dataContentType = null, ?string $dataSchema = null, ?string $subject = null, ?DateTimeImmutable $time = null, array $extensions = [] ) { $this->setId($id); $this->setSource($source); $this->setType($type); $this->setData($data); $this->setDataContentType($dataContentType); $this->setDataSchema($dataSchema); $this->setSubject($subject); $this->setTime($time); $this->setExtensions($extensions); } public static function createFromInterface(CloudEventInterface $event): self { return new self( $event->getId(), $event->getSource(), $event->getType(), $event->getData(), $event->getDataContentType(), $event->getDataSchema(), $event->getSubject(), $event->getTime(), $event->getExtensions() ); } public function getSpecVersion(): string { return CloudEventInterface::SPEC_VERSION; } public function getId(): string { return $this->id; } private function setId(string $id): self { if ('' === $id) { throw new ValueError( \sprintf('%s(): Argument #1 ($id) must be a non-empty string, "" given', __METHOD__) ); } $this->id = $id; return $this; } public function getSource(): string { return $this->source; } private function setSource(string $source): self { if ('' === $source) { throw new ValueError( \sprintf('%s(): Argument #1 ($source) must be a non-empty string, "" given', __METHOD__) ); } $this->source = $source; return $this; } public function getType(): string { return $this->type; } private function setType(string $type): self { if ('' === $type) { throw new ValueError( \sprintf('%s(): Argument #1 ($type) must be a non-empty string, "" given', __METHOD__) ); } $this->type = $type; return $this; } /** * @return mixed */ public function getData() { return $this->data; } /** * @param mixed $data */ private function setData($data): self { $this->data = $data; return $this; } public function getDataContentType(): ?string { return $this->dataContentType; } private function setDataContentType(?string $dataContentType): self { if ($dataContentType !== null && \preg_match('#^[-\w]+/[-\w]+$#', $dataContentType) !== 1) { throw new ValueError( \sprintf('%s(): Argument #1 ($dataContentType) must be a valid mime-type string or null, "%s" given', __METHOD__, $dataContentType) ); } $this->dataContentType = $dataContentType; return $this; } public function getDataSchema(): ?string { return $this->dataSchema; } private function setDataSchema(?string $dataSchema): self { if ('' === $dataSchema) { throw new ValueError( \sprintf('%s(): Argument #1 ($dataSchema) must be a non-empty string or null, "" given', __METHOD__) ); } $this->dataSchema = $dataSchema; return $this; } public function getSubject(): ?string { return $this->subject; } private function setSubject(?string $subject): self { if ('' === $subject) { throw new ValueError( \sprintf('%s(): Argument #1 ($subject) must be a non-empty string or null, "" given', __METHOD__) ); } $this->subject = $subject; return $this; } public function getTime(): ?DateTimeImmutable { return $this->time; } private function setTime(?DateTimeImmutable $time): self { $this->time = $time; return $this; } /** * @return array */ public function getExtensions(): array { return $this->extensions; } /** * @return bool|int|string|null */ public function getExtension(string $attribute) { return $this->extensions[$attribute] ?? null; } /** * @param array $extensions */ private function setExtensions(array $extensions): self { foreach ($extensions as $attribute => $value) { $this->setExtension($attribute, $value); } return $this; } /** * @param bool|int|string|null $value */ private function setExtension(string $attribute, $value): self { if (\preg_match('/^[a-z0-9]+$/', $attribute) !== 1) { throw new ValueError( \sprintf('%s(): Argument #1 ($attribute) must match the regex [a-z0-9]+, %s given', __METHOD__, $attribute) ); } /** @var list */ static $reservedAttributes = [ 'specversion', 'id', 'source', 'type', 'data', 'data_base64', 'datacontenttype', 'dataschema', 'subject', 'time', ]; if (in_array($attribute, $reservedAttributes, true)) { throw new ValueError( \sprintf('%s(): Argument #1 ($attribute) must not be a reserved attribute, %s given', __METHOD__, $attribute) ); } /** @psalm-suppress UndefinedFunction */ $type = \get_debug_type($value); $types = ['bool', 'int', 'string', 'null']; if (!in_array($type, $types, true)) { /** @psalm-suppress MixedArgument */ throw new TypeError( \sprintf('%s(): Argument #2 ($value) must be of type %s, %s given', __METHOD__, implode('|', $types), $type) ); } if ($value === null) { unset($this->extensions[$attribute]); } else { $this->extensions[$attribute] = $value; } return $this; } } tests/Unit/Http/MarshallerTest.php000064400000027176146412130150013221 0ustar00createStub(CloudEventInterface::class); $event->method('getSpecVersion')->willReturn('1.0'); $event->method('getId')->willReturn('1234-1234-1234'); $event->method('getSource')->willReturn('/var/data'); $event->method('getType')->willReturn('com.example.someevent'); $event->method('getDataContentType')->willReturn('application/json'); $event->method('getDataSchema')->willReturn('com.example/schema'); $event->method('getSubject')->willReturn('larger-context'); $event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event->method('getData')->willReturn(['key' => 'value']); $event->method('getExtensions')->willReturn(['comacme' => 'foo']); $request = Marshaller::createJsonMarshaller() ->marshalStructuredRequest($event, 'GET', 'https://example.com/endpoint'); self::assertSame( "GET /endpoint HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/cloudevents+json\r\nContent-Length: 266\r\n\r\n{\"specversion\":\"1.0\",\"id\":\"1234-1234-1234\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}}", Message::toString($request) ); } public function testMarshalBinaryRequest(): void { /** @var CloudEventInterfaceV1|Stub $event */ $event = $this->createStub(CloudEventInterface::class); $event->method('getSpecVersion')->willReturn('1.0'); $event->method('getId')->willReturn('1234-1234-1234'); $event->method('getSource')->willReturn('/var/data'); $event->method('getType')->willReturn('com.example.someevent'); $event->method('getDataContentType')->willReturn('application/json'); $event->method('getDataSchema')->willReturn('com.example/schema'); $event->method('getSubject')->willReturn('larger-context'); $event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event->method('getData')->willReturn(['key' => 'value']); $event->method('getExtensions')->willReturn(['comacme' => 'foo']); $request = Marshaller::createJsonMarshaller() ->marshalBinaryRequest($event, 'GET', 'https://example.com/endpoint'); self::assertSame( "GET /endpoint HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/json\r\nContent-Length: 15\r\nce-specversion: 1.0\r\nce-id: 1234-1234-1234\r\nce-source: %2Fvar%2Fdata\r\nce-type: com.example.someevent\r\nce-dataschema: com.example%2Fschema\r\nce-subject: larger-context\r\nce-time: 2018-04-05T17%3A31%3A00Z\r\nce-comacme: foo\r\n\r\n{\"key\":\"value\"}", Message::toString($request) ); } public function testMarshalBatchRequest(): void { /** @var CloudEventInterfaceV1|Stub $event */ $event1 = $this->createStub(CloudEventInterface::class); $event1->method('getSpecVersion')->willReturn('1.0'); $event1->method('getId')->willReturn('1234-1234-1234'); $event1->method('getSource')->willReturn('/var/data'); $event1->method('getType')->willReturn('com.example.someevent'); $event1->method('getDataContentType')->willReturn('application/json'); $event1->method('getDataSchema')->willReturn('com.example/schema'); $event1->method('getSubject')->willReturn('larger-context'); $event1->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event1->method('getData')->willReturn(['key' => 'value']); $event1->method('getExtensions')->willReturn(['comacme' => 'foo']); /** @var CloudEventInterfaceV1|Stub $event */ $event2 = $this->createStub(CloudEventInterface::class); $event2->method('getSpecVersion')->willReturn('1.0'); $event2->method('getId')->willReturn('1234-1234-2222'); $event2->method('getSource')->willReturn('/var/data'); $event2->method('getType')->willReturn('com.example.someevent'); $event2->method('getDataContentType')->willReturn('application/json'); $event2->method('getDataSchema')->willReturn('com.example/schema'); $event2->method('getSubject')->willReturn('larger-context'); $event2->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event2->method('getData')->willReturn(['key' => 'value']); $event2->method('getExtensions')->willReturn(['comacme' => 'foo']); $request = Marshaller::createJsonMarshaller() ->marshalBatchRequest([$event1, $event2], 'GET', 'https://example.com/endpoint'); self::assertSame( "GET /endpoint HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/cloudevents-batch+json\r\nContent-Length: 535\r\n\r\n[{\"specversion\":\"1.0\",\"id\":\"1234-1234-1234\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}},{\"specversion\":\"1.0\",\"id\":\"1234-1234-2222\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}}]", Message::toString($request) ); } public function testMarshalStructuredResponse(): void { /** @var CloudEventInterfaceV1|Stub $event */ $event = $this->createStub(CloudEventInterface::class); $event->method('getSpecVersion')->willReturn('1.0'); $event->method('getId')->willReturn('1234-1234-1234'); $event->method('getSource')->willReturn('/var/data'); $event->method('getType')->willReturn('com.example.someevent'); $event->method('getDataContentType')->willReturn('application/json'); $event->method('getDataSchema')->willReturn('com.example/schema'); $event->method('getSubject')->willReturn('larger-context'); $event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event->method('getData')->willReturn(['key' => 'value']); $event->method('getExtensions')->willReturn(['comacme' => 'foo']); $response = Marshaller::createJsonMarshaller() ->marshalStructuredResponse($event); self::assertSame( "HTTP/1.1 200 OK\r\nContent-Type: application/cloudevents+json\r\nContent-Length: 266\r\n\r\n{\"specversion\":\"1.0\",\"id\":\"1234-1234-1234\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}}", Message::toString($response) ); } public function testMarshalBinaryResponse(): void { /** @var CloudEventInterfaceV1|Stub $event */ $event = $this->createStub(CloudEventInterface::class); $event->method('getSpecVersion')->willReturn('1.0'); $event->method('getId')->willReturn('1234-1234-1234'); $event->method('getSource')->willReturn('/var/data'); $event->method('getType')->willReturn('com.example.someevent'); $event->method('getDataContentType')->willReturn('application/json'); $event->method('getDataSchema')->willReturn('com.example/schema'); $event->method('getSubject')->willReturn('larger-context'); $event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event->method('getData')->willReturn(['key' => 'value']); $event->method('getExtensions')->willReturn(['comacme' => 'foo']); $response = Marshaller::createJsonMarshaller() ->marshalBinaryResponse($event); self::assertSame( "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 15\r\nce-specversion: 1.0\r\nce-id: 1234-1234-1234\r\nce-source: %2Fvar%2Fdata\r\nce-type: com.example.someevent\r\nce-dataschema: com.example%2Fschema\r\nce-subject: larger-context\r\nce-time: 2018-04-05T17%3A31%3A00Z\r\nce-comacme: foo\r\n\r\n{\"key\":\"value\"}", Message::toString($response) ); } public function testMarshalBatchResponse(): void { /** @var CloudEventInterfaceV1|Stub $event */ $event1 = $this->createStub(CloudEventInterface::class); $event1->method('getSpecVersion')->willReturn('1.0'); $event1->method('getId')->willReturn('1234-1234-1234'); $event1->method('getSource')->willReturn('/var/data'); $event1->method('getType')->willReturn('com.example.someevent'); $event1->method('getDataContentType')->willReturn('application/json'); $event1->method('getDataSchema')->willReturn('com.example/schema'); $event1->method('getSubject')->willReturn('larger-context'); $event1->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event1->method('getData')->willReturn(['key' => 'value']); $event1->method('getExtensions')->willReturn(['comacme' => 'foo']); /** @var CloudEventInterfaceV1|Stub $event */ $event2 = $this->createStub(CloudEventInterface::class); $event2->method('getSpecVersion')->willReturn('1.0'); $event2->method('getId')->willReturn('1234-1234-2222'); $event2->method('getSource')->willReturn('/var/data'); $event2->method('getType')->willReturn('com.example.someevent'); $event2->method('getDataContentType')->willReturn('application/json'); $event2->method('getDataSchema')->willReturn('com.example/schema'); $event2->method('getSubject')->willReturn('larger-context'); $event2->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event2->method('getData')->willReturn(['key' => 'value']); $event2->method('getExtensions')->willReturn(['comacme' => 'foo']); $response = Marshaller::createJsonMarshaller() ->marshalBatchResponse([$event1, $event2]); self::assertSame( "HTTP/1.1 200 OK\r\nContent-Type: application/cloudevents-batch+json\r\nContent-Length: 535\r\n\r\n[{\"specversion\":\"1.0\",\"id\":\"1234-1234-1234\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}},{\"specversion\":\"1.0\",\"id\":\"1234-1234-2222\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}}]", Message::toString($response) ); } } tests/Unit/Http/UnmarshallerTest.php000064400000031051146412130150013547 0ustar00unmarshal( Message::parseRequest( "GET /endpoint HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/cloudevents+json\r\nContent-Length: 266\r\n\r\n{\"specversion\":\"1.0\",\"id\":\"1234-1234-1234\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}}" ) ); self::assertCount(1, $events); self::assertInstanceOf(CloudEventInterface::class, $events[0]); self::assertSame('1.0', $events[0]->getSpecVersion()); self::assertSame('1234-1234-1234', $events[0]->getId()); self::assertSame('/var/data', $events[0]->getSource()); self::assertSame('com.example.someevent', $events[0]->getType()); self::assertSame('application/json', $events[0]->getDataContentType()); self::assertSame('com.example/schema', $events[0]->getDataSchema()); self::assertSame('larger-context', $events[0]->getSubject()); self::assertSame(1522949460, $events[0]->getTime()->getTimestamp()); self::assertSame(['key' => 'value'], $events[0]->getData()); self::assertSame(['comacme' => 'foo'], $events[0]->getExtensions()); } public function testUnmarshalStructuredRequestInvalidContentType(): void { $this->expectException(UnsupportedContentTypeException::class); Unmarshaller::createJsonUnmarshaller()->unmarshal( Message::parseRequest( "GET /endpoint HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/cloudevents+xml\r\nContent-Length: 4\r\n\r\nDATA" ) ); } public function testUnmarshalBinaryRequest(): void { $events = Unmarshaller::createJsonUnmarshaller()->unmarshal( Message::parseRequest( "GET /endpoint HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/json\r\nContent-Length: 15\r\nce-specversion: 1.0\r\nce-id: 1234-1234-1234\r\nce-source: %2Fvar%2Fdata\r\nce-type: com.example.someevent\r\nce-dataschema: com.example%2Fschema\r\nce-subject: larger-context\r\nce-time: 2018-04-05T17%3A31%3A00Z\r\nce-comacme: foo\r\n\r\n{\"key\":\"value\"}" ) ); self::assertCount(1, $events); self::assertInstanceOf(CloudEventInterface::class, $events[0]); self::assertSame('1.0', $events[0]->getSpecVersion()); self::assertSame('1234-1234-1234', $events[0]->getId()); self::assertSame('/var/data', $events[0]->getSource()); self::assertSame('com.example.someevent', $events[0]->getType()); self::assertSame('application/json', $events[0]->getDataContentType()); self::assertSame('com.example/schema', $events[0]->getDataSchema()); self::assertSame('larger-context', $events[0]->getSubject()); self::assertSame(1522949460, $events[0]->getTime()->getTimestamp()); self::assertSame(['key' => 'value'], $events[0]->getData()); self::assertSame(['comacme' => 'foo'], $events[0]->getExtensions()); } public function testUnmarshalBinaryRequestInvalidContentType(): void { $this->expectException(UnsupportedContentTypeException::class); Unmarshaller::createJsonUnmarshaller()->unmarshal( Message::parseRequest( "GET /endpoint HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/xml\r\nContent-Length: 4\r\nce-specversion: 1.0\r\nce-id: 1234-1234-1234\r\nce-source: %2Fvar%2Fdata\r\nce-type: com.example.someevent\r\nce-dataschema: com.example%2Fschema\r\nce-subject: larger-context\r\nce-time: 2018-04-05T17%3A31%3A00Z\r\nce-comacme: foo\r\n\r\nDATA" ) ); } public function testUnmarshalBatchRequest(): void { $events = Unmarshaller::createJsonUnmarshaller()->unmarshal( Message::parseRequest( "GET /endpoint HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/cloudevents-batch+json\r\nContent-Length: 535\r\n\r\n[{\"specversion\":\"1.0\",\"id\":\"1234-1234-1234\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}},{\"specversion\":\"1.0\",\"id\":\"1234-1234-2222\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}}]" ) ); self::assertCount(2, $events); self::assertInstanceOf(CloudEventInterface::class, $events[0]); self::assertSame('1.0', $events[0]->getSpecVersion()); self::assertSame('1234-1234-1234', $events[0]->getId()); self::assertSame('/var/data', $events[0]->getSource()); self::assertSame('com.example.someevent', $events[0]->getType()); self::assertSame('application/json', $events[0]->getDataContentType()); self::assertSame('com.example/schema', $events[0]->getDataSchema()); self::assertSame('larger-context', $events[0]->getSubject()); self::assertSame(1522949460, $events[0]->getTime()->getTimestamp()); self::assertSame(['key' => 'value'], $events[0]->getData()); self::assertSame(['comacme' => 'foo'], $events[0]->getExtensions()); self::assertInstanceOf(CloudEventInterface::class, $events[1]); self::assertSame('1.0', $events[1]->getSpecVersion()); self::assertSame('1234-1234-2222', $events[1]->getId()); self::assertSame('/var/data', $events[1]->getSource()); self::assertSame('com.example.someevent', $events[1]->getType()); self::assertSame('application/json', $events[1]->getDataContentType()); self::assertSame('com.example/schema', $events[1]->getDataSchema()); self::assertSame('larger-context', $events[1]->getSubject()); self::assertSame(1522949460, $events[1]->getTime()->getTimestamp()); self::assertSame(['key' => 'value'], $events[1]->getData()); self::assertSame(['comacme' => 'foo'], $events[1]->getExtensions()); } public function testUnmarshalStructuredResponse(): void { $events = Unmarshaller::createJsonUnmarshaller()->unmarshal( Message::parseResponse( "HTTP/1.1 200 OK\r\nContent-Type: application/cloudevents+json\r\nContent-Length: 266\r\n\r\n{\"specversion\":\"1.0\",\"id\":\"1234-1234-1234\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}}" ) ); self::assertCount(1, $events); self::assertInstanceOf(CloudEventInterface::class, $events[0]); self::assertSame('1.0', $events[0]->getSpecVersion()); self::assertSame('1234-1234-1234', $events[0]->getId()); self::assertSame('/var/data', $events[0]->getSource()); self::assertSame('com.example.someevent', $events[0]->getType()); self::assertSame('application/json', $events[0]->getDataContentType()); self::assertSame('com.example/schema', $events[0]->getDataSchema()); self::assertSame('larger-context', $events[0]->getSubject()); self::assertSame(1522949460, $events[0]->getTime()->getTimestamp()); self::assertSame(['key' => 'value'], $events[0]->getData()); self::assertSame(['comacme' => 'foo'], $events[0]->getExtensions()); } public function testUnmarshalStructuredResponseInvalidContentType(): void { $this->expectException(UnsupportedContentTypeException::class); Unmarshaller::createJsonUnmarshaller()->unmarshal( Message::parseResponse( "HTTP/1.1 200 OK\r\nContent-Type: application/cloudevents+xml\r\nContent-Length: 4\r\n\r\nDATA" ) ); } public function testUnmarshalBinaryResponse(): void { $events = Unmarshaller::createJsonUnmarshaller()->unmarshal( Message::parseResponse( "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 15\r\nce-specversion: 1.0\r\nce-id: 1234-1234-1234\r\nce-source: %2Fvar%2Fdata\r\nce-type: com.example.someevent\r\nce-dataschema: com.example%2Fschema\r\nce-subject: larger-context\r\nce-time: 2018-04-05T17%3A31%3A00Z\r\nce-comacme: foo\r\n\r\n{\"key\":\"value\"}" ) ); self::assertCount(1, $events); self::assertInstanceOf(CloudEventInterface::class, $events[0]); self::assertSame('1.0', $events[0]->getSpecVersion()); self::assertSame('1234-1234-1234', $events[0]->getId()); self::assertSame('/var/data', $events[0]->getSource()); self::assertSame('com.example.someevent', $events[0]->getType()); self::assertSame('application/json', $events[0]->getDataContentType()); self::assertSame('com.example/schema', $events[0]->getDataSchema()); self::assertSame('larger-context', $events[0]->getSubject()); self::assertSame(1522949460, $events[0]->getTime()->getTimestamp()); self::assertSame(['key' => 'value'], $events[0]->getData()); self::assertSame(['comacme' => 'foo'], $events[0]->getExtensions()); } public function testUnmarshalBatchResponse(): void { $events = Unmarshaller::createJsonUnmarshaller()->unmarshal( Message::parseResponse( "HTTP/1.1 200 OK\r\nContent-Type: application/cloudevents-batch+json\r\nContent-Length: 535\r\n\r\n[{\"specversion\":\"1.0\",\"id\":\"1234-1234-1234\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}},{\"specversion\":\"1.0\",\"id\":\"1234-1234-2222\",\"source\":\"\/var\/data\",\"type\":\"com.example.someevent\",\"datacontenttype\":\"application\/json\",\"dataschema\":\"com.example\/schema\",\"subject\":\"larger-context\",\"time\":\"2018-04-05T17:31:00Z\",\"comacme\":\"foo\",\"data\":{\"key\":\"value\"}}]" ) ); self::assertCount(2, $events); self::assertInstanceOf(CloudEventInterface::class, $events[0]); self::assertSame('1.0', $events[0]->getSpecVersion()); self::assertSame('1234-1234-1234', $events[0]->getId()); self::assertSame('/var/data', $events[0]->getSource()); self::assertSame('com.example.someevent', $events[0]->getType()); self::assertSame('application/json', $events[0]->getDataContentType()); self::assertSame('com.example/schema', $events[0]->getDataSchema()); self::assertSame('larger-context', $events[0]->getSubject()); self::assertSame(1522949460, $events[0]->getTime()->getTimestamp()); self::assertSame(['key' => 'value'], $events[0]->getData()); self::assertSame(['comacme' => 'foo'], $events[0]->getExtensions()); self::assertInstanceOf(CloudEventInterface::class, $events[1]); self::assertSame('1.0', $events[1]->getSpecVersion()); self::assertSame('1234-1234-2222', $events[1]->getId()); self::assertSame('/var/data', $events[1]->getSource()); self::assertSame('com.example.someevent', $events[1]->getType()); self::assertSame('application/json', $events[1]->getDataContentType()); self::assertSame('com.example/schema', $events[1]->getDataSchema()); self::assertSame('larger-context', $events[1]->getSubject()); self::assertSame(1522949460, $events[1]->getTime()->getTimestamp()); self::assertSame(['key' => 'value'], $events[1]->getData()); self::assertSame(['comacme' => 'foo'], $events[1]->getExtensions()); } } tests/Unit/Serializers/JsonDeserializerTest.php000064400000024163146412130150015751 0ustar00createStub(DenormalizerInterface::class)) ); self::assertInstanceOf( DeserializerInterface::class, JsonDeserializer::create() ); } public function testDeserializeStructured(): void { $formatter = JsonDeserializer::create(); $event = $formatter->deserializeStructured( '{"specversion":"1.0","id":"1234-1234-1234","source":"\/var\/data","type":"com.example.someevent","datacontenttype":"application\/json","dataschema":"com.example\/schema","subject":"larger-context","time":"2018-04-05T17:31:00Z","data":{"key":"value"},"comacme":"foo"}' ); self::assertEquals('1.0', $event->getSpecVersion()); self::assertEquals('1234-1234-1234', $event->getId()); self::assertEquals('/var/data', $event->getSource()); self::assertEquals('com.example.someevent', $event->getType()); self::assertEquals('application/json', $event->getDataContentType()); self::assertEquals('com.example/schema', $event->getDataSchema()); self::assertEquals('larger-context', $event->getSubject()); self::assertEquals(new DateTimeImmutable('2018-04-05T17:31:00Z'), $event->getTime()); self::assertEquals(['key' => 'value'], $event->getData()); self::assertEquals(['comacme' => 'foo'], $event->getExtensions()); } public function testDeserializeStructuredUnsupportedSpecVersion(): void { $formatter = JsonDeserializer::create(); $this->expectException(UnsupportedSpecVersionException::class); $formatter->deserializeStructured( '{"specversion":"0.3","id":"1234-1234-1234","source":"\/var\/data","type":"com.example.someevent","datacontenttype":"application\/json","dataschema":"com.example\/schema","subject":"larger-context","time":"2018-04-05T17:31:00Z","data":{"key":"value"},"comacme":"foo"}' ); } public function testDeserializeStructuredInvalidSyntax1(): void { $formatter = JsonDeserializer::create(); $this->expectException(InvalidPayloadSyntaxException::class); $formatter->deserializeStructured( '{"specversion":"1.0","id":"1234-1' ); } public function testDeserializeStructuredInvalidSyntax2(): void { $formatter = JsonDeserializer::create(); $this->expectException(InvalidPayloadSyntaxException::class); $formatter->deserializeStructured( 'true' ); } public function testDeserializeStructuredInvalidId(): void { $formatter = JsonDeserializer::create(); $this->expectException(InvalidAttributeException::class); $formatter->deserializeStructured( '{"specversion":"1.0","id":123,"source":"\/var\/data","type":"com.example.someevent","datacontenttype":"application\/json","dataschema":"com.example\/schema","subject":"larger-context","time":"2018-04-05T17:31:00Z","data":{"key":"value"},"comacme":"foo"}' ); } public function testDeserializeBatch(): void { $formatter = JsonDeserializer::create(); $events = $formatter->deserializeBatch( '[{"specversion":"1.0","id":"1234-1234-1234","source":"\/var\/data","type":"com.example.someevent","datacontenttype":"application\/json","dataschema":"com.example\/schema","subject":"larger-context","time":"2018-04-05T17:31:00Z","data":{"key":"value"},"comacme":"foo"},{"specversion":"1.0","id":"1234-1234-2222","source":"\/var\/data","type":"com.example.someevent","datacontenttype":"application\/json","dataschema":"com.example\/schema","subject":"larger-context","time":"2018-04-05T17:31:00Z","data":{"key":"value"},"comacme":"foo"}]' ); self::assertCount(2, $events); self::assertEquals('1.0', $events[0]->getSpecVersion()); self::assertEquals('1234-1234-1234', $events[0]->getId()); self::assertEquals('/var/data', $events[0]->getSource()); self::assertEquals('com.example.someevent', $events[0]->getType()); self::assertEquals('application/json', $events[0]->getDataContentType()); self::assertEquals('com.example/schema', $events[0]->getDataSchema()); self::assertEquals('larger-context', $events[0]->getSubject()); self::assertEquals(new DateTimeImmutable('2018-04-05T17:31:00Z'), $events[0]->getTime()); self::assertEquals(['key' => 'value'], $events[0]->getData()); self::assertEquals(['comacme' => 'foo'], $events[0]->getExtensions()); self::assertEquals('1.0', $events[1]->getSpecVersion()); self::assertEquals('1234-1234-2222', $events[1]->getId()); self::assertEquals('/var/data', $events[1]->getSource()); self::assertEquals('com.example.someevent', $events[1]->getType()); self::assertEquals('application/json', $events[1]->getDataContentType()); self::assertEquals('com.example/schema', $events[1]->getDataSchema()); self::assertEquals('larger-context', $events[1]->getSubject()); self::assertEquals(new DateTimeImmutable('2018-04-05T17:31:00Z'), $events[1]->getTime()); self::assertEquals(['key' => 'value'], $events[1]->getData()); self::assertEquals(['comacme' => 'foo'], $events[1]->getExtensions()); } public function testDeserializeBatchUnsupportedSpecVersion(): void { $formatter = JsonDeserializer::create(); $this->expectException(UnsupportedSpecVersionException::class); $formatter->deserializeBatch( '[{"specversion":"1.0","id":"1234-1234-1234","source":"\/var\/data","type":"com.example.someevent","datacontenttype":"application\/json","dataschema":"com.example\/schema","subject":"larger-context","time":"2018-04-05T17:31:00Z","data":{"key":"value"},"comacme":"foo"},{"specversion":"0.3","id":"1234-1234-2222","source":"\/var\/data","type":"com.example.someevent","datacontenttype":"application\/json","dataschema":"com.example\/schema","subject":"larger-context","time":"2018-04-05T17:31:00Z","data":{"key":"value"},"comacme":"foo"}]' ); } public function testDeserializeBatchInvalidSyntax1(): void { $formatter = JsonDeserializer::create(); $this->expectException(InvalidPayloadSyntaxException::class); $formatter->deserializeBatch( '[{"specversion":"1.0","id":"1234-1' ); } public function testDeserializeBatchInvalidSyntax2(): void { $formatter = JsonDeserializer::create(); $this->expectException(InvalidPayloadSyntaxException::class); $formatter->deserializeBatch( 'true' ); } public function testDeserializeBatchInvalidSyntax3(): void { $formatter = JsonDeserializer::create(); $this->expectException(InvalidPayloadSyntaxException::class); $formatter->deserializeBatch( '[true]' ); } public function testDeserializeBinary(): void { $formatter = JsonDeserializer::create(); $event = $formatter->deserializeBinary( '{"key":"value"}', 'application/json', [ 'specversion' => '1.0', 'id' => '1234-1234-1234', 'source' => '%2Fvar%2Fdata', 'type' => 'com.example.someevent', 'dataschema' => 'com.example%2Fschema', 'subject' => 'larger-context', 'time' => '2018-04-05T17%3A31%3A00Z', 'comacme' => 'foo', ] ); self::assertEquals('1.0', $event->getSpecVersion()); self::assertEquals('1234-1234-1234', $event->getId()); self::assertEquals('/var/data', $event->getSource()); self::assertEquals('com.example.someevent', $event->getType()); self::assertEquals('application/json', $event->getDataContentType()); self::assertEquals('com.example/schema', $event->getDataSchema()); self::assertEquals('larger-context', $event->getSubject()); self::assertEquals(new DateTimeImmutable('2018-04-05T17:31:00Z'), $event->getTime()); self::assertEquals(['key' => 'value'], $event->getData()); self::assertEquals(['comacme' => 'foo'], $event->getExtensions()); } public function testDeserializeBinaryUnsupportedSpecVersion(): void { $formatter = JsonDeserializer::create(); $this->expectException(UnsupportedSpecVersionException::class); $formatter->deserializeBinary( '{"key":"value"}', 'application/json', [ 'specversion' => '0.3', 'id' => '1234-1234-1234', 'source' => '%2Fvar%2Fdata', 'type' => 'com.example.someevent', 'dataschema' => 'com.example%2Fschema', 'subject' => 'larger-context', 'time' => '2018-04-05T17%3A31%3A00Z', 'comacme' => 'foo', ] ); } public function testDeserializeBinaryInvalidSyntax1(): void { $formatter = JsonDeserializer::create(); $this->expectException(InvalidPayloadSyntaxException::class); $formatter->deserializeBinary( '{"key":"val', 'application/json', [ 'specversion' => '0.3', 'id' => '1234-1234-1234', 'source' => '%2Fvar%2Fdata', 'type' => 'com.example.someevent', 'dataschema' => 'com.example%2Fschema', 'subject' => 'larger-context', 'time' => '2018-04-05T17%3A31%3A00Z', 'comacme' => 'foo', ] ); } } tests/Unit/Serializers/JsonSerializerTest.php000064400000020611146412130150015432 0ustar00createStub(NormalizerInterface::class)) ); self::assertInstanceOf( SerializerInterface::class, JsonSerializer::create() ); } public function testSerializeStructured(): void { /** @var CloudEventInterfaceV1|Stub $event */ $event = $this->createStub(CloudEventInterfaceV1::class); $event->method('getSpecVersion')->willReturn('1.0'); $event->method('getId')->willReturn('1234-1234-1234'); $event->method('getSource')->willReturn('/var/data'); $event->method('getType')->willReturn('com.example.someevent'); $event->method('getDataContentType')->willReturn('application/json'); $event->method('getDataSchema')->willReturn('com.example/schema'); $event->method('getSubject')->willReturn('larger-context'); $event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event->method('getData')->willReturn(['key' => 'value']); $event->method('getExtensions')->willReturn(['comacme' => 'foo']); $formatter = JsonSerializer::create(); self::assertSame( '{"specversion":"1.0","id":"1234-1234-1234","source":"\/var\/data","type":"com.example.someevent","datacontenttype":"application\/json","dataschema":"com.example\/schema","subject":"larger-context","time":"2018-04-05T17:31:00Z","comacme":"foo","data":{"key":"value"}}', $formatter->serializeStructured($event) ); } public function testSerializeStructuredUnsupportedSpecVersion(): void { /** @var CloudEventInterface|Stub $event */ $event = $this->createStub(CloudEventInterface::class); $event->method('getSpecVersion')->willReturn('0.3'); $formatter = JsonSerializer::create(); $this->expectException(UnsupportedSpecVersionException::class); $formatter->serializeStructured($event); } public function testSerializeBatch(): void { /** @var CloudEventInterfaceV1|Stub $event1 */ $event1 = $this->createStub(CloudEventInterfaceV1::class); $event1->method('getSpecVersion')->willReturn('1.0'); $event1->method('getId')->willReturn('1234-1234-1234'); $event1->method('getSource')->willReturn('/var/data'); $event1->method('getType')->willReturn('com.example.someevent'); $event1->method('getDataContentType')->willReturn('application/json'); $event1->method('getDataSchema')->willReturn('com.example/schema'); $event1->method('getSubject')->willReturn('larger-context'); $event1->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event1->method('getData')->willReturn(['key' => 'value']); $event1->method('getExtensions')->willReturn(['comacme' => 'foo']); /** @var CloudEventInterfaceV1|Stub $event2 */ $event2 = $this->createStub(CloudEventInterfaceV1::class); $event2->method('getSpecVersion')->willReturn('1.0'); $event2->method('getId')->willReturn('1234-1234-2222'); $event2->method('getSource')->willReturn('/var/data'); $event2->method('getType')->willReturn('com.example.someevent'); $event2->method('getDataContentType')->willReturn('application/json'); $event2->method('getDataSchema')->willReturn('com.example/schema'); $event2->method('getSubject')->willReturn('larger-context'); $event2->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event2->method('getData')->willReturn(['key' => 'value']); $event2->method('getExtensions')->willReturn(['comacme' => 'foo']); $formatter = JsonSerializer::create(); self::assertSame( '[{"specversion":"1.0","id":"1234-1234-1234","source":"\/var\/data","type":"com.example.someevent","datacontenttype":"application\/json","dataschema":"com.example\/schema","subject":"larger-context","time":"2018-04-05T17:31:00Z","comacme":"foo","data":{"key":"value"}},{"specversion":"1.0","id":"1234-1234-2222","source":"\/var\/data","type":"com.example.someevent","datacontenttype":"application\/json","dataschema":"com.example\/schema","subject":"larger-context","time":"2018-04-05T17:31:00Z","comacme":"foo","data":{"key":"value"}}]', $formatter->serializeBatch([$event1, $event2]) ); } public function testSerializeBatchUnsupportedSpecVersion(): void { /** @var CloudEventInterfaceV1|Stub $event1 */ $event1 = $this->createStub(CloudEventInterfaceV1::class); $event1->method('getSpecVersion')->willReturn('1.0'); $event1->method('getId')->willReturn('1234-1234-1234'); $event1->method('getSource')->willReturn('/var/data'); $event1->method('getType')->willReturn('com.example.someevent'); $event1->method('getDataContentType')->willReturn('application/json'); $event1->method('getDataSchema')->willReturn('com.example/schema'); $event1->method('getSubject')->willReturn('larger-context'); $event1->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event1->method('getData')->willReturn(['key' => 'value']); $event1->method('getExtensions')->willReturn(['comacme' => 'foo']); /** @var CloudEventInterface|Stub $event */ $event2 = $this->createStub(CloudEventInterface::class); $event2->method('getSpecVersion')->willReturn('0.3'); $formatter = JsonSerializer::create(); $this->expectException(UnsupportedSpecVersionException::class); $formatter->serializeBatch([$event1, $event2]); } public function testSerializeBinary(): void { /** @var CloudEventInterfaceV1|Stub $event */ $event = $this->createStub(CloudEventInterfaceV1::class); $event->method('getSpecVersion')->willReturn('1.0'); $event->method('getId')->willReturn('1234-1234-1234'); $event->method('getSource')->willReturn('/var/data'); $event->method('getType')->willReturn('com.example.someevent'); $event->method('getDataContentType')->willReturn('application/json'); $event->method('getDataSchema')->willReturn('com.example/schema'); $event->method('getSubject')->willReturn('larger-context'); $event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event->method('getData')->willReturn(['key' => 'value']); $event->method('getExtensions')->willReturn(['comacme' => 'foo', 'comacmen' => 123, 'comacmet' => true, 'comacmef' => false]); $formatter = JsonSerializer::create(); self::assertSame( [ 'data' => '{"key":"value"}', 'contentType' => 'application/json', 'attributes' => [ 'specversion' => '1.0', 'id' => '1234-1234-1234', 'source' => '%2Fvar%2Fdata', 'type' => 'com.example.someevent', 'dataschema' => 'com.example%2Fschema', 'subject' => 'larger-context', 'time' => '2018-04-05T17%3A31%3A00Z', 'comacme' => 'foo', 'comacmen' => '123', 'comacmet' => 'true', 'comacmef' => 'false', ], ], $formatter->serializeBinary($event) ); } public function testSerializeBinaryUnsupportedSpecVersion(): void { /** @var CloudEventInterface|Stub $event */ $event = $this->createStub(CloudEventInterface::class); $event->method('getSpecVersion')->willReturn('0.3'); $formatter = JsonSerializer::create(); $this->expectException(UnsupportedSpecVersionException::class); $formatter->serializeBinary($event); } } tests/Unit/Serializers/Normalizers/V1/DenormalizerTest.php000064400000004731146412130150017722 0ustar00 '1.0', 'id' => '1234-1234-1234', 'source' => '/var/data', 'type' => 'com.example.someevent', 'datacontenttype' => 'application/json', 'dataschema' => 'com.example/schema', 'subject' => 'larger-context', 'time' => '2018-04-05T17:31:00Z', 'data' => [ 'key' => 'value', ], 'comacme' => 'foo', ]; $formatter = new Denormalizer(); $event = $formatter->denormalize($payload); self::assertEquals('1.0', $event->getSpecVersion()); self::assertEquals('1234-1234-1234', $event->getId()); self::assertEquals('/var/data', $event->getSource()); self::assertEquals('com.example.someevent', $event->getType()); self::assertEquals('application/json', $event->getDataContentType()); self::assertEquals('com.example/schema', $event->getDataSchema()); self::assertEquals('larger-context', $event->getSubject()); self::assertEquals(new DateTimeImmutable('2018-04-05T17:31:00Z'), $event->getTime()); self::assertEquals(['key' => 'value'], $event->getData()); self::assertEquals(['comacme' => 'foo'], $event->getExtensions()); } public function testDenormalizeMissingId(): void { $payload = [ 'specversion' => '1.0', 'source' => '/var/data', 'type' => 'com.example.someevent', 'datacontenttype' => 'application/json', 'dataschema' => 'com.example/schema', 'subject' => 'larger-context', 'time' => '2018-04-05T17:31:00Z', 'data' => [ 'key' => 'value', ], 'comacme' => 'foo', ]; $formatter = new Denormalizer(); $this->expectException(MissingAttributeException::class); $formatter->denormalize($payload); } } tests/Unit/Serializers/Normalizers/V1/NormalizerTest.php000064400000011245146412130150017407 0ustar00createStub(CloudEventInterface::class); $event->method('getSpecVersion')->willReturn('1.0'); $event->method('getId')->willReturn('1234-1234-1234'); $event->method('getSource')->willReturn('/var/data'); $event->method('getType')->willReturn('com.example.someevent'); $event->method('getDataContentType')->willReturn('application/json'); $event->method('getDataSchema')->willReturn('com.example/schema'); $event->method('getSubject')->willReturn('larger-context'); $event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event->method('getData')->willReturn(['key' => 'value']); $event->method('getExtensions')->willReturn(['comacme' => 'foo']); $formatter = new Normalizer(); self::assertSame( [ 'specversion' => '1.0', 'id' => '1234-1234-1234', 'source' => '/var/data', 'type' => 'com.example.someevent', 'datacontenttype' => 'application/json', 'dataschema' => 'com.example/schema', 'subject' => 'larger-context', 'time' => '2018-04-05T17:31:00Z', 'comacme' => 'foo', 'data' => [ 'key' => 'value', ], ], $formatter->normalize($event, false) ); } public function testNormalizerWithUnsetAttributes(): void { /** @var CloudEventInterface|Stub $event */ $event = $this->createStub(CloudEventInterface::class); $event->method('getSpecVersion')->willReturn('1.0'); $event->method('getId')->willReturn('1234-1234-1234'); $event->method('getSource')->willReturn('/var/data'); $event->method('getType')->willReturn('com.example.someevent'); $event->method('getSubject')->willReturn('larger-context'); $event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00Z')); $event->method('getExtensions')->willReturn([]); $formatter = new Normalizer(); self::assertSame( [ 'specversion' => '1.0', 'id' => '1234-1234-1234', 'source' => '/var/data', 'type' => 'com.example.someevent', 'subject' => 'larger-context', 'time' => '2018-04-05T17:31:00Z', ], $formatter->normalize($event, false) ); } public function testNormalizerWithSubsecondPrecisionConfiguration(): void { /** @var CloudEventInterface|Stub $event */ $event = $this->createStub(CloudEventInterface::class); $event->method('getSpecVersion')->willReturn('1.0'); $event->method('getId')->willReturn('1234-1234-1234'); $event->method('getSource')->willReturn('/var/data'); $event->method('getType')->willReturn('com.example.someevent'); $event->method('getDataContentType')->willReturn('application/json'); $event->method('getDataSchema')->willReturn('com.example/schema'); $event->method('getSubject')->willReturn('larger-context'); $event->method('getTime')->willReturn(new DateTimeImmutable('2018-04-05T17:31:00.123456Z')); $event->method('getData')->willReturn(['key' => 'value']); $event->method('getExtensions')->willReturn(['comacme' => 'foo']); $formatter = new Normalizer(['subsecondPrecision' => 3]); self::assertSame( [ 'specversion' => '1.0', 'id' => '1234-1234-1234', 'source' => '/var/data', 'type' => 'com.example.someevent', 'datacontenttype' => 'application/json', 'dataschema' => 'com.example/schema', 'subject' => 'larger-context', 'time' => '2018-04-05T17:31:00.123Z', 'comacme' => 'foo', 'data' => [ 'key' => 'value', ], ], $formatter->normalize($event, false) ); } } tests/Unit/Utilities/DataFormatterTest.php000064400000002734146412130150014711 0ustar00 ['foo' => 'bar']], DataFormatter::encode(['foo' => 'bar'], false)); self::assertEquals(['foo' => 'bar'], DataFormatter::decode(['data' => ['foo' => 'bar']])); } public function testEncodeAndDecodeBinary(): void { $data = random_bytes(1024); $encoded = base64_encode($data); self::assertEquals(['data_base64' => $encoded], DataFormatter::encode($data, false)); self::assertEquals($data, DataFormatter::decode(['data_base64' => $encoded])); self::assertEquals(['data' => $data], DataFormatter::encode($data, true)); self::assertEquals($data, DataFormatter::decode(['data' => $data])); } public function testInvalidBase64Decode1(): void { $this->expectException(ValueError::class); DataFormatter::decode(['data_base64' => 123]); } public function testInvalidBase64Decode2(): void { $this->expectException(ValueError::class); DataFormatter::decode(['data_base64' => 'a']); } public function testEncodeAndDecodeEmpty(): void { self::assertEquals([], DataFormatter::encode(null, false)); self::assertEquals(null, DataFormatter::decode([])); } } tests/Unit/Utilities/TimeFormatterTest.php000064400000012610146412130150014730 0ustar00expectException(ValueError::class); $this->expectExceptionMessage( 'CloudEvents\\Utilities\\TimeFormatter::decode(): Argument #1 ($time) is not a valid RFC3339 timestamp' ); TimeFormatter::decode($input); } } tests/Unit/V1/CloudEventImmutableTest.php000064400000014723146412130150014400 0ustar00getEvent()->getSpecVersion()); } public function testGetSetId(): void { $event = $this->getEvent(); self::assertEquals('A234-1234-1234', $event->getId()); $event = $event->withId('new-id'); self::assertEquals('new-id', $event->getId()); } public function testGetSetSource(): void { $event = $this->getEvent(); self::assertEquals('https://github.com/cloudevents/spec/pull', $event->getSource()); $event = $event->withSource('new-source'); self::assertEquals('new-source', $event->getSource()); } public function testGetSetType(): void { $event = $this->getEvent(); self::assertEquals('com.github.pull_request.opened', $event->getType()); $event = $event->withType('new-type'); self::assertEquals('new-type', $event->getType()); } public function testGetSetDataContentType(): void { $event = $this->getEvent(); self::assertEquals('text/xml', $event->getDataContentType()); $event = $event->withDataContentType('application/json'); self::assertEquals('application/json', $event->getDataContentType()); } public function testGetSetDataSchema(): void { $event = $this->getEvent(); self::assertEquals(null, $event->getDataSchema()); $event = $event->withDataSchema('new-schema'); self::assertEquals('new-schema', $event->getDataSchema()); } public function testGetSetSubject(): void { $event = $this->getEvent(); self::assertEquals('123', $event->getSubject()); $event = $event->withSubject('new-subject'); self::assertEquals('new-subject', $event->getSubject()); } public function testGetSetTime(): void { $event = $this->getEvent(); self::assertEquals(new \DateTimeImmutable('2018-04-05T17:31:00Z'), $event->getTime()); $event = $event->withTime(new \DateTimeImmutable('2021-01-19T17:31:00Z')); self::assertEquals(new \DateTimeImmutable('2021-01-19T17:31:00Z'), $event->getTime()); } public function testGetSetData(): void { $event = $this->getEvent(); self::assertEquals('', $event->getData()); $event = $event->withData('{"key": "value"}'); self::assertEquals('{"key": "value"}', $event->getData()); } public function testCannotSetEmptyExtensionValueType(): void { $event = $this->getEvent(); $this->expectException(ValueError::class); $event->withExtension('', '1.1'); } public function testCannotSetInvalidExtensionAttribute(): void { $event = $this->getEvent(); $this->expectException(ValueError::class); $event->withExtension('comBAD', '1.1'); } public function testCannotSetReservedExtensionAttribute(): void { $event = $this->getEvent(); $this->expectException(ValueError::class); $event->withExtension('specversion', '1.1'); } public function testCannotSetInvalidExtensionValueType(): void { $event = $this->getEvent(); $this->expectException(TypeError::class); $event->withExtension('comacme', 1.1); } public function testCanSetAndUnsetExtensions(): void { $event = $this->getEvent(); self::assertEquals([], $event->getExtensions()); self::assertNull($event->getExtension('comacme')); self::assertNull($event->getExtension('comacme2')); $event = $event->withExtension('comacme', 'foo'); self::assertEquals('foo', $event->getExtension('comacme')); self::assertEquals(['comacme' => 'foo'], $event->getExtensions()); $event = $event->withExtension('comacme2', 123); self::assertEquals(123, $event->getExtension('comacme2')); self::assertEquals(['comacme' => 'foo', 'comacme2' => 123], $event->getExtensions()); $event = $event->withExtension('comacme', null); self::assertNull($event->getExtension('comacme')); self::assertEquals(['comacme2' => 123], $event->getExtensions()); $event = $event->withExtensions(['comacme' => 12345, 'comacme2' => null]); self::assertEquals(12345, $event->getExtension('comacme')); self::assertNull($event->getExtension('comacme2')); self::assertEquals(['comacme' => 12345], $event->getExtensions()); } public function testCanCreateFromInterface(): void { $original = $this->getEvent(); $event = CloudEventImmutable::createFromInterface($original); self::assertInstanceOf(CloudEventImmutable::class, $original); self::assertInstanceOf(CloudEventInterface::class, $original); self::assertInstanceOf(CloudEventInterfaceV1::class, $original); self::assertSame($original->getId(), $event->getId()); self::assertSame($original->getSource(), $event->getSource()); self::assertSame($original->getType(), $event->getType()); self::assertSame($original->getData(), $event->getData()); self::assertSame($original->getDataContentType(), $event->getDataContentType()); self::assertSame($original->getDataSchema(), $event->getDataSchema()); self::assertSame($original->getSubject(), $event->getSubject()); self::assertSame($original->getTime(), $event->getTime()); self::assertSame($original->getExtensions(), $event->getExtensions()); } private function getEvent(): CloudEventImmutable { return new CloudEventImmutable( 'A234-1234-1234', 'https://github.com/cloudevents/spec/pull', 'com.github.pull_request.opened', '', 'text/xml', null, '123', new \DateTimeImmutable('2018-04-05T17:31:00Z') ); } } tests/Unit/V1/CloudEventTest.php000064400000014573146412130150012543 0ustar00getEvent()->getSpecVersion()); } public function testGetSetId(): void { $event = $this->getEvent(); self::assertEquals('A234-1234-1234', $event->getId()); $event = $event->setId('new-id'); self::assertEquals('new-id', $event->getId()); } public function testGetSetSource(): void { $event = $this->getEvent(); self::assertEquals('https://github.com/cloudevents/spec/pull', $event->getSource()); $event = $event->setSource('new-source'); self::assertEquals('new-source', $event->getSource()); } public function testGetSetType(): void { $event = $this->getEvent(); self::assertEquals('com.github.pull_request.opened', $event->getType()); $event = $event->setType('new-type'); self::assertEquals('new-type', $event->getType()); } public function testGetSetDataContentType(): void { $event = $this->getEvent(); self::assertEquals('text/xml', $event->getDataContentType()); $event = $event->setDataContentType('application/json'); self::assertEquals('application/json', $event->getDataContentType()); } public function testGetSetDataSchema(): void { $event = $this->getEvent(); self::assertEquals(null, $event->getDataSchema()); $event = $event->setDataSchema('new-schema'); self::assertEquals('new-schema', $event->getDataSchema()); } public function testGetSetSubject(): void { $event = $this->getEvent(); self::assertEquals('123', $event->getSubject()); $event = $event->setSubject('new-subject'); self::assertEquals('new-subject', $event->getSubject()); } public function testGetSetTime(): void { $event = $this->getEvent(); self::assertEquals(new \DateTimeImmutable('2018-04-05T17:31:00Z'), $event->getTime()); $event = $event->setTime(new \DateTimeImmutable('2021-01-19T17:31:00Z')); self::assertEquals(new \DateTimeImmutable('2021-01-19T17:31:00Z'), $event->getTime()); } public function testGetSetData(): void { $event = $this->getEvent(); self::assertEquals('', $event->getData()); $event = $event->setData('{"key": "value"}'); self::assertEquals('{"key": "value"}', $event->getData()); } public function testCannotSetEmptyExtensionValueType(): void { $event = $this->getEvent(); $this->expectException(ValueError::class); $event->setExtension('', '1.1'); } public function testCannotSetInvalidExtensionAttribute(): void { $event = $this->getEvent(); $this->expectException(ValueError::class); $event->setExtension('comBAD', '1.1'); } public function testCannotSetReservedExtensionAttribute(): void { $event = $this->getEvent(); $this->expectException(ValueError::class); $event->setExtension('specversion', '1.1'); } public function testCannotSetInvalidExtensionValueType(): void { $event = $this->getEvent(); $this->expectException(TypeError::class); $event->setExtension('comacme', 1.1); } public function testCanSetAndUnsetExtensions(): void { $event = $this->getEvent(); self::assertEquals([], $event->getExtensions()); self::assertNull($event->getExtension('comacme')); self::assertNull($event->getExtension('comacme2')); $event = $event->setExtension('comacme', 'foo'); self::assertEquals('foo', $event->getExtension('comacme')); self::assertEquals(['comacme' => 'foo'], $event->getExtensions()); $event = $event->setExtension('comacme2', 123); self::assertEquals(123, $event->getExtension('comacme2')); self::assertEquals(['comacme' => 'foo', 'comacme2' => 123], $event->getExtensions()); $event = $event->setExtension('comacme', null); self::assertNull($event->getExtension('comacme')); self::assertEquals(['comacme2' => 123], $event->getExtensions()); $event = $event->setExtensions(['comacme' => 12345, 'comacme2' => null]); self::assertEquals(12345, $event->getExtension('comacme')); self::assertNull($event->getExtension('comacme2')); self::assertEquals(['comacme' => 12345], $event->getExtensions()); } public function testCanCreateFromInterface(): void { $original = $this->getEvent(); $event = CloudEvent::createFromInterface($original); self::assertInstanceOf(CloudEvent::class, $original); self::assertInstanceOf(CloudEventInterface::class, $original); self::assertInstanceOf(CloudEventInterfaceV1::class, $original); self::assertSame($original->getId(), $event->getId()); self::assertSame($original->getSource(), $event->getSource()); self::assertSame($original->getType(), $event->getType()); self::assertSame($original->getData(), $event->getData()); self::assertSame($original->getDataContentType(), $event->getDataContentType()); self::assertSame($original->getDataSchema(), $event->getDataSchema()); self::assertSame($original->getSubject(), $event->getSubject()); self::assertSame($original->getTime(), $event->getTime()); self::assertSame($original->getExtensions(), $event->getExtensions()); } private function getEvent(): CloudEvent { return new CloudEvent( 'A234-1234-1234', 'https://github.com/cloudevents/spec/pull', 'com.github.pull_request.opened', '', 'text/xml', null, '123', new \DateTimeImmutable('2018-04-05T17:31:00Z') ); } }