RejectedOperation#

One operation of a change set that Project.Apply didn't apply: where it sits in the change set, and why it was rejected. You get instances from ApplyReport.Rejected, and as CandidateResult.Error when Project.Evaluate can't score a candidate.

Declaration#

namespace Plen;

public sealed class RejectedOperation : IDeepCloneable<RejectedOperation>, IEquatable<RejectedOperation>

Both properties are required, so new RejectedOperation() without Path and Message doesn't compile.

Properties#

Property Type Description
Path string Position of the operation in the change set, as {group}.{verb}[{index}].
Message string Why the operation was rejected.

Path#

public required string Path { get; set; }

group is tasks, links, resources or assignments; verb is add, update or remove; index is the zero-based position in that list of SchedulingChanges. For example, links.add[0] is the first entry of changes.Links.Add. AppliedOperation.Path uses the same form, so every operation of a change set shows up exactly once in either Applied or Rejected.

In CandidateResult.Error, a failure while solving or leveling the candidate carries the input path of that error instead, such as a field path. When that error has no path, Path is an empty string.

Message#

public required string Message { get; set; }

The validation message, without the path, e.g. duplicate link id or lag must be finite. Each change-set type lists the messages its operations can produce; see LinkAdd for an example. The text is meant for logs and diagnostics, not for matching in code.

Methods#

DeepClone#

public RejectedOperation DeepClone()

Returns a copy with the same Path and Message.

Equals#

public bool Equals(RejectedOperation? other)
public override bool Equals(object? obj)

Value equality: two instances are equal when Path and Message are equal. GetHashCode is consistent with it.

ToString#

public override string ToString()

Returns a readable rendering of both properties, for logging.

Example#

Add a link whose id already exists, and read the rejection:

using Plen;

var start = Moment.FromYmdHm(2026, 4, 6, 8, 0)
    ?? throw new InvalidOperationException("invalid date");

var data = new ProjectData
{
    Tasks =
    [
        new TaskData(1) { StartDate = start, Duration = 2.0 },
        new TaskData(2) { StartDate = start, Duration = 3.0 },
    ],
    Links = [new LinkData { Id = "L1", Source = 1, Target = 2 }],
};

var project = new Project();
project.Solve(data, new SolveOptions());

var changes = new SchedulingChanges();
changes.Links.Add.Add(new LinkAdd { Id = "L1", Source = 2, Target = 1 });

var report = project.Apply(changes);
foreach (var rejected in report.Rejected)
    Console.WriteLine($"{rejected.Path}: {rejected.Message}");
// links.add[0]: duplicate link id

A rejection sets ApplyReport.NeedsResync; to continue, see Recover from rejected changes.