/// <summary>
        /// Map a class structure to a tree-definition.
        /// </summary>
        /// <param name="context">Context to use during mapping</param>
        /// <param name="rootAliasIdentifier">Identifier for the root-alias of the tree</param>
        /// <exception cref="Exceptions.MissingTypeException">
        /// Thrown when the type for the root-alias cannot be found in the context.
        /// </exception>
        /// <exception cref="Exceptions.MappingFailureException">
        /// Thrown when an error occurs during mapping.
        /// </exception>
        /// <returns>Newly created immutable tree-definition</returns>
        public static TreeDefinition MapTree(this Context context, string rootAliasIdentifier)
        {
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (string.IsNullOrEmpty(rootAliasIdentifier))
            {
                throw new ArgumentException($"Alias-type '{rootAliasIdentifier}' is not valid", nameof(rootAliasIdentifier));
            }

            if (!context.Types.TryGetType(rootAliasIdentifier, out var rootAliasType))
            {
                throw new Exceptions.MissingTypeException(rootAliasIdentifier);
            }

            try
            {
                return(TreeDefinitionBuilder.Create(rootAliasIdentifier, b =>
                {
                    // Map the root alias
                    var rootAlias = b.MapAlias(context, rootAliasType);

                    // Map the nodes in the root-alias
                    b.MapNodes(context, rootAlias);
                }));
            }
            catch (Exception e)
            {
                throw new Exceptions.MappingFailureException(e);
            }
        }
Ejemplo n.º 2
0
        public void TreeIsMappedAccordingToSchemeRules()
        {
            var typeCollection = TypeCollection.Create(typeof(TreeMapperTests).Assembly);
            var context        = Context.Create(typeCollection, FieldSource.Properties);

            var tree = context.MapTree($"{typeof(TreeMapperTests).FullName}+IRootInterface");

            var expectedTree = TreeDefinitionBuilder.Create($"{typeof(TreeMapperTests).FullName}+IRootInterface", b =>
            {
                var rootAlias = b.PushAlias(
                    $"{typeof(TreeMapperTests).FullName}+IRootInterface",
                    $"{typeof(TreeMapperTests).FullName}+TestNodeB");

                var innerAlias = b.PushAlias(
                    $"{typeof(TreeMapperTests).FullName}+IInnerInterface",
                    $"{typeof(TreeMapperTests).FullName}+TestNodeA",
                    $"{typeof(TreeMapperTests).FullName}+TestNodeB");

                b.PushNode($"{typeof(TreeMapperTests).FullName}+TestNodeB", nb =>
                {
                    nb.PushNumberField("Field1");
                    nb.PushAliasField("Field2", innerAlias);
                });
                b.PushNode($"{typeof(TreeMapperTests).FullName}+TestNodeA", nb =>
                {
                    nb.PushStringField("Field1");
                    nb.PushNumberField("Field2");
                    nb.PushBooleanField("Field3");
                    nb.PushAliasField("Field4", innerAlias, isArray: true);
                    nb.PushAliasField("Field5", innerAlias);
                });
            });

            Assert.Equal(expectedTree, tree);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Map the implementations of an alias to a tree-defintion builder.
        /// </summary>
        /// <remarks>
        /// If nodes do not yet exist in the builder they are pushed, otherwise this will verify that
        /// the existing nodes match the fields of the new types.
        /// </remarks>
        /// <param name="builder">Builder to push the node definitions to</param>
        /// <param name="context">Context object with dependencies for the mapping</param>
        /// <param name="alias">Alias whose nodes to map</param>
        /// <exception cref="Exceptions.MissingTypeException">
        /// Thrown when a type for a node of the alias cannot be found in the context.
        /// </exception>
        /// <exception cref="Exceptions.DuplicateNodeException">
        /// Thrown when a node with the same identifier is already pushed but fields do not match.
        /// </exception>
        /// <returns>Definitions that the alias types are mapped to</returns>
        public static NodeDefinition[] MapNodes(
            this TreeDefinitionBuilder builder,
            Context context,
            AliasDefinition alias)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (alias == null)
            {
                throw new ArgumentNullException(nameof(alias));
            }

            var nodes = new NodeDefinition[alias.Values.Length];

            for (int i = 0; i < nodes.Length; i++)
            {
                var identifier = alias.Values[i];
                if (!context.Types.TryGetType(identifier, out var nodeType))
                {
                    throw new Exceptions.MissingTypeException(identifier);
                }

                nodes[i] = MapNode(builder, context, nodeType);
            }

            return(nodes);
        }
        public void EnumsCanBeMappedMultipleTimes(Type enumType)
        {
            var context = CreateContext();

            TreeDefinitionBuilder.Create("AliasA", b =>
            {
                b.PushAlias("AliasA", "NodeA");
                b.PushNode("NodeA");

                var definitionA = EnumMapper.MapEnum(b, context, enumType);
                var definitionB = EnumMapper.MapEnum(b, context, enumType);
                Assert.Equal(definitionA, definitionB);
            });
        }
