Skip to main content

Tenancy API: Refusals

Every refusal code of Tenancy, in the members of TenancyRefusals, and the resources that hold their texts.

Refusal codes​

Namespace DTS.Tenancy.

TenancyRefusals​

public static class TenancyRefusals

Every way Tenancy refuses a command, as codes a caller branches on, and the one place their kind and English text are written down.

An argument a message shows is a value, such as a permission or an id, never a word: the English and the Dutch texts are whole sentences of their own. Where a client wants to say more, it reads the stable tokens the arguments carry. A refusal of kind RefusalKind.Invalid that is about one input names it in the argument RefusalException.FieldArgument, in the word the managers call that input by, such as name or until, so a form puts the text under the input; an edge whose input is called otherwise maps the word. tenancy.tenant-required carries none, because the tenant is no input of the command: a request names it beside the command, in a header or in its address.

The Dutch texts have no word for the reader. Dutch makes a writer choose between a familiar and a formal word for "you", and that choice is the application's, so a Dutch sentence says what is the case or what is needed and leaves the word out. An application that wants another tone, or other words, registers a resx of its own in front of this one.

Each code is also the code of the nested invariant that states the same rule after the fact, where a rule has both: the method refuses before anything changes, and the invariant is the net under code that got round the method. TenancyFailures.resx holds the same English text, and its Dutch sibling a translation, for DTS.DDD.Localization to phrase a refusal in the reader's language:

