Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DI-1318] Implement Delete By Natural Key #50

Merged
merged 8 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DataImport.Models.Tests/DataMapSerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,7 +1056,7 @@ private static DataMapper[] DeserializeNormalMap(ResourceMetadata[] resourceMeta

private static DataMapper[] DeserializeDeleteByIdMap(ResourceMetadata[] resourceMetadata, string jsonMap)
{
var dataMapSerializer = new DeleteDataMapSerializer();
var dataMapSerializer = new DeleteDataMapSerializer("/testResource", resourceMetadata);

return dataMapSerializer.Deserialize(jsonMap);
}
Expand Down
2 changes: 2 additions & 0 deletions DataImport.Models/Datamap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,7 @@ public DataMap()
public ICollection<DataMapAgent> DataMapAgents { get; set; }

public bool IsDeleteOperation { get; set; }

public bool IsDeleteByNaturalKey { get; set; }
}
}
232 changes: 231 additions & 1 deletion DataImport.Models/DeleteDataMapSerializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,32 @@ namespace DataImport.Models
{
public class DeleteDataMapSerializer
{
private readonly string _resourcePath;
private readonly ResourceMetadata[] _resourceMetadatas;
private readonly bool _isDeleteByNaturalKey;

public DeleteDataMapSerializer()
{
_isDeleteByNaturalKey = false;
}

public DeleteDataMapSerializer(Resource resource)
: this(resource.Path, ResourceMetadata.DeserializeFrom(resource))
{
}

public DeleteDataMapSerializer(DataMap dataMap)
: this(dataMap.ResourcePath, ResourceMetadata.DeserializeFrom(dataMap))
{
_isDeleteByNaturalKey = dataMap.IsDeleteByNaturalKey;
}

public DeleteDataMapSerializer(string resourcePath, ResourceMetadata[] resourceMetadatas)
{
_resourcePath = resourcePath;
_resourceMetadatas = resourceMetadatas.Where(r => r.Required).ToArray();
}

public string Serialize(DataMapper[] mappings)
{
return SerializeObjectForDeleteById(mappings.Single()).ToString(Formatting.Indented);
Expand All @@ -38,7 +64,9 @@ public DataMapper[] Deserialize(string jsonMap)
}
public DataMapper[] Deserialize(JObject jsonMap)
{
return DeserializeObjectForDeleteById(jsonMap).ToArray();
return _isDeleteByNaturalKey
? DeserializeObjectForDeleteByNaturalKey(_resourceMetadatas, jsonMap).ToArray()
: DeserializeObjectForDeleteById(jsonMap).ToArray();
}

private JObject SerializeObjectForDeleteById(DataMapper node)
Expand Down Expand Up @@ -72,6 +100,208 @@ private List<DataMapper> DeserializeObjectForDeleteById(JToken objectToken)
return result;
}

private List<DataMapper> DeserializeObjectForDeleteByNaturalKey(IReadOnlyList<ResourceMetadata> nodeMetadatas, JToken objectToken)
{
var jobject = objectToken as JObject;

if (jobject == null)
throw new InvalidOperationException(
"Cannot deserialize mappings from JSON, because an object literal was expected. " +
"Instead, found: " +
$"{objectToken.ToString(Formatting.Indented)}");

var result = new List<DataMapper>();

var nodes = jobject.Children().Cast<JProperty>().ToArray();

foreach (var node in nodes)
{
if (node.Name != "Id" && nodeMetadatas.All(x => x.Name != node.Name))
throw new InvalidOperationException(
$"Cannot deserialize mappings from JSON, because the key '{node.Name}' should not exist " +
$"according to the metadata for resource '{_resourcePath}'.");
}

foreach (var nodeMetadata in nodeMetadatas)
{
var node = nodes.SingleOrDefault(n => n.Name == nodeMetadata.Name);

if (node == null)
{
result.Add(nodeMetadata.BuildInitialMappings());
}
else
{
var propertyValue = node.Children().Single();

if (nodeMetadata.DataType == "array")
{
var arrayItemMetadata = nodeMetadata.Children.Single();
result.Add(new DataMapper
{
Name = nodeMetadata.Name,
Children = DeserializeArray(arrayItemMetadata, propertyValue)
});
}
else if (nodeMetadata.Children.Any())
{
result.Add(new DataMapper
{
Name = nodeMetadata.Name,
Children = DeserializeObjectForDeleteByNaturalKey(nodeMetadata.Children, propertyValue)
});
}
else
{
result.Add(DeserializeMappedValue(nodeMetadata, propertyValue));
}
}
}

return result;
}

private List<DataMapper> DeserializeArray(ResourceMetadata arrayItemMetadata, JToken arrayToken)
{
var array = arrayToken as JArray;

if (array == null)
throw new InvalidOperationException(
"Cannot deserialize mappings from JSON, because an array literal was expected. " +
"Instead, found: " +
$"{arrayToken.ToString(Formatting.Indented)}");

var nodes = array.Children().ToArray();

var result = new List<DataMapper>();

foreach (var node in nodes)
{
if (arrayItemMetadata.DataType == "array")
{
var nestedArrayItemMetadata = arrayItemMetadata.Children.Single();
result.Add(new DataMapper
{
Name = arrayItemMetadata.Name,
Children = DeserializeArray(nestedArrayItemMetadata, node)
});
}
else if (arrayItemMetadata.Children.Any())
{
result.Add(new DataMapper
{
Name = arrayItemMetadata.Name,
Children = DeserializeObjectForDeleteByNaturalKey(arrayItemMetadata.Children, node)
});
}
else
{
result.Add(DeserializeMappedValue(arrayItemMetadata, node));
}
}

return result;
}

private DataMapper DeserializeMappedValue(ResourceMetadata metadata, JToken token)
{
if (token is JValue)
{
var staticValue = ((JValue) token).Value;

return new DataMapper
{
Name = metadata.Name,
Value = DeserializeRawValue(staticValue)
};
}

if (token is JObject)
{
var columnSource = (JObject) token;
return DeserializeColumnSource(metadata, columnSource);
}

throw new InvalidOperationException(
$"Cannot deserialize mappings from JSON, because the key '{metadata.Name}' " +
$"was expected to have a {metadata.DataType} value. Instead, the value was: " +
$"{token.ToString(Formatting.Indented)}");
}

private DataMapper DeserializeColumnSource(ResourceMetadata nodeMetadata, JObject columnSource)
{
var keys = columnSource.Children().Cast<JProperty>().Select(x => x.Name).ToArray();

if (!keys.Contains("Column"))
{
throw new InvalidOperationException(
$"Cannot deserialize mappings from JSON, because the key '{nodeMetadata.Name}' was " +
"expected to have a Column Source declaration as its value, indicating the source column. " +
$"Instead, the value was: {columnSource.ToString(Formatting.Indented)}");
}

var allowedKeys = new[] { "Column", "Lookup", "Default" };
foreach (var key in keys)
{
if (!allowedKeys.Contains(key))
{
throw new InvalidOperationException(
$"Cannot deserialize mappings from JSON, because the key '{nodeMetadata.Name}' was " +
"expected to have a Column Source declaration as its value. Instead, the value " +
$"contains unexpected property '{key}': {columnSource.ToString(Formatting.Indented)}");
}
}

var nonStringLiterals = new List<string>();
foreach (var property in columnSource.Children().Cast<JProperty>())
{
if (property.Name != "Default")
{
bool isStringLiteral = (property.Value as JValue)?.Value is string;

if (!isStringLiteral)
nonStringLiterals.Add(property.Name);
}
}

if (nonStringLiterals.Any())
throw new InvalidOperationException(
$"Cannot deserialize mappings from JSON, because the key '{nodeMetadata.Name}' was " +
"expected to have a valid Column Source declaration as its value. It has a Column Source, " +
$"but one with invalid content. {string.Join(", ", nonStringLiterals.Select(x => $"'{x}'"))} " +
$"should be strings: {columnSource.ToString(Formatting.Indented)}");

var invalidDefault = new List<string>();
foreach (var property in columnSource.Children().Cast<JProperty>())
{
if (property.Name == "Default")
{
bool isValueLiteral = property.Value is JValue;

if (!isValueLiteral)
invalidDefault.Add(property.Name);
}
}

if (invalidDefault.Any())
throw new InvalidOperationException(
$"Cannot deserialize mappings from JSON, because the key '{nodeMetadata.Name}' was " +
"expected to have a valid Column Source declaration as its value. It has a Column Source, " +
$"but one with an invalid default. 'Default' should be a single value: {columnSource.ToString(Formatting.Indented)}");

var @default = columnSource["Default"]?.Type == JTokenType.Boolean
? DeserializeRawValue(columnSource.Value<bool>("Default"))
: DeserializeRawValue(columnSource.Value<string>("Default"));

return new DataMapper
{
Name = nodeMetadata.Name,
SourceColumn = columnSource.Value<string>("Column"),
SourceTable = columnSource.Value<string>("Lookup"),
Default = @default
};
}

private static string DeserializeRawValue(object rawValue)
{
if (rawValue == null)
Expand Down
Loading
Loading