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

Adding fixes for null values #103

Merged
merged 1 commit into from
Oct 5, 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
12 changes: 4 additions & 8 deletions src/Candid/CandidConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,15 @@ public CandidValue FromObject(object obj)
public CandidTypedValue FromTypedObject<T>(T obj)
where T : notnull
{
CandidValue value = this.FromObjectInternal(obj, out ICandidValueMapper mapper);
CandidType type = mapper.GetMappedCandidType(obj.GetType()) ?? throw new InvalidOperationException("Type does not map");
CandidValue value = this.FromObjectInternal<T>(obj, out ICandidValueMapper mapper);
CandidType type = mapper.GetMappedCandidType(typeof(T)) ?? throw new InvalidOperationException("Type does not map");
return new CandidTypedValue(value, type);
}


private CandidValue FromObjectInternal(object obj, out ICandidValueMapper mapper)
private CandidValue FromObjectInternal<T>(object obj, out ICandidValueMapper mapper)
{
if (ReferenceEquals(obj, null))
{
throw new ArgumentNullException(nameof(obj));
}
mapper = this.ResolveMapper(obj.GetType());
mapper = this.ResolveMapper(typeof(T));

return mapper!.Map(obj, this);
}
Expand Down
34 changes: 0 additions & 34 deletions src/Candid/Mapping/Mappers/RawCandidMapper.cs

This file was deleted.

25 changes: 20 additions & 5 deletions src/Candid/Mapping/Mappers/RecordMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@
using EdjCase.ICP.Candid.Models;
using System;
using System.Collections.Generic;
using System.Reflection;

namespace EdjCase.ICP.Candid.Mapping.Mappers
{

internal class RecordMapper : ICandidValueMapper
{
public CandidType CandidType { get; }
public CandidRecordType CandidType { get; }
public Type Type { get; }
public Dictionary<CandidTag, PropertyMetaData> Properties { get; }

Expand All @@ -27,11 +26,23 @@ public object Map(CandidValue value, CandidConverter converter)
object obj = Activator.CreateInstance(this.Type);
foreach ((CandidTag tag, PropertyMetaData property) in this.Properties)
{
object? fieldValue;
if (!record.Fields.TryGetValue(tag, out CandidValue fieldCandidValue))
{
throw new Exception($"Could not map candid record to type '{this.Type}'. Record is missing field '{tag}'");
if (!this.CandidType.Fields.TryGetValue(tag, out CandidType fieldType))
{
// Record has property that is not in the candid type, skip
continue;
}
if (fieldType is not CandidOptionalType)
{
// Only throw if fieldType is not an opt value since the value is unset (null)
// or has an extra
throw new Exception($"Could not map candid record to type '{this.Type}'. Record is missing field '{tag}'");
}
// Set to optional value if not specified in record
fieldCandidValue = new CandidOptional(null);
}
object? fieldValue;
if (property.CustomMapper != null)
{
fieldValue = property.CustomMapper.Map(fieldCandidValue, converter);
Expand All @@ -52,7 +63,11 @@ public CandidValue Map(object value, CandidConverter converter)
{
object propValue = property.PropertyInfo.GetValue(value);
CandidValue v;
if (property.CustomMapper != null)
if (propValue == null)
{
v = new CandidOptional(null);
}
else if (property.CustomMapper != null)
{
v = property.CustomMapper.Map(propValue, converter);
}
Expand Down
8 changes: 6 additions & 2 deletions src/Candid/Mapping/Mappers/VariantMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,13 @@ public CandidValue Map(object obj, CandidConverter converter)


CandidValue innerValue;
if (optionInfo.Type != null)
if (innerObj == null)
{
innerValue = new CandidOptional(null);
}
else if (optionInfo.Type != null)
{
innerValue = converter.FromObject(innerObj!);
innerValue = converter.FromObject(innerObj);
}
else
{
Expand Down
37 changes: 33 additions & 4 deletions test/Candid.Tests/CandidConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public void Text_From_String()
this.Test(value, expected, (x, y) => x == y);
}


[Fact]
public void Vector_From_List()
{
Expand Down Expand Up @@ -85,7 +86,7 @@ public void Vector_From_Dict()
public class RecordClass
{
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public string StringField { get; set; }
public OptionalValue<string> StringField { get; set; }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
public int IntField { get; set; }

Expand All @@ -108,21 +109,21 @@ public void Record_From_Class()
{
var values = new RecordClass
{
StringField = "StringValue",
StringField = OptionalValue<string>.WithValue("StringValue"),
IntField = 2
};
CandidTag stringFieldName = CandidTag.FromName("StringField");
CandidTag intFieldName = CandidTag.FromName("IntField");
var fields = new Dictionary<CandidTag, CandidValue>
{
{stringFieldName, CandidValue.Text("StringValue")},
{stringFieldName, new CandidOptional(CandidValue.Text("StringValue"))},
{intFieldName, CandidValue.Int32(2)}
};
CandidValue expectedValue = new CandidRecord(fields);

var fieldTypes = new Dictionary<CandidTag, CandidType>
{
{stringFieldName, new CandidPrimitiveType(PrimitiveType.Text)},
{stringFieldName, new CandidOptionalType(new CandidPrimitiveType(PrimitiveType.Text))},
{intFieldName, new CandidPrimitiveType(PrimitiveType.Int32)}
};
CandidType expectedType = new CandidRecordType(fieldTypes);
Expand All @@ -136,6 +137,34 @@ public void Record_From_Class()
}


[Fact]
public void Parital_Record_From_Class()
{
// Deserialize a record with a missing opt field, the value should be a 'NoValue' opt
CandidTag stringFieldName = CandidTag.FromName("StringField");
CandidTag intFieldName = CandidTag.FromName("IntField");
var fields = new Dictionary<CandidTag, CandidValue>
{
// Missing string field
{intFieldName, CandidValue.Int32(2)}
};
CandidValue value = new CandidRecord(fields);

var fieldTypes = new Dictionary<CandidTag, CandidType>
{
{stringFieldName, new CandidOptionalType(new CandidPrimitiveType(PrimitiveType.Text))},
{intFieldName, new CandidPrimitiveType(PrimitiveType.Int32)}
};
CandidType type = new CandidRecordType(fieldTypes);
CandidTypedValue typedValue = CandidTypedValue.FromValueAndType(value, type);


RecordClass obj = CandidConverter.Default.ToObject<RecordClass>(value);
Assert.NotNull(obj);
Assert.Equal(new OptionalValue<string>(), obj.StringField);
}



[Variant]
public class VariantValueClass
Expand Down
Loading