diff --git a/.code-samples.meilisearch.yaml b/.code-samples.meilisearch.yaml index bedde7a8..4f95b685 100644 --- a/.code-samples.meilisearch.yaml +++ b/.code-samples.meilisearch.yaml @@ -612,3 +612,12 @@ patch_dynamic_search_rule_1: |- ]); delete_dynamic_search_rule_1: |- $client->deleteDynamicSearchRule('RULE_UID'); +render_template_1: |- + $client->patch('/experimental-features', ['renderRoute' => true]); + + $client->renderTemplate( + new TemplateRenderQuery( + ['kind' => 'inlineDocumentTemplate', 'inline' => '{{ doc.breed }} called {{ doc.name }}'], + ['kind' => 'inlineDocument', 'inline' => ['breed' => 'Jack Russell', 'name' => 'Iko']], + ) + ); diff --git a/src/Client.php b/src/Client.php index 98fefe39..8086ec59 100644 --- a/src/Client.php +++ b/src/Client.php @@ -17,6 +17,7 @@ use Meilisearch\Endpoints\Delegates\HandlesSnapshots; use Meilisearch\Endpoints\Delegates\HandlesSystem; use Meilisearch\Endpoints\Delegates\HandlesTasks; +use Meilisearch\Endpoints\Delegates\HandlesTemplates; use Meilisearch\Endpoints\Dumps; use Meilisearch\Endpoints\DynamicSearchRules; use Meilisearch\Endpoints\Health; @@ -26,6 +27,7 @@ use Meilisearch\Endpoints\Snapshots; use Meilisearch\Endpoints\Stats; use Meilisearch\Endpoints\Tasks; +use Meilisearch\Endpoints\Templates; use Meilisearch\Endpoints\TenantToken; use Meilisearch\Endpoints\Version; use Meilisearch\Http\Client as MeilisearchClientAdapter; @@ -46,6 +48,7 @@ class Client use HandlesBatches; use HandlesNetwork; use HandlesDynamicSearchRules; + use HandlesTemplates; /** * @param array $clientAgents @@ -72,5 +75,6 @@ public function __construct( $this->tenantToken = new TenantToken($this->http, $apiKey); $this->network = new Network($this->http); $this->dynamicSearchRules = new DynamicSearchRules($this->http); + $this->templates = new Templates($this->http); } } diff --git a/src/Contracts/TemplateRenderQuery.php b/src/Contracts/TemplateRenderQuery.php new file mode 100644 index 00000000..0d95f0f9 --- /dev/null +++ b/src/Contracts/TemplateRenderQuery.php @@ -0,0 +1,87 @@ +}|array{kind: 'indexDocument', indexUid: non-empty-string, id: string|int}|null + */ + private ?array $input = null; + + private bool $inputSet = false; + + /** + * @param array{kind: 'inlineDocumentTemplate', inline: string}|array{kind: 'documentTemplate', indexUid: non-empty-string, embedder: non-empty-string}|array{kind: 'documentTemplate', indexUid: non-empty-string, templateUid: non-empty-string} $template + * @param array{kind: 'inlineDocument', inline: array}|array{kind: 'indexDocument', indexUid: non-empty-string, id: string|int}|null $input + */ + public function __construct(array $template, ?array $input = null) + { + $this->template = $template; + + if (\func_num_args() >= 2) { + $this->input = $input; + $this->inputSet = true; + } + } + + /** + * Set the template to render. + * + * Supports two kinds: + * - inlineDocumentTemplate: ['kind' => 'inlineDocumentTemplate', 'inline' => '{{ doc.name }}'] + * - documentTemplate: ['kind' => 'documentTemplate', 'indexUid' => 'movies', 'embedder' => 'myEmbedder'] + * or ['kind' => 'documentTemplate', 'indexUid' => 'movies', 'templateUid' => 'myTemplate'] + * + * @param array{kind: 'inlineDocumentTemplate', inline: string}|array{kind: 'documentTemplate', indexUid: non-empty-string, embedder: non-empty-string}|array{kind: 'documentTemplate', indexUid: non-empty-string, templateUid: non-empty-string} $template + */ + public function setTemplate(array $template): self + { + $this->template = $template; + + return $this; + } + + /** + * Set the input document for template rendering. + * + * Supports two kinds: + * - inlineDocument: ['kind' => 'inlineDocument', 'inline' => ['name' => 'John']] + * - indexDocument: ['kind' => 'indexDocument', 'indexUid' => 'movies', 'id' => '2'] + * + * Pass null to explicitly send null input (API returns rendered: null). + * Omit this call entirely to not include input in the request. + * + * @param array{kind: 'inlineDocument', inline: array}|array{kind: 'indexDocument', indexUid: non-empty-string, id: string|int}|null $input + */ + public function setInput(?array $input): self + { + $this->input = $input; + $this->inputSet = true; + + return $this; + } + + /** + * @return array{template: array{kind: 'inlineDocumentTemplate', inline: string}|array{kind: 'documentTemplate', indexUid: non-empty-string, embedder: non-empty-string}|array{kind: 'documentTemplate', indexUid: non-empty-string, templateUid: non-empty-string}, input?: array{kind: 'inlineDocument', inline: array}|array{kind: 'indexDocument', indexUid: non-empty-string, id: string|int}|null} + */ + public function toArray(): array + { + $result = [ + 'template' => $this->template, + ]; + + if ($this->inputSet) { + $result['input'] = $this->input; + } + + return $result; + } +} diff --git a/src/Contracts/TemplateRenderResult.php b/src/Contracts/TemplateRenderResult.php new file mode 100644 index 00000000..94d69f60 --- /dev/null +++ b/src/Contracts/TemplateRenderResult.php @@ -0,0 +1,30 @@ +template = $data['template']; + $this->rendered = $data['rendered']; + } + + public function getTemplate(): string + { + return $this->template; + } + + public function getRendered(): ?string + { + return $this->rendered; + } +} diff --git a/src/Endpoints/Delegates/HandlesTemplates.php b/src/Endpoints/Delegates/HandlesTemplates.php new file mode 100644 index 00000000..ccbb1575 --- /dev/null +++ b/src/Endpoints/Delegates/HandlesTemplates.php @@ -0,0 +1,19 @@ +templates->render($query); + } +} diff --git a/src/Endpoints/Templates.php b/src/Endpoints/Templates.php new file mode 100644 index 00000000..2c408e13 --- /dev/null +++ b/src/Endpoints/Templates.php @@ -0,0 +1,21 @@ +http->post(self::PATH, $query->toArray()); + + return new TemplateRenderResult($response); + } +} diff --git a/tests/Contracts/TemplateRenderQueryTest.php b/tests/Contracts/TemplateRenderQueryTest.php new file mode 100644 index 00000000..98b96626 --- /dev/null +++ b/tests/Contracts/TemplateRenderQueryTest.php @@ -0,0 +1,69 @@ + 'inlineDocumentTemplate', 'inline' => '{{ doc.name }}'], + ['kind' => 'inlineDocument', 'inline' => ['name' => 'John']], + ); + + $result = $query->toArray(); + + self::assertSame([ + 'template' => ['kind' => 'inlineDocumentTemplate', 'inline' => '{{ doc.name }}'], + 'input' => ['kind' => 'inlineDocument', 'inline' => ['name' => 'John']], + ], $result); + } + + public function testToArrayWithDocumentTemplateAndIndexDocumentInput(): void + { + $query = new TemplateRenderQuery( + ['kind' => 'documentTemplate', 'indexUid' => 'movies', 'embedder' => 'myEmbedder'], + ['kind' => 'indexDocument', 'indexUid' => 'movies', 'id' => '2'], + ); + + $result = $query->toArray(); + + self::assertSame([ + 'template' => ['kind' => 'documentTemplate', 'indexUid' => 'movies', 'embedder' => 'myEmbedder'], + 'input' => ['kind' => 'indexDocument', 'indexUid' => 'movies', 'id' => '2'], + ], $result); + } + + public function testToArrayOmitsInputWhenNotSet(): void + { + $query = new TemplateRenderQuery( + ['kind' => 'inlineDocumentTemplate', 'inline' => '{{ doc.name }}'], + // input omitted — not passed + ); + + $result = $query->toArray(); + + self::assertArrayNotHasKey('input', $result); + self::assertSame([ + 'template' => ['kind' => 'inlineDocumentTemplate', 'inline' => '{{ doc.name }}'], + ], $result); + } + + public function testToArrayIncludesNullInputWhenExplicitlySet(): void + { + $query = new TemplateRenderQuery( + ['kind' => 'inlineDocumentTemplate', 'inline' => '{{ doc.name }}'], + null, + ); + + $result = $query->toArray(); + + self::assertArrayHasKey('input', $result); + self::assertNull($result['input']); + } +} diff --git a/tests/Endpoints/TemplatesTest.php b/tests/Endpoints/TemplatesTest.php new file mode 100644 index 00000000..3bc39241 --- /dev/null +++ b/tests/Endpoints/TemplatesTest.php @@ -0,0 +1,54 @@ +host, getenv('MEILISEARCH_API_KEY')); + $http->patch('/experimental-features', ['renderRoute' => true]); + } + + protected function tearDown(): void + { + $http = new Client($this->host, getenv('MEILISEARCH_API_KEY')); + $http->patch('/experimental-features', ['renderRoute' => false]); + + parent::tearDown(); + } + + public function testCanRenderInlineTemplate(): void + { + $query = new TemplateRenderQuery( + ['kind' => 'inlineDocumentTemplate', 'inline' => '{{ doc.breed }} called {{ doc.name }}'], + ['kind' => 'inlineDocument', 'inline' => ['breed' => 'Jack Russell', 'name' => 'Iko']], + ); + + $response = $this->client->renderTemplate($query); + + self::assertSame('{{ doc.breed }} called {{ doc.name }}', $response->getTemplate()); + self::assertSame('Jack Russell called Iko', $response->getRendered()); + } + + public function testCanRenderTemplateWithNullInput(): void + { + $query = new TemplateRenderQuery( + ['kind' => 'inlineDocumentTemplate', 'inline' => '{{ doc.breed }} called {{ doc.name }}'], + null, + ); + + $response = $this->client->renderTemplate($query); + + self::assertSame('{{ doc.breed }} called {{ doc.name }}', $response->getTemplate()); + self::assertNull($response->getRendered()); + } +}