{"record":{"id":"357370fb8610fc16","repo":"passbolt/passbolt_api","slug":"an-array-of-arrays-is-expected","errorCode":null,"errorMessage":"An array of arrays is expected.","messagePattern":"An array of arrays is expected\\.","errorType":"validation","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/PasswordExpiryPolicies/src/Service/Resources/PasswordExpiryPoliciesResourcesExpiryUpdateService.php","lineNumber":80,"sourceCode":"        }\n\n        return $resources;\n    }\n\n    /**\n     * @param array $data payload\n     * @return array<string, \\Cake\\I18n\\DateTime|null> array with the resourceIds as keys and the expiry date as value\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the expired value are not valid\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the resource_id value are not valid\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the resource_id value is found twice in the payload\n     * @throws \\Cake\\Http\\Exception\\BadRequestException if the sanitized array is empty\n     */\n    protected function validateAndParsePayload(array $data): array\n    {\n        $dataSanitized = [];\n        foreach ($data as $resource) {\n            if (!is_array($resource)) {\n                throw new BadRequestException(__('An array of arrays is expected.'));\n            }\n            $resourceId = $resource['id'] ?? null;\n            if (!Validation::uuid($resourceId)) {\n                throw new BadRequestException(__('The identifier should be a valid UUID.'));\n            }\n            $isExpiredDefined = array_key_exists('expired', $resource);\n            if (!$isExpiredDefined) {\n                throw new BadRequestException(__('The expiry date is required.'));\n            }\n            $expiryDate = $resource['expired'];\n            if (array_key_exists($resourceId, $dataSanitized)) {\n                throw new BadRequestException(__('The identifier should be unique: {0}.', $resourceId));\n            }\n            $dataSanitized[$resourceId] = is_null($expiryDate) ? $expiryDate : new DateTime($expiryDate);\n        }\n        if (empty($dataSanitized)) {\n            throw new BadRequestException(__('The data should not be empty.'));\n        }","sourceCodeStart":62,"sourceCodeEnd":98,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/PasswordExpiryPolicies/src/Service/Resources/PasswordExpiryPoliciesResourcesExpiryUpdateService.php#L62-L98","documentation":"The bulk resource expiry update endpoint expects the payload to be an array of per-resource arrays (each containing id, expired, etc.). When any element of the submitted data is not an array (e.g. a scalar, string, or null), validateAndParsePayload rejects it with this BadRequestException before any persistence occurs.","triggerScenarios":"PATCH/PUT to the resources expiry endpoint where the body's data array contains non-object elements, e.g. {\"data\": [\"abc\"]} or {\"data\": [null]}, instead of {\"data\": [{\"id\": \"<uuid>\", \"expired\": \"...\"}]}.","commonSituations":"Clients sending a flat list of UUID strings instead of objects; JSON encoding mistakes where objects become strings; scripting the API by hand and forgetting the per-resource wrapper.","solutions":["Send each entry as an object with at least 'id' and 'expired' keys: data: [{\"id\":\"<uuid>\",\"expired\":\"2026-01-01\"}]","Validate the client payload shape before calling the endpoint (each item must be an object/associative array)","Check the request Content-Type is application/json and the body is not being collapsed into a string"],"exampleFix":"// before\ndata: [\"8e3874ae-4b40-590b-968a-418f70bdbb85\"]\n\n// after\ndata: [{\"id\": \"8e3874ae-4b40-590b-968a-418f70bdbb85\", \"expired\": null}]","handlingStrategy":"validation","validationCode":"if (!is_array($data) || array_filter($data, fn($r) => !is_array($r))) {\n    throw new \\InvalidArgumentException('Each data item must be an array');\n}","typeGuard":"function isListOfArrays(mixed $data): bool {\n    return is_array($data) && array_all($data, fn($r) => is_array($r));\n}","tryCatchPattern":"try {\n    $service->updateMany($uac, $data);\n} catch (BadRequestException $e) {\n    // log and return 400 with $e->getMessage()\n}","preventionTips":["Validate payload shape client-side before sending","Use typed request DTOs to deserialize the body","Test the endpoint with malformed payloads in CI"],"tags":["passbolt","bad-request","payload-validation"],"backgroundTag":"invalid-argument-format","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}