Skip to content

CommandResult

When a command is executed, it returns a CommandResult<TResponse> that provides comprehensive information about the execution outcome. The result includes success/failure status, validation errors, authorization status, exceptions, and optional response data.

The CommandResult interface provides the following properties:

interface ICommandResult<TResponse = object> {
readonly correlationId: Guid;
readonly isSuccess: boolean;
readonly isAuthorized: boolean;
readonly isValid: boolean;
readonly hasExceptions: boolean;
readonly validationResults: ValidationResult[];
readonly exceptionMessages: string[];
readonly exceptionStackTrace: string;
readonly response?: TResponse;
}

Understanding the different status properties is crucial for proper error handling:

Indicates whether the command executed successfully overall. This is the primary indicator of whether the operation completed as intended.

  • true: The command executed without any issues (authorized, valid, and no exceptions)
  • false: The command failed for one or more reasons (could be authorization, validation, or exceptions)

Use this when:

  • You want to know if the overall operation succeeded
  • You need a single boolean check for success/failure
  • You’re implementing simple success/failure UI feedback
const result = await command.execute();
if (result.isSuccess) {
// Show success message
} else {
// Handle failure (check other properties for details)
}

Indicates whether Arc’s command pipeline authorized this command.

  • true: The user has permission to execute the command
  • false: The generated command endpoint returned an authorization failure, typically HTTP 403. An upstream authentication challenge can return HTTP 401 before a CommandResult payload is produced.

Use this when:

  • You need to distinguish authorization failures from other errors
  • You want to show specific “access denied” messages
  • You need to redirect users to login or show permission requests
const result = await command.execute();
if (!result.isAuthorized) {
// Show "Access Denied" message
// Redirect to login or request permissions
}

Indicates whether the command payload passed validation rules.

  • true: All validation rules passed
  • false: One or more validation rules failed (check validationResults for details)

Use this when:

  • You need to display validation errors to users
  • You want to highlight invalid form fields
  • You’re implementing client-side validation feedback
const result = await command.execute();
if (!result.isValid) {
// Display validation errors from result.validationResults
result.validationResults.forEach(error => {
console.log(`${error.members.join('.')}: ${error.message}`);
});
}

Indicates whether any exceptions occurred during command execution.

  • true: One or more exceptions were thrown
  • false: No exceptions occurred

Use this when:

  • You need to catch unexpected server errors
  • You want to log errors for debugging
  • You need to show generic error messages for system failures
const result = await command.execute();
if (result.hasExceptions) {
// Log exception details
console.error('Exceptions:', result.exceptionMessages);
console.error('Stack trace:', result.exceptionStackTrace);
// Show user-friendly error message
showErrorMessage('An unexpected error occurred. Please try again.');
}

The status properties work together to provide a complete picture:

// Scenario 1: Complete success
// isSuccess = true, isAuthorized = true, isValid = true, hasExceptions = false
// Scenario 2: Authorization failure
// isSuccess = false, isAuthorized = false, isValid = true, hasExceptions = false
// Scenario 3: Validation failure
// isSuccess = false, isAuthorized = true, isValid = false, hasExceptions = false
// Scenario 4: Exception occurred
// isSuccess = false, isAuthorized = true, isValid = true, hasExceptions = true
// Scenario 5: Multiple failures (validation + exception)
// isSuccess = false, isAuthorized = true, isValid = false, hasExceptions = true

Key principle: If isSuccess is false, check the other properties to determine why it failed.

When the command returns data, access it through the response property:

interface CreateAccountResponse {
accountId: string;
accountNumber: string;
}
const result = await createAccountCommand.execute();
if (result.isSuccess && result.response) {
const { accountId, accountNumber } = result.response;
console.log(`Account created: ${accountNumber}`);
}

The response is typed according to the command’s generic parameter, providing full type safety.

Access detailed validation errors through the validationResults array:

const result = await command.execute();
if (!result.isValid) {
result.validationResults.forEach(error => {
console.log(`Severity: ${error.severity}`);
console.log(`Message: ${error.message}`);
console.log(`Members: ${error.members.join('.')}`);
console.log(`State: ${JSON.stringify(error.state)}`);
console.log(`Reason: ${error.reason}`);
});
}

Each ValidationResult contains:

  • severity: The severity level of the validation error
  • message: A developer-facing message. Only meant to be shown to a user when reason is rule
  • members: Array of property names that failed validation
  • state: Additional context, set by whoever authored the rule (FluentValidation’s WithState)
  • reason: What composed the result — see below

Telling one kind of rejection from another

Section titled “Telling one kind of rejection from another”

A rejection your own rules produced and one the framework composed on your behalf arrive in the same array, in the same shape. reason is what separates them, so you never have to match the message text:

reasonWhat happenedWhat to do
ruleA rule you authored rejected the input. The default.Show message — it is your copy
concurrencyViolationThe event source moved on since it was readOffer a retry; state carries the violation
constraintViolationA constraint on the event store rejected the appendShow your own copy for that constraint
validatorFailedA validator threw; nothing the author wrote survivesShow your own generic copy, and check the server log
import { ValidationResultReason } from '@cratis/arc';
const result = await command.execute();
if (result.validationResults.some(_ => _.reason === ValidationResultReason.ConcurrencyViolation)) {
// Someone else changed this while the form was open. Re-read and resubmit — the
// input was never the problem, so do not put an error on any field.
return retry();
}

Access exception information when hasExceptions is true:

const result = await command.execute();
if (result.hasExceptions) {
// Array of exception messages
result.exceptionMessages.forEach(msg => {
console.error('Exception:', msg);
});
// Full stack trace for debugging
console.error('Stack trace:', result.exceptionStackTrace);
}

CommandResult supports a fluent API for handling different outcomes:

const result = await command.execute();
result
.onSuccess((response) => {
console.log('Success!', response);
})
.onFailed((commandResult) => {
console.log('Failed:', commandResult);
})
.onUnauthorized(() => {
console.log('Not authorized');
})
.onValidationFailure((validationResults) => {
console.log('Validation errors:', validationResults);
})
.onException((messages, stackTrace) => {
console.error('Exception:', messages, stackTrace);
});

Each callback method returns the CommandResult instance, allowing you to chain multiple handlers.

Always start by checking isSuccess for the overall outcome:

const result = await command.execute();
if (result.isSuccess) {
// Handle success
} else {
// Check specific failure reasons
if (!result.isAuthorized) { /* ... */ }
if (!result.isValid) { /* ... */ }
if (result.hasExceptions) { /* ... */ }
}

Use the specific status properties to give users meaningful feedback:

const result = await command.execute();
if (!result.isSuccess) {
if (!result.isAuthorized) {
showMessage('You do not have permission to perform this action.');
} else if (!result.isValid) {
showValidationErrors(result.validationResults);
} else if (result.hasExceptions) {
showMessage('An unexpected error occurred. Please contact support.');
}
}

Always log exception details when hasExceptions is true:

if (result.hasExceptions) {
logger.error('Command execution failed', {
messages: result.exceptionMessages,
stackTrace: result.exceptionStackTrace,
correlationId: result.correlationId
});
}

The correlationId helps track command executions across the system:

console.log(`Command executed with correlation ID: ${result.correlationId}`);

This is especially useful for debugging and support scenarios.