Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: query-string-parser exception #133

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
54 changes: 42 additions & 12 deletions packages/Agent/src/Builder/AgentFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ class AgentFactory

public static ?array $fileCacheOptions;

protected ?DatasourceContract $datasource = null;
protected bool $isBuild = false;

protected ?DatasourceContract $computedDatasource = null;

public function __construct(protected array $config)
{
Expand All @@ -46,14 +48,11 @@ public function createAgent(array $config): self
$this->config = array_merge($this->config, $config);
$this->buildLogger();
if ($this->hasEnvSecret) {
$serializableConfig = $this->config;

if (isset($this->config['customizeErrorMessage']) && is_callable($this->config['customizeErrorMessage']) && ! is_string($this->config['customizeErrorMessage'])) {
Cache::put('customizeErrorMessage', new SerializableClosure($this->config['customizeErrorMessage']));
}

unset($serializableConfig['logger'], $serializableConfig['customizeErrorMessage']);
Cache::put('config', $serializableConfig, self::TTL_CONFIG);
Cache::put('config', $this->serializeConfig(), self::TTL_CONFIG);
}

Cache::put('forestAgent', new SerializableClosure(fn () => $this), self::TTL);
Expand Down Expand Up @@ -84,11 +83,11 @@ public function use(string $plugin, array $options = []): self

public function build(): void
{
if ($this->datasource === null) {
$this->datasource = $this->customizer->getDatasource();
if (! $this->isBuild) {
$this->computedDatasource = $this->customizer->getDatasource();
Cache::put('forestAgent', new SerializableClosure(fn () => $this), self::TTL);

self::sendSchema();
$this->isBuild = true;
}
}

Expand Down Expand Up @@ -133,9 +132,8 @@ public static function getDatasource()
{
/** @var self $instance */
$forestAgentClosure = Cache::get('forestAgent');
$instance = $forestAgentClosure();

return $instance->getDatasourceInstance();
return $forestAgentClosure()->getDatasourceInstance();
}

/**
Expand All @@ -162,6 +160,14 @@ public static function sendSchema(bool $force = false): void
}
}

private function serializeConfig(): array
{
$serializableConfig = $this->config;
unset($serializableConfig['logger'], $serializableConfig['customizeErrorMessage']);

return $serializableConfig;
}

private function buildCache(): void
{
if ($this->hasEnvSecret) {
Expand All @@ -172,7 +178,7 @@ private function buildCache(): void
self::$fileCacheOptions = compact('filesystem', 'directory', 'disabledApcuCache');
}

Cache::add('config', $this->config, self::TTL_CONFIG);
Cache::add('config', $this->serializeConfig(), self::TTL_CONFIG);
}
}

Expand All @@ -188,6 +194,30 @@ private function buildLogger(): void

public function getDatasourceInstance(): ?DatasourceContract
{
return $this->datasource;
return $this->computedDatasource;
}

/**
* @codeCoverageIgnore
*/
public function __serialize(): array
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar blocks of code found in 2 locations. Consider refactoring.

{
return [
'hasEnvSecret' => $this->hasEnvSecret,
'isBuild' => $this->isBuild,
'computedDatasource' => $this->computedDatasource,
];
}

/**
* @codeCoverageIgnore
*/
public function __unserialize(array $data): void
{
$this->hasEnvSecret = $data['hasEnvSecret'];
$this->isBuild = $data['isBuild'];
$this->computedDatasource = $data['computedDatasource'];

$this->customizer = new DatasourceCustomizer();
}
}
11 changes: 7 additions & 4 deletions packages/Agent/src/Http/Traits/ErrorHandling.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace ForestAdmin\AgentPHP\Agent\Http\Traits;

use ForestAdmin\AgentPHP\Agent\Builder\AgentFactory;
use ForestAdmin\AgentPHP\DatasourceToolkit\Exceptions\ForestException;
use ReflectionClass;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
Expand All @@ -21,17 +22,19 @@ public function getErrorStatus(Throwable $error): int

public function getErrorMessage(Throwable $error): string
{
if ($error instanceof HttpException || is_subclass_of($error, HttpException::class)) {
return $error->getMessage();
}

if ($customizer = AgentFactory::get('customizeErrorMessage')) {
$message = $customizer($error);
if ($message) {
return $message;
}
}

if ($error instanceof HttpException ||
is_subclass_of($error, HttpException::class) ||
$error instanceof ForestException) {
return $error->getMessage();
}

return 'Unexpected error';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public function processIncludedResources(Scope $scope, $data)
$includedData[$include] = [
'data' => [
'type' => $item->getResourceKey(),
'id' => Id::packId(AgentFactory::get('datasource')->getCollection($this->name), $data),
'id' => Id::packId(AgentFactory::get('datasource')->getCollection($item->getResourceKey()), $item->getData()),
'attributes' => $item->getData(),
],
];
Expand Down
4 changes: 4 additions & 0 deletions packages/Agent/src/Utils/Id.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ public static function packId(CollectionContract $collection, array $record): st
throw new ForestException('This collection has no primary key');
}

if (empty($record)) {
throw new ForestException('This record does not exist');
}

return collect($primaryKeyNames)->map(fn ($pk) => $record[$pk])->join('|');
}

