diff --git a/src/Encoder.php b/src/Encoder.php index 96a4820..7562be5 100644 --- a/src/Encoder.php +++ b/src/Encoder.php @@ -5,6 +5,7 @@ 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\Engine\Encoder as SoapEncoder; use Soap\Engine\HttpBinding\SoapRequest; @@ -13,8 +14,6 @@ use Soap\WsdlReader\Model\Definitions\EncodingStyle; use Soap\WsdlReader\Model\Definitions\Namespaces; use Soap\WsdlReader\Model\Definitions\SoapVersion; -use function Psl\Type\mixed; -use function Psl\Type\non_empty_string; use function VeeWee\Reflecta\Lens\index; final class Encoder implements SoapEncoder @@ -35,19 +34,17 @@ public function encode(string $method, array $arguments): SoapRequest $bindingUse = $meta->inputBindingUsage()->map(BindingUse::from(...))->unwrapOr(BindingUse::LITERAL); $encodingStyle = $meta->inputEncodingStyle()->map(EncodingStyle::from(...)); - $request = []; + $requestParams = []; foreach ($methodInfo->getParameters() as $index => $parameter) { $type = $parameter->getType(); $context = new Context($type, $this->metadata, $this->registry, $this->namespaces, $bindingUse); - /** @var mixed $argument */ - $argument = index($index)->get($arguments); + /** @var mixed $value */ + $value = index($index)->get($arguments); - $request[] = non_empty_string()->assert( - $this->registry->detectEncoderForContext($context)->iso($context)->to($argument) - ); + $requestParams[] = (new ParameterBuilder($meta, $context, $value))(...); } - $operation = new OperationBuilder($meta, $this->namespaces, $request); + $operation = new OperationBuilder($meta, $this->namespaces, $requestParams); $writeEnvelope = new SoapEnvelopeWriter($soapVersion, $bindingUse, $encodingStyle, $operation(...)); return new SoapRequest( diff --git a/src/Xml/Reader/ChildrenReader.php b/src/Xml/Reader/ChildrenReader.php deleted file mode 100644 index abb6a27..0000000 --- a/src/Xml/Reader/ChildrenReader.php +++ /dev/null @@ -1,28 +0,0 @@ -locateDocumentElement()); - - return join( - $elements->map( - static fn (DOMElement $element): string => Document::fromXmlNode($element)->stringifyDocumentElement(), - ), - '' - ); - } -} diff --git a/src/Xml/Writer/OperationBuilder.php b/src/Xml/Writer/OperationBuilder.php index 9ef9654..e6d10cd 100644 --- a/src/Xml/Writer/OperationBuilder.php +++ b/src/Xml/Writer/OperationBuilder.php @@ -3,20 +3,19 @@ namespace Soap\Encoding\Xml\Writer; +use Closure; use Generator; -use Soap\Encoding\Xml\Reader\ChildrenReader; use Soap\Engine\Metadata\Model\MethodMeta; use Soap\WsdlReader\Model\Definitions\BindingStyle; use Soap\WsdlReader\Model\Definitions\Namespaces; use XMLWriter; -use function Psl\Vec\map; +use function VeeWee\Xml\Writer\Builder\children; use function VeeWee\Xml\Writer\Builder\namespaced_element; -use function VeeWee\Xml\Writer\Builder\raw; final class OperationBuilder { /** - * @param list $parameters + * @param list> $parameters */ public function __construct( private readonly MethodMeta $meta, @@ -29,22 +28,6 @@ public function __construct( * @return Generator */ public function __invoke(XMLWriter $writer): Generator - { - $operationName = $this->meta->operationName()->unwrap(); - $namespace = $this->meta->inputNamespace()->or($this->meta->targetNamespace())->unwrap(); - - yield from namespaced_element( - $namespace, - $this->namespaces->lookupNameFromNamespace($namespace)->unwrapOr('tns'), - $operationName, - $this->buildChildren(...) - )($writer); - } - - /** - * @return Generator - */ - private function buildChildren(XMLWriter $writer): Generator { $bindingStyle = BindingStyle::tryFrom($this->meta->bindingStyle()->unwrapOr(BindingStyle::DOCUMENT->value)); @@ -59,9 +42,7 @@ private function buildChildren(XMLWriter $writer): Generator */ private function buildDocument(XMLWriter $writer): Generator { - $documentParts = map($this->parameters, (new ChildrenReader())(...)); - - yield from raw(implode('', $documentParts))($writer); + yield from children($this->parameters)($writer); } /** @@ -69,6 +50,14 @@ private function buildDocument(XMLWriter $writer): Generator */ private function buildRpc(XMLWriter $writer): Generator { - yield from raw(implode('', $this->parameters))($writer); + $operationName = $this->meta->operationName()->unwrap(); + $namespace = $this->meta->inputNamespace()->or($this->meta->targetNamespace())->unwrap(); + + yield from namespaced_element( + $namespace, + $this->namespaces->lookupNameFromNamespace($namespace)->unwrapOr('tns'), + $operationName, + children($this->parameters), + )($writer); } } diff --git a/src/Xml/Writer/ParameterBuilder.php b/src/Xml/Writer/ParameterBuilder.php new file mode 100644 index 0000000..03bd2c1 --- /dev/null +++ b/src/Xml/Writer/ParameterBuilder.php @@ -0,0 +1,71 @@ + + */ + public function __invoke(XMLWriter $writer): Generator + { + $bindingStyle = BindingStyle::tryFrom($this->meta->bindingStyle()->unwrapOr(BindingStyle::DOCUMENT->value)); + + yield from match($bindingStyle) { + BindingStyle::DOCUMENT => $this->buildDocument($writer), + BindingStyle::RPC => $this->buildRpc($writer), + }; + } + + /** + * @return Generator + */ + private function buildDocument(XMLWriter $writer): Generator + { + $type = $this->context->type; + $context = $this->context->withType( + $type + ->withXmlTargetNodeName($type->getName()) + ->withMeta( + static fn (TypeMeta $meta) => $meta->withIsQualified(true) + ) + ); + + yield from raw($this->encode($context))($writer); + } + + /** + * @return Generator + */ + private function buildRpc(XMLWriter $writer): Generator + { + yield from raw($this->encode($this->context))($writer); + } + + /** + * @return non-empty-string + */ + private function encode(Context $context): string + { + return non_empty_string()->assert( + $context->registry->detectEncoderForContext($context)->iso($context)->to($this->value) + ); + } +} diff --git a/src/Xml/Writer/SoapEnvelopeWriter.php b/src/Xml/Writer/SoapEnvelopeWriter.php index 27f6b29..1622199 100644 --- a/src/Xml/Writer/SoapEnvelopeWriter.php +++ b/src/Xml/Writer/SoapEnvelopeWriter.php @@ -6,10 +6,12 @@ use Closure; use Generator; use Psl\Option\Option; +use Soap\Encoding\Exception\ExceptionInterface as EncodingExceptionInterface; use Soap\WsdlReader\Model\Definitions\BindingUse; use Soap\WsdlReader\Model\Definitions\EncodingStyle; use Soap\WsdlReader\Model\Definitions\SoapVersion; use Soap\Xml\Xmlns; +use VeeWee\Xml\Exception\RuntimeException as XmlRuntimeException; use VeeWee\Xml\Writer\Writer; use XMLWriter; use function Psl\Vec\filter_nulls; @@ -42,44 +44,48 @@ public function __invoke(): string SoapVersion::SOAP_12 => rtrim(Xmlns::soap12Envelope()->value(), '/'), }; - return Writer::inMemory() - ->write( - namespaced_element( - $envelopeNamespace, - 'SOAP-ENV', - 'Envelope', + try { + return Writer::inMemory() + ->write( namespaced_element( $envelopeNamespace, 'SOAP-ENV', - 'Body', - children( - filter_nulls([ - // In SOAP 1.2 the position of the encoding attributes is limited: - // See: https://www.w3.org/TR/soap12-part1/#soapencattr - // For SOAP 1.1 it can be everywhere: - // See: https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383495 - $this->encodingStyle - ->filter(fn (): bool => $this->bindingUse === BindingUse::ENCODED) - ->map( - static fn (EncodingStyle $encodingStyle) => children([ - namespace_attribute( - $encodingStyle->value, - 'SOAP-ENC' - ), - namespaced_attribute( - $envelopeNamespace, - 'SOAP-ENV', - 'encodingStyle', - $encodingStyle->value - ) - ]) - )->unwrapOr(null), - $this->children - ]) + 'Envelope', + namespaced_element( + $envelopeNamespace, + 'SOAP-ENV', + 'Body', + children( + filter_nulls([ + // In SOAP 1.2 the position of the encoding attributes is limited: + // See: https://www.w3.org/TR/soap12-part1/#soapencattr + // For SOAP 1.1 it can be everywhere: + // See: https://www.w3.org/TR/2000/NOTE-SOAP-20000508/#_Toc478383495 + $this->encodingStyle + ->filter(fn (): bool => $this->bindingUse === BindingUse::ENCODED) + ->map( + static fn (EncodingStyle $encodingStyle) => children([ + namespace_attribute( + $encodingStyle->value, + 'SOAP-ENC' + ), + namespaced_attribute( + $envelopeNamespace, + 'SOAP-ENV', + 'encodingStyle', + $encodingStyle->value + ) + ]) + )->unwrapOr(null), + $this->children + ]) + ) ) ) ) - ) - ->map(memory_output()); + ->map(memory_output()); + } catch (XmlRuntimeException $e) { + throw ($e->getPrevious() instanceof EncodingExceptionInterface) ? $e->getPrevious() : $e; + } } } diff --git a/tests/PhpCompatibility/Schema071Test.php b/tests/PhpCompatibility/Schema071Test.php index 8cd4d78..63b44a6 100644 --- a/tests/PhpCompatibility/Schema071Test.php +++ b/tests/PhpCompatibility/Schema071Test.php @@ -38,7 +38,7 @@ protected function calculateParam(): mixed #[Test] public function it_is_compatible_with_phps_encoding() { - static::markTestSkipped('Literal document seems about right - yet php soap uses the type instead of the part name. Not sure what to do here yet.'); + static::markTestSkipped('Literal document seems about right - it seems to be taking the param name instead of the complextype name though. Not sure what to do here yet.'); } protected function expectXml(): string diff --git a/tests/PhpCompatibility/Schema072Test.php b/tests/PhpCompatibility/Schema072Test.php index bb1f6f1..bbe7569 100644 --- a/tests/PhpCompatibility/Schema072Test.php +++ b/tests/PhpCompatibility/Schema072Test.php @@ -4,7 +4,6 @@ namespace Soap\Encoding\Test\PhpCompatibility; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\Test; use Soap\Encoding\Decoder; use Soap\Encoding\Driver; use Soap\Encoding\Encoder; @@ -37,12 +36,6 @@ protected function calculateParam(): mixed ]; } - #[Test] - public function it_is_compatible_with_phps_encoding() - { - static::markTestSkipped('Literal document seems about right - yet php soap uses the type instead of the part name. Not sure what to do here yet.'); - } - protected function expectXml(): string { return << [ - '', - '', - ]; - yield 'single-child' => [ - 'a', - 'a', - ]; - yield 'multi-child' => [ - 'ab', - 'ab', - ]; - } -} diff --git a/tests/Unit/Xml/Writer/OperationBuilderTest.php b/tests/Unit/Xml/Writer/OperationBuilderTest.php index e7f47e4..3439608 100644 --- a/tests/Unit/Xml/Writer/OperationBuilderTest.php +++ b/tests/Unit/Xml/Writer/OperationBuilderTest.php @@ -11,19 +11,21 @@ use Soap\WsdlReader\Model\Definitions\BindingStyle; use Soap\WsdlReader\Model\Definitions\Namespaces; use VeeWee\Xml\Writer\Writer; +use function Psl\Vec\map; +use function VeeWee\Xml\Writer\Builder\raw; use function VeeWee\Xml\Writer\Mapper\memory_output; #[CoversClass(OperationBuilder::class)] final class OperationBuilderTest extends TestCase { /** - * + * @param list $parts * @dataProvider provideOperationCases */ public function test_it_can_write_a_soap_operation(MethodMeta $meta, array $parts, string $expected): void { $actual = Writer::inMemory() - ->write(new OperationBuilder($meta, new Namespaces([], []), $parts)) + ->write(new OperationBuilder($meta, new Namespaces([], []), map($parts, raw(...)))) ->map(memory_output()); static::assertXmlStringEqualsXmlString($expected, $actual); @@ -38,16 +40,16 @@ public static function provideOperationCases() yield 'document-single-part' => [ $methodMeta->withBindingStyle(BindingStyle::DOCUMENT->value), [ - ' + ' 1 2 - ' + ' ], << + 1 2 - + EOXML ]; yield 'rpc-single-part' => [ diff --git a/tests/Unit/Xml/Writer/ParameterBuilderTest.php b/tests/Unit/Xml/Writer/ParameterBuilderTest.php new file mode 100644 index 0000000..f83c767 --- /dev/null +++ b/tests/Unit/Xml/Writer/ParameterBuilderTest.php @@ -0,0 +1,103 @@ +write(new ParameterBuilder($meta, $context, $value)) + ->map(memory_output()); + + static::assertXmlStringEqualsXmlString($expected, $actual); + } + + public static function provideParameterCases() + { + $methodMeta = (new MethodMeta()) + ->withTargetNamespace('http://tempuri.org/') + ->withOperationName('Add'); + + $int = XsdType::guess('int') + ->withMeta( + static fn (TypeMeta $typeMeta) => $typeMeta + ->withIsSimple(true) + ->withIsElement(true) + ); + + $context = new Context( + $type = XsdType::guess('MyRequest') + ->withXmlNamespace('https://tempuri.org/') + ->withXmlNamespaceName('tns') + ->withXmlTargetNodeName('parameters') + ->withXmlTargetNamespace('https://tempuri.org/') + ->withXmlTargetNamespaceName('tns') + ->withMeta( + static fn (TypeMeta $typeMeta) => $typeMeta + ->withIsElement(true) + ), + new InMemoryMetadata(new TypeCollection( + new Type( + $type, + new PropertyCollection( + new Property('a', $int->withXmlTargetNodeName('a')), + new Property('b', $int->withXmlTargetNodeName('b')), + ) + ) + ), new MethodCollection()), + EncoderRegistry::default(), + new Namespaces( + ['tns' => 'https://tempuri.org/'], + ['https://tempuri.org/' => 'tns'] + ) + ); + + yield 'document-param' => [ + $methodMeta->withBindingStyle(BindingStyle::DOCUMENT->value), + $context, + ['a' => 1, 'b' => 2], + << + 1 + 2 + + EOXML + ]; + yield 'rpc-param' => [ + $methodMeta->withBindingStyle(BindingStyle::RPC->value), + $context, + ['a' => 1, 'b' => 2], + << + 1 + 2 + + EOXML + ]; + } +} diff --git a/tests/Unit/Xml/Writer/SoapEnvelopeWriterTest.php b/tests/Unit/Xml/Writer/SoapEnvelopeWriterTest.php index 66b1647..928b56b 100644 --- a/tests/Unit/Xml/Writer/SoapEnvelopeWriterTest.php +++ b/tests/Unit/Xml/Writer/SoapEnvelopeWriterTest.php @@ -4,10 +4,14 @@ namespace Soap\Encoding\Test\Unit\Xml\Writer; +use Exception; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; use Psl\Option\Option; +use Soap\Encoding\Exception\EncodingException; +use Soap\Encoding\Exception\ExceptionInterface; use Soap\Encoding\Xml\Writer\SoapEnvelopeWriter; +use Soap\Engine\Metadata\Model\XsdType; use Soap\WsdlReader\Model\Definitions\BindingUse; use Soap\WsdlReader\Model\Definitions\EncodingStyle; use Soap\WsdlReader\Model\Definitions\SoapVersion; @@ -35,6 +39,22 @@ public function test_it_can_write_a_soap_envelope( static::assertXmlStringEqualsXmlString($expected, $actual); } + public function test_it_can_fail_writing_with_encoding_exception(): void + { + $this->expectException(ExceptionInterface::class); + + $writer = new SoapEnvelopeWriter( + SoapVersion::SOAP_11, + BindingUse::LITERAL, + some(EncodingStyle::SOAP_11), + static function () { + throw EncodingException::encodingValue('Oops', XsdType::any(), new Exception('previous')); + yield; + } + ); + $writer(); + } + public static function provideEnvelopeCases() { yield 'soap-1.1-literal' => [