diff --git a/examples/calc-encode.php b/examples/calc-encode-client.php similarity index 100% rename from examples/calc-encode.php rename to examples/calc-encode-client.php diff --git a/examples/calc-http.php b/examples/calc-http-client.php similarity index 100% rename from examples/calc-http.php rename to examples/calc-http-client.php diff --git a/examples/calc-http-server.php b/examples/calc-http-server.php new file mode 100644 index 0000000..083ba7c --- /dev/null +++ b/examples/calc-http-server.php @@ -0,0 +1,81 @@ +addClassMap('http://tempuri.org/', 'Add', Add::class) + ->addClassMap('http://tempuri.org/', 'AddResponse', AddResponse::class); +$metadataProvider = new Wsdl1MetadataProvider($wsdl, ServiceSelectionCriteria::defaults()); +$metadata = $metadataProvider->getMetadata(); + +// The soap action can be detected from a PSR-7 request headers by using: +// https://github.com/php-soap/psr18-transport/blob/main/src/HttpBinding/SoapActionDetector.php +$soapAction = 'http://tempuri.org/Add'; + +$methodContext = new MethodContext( + $method = $metadata->getMethods()->fetchBySoapAction($soapAction), + $metadata, + $registry, + $wsdl->namespaces, +); + +$request = << + + + 1 + 2 + + + +EOXML; + +$requestEncoder = new RequestEncoder(); +$requestIso = $requestEncoder->iso($methodContext); +$arguments = $requestIso->from($request); + +var_dump($arguments); + +final class Add +{ + public int $a; + public int $b; +} +final class AddResponse +{ + public function __construct( + public int $AddResult, + ) { + } +} + +$myCalculator = new class() { + public function Add(Add $add): AddResponse + { + return new AddResponse($add->a + $add->b); + } +}; + + +$result = $myCalculator->{$method->getName()}(...$arguments); + +var_dump($result); + + +$responseEncoder = new ResponseEncoder(); +$responseIso = $responseEncoder->iso($methodContext); +$response = $responseIso->to([$result]); + +var_dump($response); diff --git a/examples/calc-xsd-encoder.php b/examples/calc-xsd-encoder.php new file mode 100644 index 0000000..c0354d4 --- /dev/null +++ b/examples/calc-xsd-encoder.php @@ -0,0 +1,47 @@ +readNode($xsd->locateDocumentElement(), $file); +$namespaces = NamespacesParser::tryParse($xsd); +$types = (new SchemaToTypesConverter())( + $schema, + TypesConverterContext::default($namespaces) +); +$metadata = new InMemoryMetadata($types, new MethodCollection()); + +// Create an encoder for the Add type context: +$registry = EncoderRegistry::default(); +$encoder = $registry->detectEncoderForContext( + $context = new Context( + $types->fetchFirstByName('Add')->getXsdType(), + $metadata, + $registry, + $namespaces, + ) +); + +// Decode + Encode the Add type: +var_dump($data = $encoder->iso($context)->from( + << + 1 + 2 + +EOXML +)); + +var_dump($encoder->iso($context)->to($data)); diff --git a/examples/calc.xsd b/examples/calc.xsd new file mode 100644 index 0000000..17e1fcc --- /dev/null +++ b/examples/calc.xsd @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Decoder.php b/src/Decoder.php index 9f9ca04..84f28f2 100644 --- a/src/Decoder.php +++ b/src/Decoder.php @@ -3,16 +3,13 @@ namespace Soap\Encoding; -use Soap\Encoding\Encoder\Context; -use Soap\Encoding\Xml\Reader\OperationReader; +use Soap\Encoding\Encoder\Method\MethodContext; +use Soap\Encoding\Encoder\Method\ResponseEncoder; use Soap\Engine\Decoder as SoapDecoder; use Soap\Engine\HttpBinding\SoapResponse; use Soap\Engine\Metadata\Metadata; -use Soap\WsdlReader\Model\Definitions\BindingUse; use Soap\WsdlReader\Model\Definitions\Namespaces; use function count; -use function Psl\invariant; -use function Psl\Vec\map; final class Decoder implements SoapDecoder { @@ -29,27 +26,17 @@ public function __construct( public function decode(string $method, SoapResponse $response): mixed { $methodInfo = $this->metadata->getMethods()->fetchByName($method); - $meta = $methodInfo->getMeta(); - $bindingUse = $meta->outputBindingUsage()->map(BindingUse::from(...))->unwrapOr(BindingUse::LITERAL); + $methodContext = new MethodContext($methodInfo, $this->metadata, $this->registry, $this->namespaces); + $iso = (new ResponseEncoder())->iso($methodContext); - $returnType = $methodInfo->getReturnType(); - $context = new Context($returnType, $this->metadata, $this->registry, $this->namespaces, $bindingUse); - $decoder = $this->registry->detectEncoderForContext($context); - $iso = $decoder->iso($context); - - // The SoapResponse only contains the payload of the response (with no headers). - // It can be parsed directly as XML. $payload = $response->getPayload(); - invariant($payload !== '', 'Expected a non-empty response payload. Received an empty HTTP response'); - $parts = (new OperationReader($meta))($payload)->elements(); + /** @var list $parts */ + $parts = $iso->from($payload); return match(count($parts)) { 0 => null, - 1 => $iso->from($parts[0]), - default => map( - $parts, - static fn (string $part): mixed => $iso->from($part) - ), + 1 => $parts[0], + default => $parts, }; } } diff --git a/src/Encoder.php b/src/Encoder.php index 7562be5..149f60a 100644 --- a/src/Encoder.php +++ b/src/Encoder.php @@ -3,18 +3,14 @@ namespace Soap\Encoding; -use Soap\Encoding\Encoder\Context; -use Soap\Encoding\Xml\Writer\OperationBuilder; -use Soap\Encoding\Xml\Writer\ParameterBuilder; -use Soap\Encoding\Xml\Writer\SoapEnvelopeWriter; +use Soap\Encoding\Encoder\Method\MethodContext; +use Soap\Encoding\Encoder\Method\RequestEncoder; use Soap\Engine\Encoder as SoapEncoder; use Soap\Engine\HttpBinding\SoapRequest; use Soap\Engine\Metadata\Metadata; -use Soap\WsdlReader\Model\Definitions\BindingUse; -use Soap\WsdlReader\Model\Definitions\EncodingStyle; use Soap\WsdlReader\Model\Definitions\Namespaces; use Soap\WsdlReader\Model\Definitions\SoapVersion; -use function VeeWee\Reflecta\Lens\index; +use function Psl\Type\mixed_vec; final class Encoder implements SoapEncoder { @@ -29,26 +25,12 @@ public function encode(string $method, array $arguments): SoapRequest { $methodInfo = $this->metadata->getMethods()->fetchByName($method); $meta = $methodInfo->getMeta(); - + $methodContext = new MethodContext($methodInfo, $this->metadata, $this->registry, $this->namespaces); $soapVersion = $meta->soapVersion()->map(SoapVersion::from(...))->unwrapOr(SoapVersion::SOAP_12); - $bindingUse = $meta->inputBindingUsage()->map(BindingUse::from(...))->unwrapOr(BindingUse::LITERAL); - $encodingStyle = $meta->inputEncodingStyle()->map(EncodingStyle::from(...)); - - $requestParams = []; - foreach ($methodInfo->getParameters() as $index => $parameter) { - $type = $parameter->getType(); - $context = new Context($type, $this->metadata, $this->registry, $this->namespaces, $bindingUse); - /** @var mixed $value */ - $value = index($index)->get($arguments); - - $requestParams[] = (new ParameterBuilder($meta, $context, $value))(...); - } - - $operation = new OperationBuilder($meta, $this->namespaces, $requestParams); - $writeEnvelope = new SoapEnvelopeWriter($soapVersion, $bindingUse, $encodingStyle, $operation(...)); + $iso = (new RequestEncoder())->iso($methodContext); return new SoapRequest( - $writeEnvelope() . PHP_EOL, + $iso->to(mixed_vec()->assert($arguments)), $meta->location()->unwrap(), $meta->action()->unwrap(), match($soapVersion) { diff --git a/src/Encoder/Method/MethodContext.php b/src/Encoder/Method/MethodContext.php new file mode 100644 index 0000000..43e4c75 --- /dev/null +++ b/src/Encoder/Method/MethodContext.php @@ -0,0 +1,45 @@ +metadata, + $this->registry, + $this->namespaces, + $this->bindingUse + ); + } + + public function withBindingUse(BindingUse $bindingUse): self + { + return new self( + $this->method, + $this->metadata, + $this->registry, + $this->namespaces, + $bindingUse + ); + } +} diff --git a/src/Encoder/Method/RequestEncoder.php b/src/Encoder/Method/RequestEncoder.php new file mode 100644 index 0000000..dc9fad8 --- /dev/null +++ b/src/Encoder/Method/RequestEncoder.php @@ -0,0 +1,111 @@ +, non-empty-string> + */ +final class RequestEncoder implements SoapMethodEncoder +{ + /** + * @return Iso, non-empty-string> + */ + public function iso(MethodContext $context): Iso + { + $meta = $context->method->getMeta(); + $context = $context->withBindingUse( + $meta->inputBindingUsage()->map(BindingUse::from(...))->unwrapOr(BindingUse::LITERAL) + ); + + /** @var Iso, non-empty-string> */ + return new Iso( + /** + * @param list $arguments + * @return non-empty-string + */ + fn (array $arguments): string => $this->encode($context, $arguments), + /** + * @param non-empty-string $xml + * @return list + */ + fn (string $xml): array => $this->decode($context, $xml), + ); + } + + + /** + * @param list $arguments + * @return non-empty-string + */ + private function encode(MethodContext $context, array $arguments): string + { + $method = $context->method; + $meta = $method->getMeta(); + $soapVersion = $meta->soapVersion()->map(SoapVersion::from(...))->unwrapOr(SoapVersion::SOAP_12); + $encodingStyle = $meta->inputEncodingStyle()->map(EncodingStyle::from(...)); + + $requestParams = map_with_key( + $method->getParameters(), + /** + * @return Closure(XMLWriter): Generator + */ + static function (int $index, Parameter $parameter) use ($meta, $context, $arguments): Closure { + $type = $parameter->getType(); + $typeContext = $context->createXmlEncoderContextForType($type); + /** @var mixed $value */ + $value = index($index)->get($arguments); + + return (new ParameterBuilder($meta, $typeContext, $value))(...); + } + ); + + $operation = new OperationBuilder($meta, $context->namespaces, $requestParams); + $writeEnvelope = new SoapEnvelopeWriter($soapVersion, $context->bindingUse, $encodingStyle, $operation(...)); + + return $writeEnvelope() . PHP_EOL; + } + + + /** + * @param non-empty-string $xml + * @return list + */ + private function decode(MethodContext $context, string $xml): array + { + $method = $context->method; + $meta = $method->getMeta(); + $parts = (new OperationReader($meta))($xml)->elements(); + + return map_with_key( + $method->getParameters(), + static function (int $index, Parameter $parameter) use ($context, $parts) : mixed { + $type = $parameter->getType(); + $typeContext = $context->createXmlEncoderContextForType($type); + $decoder = $context->registry->detectEncoderForContext($typeContext); + $iso = $decoder->iso($typeContext); + + /** @var Element $value */ + $value = index($index)->get($parts); + + /** @var mixed */ + return $iso->from($value); + } + ); + } +} diff --git a/src/Encoder/Method/ResponseEncoder.php b/src/Encoder/Method/ResponseEncoder.php new file mode 100644 index 0000000..80615e5 --- /dev/null +++ b/src/Encoder/Method/ResponseEncoder.php @@ -0,0 +1,106 @@ + + */ +final class ResponseEncoder implements SoapMethodEncoder +{ + /** + * @return Iso + */ + public function iso(MethodContext $context): Iso + { + $meta = $context->method->getMeta(); + $context = $context->withBindingUse( + $meta->outputBindingUsage()->map(BindingUse::from(...))->unwrapOr(BindingUse::LITERAL) + ); + + /** @var Iso, string> */ + return new Iso( + /** + * @param list $arguments + */ + fn (array $arguments): string => $this->encode($context, $arguments), + /** + * @return list + */ + fn (string $xml): array => $this->decode($context, $xml), + ); + } + + /** + * @param list $arguments + */ + private function encode(MethodContext $context, array $arguments): string + { + $method = $context->method; + $meta = $method->getMeta(); + + if ($meta->isOneWay()->unwrapOr(false)) { + return ''; + } + + $soapVersion = $meta->soapVersion()->map(SoapVersion::from(...))->unwrapOr(SoapVersion::SOAP_12); + $encodingStyle = $meta->outputEncodingStyle()->map(EncodingStyle::from(...)); + + $returnType = $method->getReturnType(); + $typeContext = $context->createXmlEncoderContextForType($returnType); + + $responseParams = map( + $arguments, + /** + * @return Closure(XMLWriter): Generator + */ + static fn (mixed $argument): Closure => (new ParameterBuilder($meta, $typeContext, $argument))(...), + ); + + $operation = new OperationBuilder($meta, $context->namespaces, $responseParams); + $writeEnvelope = new SoapEnvelopeWriter($soapVersion, $context->bindingUse, $encodingStyle, $operation(...)); + + return $writeEnvelope() . PHP_EOL; + } + + /** + * @return list + */ + private function decode(MethodContext $context, string $xml): array + { + $method = $context->method; + $meta = $method->getMeta(); + + if ($meta->isOneWay()->unwrapOr(false)) { + return []; + } + + $returnType = $method->getReturnType(); + $typeContext = $context->createXmlEncoderContextForType($returnType); + $decoder = $context->registry->detectEncoderForContext($typeContext); + $iso = $decoder->iso($typeContext); + + // The SoapResponse only contains the payload of the response (with no headers). + // It can be parsed directly as XML. + invariant($xml !== '', 'Expected a non-empty response payload. Received an empty HTTP response'); + $parts = (new OperationReader($meta))($xml)->elements(); + + return map( + $parts, + $iso->from(...) + ); + } +} diff --git a/src/Encoder/Method/SoapMethodEncoder.php b/src/Encoder/Method/SoapMethodEncoder.php new file mode 100644 index 0000000..52007b1 --- /dev/null +++ b/src/Encoder/Method/SoapMethodEncoder.php @@ -0,0 +1,17 @@ + + */ + public function iso(MethodContext $context): Iso; +} diff --git a/src/TypeInference/XsiTypeDetector.php b/src/TypeInference/XsiTypeDetector.php index 14a3e8a..41abcee 100644 --- a/src/TypeInference/XsiTypeDetector.php +++ b/src/TypeInference/XsiTypeDetector.php @@ -98,7 +98,7 @@ private static function detectFromContext(Context $context): Option sprintf( '%s:%s', $context->namespaces->lookupNameFromNamespace($type->getXmlNamespace())->unwrap(), - $type->getName() + $type->getXmlTypeName() ) ); } diff --git a/tests/Unit/Encoder/Method/AbstractMethodEncoderTests.php b/tests/Unit/Encoder/Method/AbstractMethodEncoderTests.php new file mode 100644 index 0000000..a2febed --- /dev/null +++ b/tests/Unit/Encoder/Method/AbstractMethodEncoderTests.php @@ -0,0 +1,67 @@ + + */ + abstract public static function provideIsomorphicCases(): iterable; + + /** + * + * @dataProvider provideIsomorphicCases + */ + public function test_it_can_decode_from_xml(SoapMethodEncoder $encoder, MethodContext $context, string $xml, mixed $data): void + { + $iso = $encoder->iso($context); + $actual = $iso->from($xml); + + static::assertEquals($data, $actual); + } + + /** + * + * @dataProvider provideIsomorphicCases + */ + public function test_it_can_encode_into_xml(SoapMethodEncoder $encoder, MethodContext $context, string $xml, mixed $data): void + { + $iso = $encoder->iso($context); + $actual = $iso->to($data); + + static::assertSame($xml, $actual); + } + + protected static function createParameter(string $name): Parameter + { + return new Parameter( + $name, + self::createType($name), + ); + } + + protected static function createType(string $name): XsdType + { + return XsdType::guess('string') + ->withXmlTypeName('string') + ->withXmlNamespace(Xmlns::xsd()->value()) + ->withXmlNamespaceName('xsd') + ->withXmlTargetNodeName($name) + ->withXmlTargetNamespace(Xmlns::xsd()->value()) + ->withXmlTargetNamespaceName('xsd') + ->withMeta( + static fn (): TypeMeta => (new TypeMeta()) + ->withIsElement(true) + ->withIsSimple(true) + ); + } +} diff --git a/tests/Unit/Encoder/Method/RequestEncoderTest.php b/tests/Unit/Encoder/Method/RequestEncoderTest.php new file mode 100644 index 0000000..5f43b33 --- /dev/null +++ b/tests/Unit/Encoder/Method/RequestEncoderTest.php @@ -0,0 +1,447 @@ + new RequestEncoder(), + ]; + + yield 'soap11-document-literal-no-args' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap11-document-encoded-no-args' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap11-rpc-literal-no-args' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap11-rpc-encoded-no-args' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap12-document-literal-no-args' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap12-document-encoded-no-args' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap12-literal-rpc-no-args' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap12-encoded-rpc-no-args' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap11-document-literal-single-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(self::createParameter('arg1')), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap11-document-encoded-single-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(self::createParameter('arg1')), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap11-rpc-literal-single-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(self::createParameter('arg1')), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap11-rpc-encoded-single-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(self::createParameter('arg1')), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap12-document-literal-single-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(self::createParameter('arg1')), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap12-document-encoded-single-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(self::createParameter('arg1')), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap12-rpc-literal-single-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(self::createParameter('arg1')), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap12-rpc-encoded-single-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection(self::createParameter('arg1')), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap11-document-literal-multi-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection( + self::createParameter('arg1'), + self::createParameter('arg2'), + ), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap11-document-encoded-multi-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection( + self::createParameter('arg1'), + self::createParameter('arg2'), + ), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap11-rpc-literal-multi-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection( + self::createParameter('arg1'), + self::createParameter('arg2'), + ), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap11-rpc-encoded-multi-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection( + self::createParameter('arg1'), + self::createParameter('arg2'), + ), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap12-document-literal-multi-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection( + self::createParameter('arg1'), + self::createParameter('arg2'), + ), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap12-document-encoded-multi-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection( + self::createParameter('arg1'), + self::createParameter('arg2'), + ), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap12-rpc-literal-multi-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection( + self::createParameter('arg1'), + self::createParameter('arg2'), + ), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap12-rpc-encoded-multi-arg' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + new ParameterCollection( + self::createParameter('arg1'), + self::createParameter('arg2'), + ), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withInputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + } + + private static function createMethodContext( + ParameterCollection $params, + MethodMeta $meta + ): MethodContext { + $method = (new Method( + 'foo', + $params, + XsdType::guess('string'), + ))->withMeta( + static fn (): MethodMeta => $meta + ->withTargetNamespace('uri:tns') + ->withOperationName('foo') + ); + + return new MethodContext( + $method, + new InMemoryMetadata(new TypeCollection(), new MethodCollection($method)), + EncoderRegistry::default(), + new Namespaces( + [ + 'tns' => 'uri:tns', + 'xsd' => Xmlns::xsd()->value(), + ], + [ + 'uri:tns' => 'tns', + Xmlns::xsd()->value() => 'xsd', + ], + ) + ); + } +} diff --git a/tests/Unit/Encoder/Method/ResponseEncoderTest.php b/tests/Unit/Encoder/Method/ResponseEncoderTest.php new file mode 100644 index 0000000..46b194a --- /dev/null +++ b/tests/Unit/Encoder/Method/ResponseEncoderTest.php @@ -0,0 +1,434 @@ + new ResponseEncoder(), + ]; + + yield 'one-way' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withIsOneWay(true) + ), + 'xml' => '', + 'data' => [], + ]; + + yield 'soap11-document-literal-no-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap11-document-encoded-no-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap11-rpc-literal-no-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap11-rpc-encoded-no-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap12-document-literal-no-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap12-document-encoded-no-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap12-rpc-literal-no-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap12-rpc-encoded-no-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => << + + EOXML, + 'data' => [], + ]; + yield 'soap11-document-literal-single-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap11-document-encoded-single-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap11-rpc-literal-single-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap11-rpc-encoded-single-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap12-document-literal-single-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap12-document-encoded-single-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap12-rpc-literal-single-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap12-rpc-encoded-single-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<hello + + EOXML, + 'data' => ['hello'], + ]; + yield 'soap11-document-literal-multi-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap11-document-encoded-multi-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap11-rpc-literal-multi-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap11-rpc-encoded-multi-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_11->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap12-document-literal-multi-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap12-document-encoded-multi-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::DOCUMENT->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap12-rpc-literal-multi-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::LITERAL->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + yield 'soap12-rpc-encoded-multi-result' => [ + ...$baseConfig, + 'context' => self::createMethodContext( + self::createType('return'), + (new MethodMeta()) + ->withSoapVersion(SoapVersion::SOAP_12->value) + ->withBindingStyle(BindingStyle::RPC->value) + ->withOutputBindingUsage(BindingUse::ENCODED->value) + ), + 'xml' => <<helloworld + + EOXML, + 'data' => ['hello', 'world'], + ]; + } + + private static function createMethodContext( + XsdType $returnType, + MethodMeta $meta + ): MethodContext { + $method = (new Method( + 'foo', + new ParameterCollection(), + $returnType + ))->withMeta( + static fn (): MethodMeta => $meta + ->withTargetNamespace('uri:tns') + ->withOperationName('foo') + ); + + return new MethodContext( + $method, + new InMemoryMetadata(new TypeCollection(), new MethodCollection($method)), + EncoderRegistry::default(), + new Namespaces( + [ + 'tns' => 'uri:tns', + 'xsd' => Xmlns::xsd()->value(), + ], + [ + 'uri:tns' => 'tns', + Xmlns::xsd()->value() => 'xsd', + ], + ) + ); + } +}