• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

eliashaeussler / typo3-solver / 13271925856

11 Feb 2025 08:29PM UTC coverage: 81.979% (-6.2%) from 88.153%
13271925856

Pull #295

github

web-flow
Merge 140b2a87d into dc3a40050
Pull Request #295: [FEATURE] Add Gemini as additional solution provider

8 of 90 new or added lines in 7 files covered. (8.89%)

1 existing line in 1 file now uncovered.

878 of 1071 relevant lines covered (81.98%)

2.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/Classes/Error/AiSolverExceptionHandler.php
1
<?php
2

3
declare(strict_types=1);
4

5
/*
6
 * This file is part of the TYPO3 CMS extension "solver".
7
 *
8
 * Copyright (C) 2023-2025 Elias Häußler <elias@haeussler.dev>
9
 *
10
 * This program is free software: you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation, either version 2 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23

24
namespace EliasHaeussler\Typo3Solver\Error;
25

26
use EliasHaeussler\Typo3Solver\Authentication;
27
use EliasHaeussler\Typo3Solver\Cache;
28
use EliasHaeussler\Typo3Solver\Configuration;
29
use EliasHaeussler\Typo3Solver\Exception;
30
use EliasHaeussler\Typo3Solver\Formatter;
31
use EliasHaeussler\Typo3Solver\Middleware;
32
use EliasHaeussler\Typo3Solver\ProblemSolving;
33
use EliasHaeussler\Typo3Solver\Utility;
34
use EliasHaeussler\Typo3Solver\View;
35
use GuzzleHttp\Client;
36
use GuzzleHttp\HandlerStack;
37
use GuzzleHttp\RequestOptions;
38
use TYPO3\CMS\Core;
39

40
/**
41
 * AiSolverExceptionHandler.
42
 *
43
 * @author Elias Häußler <elias@haeussler.dev>
44
 * @license GPL-2.0-or-later
45
 */
46
final class AiSolverExceptionHandler extends Core\Error\DebugExceptionHandler
47
{
48
    private readonly Client $client;
49
    private readonly Configuration\Configuration $configuration;
50
    private readonly Formatter\Message\ExceptionFormatter $exceptionFormatter;
51
    private readonly Cache\ExceptionsCache $exceptionsCache;
52
    private readonly Cache\SolutionsCache $solutionsCache;
53
    private readonly Formatter\CliFormatter $cliFormatter;
54
    private readonly Formatter\WebFormatter $webFormatter;
55

56
    public function __construct()
×
57
    {
58
        parent::__construct();
×
59

60
        $renderer = new View\TemplateRenderer();
×
61

62
        $this->client = $this->createClient();
×
63
        $this->configuration = new Configuration\Configuration();
×
64
        $this->exceptionFormatter = new Formatter\Message\ExceptionFormatter($renderer);
×
65
        $this->exceptionsCache = new Cache\ExceptionsCache();
×
66
        $this->solutionsCache = new Cache\SolutionsCache();
×
67
        $this->cliFormatter = new Formatter\CliFormatter($renderer);
×
68
        $this->webFormatter = new Formatter\WebFormatter(
×
69
            $this->exceptionsCache,
×
70
            $renderer,
×
71
            new Authentication\StreamAuthentication(),
×
72
        );
×
73
    }
74

75
    public function echoExceptionCLI(\Throwable $exception): void
×
76
    {
77
        try {
78
            $solver = new ProblemSolving\Solver($this->configuration, $this->cliFormatter);
×
79
            $solution = $solver->solve($exception);
×
80

81
            if ($solution !== null) {
×
82
                echo $solution;
×
83
            }
84
        } catch (Exception\UnableToSolveException) {
×
85
            // Intended fallthrough.
86
        }
87

88
        parent::echoExceptionCLI($exception);
×
89
    }
90

91
    protected function getContent(\Throwable $throwable): string
×
92
    {
93
        $content = parent::getContent($throwable);
×
94
        $serverRequest = Utility\HttpUtility::getServerRequest();
×
95

96
        // Early return if solver is disabled
97
        if ($serverRequest->getQueryParams()['disableSolver'] ?? false) {
×
98
            return $content;
×
99
        }
100

101
        try {
NEW
102
            $solutionProvider = $this->configuration->getProvider();
×
103

104
            // Use solution stream if possible
NEW
105
            if ($solutionProvider->canBeUsed($throwable) && $this->isStreamedResponseSupported()) {
×
NEW
106
                $this->exceptionsCache->set($throwable);
×
107

NEW
108
                $solutionProvider = new ProblemSolving\Solution\Provider\DelegatingCacheSolutionProvider(
×
NEW
109
                    $this->solutionsCache,
×
NEW
110
                    $solutionProvider,
×
NEW
111
                );
×
112
            }
113

UNCOV
114
            $solver = new ProblemSolving\Solver($this->configuration, $this->webFormatter, $solutionProvider);
×
115
            $solution = $solver->solve($throwable);
×
116

117
            if ($solution !== null) {
×
118
                $solution .= $this->webFormatter->getAdditionalScripts();
×
119
            }
120
        } catch (\Throwable $exception) {
×
121
            $solution = $this->exceptionFormatter->format($exception);
×
122
        }
123

124
        if ($solution === null) {
×
125
            return $content;
×
126
        }
127

128
        return Utility\StringUtility::replaceFirstOccurrence(
×
129
            '<div class="trace">',
×
130
            $solution . '<div class="trace">',
×
131
            $content,
×
132
        );
×
133
    }
134

135
    protected function getStylesheet(): string
×
136
    {
137
        return parent::getStylesheet() . $this->webFormatter->getAdditionalStyles();
×
138
    }
139

140
    private function isStreamedResponseSupported(): bool
×
141
    {
142
        $serverRequest = Utility\HttpUtility::getServerRequest();
×
143
        $pingUri = $serverRequest->getUri()
×
144
            ->withPath(Middleware\PingMiddleware::ROUTE_PATH)
×
145
            ->withQuery('disableSolver=1')
×
146
        ;
×
147
        $pingResponse = $this->client->request('GET', $pingUri, [
×
148
            RequestOptions::HEADERS => [
×
149
                'Accept' => 'text/event-stream',
×
150
            ],
×
151
        ]);
×
152

153
        return $pingResponse->getStatusCode() === 200;
×
154
    }
155

156
    private function createClient(): Client
×
157
    {
158
        $handler = HandlerStack::create();
×
159
        $handler->remove('http_errors');
×
160

161
        return new Client(['handler' => $handler]);
×
162
    }
163
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc