First attempt at dependency validation

This commit is contained in:
Chris Bell 2025-07-01 20:24:32 -05:00
parent 11ba80b34b
commit 51ec6a480d
4 changed files with 116 additions and 8 deletions

View File

@ -9,7 +9,7 @@ public class CharacterTemplate : SzfObject
public string GameSystem { get; set; } = string.Empty;
public List<RequiredDatasetReference> RequiredDatasets { get; set; } = new();
public List<TemplateSection> Sections { get; set; } = new();
public override void PopulateFromMetadata(Dictionary<string, SzfField> metadata)
{
base.PopulateFromMetadata(metadata);
@ -184,6 +184,44 @@ public class CharacterTemplate : SzfObject
if (string.IsNullOrWhiteSpace(GameSystem))
result.AddError("GameSystem is required for CharacterTemplate");
// NEW: Validate resolved datasets
foreach (var reqDatasetRef in RequiredDatasets)
{
if (reqDatasetRef.Reference == null)
{
result.AddError($"Required dataset alias '{reqDatasetRef.Alias}' has a null reference.");
}
else
{
if (reqDatasetRef.ResolvedDataset == null)
{
result.AddWarning(
$"Required dataset for alias '{reqDatasetRef.Alias}' (GUID: {reqDatasetRef.Reference.Guid}) could not be resolved or loaded.");
}
else
{
// Optionally, perform a deeper validation of the resolved dataset.
// For example, if the template expects specific entries or fields in the dataset:
// if (!reqDatasetRef.ResolvedDataset.Entries.Any(e => e.Name == "ExpectedEntry"))
// {
// result.AddWarning($"Resolved dataset for '{reqDatasetRef.Alias}' is missing 'ExpectedEntry'.");
// }
// Also, you can run the resolved dataset's own validation:
var datasetValidation = reqDatasetRef.ResolvedDataset.Validate();
if (!datasetValidation.IsValid)
{
result.AddWarning(
$"Resolved dataset '{reqDatasetRef.ResolvedDataset.Metadata.Name}' (GUID: {reqDatasetRef.ResolvedDataset.Metadata.Guid}) for alias '{reqDatasetRef.Alias}' has validation issues:");
foreach (var error in datasetValidation.Errors)
{
result.AddWarning($"- Dataset Error: {error}");
}
}
}
}
}
return result;
}
}
@ -192,6 +230,7 @@ public class RequiredDatasetReference
{
public string Alias { get; set; } = string.Empty;
public DatasetReference? Reference { get; set; }
public Dataset? ResolvedDataset { get; set; } // New property to hold the resolved Dataset object
}
public class TemplateSection

View File

@ -10,7 +10,7 @@ public class Dataset : SzfObject
public string DatasetType { get; set; } = string.Empty;
public string ImageUrl { get; set; } = string.Empty; // New field for dataset image
public List<DatasetEntry> Entries { get; set; } = new();
public Dictionary<string, object> Metadata { get; set; } = new(); // To store other metadata fields
public Dictionary<string, object> OtherMetadata { get; set; } = new(); // To store other metadata fields
public override void PopulateFromMetadata(Dictionary<string, SzfField> metadata)
@ -29,7 +29,7 @@ public class Dataset : SzfObject
if (!new[] { "Name", "Version", "GUID", "Description", "Author", "Type", "ImageUrl" }
.Contains(kvp.Key, StringComparer.OrdinalIgnoreCase))
{
Metadata[kvp.Key] = kvp.Value.Value!;
OtherMetadata[kvp.Key] = kvp.Value.Value!;
}
}
}
@ -116,7 +116,7 @@ public class Dataset : SzfObject
writer.WriteField("ImageUrl", "text", ImageUrl);
// Write any other dynamic metadata
foreach (var kvp in Metadata)
foreach (var kvp in OtherMetadata)
{
writer.WriteField(kvp.Key, "text", kvp.Value); // Assuming text for generic metadata for now
}

View File