Expand Down
42 changes: 19 additions & 23 deletions packages/Agent/src/Utils/QueryStringParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,33 +57,29 @@ public static function parseConditionTree(CollectionContract $collection, Reques
*/
public static function parseProjection(CollectionContract $collection, Request $request): Projection
{
try {
$fields = $request->input('fields.' . $collection->getName());
$fields = $request->input('fields.' . $collection->getName());

if ($fields === null || $fields === '') {
return ProjectionFactory::all($collection);
}
$rootFields = collect(explode(',', $fields));
$explicitRequest = $rootFields->map(
static function ($field) use ($collection, $request) {
$field = trim($field);
$column = $collection->getFields()->get($field);

if (null !== $column && $column->getType() === 'PolymorphicManyToOne') {
return $field . ':*';
}

return null !== $column && $column->getType() === 'Column' ?
$field : $field . ':' . $request->input("fields.$field");
if ($fields === null || $fields === '') {
return ProjectionFactory::all($collection);
}
$rootFields = collect(explode(',', $fields));
$explicitRequest = $rootFields->map(
static function ($field) use ($collection, $request) {
$field = trim($field);
$column = $collection->getFields()->get($field);

if (null !== $column && $column->getType() === 'PolymorphicManyToOne') {
return $field . ':*';
}
);

ProjectionValidator::validate($collection, new Projection($explicitRequest->toArray()));
return null !== $column && $column->getType() === 'Column' ?
$field : $field . ':' . $request->input("fields.$field");
}
);

ProjectionValidator::validate($collection, new Projection($explicitRequest->toArray()));

return new Projection($explicitRequest->all());
} catch (\Exception $e) {
throw new ForestException('Invalid projection');
}
return new Projection($explicitRequest->all());
}

public static function parseProjectionWithPks(CollectionContract $collection, Request $request): Projection
Expand Down
15 changes: 11 additions & 4 deletions packages/BaseDatasource/src/BaseDatasource.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ private function makeOrm(array $databaseConfig): void
{
$this->orm = new Manager();
$this->orm->addConnection($databaseConfig);
$this->orm->bootEloquent();
}

private function makeDoctrineConnection(array $databaseConfig): void
Expand Down Expand Up @@ -84,16 +83,24 @@ private function makeDoctrineConnection(array $databaseConfig): void
/**
* @codeCoverageIgnore
*/
public function __sleep(): array
public function __serialize(): array
{
return ['collections', 'charts', 'databaseConfig'];
return [
'collections' => $this->collections,
'charts' => $this->charts,
'databaseConfig' => $this->databaseConfig,
];
}

/**
* @codeCoverageIgnore
*/
public function __wakeup(): void
public function __unserialize(array $data): void
{
$this->collections = $data['collections'];
$this->charts = $data['charts'];
$this->databaseConfig = $data['databaseConfig'];

$this->makeOrm($this->databaseConfig);
$this->makeDoctrineConnection($this->databaseConfig);
}
Expand Down
29 changes: 29 additions & 0 deletions packages/DatasourceEloquent/src/EloquentCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -338,4 +338,33 @@ private function nullablePolymorphicMorphManyRelationFields($record, $relationNa
// Do nothing
}
}

public function __serialize(): array
{
return [
'fields' => $this->fields,
'actions' => $this->actions,
'segments' => $this->segments,
'charts' => $this->charts,
'schema' => $this->schema,
'dataSource' => $this->dataSource,
'name' => $this->name,
'tableName' => $this->tableName,
'model' => $this->model::class,
];
}

public function __unserialize(array $data): void
{
$this->fields = $data['fields'];
$this->actions = $data['actions'];
$this->segments = $data['segments'];
$this->charts = $data['charts'];
$this->schema = $data['schema'];
$this->dataSource = $data['dataSource'];
$this->name = $data['name'];
$this->tableName = $data['tableName'];
$this->model = new $data['model']();
$this->nativeDriver = null;
}
}
22 changes: 22 additions & 0 deletions packages/DatasourceEloquent/src/EloquentDatasource.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,26 @@ public function getModels(): array
{
return $this->models;
}