Ejemplo n.º 5
0
        public void NodeIsMappedAccordingToSchemeRules()
        {
            var typeCollection = TypeCollection.Create(typeof(NodeMapperTests).Assembly);
            var context        = Context.Create(typeCollection, FieldSource.Properties);

            var tree = TreeDefinitionBuilder.Create($"{typeof(NodeMapperTests).FullName}+TestNodeA", b =>
            {
                b.MapNode(context, typeof(TestNodeA));
            });
            var expectedTree = TreeDefinitionBuilder.Create($"{typeof(NodeMapperTests).FullName}+TestNodeA", b =>
            {
                var nodeA = b.PushAlias(
                    $"{typeof(NodeMapperTests).FullName}+TestNodeA",
                    $"{typeof(NodeMapperTests).FullName}+TestNodeA");

                var nodeB = b.PushAlias(
                    $"{typeof(NodeMapperTests).FullName}+TestNodeB",
                    $"{typeof(NodeMapperTests).FullName}+TestNodeB");

                var nodeAorB = b.PushAlias(
                    $"{typeof(NodeMapperTests).FullName}+ITestInterface",
                    $"{typeof(NodeMapperTests).FullName}+TestNodeA",
                    $"{typeof(NodeMapperTests).FullName}+TestNodeB");

                var @enum = b.PushEnum(
                    $"{typeof(NodeMapperTests).FullName}+TestEnum",
                    ("A", 0),
                    ("B", 1));

                b.PushNode($"{typeof(NodeMapperTests).FullName}+TestNodeA", nb =>
                {
                    nb.PushStringField("Field1");
                    nb.PushNumberField("Field2");
                    nb.PushBooleanField("Field3");
                    nb.PushEnumField("Field4", @enum);
                    nb.PushAliasField("Field5", nodeA, isArray: true);
                    nb.PushAliasField("Field6", nodeB);
                });
                b.PushNode($"{typeof(NodeMapperTests).FullName}+TestNodeB", nb =>
                {
                    nb.PushNumberField("Field1");
                    nb.PushAliasField("Field2", nodeAorB);
                });
            });
Ejemplo n.º 6
0
        /// <summary>
        /// Map a interface / class / struct to an alias on a tree-defintion builder
        /// </summary>
        /// <remarks>
        /// If alias does not yet exist in the builder it is pushed, otherwise this will verify that
        /// the existing alias matches the implementations of the new type.
        /// </remarks>
        /// <param name="builder">Builder to push the alias definition to</param>
        /// <param name="context">Context object with dependencies for the mapping</param>
        /// <param name="aliasType">Interface / class / struct to map</param>
        /// <exception cref="Exceptions.DuplicateAliasException">
        /// Thrown when a alias with the same identifier is already pushed but implementations do
        /// not match.
        /// </exception>
        /// <returns>Definition that the type was mapped to</returns>
        public static AliasDefinition MapAlias(
            this TreeDefinitionBuilder builder,
            Context context,
            Type aliasType)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (aliasType == null)
            {
                throw new ArgumentNullException(nameof(aliasType));
            }

            var identifier      = aliasType.FullName;
            var implementations = context.Types.GetImplementations(aliasType, context.TypeIgnorePattern);

            if (builder.TryGetAlias(identifier, out var existingAlias))
            {
                // Verify that the existing alias defines the same implementations.
                if (!implementations.Select(i => i.FullName).SequenceEqual(existingAlias.Values))
                {
                    throw new Exceptions.DuplicateAliasException(identifier);
                }

                return(existingAlias);
            }
            else
            {
                var values = implementations.Select(i => i.FullName);

                // Diagnostic logging.
                context.Logger?.LogDebug($"Mapped alias '{identifier}'");
                context.Logger?.LogTrace(
                    $"values:\n{string.Join("\n", values.Select(n => $"* '{n}'"))}");

                // Push new alias
                return(builder.PushAlias(identifier, values));
            }
        }
