Skip to content

Commit

Permalink
implemented checkTrait graphql endpoint
Browse files Browse the repository at this point in the history
simplified trait handling and added equality checks
  • Loading branch information
maximiliancsuk committed Nov 23, 2023
1 parent 12c3d9b commit 6298dad
Show file tree
Hide file tree
Showing 9 changed files with 110 additions and 35 deletions.
34 changes: 27 additions & 7 deletions backend/Omnikeeper.Base/Entity/CIAttributeTemplate.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Omnikeeper.Base.Utils;
using Omnikeeper.Entity.AttributeValues;
using System;
using System.Collections.Generic;
using System.Collections;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;

Expand All @@ -27,7 +27,7 @@ public interface ICIAttributeValueConstraint
});
}

public class CIAttributeValueConstraintTextLength : ICIAttributeValueConstraint
public class CIAttributeValueConstraintTextLength : ICIAttributeValueConstraint, IEquatable<CIAttributeValueConstraintTextLength>
{
public readonly int? Minimum;
public readonly int? Maximum;
Expand Down Expand Up @@ -56,9 +56,13 @@ public bool HasErrors(IAttributeValue value)
return true;
}
}

public bool Equals(CIAttributeValueConstraintTextLength? other) => other != null && Minimum == other.Minimum && Maximum == other.Maximum;
public override bool Equals(object? other) => Equals(other as CIAttributeValueConstraintTextLength);
public override int GetHashCode() => HashCode.Combine(Minimum, Maximum);
}

public class CIAttributeValueConstraintArrayLength : ICIAttributeValueConstraint
public class CIAttributeValueConstraintArrayLength : ICIAttributeValueConstraint, IEquatable<CIAttributeValueConstraintArrayLength>
{
public readonly int? Minimum;
public readonly int? Maximum;
Expand Down Expand Up @@ -93,9 +97,13 @@ public bool HasErrors(IAttributeValue value)
return true;
}
}

public bool Equals(CIAttributeValueConstraintArrayLength? other) => other != null && Minimum == other.Minimum && Maximum == other.Maximum;
public override bool Equals(object? other) => Equals(other as CIAttributeValueConstraintArrayLength);
public override int GetHashCode() => HashCode.Combine(Minimum, Maximum);
}

public class CIAttributeValueConstraintTextRegex : ICIAttributeValueConstraint
public class CIAttributeValueConstraintTextRegex : ICIAttributeValueConstraint, IEquatable<CIAttributeValueConstraintTextRegex>
{
public readonly string RegexStr;
public readonly RegexOptions RegexOptions;
Expand Down Expand Up @@ -133,28 +141,40 @@ public bool HasErrors(IAttributeValue value)
return true;
}
}

public bool Equals(CIAttributeValueConstraintTextRegex? other) => other != null && RegexStr == other.RegexStr && RegexOptions == other.RegexOptions;
public override bool Equals(object? other) => Equals(other as CIAttributeValueConstraintTextRegex);
public override int GetHashCode() => HashCode.Combine(RegexStr, RegexOptions);
}

public class CIAttributeTemplate
public class CIAttributeTemplate : IEquatable<CIAttributeTemplate>
{
public readonly string Name;
public readonly AttributeValueType? Type;
public readonly bool? IsArray;
public readonly IEnumerable<ICIAttributeValueConstraint> ValueConstraints;
public readonly ICIAttributeValueConstraint[] ValueConstraints;
public readonly bool? IsID;

public static CIAttributeTemplate BuildFromParams(string name, AttributeValueType? type, bool? isArray, bool? isID, params ICIAttributeValueConstraint[] valueConstraints)
{
return new CIAttributeTemplate(name, type, isArray, isID, valueConstraints);
}

public CIAttributeTemplate(string name, AttributeValueType? type, bool? isArray, bool? isID, IEnumerable<ICIAttributeValueConstraint> valueConstraints)
public CIAttributeTemplate(string name, AttributeValueType? type, bool? isArray, bool? isID, ICIAttributeValueConstraint[] valueConstraints)
{
Name = name;
Type = type;
IsArray = isArray;
ValueConstraints = valueConstraints;
IsID = isID;
}

public bool Equals(CIAttributeTemplate? other)
{
// NOTE: see https://stackoverflow.com/questions/69133392/computing-hashcode-of-combination-of-value-type-and-array why we use StruturalComparisons
return other != null && Name == other.Name && Type == other.Type && IsArray == other.IsArray && IsID == other.IsID && StructuralComparisons.StructuralEqualityComparer.Equals(ValueConstraints, other.ValueConstraints);
}
public override bool Equals(object? other) => Equals(other as CIAttributeTemplate);
public override int GetHashCode() => HashCode.Combine(Name, Type, IsArray, IsID, StructuralComparisons.StructuralEqualityComparer.GetHashCode(ValueConstraints));
}
}
26 changes: 13 additions & 13 deletions backend/Omnikeeper.Base/Entity/GenericTrait.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;