services.AddLocalization();
services.AddDDDToolkitLocalization(options => options.AddResource<TenancyFailures>());
MemberSummary
NotSeated = "tenancy.​not-seated"The caller has no seat in the tenant it named.
TenantRequired = "tenancy.​tenant-required"The caller named no tenant. It carries no field: the tenant is named beside the command, not in it.
SeatSuspended = "tenancy.​seat-suspended"The caller's seat in the tenant is suspended.
TenantInactive = "tenancy.​tenant-inactive"The tenant the caller named is not active.
NotPermitted = "tenancy.​not-permitted"The caller does not hold the permission the command needs where it needs it. Arguments: Permission and Unit (the unit asked about, or null for the whole tenant). The message names only the permission: a client that shows the unit finds it among the arguments.
GrantExceedsOwn = "tenancy.​grant-exceeds-own"A seat would give or take away, at a unit, a role that manages access without holding there each of its permissions that do, for at least as long as the grant runs, or suspend, deactivate or reactivate a seat that holds one; would give itself a role for longer than it holds tenancy.​grants.​manage there; would add or take out a permission that manages access, or archive a role that holds one, without being an administrator; or would, by moving a unit, gain permissions or hold one for longer, or give or take away from anyone a permission that manages access it lacks at the unit for that long. Arguments: Missing, the permissions it lacks there or lacks for long enough, and Role, the role refused, for all but a move.
SelfAssignment = "tenancy.​self-assignment"A seat would be placed by itself.
SelfAppointment = "tenancy.​self-appointment"A seat would give itself a role that manages access. Argument: Role.
OtherTenant = "tenancy.​other-tenant"A change would write a row of another tenant.
StartSystemOnly = "tenancy.​start-system-only"Only system work chooses when a grant starts.
OperatorsOnly = "tenancy.​operators-only"Only an operator of the application lists the tenants (TenantManager.​ListAsync), or sends a request that requires one ([Operator], TenancyAccess.​Operator()).
TenantMismatch = "tenancy.​tenant-mismatch"An operator's request names one tenant with its [ForTenant] member and another beside it, as tenant selection reads it. It carries no field, since the tenant beside the request is no input of the command.
AddressMismatch = "tenancy.​address-mismatch"The invitation is for another address than the one the application says the accepting identity has. It names neither address.
UnitNotFound = "tenancy.​unit-not-found"The unit is not one of the tenant's.
SeatNotFound = "tenancy.​seat-not-found"The seat is not one of the tenant's, or belongs to another tenant.
RoleNotFound = "tenancy.​role-not-found"The role is not one of the tenant's, or belongs to another tenant.
PlacementNotFound = "tenancy.​placement-not-found"The seat has no placement at the unit.
GrantNotFound = "tenancy.​grant-not-found"The seat was never granted the role at the unit, or it was revoked.
InvitationNotFound = "tenancy.​invitation-not-found"No invitation has the token, or what was sent is not written as a token is. It is the one answer for both, and for an invitation of another tenant, so it says nothing about which tokens exist.
LastAdmin = "tenancy.​last-admin"After the change the tenant, which has an administrator now, would have none.
SlugTaken = "tenancy.​slug-taken"Another tenant has the slug. Argument: Slug.
IdentityHasSeat = "tenancy.​identity-has-seat"The person already has a seat in the tenant.
RoleNameTaken = "tenancy.​role-name-taken"Another role of the tenant has the name, ignoring case. Argument: Name.
TenantState = "tenancy.​tenant-state"The tenant's status does not allow the change. Arguments: Status, the status in lowercase, and Action, one of activate, suspend, reactivate, close and change-shape.
SeatState = "tenancy.​seat-state"The seat's status does not allow the change. Arguments: Status, the status in lowercase, and Action, one of suspend, reactivate, deactivate, place and grant.
UnitNotActive = "tenancy.​unit-not-active"Something new would go to an archived unit. Argument: Unit.
RootImmovable = "tenancy.​root-immovable"A move names the root.
RootNotArchivable = "tenancy.​root-not-archivable"An archive names the root.
OneRoot = "tenancy.​one-root"The organization has no root, or more than one.
Cycle = "tenancy.​cycle"A move would hang a unit below itself.
SameParent = "tenancy.​same-parent"A move names the parent the unit has now.
DepthExceeded = "tenancy.​depth-exceeded"The tree would have more levels than it may. Argument: Max.
UnitHasActiveChildren = "tenancy.​unit-has-active-children"An archive names a unit that still has active children.
UnitArchived = "tenancy.​unit-archived"A rename, move or archive names an archived unit.
FlatTenant = "tenancy.​flat-tenant"A unit would be added to a flat tenant, which has its root alone.
ShapeChange = "tenancy.​shape-change"A shape change other than flat to hierarchical.
DuplicatePlacement = "tenancy.​duplicate-placement"A second placement of one seat at one unit.
SecondPrimary = "tenancy.​second-primary"A second primary placement of one seat.
DuplicateGrant = "tenancy.​duplicate-grant"A second grant of one role at one placement.
RoleNotActive = "tenancy.​role-not-active"A grant names an archived role.
RoleArchived = "tenancy.​role-archived"A change names an archived role.
InvitationLapsed = "tenancy.​invitation-lapsed"The invitation's time ran out before it was accepted.
InvitationUsed = "tenancy.​invitation-used"The invitation was accepted already, by somebody else.
InvitationCancelled = "tenancy.​invitation-cancelled"The invitation was cancelled before it was accepted.
InvitationState = "tenancy.​invitation-state"The invitation's state does not allow the change. Arguments: State, the state in lowercase, and Action, one of cancel and accept.
InvitationUnbacked = "tenancy.​invitation-unbacked"The seat that issued the invitation may no longer give what it offers: it is not active, or it lacks a permission it needed to issue it, or holds one for too short a time. It names neither the seat nor the permissions: the person it is told to is no member of the tenant.
NameInvalid = "tenancy.​name-invalid"A name is blank, or a name or text is too long. Arguments: What, one of tenant-name, unit-name, role-name, role-description and reason; Min and Max, the lengths allowed; and Field, the input it is about: name for the name of a tenant, a unit or a role, description or reason. A seat has no name in Tenancy, so none is refused here.
InvalidSlug = "tenancy.​invalid-slug"A slug does not follow the pattern. Argument: Field, which is slug.
InvalidPeriod = "tenancy.​invalid-period"A grant would end at or before its start. Argument: Field, which is until.
ReasonRequired = "tenancy.​reason-required"A tenant would be suspended or closed without a reason. Argument: Field, which is reason.
UnknownPermission = "tenancy.​unknown-permission"Permissions that are new to a role are unknown to the catalogue, or retired. Arguments: Permissions, the permissions refused, and Field, which is permissions.
IdentityRequired = "tenancy.​identity-required"A seat would have no verified identity. Argument: Field, which is identity.
PermissionsNotNormalized = "tenancy.​permissions-not-normalized"A role's permissions are not expanded, or hold duplicates, or are out of order. Argument: Field, which is permissions.
TooManyIds = "tenancy.​too-many-ids"A question by id asks about more ids than one question takes. Arguments: Max, and Field, which is ids.
PageSizeInvalid = "tenancy.​page-size-invalid"A page of the tenants an operator lists would hold no tenant, or more than a page holds. Arguments: Max, and Field, which is size.
CursorInvalid = "tenancy.​cursor-invalid"The marker a page of the tenants an operator lists continues from is not one TenantManager.​ListAsync gave. Argument: Field, which is after.
AddressInvalid = "tenancy.​address-invalid"An invitation is not for one address a message can be sent to. Arguments: Max, the most characters an address has, and Field, which is address.
InvitationLifetime = "tenancy.​invitation-lifetime"An invitation would stay open for less or for longer than an invitation may. Arguments: Min and Max, in minutes, and Field, which is lifetime.
InvitationGrantEndsFirst = "tenancy.​invitation-grant-ends-first"The grant an invitation offers would end before the invitation does, so it could be accepted for a role that is already over. Argument: Field, which is grantUntil.
static IReadOnlyCollection<string> Codes { get; }Every code, in the order the table lists them.
KindOf(string)The kind of refusal a code is.
TemplateOf(string)The English text of a code, as a template: a placeholder such as {Max} is filled from the argument of that name.
Refuse(string, params (string Name, object? Value)[])The refusal for code, with its kind, its English message filled from arguments, and the arguments themselves for a translation to use.