/**
* @codeCoverageIgnore
*/
public function __serialize(): array
{
return array_merge(
parent::__serialize(),
['supportPolymorphicRelations' => $this->supportPolymorphicRelations]
);
}

/**
* @codeCoverageIgnore
*/
public function __unserialize(array $data): void
{
parent::__unserialize($data);

$finder = new ClassFinder(config('projectDir'));
$this->models = $finder->getModelsInNamespace('App');
}
}
4 changes: 0 additions & 4 deletions packages/DatasourceToolkit/src/CollectionMethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ public function setSegments(array $segments): Collection
*/
public function addField(string $name, ColumnSchema|RelationSchema $field): void
{
if ($this->fields->has($name)) {
throw new ForestException('Field ' . $name . ' already defined in collection');
}

$this->fields->put($name, $field);
}

Expand Down
5 changes: 2 additions & 3 deletions tests/Agent/Http/ForestControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
use ForestAdmin\AgentPHP\Agent\Http\ForestController;
use ForestAdmin\AgentPHP\Agent\Http\Request;
use ForestAdmin\AgentPHP\DatasourceToolkit\Datasource;
use ForestAdmin\AgentPHP\DatasourceToolkit\Exceptions\ForestException;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
Expand Down Expand Up @@ -59,7 +58,7 @@
->getMock();
$forestControllerMock->expects($this->once())
->method('getClosure')
->willReturn(fn () => throw new ForestException());
->willReturn(fn () => throw new \Exception());

$result = $forestControllerMock->__invoke($request);

Expand All @@ -72,7 +71,7 @@
[
'errors' => [
[
'name' => 'ForestException',
'name' => 'Exception',
'detail' => 'Unexpected error',
'status' => 500,
],
Expand Down
5 changes: 1 addition & 4 deletions tests/Agent/Http/Traits/ErrorHandlingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,6 @@

test('getErrorMessage() should return the original message when error is subclass of HttpException', function () {
$this->buildAgent(new Datasource());
$this->agent->createAgent([
'customizeErrorMessage' => fn ($error) => 'my custom message error',
]);
$trait = $this->getObjectForTrait(ErrorHandling::class);

expect($trait->getErrorMessage(new ForbiddenError('test')))->toEqual('test');
Expand All @@ -67,5 +64,5 @@
$this->buildAgent(new Datasource());
$trait = $this->getObjectForTrait(ErrorHandling::class);

expect($trait->getErrorMessage(new ForestException('test')))->toEqual('Unexpected error');
expect($trait->getErrorMessage(new \Exception('test')))->toEqual('Unexpected error');
});
3 changes: 3 additions & 0 deletions tests/Agent/Routes/Capabilities/CollectionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
'operators' => [
'In',
'Equal',
'Blank',
],
],
[
Expand All @@ -125,6 +126,7 @@
'operators' => [
'In',
'Equal',
'Blank',
],
],
[
Expand All @@ -133,6 +135,7 @@
'operators' => [
'In',
'Equal',
'Blank',
],
],
],
Expand Down
1 change: 0 additions & 1 deletion tests/Agent/Routes/Charts/ApiChartCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
->addChart('myChart', fn ($context, ResultBuilder $resultBuilder) => $resultBuilder->value(34))
->addChart('mySmartChart', fn ($context) => [])
);
$this->invokeProperty($this->agent, 'datasource', 'null');
$this->agent->build();
$_GET['record_id'] = 1;
$request = Request::createFromGlobals();
Expand Down
Loading
Loading