namespace Omnikeeper.Base.Entity
{
public interface ITrait
{
public string ID { get; }
public IImmutableSet<string> AncestorTraits { get; }
public string[] AncestorTraits { get; }
public TraitOriginV1 Origin { get; }

public IImmutableList<TraitAttribute> RequiredAttributes { get; }
public IImmutableList<TraitAttribute> OptionalAttributes { get; }
public TraitAttribute[] RequiredAttributes { get; }
public TraitAttribute[] OptionalAttributes { get; }

public IImmutableList<TraitRelation> OptionalRelations { get; }
public TraitRelation[] OptionalRelations { get; }
}

public static class TraitExtensions
Expand All @@ -33,8 +32,8 @@ public static IReadOnlySet<string> GetRelevantPredicateIDs(this ITrait trait)
/// </summary>
public class GenericTrait : ITrait
{
private GenericTrait(string id, TraitOriginV1 origin, IImmutableList<TraitAttribute> requiredAttributes, IImmutableList<TraitAttribute> optionalAttributes,
IImmutableList<TraitRelation> optionalRelations, IImmutableSet<string> ancestorTraits)
private GenericTrait(string id, TraitOriginV1 origin, TraitAttribute[] requiredAttributes, TraitAttribute[] optionalAttributes,
TraitRelation[] optionalRelations, string[] ancestorTraits)
{
ID = id;
Origin = origin;
Expand All @@ -47,20 +46,21 @@ private GenericTrait(string id, TraitOriginV1 origin, IImmutableList<TraitAttrib
public string ID { get; set; }
public TraitOriginV1 Origin { get; set; }

public IImmutableSet<string> AncestorTraits { get; set; }
public IImmutableList<TraitAttribute> RequiredAttributes { get; set; }
public IImmutableList<TraitAttribute> OptionalAttributes { get; set; }
public string[] AncestorTraits { get; set; }

public IImmutableList<TraitRelation> OptionalRelations { get; set; }
public TraitAttribute[] RequiredAttributes { get; set; }
public TraitAttribute[] OptionalAttributes { get; set; }

public TraitRelation[] OptionalRelations { get; set; }

public static GenericTrait Build(string id, TraitOriginV1 origin,
IEnumerable<TraitAttribute> requiredAttributes,
IEnumerable<TraitAttribute> optionalAttributes,
IEnumerable<TraitRelation> optionalRelations,
ISet<string> ancestorTraits)
{
return new GenericTrait(id, origin, requiredAttributes.ToImmutableList(), optionalAttributes.ToImmutableList(),
optionalRelations.ToImmutableList(), ancestorTraits.ToImmutableHashSet());
return new GenericTrait(id, origin, requiredAttributes.ToArray(), optionalAttributes.ToArray(),
optionalRelations.ToArray(), ancestorTraits.ToArray());
}
}
}
19 changes: 18 additions & 1 deletion backend/Omnikeeper.Base/Entity/RecursiveTrait.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Omnikeeper.Base.Model.TraitBased;
using Omnikeeper.Base.Utils;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
Expand All @@ -17,9 +18,17 @@ public TraitRelation(string identifier, RelationTemplate relationTemplate)
Identifier = identifier;
RelationTemplate = relationTemplate;
}

public bool Equals(TraitRelation? other)
{
// NOTE: see https://stackoverflow.com/questions/69133392/computing-hashcode-of-combination-of-value-type-and-array why we use StruturalComparisons
return other != null && Identifier == other.Identifier && StructuralComparisons.StructuralEqualityComparer.Equals(RelationTemplate, other.RelationTemplate);
}
public override bool Equals(object? other) => Equals(other as TraitRelation);
public override int GetHashCode() => HashCode.Combine(Identifier, RelationTemplate);
}

