Skip to content

Commit def91d8

Browse files
committed
fix: fixing visibiloity for fields
1 parent e060b1a commit def91d8

File tree

2 files changed

+179
-4
lines changed

2 files changed

+179
-4
lines changed

src/Fields/OrganicField.php

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
use Binaryk\LaravelRestify\MCP\Requests\McpIndexRequest;
77
use Binaryk\LaravelRestify\MCP\Requests\McpRequestable;
88
use Binaryk\LaravelRestify\MCP\Requests\McpShowRequest;
9+
use Binaryk\LaravelRestify\MCP\Requests\McpStoreRequest;
10+
use Binaryk\LaravelRestify\MCP\Requests\McpUpdateRequest;
911
use Binaryk\LaravelRestify\Traits\ProxiesCanSeeToGate;
1012
use Closure;
1113
use Illuminate\Http\Request;
@@ -148,24 +150,49 @@ public function isHiddenOnIndex(RestifyRequest $request, $repository): bool
148150

149151
public function isShownOnMcp(RestifyRequest $request, $repository): bool
150152
{
151-
if ($this->isHidden($request)) {
153+
// Check if field is hidden from MCP
154+
if ($this->isHiddenFromMcp($request, $repository)) {
152155
return false;
153156
}
154157

155-
if ($this->isHiddenFromMcp($request, $repository)) {
158+
// For store and update requests, hidden fields should still be writable
159+
// So we check isHidden() only for show/index requests
160+
$isReadOperation = $request instanceof McpShowRequest || $request instanceof McpIndexRequest;
161+
162+
if ($isReadOperation && $this->isHidden($request)) {
156163
return false;
157164
}
158165

159166
if (is_callable($this->showOnMcp)) {
160167
return call_user_func($this->showOnMcp, $request, $repository);
161168
}
162169

170+
// For MCP show requests, check the base showOnShow property without recursion
163171
if ($request instanceof McpShowRequest) {
164-
return $this->isHiddenOnShow($request, $repository) === false;
172+
if (is_callable($this->showOnShow)) {
173+
$this->showOnShow = call_user_func($this->showOnShow, $request, $repository);
174+
}
175+
176+
return $this->showOnShow;
165177
}
166178

179+
// For MCP index requests, check the base showOnIndex property without recursion
167180
if ($request instanceof McpIndexRequest) {
168-
return $this->isHiddenOnIndex($request, $repository) === false;
181+
if (is_callable($this->showOnIndex)) {
182+
$this->showOnIndex = call_user_func($this->showOnIndex, $request, $repository);
183+
}
184+
185+
return $this->showOnIndex;
186+
}
187+
188+
// For MCP store requests, check if field is not readonly
189+
if ($request instanceof McpStoreRequest) {
190+
return ! $this->isReadonly($request);
191+
}
192+
193+
// For MCP update requests, check if field is not readonly
194+
if ($request instanceof McpUpdateRequest) {
195+
return ! $this->isReadonly($request);
169196
}
170197

171198
return $this->showOnMcp;
@@ -267,21 +294,41 @@ public function isReadonly(RestifyRequest $request)
267294

268295
public function isShownOnUpdate(RestifyRequest $request, $repository): bool
269296
{
297+
// Check MCP-specific visibility for MCP requests
298+
if ($request instanceof McpRequestable) {
299+
return $this->isShownOnMcp($request, $repository);
300+
}
301+
270302
return ! $this->isReadonly($request);
271303
}
272304

273305
public function isShownOnUpdateBulk(RestifyRequest $request, $repository): bool
274306
{
307+
// Check MCP-specific visibility for MCP requests
308+
if ($request instanceof McpRequestable) {
309+
return $this->isShownOnMcp($request, $repository);
310+
}
311+
275312
return ! $this->isReadonly($request);
276313
}
277314

278315
public function isShownOnStore(RestifyRequest $request, $repository): bool
279316
{
317+
// Check MCP-specific visibility for MCP requests
318+
if ($request instanceof McpRequestable) {
319+
return $this->isShownOnMcp($request, $repository);
320+
}
321+
280322
return ! $this->isReadonly($request);
281323
}
282324

283325
public function isShownOnStoreBulk(RestifyRequest $request, $repository): bool
284326
{
327+
// Check MCP-specific visibility for MCP requests
328+
if ($request instanceof McpRequestable) {
329+
return $this->isShownOnMcp($request, $repository);
330+
}
331+
285332
return ! $this->isReadonly($request);
286333
}
287334

tests/Fields/FieldMcpVisibilityTest.php

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
use Binaryk\LaravelRestify\Http\Requests\RestifyRequest;
88
use Binaryk\LaravelRestify\MCP\Requests\McpIndexRequest;
99
use Binaryk\LaravelRestify\MCP\Requests\McpRequest;
10+
use Binaryk\LaravelRestify\MCP\Requests\McpShowRequest;
11+
use Binaryk\LaravelRestify\MCP\Requests\McpStoreRequest;
12+
use Binaryk\LaravelRestify\MCP\Requests\McpUpdateRequest;
1013
use Binaryk\LaravelRestify\Tests\Fixtures\Post\PostRepository;
1114
use Binaryk\LaravelRestify\Tests\IntegrationTestCase;
1215

@@ -172,4 +175,129 @@ public function test_field_collection_preserves_other_filters_with_mcp(): void
172175
$this->assertNotContains('mcp_hidden', $indexFieldNames);
173176
$this->assertNotContains('eager_field', $indexFieldNames); // EagerFields excluded from index
174177
}
178+
179+
public function test_mcp_show_request_does_not_cause_infinite_loop(): void
180+
{
181+
$fields = new FieldCollection([
182+
Field::make('title'),
183+
Field::make('secret_key')->hideFromMcp(),
184+
Field::make('visible_field')->showOnShow(true),
185+
]);
186+
187+
// This test ensures that McpShowRequest doesn't cause infinite recursion
188+
// between isShownOnMcp() and isShownOnShow() methods
189+
$mcpShowRequest = new McpShowRequest;
190+
191+
// This should not cause infinite loop
192+
$showFields = $fields->forShow($mcpShowRequest, $this->repository);
193+
$fieldNames = $showFields->map(fn ($field) => $field->getAttribute())->toArray();
194+
195+
$this->assertContains('title', $fieldNames);
196+
$this->assertNotContains('secret_key', $fieldNames); // Hidden from MCP
197+
$this->assertContains('visible_field', $fieldNames);
198+
}
199+
200+
public function test_mcp_index_request_does_not_cause_infinite_loop(): void
201+
{
202+
$fields = new FieldCollection([
203+
Field::make('name'),
204+
Field::make('internal_token')->hideFromMcp(),
205+
Field::make('public_data')->showOnIndex(true),
206+
]);
207+
208+
// This test ensures that McpIndexRequest doesn't cause infinite recursion
209+
$mcpIndexRequest = new McpIndexRequest;
210+
211+
// This should not cause infinite loop
212+
$indexFields = $fields->forIndex($mcpIndexRequest, $this->repository);
213+
$fieldNames = $indexFields->map(fn ($field) => $field->getAttribute())->toArray();
214+
215+
$this->assertContains('name', $fieldNames);
216+
$this->assertNotContains('internal_token', $fieldNames); // Hidden from MCP
217+
$this->assertContains('public_data', $fieldNames);
218+
}
219+
220+
public function test_mcp_store_request_respects_hide_from_mcp(): void
221+
{
222+
$fields = new FieldCollection([
223+
Field::make('title'),
224+
Field::make('secret_api_key')->hideFromMcp(),
225+
Field::make('content'),
226+
Field::make('internal_metadata')->hideFromMcp(function ($request) {
227+
return ! $request->get('is_admin', false);
228+
}),
229+
]);
230+
231+
// MCP store request without admin
232+
$mcpStoreRequest = new McpStoreRequest;
233+
$storeFields = $fields->forStore($mcpStoreRequest, $this->repository);
234+
$fieldNames = $storeFields->map(fn ($field) => $field->getAttribute())->toArray();
235+
236+
$this->assertContains('title', $fieldNames);
237+
$this->assertNotContains('secret_api_key', $fieldNames); // Hidden from MCP
238+
$this->assertContains('content', $fieldNames);
239+
$this->assertNotContains('internal_metadata', $fieldNames); // Hidden by callback
240+
241+
// MCP store request with admin
242+
$mcpStoreRequestAdmin = new McpStoreRequest(['is_admin' => true]);
243+
$storeFieldsAdmin = $fields->forStore($mcpStoreRequestAdmin, $this->repository);
244+
$fieldNamesAdmin = $storeFieldsAdmin->map(fn ($field) => $field->getAttribute())->toArray();
245+
246+
$this->assertContains('title', $fieldNamesAdmin);
247+
$this->assertNotContains('secret_api_key', $fieldNamesAdmin); // Still hidden from MCP
248+
$this->assertContains('content', $fieldNamesAdmin);
249+
$this->assertContains('internal_metadata', $fieldNamesAdmin); // Visible to admin
250+
251+
// Regular store request should show all fields except readonly
252+
$regularStoreRequest = new RestifyRequest;
253+
$regularStoreFields = $fields->forStore($regularStoreRequest, $this->repository);
254+
$regularFieldNames = $regularStoreFields->map(fn ($field) => $field->getAttribute())->toArray();
255+
256+
$this->assertContains('title', $regularFieldNames);
257+
$this->assertContains('secret_api_key', $regularFieldNames); // Visible in regular request
258+
$this->assertContains('content', $regularFieldNames);
259+
$this->assertContains('internal_metadata', $regularFieldNames);
260+
}
261+
262+
public function test_mcp_update_request_respects_hide_from_mcp(): void
263+
{
264+
$fields = new FieldCollection([
265+
Field::make('name'),
266+
Field::make('password')->hideFromMcp(),
267+
Field::make('email'),
268+
Field::make('admin_notes')->hideFromMcp(function ($request) {
269+
return $request->get('role') !== 'admin';
270+
}),
271+
]);
272+
273+
// MCP update request without admin role
274+
$mcpUpdateRequest = new McpUpdateRequest(['role' => 'user']);
275+
$updateFields = $fields->forUpdate($mcpUpdateRequest, $this->repository);
276+
$fieldNames = $updateFields->map(fn ($field) => $field->getAttribute())->toArray();
277+
278+
$this->assertContains('name', $fieldNames);
279+
$this->assertNotContains('password', $fieldNames); // Hidden from MCP
280+
$this->assertContains('email', $fieldNames);
281+
$this->assertNotContains('admin_notes', $fieldNames); // Hidden by callback
282+
283+
// MCP update request with admin role
284+
$mcpUpdateRequestAdmin = new McpUpdateRequest(['role' => 'admin']);
285+
$updateFieldsAdmin = $fields->forUpdate($mcpUpdateRequestAdmin, $this->repository);
286+
$fieldNamesAdmin = $updateFieldsAdmin->map(fn ($field) => $field->getAttribute())->toArray();
287+
288+
$this->assertContains('name', $fieldNamesAdmin);
289+
$this->assertNotContains('password', $fieldNamesAdmin); // Still hidden from MCP
290+
$this->assertContains('email', $fieldNamesAdmin);
291+
$this->assertContains('admin_notes', $fieldNamesAdmin); // Visible to admin
292+
293+
// Regular update request should show all fields except readonly
294+
$regularUpdateRequest = new RestifyRequest;
295+
$regularUpdateFields = $fields->forUpdate($regularUpdateRequest, $this->repository);
296+
$regularFieldNames = $regularUpdateFields->map(fn ($field) => $field->getAttribute())->toArray();
297+
298+
$this->assertContains('name', $regularFieldNames);
299+
$this->assertContains('password', $regularFieldNames); // Visible in regular request
300+
$this->assertContains('email', $regularFieldNames);
301+
$this->assertContains('admin_notes', $regularFieldNames);
302+
}
175303
}

0 commit comments

Comments
 (0)