Skip to content

Apex classes that resolve merge tokens (L2)

Detailed page. Use “On this page” to jump directly to the section you need.

Use this page when reviewing the package’s merge-token classes. This class-level reference is not a Setup or Flow walkthrough. Administrators should use the Flow, configuration, and evaluation guides; subscriber developers should use the public Apex API or Apex Check contract.

Use this page to understand the internal classes that read, validate, and replace merge tokens. For the token names and syntax an administrator can use, see Merge tokens.

Use the Apex class reference to place these merge-token classes in the full package structure.

Role: Validate merge tokens and replace them with Salesforce values.

Type: Shared service · public (no sharing keyword)

Handles tokens such as {!record.Name} in messages, URLs, and SOQL. A token can include quoted settings, as in {!record.Amount format="CURRENCY" fallback="Not available"}. One template can contain up to 100 tokens, and the completed text can contain up to 20,000 characters. An unknown token name, property, or setting returns a RecordHealthCheckTokenIssue instead of partially replacing the template.

Key members:

MemberPurpose
SURFACE_DISPLAY, SURFACE_URL, SURFACE_SOQLIdentify whether the completed text is a message, URL, or SOQL query
resolveFieldPath(...)Read the value of a record.* field path
applyFoundExpectedText(...)Add the administrator’s Found Value and Expected Value wording after a Check finishes

Important behavior:

  • Related fields: a record.* token can follow no more than five Salesforce relationships. A deeper path returns TOKEN_NOT_AVAILABLE_IN_PHASE.
  • URLs: when a token used in a URL is empty, the token must have a fallback value. Otherwise, the class returns MISSING_TOKEN_VALUE instead of creating a broken or unintended link.
  • Result values: rhcResult.* tokens become available only after the Check has produced its final result. Found and Expected values are not reliable before that point. A true Apex null resolves blank and may activate a fallback; the populated text value "null" remains literal text.

See also: Merge tokens

Role: Store the allowed first part and property names for merge tokens.

Type: Constants holder · public (no sharing keyword)

The first part identifies the source, such as record in {!record.Name} or rhcResult in {!rhcResult.status}. A record token can use a nonblank field path. The other sources have fixed property lists.

Key members:

MemberPurpose
record, rhcCheck, rhcSet, rhcResult, rhcRun, rhcQueryThe allowed first parts of a token
RESULT_PROPERTIESAllowed properties after rhcResult.
QUERY_COUNT_PROPERTIESThe two row-count properties after rhcQuery.
MAX_ROW_INDEXThe highest row position a token may address
resolve(namespace, property)Turns the two parts into a RecordHealthCheckTokenShape

For example, foundValuePluralSuffix lets a message display 1 Contact or 2 Contacts without requiring an administrator to write conditional logic.

resolve replaced a yes-or-no membership test once rhcQuery.sourceRows[0].Name introduced a property with three parts. Callers read the resolved shape rather than reading the property string again.

Role: Store the parts of one merge token after it has been read.

Type: Data holder · public (no sharing keyword)

Key members:

MemberPurpose
expressionComplete token text, including {! and }
namespaceNameFirst part, such as record or rhcCheck
propertyPathProperty or Salesforce field path after the first part
formatNameOptional uppercase Value Format API name from format="..."
fallbackValueOptional text from fallback="..."; null when omitted
attributeErrorError for an unknown, repeated, unquoted, or otherwise invalid setting
startIndex / endIndexToken’s location in the complete template

A shorter constructor omits fallbackValue and leaves it null for package code that does not need a fallback.

Role: Describe one invalid merge token.

Type: Data holder · public (no sharing keyword)

The constructor accepts a Reason Code, the invalid token, and a message. For example:

new RecordHealthCheckTokenIssue(
'UNSUPPORTED_TOKEN_NAMESPACE',
'{!foo.bar}', // rejected-token-fixture
'Unsupported token namespace "foo".'
);

Role: Supply the Salesforce values that merge tokens can use.

Type: Chainable data holder · public (no sharing keyword)

The withRecord, withCheck, withResult, and withRun methods supply the record, Check, parent Check Set, result, and run details used to replace tokens. The class can also supply failed and total record counts for messages that need singular or plural wording.

Key members:

MemberPurpose
withRecord(...)Supply the Salesforce record for record.* tokens
withCheck(...)Supply the Check and its parent Check Set for rhcCheck.* and rhcSet.* tokens
withResult(value, finalized)Supply the result; rhcResult.* is available only when finalized is true
withRun(...)Supply the run details for rhcRun.* tokens

Important behavior:

  • Check Set tokens: withCheck reads the parent Check Set from the Record_Health_Check_Set__r relationship on the supplied Check record. Internal package code must include that relationship in its Check query or rhcSet.* tokens have no value.
  • Result tokens: withResult(value, true) marks the result as complete and makes rhcResult.* tokens available. They are unavailable by default.
  • Token settings: raw record.* tokens can contain format="API_NAME" and fallback="text" in either order. Values must use double quotes. Unknown, repeated, or unquoted settings produce a token issue. Result tokens cannot use format because they already contain display text.

Role: Store what one merge token turned out to address, resolved once.

Type: Data holder · public (no sharing keyword)

A query row token carries three parts, and several consumers need all of them. Parsing the property string separately in each consumer is how they drift apart, so the registry parses it once into this shape and every consumer downstream reads typed fields.