public class TraitAttribute
public class TraitAttribute : IEquatable<TraitAttribute>
{
public readonly CIAttributeTemplate AttributeTemplate;
public readonly string Identifier;
Expand All @@ -29,6 +38,14 @@ public TraitAttribute(string identifier, CIAttributeTemplate attributeTemplate)
Identifier = identifier;
AttributeTemplate = attributeTemplate;
}

public bool Equals(TraitAttribute? other)
{
// NOTE: see https://stackoverflow.com/questions/69133392/computing-hashcode-of-combination-of-value-type-and-array why we use StruturalComparisons
return other != null && Identifier == other.Identifier && StructuralComparisons.StructuralEqualityComparer.Equals(AttributeTemplate , other.AttributeTemplate);
}
public override bool Equals(object? other) => Equals(other as TraitAttribute);
public override int GetHashCode() => HashCode.Combine(Identifier, AttributeTemplate);
}

public enum TraitOriginType
Expand Down
16 changes: 13 additions & 3 deletions backend/Omnikeeper.Base/Entity/RelationTemplate.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@

using System;
using System.Collections;

namespace Omnikeeper.Base.Entity
{
public class RelationTemplate
public class RelationTemplate : IEquatable<RelationTemplate>
{
public readonly string PredicateID;
public readonly bool DirectionForward;
Expand All @@ -12,7 +14,15 @@ public RelationTemplate(string predicateID, bool directionForward, string[]? tra
{
PredicateID = predicateID;
DirectionForward = directionForward;
TraitHints = traitHints ?? System.Array.Empty<string>();
TraitHints = traitHints ?? Array.Empty<string>();
}

public bool Equals(RelationTemplate? other)
{
// NOTE: see https://stackoverflow.com/questions/69133392/computing-hashcode-of-combination-of-value-type-and-array why we use StruturalComparisons
return other != null && PredicateID == other.PredicateID && DirectionForward == other.DirectionForward && StructuralComparisons.StructuralEqualityComparer.Equals(TraitHints, other.TraitHints);
}
public override bool Equals(object? other) => Equals(other as RelationTemplate);
public override int GetHashCode() => HashCode.Combine(PredicateID, DirectionForward, StructuralComparisons.StructuralEqualityComparer.GetHashCode(TraitHints));
}
}
11 changes: 5 additions & 6 deletions backend/Omnikeeper.Base/Model/CoreTraits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
using Omnikeeper.Base.Generator;
using Omnikeeper.Base.Model.TraitBased;
using Omnikeeper.Entity.AttributeValues;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;

namespace Omnikeeper.Base.Model
{
Expand Down Expand Up @@ -40,13 +40,12 @@ public class TraitEmpty : ITrait

public string ID => StaticID;

public IImmutableSet<string> AncestorTraits => ImmutableHashSet<string>.Empty;
public string[] AncestorTraits => Array.Empty<string>();

public TraitOriginV1 Origin => new TraitOriginV1(TraitOriginType.Core);

public IImmutableList<TraitAttribute> RequiredAttributes { get => ImmutableList<TraitAttribute>.Empty; }
public IImmutableList<TraitAttribute> OptionalAttributes { get => ImmutableList<TraitAttribute>.Empty; }
public IImmutableList<TraitRelation> RequiredRelations { get => ImmutableList<TraitRelation>.Empty; }
public IImmutableList<TraitRelation> OptionalRelations { get => ImmutableList<TraitRelation>.Empty; }
public TraitAttribute[] RequiredAttributes { get => Array.Empty<TraitAttribute>(); }
public TraitAttribute[] OptionalAttributes { get => Array.Empty<TraitAttribute>(); }
public TraitRelation[] OptionalRelations { get => Array.Empty<TraitRelation>(); }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public static class GenericTraitEntityHelper

foreach (var taFieldInfo in attributeFieldInfos)
{
var constraints = FieldInfo2AttributeValueConstraints(taFieldInfo.FieldInfo).ToList();
var constraints = FieldInfo2AttributeValueConstraints(taFieldInfo.FieldInfo).ToArray();
var taa = taFieldInfo.TraitAttributeAttribute;
var targetAttributeList = (taa.optional) ? optionalAttributes : requiredAttributes;
targetAttributeList.Add(new TraitAttribute(taa.taName, new CIAttributeTemplate(taa.aName, taFieldInfo.AttributeValueType, taFieldInfo.IsArray, taFieldInfo.IsID, constraints)));
Expand Down
30 changes: 30 additions & 0 deletions backend/Omnikeeper/GraphQL/GraphQLQueryRoot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Omnikeeper.Service;
using Quartz;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
Expand Down Expand Up @@ -401,6 +402,35 @@ private void CreateMain()
return traits.Values.OrderBy(t => t.ID);
});



Field<BooleanGraphType>("checkTrait")
.Arguments(
new QueryArgument<NonNullGraphType<UpsertRecursiveTraitInputType>> { Name = "trait" }
)
.Resolve(context =>
{
var userContext = context.GetUserContext();
var trait = context.GetArgument<UpsertRecursiveTraitInput>("trait")!;
var existingTraits = traitsHolder.GetTraits();
if (!existingTraits.TryGetValue(trait.ID, out var existingTrait))
return false;
if (existingTrait is not GenericTrait existingGenericTrait) // cannot compare non-generic trait
throw new ExecutionError("Cannot check against non-generic trait");
// NOTE: we don't compare Origin, because the trait can be defined anywhere
return existingGenericTrait.ID == trait.ID
&& StructuralComparisons.StructuralEqualityComparer.Equals(existingGenericTrait.RequiredAttributes, trait.RequiredAttributes)
&& StructuralComparisons.StructuralEqualityComparer.Equals(existingGenericTrait.OptionalAttributes, trait.OptionalAttributes)
&& StructuralComparisons.StructuralEqualityComparer.Equals(existingGenericTrait.OptionalRelations, trait.OptionalRelations)
&& StructuralComparisons.StructuralEqualityComparer.Equals(existingGenericTrait.AncestorTraits, trait.RequiredTraits)
;
});

Field<ListGraphType<EffectiveTraitType>>("effectiveTraitsForTrait")
.Arguments(
new QueryArgument<NonNullGraphType<StringGraphType>> { Name = "traitID" },
Expand Down
4 changes: 2 additions & 2 deletions backend/Omnikeeper/Model/EffectiveTraitModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public async Task<IDictionary<Guid, EffectiveTrait>> GetEffectiveTraitsForTrait(
case GenericTrait tt:
ILookup<(Guid ciid, string predicateID), MergedRelation> fromRelations = Enumerable.Empty<MergedRelation>().ToLookup(x => default((Guid ciid, string predicateID)));
ILookup<(Guid ciid, string predicateID), MergedRelation> toRelations = Enumerable.Empty<MergedRelation>().ToLookup(x => default((Guid ciid, string predicateID)));
if (tt.OptionalRelations.Count > 0)
if (tt.OptionalRelations.Length > 0)
{
var ciids = cis.Select(ci => ci.ID).ToHashSet();
if (tt.OptionalRelations.Any(r => r.RelationTemplate.DirectionForward))
Expand All @@ -89,7 +89,7 @@ public async Task<IDictionary<Guid, EffectiveTrait>> GetEffectiveTraitsForTrait(

foreach (var ci in cis)
{
var effectiveTraitAttributes = new Dictionary<string, MergedCIAttribute>(tt.RequiredAttributes.Count + tt.OptionalAttributes.Count);
var effectiveTraitAttributes = new Dictionary<string, MergedCIAttribute>(tt.RequiredAttributes.Length + tt.OptionalAttributes.Length);

// required attributes
foreach (var ta in tt.RequiredAttributes)
Expand Down
3 changes: 1 addition & 2 deletions backend/Tests/Serialization/TraitAttributeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
using NUnit.Framework;
using Omnikeeper.Base.Entity;
using Omnikeeper.Base.Utils;
using System.Collections.Generic;
using System.Text.Json;

namespace Tests.Serialization
Expand All @@ -13,7 +12,7 @@ public class TraitAttributeTest
public void TestTraitAttribute()
{
var t = new TraitAttribute("traitIdentifier",
new CIAttributeTemplate("attributeName", Omnikeeper.Entity.AttributeValues.AttributeValueType.MultilineText, true, false, new List<ICIAttributeValueConstraint>()
new CIAttributeTemplate("attributeName", Omnikeeper.Entity.AttributeValues.AttributeValueType.MultilineText, true, false, new ICIAttributeValueConstraint[]
{
new CIAttributeValueConstraintTextRegex("foo[12]", System.Text.RegularExpressions.RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.ECMAScript),
new CIAttributeValueConstraintTextLength(null, 2),
Expand Down

0 comments on commit 6298dad

Please sign in to comment.