Skip to content

Commit

Permalink
#44 Added the remainign features for With and Copy.
Browse files Browse the repository at this point in the history
(I think - it's been so many months since I've been near SuccincT, I may have missed something).
Took advantage of the Deconstruct on the Success<T> type and modified various tests to use this. Found a bug in the process (the paramter name on the deconstruct was wrong, so this is now fixed.
  • Loading branch information
DavidArno committed Nov 26, 2018
1 parent 49680aa commit 878d2a3
Show file tree
Hide file tree
Showing 23 changed files with 251 additions and 143 deletions.
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<RepositoryUrl>https://github.com/DavidArno/SuccincT</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageLicenseUrl>https://raw.githubusercontent.com/DavidArno/SuccincT/master/LICENSE.txt</PackageLicenseUrl>
<PackageLicense>https://raw.githubusercontent.com/DavidArno/SuccincT/master/LICENSE.txt</PackageLicense>
<PackageProjectUrl>https://github.com/DavidArno/SuccincT</PackageProjectUrl>
<PackageOutputPath>$(SolutionDir)artifacts\</PackageOutputPath>
<IncludeSymbols>True</IncludeSymbols>
Expand Down
12 changes: 6 additions & 6 deletions src/SuccincT.JSON/JSON/ValueOrErrorConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ public override object ReadJson(JsonReader reader,
JsonSerializer serializer)
{
var jsonObject = JObject.Load(reader);
var possibleValue = jsonObject.Properties().TryFirst(p => p.Name == "value");
var possibleError = jsonObject.Properties().TryFirst(p => p.Name == "error");
var (hasValue, value) = jsonObject.Properties().TryFirst(p => p.Name == "value");
var (hasError, error) = jsonObject.Properties().TryFirst(p => p.Name == "error");

if (possibleValue.HasValue)
if (hasValue)
{
return ValueOrError.WithValue(possibleValue.Value.ToObject<string>());
return ValueOrError.WithValue(value.ToObject<string>());
}

if (possibleError.HasValue)
if (hasError)
{
return ValueOrError.WithError(possibleError.Value.ToObject<string>());
return ValueOrError.WithError(error.ToObject<string>());
}

throw new JsonSerializationException(
Expand Down
114 changes: 80 additions & 34 deletions src/SuccincT/Functional/WithExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,14 @@ public static Option<T> TryCopy<T>(this T @object) where T : class
{
var cachedTypeInfo = GetCachedTypeInfo(typeof(T));

var constructorToUse = cachedTypeInfo.CachedPublicConstructors.OrderByDescending(cc => cc.Parameters.Count)
.TryFirst();
var (hasValue, value) = cachedTypeInfo.CachedPublicConstructors
.OrderByDescending(cc => cc.Parameters.Count)
.TryFirst();

if (!constructorToUse.HasValue) return Option<T>.None();
if (!hasValue) return Option<T>.None();

var sourceReadProperties = cachedTypeInfo.Properties.Except(cachedTypeInfo.WriteOnlyProperties).ToList();
var constructorParameters = constructorToUse.Value.Parameters;
var constructorParameters = value.Parameters;

var constructorParameterValues =
GetConstructorParameterValuesForCopy(@object, sourceReadProperties, constructorParameters);
Expand All @@ -33,7 +34,8 @@ public static Option<T> TryCopy<T>(this T @object) where T : class

var propertiesToOverwrite = sourceReadProperties
.Select(p => destWriteProperties.TryFirst(x => p.Name == x.Name))
.Where(x => x.HasValue).Select(x => x.Value);
.Where(x => x.HasValue)
.Select(x => x.Value);

foreach (var propertyToOverwrite in propertiesToOverwrite)
{
Expand All @@ -43,15 +45,47 @@ public static Option<T> TryCopy<T>(this T @object) where T : class
return Option<T>.Some(newObject);
}

private static object[] GetConstructorParameterValuesForCopy<T>(T @object,
IEnumerable<PropertyInfo> sourceReadProperties,
IEnumerable<ParameterInfo>
constructorParameters)
public static T Copy<T>(this T @object) where T : class
{
return constructorParameters.Select(p => sourceReadProperties.TryFirst(x => AreLinked(x, p)))
.Where(x => x.HasValue).Select(x => x.Value)
.Select(sourceReadProperty => sourceReadProperty.GetValue(@object, null))
.ToArray();
var cachedTypeInfo = GetCachedTypeInfo(typeof(T));

var (hasValue, value) = cachedTypeInfo.CachedPublicConstructors
.OrderByDescending(cc => cc.Parameters.Count)
.TryFirst();

if (!hasValue)
{
throw new CopyException(
$"Type {typeof(T).Name} does not supply a public constructor for use with Copy.");
}

var sourceReadProperties = cachedTypeInfo.Properties.Except(cachedTypeInfo.WriteOnlyProperties).ToList();
var constructorParameters = value.Parameters;

var constructorParameterValues =
GetConstructorParameterValuesForCopy(@object, sourceReadProperties, constructorParameters);

if (constructorParameterValues.Length != constructorParameters.Count)
{
throw new CopyException(
$"Type {typeof(T).Name} does not supply a suitable constructor for use with Copy, which allows all " +
"non-writable properties to be set via that constructor.");
}

var newObject = Activator.CreateInstance(typeof(T), constructorParameterValues) as T;
var destWriteProperties = cachedTypeInfo.Properties.Except(cachedTypeInfo.ReadOnlyProperties);

var propertiesToOverwrite = sourceReadProperties
.Select(p => destWriteProperties.TryFirst(x => p.Name == x.Name))
.Where(x => x.HasValue)
.Select(x => x.Value);

foreach (var propertyToOverwrite in propertiesToOverwrite)
{
CopyPropertyValue(@object, propertyToOverwrite, newObject);
}

return newObject;
}

public static Option<T> TryWith<T, TProps>(this T itemToCopy, TProps propertiesToUpdate)
Expand All @@ -62,11 +96,11 @@ public static Option<T> TryWith<T, TProps>(this T itemToCopy, TProps propertiesT
var cachedTypeInfo = GetCachedTypeInfo(typeof(T));
var sourceReadProperties = cachedTypeInfo.Properties.Except(cachedTypeInfo.WriteOnlyProperties).ToList();
var updateProperties = typeof(TProps).GetRuntimeProperties().Where(x => x.CanRead).ToList();
var constructorToUse = ConstructorToUseForWith(cachedTypeInfo, updateProperties, sourceReadProperties);
var (hasValue, value) = ConstructorToUseForWith(cachedTypeInfo, updateProperties, sourceReadProperties);

if (!constructorToUse.HasValue) return Option<T>.None();
if (!hasValue) return Option<T>.None();

var constructorParameters = constructorToUse.Value.Parameters;
var constructorParameters = value.Parameters;
var constructorParameterValues = MapUpdateValuesToConstructorParameters(itemToCopy,
propertiesToUpdate,
constructorParameters,
Expand Down Expand Up @@ -105,13 +139,16 @@ public static T With<T, TProps>(this T itemToCopy, TProps propertiesToUpdate)
var cachedTypeInfo = GetCachedTypeInfo(typeof(T));
var sourceReadProperties = cachedTypeInfo.Properties.Except(cachedTypeInfo.WriteOnlyProperties).ToList();
var updateProperties = typeof(TProps).GetRuntimeProperties().Where(x => x.CanRead).ToList();
var constructorToUse = ConstructorToUseForWith(cachedTypeInfo, updateProperties, sourceReadProperties);
var (hasValue, value) = ConstructorToUseForWith(cachedTypeInfo, updateProperties, sourceReadProperties);

if (!constructorToUse.HasValue) throw new CopyException(
$"Type {typeof(T).Name} does not supply a suitable constructor for use with With, which allows all " +
"non-writable properties to be set via that constructor.");
if (!hasValue)
{
throw new CopyException(
$"Type {typeof(T).Name} does not supply a suitable constructor for use with With, which allows all " +
"non-writable properties to be set via that constructor.");
}

var constructorParameters = constructorToUse.Value.Parameters;
var constructorParameters = value.Parameters;
var constructorParameterValues = MapUpdateValuesToConstructorParameters(itemToCopy,
propertiesToUpdate,
constructorParameters,
Expand Down Expand Up @@ -144,11 +181,24 @@ public static T With<T, TProps>(this T itemToCopy, TProps propertiesToUpdate)
}
}

private static T CreateNewObjectApplyingUpdates<T, TProps>(T itemToCopy,
TProps propertiesToUpdate,
object[] constructorParameterValues,
IEnumerable<PropertyInfo> propsToSetFromSourceObject,
IEnumerable<(PropertyInfo Value, PropertyInfo PropToUpdate)> propsToSetFromUpdateData)
private static object[] GetConstructorParameterValuesForCopy<T>(
T @object,
IEnumerable<PropertyInfo> sourceReadProperties,
IEnumerable<ParameterInfo>
constructorParameters)
{
return constructorParameters.Select(p => sourceReadProperties.TryFirst(x => AreLinked(x, p)))
.Where(x => x.HasValue).Select(x => x.Value)
.Select(sourceReadProperty => sourceReadProperty.GetValue(@object, null))
.ToArray();
}

private static T CreateNewObjectApplyingUpdates<T, TProps>(
T itemToCopy,
TProps propertiesToUpdate,
object[] constructorParameterValues,
IEnumerable<PropertyInfo> propsToSetFromSourceObject,
IEnumerable<(PropertyInfo Value, PropertyInfo PropToUpdate)> propsToSetFromUpdateData)
where T : class where TProps : class
{
var newObject = Activator.CreateInstance(typeof(T), constructorParameterValues) as T;
Expand Down Expand Up @@ -244,10 +294,8 @@ orderby constructor.Parameters.Count descending
select constructor).TryFirst();
}

private static CachedTypeInfo GetCachedTypeInfo(Type type)
{
return CachedTypeInfoDetails.GetOrAddValue(type.FullName, () => new CachedTypeInfo(type));
}
private static CachedTypeInfo GetCachedTypeInfo(Type type)
=> CachedTypeInfoDetails.GetOrAddValue(type.FullName, () => new CachedTypeInfo(type));

private static T GetOrAddValue<T>(this Dictionary<string, T> dictionary, string key, Func<T> createValue)
{
Expand All @@ -272,10 +320,8 @@ private static bool AreLinked(MemberInfo memberInfo, PropertyInfo propertyInfo)
private static bool AreLinked(ParameterInfo parameterInfo, PropertyInfo propertyInfo) =>
string.Equals(parameterInfo.Name, propertyInfo.Name, StringComparison.CurrentCultureIgnoreCase);

private static void CopyPropertyValue<T>(T from, PropertyInfo property, T to) where T : class
{
property.SetValue(to, property.GetValue(from, null));
}
private static void CopyPropertyValue<T>(T from, PropertyInfo property, T to) where T : class
=> property.SetValue(to, property.GetValue(from, null));

private static void CopyPropertyValue<T1, T2>(T1 from,
PropertyInfo fromProperty,
Expand Down
8 changes: 4 additions & 4 deletions src/SuccincT/Options/OptionMatcher{T,TR}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,20 @@ void IOptionActionMatcher<T>.Exec() =>

TResult IUnionFuncPatternMatcherAfterElse<TResult>.Result()
{
var possibleResult = _union.Case == Variant.Case1
var (hasValue, value) = _union.Case == Variant.Case1
? _case1FunctionSelector.DetermineResult(_union.Case1)
: _case2FunctionSelector.DetermineResult(_union.Case2);

return possibleResult.HasValue ? possibleResult.Value : _elseAction(_option);
return hasValue ? value : _elseAction(_option);
}

void IUnionActionPatternMatcherAfterElse.Exec()
{
var possibleResult = _union.Case == Variant.Case1
var (hasValue, value) = _union.Case == Variant.Case1
? _case1FunctionSelector.DetermineResult(_union.Case1)
: _case2FunctionSelector.DetermineResult(_union.Case2);

_ = possibleResult.HasValue ? possibleResult.Value : _elseAction(_option);
_ = hasValue ? value : _elseAction(_option);
}

IOptionFuncMatcher<T, TResult> INoneFuncMatchHandler<T, TResult>.Do(Func<TResult> action)
Expand Down
8 changes: 4 additions & 4 deletions src/SuccincT/Options/SuccessMatcher{T,TR}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,20 +80,20 @@ void ISuccessActionMatcher<T>.Exec() =>

TResult IUnionFuncPatternMatcherAfterElse<TResult>.Result()
{
var possibleResult = _union.Case == Variant.Case1
var (hasValue, value) = _union.Case == Variant.Case1
? _case1FunctionSelector.DetermineResult(_union.Case1)
: _case2FunctionSelector.DetermineResult(_union.Case2);

return possibleResult.HasValue ? possibleResult.Value : _elseAction(_success);
return hasValue ? value : _elseAction(_success);
}

void IUnionActionPatternMatcherAfterElse.Exec()
{
var possibleResult = _union.Case == Variant.Case1
var (hasValue, value) = _union.Case == Variant.Case1
? _case1FunctionSelector.DetermineResult(_union.Case1)
: _case2FunctionSelector.DetermineResult(_union.Case2);

_ = possibleResult.HasValue ? possibleResult.Value : _elseAction(_success);
_ = hasValue ? value : _elseAction(_success);
}

ISuccessFuncMatcher<T, TResult> ISuccessFuncMatchHandler<T, TResult>.Do(Func<TResult> action)
Expand Down
4 changes: 2 additions & 2 deletions src/SuccincT/Options/Success{T}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ public override bool Equals(object obj)
public static implicit operator bool(Success<T> success) => !success.IsFailure;
public static implicit operator Success<T>(T value) => Success.CreateFailure(value);

public void Deconstruct(out bool hasError, out T error)
=> (hasError, error) = (!IsFailure, IsFailure ? _error : default);
public void Deconstruct(out bool isSuccess, out T error)
=> (isSuccess, error) = (!IsFailure, IsFailure ? _error : default);

private Union<T, bool> CreateUnion() => IsFailure ? new Union<T, bool>(_error) : new Union<T, bool>(true);
}
Expand Down
8 changes: 4 additions & 4 deletions src/SuccincT/Options/ValueOrErrorMatcher{TR}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,20 @@ void IValueOrErrorActionMatcher.Exec() =>

TResult IUnionFuncPatternMatcherAfterElse<TResult>.Result()
{
var possibleResult = _valueOrError.HasValue
var (hasValue, value) = _valueOrError.HasValue
? _valueFunctionSelector.DetermineResult(_valueOrError.Value)
: _errorFunctionSelector.DetermineResult(_valueOrError.Error);

return possibleResult.HasValue ? possibleResult.Value : _elseAction(_valueOrError);
return hasValue ? value : _elseAction(_valueOrError);
}

void IUnionActionPatternMatcherAfterElse.Exec()
{
var possibleResult = _valueOrError.HasValue
var (hasValue, value) = _valueOrError.HasValue
? _valueFunctionSelector.DetermineResult(_valueOrError.Value)
: _errorFunctionSelector.DetermineResult(_valueOrError.Error);

_ = possibleResult.HasValue ? possibleResult.Value : _elseAction(_valueOrError);
_ = hasValue ? value : _elseAction(_valueOrError);
}

private void RecordValueAction(Func<string, IList<string>, bool> withTest,
Expand Down
8 changes: 4 additions & 4 deletions src/SuccincT/Options/ValueOrErrorMatcher{TV,TE,TR}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,20 @@ void IValueOrErrorActionMatcher<TValue, TError>.Exec()

TResult IUnionFuncPatternMatcherAfterElse<TResult>.Result()
{
var possibleResult = _valueOrError.HasValue
var (hasValue, value) = _valueOrError.HasValue
? _valueFunctionSelector.DetermineResult(_valueOrError.Value)
: _errorFunctionSelector.DetermineResult(_valueOrError.Error);

return possibleResult.HasValue ? possibleResult.Value : _elseAction(_valueOrError);
return hasValue ? value : _elseAction(_valueOrError);
}

void IUnionActionPatternMatcherAfterElse.Exec()
{
var possibleResult = _valueOrError.HasValue
var (hasValue, value) = _valueOrError.HasValue
? _valueFunctionSelector.DetermineResult(_valueOrError.Value)
: _errorFunctionSelector.DetermineResult(_valueOrError.Error);

_ = possibleResult.HasValue ? possibleResult.Value : _elseAction(_valueOrError);
_ = hasValue ? value : _elseAction(_valueOrError);
}

private void RecordValueAction(Func<TValue, IList<TValue>, bool> withTest,
Expand Down
8 changes: 4 additions & 4 deletions src/SuccincT/PatternMatchers/Matcher{T1,T2,T3,T4,TR}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,17 +167,17 @@ IFuncMatcher<T1, T2, T3, T4, TResult> IFuncWhereHandler<IFuncMatcher<T1, T2, T3,

void IActionMatcherAfterElse.Exec()
{
var possibleResult = _functionSelector.DetermineResult(_item);
_ = possibleResult.HasValue ? possibleResult.Value : _elseFunction(_item);
var (hasValue, value) = _functionSelector.DetermineResult(_item);
_ = hasValue ? value : _elseFunction(_item);
}

TResult IFuncMatcher<T1, T2, T3, T4, TResult>.Result() =>
_functionSelector.DetermineResultUsingDefaultIfRequired(_item);

TResult IFuncMatcherAfterElse<TResult>.Result()
{
var possibleResult = _functionSelector.DetermineResult(_item);
return possibleResult.HasValue ? possibleResult.Value : _elseFunction(_item);
var (hasValue, value) = _functionSelector.DetermineResult(_item);
return hasValue ? value : _elseFunction(_item);
}

private void RecordFunction(Func<(T1, T2, T3, T4), IList<EitherTuple<T1, T2, T3, T4>>, bool> test,
Expand Down
8 changes: 4 additions & 4 deletions src/SuccincT/PatternMatchers/Matcher{T1,T2,T3,TR}.cs
Original file line number Diff line number Diff line change
Expand Up @@ -164,17 +164,17 @@ IFuncMatcher<T1, T2, T3, TResult> IFuncWhereHandler<IFuncMatcher<T1, T2, T3, TRe

void IActionMatcherAfterElse.Exec()
{
var possibleResult = _functionSelector.DetermineResult(_item);
_ = possibleResult.HasValue ? possibleResult.Value : _elseFunction(_item);
var (hasValue, value) = _functionSelector.DetermineResult(_item);
_ = hasValue ? value : _elseFunction(_item);
}

TResult IFuncMatcher<T1, T2, T3, TResult>.Result() =>
_functionSelector.DetermineResultUsingDefaultIfRequired(_item);

TResult IFuncMatcherAfterElse<TResult>.Result()
{
var possibleResult = _functionSelector.DetermineResult(_item);
return possibleResult.HasValue ? possibleResult.Value : _elseFunction(_item);
var (hasValue, value) = _functionSelector.DetermineResult(_item);
return hasValue ? value : _elseFunction(_item);
}

private void RecordFunction(Func<(T1, T2, T3), IList<EitherTuple<T1, T2, T3>>, bool> test,
Expand Down
Loading

0 comments on commit 878d2a3

Please sign in to comment.