Key members:

MemberPurpose
kindKIND_PROPERTY for a named value, or one of the two query row kinds
collectionsourceRows or comparisonRows for a query token
rowIndexThe zero-based row index a query token addresses
fieldPathThe terminal field or relationship path the token reads
isTypedFieldWhether Salesforce still knows the value’s data type, which is what format needs
reasonCode, messageWhy the token is unusable, and the change that fixes it

An unusable token is described rather than thrown, so the caller decides whether it is an authoring finding or a runtime configuration error.

Role: Declare which merge tokens each configuration field accepts.

Type: Constants holder · public (no sharing keyword)

These rules used to be a chain of comparisons inside the validator, so every new namespace added a branch and every rule was written wherever it happened to be needed. They are a small table instead. The broad surfaces existing callers pass are still named on RecordHealthCheckTemplateService as SURFACE_SOQL, SURFACE_DISPLAY, and SURFACE_URL, and appear here as rows; the named surfaces describe one configuration field each, which is what lets a query row token be accepted in the failure message and refused in the not-applicable message.

Key members:

MemberPurpose
FAILURE_MESSAGE, UNABLE_MESSAGE, NOT_APPLICABLE_MESSAGE, FIX_MESSAGEOne surface per message field
ACTION_LABEL, ACTION_URLThe two action fields
DISPLAY_FOUND, DISPLAY_EXPECTEDThe two Found/Expected text fields
allows(surface, namespace)Whether that field accepts tokens from that namespace
allowsFormat(surface)Whether a format attribute means anything on that field

Role: Describe what a Check’s SOQL actually returns, in one place.

Type: Analyzer · public with sharing

RecordHealthCheckQueryTokenRules reads it to decide whether a query row token addresses something the query actually returns. That consumer carries a security obligation, because a row addressed by position must be the row the executor returns, so the SELECT list is split here once rather than by each caller’s own pattern.

The analysis fails closed. Anything this class cannot resolve into named, addressable columns leaves isFullyAnalyzable false, and a caller that must prove a field was selected refuses rather than assuming.

Key members:

MemberPurpose
selectedPaths, aliasesEvery name the query projects, in source order
orderPathsThe depth-zero ORDER BY field paths. Direction and null handling are not kept: only the fields an order names decide whether it can tie
isFullyAnalyzableFalse forbids any conclusion about what the query did not select
hasStableRowOrder()Whether repeating the query returns rows in the same order every time
hasExplicitRowOrder()Whether the outer query declares a readable business order
projects(name)Whether the query projects an addressable column under that name
addressableNames()Every name a merge token may read on a row of this query

Positional tokens use hasExplicitRowOrder(): the administrator’s ORDER BY defines what “first” means. Adding Id is optional. Rows tied on every authored key retain Salesforce’s native tie behavior. hasStableRowOrder() remains the stronger uniqueness check for callers that need it.

Role: Warn about Check configuration the Framework will never read.

Type: Validator · public with sharing

Every other validator asks whether a Check is missing something it needs. This one asks the opposite question, which is the one administrators actually hit: a Custom Metadata Type shows all of its fields to every record, so a Formula Check offers a Source Query and an Apex Check offers a Comparison Operator, and filling either in does nothing. Nothing fails and nothing is reported, and the Check quietly does not behave the way its configuration reads.

Every issue it returns is a WARNING with the reason code CONFIGURATION_IGNORED. Ignored configuration is confusing rather than invalid, and a half-finished Check an administrator is still editing must keep deploying.

Display: Found Text and Display: Expected Text on an Apex Check are the one case already covered elsewhere, as APEX_DISPLAY_TEXT_IGNORED.

Role: Everything a {!rhcQuery...} token must satisfy before a Check can run.

Type: Validator · public with sharing

These rules sit apart from the rest of configuration validation because they all answer one question the other rules never ask: does this token address something the Check’s own query can actually produce? Each exists because the alternative was a Check that validated cleanly and then rendered a sentence with a hole in it, or worse, a plausible wrong number.

Refusals:

Reason codeRefused when
TOKEN_NOT_ALLOWED_ON_SURFACEThe Check is Formula or Apex, so it runs no query
QUERY_ROLE_NOT_AVAILABLEA comparisonRows token with no Comparison Query, or a row count against a query that counts rather than returns
QUERY_FIELD_NOT_SELECTEDThe SELECT omits the field; the message lists what it does select
QUERY_ORDER_NOT_DETERMINISTICA potentially multi-row query has no readable outer ORDER BY
QUERY_PROJECTION_NOT_ANALYZABLEThe requested field cannot be proven from the readable outer projection
TOKEN_ROW_INDEX_INVALIDThe index exceeds the query’s own LIMIT, the Check’s Max Query Rows, or the single row a One Result Check or ungrouped aggregate can return
FIELD_TYPE_NOT_SUPPORTEDThe field is Classic encrypted
FIXED_CURRENCY_BASIS_MISSINGA currency-rendered row amount in a multi-currency org whose query did not select the matching CurrencyIsoCode

An ungrouped aggregate returns exactly one row, and an outer Id = {!record.Id} equality returns at most one record, so index 0 needs no ORDER BY. A row count reads no column and no order. A scalar selected beside an opaque relationship subquery remains independently addressable.