.github/workflows/static.yml000064400000002527146412176500012167 0ustar00name: Static Analysis on: push: branches: [ main ] pull_request: jobs: codesniffer: name: PHP CodeSniffer runs-on: ubuntu-20.04 steps: - name: Checkout code uses: actions/checkout@v2 - 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@v1 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@v2 - 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@v1 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.yml000064400000001553146412176500012040 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'] steps: - name: Checkout Code uses: actions/checkout@v2 - 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@v1 with: timeout_minutes: 5 max_attempts: 5 command: composer update --no-interaction --no-progress - name: Execute PHPUnit run: vendor/bin/phpunit .gitignore000064400000000117146412176500006541 0ustar00.phpunit.cache composer.lock phpcs.xml phpunit.xml psalm.xml coverage vendor LICENSE000064400000026135146412176500005566 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. README.md000064400000007677146412176500006052 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. composer.json000064400000006262146412176500007302 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.1.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" ] }, "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.0-dev" } }, "minimum-stability": "dev", "prefer-stable": true } hack/7.4.Dockerfile000064400000002210146412176500007754 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.Dockerfile000064400000001734146412176500007763 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.Dockerfile000064400000001734146412176500007764 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.Dockerfile000064400000001734146412176500007765 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/install-composer000075500000000735146412176500010706 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.dist000064400000052267146412176500007367 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.dist000064400000001104146412176500007721 0ustar00 tests/Unit src psalm.xml.dist000064400000000547146412176500007360 0ustar00 src/CloudEventInterface.php000064400000000211146412176500011735 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.php000064400000003510146412176500012703 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.php000064400000010564146412176500011434 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.php000064400000001314146412176500013246 0ustar00 */ public function unmarshal(MessageInterface $message): array; } src/Serializers/DeserializerInterface.php000064400000002144146412176500014612 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.php000064400000006010146412176500013617 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.php000064400000005724146412176500013321 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.php000064400000002621146412176500015575 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.php000064400000001126146412176500017415 0ustar00 $payload * * @throws InvalidAttributeException * @throws UnsupportedSpecVersionException * @throws MissingAttributeException */ public function denormalize(array $payload): CloudEventInterface; } src/Serializers/Normalizers/V1/Normalizer.php000064400000001104146412176500015257 0ustar00 */ public function normalize(CloudEventInterface $cloudEvent, bool $rawData): array { return array_merge( AttributeConverter::toArray($cloudEvent), DataFormatter::encode($cloudEvent->getData(), $rawData) ); } } src/Serializers/Normalizers/V1/NormalizerInterface.php000064400000000451146412176500017104 0ustar00 */ public function normalize(CloudEventInterface $cloudEvent, bool $rawData): array; } src/Serializers/SerializerInterface.php000064400000001405146412176500014300 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.php000064400000005462146412176500013667 0ustar00 */ public static function toArray(CloudEventInterface $cloudEvent): 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()), ], 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.php000064400000002742146412176500012567 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.php000064400000002377146412176500012620 0ustar00setTimezone(new DateTimeZone(self::TIME_ZONE))->format(self::TIME_FORMAT); } public static function decode(?string $time): ?DateTimeImmutable { if ($time === null) { return null; } /** @psalm-suppress UndefinedFunction */ $decoded = DateTimeImmutable::createFromFormat( \str_contains($time, '.') ? self::RFC3339_EXTENDED_FORMAT : self::RFC3339_FORMAT, \strtoupper($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; } } src/V1/CloudEvent.php000064400000000772146412176500010416 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.php000064400000001432146412176500012231 0ustar00 */ public function getExtensions(): array; /** * @return bool|int|string|null */ public function getExtension(string $attribute); } src/V1/CloudEventTrait.php000064400000015541146412176500011422 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.php000064400000027176146412176500013232 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.php000064400000031051146412176500013560 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.php000064400000024163146412176500015762 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.php000064400000020611146412176500015443 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.php000064400000004731146412176500017733 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.php000064400000006122146412176500017416 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) ); } } tests/Unit/Utilities/DataFormatterTest.php000064400000002734146412176500014722 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.php000064400000006545146412176500014753 0ustar00expectException(ValueError::class); $this->expectExceptionMessage( 'CloudEvents\\Utilities\\TimeFormatter::decode(): Argument #1 ($time) is not a valid RFC3339 timestamp' ); TimeFormatter::decode('2018asdsdsafd'); } } tests/Unit/V1/CloudEventImmutableTest.php000064400000014723146412176500014411 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.php000064400000014573146412176500012554 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') ); } }