The schema module describes record fields for reusable list and form
components. A DataSchema contains field definitions, validators, optional list
column configuration, and whether list rows are selectable. It contains
metadata only; blocks still own data loading, persistence, and authorization.
For a quick list, derive fields from a Java record:
record Post(String id, String title, String content) {}
DataSchema schema = DataSchema.fromRecordClass(Post.class);
List<Map<String, Object>> rows = schema.toMapList(posts);Reflection-based derivation requires a record. fromFirstItem(item) uses the
same mechanism, and toMap/toMapList reject non-record values.
Use the fluent builder when labels, validation, widgets, or list behavior matter:
import rsp.compositions.schema.DataSchema;
import rsp.compositions.schema.FieldType;
import rsp.compositions.schema.Widget;
DataSchema schema = DataSchema.builder()
.field("id", FieldType.ID)
.hidden()
.field("title", FieldType.STRING)
.label("Post title")
.required()
.maxLength(200)
.placeholder("Enter a title")
.field("content", FieldType.TEXT)
.widget(Widget.TEXTAREA)
.column("id")
.sortable()
.column("title")
.sortable()
.filterable()
.width("40%")
.formatter(value -> value.toString().trim())
.build()
.withSelectable(true);Each call to field(...) or column(...) finishes the previous definition.
Calling build() finishes the current definition and returns an immutable
schema. Immutable customization methods such as withSelectable(true) return a
new schema.
FieldType describes the semantic value type and supplies a default Java type.
Widget selects the default form control. A field can override its label,
widget, Java type, default value, format, visibility, and read-only state.
Built-in validators cover required values, minimum and maximum length, regular
expressions, email addresses, and numeric ranges. Custom validation implements
Validator. Validate a submitted value map at the schema boundary:
ValidationResult result = schema.validate(fieldValues);
if (!result.isValid()) {
Map<String, List<String>> errors = result.errors();
}Validators also expose compatible HTML validation attributes, allowing the default form view to provide browser feedback without replacing server-side validation.
A scalar foreign key can declare reference semantics independently of a
repository or route. references(...) records the resource key and selects the
reference widget:
.field("postId", FieldType.STRING)
.label("Post")
.required()
.references("posts")
.placeholder("Select a post…")The schema stores a ReferenceDef; the mounted form block supplies the
authorized FieldChoice ID/label pairs. IDs remain the submitted values while
labels are display-only. This separation lets the same schema describe data
without depending on a service, and prevents a browser or agent from submitting
an ID outside the block's current choice set. The persistence service must still
enforce referential integrity because the target can change after choices load.
At the form boundary, FormValueCodec converts untrusted browser or agent
values to the field's declared Java type. It supports strings, numeric types,
booleans, dates, date-times, and enums. Blank optional reference values become
null; invalid values produce field errors and are never silently replaced by
a default. Configured defaults pass through the same conversion during form
initialization.
listColumns()returns fields in explicitly configured column order, or all visible fields when no columns are configured. Explicit configuration opts a field into the list even when its form widget is hidden, which lets an ID be hidden by default in forms but visible in a grid. A field explicitly marked.hidden()remains excluded.- Column settings control sortability, filterability, width, alignment, and formatting. The default grid turns only sortable columns into controls and only filterable columns into filter inputs.
withSelectable(true)enables row-selection state used by bulk list actions.renameColumn,hideColumn, andreorderColumnspreserve the remaining column configuration and selectable flag.FormBlockuses a schema for typed initial values, editable-field whitelisting, rendering, and validation. Hidden and read-only fields retain their server-owned values when a browser or agent submits a payload.- Reference metadata is preserved by immutable schema transformations such as
renameColumn; choice data belongs to form state rather thanDataSchema. ListBlockrequires a stablelistSchema()so headers, filters, and column behavior remain available for empty and failed result pages. Record-derived schemas remain useful for simpler custom views, but a reusable grid should declare its list columns explicitly.
See the real schemas in PostsListBlock.java, the data-grid guide, the data-forms guide, and the surrounding compositions model.