Skip to content

forwardRef()

forwardRef() wraps a function that returns a validator. Luthor calls the function each time a value is validated, so the validator it returns can be one that is still being defined when the schema literal is built. Schemas has the explanation.

ValidatorReference<O> forwardRef<O>(Validator<O> Function() resolver)
abstract interface class ValidatorReference<O> {
Validator<O> resolve();
}

ValidatorReference is the interface shared by every validator and every forward reference. Schema fields, list elements, map keys and values, and union options are all typed as ValidatorReference, so a forward reference fits wherever a validator does.

Parameter Type Meaning
resolver Validator<O> Function() Called on each validation. Return the validator to use, with modifiers such as .required() applied inside the function.
late final SchemaValidator node;
node = l.schema({
'value': l.string().required(),
'children': l.list(forwardRef(() => node.required())),
});
node.validate(input)+1-1
  1. valid: {'value': 'root', 'children': [{'value': 'leaf'}]}
  2. invalid: {'value': 'root', 'children': [{'value': 'a', 'children': [{'value': 1}]}]}children.0.children.0.value: value must be a string

Every level is validated, and the error path says where:

node.validate(...).getError('children.0.children.0.value')
value must be a string

Two schemas that refer to each other follow the same pattern:

late final SchemaValidator author;
late final SchemaValidator post;
author = l.schema({
'name': l.string().required(),
'posts': l.list(forwardRef(() => post.required())),
});
post = l.schema({
'title': l.string().required(),
'author': forwardRef(() => author),
});
{posts.0.author.name: [name must be a string]}

Recursion is bounded by the input and by l.maxDepth.

The generator wraps every reference to another model’s schema in forwardRef(), so self-referential and mutually recursive models work without any annotation.

@luthorForwardRef and its class LuthorForwardRef are deprecated. The generator still accepts the annotation, and the generated code is the same with or without it. Remove it; the analyzer reports a deprecation warning while it is there. Hand-written forwardRef() in runtime code is unaffected and still needed for a hand-written schema that refers to itself.

List<Node>? children, // l.list(forwardRef(() => $NodeSchema.required()))
Node? parent, // forwardRef(() => $NodeSchema)