passbolt/passbolt_api · error · CustomValidationException

The role could not be saved.

Error message

The role could not be saved.

What it means

RolesAddService::add throws this CustomValidationException when the Roles table's saveOrFail raises a PersistenceFailedException, i.e. the new role entity failed model rules/rulesChecker before INSERT. The entity's field errors are attached so the API can report which fields were rejected.

Solutions

  1. Read the `errors` payload in the 400 response to identify the failing field (usually name).
  2. Verify the role name is unique: GET /roles and check for an existing entry.
  3. Resend POST /roles with a valid, non-empty, unique name.
  4. If persistence fails repeatedly without a clear field error, check DB connectivity/constraints and the Roles table rules.

Example fix

// before
POST /roles {"name": "admin"}  // already exists -> 400
// after
POST /roles {"name": "auditor"} // unique -> 201
Defensive patterns

Strategy: validation

Validate before calling

function validRoleName(name) {
  return typeof name === 'string' && name.trim().length > 0 && name.length <= 255;
}
if (!validRoleName(roleName)) throw new Error('Invalid role name before POST /roles');

Type guard

function isRolePayload(v) {
  return typeof v === 'object' && v !== null && typeof v.name === 'string' && v.name.trim().length > 0;
}

Try / catch

try {
  await api.createRole({ name });
} catch (e) {
  if (e.status === 400 && e.body?.errors?.name) {
    // duplicate or invalid name: prompt user for a new one
  } else throw e;
}

Prevention

When it happens

Trigger: POST /roles with a role name that violates table validation (empty name, name longer than 255 chars, or a rulesChecker uniqueness violation on name).

Common situations: Creating a role whose name already exists (duplicate slug/name), posting an empty or whitespace-only name from a misbehaving client, or importing roles in bulk where one collides with an existing one.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/6e74bbc1fe6e93d6. Report an issue: GitHub.

Appendix: source

Thrown at src/Service/Roles/RolesAddService.php:64

        $data = array_merge($data, [
            'description' => null,
            'created_by' => $uac->getId(),
            'modified_by' => $uac->getId(),
        ]);

        $role = $rolesTable->newEntity($data, ['accessibleFields' => [
            'name' => true,
            'description' => true,
            'created_by' => true,
            'modified_by' => true,
        ]]);

        try {
            $result = $rolesTable->saveOrFail($role);
        } catch (PersistenceFailedException $e) { // @phpstan-ignore-line
            $errors = $e->getEntity()->getErrors();

            throw new CustomValidationException(
                __('The role could not be saved.'),
                $errors
            );
        } catch (Exception $e) {
            throw new InternalErrorException(__('Could not save the role, please try again later.'), null, $e);
        }

        $this->dispatchEvent(self::AFTER_ROLE_CREATE_SUCCESS_EVENT_NAME, [
            'uac' => $uac,
            'role' => $result,
        ]);

        return $result;
    }
}

View on GitHub (pinned to 31c1bbc10f)