mirror of
https://github.com/ajisaacs/OpenNest.git
synced 2026-10-24 13:51:06 -04:00
fix(io): read a malformed removedClamps value without failing the nest
The optional per-plate removedClamps list was deserialized as a typed List<string>, so a string or a list of numbers threw JsonException and the whole nest failed to open. It now reads leniently: a non-array value reads as none removed and non-string entries are skipped. The saved zones load either way, so a removed clamp's area is never silently re-blocked or lost. PM 9f3d676b-8a1c-4d9a-b877-b5e01e70c3f0
This commit is contained in:
1 parent
2b208c9b6a
commit
53db543d6b
2 files changed
+79
-1
No files matched your search
@@ -246,6 +246,46 @@ public class ClampRailPersistenceTests
|
||||
Assert.Equal(7, plate.ClampZones.Count);
|
||||
}
|
||||
|
||||
// Slice 6 task 6.4 (Slice 3b quality review): optional removal fields read leniently
|
||||
// and are only written when there is something to say.
|
||||
|
||||
private static MemoryStream RemovedClamp3Nest()
|
||||
{
|
||||
var nest = new Nest("railed");
|
||||
nest.PlateDefaults.Size = new Size(48, 120);
|
||||
nest.SetClampRail(Machine());
|
||||
var plate = nest.CreatePlate();
|
||||
plate.SetClampRail(Machine(), new[] { "clamp-3" });
|
||||
return Write(nest);
|
||||
}
|
||||
|
||||
private static MemoryStream WithPlateProperty(MemoryStream file, string name, string json) =>
|
||||
RewriteNestJson(file, text =>
|
||||
{
|
||||
var root = JsonNode.Parse(text)!;
|
||||
root["plates"]![0]![name] = JsonNode.Parse(json);
|
||||
return root.ToJsonString();
|
||||
});
|
||||
|
||||
// PM 9f3d676b: a wrongly shaped removedClamps value must not fail the whole nest.
|
||||
[Theory]
|
||||
[InlineData("\"clamp-3\"", false)]
|
||||
[InlineData("[-1]", false)]
|
||||
[InlineData("{\"id\": \"clamp-3\"}", false)]
|
||||
[InlineData("[-1, \"clamp-3\"]", true)]
|
||||
public void MalformedRemovedClampsLoadWithoutFailingTheNest(string value, bool keepsRemoval)
|
||||
{
|
||||
var tampered = WithPlateProperty(RemovedClamp3Nest(), "removedClamps", value);
|
||||
|
||||
var plate = Assert.Single(new NestReader(tampered).Read().Plates);
|
||||
|
||||
// The saved zones always load, so the removed clamp's area is never re-blocked or lost.
|
||||
Assert.Equal(7, plate.ClampZones.Count);
|
||||
Assert.DoesNotContain(plate.ClampZones, z => z.Id == "clamp-3");
|
||||
Assert.Equal(keepsRemoval, plate.RemovedClamps.Contains("clamp-3"));
|
||||
Assert.Equal(keepsRemoval, plate.ClampRail != null);
|
||||
}
|
||||
|
||||
/// <summary>Removes a named property wherever it appears in the document, simulating a
|
||||
/// key an older writer never emitted. Rebuilds via <see cref="JsonNode"/> so the result
|
||||
/// is always valid JSON regardless of whether the property was first, last or only one
|
||||
|
||||
@@ -119,6 +119,41 @@ namespace OpenNest.IO
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads an optional list of strings without failing the nest: a non-array value
|
||||
/// reads as an empty list and non-string entries are skipped.
|
||||
/// </summary>
|
||||
public sealed class LenientStringListConverter : JsonConverter<List<string>>
|
||||
{
|
||||
public override List<string> Read(
|
||||
ref Utf8JsonReader reader,
|
||||
Type typeToConvert,
|
||||
JsonSerializerOptions options
|
||||
)
|
||||
{
|
||||
using var document = JsonDocument.ParseValue(ref reader);
|
||||
var list = new List<string>();
|
||||
if (document.RootElement.ValueKind != JsonValueKind.Array)
|
||||
return list;
|
||||
foreach (var element in document.RootElement.EnumerateArray())
|
||||
if (element.ValueKind == JsonValueKind.String)
|
||||
list.Add(element.GetString());
|
||||
return list;
|
||||
}
|
||||
|
||||
public override void Write(
|
||||
Utf8JsonWriter writer,
|
||||
List<string> value,
|
||||
JsonSerializerOptions options
|
||||
)
|
||||
{
|
||||
writer.WriteStartArray();
|
||||
foreach (var item in value)
|
||||
writer.WriteStringValue(item);
|
||||
writer.WriteEndArray();
|
||||
}
|
||||
}
|
||||
|
||||
public record ClampRailDto
|
||||
{
|
||||
/// <summary>Clamp extent along the origin-side edge (X).</summary>
|
||||
@@ -172,8 +207,11 @@ namespace OpenNest.IO
|
||||
|
||||
/// <summary>
|
||||
/// Rail clamp ids removed from this sheet as a last resort (D8). Missing or empty
|
||||
/// means none were removed; <see cref="Clamps"/> already excludes them.
|
||||
/// means none were removed; <see cref="Clamps"/> already excludes them. Read
|
||||
/// leniently: a wrongly shaped value reads as none removed, non-string entries
|
||||
/// are skipped, and the saved zones load either way.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(LenientStringListConverter))]
|
||||
public List<string> RemovedClamps { get; init; } = new();
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user