@ -132,7 +132,7 @@
errorMessage = string.Empty;
}
private void ParseSzf() // Changed from async Task to void
private async void ParseSzf() // Changed from async Task to void for event handler, but calls async internally
{
ClearMessages();
try
@ -143,9 +143,13 @@
return;
}
// Perform initial parsing
currentSzfObject = SzfParser.Parse(szfInputText);
statusMessage = $"SZF successfully parsed. Type: {currentSzfObject.TypeIdentifier}, Name: {currentSzfObject.Metadata.Name}";
szfOutputText = string.Empty; // Clear generated output on new parse
// NEW: Resolve dependencies after initial parsing
await ResolveSzfObjectDependencies(currentSzfObject);
}
catch (SzfParseException ex)
{
@ -199,8 +203,8 @@
{
Guid = currentSzfObject.Metadata.Guid.ToString(),
Name = currentSzfObject.Metadata.Name,
Type = currentSzfObject.TypeIdentifier, // The type identifier (e.g., "character", "dataset")
Version = currentSzfObject.Metadata.Version.ToString(),
Type = currentSzfObject.TypeIdentifier, // Use TypeIdentifier
Version = currentSzfObject.Metadata.Version?.ToString() ?? "1.0.0", // Handle null version
Content = rawContent // Storing the raw SZF string
};
@ -255,6 +259,9 @@
szfInputText = loadedSzfFileDto.Content!; // Using null-forgiving operator
szfOutputText = string.Empty; // Clear previous generated output
statusMessage = $"SZF '{currentSzfObject.Metadata.Name}' (GUID: {currentSzfObject.Metadata.Guid}) successfully loaded and parsed.";
// NEW: Resolve dependencies after loading and parsing
await ResolveSzfObjectDependencies(currentSzfObject);
}
catch (SzfParseException ex)
{
@ -267,4 +274,63 @@
Logger.LogError(ex, "Unexpected error during SZF loading.");
}
}
}
/// <summary>
/// Attempts to resolve dependencies for the given SzfObject.
/// Currently, this resolves RequiredDatasets for CharacterTemplates.
/// </summary>
/// <param name="szfObject">The SzfObject to resolve dependencies for.</param>
private async Task ResolveSzfObjectDependencies(SzfObject szfObject)
{
if (szfObject is CharacterTemplate characterTemplate)
{
foreach (var reqDatasetRef in characterTemplate.RequiredDatasets)
{
if (reqDatasetRef.Reference != null)
{
var datasetGuid = reqDatasetRef.Reference.Guid;
try
{
var loadedSzfFileDto = await SzfStorageService.GetSzfFileByGuidAsync(datasetGuid.ToString());
if (loadedSzfFileDto != null && !string.IsNullOrWhiteSpace(loadedSzfFileDto.Content))
{
// Parse the content of the referenced dataset
var parsedReferencedObject = SzfParser.Parse(loadedSzfFileDto.Content);
if (parsedReferencedObject is Dataset dataset)
{
reqDatasetRef.ResolvedDataset = dataset;
// Add a message that the dataset was resolved
statusMessage += $"\n- Resolved dataset '{dataset.Metadata.Name}' (GUID: {dataset.Metadata.Guid}) for alias '{reqDatasetRef.Alias}'.";
// You can optionally run validation on the resolved dataset immediately
// if you want feedback during resolution, in addition to the main object's validation.
var validationResult = dataset.Validate();
if (!validationResult.IsValid)
{
statusMessage += $"\n (Warning: Resolved dataset has validation issues.)";
}
}
else
{
// Log a warning if the resolved object is not a Dataset
statusMessage += $"\n- Warning: Reference for alias '{reqDatasetRef.Alias}' (GUID: {datasetGuid}) resolved to an object of type '{parsedReferencedObject?.GetType().Name ?? "Unknown"}' instead of 'Dataset'.";
}
}
else
{
// Log a warning if the dataset file is not found or has no content
statusMessage += $"\n- Warning: Dataset for alias '{reqDatasetRef.Alias}' (GUID: {datasetGuid}) not found in storage or is empty. Cannot resolve.";
}
}
catch (Exception ex)
{
// Log any parsing errors for the referenced dataset
statusMessage += $"\n- Warning: Failed to load or parse dataset for alias '{reqDatasetRef.Alias}' (GUID: {datasetGuid}): {ex.Message}";
Logger.LogError(ex, $"Failed to load/parse referenced dataset {datasetGuid} for CharacterTemplate.");
}
}
}
}
// Add similar logic for other SzfObject types that might have dependencies
}
}

View File

@ -111,10 +111,13 @@ A dataset file is a collection of structured entries.
# Metadata for the entire item collection
[Metadata]
# These fields are mandatory for all datasets.
Name (text) = Core Fantasy Items
Type (text) = items
Guid (text) = c3d4e5f6-g7h8-9012-cdef-123456789012
Version (text) = 1.0.0
# Optional metadata fields
Author (text) = Fantasy Creator
Description (text-field) = A collection of basic items for any fantasy campaign.
# Definition for a Longsword