Reference

TrainInputReader

Package Trax.MediatorNamespace Trax.Mediator.Services.TrainExecutionSource TrainInputReader.cs

The rules for reading a caller's train input JSON, as a public static class. ITrainExecutionService's QueueAsync, RunAsync and PrepareAsync read input through it, and a host or package that takes train input JSON on a path of its own calls it to accept and refuse the same JSON. One that builds an input object and hands it to ITrainExecutionService as JSON, as the GraphQL train mutations do, writes it with Write.

Signature

namespace Trax.Mediator.Services.TrainExecution;
 
public static class TrainInputReader
{
    public const int StoredInputGrowthFactor = 4;
 
    public static object Read(
        string? inputJson,
        TrainRegistration registration,
        int maxInputJsonBytes
    );
 
    public static string ResolveSavedInput(
        string savedInputJson,
        TrainRegistration registration,
        int maxInputJsonBytes
    );
 
    public static string Write(object? input, Type inputType);
}

Members

MemberDescription
StoredInputGrowthFactorHow many times MaxInputJsonBytes a queued input's stored form may be: 4
ReadReads a caller's JSON into the train's input type
WriteWrites an input as JSON that Read reads back as the same input
ResolveSavedInputTurns a run's saved input into JSON that Read accepts

Read

ParameterTypeDescription
inputJsonstring?The caller's JSON. Null or blank is read as {}
registrationTrainRegistrationThe train whose InputType is read
maxInputJsonBytesintThe size cap in UTF-8 bytes, normally MediatorConfiguration.MaxInputJsonBytes

Returns: an instance of registration.InputType, never null.

Throws: TrainInputValidationException when the JSON is larger than maxInputJsonBytes, checked before parsing. JsonException when it cannot be read as the input type, names a property twice in the same or another casing, uses $id/$values reference metadata where the type expects a list, is the literal null, or is blank for an input type that needs values (a constructor parameter with no default, or a required member). When JSON whose root carries an $id is refused, the message also says that reference metadata is not accepted and names Write and ResolveSavedInput.

Property names match whatever their case, so {"Amount":5} and {"amount":5} are the same input (Trax.Docs/adr/0023). $ref is read as an unknown property, not as a reference. Read does not authorize; PrepareAsync calls it after the caller has been authorized for the train, so a caller who may not use the train learns nothing about its input from a parse error.

Write

ParameterTypeDescription
inputobject?The input, an instance of inputType
inputTypeTypeThe train's input type

Returns: the input as JSON that Read reads back as the same input.

It writes with the options Read reads with: the host's SaveTrainParameters options, so their naming policy and converters apply, without reference handling. Those options preserve references (TraxJsonSerializationOptions.Default does, unless the host gives others), and a list written with them is an object of $values that Read refuses. Up to Trax.Api.GraphQL 1.61.0 the GraphQL train mutations wrote their input with the host's options directly, so from Trax.Mediator 1.23.2, which stopped honouring reference metadata in a caller's input, any run, queue or prepare whose input held a list or an array failed before the train ran.

ResolveSavedInput

Turns a run's saved input (the Input column SaveTrainParameters() writes) into JSON that Read reads as the input the run was given. A re-queue passes the saved input through it first.

ParameterTypeDescription
savedInputJsonstringThe saved input
registrationTrainRegistrationThe train the input was saved for
maxInputJsonBytesintThe cap Read will hold the result to, normally MediatorConfiguration.MaxInputJsonBytes

Returns: the input as a plain JSON tree; an input with no reference metadata at its root, written back compact; input that is not JSON, unchanged, for Read to refuse.

Throws: JsonException when the saved input's reference metadata is malformed or sits where a reference-preserving writer would not have put it, one $id is given to two values, a $ref names an $id that is not there, a value refers to a value that contains it (a cycle has no plain tree), or the resolved input nests more than 64 levels deep. TrainInputValidationException when the plain form is larger than maxInputJsonBytes. Writing stops the moment the result passes the cap, so references that copy one value many times over never make a small saved input into a large one.

SaveTrainParameters() writes with TraxJsonSerializationOptions.Default, which preserves references: every object carries an $id, a list is written as {"$id":"2","$values":[...]}, and a second occurrence of the same object as {"$ref":"3"}. ResolveSavedInput rewrites a saved input whose root object carries an $id as the plain tree it stands for: each $id dropped, each $values object written as its array, and each $ref written as a full copy of the value it names, so no two members of the input share an object. It recognises the metadata wherever it sits in its object, because a jsonb column orders an object's properties shortest name first, so an input with a one- or two-letter property such as id comes back as {"id": 5, "$id": "1", ...}. Before Trax.Mediator 1.25.0 only an $id in first place was recognised.

Any other input keeps every member as it is, reference metadata below the root included, and is written back compact: a saved input read from a jsonb column comes back with a space after every colon and comma, which would put one saved at MaxInputJsonBytes over it. Before Trax.Mediator 1.25.0 such an input was returned unchanged. Compacting removes only that growth: the saved form carries every member, so an input that left members out and was accepted near the cap can still be refused when it is re-queued.

Nothing it does depends on the input type, so it reveals nothing about the input and may run before the caller is authorized. Its result still goes through Read, and through authorization, like any other input.

Example

public static class OrderInputs
{
    public static OrderInput ReadOrder(
        string inputJson,
        TrainRegistration registration,
        MediatorConfiguration configuration) =>
        (OrderInput)TrainInputReader.Read(inputJson, registration, configuration.MaxInputJsonBytes);
 
    public static string WriteOrder(OrderInput input) =>
        TrainInputReader.Write(input, typeof(OrderInput));
}

See also