KindOf(string)​

public static RefusalKind KindOf(string code)

The kind of refusal a code is.

Parameters

  • code: One of the codes above.

Exceptions

  • ArgumentException: The code is not one of Tenancy's.

TemplateOf(string)​

public static string TemplateOf(string code)

The English text of a code, as a template: a placeholder such as {Max} is filled from the argument of that name.

Parameters

  • code: One of the codes above.

Exceptions

  • ArgumentException: The code is not one of Tenancy's.

Refuse(string, params (string Name, object? Value)[])​

public static RefusalException Refuse(string code, params (string Name, object? Value)[] arguments)

The refusal for code, with its kind, its English message filled from arguments, and the arguments themselves for a translation to use. A code that is always about the same input also carries that input's name, as the argument RefusalException.FieldArgument, unless arguments name one themselves.

Parameters

  • code: One of the codes above.
  • arguments: The values the message names, by name.

Exceptions

  • ArgumentException: The code is not one of Tenancy's: a refusal nobody can look up is a bug.

Texts​

Namespace DTS.Tenancy.

TenancyFailures​

public sealed class TenancyFailures

The marker type TenancyFailures.resx and TenancyFailures.nl.resx are named after: the English and Dutch text of every tenancy.* code in TenancyRefusals. Add it to the localizer so refusals reach the reader in their language:

services.AddLocalization();
services.AddDDDToolkitLocalization(options => options.AddResource<TenancyFailures>());

A language the package does not ship is a resx of your own with the same keys, added before this one.

The Dutch texts never use a word for the reader, neither the familiar one nor the formal one: which of the two fits is the application's to say. Another tone, or another word for a tenant or a seat, is a resx of your own as well, with the permissions you want to say differently, in every language you support, added before this one.