diff --git a/CHANGELOG.md b/CHANGELOG.md index 070d2fa..66305bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Added + +- `Pipe->endpoint()->{method}()` pattern + ### Fixed - `Any::from()` was erasing guarded errors diff --git a/proofs/pipe.php b/proofs/pipe.php index a125e9e..9fb607b 100644 --- a/proofs/pipe.php +++ b/proofs/pipe.php @@ -338,4 +338,50 @@ static function($assert, $method, $other, $protocolVersion) { ); }, ); + + yield proof( + 'Pipe->endpoint()->method()->handle()', + given( + Set::of(...Http\Method::cases()), + Set::of(...Http\ProtocolVersion::cases()), + ), + static function($assert, $method, $protocolVersion) { + $request = Http\ServerRequest::of( + Url::of('/foo'), + $method, + $protocolVersion, + ); + $expected = Http\Response::of( + Http\Response\StatusCode::ok, + $request->protocolVersion(), + ); + $router = Router::of( + Pipe::new() + ->endpoint('{/watev}') + ->{$method->name}() + ->handle(static function($in, $input) use ($assert, $request, $expected) { + $assert->same($request, $in); + $assert->same( + 'foo', + $input + ->get('watev') + ->match( + static fn($value) => $value, + static fn() => null, + ), + ); + + return Attempt::result($expected); + }), + ); + + $assert->same( + $expected, + $router($request)->match( + static fn($response) => $response, + static fn($error) => $error, + ), + ); + }, + ); }; diff --git a/src/Pipe/Endpoint.php b/src/Pipe/Endpoint.php index d0ec63c..0fccf2f 100644 --- a/src/Pipe/Endpoint.php +++ b/src/Pipe/Endpoint.php @@ -9,6 +9,7 @@ Component\Like, Handle, Any, + Method, }; use Innmind\Http\{ ServerRequest, @@ -67,6 +68,105 @@ public function spread(): Endpoint\Spread return Endpoint\Spread::of($this->endpoint); } + #[\NoDiscard] + public function get(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::get(), + ); + } + + #[\NoDiscard] + public function post(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::post(), + ); + } + + #[\NoDiscard] + public function put(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::put(), + ); + } + + #[\NoDiscard] + public function patch(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::patch(), + ); + } + + #[\NoDiscard] + public function delete(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::delete(), + ); + } + + #[\NoDiscard] + public function options(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::options(), + ); + } + + #[\NoDiscard] + public function trace(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::trace(), + ); + } + + #[\NoDiscard] + public function connect(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::connect(), + ); + } + + #[\NoDiscard] + public function head(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::head(), + ); + } + + #[\NoDiscard] + public function link(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::link(), + ); + } + + #[\NoDiscard] + public function unlink(): Endpoint\Method + { + return Endpoint\Method::of( + $this->endpoint, + Method::unlink(), + ); + } + /** * @no-named-arguments * diff --git a/src/Pipe/Endpoint/Method.php b/src/Pipe/Endpoint/Method.php new file mode 100644 index 0000000..6c9abe8 --- /dev/null +++ b/src/Pipe/Endpoint/Method.php @@ -0,0 +1,80 @@ +> + */ +final class Method implements Provider +{ + /** @use Like> */ + use Like; + + /** + * @param Component> $endpoint + * @param Component $method + */ + private function __construct( + private Component $endpoint, + private Component $method, + ) { + } + + /** + * @internal + * @psalm-pure + * + * @param Component> $endpoint + * @param Component $method + */ + #[\NoDiscard] + public static function of(Component $endpoint, Component $method): self + { + return new self($endpoint, $method); + } + + /** + * @param callable(Http\ServerRequest, Map): Attempt $handle + * + * @return Component + */ + #[\NoDiscard] + public function handle(callable $handle): Component + { + return $this + ->toComponent() + ->feed(Handle::via($handle)); + } + + public function spread(): Method\Spread + { + return Method\Spread::of($this->toComponent()); + } + + #[\Override] + public function toComponent(): Component + { + $method = $this->method; + + /** + * @psalm-suppress MixedArgumentTypeCoercion + */ + return $this->endpoint->guard( + static fn($input) => $method->map(static fn() => $input), + ); + } +} diff --git a/src/Pipe/Endpoint/Method/Spread.php b/src/Pipe/Endpoint/Method/Spread.php new file mode 100644 index 0000000..d40b026 --- /dev/null +++ b/src/Pipe/Endpoint/Method/Spread.php @@ -0,0 +1,52 @@ +> $previous + */ + private function __construct( + private Component $previous, + ) { + } + + /** + * @internal + * @psalm-pure + * + * @param Component> $previous + */ + #[\NoDiscard] + public static function of(Component $previous): self + { + return new self($previous); + } + + /** + * @param callable(...mixed): Attempt $handle + * + * @return Component + */ + #[\NoDiscard] + public function handle(callable $handle): Component + { + /** @psalm-suppress MixedArgumentTypeCoercion */ + return $this->previous->feed(Handle::of($handle)); + } +}