From d5376733f59f64acf283dc5a767fd40359ef4b4c Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 12 Apr 2026 11:50:24 +0200 Subject: [PATCH 1/9] update dependencies --- .github/workflows/ci.yml | 95 +-- CHANGELOG.md | 12 + composer.json | 18 +- src/Cli/Record.php | 121 ++++ src/Cli/RecordAppGraph.php | 42 -- src/Cli/RecordEnvironment.php | 60 -- src/Cli/RecordException.php | 44 -- src/Cli/StartProfile.php | 73 --- src/Http/EndProfile.php | 61 ++ src/Http/FreeProfile.php | 41 ++ src/Http/RecordAppGraph.php | 41 -- src/Http/RecordCall.php | 53 -- src/Http/RecordEnvironment.php | 60 +- src/Http/RecordException.php | 66 ++- src/Http/StartProfile.php | 106 ++-- src/Kernel.php | 1 + src/Kernel/App.php | 110 ++-- src/Kernel/OperatingSystem.php | 7 +- src/Kernel/Services.php | 105 ++++ src/OperatingSystem.php | 89 --- src/OperatingSystem/Control.php | 55 -- src/OperatingSystem/Control/Process/Debug.php | 128 ---- src/OperatingSystem/Control/Processes.php | 55 -- src/OperatingSystem/Debug.php | 216 +++++++ src/OperatingSystem/Remote.php | 64 -- src/OperatingSystem/Remote/Http.php | 88 --- src/OperatingSystem/Remote/Sql.php | 64 -- src/OperatingSystem/Remote/Ssh.php | 58 -- .../Remote/Ssh/Process/Debug.php | 143 ----- src/OperatingSystem/Remote/Ssh/Processes.php | 61 -- src/Record.php | 11 +- src/Record/Nothing.php | 9 +- src/Record/Profile.php | 6 +- src/Recorder/All.php | 1 + src/Recorder/AppGraph.php | 120 ---- src/Recorder/Beacon.php | 1 + src/Recorder/Exception.php | 21 +- tests/FunctionalTest.php | 546 +++++++++--------- 38 files changed, 1060 insertions(+), 1792 deletions(-) create mode 100644 src/Cli/Record.php delete mode 100644 src/Cli/RecordAppGraph.php delete mode 100644 src/Cli/RecordEnvironment.php delete mode 100644 src/Cli/RecordException.php delete mode 100644 src/Cli/StartProfile.php create mode 100644 src/Http/EndProfile.php create mode 100644 src/Http/FreeProfile.php delete mode 100644 src/Http/RecordAppGraph.php delete mode 100644 src/Http/RecordCall.php create mode 100644 src/Kernel/Services.php delete mode 100644 src/OperatingSystem.php delete mode 100644 src/OperatingSystem/Control.php delete mode 100644 src/OperatingSystem/Control/Process/Debug.php delete mode 100644 src/OperatingSystem/Control/Processes.php create mode 100644 src/OperatingSystem/Debug.php delete mode 100644 src/OperatingSystem/Remote.php delete mode 100644 src/OperatingSystem/Remote/Http.php delete mode 100644 src/OperatingSystem/Remote/Sql.php delete mode 100644 src/OperatingSystem/Remote/Ssh.php delete mode 100644 src/OperatingSystem/Remote/Ssh/Process/Debug.php delete mode 100644 src/OperatingSystem/Remote/Ssh/Processes.php delete mode 100644 src/Recorder/AppGraph.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6382de2..2f3eecb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,96 +4,11 @@ on: [push, pull_request] jobs: blackbox: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macOS-latest] - php-version: ['8.2', '8.3'] - dependencies: ['lowest', 'highest'] - name: 'BlackBox' - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Graphviz - uses: tlylt/install-graphviz@main - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl - coverage: none - - name: Composer - uses: "ramsey/composer-install@v2" - with: - dependency-versions: ${{ matrix.dependencies }} - - name: BlackBox - run: php blackbox.php + uses: innmind/github-workflows/.github/workflows/black-box-matrix.yml@main coverage: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macOS-latest] - php-version: ['8.2', '8.3'] - dependencies: ['lowest', 'highest'] - name: 'Coverage' - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Graphviz - uses: tlylt/install-graphviz@main - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl - coverage: xdebug - - name: Composer - uses: "ramsey/composer-install@v2" - with: - dependency-versions: ${{ matrix.dependencies }} - - name: BlackBox - run: php blackbox.php - env: - ENABLE_COVERAGE: 'true' - - uses: codecov/codecov-action@v3 - with: - token: ${{ secrets.CODECOV_TOKEN }} + uses: innmind/github-workflows/.github/workflows/coverage-matrix.yml@main + secrets: inherit psalm: - runs-on: ubuntu-latest - strategy: - matrix: - php-version: ['8.2', '8.3'] - dependencies: ['lowest', 'highest'] - name: 'Psalm' - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl - - name: Composer - uses: "ramsey/composer-install@v2" - with: - dependency-versions: ${{ matrix.dependencies }} - - name: Psalm - run: vendor/bin/psalm --shepherd + uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main cs: - runs-on: ubuntu-latest - strategy: - matrix: - php-version: ['8.2'] - name: 'CS' - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl - - name: Composer - uses: "ramsey/composer-install@v2" - - name: CS - run: vendor/bin/php-cs-fixer fix --diff --dry-run + uses: innmind/github-workflows/.github/workflows/cs.yml@main diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b94ef3..545cbb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## [Unreleased] + +### Changed + +- Requires PHP `8.4` +- Requires `innmind/profiler:~5.0` +- Requires `innmind/framework:~4.0` + +### Removed + +- App graph is no longer recorded + ## 4.0.0 - 2023-11-26 ### Changed diff --git a/composer.json b/composer.json index 7542a14..6dea80c 100644 --- a/composer.json +++ b/composer.json @@ -15,12 +15,12 @@ "issues": "http://github.com/Innmind/Debug/issues" }, "require": { - "php": "~8.2", - "innmind/profiler": "~4.0", - "innmind/framework": "~2.0", - "innmind/http": "~7.0", - "innmind/stack-trace": "~4.0", - "innmind/object-graph": "~3.1" + "php": "~8.4", + "innmind/profiler": "~5.0", + "innmind/framework": "~4.1", + "innmind/foundation": "~2.1", + "innmind/stack-trace": "~5.0", + "innmind/object-graph": "~3.4" }, "autoload": { "psr-4": { @@ -33,11 +33,11 @@ } }, "require-dev": { - "vimeo/psalm": "~5.6", - "innmind/black-box": "~5.5", + "innmind/static-analysis": "~1.3", + "innmind/black-box": "~6.5", "innmind/coding-standard": "~2.0" }, "provide": { - "innmind/framework-middlewares": "1.0" + "innmind/framework-middlewares": "4.0" } } diff --git a/src/Cli/Record.php b/src/Cli/Record.php new file mode 100644 index 0000000..eee8c89 --- /dev/null +++ b/src/Cli/Record.php @@ -0,0 +1,121 @@ +environment()->arguments()->toList()); + + return $this + ->profiler + ->start($name) + ->map(fn($profile) => _Record\Profile::of( + $this->profiler, + $profile, + )) + ->flatMap( + static fn($record) => $record( + static fn($mutation) => $mutation + ->sections() + ->environment() + ->record($console->variables()), + )->map(static fn() => $record), + ) + ->flatMap(function($record) use ($console) { + $this->recorder->push($record); + + return ($this->inner)($console)->eitherWay( + fn($console) => $this->result($console, $record), + fn($e) => $this->error($e, $record), + ); + }) + ->map(function($console) { + $this->recorder->push(new _Record\Nothing); + + return $console; + }) + ->mapError(function($e) { + $this->recorder->push(new _Record\Nothing); + + return $e; + }); + } + + public static function of( + Command $inner, + Profiler $profiler, + Recorder $recorder, + Recorder\Exception $exception, + ): self { + return new self($inner, $profiler, $recorder, $exception); + } + + /** + * @psalm-mutation-free + */ + #[\Override] + public function usage(): Usage + { + return $this->inner->usage(); + } + + /** + * @return Attempt + */ + private function result( + Console $console, + _Record $record, + ): Attempt { + return $record( + static fn($mutation) => $console->environment()->exitCode()->match( + static fn($exit) => match ($exit->successful()) { + true => $mutation->succeed('0'), + false => $mutation->fail((string) $exit->toInt()), + }, + static fn() => $mutation->succeed('0'), + ), + )->map(static fn() => $console); + } + + /** + * @return Attempt + */ + private function error( + \Throwable $e, + _Record $record, + ): Attempt { + /** @var Attempt */ + return ($this->exception)($e) + ->flatMap(static fn() => $record( + static fn($mutation) => $mutation->fail('crashed'), + )) + ->flatMap(static fn() => Attempt::error($e)); + } +} diff --git a/src/Cli/RecordAppGraph.php b/src/Cli/RecordAppGraph.php deleted file mode 100644 index 4a35cb7..0000000 --- a/src/Cli/RecordAppGraph.php +++ /dev/null @@ -1,42 +0,0 @@ -inner = $inner; - $this->record = $record; - } - - public function __invoke(Console $console): Console - { - try { - return ($this->inner)($console); - } finally { - ($this->record)($this->inner); - } - } - - /** - * @psalm-mutation-free - */ - public function usage(): string - { - return $this->inner->usage(); - } -} diff --git a/src/Cli/RecordEnvironment.php b/src/Cli/RecordEnvironment.php deleted file mode 100644 index 28f14ad..0000000 --- a/src/Cli/RecordEnvironment.php +++ /dev/null @@ -1,60 +0,0 @@ -record = new Record\Nothing; - $this->inner = $inner; - $this->env = $env; - } - - public function __invoke(Console $console): Console - { - try { - return ($this->inner)($console); - } finally { - ($this->record)( - fn($mutation) => $mutation - ->sections() - ->environment() - ->record($this->env->all()), - ); - } - } - - /** - * @psalm-mutation-free - */ - public function usage(): string - { - return $this->inner->usage(); - } - - public function push(Record $record): void - { - $this->record = $record; - } -} diff --git a/src/Cli/RecordException.php b/src/Cli/RecordException.php deleted file mode 100644 index 5cd1ec8..0000000 --- a/src/Cli/RecordException.php +++ /dev/null @@ -1,44 +0,0 @@ -inner = $inner; - $this->record = $record; - } - - public function __invoke(Console $console): Console - { - try { - return ($this->inner)($console); - } catch (\Throwable $e) { - ($this->record)($e); - - throw $e; - } - } - - /** - * @psalm-mutation-free - */ - public function usage(): string - { - return $this->inner->usage(); - } -} diff --git a/src/Cli/StartProfile.php b/src/Cli/StartProfile.php deleted file mode 100644 index d2b4e94..0000000 --- a/src/Cli/StartProfile.php +++ /dev/null @@ -1,73 +0,0 @@ -profiler = $profiler; - $this->recorder = $recorder; - $this->inner = $inner; - } - - public function __invoke(Console $console): Console - { - $profile = $this->profiler->start(\implode(' ', $console->environment()->arguments()->toList())); - $this->recorder->push(Record\Profile::of($this->profiler, $profile)); - - try { - $console = ($this->inner)($console); - $this->profiler->mutate( - $profile, - static fn($mutation) => $console->environment()->exitCode()->match( - static fn($exit) => match ($exit->successful()) { - true => $mutation->succeed('0'), - false => $mutation->fail((string) $exit->toInt()), - }, - static fn() => $mutation->succeed('0'), - ), - ); - - return $console; - } catch (\Throwable $e) { - $this->profiler->mutate( - $profile, - static fn($mutation) => $mutation->fail('crashed'), - ); - - throw $e; - } finally { - $this->recorder->push(new Record\Nothing); - } - } - - /** - * @psalm-mutation-free - */ - public function usage(): string - { - return $this->inner->usage(); - } -} diff --git a/src/Http/EndProfile.php b/src/Http/EndProfile.php new file mode 100644 index 0000000..35d8f1c --- /dev/null +++ b/src/Http/EndProfile.php @@ -0,0 +1,61 @@ + + */ +final class EndProfile implements Recorder, Component\Provider +{ + private function __construct( + private Record $record = new Record\Nothing, + ) { + } + + public static function new(): self + { + return new self; + } + + #[\Override] + public function push(Record $record): void + { + /** @psalm-suppress InaccessibleProperty */ + $this->record = $record; + } + + #[\Override] + public function toComponent(): Component + { + return Component::of( + fn(ServerRequest $request, Response $response) => ($this->record)( + static fn($mutation) => $mutation + ->sections() + ->http() + ->respondedWith(Response\Stringable::new()($response)), + ) + ->flatMap( + fn() => ($this->record)( + static fn($mutation) => match ($response->statusCode()->successful()) { + true => $mutation->succeed($response->statusCode()->toString()), + false => $mutation->fail($response->statusCode()->toString()), + }, + ), + ) + ->map(static fn() => $response), + ); + } +} diff --git a/src/Http/FreeProfile.php b/src/Http/FreeProfile.php new file mode 100644 index 0000000..fd09f1d --- /dev/null +++ b/src/Http/FreeProfile.php @@ -0,0 +1,41 @@ + + */ + public static function of(Recorder $recorder): Component + { + return Component::of(static function( + ServerRequest $request, + Response $input, + ) use ( + $recorder, + ) { + $recorder->push(new Record\Nothing); + + return Attempt::result($input); + }); + } +} diff --git a/src/Http/RecordAppGraph.php b/src/Http/RecordAppGraph.php deleted file mode 100644 index 86e1659..0000000 --- a/src/Http/RecordAppGraph.php +++ /dev/null @@ -1,41 +0,0 @@ -inner = $inner; - $this->record = $record; - $this->container = $container; - } - - public function __invoke(ServerRequest $request): Response - { - try { - return ($this->inner)($request); - } finally { - ($this->record)($this->container); - } - } -} diff --git a/src/Http/RecordCall.php b/src/Http/RecordCall.php deleted file mode 100644 index 5b14755..0000000 --- a/src/Http/RecordCall.php +++ /dev/null @@ -1,53 +0,0 @@ -record = new Record\Nothing; - $this->inner = $inner; - } - - public function __invoke(ServerRequest $request): Response - { - ($this->record)( - static fn($mutation) => $mutation - ->sections() - ->http() - ->received(ServerRequest\Stringable::new()($request)), - ); - $response = ($this->inner)($request); - ($this->record)( - static fn($mutation) => $mutation - ->sections() - ->http() - ->respondedWith(Response\Stringable::new()($response)), - ); - - return $response; - } - - public function push(Record $record): void - { - $this->record = $record; - } -} diff --git a/src/Http/RecordEnvironment.php b/src/Http/RecordEnvironment.php index 621b074..13cc544 100644 --- a/src/Http/RecordEnvironment.php +++ b/src/Http/RecordEnvironment.php @@ -7,49 +7,49 @@ Recorder, Record, }; -use Innmind\Framework\{ - Http\RequestHandler, - Environment, -}; -use Innmind\Http\{ - ServerRequest, - Response, -}; +use Innmind\Framework\Environment; +use Innmind\Router\Component; +use Innmind\Http\ServerRequest; +use Innmind\Immutable\SideEffect; /** * @internal + * @psalm-immutable + * @implements Component\Provider */ -final class RecordEnvironment implements RequestHandler, Recorder +final class RecordEnvironment implements Recorder, Component\Provider { - private Record $record; - private RequestHandler $inner; - private Environment $env; - - public function __construct( - RequestHandler $inner, - Environment $env, + private function __construct( + private Environment $env, + private Record $record = new Record\Nothing, ) { - $this->record = new Record\Nothing; - $this->inner = $inner; - $this->env = $env; } - public function __invoke(ServerRequest $request): Response + /** + * @internal + */ + public static function of(Environment $env): self { - try { - return ($this->inner)($request); - } finally { - ($this->record)( - fn($mutation) => $mutation - ->sections() - ->environment() - ->record($this->env->all()), - ); - } + return new self($env); } + #[\Override] public function push(Record $record): void { + /** @psalm-suppress InaccessibleProperty */ $this->record = $record; } + + #[\Override] + public function toComponent(): Component + { + return Component::of( + fn(ServerRequest $request, SideEffect $input) => ($this->record)( + fn($mutation) => $mutation + ->sections() + ->environment() + ->record($this->env->all()), + )->map(static fn() => $input), + ); + } } diff --git a/src/Http/RecordException.php b/src/Http/RecordException.php index c540354..1a97794 100644 --- a/src/Http/RecordException.php +++ b/src/Http/RecordException.php @@ -3,35 +3,71 @@ namespace Innmind\Debug\Http; -use Innmind\Debug\Recorder\Exception; -use Innmind\Framework\Http\RequestHandler; +use Innmind\Debug\{ + Recorder, + Recorder\Exception, + Record, +}; +use Innmind\Router\{ + Component, + Exception\NotFound, +}; use Innmind\Http\{ ServerRequest, Response, }; +use Innmind\Immutable\{ + Attempt, + SideEffect, +}; /** * @internal */ -final class RecordException implements RequestHandler +final class RecordException implements Recorder { - private RequestHandler $inner; - private Exception $record; + private function __construct( + private Exception $exception, + private Record $record = new Record\Nothing, + ) { + } - public function __construct(RequestHandler $inner, Exception $record) + /** + * @return Component + */ + public function __invoke(\Throwable $e): Component { - $this->inner = $inner; - $this->record = $record; + if ($e instanceof NotFound) { + /** @var Component */ + return Component::of(static fn( + ServerRequest $request, + SideEffect $_, + ) => Attempt::error($e)); + } + + /** @var Attempt */ + $result = ($this->exception)($e)->flatMap( + fn() => ($this->record)( + static fn($mutation) => $mutation->fail('crashed'), + ), + )->flatMap(static fn() => Attempt::error($e)); + + /** @var Component */ + return Component::of(static fn( + ServerRequest $request, + SideEffect $_, + ) => $result); } - public function __invoke(ServerRequest $request): Response + public static function of(Exception $record): self { - try { - return ($this->inner)($request); - } catch (\Throwable $e) { - ($this->record)($e); + return new self($record); + } - throw $e; - } + #[\Override] + public function push(Record $record): void + { + $this->record = $record; + $this->exception->push($record); } } diff --git a/src/Http/StartProfile.php b/src/Http/StartProfile.php index 78959de..93e2438 100644 --- a/src/Http/StartProfile.php +++ b/src/Http/StartProfile.php @@ -7,76 +7,60 @@ Recorder, Record, }; -use Innmind\Framework\Http\RequestHandler; use Innmind\Profiler\Profiler; -use Innmind\Http\{ - ServerRequest, - Response, +use Innmind\Router\Component; +use Innmind\Http\ServerRequest; +use Innmind\Immutable\{ + Attempt, + SideEffect, }; /** * @internal */ -final class StartProfile implements RequestHandler +final class StartProfile { - private Profiler $profiler; - private Recorder $recorder; - private RequestHandler $inner; - - public function __construct( - Profiler $profiler, - Recorder $recorder, - RequestHandler $inner, - ) { - $this->profiler = $profiler; - $this->recorder = $recorder; - $this->inner = $inner; - } - - public function __invoke(ServerRequest $request): Response + /** + * @internal + * @psalm-pure + * + * @return Component + */ + public static function of(Profiler $profiler, Recorder $recorder): Component { - $inProfiler = \str_starts_with($request->url()->path()->toString(), '/_profiler'); - - return match ($inProfiler) { - true => $this->dontProfile($request), - false => $this->profile($request), - }; - } - - private function dontProfile(ServerRequest $request): Response - { - return ($this->inner)($request); - } - - private function profile(ServerRequest $request): Response - { - $profile = $this->profiler->start(\sprintf( - '%s %s', - $request->method()->toString(), - $request->url()->path()->toString(), - )); - $this->recorder->push(Record\Profile::of($this->profiler, $profile)); - - try { - $response = ($this->inner)($request); - $this->profiler->mutate( - $profile, - static fn($mutation) => match ($response->statusCode()->successful()) { - true => $mutation->succeed($response->statusCode()->toString()), - false => $mutation->fail($response->statusCode()->toString()), - }, - ); + return Component::of(static function( + ServerRequest $request, + SideEffect $input, + ) use ( + $profiler, + $recorder, + ) { + $inProfiler = \str_starts_with($request->url()->path()->toString(), '/_profiler'); - return $response; - } catch (\Throwable $e) { - $this->profiler->mutate( - $profile, - static fn($mutation) => $mutation->fail('crashed'), - ); + if ($inProfiler) { + return Attempt::result($input); + } - throw $e; - } finally { - $this->recorder->push(new Record\Nothing); - } + return $profiler + ->start(\sprintf( + '%s %s', + $request->method()->toString(), + $request->url()->path()->toString(), + )) + ->map(static fn($profile) => Record\Profile::of( + $profiler, + $profile, + )) + ->flatMap( + static fn($record) => $record( + static fn($mutation) => $mutation + ->sections() + ->http() + ->received(ServerRequest\Stringable::new()($request)), + )->map(static fn() => $record), + ) + ->map($recorder->push(...)) + ->map(static fn(): mixed => $input); + }); } } diff --git a/src/Kernel.php b/src/Kernel.php index 0d41815..7c87edb 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -21,6 +21,7 @@ private function __construct(IDE $ide, FormatPath $formatPath) $this->formatPath = $formatPath; } + #[\Override] public function __invoke(Application $app): Application { return $app diff --git a/src/Kernel/App.php b/src/Kernel/App.php index 282848f..2bf6558 100644 --- a/src/Kernel/App.php +++ b/src/Kernel/App.php @@ -9,6 +9,7 @@ Recorder, IDE, }; +use Innmind\Profiler; use Innmind\Framework\{ Application, Middleware, @@ -31,92 +32,65 @@ public function __construct(IDE $ide, FormatPath $formatPath) $this->formatPath = $formatPath; } + #[\Override] public function __invoke(Application $app): Application { return $app - ->service('innmind/debug.appGraph', fn($get, $os, $env) => new Recorder\AppGraph( - $os, - $env->all()->filter(static fn($name) => $name === 'PATH'), - $this->ide, - )) - ->service('innmind/debug.exception', fn($get, $os, $env) => new Recorder\Exception( + ->service(Services::exception, fn($get, $os, $env) => new Recorder\Exception( $os, $env->all()->filter(static fn($name) => $name === 'PATH'), $this->ide, $this->formatPath, )) - ->mapCommand(static function($command, $get, $os, $env) { - $appGraph = $get('innmind/debug.appGraph'); - $exception = $get('innmind/debug.exception'); - - $recordAppGraph = new Cli\RecordAppGraph( - $command, - $appGraph, - ); - $recordException = new Cli\RecordException( - $recordAppGraph, - $exception, - ); - $recordEnvironment = new Cli\RecordEnvironment( - $recordException, - $env, - ); - $all = [ - $appGraph, - $exception, - $recordEnvironment, - ]; - + ->service(Services::httpEndProfile, static fn() => Http\EndProfile::new()) + ->service(Services::httpEnvironment, static fn($_, $__, $env) => Http\RecordEnvironment::of($env)) + ->service(Services::httpException, static fn($get) => Http\RecordException::of( + $get(Services::exception()), + )) + ->service(Services::httpRecorder, static function($get) { try { - $all[] = $get('innmind/debug.beacon'); - } catch (ServiceNotFound $e) { - // pass + $os = [$get(Services::beacon())]; + } catch (ServiceNotFound) { // this means the user didn't use the OS kernel + $os = []; } - return new Cli\StartProfile( - $get('innmind/profiler'), - Recorder\All::of(...$all), - $recordEnvironment, + return Recorder\All::of( + $get(Services::httpEndProfile()), + $get(Services::httpEnvironment()), + $get(Services::httpException()), + ...$os, ); }) - ->mapRequestHandler(static function($handler, $get, $os, $env) { - $appGraph = $get('innmind/debug.appGraph'); - $exception = $get('innmind/debug.exception'); - - $recordAppGraph = new Http\RecordAppGraph( - $handler, - $appGraph, - $get, - ); - $recordException = new Http\RecordException( - $recordAppGraph, - $exception, - ); - $recordEnvironment = new Http\RecordEnvironment( - $recordException, - $env, - ); - $recordCall = new Http\RecordCall($recordEnvironment); - $all = [ - $appGraph, - $exception, - $recordEnvironment, - $recordCall, - ]; - + ->service(Services::cliRecorder, static function($get) { try { - $all[] = $get('innmind/debug.beacon'); - } catch (ServiceNotFound $e) { - // pass + $os = [$get(Services::beacon())]; + } catch (ServiceNotFound) { // this means the user didn't use the OS kernel + $os = []; } - return new Http\StartProfile( - $get('innmind/profiler'), - Recorder\All::of(...$all), - $recordCall, + return Recorder\All::of( + $get(Services::exception()), + ...$os, ); - }); + }) + ->mapCommand(static fn($command, $get) => Cli\Record::of( + $command, + $get(Profiler\Web\Services::profiler()), + $get(Services::cliRecorder()), + $get(Services::exception()), + )) + ->mapRoutes( + static fn($route, $get) => Http\StartProfile::of( + $get(Profiler\Web\Services::profiler()), + $get(Services::httpRecorder()), + ) + ->pipe($get(Services::httpEnvironment())) + ->pipe($route) + ->otherwise($get(Services::httpException())) + ->pipe($get(Services::httpEndProfile())) + ->pipe(Http\FreeProfile::of($get(Services::httpRecorder()))), + ); } } diff --git a/src/Kernel/OperatingSystem.php b/src/Kernel/OperatingSystem.php index 8e71615..a8dfebb 100644 --- a/src/Kernel/OperatingSystem.php +++ b/src/Kernel/OperatingSystem.php @@ -5,7 +5,7 @@ use Innmind\Debug\{ Recorder\Beacon, - OperatingSystem as Debug, + OperatingSystem\Debug, }; use Innmind\Framework\{ Application, @@ -17,12 +17,13 @@ */ final class OperatingSystem implements Middleware { + #[\Override] public function __invoke(Application $app): Application { $beacon = new Beacon; return $app - ->service('innmind/debug.beacon', static fn() => $beacon) - ->mapOperatingSystem(static fn($os) => Debug::of($os, $beacon)); + ->service(Services::beacon, static fn() => $beacon) + ->mapOperatingSystem(static fn($os) => $os->map(Debug::of($beacon))); } } diff --git a/src/Kernel/Services.php b/src/Kernel/Services.php new file mode 100644 index 0000000..59a68d2 --- /dev/null +++ b/src/Kernel/Services.php @@ -0,0 +1,105 @@ + + */ +enum Services implements Service +{ + case beacon; + case exception; + case cliRecorder; + case httpRecorder; + case httpEnvironment; + case httpException; + case httpEndProfile; + + /** + * @internal + * + * @return self + */ + public static function beacon(): self + { + /** @var self */ + return self::beacon; + } + + /** + * @internal + * + * @return self + */ + public static function exception(): self + { + /** @var self */ + return self::exception; + } + + /** + * @internal + * + * @return self + */ + public static function cliRecorder(): self + { + /** @var self */ + return self::cliRecorder; + } + + /** + * @internal + * + * @return self + */ + public static function httpRecorder(): self + { + /** @var self */ + return self::httpRecorder; + } + + /** + * @internal + * + * @return self + */ + public static function httpEndProfile(): self + { + /** @var self */ + return self::httpEndProfile; + } + + /** + * @internal + * + * @return self + */ + public static function httpEnvironment(): self + { + /** @var self */ + return self::httpEnvironment; + } + + /** + * @internal + * + * @return self + */ + public static function httpException(): self + { + /** @var self */ + return self::httpException; + } +} diff --git a/src/OperatingSystem.php b/src/OperatingSystem.php deleted file mode 100644 index e7cb1a8..0000000 --- a/src/OperatingSystem.php +++ /dev/null @@ -1,89 +0,0 @@ -inner = $inner; - $this->beacon = $beacon; - } - - public static function of(OS $inner, Beacon $beacon): self - { - return new self($inner, $beacon); - } - - public function map(callable $map): self - { - return new self( - $this->inner->map($map), - $this->beacon, - ); - } - - public function clock(): Clock - { - return $this->inner->clock(); - } - - public function filesystem(): Filesystem - { - return $this->inner->filesystem(); - } - - public function status(): ServerStatus - { - return $this->inner->status(); - } - - public function control(): ServerControl - { - return Control::of($this->inner->control(), $this->beacon); - } - - public function ports(): Ports - { - return $this->inner->ports(); - } - - public function sockets(): Sockets - { - return $this->inner->sockets(); - } - - public function remote(): Remote - { - return RemoteDebug::of($this->inner->remote(), $this->beacon); - } - - public function process(): CurrentProcess - { - return $this->inner->process(); - } -} diff --git a/src/OperatingSystem/Control.php b/src/OperatingSystem/Control.php deleted file mode 100644 index dc4bbd9..0000000 --- a/src/OperatingSystem/Control.php +++ /dev/null @@ -1,55 +0,0 @@ -inner = $inner; - $this->beacon = $beacon; - } - - public static function of(Server $inner, Beacon $beacon): self - { - return new self($inner, $beacon); - } - - public function processes(): Processes - { - return Debug::of($this->inner->processes(), $this->beacon); - } - - public function volumes(): Volumes - { - return $this->inner->volumes(); - } - - public function reboot(): Either - { - return $this->inner->reboot(); - } - - public function shutdown(): Either - { - return $this->inner->shutdown(); - } -} diff --git a/src/OperatingSystem/Control/Process/Debug.php b/src/OperatingSystem/Control/Process/Debug.php deleted file mode 100644 index 1066efd..0000000 --- a/src/OperatingSystem/Control/Process/Debug.php +++ /dev/null @@ -1,128 +0,0 @@ -command = $command; - $this->inner = $inner; - $this->record = $record; - } - - public static function of( - Command $command, - Process $process, - Record $record, - ): self { - return new self($command, $process, $record); - } - - public function pid(): Maybe - { - return $this->inner->pid(); - } - - public function output(): Output - { - return $this->inner->output(); - } - - public function wait(): Either - { - $command = $this->command->toString(); - $command = $this->command->workingDirectory()->match( - static fn($path) => $path->toString().': '.$command, - static fn() => $command, - ); - - // Either - return $this - ->inner - ->wait() - ->map(function($success) use ($command) { - $content = Sequence::lazyStartingWith(Str::of('[0] '.$command)); - - if (!$this->command->outputToBeStreamed()) { - $content = $content - ->add(Str::of("\n")) - ->append( - $success - ->output() - ->chunks() - ->map(static fn($pair) => $pair[0]), - ); - } - - ($this->record)( - static fn($mutation) => $mutation - ->sections() - ->processes() - ->record(Content::ofChunks($content)), - ); - - return $success; - }) - ->leftMap(function($error) use ($command) { - $status = match (true) { - $error instanceof Failed => $error->exitCode()->toString(), - $error instanceof TimedOut => 'timed-out', - $error instanceof Signaled => 'signaled', - }; - $content = Sequence::lazyStartingWith(Str::of(\sprintf( - '[%s] %s', - $status, - $command, - ))); - - if ($error instanceof Failed && !$this->command->outputToBeStreamed()) { - $content = $content - ->add(Str::of("\n")) - ->append( - $error - ->output() - ->chunks() - ->map(static fn($pair) => $pair[0]), - ); - } - - ($this->record)( - static fn($mutation) => $mutation - ->sections() - ->processes() - ->record(Content::ofChunks($content)), - ); - - return $error; - }); - } -} diff --git a/src/OperatingSystem/Control/Processes.php b/src/OperatingSystem/Control/Processes.php deleted file mode 100644 index 899290a..0000000 --- a/src/OperatingSystem/Control/Processes.php +++ /dev/null @@ -1,55 +0,0 @@ -inner = $inner; - $this->beacon = $beacon; - } - - public static function of(ProcessesInterface $inner, Beacon $beacon): self - { - return new self($inner, $beacon); - } - - public function execute(Command $command): Process - { - if ($command->environment()->contains('X_INNMIND_DEBUG')) { - return $this->inner->execute($command); - } - - return Debug::of( - $command, - $this->inner->execute($command), - $this->beacon->record(), - ); - } - - public function kill(Pid $pid, Signal $signal): Either - { - return $this->inner->kill($pid, $signal); - } -} diff --git a/src/OperatingSystem/Debug.php b/src/OperatingSystem/Debug.php new file mode 100644 index 0000000..7391e01 --- /dev/null +++ b/src/OperatingSystem/Debug.php @@ -0,0 +1,216 @@ +mapServerControl(fn($server) => Control\Server::via(function($command) use ($server) { + // todo find a way to record the exit and output + $record = $this->beacon->record(); + + if ($command instanceof Command\OverSsh) { + $port = $command->port(); + $port = match ($port) { + null => '', + default => $port->format(), + }; + $ssh = \sprintf( + 'ssh: %s@%s%s', + $command->user()->toString(), + $command->host()->toString(), + $port, + ); + $normalized = $command->normalize(); + $inner = $command->command(); + $name = $inner->toString(); + + if ($inner instanceof Command) { + $name = $inner->workingDirectory()->match( + static fn($path) => $path->toString().': '.$name, + static fn() => $name, + ); + } + + return $record( + static fn($mutation) => $mutation + ->sections() + ->remote() + ->processes() + ->record(Content::ofChunks( + Sequence::of($ssh, $name) + ->map(Str::of(...)) + ->map(static fn($chunk) => $chunk->append("\n")), + )), + )->flatMap(static fn() => $server->processes()->execute($normalized)); + } + + // to not record this package usage of the OS + if ($command->environment()->contains('X_INNMIND_DEBUG')) { + return $server + ->processes() + ->execute($command); + } + + $name = $command->toString(); + $name = $command->workingDirectory()->match( + static fn($path) => $path->toString().': '.$name, + static fn() => $name, + ); + + return $record( + static fn($mutation) => $mutation + ->sections() + ->processes() + ->record(Content::ofString($name)), + )->flatMap(static fn() => $server->processes()->execute($command)); + })) + ->mapHttpTransport(fn($transport) => Transport::via(function($request) use ($transport) { + $record = $this->beacon->record(); + $toString = Response\Stringable::new(); + + return $record( + static fn($mutation) => $mutation + ->sections() + ->remote() + ->http() + ->sent(Request\Stringable::new()($request)), + ) + ->either() + ->leftMap(static fn($e) => new Failure( + $request, + $e->getMessage(), + )) + ->flatMap( + static fn() => $transport($request) + ->flatMap( + static fn($success) => $record( + static fn($mutation) => $mutation + ->sections() + ->remote() + ->http() + ->got($toString($success->response())), + ) + ->either() + ->leftMap(static fn($e) => new Failure( + $request, + $e->getMessage(), + )) + ->map(static fn() => $success), + ) + ->otherwise(static function($error) use ($request, $record, $toString) { + $got = match (true) { + $error instanceof Information => $toString($error->response()), + $error instanceof Redirection => $toString($error->response()), + $error instanceof ClientError => $toString($error->response()), + $error instanceof ServerError => $toString($error->response()), + $error instanceof MalformedResponse => Content::ofString('malformed response'), + $error instanceof ConnectionFailed => Content::ofString($error->reason()), + $error instanceof Failure => Content::ofString($error->reason()), + }; + + return $record( + static fn($mutation) => $mutation + ->sections() + ->remote() + ->http() + ->got($got), + ) + ->either() + ->leftMap(static fn($e) => new Failure( + $request, + $e->getMessage(), + )) + ->flatMap(static fn() => Either::left($error)); + }), + ); + })) + ->mapSQLConnection(fn($connection) => Connection::intercept( + $connection, + function($run, $driver, $query) { + $normalized = $query; + + if ($normalized instanceof Query\Builder) { + $normalized = $normalized->normalize($driver); + } + + $parameters = $normalized + ->parameters() + ->map(static fn($parameter) => \sprintf( + "%s: %s\n", + $parameter->name()->match( + static fn($name) => $name, + static fn() => '?', + ), + (string) $parameter->value(), + )) + ->map(Str::of(...)); + // the sql connection throws when something fails + $_ = $this + ->beacon + ->record()( + static fn($mutation) => $mutation + ->sections() + ->remote() + ->sql() + ->record(Content::ofChunks( + Sequence::of($normalized->sql()) + ->add("\n") + ->map(Str::of(...)) + ->append($parameters), + )), + ) + ->unwrap(); + + return $run($query); + }, + )); + } + + public static function of(Beacon $beacon): self + { + return new self($beacon); + } +} diff --git a/src/OperatingSystem/Remote.php b/src/OperatingSystem/Remote.php deleted file mode 100644 index 3951d23..0000000 --- a/src/OperatingSystem/Remote.php +++ /dev/null @@ -1,64 +0,0 @@ -inner = $inner; - $this->beacon = $beacon; - } - - public static function of(RemoteInterface $inner, Beacon $beacon): self - { - return new self($inner, $beacon); - } - - public function ssh(Url $server): Server - { - return Ssh::of($this->inner->ssh($server), $this->beacon, $server); - } - - public function socket(Transport $transport, Authority $authority): Maybe - { - return $this->inner->socket($transport, $authority); - } - - public function http(): HttpTransport - { - return Http::of($this->inner->http(), $this->beacon); - } - - public function sql(Url $server): Connection - { - return Sql::of($this->inner->sql($server), $this->beacon); - } -} diff --git a/src/OperatingSystem/Remote/Http.php b/src/OperatingSystem/Remote/Http.php deleted file mode 100644 index f21912c..0000000 --- a/src/OperatingSystem/Remote/Http.php +++ /dev/null @@ -1,88 +0,0 @@ -inner = $inner; - $this->beacon = $beacon; - } - - public function __invoke(Request $request): Either - { - $record = $this->beacon->record(); - $record( - static fn($mutation) => $mutation - ->sections() - ->remote() - ->http() - ->sent(Request\Stringable::new()($request)), - ); - $toString = Response\Stringable::new(); - - return ($this->inner)($request) - ->map(static function($success) use ($record, $toString) { - $record( - static fn($mutation) => $mutation - ->sections() - ->remote() - ->http() - ->got($toString($success->response())), - ); - - return $success; - }) - ->leftMap(static function($error) use ($record, $toString) { - $got = match (true) { - $error instanceof Information => $toString($error->response()), - $error instanceof Redirection => $toString($error->response()), - $error instanceof ClientError => $toString($error->response()), - $error instanceof ServerError => $toString($error->response()), - $error instanceof MalformedResponse => Content::ofString('malformed response'), - $error instanceof ConnectionFailed => Content::ofString($error->reason()), - $error instanceof Failure => Content::ofString($error->reason()), - }; - $record( - static fn($mutation) => $mutation - ->sections() - ->remote() - ->http() - ->got($got), - ); - - return $error; - }); - } - - public static function of(Transport $inner, Beacon $beacon): self - { - return new self($inner, $beacon); - } -} diff --git a/src/OperatingSystem/Remote/Sql.php b/src/OperatingSystem/Remote/Sql.php deleted file mode 100644 index f89d61d..0000000 --- a/src/OperatingSystem/Remote/Sql.php +++ /dev/null @@ -1,64 +0,0 @@ -inner = $inner; - $this->beacon = $beacon; - } - - public function __invoke(Query $query): Sequence - { - $parameters = $query - ->parameters() - ->map(static fn($parameter) => \sprintf( - "%s: %s\n", - $parameter->name()->match( - static fn($name) => $name, - static fn() => '?', - ), - (string) $parameter->value(), - )) - ->map(Str::of(...)); - $this->beacon->record()( - static fn($mutation) => $mutation - ->sections() - ->remote() - ->sql() - ->record(Content::ofChunks( - Sequence::of($query->sql()) - ->add("\n") - ->map(Str::of(...)) - ->append($parameters), - )), - ); - - return ($this->inner)($query); - } - - public static function of(Connection $inner, Beacon $beacon): self - { - return new self($inner, $beacon); - } -} diff --git a/src/OperatingSystem/Remote/Ssh.php b/src/OperatingSystem/Remote/Ssh.php deleted file mode 100644 index c1d2392..0000000 --- a/src/OperatingSystem/Remote/Ssh.php +++ /dev/null @@ -1,58 +0,0 @@ -inner = $inner; - $this->beacon = $beacon; - $this->server = $server; - } - - public static function of(Server $inner, Beacon $beacon, Url $server): self - { - return new self($inner, $beacon, $server); - } - - public function processes(): Processes - { - return Debug::of($this->inner->processes(), $this->beacon, $this->server); - } - - public function volumes(): Volumes - { - return $this->inner->volumes(); - } - - public function reboot(): Either - { - return $this->inner->reboot(); - } - - public function shutdown(): Either - { - return $this->inner->shutdown(); - } -} diff --git a/src/OperatingSystem/Remote/Ssh/Process/Debug.php b/src/OperatingSystem/Remote/Ssh/Process/Debug.php deleted file mode 100644 index 82f05d9..0000000 --- a/src/OperatingSystem/Remote/Ssh/Process/Debug.php +++ /dev/null @@ -1,143 +0,0 @@ -server = $server; - $this->command = $command; - $this->inner = $inner; - $this->record = $record; - } - - public static function of( - Url $server, - Command $command, - Process $process, - Record $record, - ): self { - return new self($server, $command, $process, $record); - } - - public function pid(): Maybe - { - return $this->inner->pid(); - } - - public function output(): Output - { - return $this->inner->output(); - } - - public function wait(): Either - { - $ssh = \sprintf( - "ssh: %s\n", - $this->server->authority()->toString(), - ); - $command = $this->command->toString(); - $command = $this->command->workingDirectory()->match( - static fn($path) => $path->toString().': '.$command, - static fn() => $command, - ); - - // Either - return $this - ->inner - ->wait() - ->map(function($success) use ($ssh, $command) { - $content = Sequence::lazyStartingWith($ssh, '[0] '.$command) - ->map(Str::of(...)); - - if (!$this->command->outputToBeStreamed()) { - $content = $content - ->add(Str::of("\n")) - ->append( - $success - ->output() - ->chunks() - ->map(static fn($pair) => $pair[0]), - ); - } - - ($this->record)( - static fn($mutation) => $mutation - ->sections() - ->remote() - ->processes() - ->record(Content::ofChunks($content)), - ); - - return $success; - }) - ->leftMap(function($error) use ($ssh, $command) { - $status = match (true) { - $error instanceof Failed => $error->exitCode()->toString(), - $error instanceof TimedOut => 'timed-out', - $error instanceof Signaled => 'signaled', - }; - $content = Sequence::lazyStartingWith( - $ssh, - \sprintf( - '[%s] %s', - $status, - $command, - ), - )->map(Str::of(...)); - - if ($error instanceof Failed && !$this->command->outputToBeStreamed()) { - $content = $content - ->add(Str::of("\n")) - ->append( - $error - ->output() - ->chunks() - ->map(static fn($pair) => $pair[0]), - ); - } - - ($this->record)( - static fn($mutation) => $mutation - ->sections() - ->remote() - ->processes() - ->record(Content::ofChunks($content)), - ); - - return $error; - }); - } -} diff --git a/src/OperatingSystem/Remote/Ssh/Processes.php b/src/OperatingSystem/Remote/Ssh/Processes.php deleted file mode 100644 index 7535a19..0000000 --- a/src/OperatingSystem/Remote/Ssh/Processes.php +++ /dev/null @@ -1,61 +0,0 @@ -inner = $inner; - $this->beacon = $beacon; - $this->server = $server; - } - - public static function of( - ProcessesInterface $inner, - Beacon $beacon, - Url $server, - ): self { - return new self($inner, $beacon, $server); - } - - public function execute(Command $command): Process - { - return Debug::of( - $this->server, - $command, - $this->inner->execute($command), - $this->beacon->record(), - ); - } - - public function kill(Pid $pid, Signal $signal): Either - { - return $this->inner->kill($pid, $signal); - } -} diff --git a/src/Record.php b/src/Record.php index 276e558..5c4ca39 100644 --- a/src/Record.php +++ b/src/Record.php @@ -4,6 +4,10 @@ namespace Innmind\Debug; use Innmind\Profiler\Profiler\Mutation; +use Innmind\Immutable\{ + Attempt, + SideEffect, +}; /** * @internal @@ -11,7 +15,10 @@ interface Record { /** - * @param callable(Mutation): void $mutation + * @param callable(Mutation): Attempt $mutation + * + * @return Attempt */ - public function __invoke(callable $mutation): void; + #[\NoDiscard] + public function __invoke(callable $mutation): Attempt; } diff --git a/src/Record/Nothing.php b/src/Record/Nothing.php index ddadbae..7fff23f 100644 --- a/src/Record/Nothing.php +++ b/src/Record/Nothing.php @@ -4,14 +4,19 @@ namespace Innmind\Debug\Record; use Innmind\Debug\Record; +use Innmind\Immutable\{ + Attempt, + SideEffect, +}; /** * @internal */ final class Nothing implements Record { - public function __invoke(callable $mutation): void + #[\Override] + public function __invoke(callable $mutation): Attempt { - // nothing to do + return Attempt::result(SideEffect::identity); } } diff --git a/src/Record/Profile.php b/src/Record/Profile.php index c9918f9..c63fb36 100644 --- a/src/Record/Profile.php +++ b/src/Record/Profile.php @@ -8,6 +8,7 @@ Profiler, Profile\Id, }; +use Innmind\Immutable\Attempt; /** * @internal @@ -23,9 +24,10 @@ private function __construct(Profiler $profiler, Id $profile) $this->profile = $profile; } - public function __invoke(callable $mutation): void + #[\Override] + public function __invoke(callable $mutation): Attempt { - $this->profiler->mutate($this->profile, $mutation); + return $this->profiler->mutate($this->profile, $mutation); } public static function of(Profiler $profiler, Id $profile): self diff --git a/src/Recorder/All.php b/src/Recorder/All.php index 94dae33..07dc3a6 100644 --- a/src/Recorder/All.php +++ b/src/Recorder/All.php @@ -32,6 +32,7 @@ public static function of(Recorder ...$recorders): self return new self($recorders); } + #[\Override] public function push(Record $record): void { foreach ($this->recorders as $recorder) { diff --git a/src/Recorder/AppGraph.php b/src/Recorder/AppGraph.php deleted file mode 100644 index 79fcc0c..0000000 --- a/src/Recorder/AppGraph.php +++ /dev/null @@ -1,120 +0,0 @@ - */ - private Map $env; - private Record $record; - private Render $render; - private Lookup $lookup; - - /** - * @param Map $env - */ - public function __construct( - OperatingSystem $os, - Map $env, - IDE $ide, - ) { - $this->os = $os; - $this->env = ($env)('X_INNMIND_DEBUG', 'true'); - $this->record = new Record\Nothing; - $this->render = Render::of(match ($ide) { - IDE::sublimeText => new SublimeHandler, - default => null, - })->fromTopToBottom(); - $this->lookup = Lookup::of(); - } - - public function __invoke(object $root): void - { - $graph = ($this->lookup)($root); - $graph = $this->clean($graph); - $svg = $this - ->os - ->control() - ->processes() - ->execute( - Command::foreground('dot') - ->withShortOption('Tsvg') - ->withEnvironments($this->env) - ->withInput(($this->render)($graph)), - ) - ->wait() - ->match( - static fn($success) => Content::ofChunks( - $success - ->output() - ->chunks() - ->map(static fn($pair) => $pair[0]), - ), - static fn() => Content::ofString('Unable to render the app graph'), - ); - ($this->record)( - static fn($mutation) => $mutation - ->sections() - ->appGraph() - ->record($svg), - ); - } - - public function push(Record $record): void - { - $this->record = $record; - } - - private function clean(Graph $graph): Graph - { - if ($graph->root()->class()->toString() !== Container::class) { - return $graph; - } - - $children = $graph->nodes()->remove($graph->root()); - $unwanted = $graph - ->root() - ->relations() - ->filter(static fn($relation) => $relation->property()->toString() !== 'services') - ->map(static fn($relation) => $relation->reference()); - $toRemove = $children - ->filter(static fn($node) => $unwanted->any( - static fn($reference) => $reference->equals($node->reference()), - )) - ->flatMap(static fn($node) => $node->relations()) - ->map(static fn($relation) => $relation->reference()) - ->merge($unwanted); - $children = $children->filter(static fn($node) => !$toRemove->any( - static fn($reference) => $reference->equals($node->reference()), - )); - - return Graph::of( - $graph - ->root() - ->filterRelation(static fn($relation) => $relation->property()->toString() === 'services'), - $children, - ); - } -} diff --git a/src/Recorder/Beacon.php b/src/Recorder/Beacon.php index 72bb627..57ddc90 100644 --- a/src/Recorder/Beacon.php +++ b/src/Recorder/Beacon.php @@ -25,6 +25,7 @@ public function record(): Record return $this->record; } + #[\Override] public function push(Record $record): void { $this->record = $record; diff --git a/src/Recorder/Exception.php b/src/Recorder/Exception.php index cb866ff..98de618 100644 --- a/src/Recorder/Exception.php +++ b/src/Recorder/Exception.php @@ -17,7 +17,11 @@ Link\SublimeHandler, FormatPath, }; -use Innmind\Immutable\Map; +use Innmind\Immutable\{ + Map, + Attempt, + SideEffect, +}; /** * @internal @@ -51,7 +55,10 @@ public function __construct( ); } - public function __invoke(\Throwable $e): void + /** + * @return Attempt + */ + public function __invoke(\Throwable $e): Attempt { $graph = $this ->os @@ -63,17 +70,18 @@ public function __invoke(\Throwable $e): void ->withEnvironments($this->env) ->withInput(($this->render)(StackTrace::of($e))), ) - ->wait() + ->maybe() + ->flatMap(static fn($process) => $process->wait()->maybe()) ->match( static fn($success) => Content::ofChunks( $success ->output() - ->chunks() - ->map(static fn($pair) => $pair[0]), + ->map(static fn($chunk) => $chunk->data()), ), static fn() => Content::ofString('Unable to render the exception graph'), ); - ($this->record)( + + return ($this->record)( static fn($mutation) => $mutation ->sections() ->exception() @@ -81,6 +89,7 @@ public function __invoke(\Throwable $e): void ); } + #[\Override] public function push(Record $record): void { $this->record = $record; diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index fb7b835..e346256 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -11,11 +11,12 @@ }; use Innmind\CLI\{ Command as CliCommand, + Command\Usage, Console, - Environment\InMemory, + Environment as CliEnvironment, }; use Innmind\OperatingSystem\Factory; -use Innmind\Filesystem\Adapter\Filesystem; +use Innmind\Filesystem\Adapter; use Innmind\Server\Control\Server\Command; use Innmind\Http\{ ServerRequest, @@ -25,19 +26,27 @@ Response\StatusCode, ProtocolVersion, }; -use Innmind\Router\Route; +use Innmind\DI\Service; use Innmind\Url\{ Url, Path, }; use Innmind\Html\{ - Reader\Reader, + Reader, Visitor\Elements, Visitor\Element, }; -use Innmind\Immutable\Str; +use Innmind\Immutable\{ + Str, + Attempt, +}; use Innmind\BlackBox\PHPUnit\Framework\TestCase; +enum _Services implements Service +{ + case os; +} + class FunctionalTest extends TestCase { private Path $storage; @@ -49,8 +58,8 @@ public function setUp(): void public function tearDown(): void { - $storage = Filesystem::mount($this->storage); - $storage->root()->all()->foreach( + $storage = Adapter::mount($this->storage)->unwrap(); + $_ = $storage->root()->foreach( static fn($file) => $storage->remove($file->name()), ); } @@ -60,72 +69,83 @@ public function testRecordHttpContext() $app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) ->map(Debug::inApp()) - ->appendRoutes(static fn($routes, $_, $os) => $routes->add( - Route::literal('GET /')->handle(static function() use ($os) { - $os - ->control() - ->processes() - ->execute(Command::foreground('cat hello')) - ->wait() - ->match( - static fn() => null, - static fn() => null, - ); - $os - ->control() - ->processes() - ->execute(Command::foreground('echo hello')) - ->wait() - ->match( - static fn() => null, - static fn() => null, - ); - $os - ->remote() - ->http()(Request::of( - Url::of('https://github.com'), - Method::get, - ProtocolVersion::v11, - )) - ->match( - static fn() => null, - static fn() => null, - ); - $os - ->remote() - ->http()(Request::of( - Url::of('http://example.com/unknown'), - Method::get, - ProtocolVersion::v11, - )) - ->match( - static fn() => null, - static fn() => null, - ); - - throw new \Exception('foo'); - }), - )); + ->service(_Services::os, static fn($_, $os) => $os) + ->route( + static fn($pipe, $get) => $pipe + ->get() + ->endpoint('/') + ->handle( + static function() use ($get) { + $os = $get(_Services::os); + $_ = $os + ->control() + ->processes() + ->execute(Command::foreground('cat hello')) + ->flatMap(static fn($process) => $process->wait()->attempt( + static fn() => new \Exception, + )) + ->match( + static fn() => null, + static fn() => null, + ); + $_ = $os + ->control() + ->processes() + ->execute(Command::foreground('echo hello')) + ->flatMap(static fn($process) => $process->wait()->attempt( + static fn() => new \Exception, + )) + ->match( + static fn() => null, + static fn() => null, + ); + $_ = $os + ->remote() + ->http()(Request::of( + Url::of('https://github.com'), + Method::get, + ProtocolVersion::v11, + )) + ->match( + static fn() => null, + static fn() => null, + ); + $_ = $os + ->remote() + ->http()(Request::of( + Url::of('http://example.com/unknown'), + Method::get, + ProtocolVersion::v11, + )) + ->match( + static fn() => null, + static fn() => null, + ); + + return Attempt::error(new \Exception('foo')); + }, + ), + ); - try { - $app->run(ServerRequest::of( - Url::of('/'), - Method::get, - ProtocolVersion::v11, - )); - $this->fail('it should throw'); - } catch (\Exception $e) { - $this->assertSame('foo', $e->getMessage()); - } + $this->assert()->throws( + static fn() => $app + ->run(ServerRequest::of( + Url::of('/'), + Method::get, + ProtocolVersion::v11, + )) + ->unwrap(), + ); $response = $app->run(ServerRequest::of( Url::of('/_profiler/'), Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $profile = Reader::default()($response->body()) + $profile = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('main')) ->flatMap(Element::of('li')) @@ -140,27 +160,24 @@ public function testRecordHttpContext() $profile, Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $sections = Reader::default()($response->body()) + $sections = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('header')) - ->map(static fn($header) => Elements::of('li')($header)) - ->map( - static fn($lis) => $lis - ->flatMap(Elements::of('a')) - ->map(static fn($a) => $a->content()) - ->map(Str::of(...)) - ->map(static fn($text) => $text->trim()->toString()), - ) - ->match( - static fn($sections) => $sections->toList(), - static fn() => [], - ); + ->toSequence() + ->flatMap(Elements::of('li')) + ->flatMap(Elements::of('a')) + ->flatMap(static fn($a) => $a->normalize()->children()) + ->map(static fn($a) => $a->content()) + ->map(Str::of(...)) + ->map(static fn($text) => $text->trim()->toString()) + ->toList(); $this->assertSame( - ['Exception', 'Http', 'Environment', 'Processes', 'Remote/Http', 'App graph'], + ['Exception', 'Http', 'Environment', 'Processes', 'Remote/Http'], $sections, ); } @@ -170,52 +187,63 @@ public function testRecordHttpContextWithoutOSLayer() $app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) ->map(Debug::inApp()->app()) - ->appendRoutes(static fn($routes, $_, $os) => $routes->add( - Route::literal('GET /')->handle(static function() use ($os) { - $os - ->control() - ->processes() - ->execute(Command::foreground('cat hello')) - ->wait() - ->match( - static fn() => null, - static fn() => null, - ); - $os - ->remote() - ->http()(Request::of( - Url::of('https://github.com'), - Method::get, - ProtocolVersion::v11, - )) - ->match( - static fn() => null, - static fn() => null, - ); - - throw new \Exception('foo'); - }), - )); + ->service(_Services::os, static fn($_, $os) => $os) + ->route( + static fn($pipe, $get) => $pipe + ->get() + ->endpoint('/') + ->handle( + static function() use ($get) { + $os = $get(_Services::os); + $_ = $os + ->control() + ->processes() + ->execute(Command::foreground('cat hello')) + ->maybe() + ->flatMap(static fn($process) => $process->wait()->maybe()) + ->match( + static fn() => null, + static fn() => null, + ); + $_ = $os + ->remote() + ->http()(Request::of( + Url::of('https://github.com'), + Method::get, + ProtocolVersion::v11, + )) + ->match( + static fn() => null, + static fn() => null, + ); + + return Attempt::error(new \Exception('foo')); + }, + ), + ); - try { - $app->run(ServerRequest::of( + $error = $app + ->run(ServerRequest::of( Url::of('/'), Method::get, ProtocolVersion::v11, - )); - $this->fail('it should throw'); - } catch (\Exception $e) { - $this->assertSame('foo', $e->getMessage()); - } + )) + ->match( + static fn() => null, + static fn($e) => $e, + ); + + $this->assertSame('foo', $error->getMessage()); $response = $app->run(ServerRequest::of( Url::of('/_profiler/'), Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $profile = Reader::default()($response->body()) + $profile = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('main')) ->flatMap(Element::of('li')) @@ -230,27 +258,24 @@ public function testRecordHttpContextWithoutOSLayer() $profile, Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $sections = Reader::default()($response->body()) + $sections = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('header')) - ->map(static fn($header) => Elements::of('li')($header)) - ->map( - static fn($lis) => $lis - ->flatMap(Elements::of('a')) - ->map(static fn($a) => $a->content()) - ->map(Str::of(...)) - ->map(static fn($text) => $text->trim()->toString()), - ) - ->match( - static fn($sections) => $sections->toList(), - static fn() => [], - ); + ->toSequence() + ->flatMap(Elements::of('li')) + ->flatMap(Elements::of('a')) + ->flatMap(static fn($a) => $a->normalize()->children()) + ->map(static fn($a) => $a->content()) + ->map(Str::of(...)) + ->map(static fn($text) => $text->trim()->toString()) + ->toList(); $this->assertSame( - ['Exception', 'Http', 'Environment', 'App graph'], + ['Exception', 'Http', 'Environment'], $sections, ); } @@ -261,20 +286,21 @@ public function testRecordHttpResponse() ->map(Debug::inApp()->operatingSystem()) ->map(Profiler::inApp($this->storage)) ->map(Debug::inApp()->app()) - ->appendRoutes(static fn($routes, $_, $os) => $routes->add( - Route::literal('GET /')->handle(static function($request) use ($os) { - return Response::of( + ->route( + static fn($pipe, $get) => $pipe + ->get() + ->endpoint('/') + ->handle(static fn($request) => Attempt::result(Response::of( StatusCode::ok, $request->protocolVersion(), - ); - }), - )); + ))), + ); $response = $app->run(ServerRequest::of( Url::of('/'), Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); @@ -282,10 +308,11 @@ public function testRecordHttpResponse() Url::of('/_profiler/'), Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $profile = Reader::default()($response->body()) + $profile = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('main')) ->flatMap(Element::of('li')) @@ -300,27 +327,24 @@ public function testRecordHttpResponse() $profile, Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $sections = Reader::default()($response->body()) + $sections = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('header')) - ->map(static fn($header) => Elements::of('li')($header)) - ->map( - static fn($lis) => $lis - ->flatMap(Elements::of('a')) - ->map(static fn($a) => $a->content()) - ->map(Str::of(...)) - ->map(static fn($text) => $text->trim()->toString()), - ) - ->match( - static fn($sections) => $sections->toList(), - static fn() => [], - ); + ->toSequence() + ->flatMap(Elements::of('li')) + ->flatMap(Elements::of('a')) + ->flatMap(static fn($a) => $a->normalize()->children()) + ->map(static fn($a) => $a->content()) + ->map(Str::of(...)) + ->map(static fn($text) => $text->trim()->toString()) + ->toList(); $this->assertSame( - ['Http', 'Environment', 'App graph'], + ['Http', 'Environment'], $sections, ); } @@ -330,24 +354,26 @@ public function testRecordCliContext() $app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) ->map(Debug::inApp()) - ->command(static fn($_, $os) => new class($os) implements CliCommand { + ->service(_Services::os, static fn($_, $os) => $os) + ->command(static fn($get) => new class($get(_Services::os)) implements CliCommand { public function __construct(private $os) { } - public function __invoke(Console $console): Console + public function __invoke(Console $console): Attempt { - $this + $_ = $this ->os ->control() ->processes() ->execute(Command::foreground('cat hello')) - ->wait() + ->maybe() + ->flatMap(static fn($process) => $process->wait()->maybe()) ->match( static fn() => null, static fn() => null, ); - $this + $_ = $this ->os ->remote() ->http()(Request::of( @@ -360,27 +386,29 @@ public function __invoke(Console $console): Console static fn() => null, ); - throw new \Exception('foo'); + return Attempt::error(new \Exception('foo')); } - public function usage(): string + public function usage(): Usage { - return 'hello-world'; + return Usage::parse('hello-world'); } }); - try { - $app->run(InMemory::of( + $error = $app + ->run(CliEnvironment::inMemory( [], true, ['bin', 'hello-world'], [['PATH', \getenv('PATH')]], '/', - )); - $this->fail('it should throw'); - } catch (\Exception $e) { - $this->assertSame('foo', $e->getMessage()); - } + )) + ->match( + static fn() => null, + static fn($e) => $e, + ); + + $this->assertSame('foo', $error->getMessage()); $app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Debug::inApp()->operatingSystem()) @@ -391,10 +419,11 @@ public function usage(): string Url::of('/_profiler/'), Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $profile = Reader::default()($response->body()) + $profile = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('main')) ->flatMap(Element::of('li')) @@ -409,27 +438,24 @@ public function usage(): string $profile, Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $sections = Reader::default()($response->body()) + $sections = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('header')) - ->map(static fn($header) => Elements::of('li')($header)) - ->map( - static fn($lis) => $lis - ->flatMap(Elements::of('a')) - ->map(static fn($a) => $a->content()) - ->map(Str::of(...)) - ->map(static fn($text) => $text->trim()->toString()), - ) - ->match( - static fn($sections) => $sections->toList(), - static fn() => [], - ); + ->toSequence() + ->flatMap(Elements::of('li')) + ->flatMap(Elements::of('a')) + ->flatMap(static fn($a) => $a->normalize()->children()) + ->map(static fn($a) => $a->content()) + ->map(Str::of(...)) + ->map(static fn($text) => $text->trim()->toString()) + ->toList(); $this->assertSame( - ['Exception', 'Environment', 'Processes', 'Remote/Http', 'App graph'], + ['Exception', 'Environment', 'Processes', 'Remote/Http'], $sections, ); } @@ -439,24 +465,26 @@ public function testRecordCliContextWithoutOSLayer() $app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) ->map(Debug::inApp()->app()) - ->command(static fn($_, $os) => new class($os) implements CliCommand { + ->service(_Services::os, static fn($_, $os) => $os) + ->command(static fn($get) => new class($get(_Services::os)) implements CliCommand { public function __construct(private $os) { } - public function __invoke(Console $console): Console + public function __invoke(Console $console): Attempt { - $this + $_ = $this ->os ->control() ->processes() ->execute(Command::foreground('cat hello')) - ->wait() + ->maybe() + ->flatMap(static fn($process) => $process->wait()->maybe()) ->match( static fn() => null, static fn() => null, ); - $this + $_ = $this ->os ->remote() ->http()(Request::of( @@ -469,27 +497,29 @@ public function __invoke(Console $console): Console static fn() => null, ); - throw new \Exception('foo'); + return Attempt::error(new \Exception('foo')); } - public function usage(): string + public function usage(): Usage { - return 'hello-world'; + return Usage::parse('hello-world'); } }); - try { - $app->run(InMemory::of( + $error = $app + ->run(CliEnvironment::inMemory( [], true, ['bin', 'hello-world'], [['PATH', \getenv('PATH')]], '/', - )); - $this->fail('it should throw'); - } catch (\Exception $e) { - $this->assertSame('foo', $e->getMessage()); - } + )) + ->match( + static fn() => null, + static fn($e) => $e, + ); + + $this->assertSame('foo', $error->getMessage()); $app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Debug::inApp()->operatingSystem()) @@ -500,10 +530,11 @@ public function usage(): string Url::of('/_profiler/'), Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $profile = Reader::default()($response->body()) + $profile = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('main')) ->flatMap(Element::of('li')) @@ -518,27 +549,24 @@ public function usage(): string $profile, Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $sections = Reader::default()($response->body()) + $sections = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('header')) - ->map(static fn($header) => Elements::of('li')($header)) - ->map( - static fn($lis) => $lis - ->flatMap(Elements::of('a')) - ->map(static fn($a) => $a->content()) - ->map(Str::of(...)) - ->map(static fn($text) => $text->trim()->toString()), - ) - ->match( - static fn($sections) => $sections->toList(), - static fn() => [], - ); + ->toSequence() + ->flatMap(Elements::of('li')) + ->flatMap(Elements::of('a')) + ->flatMap(static fn($a) => $a->normalize()->children()) + ->map(static fn($a) => $a->content()) + ->map(Str::of(...)) + ->map(static fn($text) => $text->trim()->toString()) + ->toList(); $this->assertSame( - ['Exception', 'Environment', 'App graph'], + ['Exception', 'Environment'], $sections, ); } @@ -548,29 +576,25 @@ public function testRecordCliReturnCode() $app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) ->map(Debug::inApp()->app()) - ->command(static fn($_, $os) => new class($os) implements CliCommand { - public function __construct(private $os) - { - } - - public function __invoke(Console $console): Console + ->command(static fn() => new class implements CliCommand { + public function __invoke(Console $console): Attempt { - return $console->exit(1); + return Attempt::result($console->exit(1)); } - public function usage(): string + public function usage(): Usage { - return 'hello-world'; + return Usage::parse('hello-world'); } }); - $environment = $app->run(InMemory::of( + $environment = $app->run(CliEnvironment::inMemory( [], true, ['bin', 'hello-world'], [['PATH', \getenv('PATH')]], '/', - )); + ))->unwrap(); $this->assertSame(1, $environment->exitCode()->match( static fn($exit) => $exit->toInt(), @@ -586,10 +610,11 @@ public function usage(): string Url::of('/_profiler/'), Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $profile = Reader::default()($response->body()) + $profile = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('main')) ->flatMap(Element::of('li')) @@ -604,27 +629,24 @@ public function usage(): string $profile, Method::get, ProtocolVersion::v11, - )); + ))->unwrap(); $this->assertSame(StatusCode::ok, $response->statusCode()); - $sections = Reader::default()($response->body()) + $sections = Reader::new()($response->body()) + ->maybe() ->flatMap(Element::body()) ->flatMap(Element::of('header')) - ->map(static fn($header) => Elements::of('li')($header)) - ->map( - static fn($lis) => $lis - ->flatMap(Elements::of('a')) - ->map(static fn($a) => $a->content()) - ->map(Str::of(...)) - ->map(static fn($text) => $text->trim()->toString()), - ) - ->match( - static fn($sections) => $sections->toList(), - static fn() => [], - ); + ->toSequence() + ->flatMap(Elements::of('li')) + ->flatMap(Elements::of('a')) + ->flatMap(static fn($a) => $a->normalize()->children()) + ->map(static fn($a) => $a->content()) + ->map(Str::of(...)) + ->map(static fn($text) => $text->trim()->toString()) + ->toList(); $this->assertSame( - ['Environment', 'App graph'], + ['Environment'], $sections, ); } @@ -634,44 +656,36 @@ public function testMakeSureCorrectCommandIsRun() $app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) ->map(Debug::inApp()->app()) - ->command(static fn($_, $os) => new class($os) implements CliCommand { - public function __construct(private $os) - { - } - - public function __invoke(Console $console): Console + ->command(static fn() => new class implements CliCommand { + public function __invoke(Console $console): Attempt { - return $console->exit(1); + return Attempt::result($console->exit(1)); } - public function usage(): string + public function usage(): Usage { - return 'foo'; + return Usage::parse('foo'); } }) - ->command(static fn($_, $os) => new class($os) implements CliCommand { - public function __construct(private $os) + ->command(static fn() => new class implements CliCommand { + public function __invoke(Console $console): Attempt { + return Attempt::result($console); } - public function __invoke(Console $console): Console + public function usage(): Usage { - return $console; - } - - public function usage(): string - { - return 'hello-world'; + return Usage::parse('hello-world'); } }); - $environment = $app->run(InMemory::of( + $environment = $app->run(CliEnvironment::inMemory( [], true, ['bin', 'hello-world'], [['PATH', \getenv('PATH')]], '/', - )); + ))->unwrap(); $this->assertNull($environment->exitCode()->match( static fn($exit) => $exit->toInt(), From 2dba2c51504c4422c006368f781dcc096913aa58 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 12 Apr 2026 11:56:01 +0200 Subject: [PATCH 2/9] store test profiles in a local folder --- .gitignore | 1 + tests/FunctionalTest.php | 2 +- var/.gitkeep | 0 3 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 var/.gitkeep diff --git a/.gitignore b/.gitignore index ff72e2d..4f88347 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /composer.lock /vendor +/var/* diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index e346256..7f390ee 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -53,7 +53,7 @@ class FunctionalTest extends TestCase public function setUp(): void { - $this->storage = Path::of(\sys_get_temp_dir().'/innmind_debug/'); + $this->storage = Path::of(\dirname(__DIR__).'/var/'); } public function tearDown(): void diff --git a/var/.gitkeep b/var/.gitkeep new file mode 100644 index 0000000..e69de29 From de04fe406c0fe05cd5f1fcd562056e57191b7545 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 12 Apr 2026 11:57:58 +0200 Subject: [PATCH 3/9] discard psalm error --- psalm.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/psalm.xml b/psalm.xml index 5d768ff..a270b9b 100644 --- a/psalm.xml +++ b/psalm.xml @@ -14,4 +14,7 @@ + + + From 0e4a1b1e0b5b4189b90940176e667a79abb85e9d Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 12 Apr 2026 12:06:29 +0200 Subject: [PATCH 4/9] avoid deleting gitkeep --- tests/FunctionalTest.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index 7f390ee..a2e856a 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -59,9 +59,13 @@ public function setUp(): void public function tearDown(): void { $storage = Adapter::mount($this->storage)->unwrap(); - $_ = $storage->root()->foreach( - static fn($file) => $storage->remove($file->name()), - ); + $_ = $storage + ->root() + ->all() + ->exclude(static fn($file) => $file->name()->toString() === '.gitkeep') + ->foreach( + static fn($file) => $storage->remove($file->name()), + ); } public function testRecordHttpContext() From 00d1685cd10749efc56913c5d581618f273b6845 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 12 Apr 2026 12:10:38 +0200 Subject: [PATCH 5/9] remove local path from stack traces --- tests/FunctionalTest.php | 49 ++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index a2e856a..dde4011 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -70,9 +70,12 @@ public function tearDown(): void public function testRecordHttpContext() { + $debug = Debug::inApp()->removePathFromStackTrace( + Url::of(__DIR__), + ); $app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) - ->map(Debug::inApp()) + ->map($debug) ->service(_Services::os, static fn($_, $os) => $os) ->route( static fn($pipe, $get) => $pipe @@ -188,9 +191,12 @@ static function() use ($get) { public function testRecordHttpContextWithoutOSLayer() { + $debug = Debug::inApp()->removePathFromStackTrace( + Url::of(__DIR__), + ); $app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) - ->map(Debug::inApp()->app()) + ->map($debug->app()) ->service(_Services::os, static fn($_, $os) => $os) ->route( static fn($pipe, $get) => $pipe @@ -286,10 +292,13 @@ static function() use ($get) { public function testRecordHttpResponse() { + $debug = Debug::inApp()->removePathFromStackTrace( + Url::of(__DIR__), + ); $app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) - ->map(Debug::inApp()->operatingSystem()) + ->map($debug->operatingSystem()) ->map(Profiler::inApp($this->storage)) - ->map(Debug::inApp()->app()) + ->map($debug->app()) ->route( static fn($pipe, $get) => $pipe ->get() @@ -355,9 +364,12 @@ public function testRecordHttpResponse() public function testRecordCliContext() { + $debug = Debug::inApp()->removePathFromStackTrace( + Url::of(__DIR__), + ); $app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) - ->map(Debug::inApp()) + ->map($debug) ->service(_Services::os, static fn($_, $os) => $os) ->command(static fn($get) => new class($get(_Services::os)) implements CliCommand { public function __construct(private $os) @@ -415,9 +427,9 @@ public function usage(): Usage $this->assertSame('foo', $error->getMessage()); $app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) - ->map(Debug::inApp()->operatingSystem()) + ->map($debug->operatingSystem()) ->map(Profiler::inApp($this->storage)) - ->map(Debug::inApp()->app()); + ->map($debug->app()); $response = $app->run(ServerRequest::of( Url::of('/_profiler/'), @@ -466,9 +478,12 @@ public function usage(): Usage public function testRecordCliContextWithoutOSLayer() { + $debug = Debug::inApp()->removePathFromStackTrace( + Url::of(__DIR__), + ); $app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) - ->map(Debug::inApp()->app()) + ->map($debug->app()) ->service(_Services::os, static fn($_, $os) => $os) ->command(static fn($get) => new class($get(_Services::os)) implements CliCommand { public function __construct(private $os) @@ -526,9 +541,9 @@ public function usage(): Usage $this->assertSame('foo', $error->getMessage()); $app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) - ->map(Debug::inApp()->operatingSystem()) + ->map($debug->operatingSystem()) ->map(Profiler::inApp($this->storage)) - ->map(Debug::inApp()->app()); + ->map($debug->app()); $response = $app->run(ServerRequest::of( Url::of('/_profiler/'), @@ -577,9 +592,12 @@ public function usage(): Usage public function testRecordCliReturnCode() { + $debug = Debug::inApp()->removePathFromStackTrace( + Url::of(__DIR__), + ); $app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) - ->map(Debug::inApp()->app()) + ->map($debug->app()) ->command(static fn() => new class implements CliCommand { public function __invoke(Console $console): Attempt { @@ -606,9 +624,9 @@ public function usage(): Usage )); $app = Application::http(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) - ->map(Debug::inApp()->operatingSystem()) + ->map($debug->operatingSystem()) ->map(Profiler::inApp($this->storage)) - ->map(Debug::inApp()->app()); + ->map($debug->app()); $response = $app->run(ServerRequest::of( Url::of('/_profiler/'), @@ -657,9 +675,12 @@ public function usage(): Usage public function testMakeSureCorrectCommandIsRun() { + $debug = Debug::inApp()->removePathFromStackTrace( + Url::of(__DIR__), + ); $app = Application::cli(Factory::build(), Environment::test([['PATH', \getenv('PATH')]])) ->map(Profiler::inApp($this->storage)) - ->map(Debug::inApp()->app()) + ->map($debug->app()) ->command(static fn() => new class implements CliCommand { public function __invoke(Console $console): Attempt { From 7cb2631a27a618cb39c99f97d97c995cc9d424b4 Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 12 Apr 2026 14:21:02 +0200 Subject: [PATCH 6/9] mention the processes output and exit code are no longer recorded --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 545cbb2..20796b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ ### Removed - App graph is no longer recorded +- The processes output and exit code are not recorded ## 4.0.0 - 2023-11-26 From bcaa3dada915d995b24a176f1dc1dd77fd0aad2f Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 12 Apr 2026 14:21:11 +0200 Subject: [PATCH 7/9] add missing internal flags --- src/Cli/Record.php | 3 +++ src/Http/EndProfile.php | 3 +++ src/Http/RecordException.php | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/Cli/Record.php b/src/Cli/Record.php index eee8c89..b656aaf 100644 --- a/src/Cli/Record.php +++ b/src/Cli/Record.php @@ -68,6 +68,9 @@ public function __invoke(Console $console): Attempt }); } + /** + * @internal + */ public static function of( Command $inner, Profiler $profiler, diff --git a/src/Http/EndProfile.php b/src/Http/EndProfile.php index 35d8f1c..f59ed11 100644 --- a/src/Http/EndProfile.php +++ b/src/Http/EndProfile.php @@ -25,6 +25,9 @@ private function __construct( ) { } + /** + * @internal + */ public static function new(): self { return new self; diff --git a/src/Http/RecordException.php b/src/Http/RecordException.php index 1a97794..43209a5 100644 --- a/src/Http/RecordException.php +++ b/src/Http/RecordException.php @@ -59,6 +59,9 @@ public function __invoke(\Throwable $e): Component ) => $result); } + /** + * @internal + */ public static function of(Exception $record): self { return new self($record); From 560d08e82f74083ce0dd8ca5255cd782655a9ced Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 12 Apr 2026 14:23:18 +0200 Subject: [PATCH 8/9] debug exception renderer --- src/Recorder/Exception.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Recorder/Exception.php b/src/Recorder/Exception.php index 98de618..95b3622 100644 --- a/src/Recorder/Exception.php +++ b/src/Recorder/Exception.php @@ -60,6 +60,8 @@ public function __construct( */ public function __invoke(\Throwable $e): Attempt { + $content = ($this->render)(StackTrace::of($e)); + dump($content->toString()); $graph = $this ->os ->control() @@ -68,7 +70,7 @@ public function __invoke(\Throwable $e): Attempt Command::foreground('dot') ->withShortOption('Tsvg') ->withEnvironments($this->env) - ->withInput(($this->render)(StackTrace::of($e))), + ->withInput($content), ) ->maybe() ->flatMap(static fn($process) => $process->wait()->maybe()) From fd002bd0968505681af01774098295fb74c2cd4b Mon Sep 17 00:00:00 2001 From: Baptiste Langlade Date: Sun, 12 Apr 2026 14:27:24 +0200 Subject: [PATCH 9/9] Revert "debug exception renderer" This reverts commit 560d08e82f74083ce0dd8ca5255cd782655a9ced. --- src/Recorder/Exception.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Recorder/Exception.php b/src/Recorder/Exception.php index 95b3622..98de618 100644 --- a/src/Recorder/Exception.php +++ b/src/Recorder/Exception.php @@ -60,8 +60,6 @@ public function __construct( */ public function __invoke(\Throwable $e): Attempt { - $content = ($this->render)(StackTrace::of($e)); - dump($content->toString()); $graph = $this ->os ->control() @@ -70,7 +68,7 @@ public function __invoke(\Throwable $e): Attempt Command::foreground('dot') ->withShortOption('Tsvg') ->withEnvironments($this->env) - ->withInput($content), + ->withInput(($this->render)(StackTrace::of($e))), ) ->maybe() ->flatMap(static fn($process) => $process->wait()->maybe())