Ejemplo n.º 7
0
        private static IEnumerable <(TreeDefinition tree, string json)> GetTestTreeJsonPairs()
        {
            yield return
                (
                tree : TreeDefinitionBuilder.Create("AliasA", b =>
            {
                b.PushAlias("AliasA", "NodeA");
                b.PushNode("NodeA", nb =>
                {
                    nb.Comment = "This is a usefull node";
                });
            }),
                json : @"{
                    ""rootAlias"": ""AliasA"",
                    ""aliases"": [
                        { ""identifier"": ""AliasA"", ""values"": [ ""NodeA"" ] }
                    ],
                    ""enums"": [ ],
                    ""nodes"": [
                        {
                            ""nodeType"": ""NodeA"",
                            ""comment"": ""This is a usefull node"",
                            ""fields"": []
                        }
                    ]
                }"
                );

            yield return
                (
                tree : TreeDefinitionBuilder.Create("Alias", b =>
            {
                var alias = b.PushAlias("Alias", "NodeA", "NodeB");
                var @enum = b.PushEnum("Enum", ("A", 0), ("B", 1));
                b.PushNode("NodeA");
                b.PushNode("NodeB", bn =>
                {
                    bn.PushBooleanField("field1");
                    bn.PushStringField("field2");
                    bn.PushNumberField("field3", isArray: true);
                    bn.PushAliasField("field4", alias, isArray: true);
                    bn.PushEnumField("field5", @enum, isArray: true);
                });
            }),
        public void EnumTypesAreMappedAccordingToSchemeRules(Type enumType, BuildEnum referenceBuilder)
        {
            var context = CreateContext();

            var tree = TreeDefinitionBuilder.Create("AliasA", b =>
            {
                b.PushAlias("AliasA", "NodeA");
                b.PushNode("NodeA");

                EnumMapper.MapEnum(b, context, enumType);
            });
            var expectedTree = TreeDefinitionBuilder.Create("AliasA", b =>
            {
                b.PushAlias("AliasA", "NodeA");
                b.PushNode("NodeA");

                referenceBuilder(b);
            });

            Assert.Equal(expectedTree, tree);
        }
Ejemplo n.º 9
0
 private static TreeDefinition CreateTestScheme() => TreeDefinitionBuilder.Create("AliasA", b =>
 {
     var aliasA = b.PushAlias("AliasA", "NodeA");
     var enumA  = b.PushEnum("EnumA", ("Option1", 1), ("Option2", 2));
        public void ThrowsIfTooBigEnumValueIsPushed() => Assert.Throws <InvalidEnumValueException>(() =>
                                                                                                   TreeDefinitionBuilder.Create("AliasA", b =>
        {
            var context = CreateContext();

            b.PushAlias("AliasA", "NodeA");
            b.PushNode("NodeA");

            EnumMapper.MapEnum(b, context, typeof(BigLongEnum));
        }));
Ejemplo n.º 11
0
        /// <summary>
        /// Map a struct / class type to a tree-defintion builder.
        /// </summary>
        /// <remarks>
        /// If node does not yet exist in the builder it is pushed, otherwise this will verify that
        /// the existing node matches the fields of the new type.
        /// </remarks>
        /// <param name="builder">Builder to push the node definition to</param>
        /// <param name="context">Context object with dependencies for the mapping</param>
        /// <param name="nodeType">Struct / class to map</param>
        /// <exception cref="Exceptions.DuplicateNodeException">
        /// Thrown when a node with the same identifier is already pushed but fields do not match.
        /// </exception>
        /// <returns>Definition that the type was mapped to</returns>
        public static NodeDefinition MapNode(
            this TreeDefinitionBuilder builder,
            Context context,
            Type nodeType)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (nodeType == null)
            {
                throw new ArgumentNullException(nameof(nodeType));
            }
            if (nodeType.IsPrimitive || nodeType.IsEnum || nodeType.IsInterface || nodeType.IsAbstract)
            {
                throw new ArgumentException($"Provided type: '{nodeType}' is not a class / struct", nameof(nodeType));
            }

            var identifier = nodeType.FullName;

            if (builder.TryGetNode(identifier, out var existingNode))
            {
                // Verify if this matches with the existing node with the same identifier.
                var fields = GetFields().Where(f => f.@class > Classification.Invalid);

                // Verify that it contains the same amount of fields.
                if (fields.Count() != existingNode.Fields.Length)
                {
                    throw new Exceptions.DuplicateNodeException(identifier);
                }

                // Verify that it contains all the same field identifiers.
                if (fields.Any(f => !existingNode.TryGetField(f.id, out var _)))
                {
                    throw new Exceptions.DuplicateNodeException(identifier);
                }

                return(existingNode);
            }
            else
            {
                // Push new node
                var result = builder.PushNode(identifier, b =>
                {
                    // Add optional comment.
                    b.Comment = context.NodeCommentProvider?.GetComment(nodeType);

                    // Add nodes.
                    foreach (var field in GetFields())
                    {
                        switch (field.@class)
                        {
                        case Classification.Number:
                            b.PushNumberField(field.id, field.isArray);
                            break;

                        case Classification.String:
                            b.PushStringField(field.id, field.isArray);
                            break;

                        case Classification.Boolean:
                            b.PushBooleanField(field.id, field.isArray);
                            break;

                        case Classification.Alias:
                            var aliasDefinition = builder.MapAlias(context, field.type);
                            b.PushAliasField(field.id, aliasDefinition, field.isArray);
                            break;

                        case Classification.Enum:
                            var enumDefinition = builder.MapEnum(context, field.type);
                            b.PushEnumField(field.id, enumDefinition, field.isArray);
                            break;
                        }
                    }
                });

                // Diagnostic logging.
                context.Logger?.LogDebug($"Mapped node '{identifier}'");
                if (result.Fields.Length == 0)
                {
                    context.Logger?.LogTrace("no fields");
                }
                else
                {
                    context.Logger?.LogTrace(
                        $"fields:\n{string.Join("\n", result.Fields.Select(f => $"* '{f}'"))}");
                }

                // Push all the 'inner' nodes of this node.
                foreach (var field in result.Fields)
                {
                    if (field is AliasNodeField aliasField)
                    {
                        MapNodes(builder, context, aliasField.Alias);
                    }
                }

                return(result);
            }

            IEnumerable <(string id, Classification @class, Type type, bool isArray)> GetFields()
            {
                foreach (var field in nodeType.FindFields(context.FieldSource))
                {
                    var isArray        = field.type.TryGetElementType(out var elementType);
                    var type           = isArray ? elementType : field.type;
                    var classification = Classifier.Classify(context.Types, type, context.TypeIgnorePattern);

                    yield return(field.identifier, classification, type, isArray);
                }
            }
        }
        /// <summary>
        /// Map a <see cref="System.Enum"/> type to a tree-defintion builder.
        /// </summary>
        /// <remarks>
        /// If enum does not yet exist in the builder it is pushed, otherwise this will verify that
        /// the existing enum matches the signature of the new enum.
        /// </remarks>
        /// <param name="builder">Builder to push the enum definition to</param>
        /// <param name="context">Context object with dependencies for the mapping</param>
        /// <param name="enumType">Enum-type to map</param>
        /// <exception cref="Exceptions.DuplicateEnumException">
        /// Thrown when two different enums have the same full-name.
        /// </exception>
        /// <exception cref="Exceptions.InvalidEnumValueException">
        /// Thrown when an enum value is not convertible to a 32 bit signed integer.
        /// </exception>
        /// <returns>Definition that the type was mapped to</returns>
        public static EnumDefinition MapEnum(
            this TreeDefinitionBuilder builder,
            Context context,
            Type enumType)
        {
            if (builder == null)
            {
                throw new ArgumentNullException(nameof(builder));
            }
            if (context == null)
            {
                throw new ArgumentNullException(nameof(context));
            }
            if (enumType == null)
            {
                throw new ArgumentNullException(nameof(enumType));
            }
            if (!enumType.IsEnum)
            {
                throw new ArgumentException($"Provided type: '{enumType}' is not an enum", nameof(enumType));
            }

            var identifier = enumType.FullName;
            var entries    = GetEntries();

            if (builder.TryGetEnum(identifier, out var existingEnum))
            {
                // Verify if this matches with the existing enum with the same identifier
                if (!existingEnum.Values.SequenceEqual(entries))
                {
                    throw new Exceptions.DuplicateEnumException(identifier);
                }
                return(existingEnum);
            }
            else
            {
                // Diagnostic logging.
                context.Logger?.LogDebug($"Mapped enum '{identifier}'");
                context.Logger?.LogTrace(
                    $"entries:\n{string.Join("\n", entries.Select(e => $"* '{e}'"))}");

                // Push new enum
                return(builder.PushEnum(identifier, entries));
            }

            IEnumerable <EnumEntry> GetEntries()
            {
                var names  = Enum.GetNames(enumType);
                var values = Enum.GetValues(enumType);

                for (int i = 0; i < names.Length; i++)
                {
                    var rawVal = values.GetValue(i);
                    yield return(new EnumEntry(names[i], ConvertEnumValue(rawVal)));
                }
            }

            int ConvertEnumValue(object rawVal)
            {
                try
                {
                    return(Convert.ToInt32(rawVal, CultureInfo.InvariantCulture));
                }
                catch (OverflowException)
                {
                    throw new Exceptions.InvalidEnumValueException(identifier, rawVal);
                }
            }
        }
 public void ThrowsIfAliasReferencesMissingNode() => Assert.Throws <NodeNotFoundException>(() =>
                                                                                           TreeDefinitionBuilder.Create("AliasA", b =>
 {
     b.PushAlias("AliasA", "NodeB");
     b.PushNode("NodeA");
 }));
 public void ThrowsIfAliasIsEmpty() => Assert.Throws <EmptyAliasException>(() =>
                                                                           TreeDefinitionBuilder.Create("AliasA", b =>
 {
     b.PushAlias("AliasA");
     b.PushNode("NodeA");
 }));
 public void ThrowsIfDuplicateAliasIsPushed() => Assert.Throws <DuplicateAliasIdentifierException>(() =>
                                                                                                   TreeDefinitionBuilder.Create("AliasA", b =>
 {
     b.PushAlias("AliasA", "NodeA");
     b.PushAlias("AliasA", "NodeA");
     b.PushNode("NodeA");
 }));
 public void ThrowsIfRootAliasDoesntExist() => Assert.Throws <AliasNotFoundException>(() =>
                                                                                      TreeDefinitionBuilder.Create("AliasA", b =>
 {
     b.PushNode("NodeA");
 }));