├── .editorconfig ├── .gitignore ├── LICENSE.txt ├── README.md ├── ReleaseNotes.md ├── azure-pipelines-cd.yml ├── azure-pipelines-ci.yml ├── azure-templates ├── stage-build.yml └── stage-deploy.yml └── src ├── Directory.Build.props ├── Sample ├── Order.cs ├── OrderLine.cs ├── OrderStatus.cs ├── Program.cs ├── Prop.cs └── Sample.csproj ├── Structurizer.sln ├── Structurizer ├── Configuration │ ├── StructureTypeConfig.cs │ ├── StructureTypeConfigurations.cs │ └── StructureTypeConfigurator.cs ├── DataTypeCode.cs ├── DataTypeConverter.cs ├── Extensions │ ├── ExpressionExtensions.cs │ └── TypeExtensions.cs ├── FlexibleStructureBuilder.cs ├── IDataTypeConverter.cs ├── IIndexAccessor.cs ├── IStructure.cs ├── IStructureBuilder.cs ├── IStructureIndex.cs ├── IStructureIndexValue.cs ├── IStructureIndexesFactory.cs ├── IStructureProperty.cs ├── IStructurePropertyFactory.cs ├── IStructureSchema.cs ├── IStructureSchemaFactory.cs ├── IStructureType.cs ├── IStructureTypeConfig.cs ├── IStructureTypeConfigurations.cs ├── IStructureTypeConfigurator.cs ├── IStructureTypeFactory.cs ├── IStructureTypeReflecter.cs ├── IndexMode.cs ├── Schemas │ ├── DynamicGetter.cs │ ├── DynamicPropertyFactory.cs │ ├── IndexAccessor.cs │ ├── PropertyPathBuilder.cs │ ├── StructureProperty.cs │ ├── StructurePropertyCallstack.cs │ ├── StructurePropertyFactory.cs │ ├── StructurePropertyFactoryRules.cs │ ├── StructurePropertyInfo.cs │ ├── StructureSchema.cs │ ├── StructureType.cs │ └── StructureTypeReflecter.cs ├── Structure.cs ├── StructureBuilder.cs ├── StructureIndex.cs ├── StructureIndexValue.cs ├── StructureIndexesFactory.cs ├── StructureSchemaFactory.cs ├── StructureTypeFactory.cs ├── Structurizer.csproj ├── StructurizerException.cs └── StructurizerExceptionMessages.cs └── UnitTests ├── Configuration ├── StructureTypeConfigTests.cs ├── StructureTypeConfigurationsTests.cs └── StructureTypeConfiguratorTests.cs ├── DataTypeCodeTests.cs ├── FlexibleStructureBuilderTests.cs ├── Schemas ├── DataTypeConverterTests.cs ├── IndexAccessorTestFactory.cs ├── MemberAccessors │ ├── IndexAccessorGetValuesOnDeepGraphTests.cs │ ├── IndexAccessorGetValuesOnSubItemTests.cs │ └── IndexAccessorGetValuesTests.cs ├── PropertyPathBuilderTests.cs ├── StructurePropertyTestFactory.cs ├── StructurePropertyTests │ ├── StructurePropertyAttributesTests.cs │ ├── StructurePropertyEnumerableTests.cs │ ├── StructurePropertyGetGuidValueTests.cs │ ├── StructurePropertyGetIdentityValueTests.cs │ ├── StructurePropertyGetPrimitiveValueTests.cs │ └── StructurePropertyGetStringValueTests.cs ├── StructureSchemaFactoryTests.cs ├── StructureSchemaTests.cs ├── StructureTypeFactoryTests.cs └── StructureTypeReflecterTests │ ├── StructureTypeReflecterComplexIndexablePropertiesTests.cs │ ├── StructureTypeReflecterEnumerableIndexablePropertiesTests.cs │ ├── StructureTypeReflecterGetIndexablePropertiesExceptTests.cs │ ├── StructureTypeReflecterGetSpecificIndexablePropertiesTests.cs │ ├── StructureTypeReflecterSimpleIndexablePropertiesTests.cs │ └── StructureTypeReflecterTestsBase.cs ├── StructureBuilderTests ├── StructureBuilderBaseTests.cs ├── StructureBuilderEnumerableTests.cs ├── StructureBuilderGraphTests.cs ├── StructureBuilderIncludeAndExcludeTests.cs ├── StructureBuilderNullablesTests.cs └── StructureBuilderTests.cs ├── StructureIndexesFactoryTests.cs ├── StructureSchemaTestFactory.cs ├── StructureTypeTestFactory.cs ├── UnitTests.cs └── UnitTests.csproj /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = crlf 5 | insert_final_newline = true 6 | indent_style = space 7 | 8 | [*.props, *.csproj, *.yml, *.json] 9 | indent_size = 2 10 | 11 | [*.cs] 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vs 2 | .vs/ 3 | [Bb]in/ 4 | [D]ebug/ 5 | [Aa]rtifacts/ 6 | [Oo]bj/ 7 | [Tt]est[Rr]esult/ 8 | [Tt]emp/ 9 | *.user 10 | *.vspscc 11 | *.vssscc 12 | *.suo 13 | *.cache 14 | 15 | # misc 16 | *~ 17 | *.swp 18 | *.sdf 19 | *.orig 20 | *.pfx 21 | 22 | # rider 23 | .idea 24 | 25 | # vs-code 26 | .vscode 27 | 28 | # resharper 29 | _ReSharper.* 30 | *.resharper* 31 | *.[Rr]e[Ss]harper.user 32 | *.DotSettings.user 33 | 34 | #windows stuff 35 | Thumbs.db 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Daniel Wertheim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Structurizer 2 | [![NuGet](https://img.shields.io/nuget/v/structurizer.svg?cacheSeconds=3600)](https://www.nuget.org/packages/structurizer) 3 | [![License MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://choosealicense.com/licenses/mit/) 4 | [![Build Status](https://dev.azure.com/danielwertheim/structurizer/_apis/build/status/danielwertheim.structurizer-CI?branchName=master)](https://dev.azure.com/danielwertheim/structurizer/_build/latest?definitionId=31&branchName=master) 5 | 6 | Structurizer is extracted from one of my other projects [PineCone](https://github.com/danielwertheim/pinecone), which was used for much of the underlying indexing stuff for another project of mine, SisoDB. Structurizer is reduced to only manage a key-value representation of an object-graph. 7 | 8 | ## Release notes 9 | Release notes are [kept here](ReleaseNotes.md). 10 | 11 | ## Usage 12 | ### Define a model 13 | You need some model to create key-values for. It will only extract public properties. 14 | 15 | ```csharp 16 | public class MyRoot 17 | { 18 | public string Name { get; set; } 19 | public int Score { get; set; } 20 | public MyChild OneChild { get; set; } 21 | public List ManyChildren { get; set; } 22 | } 23 | 24 | public class MyChild 25 | { 26 | public string SomeString { get; set; } 27 | } 28 | ``` 29 | 30 | ### Install 31 | 32 | ``` 33 | install-package structurizer 34 | ``` 35 | 36 | ### Builder construction 37 | The easiest builder to use is the `FlexibleStructureBuilder` (introduced in `v3.0.0`). 38 | 39 | ```csharp 40 | var builder = new FlexibleStructureBuilder(); 41 | ``` 42 | 43 | You can also use the more static configured `StructureBuilder`: 44 | 45 | ```csharp 46 | var typeConfigs = new StructureTypeConfigurations(); 47 | typeConfigs.Register(); 48 | 49 | var builder = StructureBuilder.Create(typeConfigs); 50 | ``` 51 | 52 | ### Create key-values 53 | Now you can create a structure which will hold key-value `StructureIndex` items for the graph. 54 | 55 | ```csharp 56 | var item = new MyRoot 57 | { 58 | Name = "Foo Bar", 59 | Score = 2345, 60 | OneChild = new MyChild 61 | { 62 | SomeString = "One child" 63 | }, 64 | ManyChildren = new List 65 | { 66 | new MyChild {SomeString = "List Child1"}, 67 | new MyChild {SomeString = "List Child2"} 68 | } 69 | }; 70 | 71 | var structure = builder.CreateStructure(item); 72 | foreach (var index in structure.Indexes) 73 | { 74 | Console.WriteLine($"{index.Path}={index.Value}"); 75 | } 76 | ``` 77 | 78 | will generate: 79 | 80 | ``` 81 | Name=Foo Bar 82 | Score=2345 83 | OneChild.SomeString=One child 84 | ManyChildren[0].SomeString=List Child1 85 | ManyChildren[1].SomeString=List Child2 86 | ``` 87 | 88 | ## Control what's being indexed 89 | 90 | ### Using the FlexibleStructureBuilder 91 | At any point (last in wins), just use the `Configure` methods, e.g. 92 | 93 | ```csharp 94 | builder.Configure(i => cfg 95 | .UseIndexMode(IndexMode.Inclusive) 96 | .Members(e => e.Name, e => e.Score)); 97 | ``` 98 | 99 | ### Using the Static StructureBuilder 100 | This is controlled using the `StructureTypeConfigurations.Register` member. 101 | 102 | By default it's going to index everything as the default is to have `IndexMode.Exclusive` with no exclusions. This can be changed. 103 | 104 | ```csharp 105 | var typeConfigs = new StructureTypeConfigurations(); 106 | typeConfigs.Register(cfg => cfg 107 | .UseIndexMode(IndexMode.Inclusive) 108 | .Members(t => t.Name, t => t.Score) 109 | //Use array access to define path of childrens in enumerable 110 | .Members(t => t.ManyChildren[0].SomeString)) 111 | //Can also be done using strings if no index property exists 112 | .Members("ManyChildren.SomeString"); 113 | ``` 114 | 115 | # Use cases? 116 | Anywhere where you efficiently need to extract key-values from an object-graph. Could e.g. be for: 117 | 118 | - Logging 119 | - Selectively extracting some property values for generating a checksum for an object-graph 120 | - Comparing values 121 | - Storing key-values for searching 122 | - ... 123 | -------------------------------------------------------------------------------- /ReleaseNotes.md: -------------------------------------------------------------------------------- 1 | #Release notes 2 | 3 | ## v4.0.1 4 | From this version release notes are kept in [each release](https://github.com/danielwertheim/structurizer/releases) on GitHub. 5 | 6 | ## v4.0.0 - 2018-11-16 7 | - *New*: .NET Standard 2.0 instead of .NET Standard 1.3. Also supports .NET4.5.1 for a while more. 8 | - *Changed*: Support for anonymous types passed as object. 9 | 10 | ## v3.1.0 - 2017-06-03 11 | - *New*: Added `IStructureIndex.IsNumeric` to indicate if the carried value is numeric or not. Use `DataTypeCode` for more finegrained details. 12 | 13 | ## v3.0.0 - 2017-06-03 14 | - *New*: `FlexibleStructureBuilder` which doesn't require you to pre-register types. It will do that automatically upon first contact with a new type. You can configure and reconfigure it at any time. 15 | - *Changed*: `StructureTypeConfigurations` are now thread-safe and you can call register multiple times, last in wins. 16 | 17 | ## v2.0.0 - 2017-05-02 18 | - Updated to target .NET Standard 1.3 19 | - Now also extracting attributes from properties for those using Structurizer for meta-data and schema stuff. 20 | 21 | ## v1.0.0 - 2016-11-28 22 | Might be weird you think to set 1.0.0 so soon. Remember that the code comes from two generations. One generation from the PineCone project and one from SisoDB. Structurizer is based upon this. But with a lot of stuff removed. 23 | 24 | With this release focus has been on performance and resource usage. Read more here: http://danielwertheim.se/structurizer-improvements/ 25 | 26 | ## v0.2.0 - 2016-11-13 27 | - Breaking changes in API to simplify usage of Structurizer. 28 | - Added possibility of per type define `IndexMode.Inclusive|Exclusive`. 29 | - `StructureProperty.SetValue` is removed. Structurizer is about reading values. 30 | - Indexes paths has now changed. When part of something that is enumerable (arrays, lists, collections etc.) there will be an indicator telling at what position the value was. 31 | 32 | You get: 33 | 34 | ``` 35 | OrderLines[0].ArticleNo="123454" 36 | OrderLines[0].Qty=1 37 | OrderLines[1].ArticleNo="123454" 38 | OrderLines[1].Qty=3 39 | ``` 40 | 41 | instead off: 42 | 43 | ``` 44 | OrderLines.ArticleNo="123454" 45 | OrderLines.Qty=1 46 | OrderLines.ArticleNo="123454" 47 | OrderLines.Qty=3 48 | ``` 49 | 50 | ## v0.1.0 - 2016-05-24 51 | First release, after import from PineCone. Heavily reduced when it comes to features. -------------------------------------------------------------------------------- /azure-pipelines-cd.yml: -------------------------------------------------------------------------------- 1 | name: $(SemVer) 2 | 3 | variables: 4 | SemVer: $[ variables['Build.SourceBranchName'] ] 5 | CommitId: $(Build.SourceVersion) 6 | 7 | trigger: 8 | batch: true 9 | branches: 10 | include: 11 | - refs/tags/* 12 | 13 | pr: none 14 | 15 | pool: 16 | vmImage: windows-2019 17 | 18 | stages: 19 | - template: azure-templates/stage-build.yml 20 | - template: azure-templates/stage-deploy.yml 21 | -------------------------------------------------------------------------------- /azure-pipelines-ci.yml: -------------------------------------------------------------------------------- 1 | name: $(SemVer) 2 | 3 | variables: 4 | BuildRev: $[counter(format('{0:yyyyMMdd}', pipeline.startTime), 1)] 5 | SemVer: $[format('{0:yyyy}.{0:MM}.{0:dd}-pre{1}', pipeline.startTime, variables.BuildRev)] 6 | CommitId: $(Build.SourceVersion) 7 | 8 | trigger: 9 | batch: true 10 | branches: 11 | include: 12 | - master 13 | 14 | pr: 15 | autoCancel: true 16 | branches: 17 | include: 18 | - master 19 | 20 | pool: 21 | vmImage: windows-2019 22 | 23 | stages: 24 | - template: azure-templates/stage-build.yml 25 | -------------------------------------------------------------------------------- /azure-templates/stage-build.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - stage: Build 3 | jobs: 4 | - job: BuildTestPack 5 | displayName: 'Build, test & pack' 6 | timeoutInMinutes: 5 7 | cancelTimeoutInMinutes: 2 8 | steps: 9 | - task: UseDotNet@2 10 | displayName: 'Use .NET Core 3.1.x' 11 | inputs: 12 | version: '3.1.x' 13 | packageType: sdk 14 | 15 | - task: DotNetCoreCLI@2 16 | displayName: 'Build Solution' 17 | inputs: 18 | command: build 19 | projects: 'src/*.sln' 20 | arguments: '-c $(BuildConfiguration) --no-incremental --nologo -p:TreatWarningsAsErrors=true -p:Version=$(SemVer) -p:InformationalVersion=$(CommitId)' 21 | 22 | - task: DotNetCoreCLI@2 23 | displayName: 'UnitTests' 24 | inputs: 25 | command: test 26 | projects: 'src/**/UnitTests.csproj' 27 | arguments: '-c $(BuildConfiguration) --no-build' 28 | testRunTitle: 'UnitTests' 29 | 30 | - task: DotNetCoreCLI@2 31 | displayName: 'Pack Nupkg' 32 | inputs: 33 | command: custom 34 | custom: pack 35 | projects: 'src/*.sln' 36 | arguments: '-c $(BuildConfiguration) --no-build -o $(Build.ArtifactStagingDirectory) -p:Version=$(SemVer) -p:InformationalVersion=$(CommitId)' 37 | 38 | - task: PublishPipelineArtifact@1 39 | displayName: 'Publish Artifacts' 40 | inputs: 41 | path: '$(Build.ArtifactStagingDirectory)' 42 | artifact: Artifacts 43 | -------------------------------------------------------------------------------- /azure-templates/stage-deploy.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - stage: Deploy 3 | condition: and (succeeded(), startsWith( variables['Build.SourceBranch'], 'refs/tags' )) 4 | dependsOn: Build 5 | jobs: 6 | - deployment: DeployArtifacts 7 | environment: 'Prod' 8 | displayName: 'Deploys artifacts' 9 | timeoutInMinutes: 4 10 | cancelTimeoutInMinutes: 2 11 | strategy: 12 | runOnce: 13 | deploy: 14 | steps: 15 | - checkout: none 16 | - task: NuGetCommand@2 17 | displayName: 'Push Nupkg to NuGet' 18 | inputs: 19 | command: push 20 | nugetFeedType: external 21 | publishFeedCredentials: nuget_push_new_versions 22 | verbosityPush: Normal 23 | packagesToPush: '$(Pipeline.Workspace)/**/*.nupkg;!$(Pipeline.Workspace)/**/*.symbols.nupkg' 24 | -------------------------------------------------------------------------------- /src/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0.0.0 4 | danielwertheim 5 | danielwertheim 6 | Copyright © danielwertheim 7 | latest 8 | https://github.com/danielwertheim/structurizer 9 | Git 10 | MIT 11 | https://github.com/danielwertheim/structurizer 12 | https://github.com/danielwertheim/structurizer/releases 13 | true 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/Sample/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Sample 5 | { 6 | public class Order 7 | { 8 | public long Id { get; set; } 9 | public Guid MerchantId { get; set; } 10 | public string OrderNo { get; set; } 11 | public Guid? CustomerId { get; set; } 12 | public OrderStatus Status { get; set; } 13 | public bool IsShipped { get; set; } 14 | public DateTime PlacedAt { get; set; } 15 | public decimal FreightCost { get; set; } 16 | public decimal Amount { get; set; } 17 | public int Discount { get; set; } 18 | public decimal AmountToPay { get; set; } 19 | public List Lines { get; set; } 20 | public string[] Tags { get; set; } 21 | 22 | public static Order CreateSample() 23 | { 24 | var order = new Order 25 | { 26 | Id = DateTime.Now.Ticks, 27 | MerchantId = Guid.NewGuid(), 28 | OrderNo = "2016-1234", 29 | CustomerId = Guid.NewGuid(), 30 | Tags = new[] { "Test1", "Test2", "Gold customer" }, 31 | PlacedAt = DateTime.Now.Subtract(TimeSpan.FromDays(2)), 32 | Status = OrderStatus.Payed, 33 | IsShipped = true, 34 | FreightCost = 33.50M, 35 | Amount = 1300M, 36 | Discount = 100, 37 | AmountToPay = 1233.50M, 38 | Lines = new List 39 | { 40 | new OrderLine 41 | { 42 | ArticleNo = "Article-Line0", 43 | Qty = 42, 44 | Props = new List 45 | { 46 | new Prop 47 | { 48 | Name = "Key-Line0-Item0", 49 | Value = "Value-Line0-Item0", 50 | }, 51 | new Prop 52 | { 53 | Name = "Key-Line0-Item1", 54 | Value = "Value-Line0-Item1" 55 | } 56 | } 57 | }, 58 | new OrderLine 59 | { 60 | ArticleNo = "Article-Line1", 61 | Qty = 3, 62 | Props = new List 63 | { 64 | new Prop 65 | { 66 | Name = "Key-Line1-Item0", 67 | Value = "Value-Line1-Item0" 68 | }, 69 | new Prop 70 | { 71 | Name = "Key-Line1-Item1", 72 | Value = "Value-Line1-Item1" 73 | } 74 | } 75 | } 76 | } 77 | }; 78 | 79 | return order; 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/Sample/OrderLine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Sample 4 | { 5 | public class OrderLine 6 | { 7 | public string ArticleNo { get; set; } 8 | public int Qty { get; set; } 9 | public List Props { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Sample/OrderStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Sample 2 | { 3 | public enum OrderStatus 4 | { 5 | Created, 6 | Approved, 7 | Shipped, 8 | Payed 9 | } 10 | } -------------------------------------------------------------------------------- /src/Sample/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Structurizer; 3 | 4 | namespace Sample 5 | { 6 | class Program 7 | { 8 | static void Main(string[] args) 9 | { 10 | //var typeConfigs = new StructureTypeConfigurations(); 11 | //typeConfigs.Register(cfg => cfg 12 | // .UsingIndexMode(IndexMode.Exclusive) 13 | // .Members(i => i.Lines[0].Props)); 14 | 15 | //inclusive:Lines.* 16 | //inclusive:Lines.ArticleNo 17 | //exclusive:Lines.* 18 | //exclusive.Lines.ArticleNo 19 | 20 | //var structureBuilder = StructureBuilder.Create(cfg => cfg.Register()); 21 | //var order = Order.CreateSample(); 22 | //var orderStructure = structureBuilder.CreateStructure(order); 23 | //DumpStructure(orderStructure); 24 | 25 | //var structureBuilder = StructureBuilder.Create(cfg => cfg.Register()); 26 | //var foo = Foo.CreateSample(); 27 | //var fooStructure = structureBuilder.CreateStructure(foo); 28 | //DumpStructure(fooStructure); 29 | 30 | var flexibleStructureBuilder = new FlexibleStructureBuilder(); 31 | 32 | //Optionally configure 33 | //flexibleStructureBuilder.ConfigureUsingTemplate( 34 | // new { Age = default(int), Name = default(string) }, 35 | // c => c.Members(i => i.Age)); 36 | 37 | var structure1 = flexibleStructureBuilder.CreateStructure(new { Age = 37, Name = "Daniel" }); 38 | var structure2 = flexibleStructureBuilder.CreateStructure(new { Age = 36, Name = "John" }); 39 | DumpStructure(structure1); 40 | DumpStructure(structure2); 41 | 42 | Console.ReadLine(); 43 | } 44 | 45 | private static void DumpStructure(IStructure structure) 46 | { 47 | Console.WriteLine($"===== {structure.Name} ====="); 48 | foreach (var index in structure.Indexes) 49 | Console.WriteLine(DefaultIndexValueFormatter.Format(index)); 50 | } 51 | } 52 | 53 | public static class DefaultIndexValueFormatter 54 | { 55 | public static string Format(IStructureIndex index) 56 | { 57 | return $"{index.Path}=\"{index.Value}\""; 58 | //switch (index.DataTypeCode) 59 | //{ 60 | // case DataTypeCode.String: 61 | // case DataTypeCode.Guid: 62 | // case DataTypeCode.Enum: 63 | // return $"Path\t{index.Path}=\"{index.Value}\""; 64 | // case DataTypeCode.DateTime: 65 | // return $"Path\t{index.Path}=\"{((DateTime)index.Value):O}\""; 66 | // default: 67 | // return $"Path\t{index.Path}={index.Value}"; 68 | //} 69 | } 70 | } 71 | 72 | public class Foo 73 | { 74 | public int Score { get; set; } 75 | public int[] Scores { get; set; } 76 | public FooType Type { get; set; } 77 | public DateTime TimeStamp { get; set; } 78 | public int? OptScore { get; set; } 79 | public int?[] OptScores { get; set; } 80 | public string Name { get; set; } 81 | public string[] Names { get; set; } 82 | public bool Bool { get; set; } 83 | public bool OptBool { get; set; } 84 | //public Bar Bar { get; set; } 85 | //public KeyValuePair Kv { get; set; } 86 | 87 | public static Foo CreateSample() 88 | { 89 | return new Foo 90 | { 91 | Score = 42, 92 | Scores = new[] { 1, 2, 3 }, 93 | Type = FooType.One, 94 | OptScore = 111, 95 | OptScores = new int?[] { 111, 112, 113 }, 96 | Bool = true, 97 | OptBool = true, 98 | TimeStamp = DateTime.Now, 99 | Name = "Test", 100 | Names = new[] { "Name1", "Name2" }, 101 | //Kv = new KeyValuePair(333, "aaa") 102 | }; 103 | } 104 | } 105 | 106 | public enum FooType 107 | { 108 | One 109 | } 110 | 111 | public class Bar 112 | { 113 | public int[] BarScore { get; set; } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Sample/Prop.cs: -------------------------------------------------------------------------------- 1 | namespace Sample 2 | { 3 | public class Prop 4 | { 5 | public string Name { get; set; } 6 | public string Value { get; set; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Sample/Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.1 6 | false 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Structurizer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29613.14 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{47EDD254-E0BB-4EA2-AEA5-8AC817802C29}" 7 | ProjectSection(SolutionItems) = preProject 8 | Directory.Build.props = Directory.Build.props 9 | EndProjectSection 10 | EndProject 11 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Structurizer", "Structurizer\Structurizer.csproj", "{CDB3E809-4B1E-4EF3-808E-4FD503D325B8}" 12 | EndProject 13 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{D9782C06-2123-48EB-9855-6CEA85066CF1}" 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sample", "Sample\Sample.csproj", "{48863385-3B25-473C-91F0-00F4615F6188}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {CDB3E809-4B1E-4EF3-808E-4FD503D325B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {CDB3E809-4B1E-4EF3-808E-4FD503D325B8}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {CDB3E809-4B1E-4EF3-808E-4FD503D325B8}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {CDB3E809-4B1E-4EF3-808E-4FD503D325B8}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {D9782C06-2123-48EB-9855-6CEA85066CF1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {D9782C06-2123-48EB-9855-6CEA85066CF1}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {D9782C06-2123-48EB-9855-6CEA85066CF1}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {D9782C06-2123-48EB-9855-6CEA85066CF1}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {48863385-3B25-473C-91F0-00F4615F6188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {48863385-3B25-473C-91F0-00F4615F6188}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {48863385-3B25-473C-91F0-00F4615F6188}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {48863385-3B25-473C-91F0-00F4615F6188}.Release|Any CPU.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | GlobalSection(ExtensibilityGlobals) = postSolution 40 | SolutionGuid = {87631C88-A07E-4057-8EEE-590DB1278E76} 41 | EndGlobalSection 42 | EndGlobal 43 | -------------------------------------------------------------------------------- /src/Structurizer/Configuration/StructureTypeConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using EnsureThat; 5 | 6 | namespace Structurizer.Configuration 7 | { 8 | public class StructureTypeConfig : IStructureTypeConfig 9 | { 10 | public Type Type { get; } 11 | public IndexMode IndexMode { get; } 12 | public IReadOnlyList MemberPaths { get; } 13 | 14 | public StructureTypeConfig(Type structureType, IndexMode indexMode, ISet memberPaths) 15 | { 16 | Ensure.That(structureType, nameof(structureType)).IsNotNull(); 17 | Ensure.That(memberPaths, nameof(memberPaths)).IsNotNull(); 18 | 19 | Type = structureType; 20 | IndexMode = indexMode; 21 | MemberPaths = new ReadOnlyCollection(new List(memberPaths)); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/Structurizer/Configuration/StructureTypeConfigurations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using EnsureThat; 6 | 7 | namespace Structurizer.Configuration 8 | { 9 | public class StructureTypeConfigurations : IStructureTypeConfigurations 10 | { 11 | private readonly ConcurrentDictionary _configurations; 12 | 13 | public StructureTypeConfigurations() 14 | { 15 | _configurations = new ConcurrentDictionary(); 16 | } 17 | 18 | IEnumerator IEnumerable.GetEnumerator() => _configurations.Values.GetEnumerator(); 19 | 20 | IEnumerator IEnumerable.GetEnumerator() => _configurations.Values.GetEnumerator(); 21 | 22 | public IStructureTypeConfig GetConfiguration() where T : class => GetConfiguration(typeof(T)); 23 | 24 | public IStructureTypeConfig GetConfiguration(Type type) => _configurations.TryGetValue(type, out IStructureTypeConfig config) 25 | ? config 26 | : null; 27 | 28 | public IStructureTypeConfig Register(Type structureType, Action configurator = null) 29 | { 30 | Ensure.That(structureType, nameof(structureType)).IsNotNull(); 31 | 32 | return _configurations.AddOrUpdate( 33 | structureType, 34 | t => CreateStructureTypeConfig(t, configurator), 35 | (t, existing) => CreateStructureTypeConfig(t, configurator)); 36 | } 37 | 38 | private static IStructureTypeConfig CreateStructureTypeConfig(Type structureType, Action config = null) 39 | { 40 | var configurator = new StructureTypeConfigurator(structureType); 41 | config?.Invoke(configurator); 42 | 43 | return configurator.GenerateConfig(); 44 | } 45 | 46 | public IStructureTypeConfig Register(Action> configurator = null) where T : class 47 | => _configurations.AddOrUpdate( 48 | typeof(T), 49 | t => CreateStructureTypeConfig(t, configurator), 50 | (t, existing) => CreateStructureTypeConfig(t, configurator)); 51 | 52 | private static IStructureTypeConfig CreateStructureTypeConfig(Type structureType, Action> config = null) where T : class 53 | { 54 | var configurator = new StructureTypeConfigurator(structureType); 55 | config?.Invoke(configurator); 56 | 57 | return configurator.GenerateConfig(); 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/Structurizer/Configuration/StructureTypeConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using EnsureThat; 6 | using Structurizer.Extensions; 7 | 8 | namespace Structurizer.Configuration 9 | { 10 | public class StructureTypeConfigurator : IStructureTypeConfigurator 11 | { 12 | private readonly Type _structureType; 13 | private readonly ISet _memberPaths = new HashSet(); 14 | private IndexMode _indexMode; 15 | 16 | public StructureTypeConfigurator(Type structureType) 17 | { 18 | Ensure.That(structureType, nameof(structureType)).IsNotNull(); 19 | 20 | _structureType = structureType; 21 | } 22 | 23 | public IStructureTypeConfigurator UsingIndexMode(IndexMode mode) 24 | { 25 | _indexMode = mode; 26 | 27 | return this; 28 | } 29 | 30 | public virtual IStructureTypeConfigurator Members(params string[] memberPaths) 31 | { 32 | foreach (var memberPath in memberPaths) 33 | _memberPaths.Add(memberPath); 34 | 35 | return this; 36 | } 37 | 38 | public IStructureTypeConfig GenerateConfig() 39 | => new StructureTypeConfig(_structureType, _indexMode, _memberPaths); 40 | } 41 | 42 | public class StructureTypeConfigurator : IStructureTypeConfigurator where T : class 43 | { 44 | private readonly StructureTypeConfigurator _internalConfigurator; 45 | 46 | public StructureTypeConfigurator(Type structureType) 47 | { 48 | _internalConfigurator = new StructureTypeConfigurator(structureType); 49 | } 50 | 51 | public IStructureTypeConfigurator UsingIndexMode(IndexMode mode) 52 | { 53 | _internalConfigurator.UsingIndexMode(mode); 54 | 55 | return this; 56 | } 57 | 58 | public virtual IStructureTypeConfigurator Members(params string[] memberPaths) 59 | { 60 | _internalConfigurator.Members(memberPaths); 61 | 62 | return this; 63 | } 64 | 65 | public virtual IStructureTypeConfigurator Members(params Expression>[] members) 66 | { 67 | Members(members 68 | .Select(e => e.GetRightMostMember().ToPath()) 69 | .ToArray()); 70 | 71 | return this; 72 | } 73 | 74 | public IStructureTypeConfig GenerateConfig() => _internalConfigurator.GenerateConfig(); 75 | } 76 | } -------------------------------------------------------------------------------- /src/Structurizer/DataTypeCode.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizer 2 | { 3 | public enum DataTypeCode 4 | { 5 | Unknown, 6 | IntegerNumber, 7 | UnsignedIntegerNumber, 8 | FractalNumber, 9 | Bool, 10 | DateTime, 11 | Guid, 12 | String, 13 | Enum 14 | } 15 | 16 | public static class DataTypeCodeExtensions 17 | { 18 | public static bool IsNumeric(this DataTypeCode code) => 19 | code == DataTypeCode.IntegerNumber || 20 | code == DataTypeCode.FractalNumber || 21 | code == DataTypeCode.UnsignedIntegerNumber; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Structurizer/DataTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Structurizer.Extensions; 3 | 4 | namespace Structurizer 5 | { 6 | public class DataTypeConverter : IDataTypeConverter 7 | { 8 | public DataTypeCode Convert(IStructureProperty property) 9 | { 10 | return Convert(property.ElementDataType ?? property.DataType); 11 | } 12 | 13 | public DataTypeCode Convert(Type dataType) 14 | { 15 | if (dataType.IsAnySignedIntegerNumberType()) 16 | return DataTypeCode.IntegerNumber; 17 | 18 | if (dataType.IsAnyUnsignedType()) 19 | return DataTypeCode.UnsignedIntegerNumber; 20 | 21 | if (dataType.IsAnyFractalNumberType()) 22 | return DataTypeCode.FractalNumber; 23 | 24 | if (dataType.IsAnyBoolType()) 25 | return DataTypeCode.Bool; 26 | 27 | if (dataType.IsAnyDateTimeType()) 28 | return DataTypeCode.DateTime; 29 | 30 | if (dataType.IsAnyGuidType()) 31 | return DataTypeCode.Guid; 32 | 33 | if (dataType.IsStringType()) 34 | return DataTypeCode.String; 35 | 36 | return dataType.IsAnyEnumType() 37 | ? DataTypeCode.Enum 38 | : DataTypeCode.Unknown; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/Structurizer/Extensions/ExpressionExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | 5 | namespace Structurizer.Extensions 6 | { 7 | internal static class ExpressionExtensions 8 | { 9 | internal static object Evaluate(this Expression e) 10 | { 11 | if (e is MethodCallExpression) 12 | return (e as MethodCallExpression).Evaluate(); 13 | 14 | if (e is MemberExpression) 15 | return (e as MemberExpression).Evaluate(); 16 | 17 | if (e is ConstantExpression) 18 | return (e as ConstantExpression).Evaluate(); 19 | 20 | if (e is NewArrayExpression) 21 | return (e as NewArrayExpression).Evaluate(); 22 | 23 | if (e is UnaryExpression) 24 | return (e as UnaryExpression).Evaluate(); 25 | 26 | throw new StructurizerException( 27 | string.Format(StructurizerExceptionMessages.ExpressionEvaluation_DontKnowHowToEvalExpression, e.GetType().Name)); 28 | } 29 | 30 | internal static object[] Evaluate(this NewArrayExpression e) => e.Expressions.Select(ie => ie.Evaluate()).ToArray(); 31 | 32 | internal static object Evaluate(this UnaryExpression e) 33 | { 34 | if (e.Operand is ConstantExpression) 35 | return (e.Operand as ConstantExpression).Evaluate(); 36 | 37 | if (e.Operand is MethodCallExpression) 38 | return (e.Operand as MethodCallExpression).Evaluate(); 39 | 40 | throw new StructurizerException( 41 | string.Format(StructurizerExceptionMessages.ExpressionEvaluation_DontKnowHowToEvalUnaryExpression, e.NodeType)); 42 | } 43 | 44 | internal static object Evaluate(this MethodCallExpression methodExpression) 45 | { 46 | if (methodExpression.Object == null) 47 | { 48 | var args = methodExpression.Arguments.OfType().Select(c => c.Value).ToArray(); 49 | if (args.Length == methodExpression.Arguments.Count) 50 | return methodExpression.Method.Invoke(null, args); 51 | } 52 | 53 | return Expression.Lambda(methodExpression).Compile().DynamicInvoke(); 54 | } 55 | 56 | internal static object Evaluate(this MemberExpression memberExpression) 57 | { 58 | var fieldInfo = memberExpression.Member as FieldInfo; 59 | 60 | if (fieldInfo != null && memberExpression.Expression is ConstantExpression) 61 | { 62 | var ce = (ConstantExpression)memberExpression.Expression; 63 | var obj = ce.Value; 64 | return obj == null 65 | ? null 66 | : fieldInfo.GetValue(obj); 67 | } 68 | return Expression.Lambda(memberExpression).Compile().DynamicInvoke(); 69 | } 70 | 71 | internal static object Evaluate(this ConstantExpression constantExpression) => constantExpression.Value; 72 | 73 | public static MemberExpression GetRightMostMember(this Expression e) 74 | { 75 | if (e is LambdaExpression) 76 | return GetRightMostMember(((LambdaExpression)e).Body); 77 | 78 | if (e is MemberExpression) 79 | return (MemberExpression)e; 80 | 81 | if (e is MethodCallExpression) 82 | { 83 | var callExpression = (MethodCallExpression)e; 84 | 85 | if (callExpression.Object is MethodCallExpression || callExpression.Object is MemberExpression) 86 | return GetRightMostMember(callExpression.Object); 87 | 88 | var member = callExpression.Arguments.Count > 0 ? callExpression.Arguments[0] : callExpression.Object; 89 | return GetRightMostMember(member); 90 | } 91 | 92 | if (e is UnaryExpression) 93 | { 94 | var unaryExpression = (UnaryExpression)e; 95 | return GetRightMostMember(unaryExpression.Operand); 96 | } 97 | 98 | return null; 99 | } 100 | 101 | public static string ToPath(this MemberExpression e) 102 | { 103 | if (e == null) 104 | return null; 105 | 106 | if (e.Expression == null) 107 | return e.Member.Name; 108 | 109 | var parentPath = e.Expression != null ? GetRightMostMember(e.Expression).ToPath() : null; 110 | 111 | return parentPath != null 112 | ? string.Concat(parentPath, ".", e.Member.Name) 113 | : e.Member.Name; 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /src/Structurizer/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Reflection; 5 | 6 | namespace Structurizer.Extensions 7 | { 8 | internal static class TypeExtensions 9 | { 10 | private static readonly Type EnumerableType = typeof(IEnumerable); 11 | private static readonly Type DictionaryType = typeof(IDictionary); 12 | private static readonly Type DictionaryOfTType = typeof(IDictionary<,>); 13 | private static readonly Type KeyValuePairType = typeof(KeyValuePair<,>); 14 | private static readonly Type EnumType = typeof(Enum); 15 | 16 | private static readonly Type StringType = typeof(string); 17 | private static readonly Type DateTimeType = typeof(DateTime); 18 | private static readonly Type BoolType = typeof(bool); 19 | private static readonly Type GuidType = typeof(Guid); 20 | private static readonly Type CharType = typeof(char); 21 | 22 | private static readonly Type ByteType = typeof(byte); 23 | private static readonly Type ShortType = typeof(short); 24 | private static readonly Type IntType = typeof(int); 25 | private static readonly Type LongType = typeof(long); 26 | 27 | private static readonly Type SingleType = typeof(Single); 28 | private static readonly Type FloatType = typeof(float); 29 | private static readonly Type DecimalType = typeof(decimal); 30 | private static readonly Type DoubleType = typeof(double); 31 | 32 | private static readonly Type NullableType = typeof(Nullable<>); 33 | 34 | private static readonly Type NullableDateTimeType = typeof(DateTime?); 35 | private static readonly Type NullableGuidType = typeof(Guid?); 36 | private static readonly Type NullableBoolType = typeof(bool?); 37 | private static readonly Type NullableCharType = typeof(Char?); 38 | 39 | private static readonly Type NullableByteType = typeof(byte?); 40 | private static readonly Type NullableShortType = typeof(short?); 41 | private static readonly Type NullableIntType = typeof(int?); 42 | private static readonly Type NullableLongType = typeof(long?); 43 | 44 | private static readonly Type NullableSingleType = typeof(Single?); 45 | private static readonly Type NullableFloatType = typeof(float?); 46 | private static readonly Type NullableDecimalType = typeof(decimal?); 47 | private static readonly Type NullableDoubleType = typeof(double?); 48 | 49 | private static readonly HashSet ExtraPrimitiveTypes = new HashSet { typeof(string), typeof(Guid), typeof(DateTime), typeof(decimal) }; 50 | private static readonly HashSet ExtraPrimitiveNullableTypes = new HashSet { typeof(Guid?), typeof(DateTime?), typeof(decimal?) }; 51 | private static readonly HashSet UnsignedTypes = new HashSet { typeof(ushort), typeof(uint), typeof(ulong) }; 52 | private static readonly HashSet NullableUnsignedTypes = new HashSet { typeof(ushort?), typeof(uint?), typeof(ulong?) }; 53 | 54 | internal static bool IsSimpleType(this Type type) 55 | { 56 | var info = type.GetTypeInfo(); 57 | return (info.IsGenericType == false && info.IsValueType) || info.IsPrimitive || info.IsEnum || ExtraPrimitiveTypes.Contains(type) || type.IsNullablePrimitiveType(); 58 | } 59 | 60 | internal static bool IsKeyValuePairType(this Type type) 61 | { 62 | var info = type.GetTypeInfo(); 63 | return info.IsGenericType && info.IsValueType && type.GetGenericTypeDefinition() == KeyValuePairType; 64 | } 65 | 66 | //internal static bool IsNumericType(this Type type) => IsAnyIntegerNumberType(type) || 67 | // IsAnyFractalNumberType(type); 68 | 69 | //internal static bool IsAnyIntegerNumberType(this Type type) => type.IsAnySignedIntegerNumberType() || type.IsAnyUnsignedType(); 70 | 71 | internal static bool IsAnySignedIntegerNumberType(this Type type) => type.IsAnyIntType() 72 | || type.IsAnyLongType() 73 | || type.IsAnyShortType() 74 | || type.IsAnyByteType(); 75 | 76 | internal static bool IsAnyFractalNumberType(this Type type) => type.IsAnyDoubleType() 77 | || type.IsAnyDecimalType() 78 | || type.IsAnySingleType() 79 | || type.IsAnyFloatType(); 80 | 81 | internal static bool IsEnumerableType(this Type type) 82 | { 83 | var info = type.GetTypeInfo(); 84 | return type != StringType 85 | && info.IsValueType == false 86 | && info.IsPrimitive == false 87 | && EnumerableType.IsAssignableFrom(type); 88 | } 89 | 90 | internal static bool IsEnumerableBytesType(this Type type) 91 | { 92 | if (!IsEnumerableType(type)) 93 | return false; 94 | 95 | var elementType = GetEnumerableElementType(type); 96 | 97 | return elementType.IsByteType() || elementType.IsNullableByteType(); 98 | } 99 | 100 | internal static Type GetEnumerableElementType(this Type type) 101 | { 102 | var info = type.GetTypeInfo(); 103 | var elementType = (info.IsGenericType ? ExtractEnumerableGenericType(type) : type.GetElementType()); 104 | if (elementType != null) 105 | return elementType; 106 | 107 | if (info.BaseType.IsEnumerableType()) 108 | elementType = info.BaseType.GetEnumerableElementType(); 109 | 110 | return elementType; 111 | } 112 | 113 | private static Type ExtractEnumerableGenericType(Type type) 114 | { 115 | var generics = type.GetGenericArguments(); 116 | 117 | if (generics.Length == 1) 118 | return generics[0]; 119 | 120 | if (generics.Length == 2 && (DictionaryType.IsAssignableFrom(type) || type.GetGenericTypeDefinition() == DictionaryOfTType)) 121 | return KeyValuePairType.MakeGenericType(generics[0], generics[1]); 122 | 123 | throw new StructurizerException(StructurizerExceptionMessages.TypeExtensions_ExtractEnumerableGenericType); 124 | } 125 | 126 | internal static bool IsStringType(this Type t) => t == StringType; 127 | 128 | internal static bool IsDateTimeType(this Type t) => t == DateTimeType; 129 | 130 | internal static bool IsAnyDateTimeType(this Type t) => IsDateTimeType(t) || IsNullableDateTimeType(t); 131 | 132 | internal static bool IsBoolType(this Type t) => t == BoolType; 133 | 134 | internal static bool IsAnyBoolType(this Type t) => IsBoolType(t) || IsNullableBoolType(t); 135 | 136 | internal static bool IsDecimalType(this Type t) => t == DecimalType; 137 | 138 | internal static bool IsAnyDecimalType(this Type t) => IsDecimalType(t) || IsNullableDecimalType(t); 139 | 140 | internal static bool IsSingleType(this Type t) => t == SingleType; 141 | 142 | internal static bool IsAnySingleType(this Type t) => IsSingleType(t) || IsNullableSingleType(t); 143 | 144 | internal static bool IsFloatType(this Type t) => t == FloatType; 145 | 146 | internal static bool IsAnyFloatType(this Type t) => IsFloatType(t) || IsNullableFloatType(t); 147 | 148 | internal static bool IsDoubleType(this Type t) => t == DoubleType; 149 | 150 | internal static bool IsAnyDoubleType(this Type t) => IsDoubleType(t) || IsNullableDoubleType(t); 151 | 152 | internal static bool IsLongType(this Type t) => t == LongType; 153 | 154 | internal static bool IsAnyLongType(this Type t) => IsLongType(t) || IsNullableLongType(t); 155 | 156 | internal static bool IsGuidType(this Type t) => t == GuidType; 157 | 158 | internal static bool IsAnyGuidType(this Type t) => IsGuidType(t) || IsNullableGuidType(t); 159 | 160 | internal static bool IsIntType(this Type t) => t == IntType; 161 | 162 | internal static bool IsAnyIntType(this Type t) => IsIntType(t) || IsNullableIntType(t); 163 | 164 | internal static bool IsByteType(this Type t) => t == ByteType; 165 | 166 | internal static bool IsAnyByteType(this Type t) => IsByteType(t) || IsNullableByteType(t); 167 | 168 | internal static bool IsShortType(this Type t) => t == ShortType; 169 | 170 | internal static bool IsAnyShortType(this Type t) => IsShortType(t) || IsNullableShortType(t); 171 | 172 | internal static bool IsCharType(this Type t) => t == CharType; 173 | 174 | internal static bool IsAnyCharType(this Type t) => IsCharType(t) || IsNullableCharType(t); 175 | 176 | internal static bool IsEnumType(this Type t) 177 | { 178 | var info = t.GetTypeInfo(); 179 | 180 | return (info.BaseType == EnumType) || info.IsEnum; 181 | } 182 | 183 | internal static bool IsAnyEnumType(this Type t) => IsEnumType(t) || IsNullableEnumType(t); 184 | 185 | internal static bool IsNullablePrimitiveType(this Type t) 186 | { 187 | var info = t.GetTypeInfo(); 188 | 189 | return ExtraPrimitiveNullableTypes.Contains(t) || (info.IsValueType && info.IsGenericType && t.GetGenericTypeDefinition() == NullableType && t.GetGenericArguments()[0].GetTypeInfo().IsPrimitive); 190 | } 191 | 192 | internal static bool IsNullableDateTimeType(this Type t) => t == NullableDateTimeType; 193 | 194 | internal static bool IsNullableDecimalType(this Type t) => t == NullableDecimalType; 195 | 196 | internal static bool IsNullableSingleType(this Type t) => t == NullableSingleType; 197 | 198 | internal static bool IsNullableFloatType(this Type t) => t == NullableFloatType; 199 | 200 | internal static bool IsNullableDoubleType(this Type t) => t == NullableDoubleType; 201 | 202 | internal static bool IsNullableBoolType(this Type t) => t == NullableBoolType; 203 | 204 | internal static bool IsNullableGuidType(this Type t) => t == NullableGuidType; 205 | 206 | internal static bool IsNullableShortType(this Type t) => t == NullableShortType; 207 | 208 | internal static bool IsNullableIntType(this Type t) => t == NullableIntType; 209 | 210 | internal static bool IsNullableByteType(this Type t) => t == NullableByteType; 211 | 212 | internal static bool IsNullableLongType(this Type t) => t == NullableLongType; 213 | 214 | internal static bool IsNullableCharType(this Type t) => t == NullableCharType; 215 | 216 | internal static bool IsNullableEnumType(this Type t) 217 | { 218 | var info = t.GetTypeInfo(); 219 | 220 | if (info.IsGenericType && t.GetGenericTypeDefinition() == NullableType) 221 | { 222 | t = Nullable.GetUnderlyingType(t); 223 | return t.IsEnumType(); 224 | } 225 | 226 | return false; 227 | } 228 | 229 | internal static bool IsAnyUnsignedType(this Type t) => t.IsUnsignedType() || t.IsNullableUnsignedType(); 230 | 231 | internal static bool IsUnsignedType(this Type t) 232 | { 233 | var info = t.GetTypeInfo(); 234 | 235 | return info.IsValueType && UnsignedTypes.Contains(t); 236 | } 237 | 238 | internal static bool IsNullableUnsignedType(this Type t) 239 | { 240 | var info = t.GetTypeInfo(); 241 | 242 | return info.IsValueType && NullableUnsignedTypes.Contains(t); 243 | } 244 | } 245 | } -------------------------------------------------------------------------------- /src/Structurizer/FlexibleStructureBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | using EnsureThat; 5 | using Structurizer.Configuration; 6 | 7 | namespace Structurizer 8 | { 9 | /// 10 | /// This builder adopts as time goes in the sense that if you 11 | /// ask it to create a structure for a type it does not know of, 12 | /// it creates the schemas etc and caches them. You can reconfigure 13 | /// it at anytime using the and the 14 | /// methods. 15 | /// 16 | public class FlexibleStructureBuilder : IStructureBuilder 17 | { 18 | private readonly IStructureTypeConfigurations _typeConfigurations; 19 | private readonly IStructureTypeFactory _typeFactory; 20 | private readonly IStructureSchemaFactory _schemaFactory; 21 | private readonly IStructureIndexesFactory _indexesFactory; 22 | private readonly ConcurrentDictionary _schemas; 23 | 24 | public FlexibleStructureBuilder() 25 | { 26 | _typeConfigurations = new StructureTypeConfigurations(); 27 | _typeFactory = new StructureTypeFactory(); 28 | _schemaFactory = new StructureSchemaFactory(); 29 | _indexesFactory = new StructureIndexesFactory(); 30 | _schemas = new ConcurrentDictionary(); 31 | } 32 | 33 | public void Configure(Type structureType, Action configurator = null) 34 | { 35 | EnsureArg.IsNotNull(structureType, nameof(structureType)); 36 | 37 | var typeConfig = _typeConfigurations.Register(structureType, configurator); 38 | 39 | _schemas.AddOrUpdate( 40 | structureType, 41 | t => CreateSchema(t, typeConfig), 42 | (type, schema) => CreateSchema(type, typeConfig)); 43 | } 44 | 45 | public void Configure(Action> configurator = null) where T : class 46 | { 47 | var typeConfig = _typeConfigurations.Register(configurator); 48 | 49 | _schemas.AddOrUpdate( 50 | typeof(T), 51 | t => CreateSchema(t, typeConfig), 52 | (type, schema) => CreateSchema(type, typeConfig)); 53 | } 54 | 55 | public void ConfigureUsingTemplate(T template, Action> configurator = null) where T : class 56 | => Configure(configurator); 57 | 58 | public IStructure CreateStructure(T item) where T : class 59 | { 60 | EnsureArg.IsNotNull(item, nameof(item)); 61 | 62 | var schema = GetSchema(item.GetType()); 63 | 64 | return new Structure(schema.Name, _indexesFactory.CreateIndexes(schema, item)); 65 | } 66 | 67 | public IStructure[] CreateStructures(T[] items) where T : class 68 | { 69 | EnsureArg.HasItems(items, nameof(items)); 70 | 71 | return items.Select(CreateStructure).ToArray(); 72 | } 73 | 74 | private IStructureSchema GetSchema(Type type) 75 | => _schemas.GetOrAdd(type, t => CreateSchema(t)); 76 | 77 | private IStructureSchema CreateSchema(Type type, IStructureTypeConfig typeConfig = null) 78 | { 79 | typeConfig = typeConfig ?? _typeConfigurations.GetConfiguration(type) ?? _typeConfigurations.Register(type); 80 | var structureType = _typeFactory.CreateFor(typeConfig); 81 | 82 | return _schemaFactory.CreateSchema(structureType); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/Structurizer/IDataTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizer 4 | { 5 | public interface IDataTypeConverter 6 | { 7 | DataTypeCode Convert(IStructureProperty property); 8 | DataTypeCode Convert(Type dataType); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Structurizer/IIndexAccessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Structurizer 5 | { 6 | public interface IIndexAccessor 7 | { 8 | string Path { get; } 9 | Type DataType { get; } 10 | DataTypeCode DataTypeCode { get; } 11 | bool IsEnumerable { get; } 12 | bool IsElement { get; } 13 | 14 | IList GetValues(T item) where T : class; 15 | } 16 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructure.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Structurizer 4 | { 5 | public interface IStructure 6 | { 7 | string Name { get; } 8 | IList Indexes { get; } 9 | } 10 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizer 2 | { 3 | /// 4 | /// Builds instances from sent Items. 5 | /// 6 | public interface IStructureBuilder 7 | { 8 | /// 9 | /// Creates a single for sent item. 10 | /// 11 | /// 12 | /// 13 | /// 14 | IStructure CreateStructure(T item) where T : class; 15 | 16 | /// 17 | /// Creates one for each sent item in . 18 | /// 19 | /// 20 | /// 21 | /// 22 | IStructure[] CreateStructures(T[] items) where T : class; 23 | } 24 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizer 4 | { 5 | public interface IStructureIndex 6 | { 7 | string Name { get; } 8 | string Path { get; } 9 | object Value { get; } 10 | Type DataType { get; } 11 | DataTypeCode DataTypeCode { get; } 12 | bool IsNumeric { get; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureIndexValue.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizer 2 | { 3 | public interface IStructureIndexValue 4 | { 5 | string Path { get; } 6 | object Value { get; } 7 | } 8 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureIndexesFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Structurizer 4 | { 5 | public interface IStructureIndexesFactory 6 | { 7 | IList CreateIndexes(IStructureSchema structureSchema, T item) where T : class; 8 | } 9 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizer 4 | { 5 | public interface IStructureProperty 6 | { 7 | string Name { get; } 8 | string Path { get; } 9 | Type DataType { get; } 10 | IStructureProperty Parent { get; } 11 | bool IsRootMember { get; } 12 | bool IsEnumerable { get; } 13 | bool IsElement { get; } 14 | Type ElementDataType { get; } 15 | Attribute[] Attributes { get; } 16 | 17 | object GetValue(object item); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructurePropertyFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Structurizer 4 | { 5 | public interface IStructurePropertyFactory 6 | { 7 | IStructureProperty CreateRootPropertyFrom(PropertyInfo propertyInfo); 8 | IStructureProperty CreateChildPropertyFrom(IStructureProperty parent, PropertyInfo propertyInfo); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureSchema.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Structurizer 4 | { 5 | public interface IStructureSchema 6 | { 7 | IStructureType StructureType { get; } 8 | string Name { get; } 9 | IReadOnlyList IndexAccessors { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureSchemaFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizer 2 | { 3 | public interface IStructureSchemaFactory 4 | { 5 | IStructureSchema CreateSchema(IStructureType structureType); 6 | } 7 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Structurizer 4 | { 5 | public interface IStructureType 6 | { 7 | Type Type { get; } 8 | string Name { get; } 9 | IStructureProperty[] IndexableProperties { get; } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureTypeConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Structurizer 5 | { 6 | public interface IStructureTypeConfig 7 | { 8 | Type Type { get; } 9 | IndexMode IndexMode { get; } 10 | IReadOnlyList MemberPaths { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureTypeConfigurations.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Structurizer 5 | { 6 | public interface IStructureTypeConfigurations : IEnumerable 7 | { 8 | /// 9 | /// Registreres and configures a certain type. 10 | /// 11 | /// 12 | /// 13 | /// 14 | IStructureTypeConfig Register(Type structureType, Action configurator = null); 15 | 16 | /// 17 | /// Registreres and configures a certain type. 18 | /// 19 | /// The type to configure 20 | /// 21 | /// 22 | IStructureTypeConfig Register(Action> configurator = null) where T : class; 23 | 24 | /// 25 | /// Returns any registrered configuration for the defined type. 26 | /// 27 | /// 28 | /// 29 | IStructureTypeConfig GetConfiguration(Type type); 30 | 31 | /// 32 | /// Returns any regisrered configuration for the generic type. 33 | /// 34 | /// 35 | /// 36 | IStructureTypeConfig GetConfiguration() where T : class; 37 | } 38 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureTypeConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | 4 | namespace Structurizer 5 | { 6 | public interface IStructureTypeConfigurator 7 | { 8 | IStructureTypeConfigurator UsingIndexMode(IndexMode mode); 9 | IStructureTypeConfigurator Members(params string[] memberPaths); 10 | } 11 | 12 | public interface IStructureTypeConfigurator where T : class 13 | { 14 | IStructureTypeConfigurator UsingIndexMode(IndexMode mode); 15 | IStructureTypeConfigurator Members(params string[] memberPaths); 16 | IStructureTypeConfigurator Members(params Expression>[] members); 17 | } 18 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureTypeFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizer 2 | { 3 | public interface IStructureTypeFactory 4 | { 5 | IStructureType CreateFor(IStructureTypeConfig typeConfig); 6 | } 7 | } -------------------------------------------------------------------------------- /src/Structurizer/IStructureTypeReflecter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Structurizer 5 | { 6 | /// 7 | /// Responsible for identifying the Properties that should be used as 8 | /// indexes for a certain structure type. 9 | /// 10 | public interface IStructureTypeReflecter 11 | { 12 | IStructureProperty[] GetIndexableProperties(Type structureType); 13 | IStructureProperty[] GetIndexablePropertiesExcept(Type structureType, IList memberPaths); 14 | IStructureProperty[] GetSpecificIndexableProperties(Type structureType, IList memberPaths); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Structurizer/IndexMode.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizer 2 | { 3 | //TODO: Switch default 4 | public enum IndexMode 5 | { 6 | Exclusive, 7 | Inclusive 8 | } 9 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/DynamicGetter.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizer.Schemas 2 | { 3 | public delegate object DynamicGetter(object obj); 4 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/DynamicPropertyFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using System.Reflection.Emit; 5 | using Structurizer.Extensions; 6 | 7 | namespace Structurizer.Schemas 8 | { 9 | public static class DynamicPropertyFactory 10 | { 11 | private static readonly Type ObjectType = typeof(object); 12 | private static readonly Type IlGetterType = typeof(Func); 13 | 14 | public static DynamicGetter GetterFor(PropertyInfo p) 15 | { 16 | if (p.DeclaringType.IsKeyValuePairType()) 17 | return new DynamicGetter(CreateLambdaGetter(p.DeclaringType, p)); 18 | 19 | return new DynamicGetter(CreateIlGetter(p)); 20 | } 21 | 22 | private static Func CreateLambdaGetter(Type type, PropertyInfo property) 23 | { 24 | var objExpr = Expression.Parameter(ObjectType, "theItem"); 25 | var castedObjExpr = Expression.Convert(objExpr, type); 26 | 27 | var p = Expression.Property(castedObjExpr, property); 28 | var castedProp = Expression.Convert(p, ObjectType); 29 | 30 | var lambda = Expression.Lambda>(castedProp, objExpr); 31 | 32 | return lambda.Compile(); 33 | } 34 | 35 | private static Func CreateIlGetter(PropertyInfo propertyInfo) 36 | { 37 | var propGetMethod = propertyInfo.GetGetMethod(true); 38 | if (propGetMethod == null) 39 | return null; 40 | 41 | var getter = CreateDynamicGetMethod(propertyInfo); 42 | var generator = getter.GetILGenerator(); 43 | 44 | var x = generator.DeclareLocal(propertyInfo.DeclaringType);//Arg 45 | var y = generator.DeclareLocal(propertyInfo.PropertyType); //Prop val 46 | var z = generator.DeclareLocal(ObjectType); //Prop val as obj 47 | 48 | generator.Emit(OpCodes.Ldarg_0); 49 | generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType); 50 | generator.Emit(OpCodes.Stloc, x); 51 | 52 | generator.Emit(OpCodes.Ldloc, x); 53 | generator.EmitCall(OpCodes.Callvirt, propGetMethod, null); 54 | generator.Emit(OpCodes.Stloc, y); 55 | 56 | generator.Emit(OpCodes.Ldloc, y); 57 | 58 | if (!propertyInfo.PropertyType.GetTypeInfo().IsClass) 59 | { 60 | generator.Emit(OpCodes.Box, propertyInfo.PropertyType); 61 | generator.Emit(OpCodes.Stloc, z); 62 | generator.Emit(OpCodes.Ldloc, z); 63 | } 64 | 65 | generator.Emit(OpCodes.Ret); 66 | 67 | return (Func)getter.CreateDelegate(IlGetterType); 68 | } 69 | 70 | private static DynamicMethod CreateDynamicGetMethod(PropertyInfo propertyInfo) 71 | { 72 | var args = new[] { ObjectType }; 73 | var name = $"_{propertyInfo.DeclaringType.Name}_Get{propertyInfo.Name}_"; 74 | var returnType = ObjectType; 75 | 76 | return !propertyInfo.DeclaringType.GetTypeInfo().IsInterface 77 | ? new DynamicMethod( 78 | name, 79 | returnType, 80 | args, 81 | propertyInfo.DeclaringType, 82 | true) 83 | : new DynamicMethod( 84 | name, 85 | returnType, 86 | args, 87 | propertyInfo.Module, 88 | true); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/IndexAccessor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace Structurizer.Schemas 6 | { 7 | public class IndexAccessor : IIndexAccessor 8 | { 9 | private readonly StructurePropertyCallstack _callstack; 10 | 11 | protected IStructureProperty Property { get; } 12 | public string Path => Property.Path; 13 | public Type DataType => Property.DataType; 14 | public DataTypeCode DataTypeCode { get; } 15 | public bool IsEnumerable => Property.IsEnumerable; 16 | public bool IsElement => Property.IsElement; 17 | 18 | public IndexAccessor(IStructureProperty property, DataTypeCode dataTypeCode) 19 | { 20 | _callstack = StructurePropertyCallstack.Generate(property); 21 | Property = property; 22 | DataTypeCode = dataTypeCode; 23 | } 24 | 25 | public IList GetValues(T item) where T : class 26 | { 27 | if (!Property.IsRootMember) 28 | return EvaluateCallstack(item, startAtCallstackIndex: 0, startPath: null); 29 | 30 | var rootMemberValue = Property.GetValue(item); 31 | if (rootMemberValue == null) 32 | return null; 33 | 34 | return IsEnumerable 35 | ? CollectionOfValuesToList((IEnumerable)rootMemberValue, Property, startPath: null) 36 | : new List { new StructureIndexValue(Property.Path, rootMemberValue) }; 37 | } 38 | 39 | private IList EvaluateCallstack(T startNode, int startAtCallstackIndex, string startPath) 40 | { 41 | if (startPath == null) 42 | startPath = _callstack[0].Name; 43 | 44 | object currentNode = startNode; 45 | var maxCallstackIndex = _callstack.Length - 1; 46 | for (var callstackIndex = startAtCallstackIndex; callstackIndex < _callstack.Length; callstackIndex++) 47 | { 48 | if (currentNode == null) 49 | return null; 50 | 51 | var currentProperty = _callstack[callstackIndex]; 52 | 53 | var enumerableNode = currentNode as IEnumerable; 54 | var isLastProperty = callstackIndex == maxCallstackIndex; 55 | if (isLastProperty) 56 | return enumerableNode != null 57 | ? ExtractValuesForEnumerableNode(enumerableNode, currentProperty, startPath) 58 | : ExtractValuesForSimpleNode(currentNode, currentProperty, startPath); 59 | 60 | if (enumerableNode == null) 61 | currentNode = currentProperty.GetValue(currentNode); 62 | else 63 | { 64 | var values = new List(); 65 | var i = -1; 66 | foreach (var node in enumerableNode) 67 | { 68 | i += 1; 69 | 70 | if (node == null) 71 | continue; 72 | 73 | var tmpValues = EvaluateCallstack( 74 | currentProperty.GetValue(node), 75 | startAtCallstackIndex: callstackIndex + 1, 76 | startPath: $"{currentProperty.Parent.Path}[{i.ToString()}].{currentProperty.Name}"); 77 | 78 | if (tmpValues != null) 79 | { 80 | values.AddRange(tmpValues); 81 | } 82 | } 83 | return values; 84 | } 85 | } 86 | 87 | return null; 88 | } 89 | 90 | private static IList ExtractValuesForEnumerableNode(T nodes, IStructureProperty property, string startPath) where T : IEnumerable 91 | { 92 | var collection = nodes as ICollection; 93 | var values = collection != null 94 | ? new List(collection.Count) 95 | : new List(); 96 | 97 | var i = -1; 98 | foreach (var node in nodes) 99 | { 100 | i += 1; 101 | var path = $"{startPath}[{i}]"; 102 | 103 | if (node == null) 104 | continue; 105 | 106 | var nodeValue = property.GetValue(node); 107 | if (nodeValue == null) 108 | continue; 109 | 110 | var enumerableNode = nodeValue as IEnumerable; 111 | 112 | if (enumerableNode != null && !(nodeValue is string)) 113 | values.AddRange(CollectionOfValuesToList(enumerableNode, property, path)); 114 | else 115 | values.Add(new StructureIndexValue($"{path}.{property.Name}", nodeValue)); 116 | } 117 | 118 | return values; 119 | } 120 | 121 | private static IList ExtractValuesForSimpleNode(object node, IStructureProperty property, string startPath) 122 | { 123 | var currentValue = property.GetValue(node); 124 | 125 | if (currentValue == null) 126 | return null; 127 | 128 | if (!property.IsEnumerable) 129 | return new List { new StructureIndexValue($"{startPath}.{property.Name}", currentValue) }; 130 | 131 | return CollectionOfValuesToList((IEnumerable)currentValue, property, startPath); 132 | } 133 | 134 | private static IList CollectionOfValuesToList(T elements, IStructureProperty property, string startPath) where T : IEnumerable 135 | { 136 | var collection = elements as ICollection; 137 | var values = collection != null 138 | ? new List(collection.Count) 139 | : new List(); 140 | 141 | if (startPath != null) 142 | startPath = startPath + "."; 143 | 144 | var i = 0; 145 | foreach (var element in elements) 146 | { 147 | if (element != null) 148 | values.Add(new StructureIndexValue($"{startPath}{property.Name}[{i.ToString()}]", element)); 149 | i += 1; 150 | } 151 | 152 | return values; 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/PropertyPathBuilder.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizer.Schemas 2 | { 3 | public static class PropertyPathBuilder 4 | { 5 | public static string BuildPath(IStructureProperty property) => property.IsRootMember 6 | ? property.Name 7 | : string.Concat(BuildPath(property.Parent), ".", property.Name); 8 | 9 | public static string BuildPath(IStructureProperty parent, string name) => parent == null 10 | ? name 11 | : string.Concat(parent.Path, ".", name); 12 | } 13 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/StructureProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Structurizer.Extensions; 3 | 4 | namespace Structurizer.Schemas 5 | { 6 | public class StructureProperty : IStructureProperty 7 | { 8 | private readonly StructurePropertyInfo _info; 9 | private readonly DynamicGetter _getter; 10 | 11 | public string Name => _info.Name; 12 | public string Path { get; } 13 | public Type DataType => _info.DataType; 14 | public IStructureProperty Parent => _info.Parent; 15 | public bool IsRootMember { get; } 16 | public bool IsEnumerable { get; } 17 | public bool IsElement { get; } 18 | public Type ElementDataType { get; } 19 | public Attribute[] Attributes => _info.Attributes; 20 | 21 | public StructureProperty(StructurePropertyInfo info, DynamicGetter getter) 22 | { 23 | _info = info; 24 | _getter = getter; 25 | 26 | IsRootMember = info.Parent == null; 27 | IsEnumerable = !DataType.IsSimpleType() && DataType.IsEnumerableType(); 28 | IsElement = Parent != null && (Parent.IsElement || Parent.IsEnumerable); 29 | ElementDataType = IsEnumerable ? DataType.GetEnumerableElementType() : null; 30 | Path = PropertyPathBuilder.BuildPath(this); 31 | } 32 | 33 | public object GetValue(object item) => _getter(item); 34 | } 35 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/StructurePropertyCallstack.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Structurizer.Schemas 4 | { 5 | public class StructurePropertyCallstack 6 | { 7 | private readonly IStructureProperty[] _stack; 8 | 9 | public IStructureProperty this[int index] => _stack[index]; 10 | public int Length => _stack.Length; 11 | 12 | private StructurePropertyCallstack(IStructureProperty[] stack) 13 | { 14 | _stack = stack; 15 | } 16 | 17 | public static StructurePropertyCallstack Generate(IStructureProperty property) 18 | { 19 | var callstack = ExtractCallstack(property); 20 | 21 | callstack.Reverse(); 22 | 23 | return new StructurePropertyCallstack(callstack.ToArray()); 24 | } 25 | 26 | private static List ExtractCallstack(IStructureProperty property) 27 | { 28 | if (property.IsRootMember) 29 | return new List { property }; 30 | 31 | var props = new List { property }; 32 | props.AddRange( 33 | ExtractCallstack(property.Parent)); 34 | 35 | return props; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/StructurePropertyFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace Structurizer.Schemas 6 | { 7 | public class StructurePropertyFactory : IStructurePropertyFactory 8 | { 9 | public virtual IStructureProperty CreateRootPropertyFrom(PropertyInfo propertyInfo) => new StructureProperty( 10 | ConvertInfo(propertyInfo), 11 | DynamicPropertyFactory.GetterFor(propertyInfo)); 12 | 13 | public virtual IStructureProperty CreateChildPropertyFrom(IStructureProperty parent, PropertyInfo propertyInfo) => new StructureProperty( 14 | ConvertInfo(propertyInfo, parent), 15 | DynamicPropertyFactory.GetterFor(propertyInfo)); 16 | 17 | private static StructurePropertyInfo ConvertInfo(PropertyInfo propertyInfo, IStructureProperty parent = null) => new StructurePropertyInfo( 18 | propertyInfo.Name, 19 | propertyInfo.PropertyType, 20 | propertyInfo.GetCustomAttributes(true).OfType().ToArray(), 21 | parent); 22 | } 23 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/StructurePropertyFactoryRules.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Structurizer.Schemas 5 | { 6 | public class StructurePropertyFactoryRules 7 | { 8 | private static readonly string[] DefaultTextDataTypeConventions = new string[] { "Text", "Content", "Description" }; 9 | 10 | public Func MemberNameIsForTextType { get; set; } 11 | 12 | public StructurePropertyFactoryRules() 13 | { 14 | MemberNameIsForTextType = OnMemberNameIsForTextType; 15 | } 16 | 17 | protected virtual bool OnMemberNameIsForTextType(string memberName) 18 | { 19 | return DefaultTextDataTypeConventions.Any(conv => conv.EndsWith(memberName, StringComparison.OrdinalIgnoreCase)); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/StructurePropertyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EnsureThat; 3 | 4 | namespace Structurizer.Schemas 5 | { 6 | public class StructurePropertyInfo 7 | { 8 | public readonly string Name; 9 | public readonly Type DataType; 10 | public readonly IStructureProperty Parent; 11 | public readonly Attribute[] Attributes; 12 | 13 | public StructurePropertyInfo( 14 | string name, 15 | Type dataType, 16 | Attribute[] attributes, 17 | IStructureProperty parent = null) 18 | { 19 | EnsureArg.IsNotNullOrWhiteSpace(name, nameof(name)); 20 | EnsureArg.IsNotNull(dataType, nameof(dataType)); 21 | EnsureArg.IsNotNull(attributes, nameof(attributes)); 22 | 23 | Name = name; 24 | DataType = dataType; 25 | Attributes = attributes; 26 | Parent = parent; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/StructureSchema.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using EnsureThat; 3 | 4 | namespace Structurizer.Schemas 5 | { 6 | public class StructureSchema : IStructureSchema 7 | { 8 | public IStructureType StructureType { get; } 9 | public string Name => StructureType.Name; 10 | public IReadOnlyList IndexAccessors { get; } 11 | 12 | public StructureSchema(IStructureType structureType, ICollection indexAccessors = null) 13 | { 14 | Ensure.That(structureType, nameof(structureType)).IsNotNull(); 15 | 16 | StructureType = structureType; 17 | IndexAccessors = indexAccessors != null 18 | ? new List(indexAccessors) 19 | : new List(); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/StructureType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EnsureThat; 3 | 4 | namespace Structurizer.Schemas 5 | { 6 | public class StructureType : IStructureType 7 | { 8 | public Type Type { get; } 9 | public string Name => Type.Name; 10 | public IStructureProperty[] IndexableProperties { get; } 11 | 12 | public StructureType( 13 | Type type, 14 | IStructureProperty[] indexableProperties = null) 15 | { 16 | Ensure.That(type, nameof(type)).IsNotNull(); 17 | 18 | Type = type; 19 | IndexableProperties = indexableProperties ?? new IStructureProperty[] { }; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/Structurizer/Schemas/StructureTypeReflecter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using EnsureThat; 7 | using Structurizer.Extensions; 8 | 9 | namespace Structurizer.Schemas 10 | { 11 | public class StructureTypeReflecter : IStructureTypeReflecter 12 | { 13 | private static readonly string[] EmptyArray = new string[0]; 14 | 15 | public const BindingFlags PropertyBindingFlags = 16 | BindingFlags.Public | BindingFlags.Instance; 17 | 18 | protected IStructurePropertyFactory PropertyFactory { get; } 19 | 20 | public StructureTypeReflecter(IStructurePropertyFactory propertyFactory = null) 21 | { 22 | PropertyFactory = propertyFactory ?? new StructurePropertyFactory(); 23 | } 24 | 25 | public IStructureProperty[] GetIndexableProperties(Type structureType) 26 | { 27 | return GetIndexableProperties(structureType, null, EmptyArray, null); 28 | } 29 | 30 | public IStructureProperty[] GetIndexablePropertiesExcept(Type structureType, IList memberPaths) 31 | { 32 | Ensure.That(memberPaths, nameof(memberPaths)).HasItems(); 33 | 34 | return GetIndexableProperties(structureType, null, memberPaths, null); 35 | } 36 | 37 | public IStructureProperty[] GetSpecificIndexableProperties(Type structureType, IList memberPaths) 38 | { 39 | Ensure.That(memberPaths, nameof(memberPaths)).HasItems(); 40 | 41 | var sb = new StringBuilder(); 42 | var paths = new HashSet(); 43 | foreach (var path in memberPaths) 44 | { 45 | sb.Clear(); 46 | var parts = path.Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries); 47 | foreach (var part in parts) 48 | { 49 | sb.Append(part); 50 | paths.Add(sb.ToString()); 51 | sb.Append("."); 52 | } 53 | } 54 | 55 | return GetIndexableProperties(structureType, null, EmptyArray, paths); 56 | } 57 | 58 | private IStructureProperty[] GetIndexableProperties( 59 | Type type, 60 | IStructureProperty parent, 61 | ICollection nonIndexablePaths, 62 | ICollection indexablePaths) 63 | { 64 | var initialPropertyInfos = GetIndexablePropertyInfos(type); 65 | if (initialPropertyInfos.Length == 0) 66 | return new IStructureProperty[] { }; 67 | 68 | var properties = new List(); 69 | 70 | var simplePropertyInfos = GetSimpleIndexablePropertyInfos(initialPropertyInfos, parent, nonIndexablePaths, indexablePaths); 71 | properties.AddRange(simplePropertyInfos.Select(spi => PropertyFactory.CreateChildPropertyFrom(parent, spi))); 72 | 73 | initialPropertyInfos = initialPropertyInfos.Where(p => !simplePropertyInfos.Contains(p)).ToArray(); 74 | 75 | foreach (var complexPropertyInfo in GetComplexIndexablePropertyInfos(initialPropertyInfos, parent, nonIndexablePaths, indexablePaths)) 76 | { 77 | var complexProperty = PropertyFactory.CreateChildPropertyFrom(parent, complexPropertyInfo); 78 | var simpleComplexProps = GetIndexableProperties( 79 | complexProperty.DataType, complexProperty, nonIndexablePaths, indexablePaths); 80 | 81 | var beforeCount = properties.Count; 82 | properties.AddRange(simpleComplexProps); 83 | 84 | if (properties.Count == beforeCount && complexProperty.DataType.GetTypeInfo().IsValueType) 85 | properties.Add(complexProperty); 86 | } 87 | 88 | foreach (var enumerablePropertyInfo in GetEnumerableIndexablePropertyInfos(initialPropertyInfos, parent, nonIndexablePaths, indexablePaths)) 89 | { 90 | var enumerableProperty = PropertyFactory.CreateChildPropertyFrom(parent, enumerablePropertyInfo); 91 | if (enumerableProperty.ElementDataType.IsSimpleType()) 92 | { 93 | properties.Add(enumerableProperty); 94 | continue; 95 | } 96 | 97 | var elementProperties = GetIndexableProperties( 98 | enumerableProperty.ElementDataType, 99 | enumerableProperty, 100 | nonIndexablePaths, 101 | indexablePaths); 102 | 103 | properties.AddRange(elementProperties); 104 | } 105 | 106 | return properties.ToArray(); 107 | } 108 | 109 | private static PropertyInfo[] GetIndexablePropertyInfos(Type type) => type.GetProperties(PropertyBindingFlags).ToArray(); 110 | 111 | private static PropertyInfo[] GetSimpleIndexablePropertyInfos( 112 | PropertyInfo[] properties, 113 | IStructureProperty parent = null, 114 | ICollection nonIndexablePaths = null, 115 | ICollection indexablePaths = null) 116 | { 117 | if (properties.Length == 0) 118 | return new PropertyInfo[0]; 119 | 120 | var filteredProperties = properties.Where(p => p.PropertyType.IsSimpleType()); 121 | 122 | if (nonIndexablePaths != null && nonIndexablePaths.Any()) 123 | filteredProperties = filteredProperties.Where(p => !nonIndexablePaths.Contains( 124 | PropertyPathBuilder.BuildPath(parent, p.Name))); 125 | 126 | if (indexablePaths != null && indexablePaths.Any()) 127 | filteredProperties = filteredProperties.Where(p => indexablePaths.Contains( 128 | PropertyPathBuilder.BuildPath(parent, p.Name))); 129 | 130 | return filteredProperties.ToArray(); 131 | } 132 | 133 | private static PropertyInfo[] GetComplexIndexablePropertyInfos( 134 | PropertyInfo[] properties, 135 | IStructureProperty parent = null, 136 | ICollection nonIndexablePaths = null, 137 | ICollection indexablePaths = null) 138 | { 139 | if (properties.Length == 0) 140 | return new PropertyInfo[0]; 141 | 142 | var filteredProperties = properties.Where(p => 143 | !p.PropertyType.IsSimpleType() && 144 | !p.PropertyType.IsEnumerableType()); 145 | 146 | if (nonIndexablePaths != null && nonIndexablePaths.Any()) 147 | filteredProperties = filteredProperties.Where(p => !nonIndexablePaths.Contains( 148 | PropertyPathBuilder.BuildPath(parent, p.Name))); 149 | 150 | if (indexablePaths != null && indexablePaths.Any()) 151 | filteredProperties = filteredProperties.Where(p => indexablePaths.Contains( 152 | PropertyPathBuilder.BuildPath(parent, p.Name))); 153 | 154 | return filteredProperties.ToArray(); 155 | } 156 | 157 | private static PropertyInfo[] GetEnumerableIndexablePropertyInfos( 158 | PropertyInfo[] properties, 159 | IStructureProperty parent = null, 160 | ICollection nonIndexablePaths = null, 161 | ICollection indexablePaths = null) 162 | { 163 | if (properties.Length == 0) 164 | return new PropertyInfo[0]; 165 | 166 | var filteredProperties = properties.Where(p => 167 | !p.PropertyType.IsSimpleType() && 168 | p.PropertyType.IsEnumerableType() && 169 | !p.PropertyType.IsEnumerableBytesType()); 170 | 171 | if (nonIndexablePaths != null && nonIndexablePaths.Any()) 172 | filteredProperties = filteredProperties.Where(p => !nonIndexablePaths.Contains( 173 | PropertyPathBuilder.BuildPath(parent, p.Name))); 174 | 175 | if (indexablePaths != null && indexablePaths.Any()) 176 | filteredProperties = filteredProperties.Where(p => indexablePaths.Contains( 177 | PropertyPathBuilder.BuildPath(parent, p.Name))); 178 | 179 | return filteredProperties.ToArray(); 180 | } 181 | } 182 | } -------------------------------------------------------------------------------- /src/Structurizer/Structure.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using EnsureThat; 3 | 4 | namespace Structurizer 5 | { 6 | public class Structure : IStructure 7 | { 8 | public string Name { get; } 9 | public IList Indexes { get; } 10 | 11 | public Structure(string name, IList indexes) 12 | { 13 | Ensure.That(name, nameof(name)).IsNotNullOrWhiteSpace(); 14 | 15 | Name = name; 16 | Indexes = indexes ?? new List(0); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Structurizer/StructureBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using EnsureThat; 5 | using Structurizer.Configuration; 6 | 7 | namespace Structurizer 8 | { 9 | /// 10 | /// Static implementation of in the sense 11 | /// of, when created, you pass all the type configurations 12 | /// that the builder should be able to create structures for. 13 | /// If you want a more dynamic and flexible builder, . 14 | /// 15 | public class StructureBuilder : IStructureBuilder 16 | { 17 | protected IDictionary Schemas { get; } 18 | protected IStructureIndexesFactory IndexesFactory { get; } 19 | 20 | private StructureBuilder( 21 | IDictionary schemas, 22 | IStructureIndexesFactory indexesFactory = null) 23 | { 24 | EnsureArg.HasItems(schemas, nameof(schemas)); 25 | 26 | Schemas = schemas; 27 | IndexesFactory = indexesFactory ?? new StructureIndexesFactory(); 28 | } 29 | 30 | public static IStructureBuilder Create(Action config) 31 | { 32 | EnsureArg.IsNotNull(config, nameof(config)); 33 | 34 | var configs = new StructureTypeConfigurations(); 35 | 36 | config(configs); 37 | 38 | return Create(configs); 39 | } 40 | 41 | public static IStructureBuilder Create(IStructureTypeConfigurations typeConfigs) 42 | { 43 | EnsureArg.IsNotNull(typeConfigs, nameof(typeConfigs)); 44 | 45 | var structureTypeFactory = new StructureTypeFactory(); 46 | var schemaFactory = new StructureSchemaFactory(); 47 | 48 | var schemas = typeConfigs 49 | .Select(tc => structureTypeFactory.CreateFor(tc)) 50 | .Select(st => schemaFactory.CreateSchema(st)) 51 | .ToDictionary(s => s.StructureType.Type); 52 | 53 | return new StructureBuilder(schemas); 54 | } 55 | 56 | public IStructure CreateStructure(T item) where T : class 57 | { 58 | var schema = GetSchema(typeof(T)); 59 | 60 | return new Structure( 61 | schema.Name, 62 | CreateIndexes(schema, item)); 63 | } 64 | 65 | public IStructure[] CreateStructures(T[] items) where T : class 66 | { 67 | var schema = GetSchema(typeof(T)); 68 | 69 | var structures = new IStructure[items.Length]; 70 | 71 | for (var i = 0; i < structures.Length; i++) 72 | { 73 | var itm = items[i]; 74 | 75 | structures[i] = new Structure( 76 | schema.Name, 77 | CreateIndexes(schema, itm)); 78 | } 79 | 80 | return structures; 81 | } 82 | 83 | private IStructureSchema GetSchema(Type type) => Schemas[type]; 84 | 85 | private IList CreateIndexes(IStructureSchema schema, T item) where T : class 86 | => IndexesFactory.CreateIndexes(schema, item); 87 | } 88 | } -------------------------------------------------------------------------------- /src/Structurizer/StructureIndex.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using EnsureThat; 3 | 4 | namespace Structurizer 5 | { 6 | public class StructureIndex : IStructureIndex 7 | { 8 | public string Name { get; } 9 | public string Path { get; } 10 | public object Value { get; } 11 | public Type DataType { get; } 12 | public DataTypeCode DataTypeCode { get; } 13 | public bool IsNumeric { get; } 14 | 15 | public StructureIndex(string name, string path, object value, Type dataType, DataTypeCode dataTypeCode) 16 | { 17 | EnsureArg.IsNotNullOrWhiteSpace(name, nameof(name)); 18 | EnsureArg.IsNotNullOrWhiteSpace(path, nameof(path)); 19 | EnsureArg.IsNotNull(dataType, nameof(dataType)); 20 | 21 | Name = name; 22 | Path = path; 23 | Value = value; 24 | DataType = dataType; 25 | DataTypeCode = dataTypeCode; 26 | IsNumeric = dataTypeCode.IsNumeric(); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Structurizer/StructureIndexValue.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizer 2 | { 3 | internal class StructureIndexValue : IStructureIndexValue 4 | { 5 | public string Path { get; } 6 | public object Value { get; } 7 | 8 | internal StructureIndexValue(string path, object value) 9 | { 10 | Path = path; 11 | Value = value; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Structurizer/StructureIndexesFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Structurizer 4 | { 5 | public class StructureIndexesFactory : IStructureIndexesFactory 6 | { 7 | public IList CreateIndexes(IStructureSchema structureSchema, T item) where T : class 8 | { 9 | var result = new List(); 10 | 11 | for (var i = 0; i < structureSchema.IndexAccessors.Count; i++) 12 | { 13 | var indexAccessor = structureSchema.IndexAccessors[i]; 14 | var values = indexAccessor.GetValues(item); 15 | 16 | var valuesExists = values != null && values.Count > 0; 17 | if (!valuesExists) 18 | continue; 19 | 20 | var isCollectionOfValues = indexAccessor.IsEnumerable || indexAccessor.IsElement || values.Count > 1; 21 | if (!isCollectionOfValues) 22 | { 23 | if (values[0].Value == null) 24 | continue; 25 | 26 | result.Add(new StructureIndex(indexAccessor.Path, values[0].Path, values[0].Value, indexAccessor.DataType, indexAccessor.DataTypeCode)); 27 | } 28 | else 29 | { 30 | for (var subC = 0; subC < values.Count; subC++) 31 | { 32 | if (values[subC] == null) 33 | continue; 34 | 35 | if (values[subC].Value == null) 36 | continue; 37 | 38 | result.Add(new StructureIndex( 39 | indexAccessor.Path, 40 | values[subC].Path, 41 | values[subC].Value, 42 | indexAccessor.DataType, 43 | indexAccessor.DataTypeCode)); 44 | } 45 | } 46 | } 47 | 48 | return result; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Structurizer/StructureSchemaFactory.cs: -------------------------------------------------------------------------------- 1 | using EnsureThat; 2 | using Structurizer.Schemas; 3 | 4 | namespace Structurizer 5 | { 6 | public class StructureSchemaFactory : IStructureSchemaFactory 7 | { 8 | protected IDataTypeConverter DataTypeConverter { get; } 9 | 10 | public StructureSchemaFactory(IDataTypeConverter dataTypeConverter = null) 11 | { 12 | DataTypeConverter = dataTypeConverter ?? new DataTypeConverter(); 13 | } 14 | 15 | public virtual IStructureSchema CreateSchema(IStructureType structureType) 16 | { 17 | Ensure.That(structureType, nameof(structureType)).IsNotNull(); 18 | 19 | var indexAccessors = GetIndexAccessors(structureType); 20 | if (indexAccessors == null || indexAccessors.Length < 1) 21 | throw new StructurizerException(string.Format(StructurizerExceptionMessages.AutoSchemaBuilder_MissingIndexableMembers, structureType.Name)); 22 | 23 | return new StructureSchema(structureType, indexAccessors); 24 | } 25 | 26 | protected virtual IIndexAccessor[] GetIndexAccessors(IStructureType structureType) 27 | { 28 | var accessors = new IIndexAccessor[structureType.IndexableProperties.Length]; 29 | 30 | for (var i = 0; i < structureType.IndexableProperties.Length; i++) 31 | { 32 | var property = structureType.IndexableProperties[i]; 33 | accessors[i] = new IndexAccessor(property, DataTypeConverter.Convert(property)); 34 | } 35 | 36 | return accessors; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Structurizer/StructureTypeFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Structurizer.Schemas; 3 | 4 | namespace Structurizer 5 | { 6 | public class StructureTypeFactory : IStructureTypeFactory 7 | { 8 | protected IStructureTypeReflecter Reflecter { get; } 9 | 10 | public StructureTypeFactory( 11 | IStructureTypeReflecter reflecter = null) 12 | { 13 | Reflecter = reflecter ?? new StructureTypeReflecter(); 14 | } 15 | 16 | public IStructureType CreateFor(IStructureTypeConfig typeConfig) 17 | { 18 | var shouldIndexAllMembers = typeConfig.IndexMode == IndexMode.Exclusive && !typeConfig.MemberPaths.Any(); 19 | 20 | if (shouldIndexAllMembers) 21 | return new StructureType( 22 | typeConfig.Type, 23 | Reflecter.GetIndexableProperties(typeConfig.Type)); 24 | 25 | return new StructureType( 26 | typeConfig.Type, 27 | typeConfig.IndexMode == IndexMode.Exclusive 28 | ? Reflecter.GetIndexablePropertiesExcept(typeConfig.Type, typeConfig.MemberPaths.ToList()) 29 | : Reflecter.GetSpecificIndexableProperties(typeConfig.Type, typeConfig.MemberPaths.ToList())); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/Structurizer/Structurizer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net451;netstandard2.0 5 | Used for creating key-value representation of object graphs 6 | key-value structure object-graph 7 | 8 | 9 | 10 | 1701;1702;1705;1591 11 | bin\Release\$(TargetFramework)\Structurizer.xml 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Structurizer/StructurizerException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Structurizer 5 | { 6 | public class StructurizerException : AggregateException 7 | { 8 | public StructurizerException(string message) 9 | : base(message) 10 | { 11 | } 12 | 13 | public StructurizerException(string message, IEnumerable innerExceptions) 14 | : base(message, innerExceptions) 15 | { 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Structurizer/StructurizerExceptionMessages.cs: -------------------------------------------------------------------------------- 1 | namespace Structurizer 2 | { 3 | public static class StructurizerExceptionMessages 4 | { 5 | public static readonly string AutoSchemaBuilder_MissingIndexableMembers 6 | = "The Item of type '{0}' has no members that are indexable. There's no point in treating items that has nothing to index."; 7 | 8 | public static readonly string ExpressionEvaluation_DontKnowHowToEvalExpression 9 | = "Don't know how to evaluate the expression type: '{0}'."; 10 | public static readonly string ExpressionEvaluation_DontKnowHowToEvalUnaryExpression 11 | = "Don't know how to evaluate the unary expression of node type: '{0}'."; 12 | 13 | public static readonly string TypeExtensions_ExtractEnumerableGenericType 14 | = "When extracting generic element type from enumerables, a maximum of two generic arguments are supported, which then are supposed to belong to KeyValuePair."; 15 | } 16 | } -------------------------------------------------------------------------------- /src/UnitTests/Configuration/StructureTypeConfigTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using FluentAssertions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Structurizer; 6 | using Structurizer.Configuration; 7 | 8 | namespace UnitTests.Configuration 9 | { 10 | [TestClass] 11 | public class StructureTypeConfigTests : UnitTests 12 | { 13 | [TestMethod] 14 | public void Ctor_Should_throw_When_missing_type() 15 | { 16 | Action invalidAction = () => new StructureTypeConfig(null, default(IndexMode), new HashSet()); 17 | 18 | invalidAction.Should().Throw().And.ParamName.Should().Be("structureType"); 19 | } 20 | 21 | [TestMethod] 22 | public void Ctor_Should_throw_When_missing_member_paths() 23 | { 24 | Action invalidAction = () => new StructureTypeConfig(typeof(Dummy), default(IndexMode), null); 25 | 26 | invalidAction.Should().Throw().And.ParamName.Should().Be("memberPaths"); 27 | } 28 | 29 | [TestMethod] 30 | public void Ctor_Should_initialize_properly() 31 | { 32 | var expectedType = typeof(Dummy); 33 | var config = new StructureTypeConfig(expectedType, IndexMode.Inclusive, new HashSet { "Foo", "Bar" }); 34 | 35 | config.Type.Should().Be(expectedType); 36 | config.IndexMode.Should().Be(IndexMode.Inclusive); 37 | config.MemberPaths.Should().BeEquivalentTo("Foo", "Bar"); 38 | } 39 | 40 | private class Dummy { } 41 | } 42 | } -------------------------------------------------------------------------------- /src/UnitTests/Configuration/StructureTypeConfigurationsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Structurizer; 6 | using Structurizer.Configuration; 7 | 8 | namespace UnitTests.Configuration 9 | { 10 | [TestClass] 11 | public class StructureTypeConfigurationsTests : UnitTests 12 | { 13 | [TestMethod] 14 | public void Register_with_null_config_Should_add_config_with_exclusive_mode() 15 | { 16 | var configs = new StructureTypeConfigurations(); 17 | 18 | configs.Register(typeof(Dummy)); 19 | 20 | configs.Should().HaveCount(1); 21 | configs.First().IndexMode.Should().Be(IndexMode.Exclusive); 22 | } 23 | 24 | [TestMethod] 25 | public void Register_with_empty_config_Should_add_config_with_exclusive_mode() 26 | { 27 | var configs = new StructureTypeConfigurations(); 28 | 29 | configs.Register(typeof(Dummy), cfg => { }); 30 | 31 | configs.Should().HaveCount(1); 32 | configs.First().IndexMode.Should().Be(IndexMode.Exclusive); 33 | } 34 | 35 | [TestMethod] 36 | public void Register_generic_with_null_config_Should_add_config_with_exclusive_mode() 37 | { 38 | var configs = new StructureTypeConfigurations(); 39 | 40 | configs.Register(); 41 | 42 | configs.Should().HaveCount(1); 43 | configs.First().IndexMode.Should().Be(IndexMode.Exclusive); 44 | } 45 | 46 | [TestMethod] 47 | public void Register_generic_with_empty_config_Should_add_config_with_exclusive_mode() 48 | { 49 | var configs = new StructureTypeConfigurations(); 50 | 51 | configs.Register(cfg => { }); 52 | 53 | configs.Should().HaveCount(1); 54 | configs.First().IndexMode.Should().Be(IndexMode.Exclusive); 55 | } 56 | 57 | [TestMethod] 58 | public void Register_Should_not_throw_When_registrering_same_type_more_than_once() 59 | { 60 | var configs = new StructureTypeConfigurations(); 61 | configs.Register(typeof(Dummy)); 62 | 63 | Action action = () => configs.Register(typeof(Dummy)); 64 | 65 | action.Should().NotThrow(); 66 | } 67 | 68 | [TestMethod] 69 | public void Register_generic_Should_not_throw_When_registrering_same_type_more_than_once() 70 | { 71 | var configs = new StructureTypeConfigurations(); 72 | configs.Register(); 73 | 74 | Action action = () => configs.Register(); 75 | 76 | action.Should().NotThrow(); 77 | } 78 | 79 | [TestMethod] 80 | public void GetConfigurations_Should_return_null_When_no_config_registration_exists() 81 | { 82 | var configs = new StructureTypeConfigurations(); 83 | 84 | var config = configs.GetConfiguration(typeof(Dummy)); 85 | 86 | config.Should().BeNull(); 87 | } 88 | 89 | [TestMethod] 90 | public void GetConfigurations_Should_return_config_When_registrated_via_generic_version() 91 | { 92 | var configs = new StructureTypeConfigurations(); 93 | configs.Register(cfg => { }); 94 | 95 | var config = configs.GetConfiguration(typeof(Dummy)); 96 | 97 | Assert.IsNotNull(config); 98 | Assert.AreEqual(typeof(Dummy), config.Type); 99 | } 100 | 101 | [TestMethod] 102 | public void GetConfigurations_Should_return_config_When_registrated_via_non_generic_version() 103 | { 104 | var configs = new StructureTypeConfigurations(); 105 | configs.Register(typeof(Dummy), cfg => { }); 106 | 107 | var config = configs.GetConfiguration(typeof(Dummy)); 108 | 109 | Assert.IsNotNull(config); 110 | Assert.AreEqual(typeof(Dummy), config.Type); 111 | } 112 | 113 | private class Dummy { } 114 | } 115 | } -------------------------------------------------------------------------------- /src/UnitTests/Configuration/StructureTypeConfiguratorTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Structurizer; 6 | using Structurizer.Configuration; 7 | 8 | namespace UnitTests.Configuration 9 | { 10 | [TestClass] 11 | public class StructureTypeConfiguratorTests : UnitTests 12 | { 13 | private IStructureTypeConfig UseNonGenericConfiguratorFor(Action configure) where T : class 14 | { 15 | var configurator = new StructureTypeConfigurator(typeof(T)); 16 | 17 | configure(configurator); 18 | 19 | return configurator.GenerateConfig(); 20 | } 21 | 22 | private IStructureTypeConfig UseGenericConfiguratorFor(Action> configure) where T : class 23 | { 24 | var configurator = new StructureTypeConfigurator(typeof(T)); 25 | 26 | configure(configurator); 27 | 28 | return configurator.GenerateConfig(); 29 | } 30 | 31 | [TestMethod] 32 | public void NonGeneric_Should_assign_IndexMode() 33 | { 34 | var config1 = UseNonGenericConfiguratorFor(cfg => cfg.UsingIndexMode(IndexMode.Exclusive)); 35 | var config2 = UseNonGenericConfiguratorFor(cfg => cfg.UsingIndexMode(IndexMode.Inclusive)); 36 | 37 | config1.IndexMode.Should().Be(IndexMode.Exclusive); 38 | config2.IndexMode.Should().Be(IndexMode.Inclusive); 39 | } 40 | 41 | [TestMethod] 42 | public void Generic_Should_assign_IndexMode() 43 | { 44 | var config1 = UseGenericConfiguratorFor(cfg => cfg.UsingIndexMode(IndexMode.Exclusive)); 45 | var config2 = UseGenericConfiguratorFor(cfg => cfg.UsingIndexMode(IndexMode.Inclusive)); 46 | 47 | config1.IndexMode.Should().Be(IndexMode.Exclusive); 48 | config2.IndexMode.Should().Be(IndexMode.Inclusive); 49 | } 50 | 51 | [TestMethod] 52 | public void NonGeneric_Should_store_member_paths_When_defining_specific_members() 53 | { 54 | var config = UseNonGenericConfiguratorFor(cfg => cfg.Members("Int1", "String1", "Nested.Int1", "Nested.String1")); 55 | 56 | var memberPaths = config.MemberPaths.ToArray(); 57 | memberPaths.Should().HaveCount(4); 58 | memberPaths.Should().BeEquivalentTo("Int1", "String1", "Nested.Int1", "Nested.String1"); 59 | } 60 | 61 | [TestMethod] 62 | public void Generic_Should_store_member_paths_When_defining_specific_members() 63 | { 64 | var config = UseGenericConfiguratorFor(cfg => cfg.Members(x => x.Int1, x => x.String1, x => x.Nested.Int1, x => x.Nested.String1)); 65 | 66 | var memberPaths = config.MemberPaths.ToArray(); 67 | memberPaths.Should().HaveCount(4); 68 | memberPaths.Should().BeEquivalentTo("Int1", "String1", "Nested.Int1", "Nested.String1"); 69 | } 70 | 71 | [TestMethod] 72 | public void NonGeneric_Should_only_store_member_once_When_called_twice() 73 | { 74 | var config = UseNonGenericConfiguratorFor(cfg => cfg.Members("String1", "String1")); 75 | 76 | config.MemberPaths.Should().HaveCount(1); 77 | } 78 | 79 | [TestMethod] 80 | public void Generic_Should_only_store_member_once_When_called_twice() 81 | { 82 | var config = UseGenericConfiguratorFor(cfg => cfg.Members(x => x.String1, x => x.String1)); 83 | 84 | config.MemberPaths.Should().HaveCount(1); 85 | } 86 | 87 | private class Dummy 88 | { 89 | public int Int1 { get; set; } 90 | 91 | public string String1 { get; set; } 92 | 93 | public Nested Nested { get; set; } 94 | } 95 | 96 | private class Nested 97 | { 98 | public int Int1 { get; set; } 99 | 100 | public string String1 { get; set; } 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /src/UnitTests/DataTypeCodeTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Structurizer; 6 | 7 | namespace UnitTests 8 | { 9 | [TestClass] 10 | public class DataTypeCodeTests : UnitTests 11 | { 12 | [TestMethod] 13 | public void IsNumeric_Should_return_true_When_number_type_only() 14 | { 15 | var allNumbers = new[] 16 | { 17 | DataTypeCode.FractalNumber, 18 | DataTypeCode.IntegerNumber, 19 | DataTypeCode.UnsignedIntegerNumber 20 | }; 21 | 22 | var all = Enum.GetValues(typeof(DataTypeCode)).Cast().ToList(); 23 | 24 | all 25 | .Where(code => allNumbers.Contains(code)) 26 | .ToList() 27 | .ForEach(code => 28 | code.IsNumeric().Should().BeTrue($"{code} is not numeric")); 29 | 30 | all 31 | .Where(code => !allNumbers.Contains(code)) 32 | .ToList() 33 | .ForEach(code => 34 | code.IsNumeric().Should().BeFalse($"{code} is numeric")); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/UnitTests/FlexibleStructureBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Structurizer; 4 | 5 | namespace UnitTests 6 | { 7 | [TestClass] 8 | public class FlexibleStructureBuilderTests : UnitTestsOf 9 | { 10 | [TestInitialize] 11 | public virtual void OnTestInit() 12 | { 13 | UnitUnderTest = new FlexibleStructureBuilder(); 14 | } 15 | 16 | [TestMethod] 17 | public void Should_be_able_to_create_a_structure_When_type_is_unknown() 18 | { 19 | var item = new MyType 20 | { 21 | MyInt = 42, 22 | MyString = "Foo" 23 | }; 24 | 25 | var structure = UnitUnderTest.CreateStructure(item); 26 | 27 | structure.Name.Should().Be(nameof(MyType)); 28 | structure.Indexes.Should().HaveCount(2); 29 | structure.Indexes.Should() 30 | .Contain(i => i.Name == nameof(item.MyInt)) 31 | .Which.Value.Should().Be(item.MyInt); 32 | structure.Indexes.Should() 33 | .Contain(i => i.Name == nameof(item.MyString)) 34 | .Which.Value.Should().Be(item.MyString); 35 | } 36 | 37 | [TestMethod] 38 | public void Should_be_able_to_create_a_structure_When_type_is_anonymous() 39 | { 40 | var item = new 41 | { 42 | MyInt = 42, 43 | MyString = "Foo" 44 | }; 45 | 46 | var structure = UnitUnderTest.CreateStructure(item); 47 | 48 | structure.Name.Should().Contain("Anonymous"); 49 | structure.Indexes.Should().HaveCount(2); 50 | structure.Indexes.Should() 51 | .Contain(i => i.Name == nameof(item.MyInt)) 52 | .Which.Value.Should().Be(item.MyInt); 53 | structure.Indexes.Should() 54 | .Contain(i => i.Name == nameof(item.MyString)) 55 | .Which.Value.Should().Be(item.MyString); 56 | } 57 | 58 | [TestMethod] 59 | public void Should_be_able_to_reconfigure_using_type() 60 | { 61 | var item = new MyType 62 | { 63 | MyInt = 42, 64 | MyString = "Foo" 65 | }; 66 | 67 | var structure = UnitUnderTest.CreateStructure(item); 68 | structure.Name.Should().Be(nameof(MyType)); 69 | structure.Indexes.Should().HaveCount(2); 70 | structure.Indexes.Should() 71 | .Contain(i => i.Name == nameof(item.MyInt)) 72 | .Which.Value.Should().Be(item.MyInt); 73 | structure.Indexes.Should() 74 | .Contain(i => i.Name == nameof(item.MyString)) 75 | .Which.Value.Should().Be(item.MyString); 76 | 77 | UnitUnderTest.Configure(typeof(MyType), cfg => cfg.Members("MyInt").UsingIndexMode(IndexMode.Inclusive)); 78 | 79 | structure = UnitUnderTest.CreateStructure(item); 80 | structure.Name.Should().Be(nameof(MyType)); 81 | structure.Indexes.Should().HaveCount(1); 82 | structure.Indexes.Should() 83 | .Contain(i => i.Name == nameof(item.MyInt)) 84 | .Which.Value.Should().Be(item.MyInt); 85 | } 86 | 87 | [TestMethod] 88 | public void Should_be_able_to_reconfigure_using_generics_type() 89 | { 90 | var item = new MyType 91 | { 92 | MyInt = 42, 93 | MyString = "Foo" 94 | }; 95 | 96 | var structure = UnitUnderTest.CreateStructure(item); 97 | structure.Name.Should().Be(nameof(MyType)); 98 | structure.Indexes.Should().HaveCount(2); 99 | structure.Indexes.Should() 100 | .Contain(i => i.Name == nameof(item.MyInt)) 101 | .Which.Value.Should().Be(item.MyInt); 102 | structure.Indexes.Should() 103 | .Contain(i => i.Name == nameof(item.MyString)) 104 | .Which.Value.Should().Be(item.MyString); 105 | 106 | UnitUnderTest.Configure(cfg => cfg.Members("MyInt").UsingIndexMode(IndexMode.Inclusive)); 107 | 108 | structure = UnitUnderTest.CreateStructure(item); 109 | structure.Name.Should().Be(nameof(MyType)); 110 | structure.Indexes.Should().HaveCount(1); 111 | structure.Indexes.Should() 112 | .Contain(i => i.Name == nameof(item.MyInt)) 113 | .Which.Value.Should().Be(item.MyInt); 114 | } 115 | 116 | [TestMethod] 117 | public void Should_be_able_to_reconfigure_using_anonymous_type() 118 | { 119 | var item = new 120 | { 121 | MyInt = 42, 122 | MyString = "Foo" 123 | }; 124 | 125 | var structure = UnitUnderTest.CreateStructure(item); 126 | structure.Name.Should().Contain("Anonymous"); 127 | structure.Indexes.Should().HaveCount(2); 128 | structure.Indexes.Should() 129 | .Contain(i => i.Name == nameof(item.MyInt)) 130 | .Which.Value.Should().Be(item.MyInt); 131 | structure.Indexes.Should() 132 | .Contain(i => i.Name == nameof(item.MyString)) 133 | .Which.Value.Should().Be(item.MyString); 134 | 135 | UnitUnderTest.ConfigureUsingTemplate(item, cfg => cfg.Members(i => i.MyInt).UsingIndexMode(IndexMode.Inclusive)); 136 | 137 | structure = UnitUnderTest.CreateStructure(item); 138 | structure.Name.Should().Contain("Anonymous"); 139 | structure.Indexes.Should().HaveCount(1); 140 | structure.Indexes.Should() 141 | .Contain(i => i.Name == nameof(item.MyInt)) 142 | .Which.Value.Should().Be(item.MyInt); 143 | } 144 | 145 | private class MyType 146 | { 147 | public int MyInt { get; set; } 148 | public string MyString { get; set; } 149 | } 150 | } 151 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/DataTypeConverterTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Moq; 4 | using Structurizer; 5 | 6 | namespace UnitTests.Schemas 7 | { 8 | [TestClass] 9 | public class DataTypeConverterTests : UnitTests 10 | { 11 | private IDataTypeConverter _converter; 12 | 13 | public DataTypeConverterTests() 14 | { 15 | _converter = new DataTypeConverter(); 16 | } 17 | 18 | private IStructureProperty CreateProperty(Type type, string name = null) 19 | { 20 | var property = new Mock(); 21 | property.SetupGet(f => f.Name).Returns(name ?? "Foo"); 22 | property.SetupGet(f => f.DataType).Returns(type); 23 | 24 | return property.Object; 25 | } 26 | 27 | [TestMethod] 28 | [DataRow(typeof(ushort))] 29 | [DataRow(typeof(ushort?))] 30 | [DataRow(typeof(uint))] 31 | [DataRow(typeof(uint?))] 32 | [DataRow(typeof(ulong))] 33 | [DataRow(typeof(ulong?))] 34 | public void Convert_TypeIsUnsignedIntegerFamily_ReturnsUnsignedIntegerNumber(Type type) 35 | { 36 | Assert.AreEqual(DataTypeCode.UnsignedIntegerNumber, _converter.Convert(CreateProperty(type))); 37 | } 38 | 39 | [TestMethod] 40 | [DataRow(typeof(short))] 41 | [DataRow(typeof(short?))] 42 | [DataRow(typeof(int))] 43 | [DataRow(typeof(int?))] 44 | [DataRow(typeof(long))] 45 | [DataRow(typeof(long?))] 46 | public void Convert_TypeIsIntegerFamily_ReturnsIntegerNumber(Type type) 47 | { 48 | Assert.AreEqual(DataTypeCode.IntegerNumber, _converter.Convert(CreateProperty(type))); 49 | } 50 | 51 | [TestMethod] 52 | [DataRow(typeof(Single))] 53 | [DataRow(typeof(Single?))] 54 | [DataRow(typeof(double))] 55 | [DataRow(typeof(double?))] 56 | [DataRow(typeof(decimal))] 57 | [DataRow(typeof(decimal?))] 58 | [DataRow(typeof(float))] 59 | [DataRow(typeof(float?))] 60 | public void Convert_TypeIsFractalFamily_ReturnsFractalNumber(Type type) 61 | { 62 | Assert.AreEqual(DataTypeCode.FractalNumber, _converter.Convert(CreateProperty(type))); 63 | } 64 | 65 | [TestMethod] 66 | [DataRow(typeof(bool))] 67 | [DataRow(typeof(bool?))] 68 | public void Convert_TypeIsBool_ReturnsBool(Type type) 69 | { 70 | Assert.AreEqual(DataTypeCode.Bool, _converter.Convert(CreateProperty(type))); 71 | } 72 | 73 | [TestMethod] 74 | [DataRow(typeof(DateTime))] 75 | [DataRow(typeof(DateTime?))] 76 | public void Convert_TypeIsDateTime_ReturnsDateTime(Type type) 77 | { 78 | Assert.AreEqual(DataTypeCode.DateTime, _converter.Convert(CreateProperty(type))); 79 | } 80 | 81 | [TestMethod] 82 | [DataRow(typeof(Guid))] 83 | [DataRow(typeof(Guid?))] 84 | public void Convert_TypeIsGuid_ReturnsGuid(Type type) 85 | { 86 | Assert.AreEqual(DataTypeCode.Guid, _converter.Convert(CreateProperty(type))); 87 | } 88 | 89 | [TestMethod] 90 | [DataRow(typeof(string))] 91 | public void Convert_TypeIsString_ReturnsString(Type type) 92 | { 93 | Assert.AreEqual(DataTypeCode.String, _converter.Convert(CreateProperty(type))); 94 | } 95 | 96 | [TestMethod] 97 | [DataRow(typeof(DataTypeCode))] 98 | [DataRow(typeof(DataTypeCode?))] 99 | public void Convert_TypeIsEnum_ReturnsEnum(Type type) 100 | { 101 | Assert.AreEqual(DataTypeCode.Enum, _converter.Convert(CreateProperty(type))); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/IndexAccessorTestFactory.cs: -------------------------------------------------------------------------------- 1 | using Structurizer; 2 | using Structurizer.Schemas; 3 | 4 | namespace UnitTests.Schemas 5 | { 6 | internal static class IndexAccessorTestFactory 7 | { 8 | private static readonly IDataTypeConverter DataTypeConverter = new DataTypeConverter(); 9 | 10 | internal static IIndexAccessor CreateFor(IStructureProperty property) 11 | { 12 | return new IndexAccessor(property, DataTypeConverter.Convert(property)); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/MemberAccessors/IndexAccessorGetValuesOnDeepGraphTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace UnitTests.Schemas.MemberAccessors 6 | { 7 | [TestClass] 8 | public class IndexAccessorGetValuesOnDeepGraphTests : UnitTests 9 | { 10 | [TestMethod] 11 | public void GetValues_WhenDeepGraphWithEnumerables_CanExtractValues() 12 | { 13 | var prodNoProperty = StructurePropertyTestFactory.GetPropertyByPath("Orders.Lines.ProductNo"); 14 | var pricesProperty = StructurePropertyTestFactory.GetPropertyByPath("Orders.Lines.Prices"); 15 | 16 | var graph = new TestCustomer 17 | { 18 | Orders = 19 | { 20 | new TestOrder 21 | { 22 | Lines = 23 | { 24 | new TestOrderLine { ProductNo = "P1", Quantity = 1, Prices = new[] { 42, 4242 }}, 25 | new TestOrderLine { ProductNo = "P2", Quantity = 2, Prices = new[] { 43, 4343 }} 26 | } 27 | } 28 | } 29 | }; 30 | 31 | var productNos = IndexAccessorTestFactory.CreateFor(prodNoProperty).GetValues(graph); 32 | var prices = IndexAccessorTestFactory.CreateFor(pricesProperty).GetValues(graph); 33 | 34 | CollectionAssert.AreEqual(new[] { "P1", "P2" }, productNos.Select(i => i.Value).ToArray()); 35 | CollectionAssert.AreEqual(new[] { 42, 4242, 43, 4343 }, prices.Select(i => i.Value).ToArray()); 36 | } 37 | 38 | private class TestCustomer 39 | { 40 | public string CustomerNo { get; set; } 41 | 42 | public List Orders { get; set; } 43 | 44 | public int[] Points { get; set; } 45 | 46 | public string[] Addresses { get; set; } 47 | 48 | public TestCustomer() 49 | { 50 | Orders = new List(); 51 | } 52 | } 53 | 54 | private class TestOrder 55 | { 56 | public List Lines { get; set; } 57 | 58 | public TestOrder() 59 | { 60 | Lines = new List(); 61 | } 62 | } 63 | 64 | private class TestOrderLine 65 | { 66 | public string ProductNo { get; set; } 67 | 68 | public int Quantity { get; set; } 69 | 70 | public int[] Prices { get; set; } 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/MemberAccessors/IndexAccessorGetValuesOnSubItemTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using FluentAssertions; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace UnitTests.Schemas.MemberAccessors 6 | { 7 | [TestClass] 8 | public class IndexAccessorGetValuesOnSubItemTests : UnitTests 9 | { 10 | [TestMethod] 11 | public void GetValues_WhenSubItemsArrayHasElementsWithValues_ReturnsTheValues() 12 | { 13 | var subItems = new[] { new SubItem { Value = "A" }, new SubItem { Value = "B" } }; 14 | var item = new Item { SubItems = subItems }; 15 | var valueProp = StructurePropertyTestFactory.GetPropertyByPath("SubItems.Value"); 16 | var indexAccessor = IndexAccessorTestFactory.CreateFor(valueProp); 17 | 18 | var values = indexAccessor.GetValues(item); 19 | 20 | CollectionAssert.AreEquivalent(new[] { "A", "B" }, values.Select(i => i.Value).ToArray()); 21 | } 22 | 23 | [TestMethod] 24 | public void GetValues_WhenSubItemsArrayHasElementsWithNullValues_DoesNotReturnTheNullValues() 25 | { 26 | var subItems = new[] { new SubItem { Value = null }, new SubItem { Value = null } }; 27 | var item = new Item { SubItems = subItems }; 28 | var valueProp = StructurePropertyTestFactory.GetPropertyByPath("SubItems.Value"); 29 | var indexAccessor = IndexAccessorTestFactory.CreateFor(valueProp); 30 | 31 | var values = indexAccessor.GetValues(item); 32 | 33 | values.Should().BeEmpty(); 34 | } 35 | 36 | [TestMethod] 37 | public void GetValues_WhenSubItemsArrayIsNull_ReturnsNull() 38 | { 39 | var item = new Item { SubItems = null }; 40 | var valueProp = StructurePropertyTestFactory.GetPropertyByPath("SubItems.Value"); 41 | var indexAccessor = IndexAccessorTestFactory.CreateFor(valueProp); 42 | 43 | var value = indexAccessor.GetValues(item); 44 | 45 | value.Should().BeNull(); 46 | } 47 | 48 | [TestMethod] 49 | public void GetValues_WhenSubItemsArrayHasBothNullAndNonNullItems_ReturnsNonNullItemsOnly() 50 | { 51 | var subItems = new[] { null, new SubItem { Value = "A" } }; 52 | var item = new Item { SubItems = subItems }; 53 | var valueProp = StructurePropertyTestFactory.GetPropertyByPath("SubItems.Value"); 54 | var indexAccessor = IndexAccessorTestFactory.CreateFor(valueProp); 55 | 56 | var value = indexAccessor.GetValues(item); 57 | 58 | value.Select(i => i.Value).Should().BeEquivalentTo(new object[] { "A" }); 59 | } 60 | 61 | [TestMethod] 62 | public void GetValues_WhenStringOnSingleSubItem_ReturnsValue() 63 | { 64 | var subItem = new SubItem { Value = "The value" }; 65 | var item = new Item { SingleSubItem = subItem }; 66 | var valueProp = StructurePropertyTestFactory.GetPropertyByPath("SingleSubItem.Value"); 67 | var indexAccessor = IndexAccessorTestFactory.CreateFor(valueProp); 68 | 69 | var value = indexAccessor.GetValues(item); 70 | 71 | value.Select(i => i.Value).Should().BeEquivalentTo(new object[] {"The value"}); 72 | } 73 | 74 | private class Item 75 | { 76 | public SubItem SingleSubItem { get; set; } 77 | 78 | public SubItem[] SubItems { get; set; } 79 | } 80 | 81 | private class SubItem 82 | { 83 | public string Value { get; set; } 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/MemberAccessors/IndexAccessorGetValuesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace UnitTests.Schemas.MemberAccessors 6 | { 7 | [TestClass] 8 | public class IndexAccessorGetValuesTests : UnitTests 9 | { 10 | [TestMethod] 11 | public void GetValues_FromAssignedString_ReturnsAssignedString() 12 | { 13 | const string initialValue = "Hello tester!"; 14 | var item = new Dummy { StringProp = initialValue }; 15 | var property = StructurePropertyTestFactory.GetPropertyByPath("StringProp"); 16 | var indexAccessor = IndexAccessorTestFactory.CreateFor(property); 17 | 18 | var retrievedValues = indexAccessor.GetValues(item); 19 | 20 | CollectionAssert.AreEqual(new[] { initialValue }, retrievedValues.Select(i => i.Value).ToArray()); 21 | } 22 | 23 | [TestMethod] 24 | public void GetValues_FromNullString_ReturnsNullString() 25 | { 26 | const string initialValue = null; 27 | var item = new Dummy { StringProp = initialValue }; 28 | var property = StructurePropertyTestFactory.GetPropertyByPath("StringProp"); 29 | var indexAccessor = IndexAccessorTestFactory.CreateFor(property); 30 | 31 | var retrievedValues = indexAccessor.GetValues(item); 32 | 33 | Assert.IsNull(retrievedValues); 34 | } 35 | 36 | [TestMethod] 37 | public void GetValues_FromAssignedInt_ReturnsAssignedInt() 38 | { 39 | const int initialValue = 12345; 40 | var item = new Dummy { IntProp = initialValue }; 41 | var property = StructurePropertyTestFactory.GetPropertyByPath("IntProp"); 42 | var indexAccessor = IndexAccessorTestFactory.CreateFor(property); 43 | 44 | var retrievedValues = indexAccessor.GetValues(item); 45 | 46 | CollectionAssert.AreEqual(new[] { initialValue }, retrievedValues.Select(i => i.Value).ToArray()); 47 | } 48 | 49 | [TestMethod] 50 | public void GetValues_FromAssignedDecimal_ReturnsAssignedDecimal() 51 | { 52 | const decimal initialValue = 12.56M; 53 | var item = new Dummy { DecimalProp = initialValue }; 54 | var property = StructurePropertyTestFactory.GetPropertyByPath("DecimalProp"); 55 | var indexAccessor = IndexAccessorTestFactory.CreateFor(property); 56 | 57 | var retrievedValues = indexAccessor.GetValues(item); 58 | 59 | CollectionAssert.AreEqual(new[] { initialValue }, retrievedValues.Select(i => i.Value).ToArray()); 60 | } 61 | 62 | [TestMethod] 63 | public void GetValues_FromAssignedNullableDecimal_ReturnsAssignedNullableDecimal() 64 | { 65 | decimal? initialValue = 13.34M; 66 | var item = new Dummy { NullableDecimalProp = initialValue }; 67 | var property = StructurePropertyTestFactory.GetPropertyByPath("NullableDecimalProp"); 68 | var indexAccessor = IndexAccessorTestFactory.CreateFor(property); 69 | 70 | var retrievedValues = indexAccessor.GetValues(item); 71 | 72 | CollectionAssert.AreEqual(new[] { initialValue.GetValueOrDefault() }, retrievedValues.Select(i => i.Value).ToArray()); 73 | } 74 | 75 | [TestMethod] 76 | public void GetValues_FromNullableDecimalWithNullValue_ReturnsNull() 77 | { 78 | decimal? initialValue = null; 79 | var item = new Dummy { NullableDecimalProp = initialValue }; 80 | var property = StructurePropertyTestFactory.GetPropertyByPath("NullableDecimalProp"); 81 | var indexAccessor = IndexAccessorTestFactory.CreateFor(property); 82 | 83 | var retrievedValues = indexAccessor.GetValues(item); 84 | 85 | Assert.IsNull(retrievedValues); 86 | } 87 | 88 | [TestMethod] 89 | public void GetValue_FromAssignedDateTime_ReturnsAssignedDateTime() 90 | { 91 | var initialValue = new DateTime(2010, 2, 3); 92 | var item = new Dummy { DateTimeProp = initialValue }; 93 | var property = StructurePropertyTestFactory.GetPropertyByPath("DateTimeProp"); 94 | var indexAccessor = IndexAccessorTestFactory.CreateFor(property); 95 | 96 | var retrievedValues = indexAccessor.GetValues(item); 97 | 98 | CollectionAssert.AreEqual(new[] { initialValue }, retrievedValues.Select(i => i.Value).ToArray()); 99 | } 100 | 101 | [TestMethod] 102 | public void GetValue_FromAssignedBool_ReturnsAssignedBool() 103 | { 104 | const bool initialValue = true; 105 | var item = new Dummy { BoolProp = initialValue }; 106 | var property = StructurePropertyTestFactory.GetPropertyByPath("BoolProp"); 107 | var indexAccessor = IndexAccessorTestFactory.CreateFor(property); 108 | 109 | var retrievedValues = indexAccessor.GetValues(item); 110 | 111 | CollectionAssert.AreEqual(new[] { initialValue }, retrievedValues.Select(i => i.Value).ToArray()); 112 | } 113 | 114 | [TestMethod] 115 | public void GetValues_FromAssignedUInt_ReturnsAssignedInt() 116 | { 117 | const uint initialValue = 12345; 118 | var item = new Dummy { UIntProp = initialValue }; 119 | var property = StructurePropertyTestFactory.GetPropertyByPath("UIntProp"); 120 | var indexAccessor = IndexAccessorTestFactory.CreateFor(property); 121 | 122 | var retrievedValues = indexAccessor.GetValues(item); 123 | 124 | CollectionAssert.AreEqual(new[] { initialValue }, retrievedValues.Select(i => i.Value).ToArray()); 125 | } 126 | 127 | private class Dummy 128 | { 129 | public string StringProp { get; set; } 130 | public int IntProp { get; set; } 131 | public uint UIntProp { get; set; } 132 | public decimal DecimalProp { get; set; } 133 | public DateTime DateTimeProp { get; set; } 134 | public bool BoolProp { get; set; } 135 | public decimal? NullableDecimalProp { get; set; } 136 | } 137 | } 138 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/PropertyPathBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Moq; 3 | using Structurizer; 4 | using Structurizer.Schemas; 5 | 6 | namespace UnitTests.Schemas 7 | { 8 | [TestClass] 9 | public class PropertyPathBuilderTests : UnitTests 10 | { 11 | [TestMethod] 12 | public void BuildPath_ForRootMember_PathContainsOnlyName() 13 | { 14 | var property = new Mock(); 15 | property.Setup(s => s.IsRootMember).Returns(true); 16 | property.Setup(s => s.Name).Returns("TheName"); 17 | 18 | var path = PropertyPathBuilder.BuildPath(property.Object); 19 | 20 | Assert.AreEqual("TheName", path); 21 | } 22 | 23 | [TestMethod] 24 | public void BuildPath_ForNestedMember_PathContainsRootAndDelimitorAndNameOfNested() 25 | { 26 | var rootProperty = new Mock(); 27 | rootProperty.Setup(s => s.IsRootMember).Returns(true); 28 | rootProperty.Setup(s => s.Name).Returns("TheRootMember"); 29 | 30 | var nestedProperty = new Mock(); 31 | nestedProperty.Setup(s => s.IsRootMember).Returns(false); 32 | nestedProperty.Setup(s => s.Parent).Returns(rootProperty.Object); 33 | nestedProperty.Setup(s => s.Name).Returns("TheNestedProperty"); 34 | 35 | var path = PropertyPathBuilder.BuildPath(nestedProperty.Object); 36 | 37 | Assert.AreEqual("TheRootMember.TheNestedProperty", path); 38 | } 39 | 40 | [TestMethod] 41 | public void BuildPath_ForRootMember_PathContainsNoRootButNestedNameAndMember() 42 | { 43 | var path = PropertyPathBuilder.BuildPath(null, "Temp"); 44 | 45 | Assert.AreEqual("Temp", path); 46 | } 47 | 48 | [TestMethod] 49 | public void BuildPath_ForNestedMember_PathContainsNoRootButNestedNameAndMember() 50 | { 51 | var property = new Mock(); 52 | property.Setup(s => s.Path).Returns("Nested"); 53 | 54 | var path = PropertyPathBuilder.BuildPath(property.Object, "Temp"); 55 | 56 | Assert.AreEqual("Nested.Temp", path); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructurePropertyTestFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Structurizer; 3 | using Structurizer.Schemas; 4 | 5 | namespace UnitTests.Schemas 6 | { 7 | internal static class StructurePropertyTestFactory 8 | { 9 | private static readonly IStructurePropertyFactory PropertyFactory = new StructurePropertyFactory(); 10 | 11 | internal static IStructureProperty GetPropertyByPath(string path) where T : class 12 | { 13 | return ReflecterFor().GetIndexableProperties(typeof(T)).Single(i => i.Path == path); 14 | } 15 | 16 | internal static IStructureProperty GetPropertyByName(string name) where T : class 17 | { 18 | return ReflecterFor().GetIndexableProperties(typeof(T)).Single(i => i.Name == name); 19 | } 20 | 21 | internal static IStructureProperty GetRawProperty(string name) where T : class 22 | { 23 | var type = typeof(T); 24 | var propertyInfo = type.GetProperty(name); 25 | 26 | return PropertyFactory.CreateRootPropertyFrom(propertyInfo); 27 | } 28 | 29 | internal static IStructureProperty GetRawProperty(string name, IStructureProperty parent) where T : class 30 | { 31 | var type = typeof(T); 32 | var propertyInfo = type.GetProperty(name); 33 | 34 | return PropertyFactory.CreateChildPropertyFrom(parent, propertyInfo); 35 | } 36 | 37 | private static IStructureTypeReflecter ReflecterFor() 38 | { 39 | return new StructureTypeReflecter(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructurePropertyTests/StructurePropertyAttributesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Structurizer; 5 | 6 | namespace UnitTests.Schemas.StructurePropertyTests 7 | { 8 | [TestClass] 9 | public class StructurePropertyAttributesTests : UnitTests 10 | { 11 | [TestMethod] 12 | public void Should_have_two_attributes_When_property_has_two_attributes() 13 | { 14 | var property = GetProperty("SomePropWithAttributes"); 15 | 16 | property.Attributes.Should().Contain(new My1Attribute()); 17 | property.Attributes.Should().Contain(new System.ComponentModel.DescriptionAttribute("test")); 18 | } 19 | 20 | [TestMethod] 21 | public void Should_have_no_attributes_When_property_has_no_properties() 22 | { 23 | var property = GetProperty("SomePropWithoutAttributes"); 24 | 25 | property.Attributes.Should().BeEmpty(); 26 | } 27 | 28 | private static IStructureProperty GetProperty(string name) where T : class 29 | { 30 | return StructurePropertyTestFactory.GetPropertyByPath(name); 31 | } 32 | 33 | private class DummyForAttributesTests 34 | { 35 | [My1, System.ComponentModel.Description("test")] 36 | public int SomePropWithAttributes { get; set; } 37 | 38 | public int SomePropWithoutAttributes { get; set; } 39 | } 40 | 41 | [AttributeUsage(AttributeTargets.Property)] 42 | public class My1Attribute : Attribute { } 43 | } 44 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructurePropertyTests/StructurePropertyEnumerableTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Structurizer; 4 | 5 | namespace UnitTests.Schemas.StructurePropertyTests 6 | { 7 | [TestClass] 8 | public class StructurePropertyEnumerableTests : UnitTests 9 | { 10 | [TestMethod] 11 | public void IsEnumerable_WhenIEnumerableOfSimpleType_ReturnsTrue() 12 | { 13 | var property = GetProperty("Ints"); 14 | 15 | Assert.IsTrue(property.IsEnumerable); 16 | } 17 | 18 | [TestMethod] 19 | public void IsEnumerable_WhenPrimitiveType_ReturnsFalse() 20 | { 21 | var property = GetProperty("Int1"); 22 | 23 | Assert.IsFalse(property.IsEnumerable); 24 | } 25 | 26 | private static IStructureProperty GetProperty(string name) where T : class 27 | { 28 | return StructurePropertyTestFactory.GetPropertyByPath(name); 29 | } 30 | 31 | private class DummyForEnumerableTests 32 | { 33 | public int Int1 { get; set; } 34 | 35 | public IEnumerable Ints { get; set; } 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructurePropertyTests/StructurePropertyGetGuidValueTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace UnitTests.Schemas.StructurePropertyTests 5 | { 6 | [TestClass] 7 | public class StructurePropertyGetGuidValueTests : UnitTests 8 | { 9 | [TestMethod] 10 | public void GetIdValue_WhenGuidOnFirstLevel_ReturnsGuid() 11 | { 12 | var expected = Guid.Parse("4217F3B7-6DEB-4DFA-B195-D111C1297988"); 13 | var item = new GuidOnRoot { Value = expected }; 14 | var property = StructurePropertyTestFactory.GetPropertyByPath("Value"); 15 | 16 | var actual = property.GetValue(item); 17 | 18 | Assert.AreEqual(expected, actual); 19 | } 20 | 21 | [TestMethod] 22 | public void GetIdValue_WhenNullableGuidOnFirstLevel_ReturnsGuid() 23 | { 24 | var expected = Guid.Parse("4217F3B7-6DEB-4DFA-B195-D111C1297988"); 25 | var item = new NullableGuidOnRoot { Value = expected }; 26 | var property = StructurePropertyTestFactory.GetPropertyByPath("Value"); 27 | 28 | var actual = property.GetValue(item); 29 | 30 | Assert.AreEqual(expected, actual); 31 | } 32 | 33 | [TestMethod] 34 | public void GetIdValue_WhenNullAssignedNullableGuidOnFirstLevel_ReturnsNull() 35 | { 36 | var item = new NullableGuidOnRoot { Value = null }; 37 | var property = StructurePropertyTestFactory.GetPropertyByPath("Value"); 38 | 39 | var actual = property.GetValue(item); 40 | 41 | Assert.IsNull(actual); 42 | } 43 | 44 | private class GuidOnRoot 45 | { 46 | public Guid Value { get; set; } 47 | } 48 | 49 | private class NullableGuidOnRoot 50 | { 51 | public Guid? Value { get; set; } 52 | } 53 | 54 | private class Container 55 | { 56 | public GuidOnRoot GuidOnRootItem { get; set; } 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructurePropertyTests/StructurePropertyGetIdentityValueTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace UnitTests.Schemas.StructurePropertyTests 4 | { 5 | [TestClass] 6 | public class StructurePropertyGetIdentityValueTests : UnitTests 7 | { 8 | [TestMethod] 9 | public void GetIdValue_WhenIntOnFirstLevel_ReturnsInt() 10 | { 11 | const int expected = 42; 12 | var item = new IdentityOnRoot { Value = expected }; 13 | var property = StructurePropertyTestFactory.GetPropertyByPath("Value"); 14 | 15 | var actual = property.GetValue(item); 16 | 17 | Assert.AreEqual(expected, actual); 18 | } 19 | 20 | [TestMethod] 21 | public void GetIdValue_WhenNullableIntOnFirstLevel_ReturnsInt() 22 | { 23 | const int expectedInt = 42; 24 | var item = new NullableIdentityOnRoot { Value = expectedInt }; 25 | var property = StructurePropertyTestFactory.GetPropertyByPath("Value"); 26 | 27 | var actual = property.GetValue(item); 28 | 29 | Assert.AreEqual(expectedInt, actual); 30 | } 31 | 32 | [TestMethod] 33 | public void GetIdValue_WhenNullAssignedNullableIntOnFirstLevel_ReturnsInt() 34 | { 35 | var item = new NullableIdentityOnRoot { Value = null }; 36 | var property = StructurePropertyTestFactory.GetPropertyByPath("Value"); 37 | 38 | var actual = property.GetValue(item); 39 | 40 | Assert.IsNull(actual); 41 | } 42 | 43 | private class IdentityOnRoot 44 | { 45 | public int Value { get; set; } 46 | } 47 | 48 | private class NullableIdentityOnRoot 49 | { 50 | public int? Value { get; set; } 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructurePropertyTests/StructurePropertyGetPrimitiveValueTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace UnitTests.Schemas.StructurePropertyTests 4 | { 5 | [TestClass] 6 | public class StructurePropertyGetPrimitiveValueTests : UnitTests 7 | { 8 | [TestMethod] 9 | public void GetValue_WhenAssignedInt_ReturnsAssignedValue() 10 | { 11 | const int expected = 33; 12 | var item = new Dummy { Int1 = expected }; 13 | var property = StructurePropertyTestFactory.GetPropertyByPath("Int1"); 14 | 15 | var actual = property.GetValue(item); 16 | 17 | Assert.AreEqual(expected, actual); 18 | } 19 | 20 | [TestMethod] 21 | public void GetValue_WhenAssignedNullableInt_ReturnsAssignedValue() 22 | { 23 | const int expected = 33; 24 | var item = new Dummy { NullableInt1 = expected }; 25 | var property = StructurePropertyTestFactory.GetPropertyByPath("NullableInt1"); 26 | 27 | var actual = property.GetValue(item); 28 | 29 | Assert.AreEqual(expected, actual); 30 | } 31 | 32 | [TestMethod] 33 | public void GetValue_WhenUnAssignedNullableInt_ReturnsNull() 34 | { 35 | var item = new Dummy { NullableInt1 = null }; 36 | var property = StructurePropertyTestFactory.GetPropertyByPath("NullableInt1"); 37 | 38 | var actual = property.GetValue(item); 39 | 40 | Assert.IsNull(actual); 41 | } 42 | 43 | [TestMethod] 44 | public void GetValue_WhenAssignedBool_ReturnsAssignedValue() 45 | { 46 | const bool expected = true; 47 | var item = new Dummy { Bool1 = expected }; 48 | var property = StructurePropertyTestFactory.GetPropertyByPath("Bool1"); 49 | 50 | var actual = property.GetValue(item); 51 | 52 | Assert.AreEqual(expected, actual); 53 | } 54 | 55 | [TestMethod] 56 | public void GetValue_WhenAssignedNullableBool_ReturnsAssignedValue() 57 | { 58 | const bool expected = true; 59 | var item = new Dummy { NullableBool1 = expected }; 60 | var property = StructurePropertyTestFactory.GetPropertyByPath("NullableBool1"); 61 | 62 | var actual = property.GetValue(item); 63 | 64 | Assert.AreEqual(expected, actual); 65 | } 66 | 67 | [TestMethod] 68 | public void GetValue_WhenUnAssignedNullableBool_ReturnsNull() 69 | { 70 | var item = new Dummy { NullableBool1 = null }; 71 | var property = StructurePropertyTestFactory.GetPropertyByPath("NullableBool1"); 72 | 73 | var actual = property.GetValue(item); 74 | 75 | Assert.IsNull(actual); 76 | } 77 | 78 | [TestMethod] 79 | public void GetValue_WhenAssignedDecimal_ReturnsAssignedValue() 80 | { 81 | const decimal expected = 1.33m; 82 | var item = new Dummy { Decimal1 = expected }; 83 | var property = StructurePropertyTestFactory.GetPropertyByPath("Decimal1"); 84 | 85 | var actual = property.GetValue(item); 86 | 87 | Assert.AreEqual(expected, actual); 88 | } 89 | 90 | [TestMethod] 91 | public void GetValue_WhenAssignedNullableDecimal_ReturnsAssignedValue() 92 | { 93 | const decimal expected = 1.33m; 94 | var item = new Dummy { NullableDecimal1 = expected }; 95 | var property = StructurePropertyTestFactory.GetPropertyByPath("NullableDecimal1"); 96 | 97 | var actual = property.GetValue(item); 98 | 99 | Assert.AreEqual(expected, actual); 100 | } 101 | 102 | [TestMethod] 103 | public void GetValue_WhenUnAssignedNullableDecimal_ReturnsNull() 104 | { 105 | var item = new Dummy { NullableDecimal1 = null }; 106 | var property = StructurePropertyTestFactory.GetPropertyByPath("NullableDecimal1"); 107 | 108 | var actual = property.GetValue(item); 109 | 110 | Assert.IsNull(actual); 111 | } 112 | 113 | [TestMethod] 114 | public void GetValue_WhenAssignedUInt_ReturnsAssignedValue() 115 | { 116 | const uint expected = 33; 117 | var item = new Dummy { UInt1 = expected }; 118 | var property = StructurePropertyTestFactory.GetPropertyByPath("UInt1"); 119 | 120 | var actual = property.GetValue(item); 121 | 122 | Assert.AreEqual(expected, actual); 123 | } 124 | 125 | [TestMethod] 126 | public void GetValue_WhenAssignedNullableUInt_ReturnsAssignedValue() 127 | { 128 | const uint expected = 33; 129 | var item = new Dummy { NullableUInt1 = expected }; 130 | var property = StructurePropertyTestFactory.GetPropertyByPath("NullableUInt1"); 131 | 132 | var actual = property.GetValue(item); 133 | 134 | Assert.AreEqual(expected, actual); 135 | } 136 | 137 | [TestMethod] 138 | public void GetValue_WhenUnAssignedNullableUInt_ReturnsNull() 139 | { 140 | var item = new Dummy { NullableUInt1 = null }; 141 | var property = StructurePropertyTestFactory.GetPropertyByPath("NullableUInt1"); 142 | 143 | var actual = property.GetValue(item); 144 | 145 | Assert.IsNull(actual); 146 | } 147 | 148 | private class Dummy 149 | { 150 | public int Int1 { get; set; } 151 | public uint UInt1 { get; set; } 152 | public bool Bool1 { get; set; } 153 | public decimal Decimal1 { get; set; } 154 | public int? NullableInt1 { get; set; } 155 | public uint? NullableUInt1 { get; set; } 156 | public bool? NullableBool1 { get; set; } 157 | public decimal? NullableDecimal1 { get; set; } 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructurePropertyTests/StructurePropertyGetStringValueTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace UnitTests.Schemas.StructurePropertyTests 6 | { 7 | [TestClass] 8 | public class StructurePropertyGetStringValueTests : UnitTests 9 | { 10 | [TestMethod] 11 | public void GetValue_WhenSingleStringMember_SingleValueIsReturned() 12 | { 13 | var property = StructurePropertyTestFactory.GetPropertyByPath("CustomerNo"); 14 | 15 | var customer = new TestCustomer { CustomerNo = "1234" }; 16 | var customerNos = (string)property.GetValue(customer); 17 | 18 | Assert.AreEqual("1234", customerNos); 19 | } 20 | 21 | [TestMethod] 22 | public void GetValue_WhenArrayOfInt_ReturnsAValueArray() 23 | { 24 | var property = StructurePropertyTestFactory.GetPropertyByPath("Points"); 25 | 26 | var container = new TestCustomer { Points = new[] { 5, 4, 3, 2, 1 } }; 27 | var values = (IEnumerable)property.GetValue(container); 28 | 29 | CollectionAssert.AreEqual(new[] { 5, 4, 3, 2, 1 }, values.ToArray()); 30 | } 31 | 32 | private class TestCustomer 33 | { 34 | public string CustomerNo { get; set; } 35 | 36 | public List Orders { get; set; } 37 | 38 | public int[] Points { get; set; } 39 | 40 | public string[] Addresses { get; set; } 41 | 42 | public TestCustomer() 43 | { 44 | Orders = new List(); 45 | } 46 | } 47 | 48 | private class TestOrder 49 | { 50 | public List Lines { get; set; } 51 | 52 | public TestOrder() 53 | { 54 | Lines = new List(); 55 | } 56 | } 57 | 58 | private class TestOrderLine 59 | { 60 | public string ProductNo { get; set; } 61 | public int Quantity { get; set; } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructureSchemaFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | using Structurizer; 6 | 7 | namespace UnitTests.Schemas 8 | { 9 | [TestClass] 10 | public class StructureSchemaFactoryTests : UnitTests 11 | { 12 | private static IStructureSchemaFactory CreateSut(IDataTypeConverter converter = null) 13 | => new StructureSchemaFactory(converter ?? Mock.Of()); 14 | 15 | //Dear lord, give me strength to instead fully mock the hiearchy of... I tried, but I gave up 16 | private IStructureType GetStructureTypeFor() where T : class => StructureTypeTestFactory.CreateFor(); 17 | 18 | [TestMethod] 19 | public void Should_create_schema_for_passed_type() 20 | { 21 | var structureType = GetStructureTypeFor(); 22 | 23 | var schema = CreateSut().CreateSchema(structureType); 24 | 25 | Assert.AreEqual(structureType.Type, schema.StructureType.Type); 26 | } 27 | 28 | [TestMethod] 29 | public void Should_throw_When_structure_type_contains_no_index_accessors() 30 | { 31 | var structureType = GetStructureTypeFor(); 32 | 33 | Action action = () => CreateSut().CreateSchema(structureType); 34 | 35 | action 36 | .Should().Throw() 37 | .WithMessage(string.Format(StructurizerExceptionMessages.AutoSchemaBuilder_MissingIndexableMembers, structureType.Name)); 38 | } 39 | 40 | [TestMethod] 41 | public void Should_create_index_accessors_for_each_indexable_property() 42 | { 43 | var structureType = GetStructureTypeFor(); 44 | 45 | var schema = CreateSut().CreateSchema(structureType); 46 | schema.IndexAccessors.Should().HaveCount(2); 47 | } 48 | 49 | [TestMethod] 50 | public void Should_use_data_type_converter_When_creating_index_accessors() 51 | { 52 | var dataTypeConverterMock = new Mock(); 53 | var structureType = GetStructureTypeFor(); 54 | 55 | CreateSut(dataTypeConverterMock.Object).CreateSchema(structureType); 56 | 57 | dataTypeConverterMock.Verify(m => m.Convert(It.IsAny()), Times.Exactly(2)); 58 | } 59 | 60 | private class WithNoProperties { } 61 | 62 | private class WithTwoProperties 63 | { 64 | public int SomeIntProp { get; set; } 65 | public string SomeStringProp { get; set; } 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructureSchemaTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using FluentAssertions; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Moq; 5 | using Structurizer; 6 | using Structurizer.Schemas; 7 | 8 | namespace UnitTests.Schemas 9 | { 10 | public class StructureSchemaTests : UnitTests 11 | { 12 | [TestMethod] 13 | public void Should_initialize_type_and_name() 14 | { 15 | var structureType = new StructureType(typeof(Foo)); 16 | 17 | var schema = new StructureSchema(structureType); 18 | 19 | schema.StructureType.Type.Should().Be(typeof(Foo)); 20 | schema.Name.Should().Be(typeof(Foo).Name); 21 | } 22 | 23 | [TestMethod] 24 | public void Should_assign_empty_index_accessors_When_not_passed() 25 | { 26 | var structureType = new StructureType(typeof(Foo)); 27 | 28 | var schema = new StructureSchema(structureType); 29 | 30 | schema.IndexAccessors.Should().BeEmpty(); 31 | } 32 | 33 | [TestMethod] 34 | public void Should_assign_index_accessors_When_present() 35 | { 36 | var structureType = new StructureType(typeof(Foo)); 37 | 38 | var schema = new StructureSchema(structureType, new List { Mock.Of() }); 39 | 40 | schema.IndexAccessors.Should().HaveCount(1); 41 | } 42 | 43 | private class Foo 44 | { 45 | public int SomeInt { get; set; } 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructureTypeFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Moq; 4 | using Structurizer; 5 | 6 | namespace UnitTests.Schemas 7 | { 8 | [TestClass] 9 | public class StructureTypeFactoryTests : UnitTests 10 | { 11 | [TestMethod] 12 | public void Should_use_reflecter_to_get_all_indexable_properties_When_no_explicit_excludes_exists() 13 | { 14 | var typeConfigStub = new Mock(); 15 | var typeConfig = typeConfigStub.Object; 16 | typeConfigStub.Setup(m => m.Type).Returns(typeof(MyClass)); 17 | typeConfigStub.Setup(m => m.IndexMode).Returns(IndexMode.Exclusive); 18 | typeConfigStub.Setup(m => m.MemberPaths).Returns(new List()); 19 | 20 | var reflecterMock = new Mock(); 21 | 22 | var factory = new StructureTypeFactory(reflecterMock.Object); 23 | factory.CreateFor(typeConfig); 24 | 25 | reflecterMock.Verify(m => m.GetIndexableProperties(typeConfig.Type)); 26 | } 27 | 28 | [TestMethod] 29 | public void Should_use_reflecter_to_get_indexable_properties_except_excluded_ones_When_excludes_exists() 30 | { 31 | var typeConfigStub = new Mock(); 32 | var typeConfig = typeConfigStub.Object; 33 | typeConfigStub.Setup(m => m.Type).Returns(typeof(MyClass)); 34 | typeConfigStub.Setup(m => m.IndexMode).Returns(IndexMode.Exclusive); 35 | typeConfigStub.Setup(m => m.MemberPaths).Returns(new List { "FooBeingExcluded" }); 36 | 37 | var reflecterMock = new Mock(); 38 | 39 | var factory = new StructureTypeFactory(reflecterMock.Object); 40 | factory.CreateFor(typeConfig); 41 | 42 | reflecterMock.Verify(m => m.GetIndexablePropertiesExcept(typeConfig.Type, It.IsAny>())); 43 | } 44 | 45 | [TestMethod] 46 | public void Should_use_reflecter_to_get_specific_indexable_properties_When_includes_exists() 47 | { 48 | var typeConfigStub = new Mock(); 49 | var typeConfig = typeConfigStub.Object; 50 | typeConfigStub.Setup(m => m.Type).Returns(typeof(MyClass)); 51 | typeConfigStub.Setup(m => m.IndexMode).Returns(IndexMode.Inclusive); 52 | typeConfigStub.Setup(m => m.MemberPaths).Returns(new List { "FooBeingIncluded" }); 53 | 54 | var reflecterMock = new Mock(); 55 | 56 | var factory = new StructureTypeFactory(reflecterMock.Object); 57 | factory.CreateFor(typeConfig); 58 | 59 | reflecterMock.Verify(m => m.GetSpecificIndexableProperties(typeConfig.Type, It.IsAny>())); 60 | } 61 | 62 | private class MyClass { } 63 | } 64 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructureTypeReflecterTests/StructureTypeReflecterComplexIndexablePropertiesTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace UnitTests.Schemas.StructureTypeReflecterTests 6 | { 7 | [TestClass] 8 | public class StructureTypeReflecterComplexIndexablePropertiesTests : StructureTypeReflecterTestsBase 9 | { 10 | [TestMethod] 11 | public void GetIndexableProperties_WhenItemWithComplexProperty_ReturnsComplexProperties() 12 | { 13 | var properties = ReflecterFor().GetIndexableProperties(typeof(WithComplexProperty)); 14 | 15 | Assert.AreEqual(2, properties.Count()); 16 | Assert.IsTrue(properties.Any(p => p.Path == "Complex.IntValue")); 17 | Assert.IsTrue(properties.Any(p => p.Path == "Complex.StringValue")); 18 | } 19 | 20 | [TestMethod] 21 | public void GetIndexableProperties_WhenRootWithEnumerable_EnumerableMemberIsNotReturnedAsComplex() 22 | { 23 | var properties = ReflecterFor().GetIndexableProperties(typeof(WithEnumerableOfComplex)); 24 | 25 | Assert.AreEqual(2, properties.Count()); 26 | Assert.IsTrue(properties.Any(p => p.Path == "Items.IntValue")); 27 | Assert.IsTrue(properties.Any(p => p.Path == "Items.StringValue")); 28 | } 29 | 30 | private class WithComplexProperty 31 | { 32 | public Item Complex { get; set; } 33 | } 34 | 35 | private class WithEnumerableOfComplex 36 | { 37 | public IEnumerable Items { get; set; } 38 | } 39 | 40 | private class Item 41 | { 42 | public int IntValue { get; set; } 43 | public string StringValue { get; set; } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructureTypeReflecterTests/StructureTypeReflecterEnumerableIndexablePropertiesTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace UnitTests.Schemas.StructureTypeReflecterTests 7 | { 8 | [TestClass] 9 | public class StructureTypeReflecterEnumerableIndexablePropertiesTests : StructureTypeReflecterTestsBase 10 | { 11 | [TestMethod] 12 | public void GetIndexableProperties_WhenIListOfTIndexesExists_ReturnsTheElementMembers() 13 | { 14 | var props = ReflecterFor().GetIndexableProperties(typeof(WithCollectionIndexes)); 15 | 16 | Assert.IsTrue(props.Any(p => p.Path == "IList1.ElementInt1")); 17 | } 18 | 19 | [TestMethod] 20 | public void GetIndexableProperties_WhenIEnumerableOfTIndexesExists_ReturnsTheElementMembers() 21 | { 22 | var props = ReflecterFor().GetIndexableProperties(typeof(WithCollectionIndexes)); 23 | 24 | Assert.IsTrue(props.Any(p => p.Path == "IEnumerable1.ElementInt1")); 25 | } 26 | 27 | [TestMethod] 28 | public void GetIndexableProperties_WhenEnumerableOfBytes_NoPropertiesAreReturned() 29 | { 30 | var props = ReflecterFor().GetIndexableProperties(typeof(WithEnumerableBytes)); 31 | 32 | Assert.IsFalse(props.Any()); 33 | } 34 | 35 | [TestMethod] 36 | public void GetIndexableProperties_WhenArrayOfStrings_OnlyReturnsPropertyForAccessingTheStringArray() 37 | { 38 | var properties = ReflecterFor().GetIndexableProperties(typeof(WithArrayOfStrings)); 39 | var arrayIndex = properties.SingleOrDefault(p => p.Path == "Values"); 40 | 41 | Assert.AreEqual(1, properties.Count()); 42 | Assert.IsNotNull(arrayIndex); 43 | } 44 | 45 | [TestMethod] 46 | public void GetIndexableProperties_WhenArrayOfIntegers_OnlyReturnsPropertyForAccessingTheStringArray() 47 | { 48 | var properties = ReflecterFor().GetIndexableProperties(typeof(WithArrayOfIntegers)); 49 | var arrayIndex = properties.SingleOrDefault(p => p.Path == "Values"); 50 | 51 | Assert.AreEqual(1, properties.Count()); 52 | Assert.IsNotNull(arrayIndex); 53 | } 54 | 55 | [TestMethod] 56 | public void GetIndexableProperties_WhenWithNestedArrayOfStrings_OnlyReturnsPropertyForAccessingTheStringArray() 57 | { 58 | var properties = ReflecterFor().GetIndexableProperties(typeof(WithNestedArrayOfStrings)); 59 | var arrayIndex = properties.SingleOrDefault(p => p.Path == "Item.Values"); 60 | 61 | Assert.AreEqual(1, properties.Count()); 62 | Assert.IsNotNull(arrayIndex); 63 | } 64 | 65 | [TestMethod] 66 | public void GetIndexableProperties_WhenWithArrayOfNestedArrayOfStrings_OnlyReturnsPropertyForAccessingTheStringArray() 67 | { 68 | var properties = ReflecterFor().GetIndexableProperties(typeof(WithArrayOfNestedArrayOfStrings)); 69 | var arrayIndex = properties.SingleOrDefault(p => p.Path == "Items.Values"); 70 | 71 | Assert.AreEqual(1, properties.Count()); 72 | Assert.IsNotNull(arrayIndex); 73 | } 74 | 75 | private class WithEnumerableBytes 76 | { 77 | public byte[] Bytes1 { get; set; } 78 | public IEnumerable Bytes2 { get; set; } 79 | public IList Bytes3 { get; set; } 80 | public List Bytes4 { get; set; } 81 | public ICollection Bytes5 { get; set; } 82 | public Collection Bytes6 { get; set; } 83 | } 84 | 85 | private class WithCollectionIndexes 86 | { 87 | public IEnumerable IEnumerable1 { get; set; } 88 | public IList IList1 { get; set; } 89 | } 90 | 91 | private class Element 92 | { 93 | public int ElementInt1 { get; set; } 94 | } 95 | 96 | private class WithArrayOfStrings 97 | { 98 | public string[] Values { get; set; } 99 | } 100 | 101 | private class WithArrayOfIntegers 102 | { 103 | public int[] Values { get; set; } 104 | } 105 | 106 | private class WithNestedArrayOfStrings 107 | { 108 | public WithArrayOfStrings Item { get; set; } 109 | } 110 | 111 | private class WithArrayOfNestedArrayOfStrings 112 | { 113 | public WithArrayOfStrings[] Items { get; set; } 114 | } 115 | } 116 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructureTypeReflecterTests/StructureTypeReflecterGetIndexablePropertiesExceptTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace UnitTests.Schemas.StructureTypeReflecterTests 7 | { 8 | [TestClass] 9 | public class StructureTypeReflecterGetIndexablePropertiesExceptTests : StructureTypeReflecterTestsBase 10 | { 11 | [TestMethod] 12 | public void GetIndexablePropertiesExcept_WhenCalledWithNullExlcudes_ThrowsArgumentException() 13 | { 14 | Action a = () => ReflecterFor().GetIndexablePropertiesExcept(typeof(Item), null); 15 | 16 | a.Should().Throw().Where(ex => ex.ParamName == "memberPaths"); 17 | } 18 | 19 | [TestMethod] 20 | public void GetIndexablePropertiesExcept_WhenCalledWithNoExlcudes_ThrowsArgumentNullException() 21 | { 22 | Action a = () => ReflecterFor().GetIndexablePropertiesExcept(typeof(Item), new string[] { }); 23 | 24 | a.Should().Throw().Where(ex => ex.ParamName == "memberPaths"); 25 | } 26 | 27 | [TestMethod] 28 | public void GetIndexablePropertiesExcept_WhenBytesArrayExists_PropertyIsNotReturned() 29 | { 30 | var properties = ReflecterFor().GetIndexablePropertiesExcept(typeof(Item), new[] { "" }); 31 | 32 | Assert.IsNull(properties.SingleOrDefault(p => p.Path == "Bytes1")); 33 | } 34 | 35 | [TestMethod] 36 | public void GetIndexablePropertiesExcept_WhenExcludingAllProperties_NoPropertiesAreReturned() 37 | { 38 | var properties = ReflecterFor().GetIndexablePropertiesExcept(typeof(Item), new[] { "Bool1", "DateTime1", "String1", "Nested", "Nested.Int1OnNested", "Nested.String1OnNested" }); 39 | 40 | Assert.AreEqual(0, properties.Count()); 41 | } 42 | 43 | [TestMethod] 44 | public void GetIndexablePropertiesExcept_WhenExcludingComplexNested_NoNestedPropertiesAreReturned() 45 | { 46 | var properties = ReflecterFor().GetIndexablePropertiesExcept(typeof(Item), new[] { "Nested" }); 47 | 48 | Assert.AreEqual(0, properties.Count(p => p.Path.StartsWith("Nested"))); 49 | } 50 | 51 | [TestMethod] 52 | public void GetIndexablePropertiesExcept_WhenExcludingNestedSimple_OtherSimpleNestedPropertiesAreReturned() 53 | { 54 | var properties = ReflecterFor().GetIndexablePropertiesExcept(typeof(Item), new[] { "Nested.String1OnNested" }); 55 | 56 | Assert.AreEqual(1, properties.Count(p => p.Path.StartsWith("Nested"))); 57 | Assert.AreEqual(1, properties.Count(p => p.Path == "Nested.Int1OnNested")); 58 | } 59 | 60 | private class Item 61 | { 62 | public bool Bool1 { get; set; } 63 | public DateTime DateTime1 { get; set; } 64 | public string String1 { get; set; } 65 | public byte[] Bytes1 { get; set; } 66 | public Nested Nested { get; set; } 67 | } 68 | 69 | private class Nested 70 | { 71 | public int Int1OnNested { get; set; } 72 | public string String1OnNested { get; set; } 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructureTypeReflecterTests/StructureTypeReflecterGetSpecificIndexablePropertiesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | 6 | namespace UnitTests.Schemas.StructureTypeReflecterTests 7 | { 8 | [TestClass] 9 | public class StructureTypeReflecterGetSpecificIndexablePropertiesTests : StructureTypeReflecterTestsBase 10 | { 11 | [TestMethod] 12 | public void GetSpecificIndexableProperties_WhenCalledWithNullExlcudes_ThrowsArgumentException() 13 | { 14 | Action a = () => ReflecterFor().GetSpecificIndexableProperties(typeof(TestItem), null); 15 | 16 | a.Should().Throw().Where(ex => ex.ParamName == "memberPaths"); 17 | } 18 | 19 | [TestMethod] 20 | public void GetSpecificIndexableProperties_WhenCalledWithNoExlcudes_ThrowsArgumentException() 21 | { 22 | Action a = () => ReflecterFor().GetSpecificIndexableProperties(typeof(TestItem), new string[] { }); 23 | 24 | a.Should().Throw().Where(ex => ex.ParamName == "memberPaths"); 25 | } 26 | 27 | [TestMethod] 28 | public void GetSpecificIndexableProperties_WhenIncludingBytesArray_PropertyIsNotReturned() 29 | { 30 | var properties = ReflecterFor().GetSpecificIndexableProperties(typeof(TestItem), new[] { "Bytes1" }); 31 | 32 | Assert.AreEqual(0, properties.Length); 33 | Assert.IsNull(properties.SingleOrDefault(p => p.Path == "Bytes1")); 34 | } 35 | 36 | [TestMethod] 37 | public void GetSpecificIndexableProperties_WhenIncludingProperty_PropertyIsReturned() 38 | { 39 | var properties = ReflecterFor().GetSpecificIndexableProperties(typeof(TestItem), new[] { "Bool1" }); 40 | 41 | Assert.AreEqual(1, properties.Length); 42 | Assert.IsNotNull(properties.SingleOrDefault(p => p.Path == "Bool1")); 43 | } 44 | 45 | [TestMethod] 46 | public void GetSpecificIndexableProperties_WhenIncludingNestedProperty_PropertyIsReturned() 47 | { 48 | var properties = ReflecterFor().GetSpecificIndexableProperties(typeof(TestItem), new[] { "Nested", "Nested.Int1OnNested" }); 49 | 50 | Assert.AreEqual(1, properties.Length); 51 | Assert.IsNotNull(properties.SingleOrDefault(p => p.Path == "Nested.Int1OnNested")); 52 | } 53 | 54 | private class TestItem 55 | { 56 | public int Int1 { get; set; } 57 | public bool Bool1 { get; set; } 58 | public DateTime DateTime1 { get; set; } 59 | public string String1 { get; set; } 60 | public byte[] Bytes1 { get; set; } 61 | public Nested Nested { get; set; } 62 | } 63 | 64 | private class Nested 65 | { 66 | public int Int1OnNested { get; set; } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructureTypeReflecterTests/StructureTypeReflecterSimpleIndexablePropertiesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace UnitTests.Schemas.StructureTypeReflecterTests 6 | { 7 | [TestClass] 8 | public class StructureTypeReflecterSimpleIndexablePropertiesTests : StructureTypeReflecterTestsBase 9 | { 10 | [TestMethod] 11 | public void GetIndexableProperties_WhenMultiplePublicSimplePropertiesExistsAndNoExclusions_ReturnsAllPublicSimpleProperties() 12 | { 13 | var properties = ReflecterFor().GetIndexableProperties(typeof(WithSimpleProperties)); 14 | 15 | var paths = properties.Select(p => p.Path).ToArray(); 16 | Assert.AreEqual(9, properties.Count()); 17 | CollectionAssert.Contains(paths, "GuidValue"); 18 | CollectionAssert.Contains(paths, "ShortValue"); 19 | CollectionAssert.Contains(paths, "IntValue"); 20 | CollectionAssert.Contains(paths, "LongValue"); 21 | CollectionAssert.Contains(paths, "StringValue"); 22 | CollectionAssert.Contains(paths, "DecimalValue"); 23 | CollectionAssert.Contains(paths, "DateTimeValue"); 24 | CollectionAssert.Contains(paths, "BoolValue"); 25 | CollectionAssert.Contains(paths, "ByteValue"); 26 | } 27 | 28 | [TestMethod] 29 | public void GetIndexableProperties_WhenMultiplePublicSimpleNullablePropertiesExistsAndNoExclusions_ReturnsAllPublicSimpleProperties() 30 | { 31 | var properties = ReflecterFor().GetIndexableProperties(typeof(WithSimpleNullableProperties)); 32 | 33 | var paths = properties.Select(p => p.Path).ToArray(); 34 | Assert.AreEqual(8, properties.Count()); 35 | CollectionAssert.Contains(paths, "GuidValue"); 36 | CollectionAssert.Contains(paths, "ShortValue"); 37 | CollectionAssert.Contains(paths, "IntValue"); 38 | CollectionAssert.Contains(paths, "LongValue"); 39 | CollectionAssert.Contains(paths, "DecimalValue"); 40 | CollectionAssert.Contains(paths, "DateTimeValue"); 41 | CollectionAssert.Contains(paths, "BoolValue"); 42 | CollectionAssert.Contains(paths, "ByteValue"); 43 | } 44 | 45 | [TestMethod] 46 | public void GetIndexableProperties_WhenSimplePrivatePropertyExists_PrivatePropertyIsNotReturned() 47 | { 48 | var properties = ReflecterFor().GetIndexableProperties(typeof(WithPrivateProperty)); 49 | 50 | Assert.AreEqual(0, properties.Count()); 51 | } 52 | 53 | private class WithSimpleProperties 54 | { 55 | public Guid GuidValue { get; set; } 56 | public short ShortValue { get; set; } 57 | public int IntValue { get; set; } 58 | public long LongValue { get; set; } 59 | public string StringValue { get; set; } 60 | public DateTime DateTimeValue { get; set; } 61 | public decimal DecimalValue { get; set; } 62 | public bool BoolValue { get; set; } 63 | public byte ByteValue { get; set; } 64 | } 65 | 66 | private class WithSimpleNullableProperties 67 | { 68 | public Guid? GuidValue { get; set; } 69 | public short? ShortValue { get; set; } 70 | public int? IntValue { get; set; } 71 | public long? LongValue { get; set; } 72 | public DateTime? DateTimeValue { get; set; } 73 | public decimal? DecimalValue { get; set; } 74 | public bool? BoolValue { get; set; } 75 | public byte? ByteValue { get; set; } 76 | } 77 | 78 | private class WithPrivateProperty 79 | { 80 | private int Int { get; set; } 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /src/UnitTests/Schemas/StructureTypeReflecterTests/StructureTypeReflecterTestsBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Structurizer; 3 | using Structurizer.Schemas; 4 | 5 | namespace UnitTests.Schemas.StructureTypeReflecterTests 6 | { 7 | [TestClass] 8 | public abstract class StructureTypeReflecterTestsBase : UnitTests 9 | { 10 | protected IStructureTypeReflecter ReflecterFor() 11 | { 12 | return new StructureTypeReflecter(); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/UnitTests/StructureBuilderTests/StructureBuilderBaseTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Structurizer; 3 | 4 | namespace UnitTests.StructureBuilderTests 5 | { 6 | [TestClass] 7 | public abstract class StructureBuilderBaseTests : UnitTests 8 | { 9 | protected IStructureBuilder Builder; 10 | } 11 | } -------------------------------------------------------------------------------- /src/UnitTests/StructureBuilderTests/StructureBuilderGraphTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using Structurizer; 3 | 4 | namespace UnitTests.StructureBuilderTests 5 | { 6 | [TestClass] 7 | public class StructureBuilderGraphTests : StructureBuilderBaseTests 8 | { 9 | public StructureBuilderGraphTests() 10 | { 11 | Builder = StructureBuilder.Create(c => c.Register()); 12 | } 13 | 14 | [TestMethod] 15 | public void CreateStructure_WhenNestedItemExists_NestedWillBePartOfStructure() 16 | { 17 | var item = new Root { IntOnRoot = 142, Nested = new Child { IntOnChild = 242 } }; 18 | 19 | var structure = Builder.CreateStructure(item); 20 | 21 | Assert.AreEqual(2, structure.Indexes.Count); 22 | Assert.AreEqual("IntOnRoot", structure.Indexes[0].Path); 23 | Assert.AreEqual("Nested.IntOnChild", structure.Indexes[1].Path); 24 | } 25 | 26 | private class Root 27 | { 28 | public int IntOnRoot { get; set; } 29 | public Child Nested { get; set; } 30 | } 31 | 32 | private class Child 33 | { 34 | public int IntOnChild { get; set; } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/UnitTests/StructureBuilderTests/StructureBuilderIncludeAndExcludeTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Structurizer; 4 | 5 | namespace UnitTests.StructureBuilderTests 6 | { 7 | [TestClass] 8 | public class StructureBuilderIncludeAndExcludeTests : StructureBuilderBaseTests 9 | { 10 | [TestMethod] 11 | public void Should_be_able_to_index_nested_child_by_registrating_child_only() 12 | { 13 | Builder = StructureBuilder.Create(c => c.Register(cfg => cfg 14 | .UsingIndexMode(IndexMode.Inclusive) 15 | .Members(e => e.OneChild.GrandChild.SomeInt))); 16 | 17 | var item = new MyRoot 18 | { 19 | SomeString = "Foo Bar", 20 | SomeInt = 1, 21 | OneChild = new MyChild 22 | { 23 | SomeString = "One child", 24 | SomeInt = 2, 25 | GrandChild = new MyGrandChild 26 | { 27 | SomeString = "Grand child 2.1", 28 | SomeInt = 21 29 | } 30 | }, 31 | ManyChildren = new List 32 | { 33 | new MyChild 34 | { 35 | SomeString = "List Child1", 36 | SomeInt = 3, 37 | GrandChild = new MyGrandChild 38 | { 39 | SomeString = "Grand child 3.1", 40 | SomeInt = 31 41 | } 42 | }, 43 | new MyChild 44 | { 45 | SomeString = "List Child2", 46 | SomeInt = 4, 47 | GrandChild = new MyGrandChild 48 | { 49 | SomeString = "Grand child 4.1", 50 | SomeInt = 41 51 | } 52 | } 53 | } 54 | }; 55 | 56 | var structure = Builder.CreateStructure(item); 57 | 58 | Assert.AreEqual(1, structure.Indexes.Count); 59 | } 60 | 61 | private class MyRoot 62 | { 63 | public string SomeString { get; set; } 64 | public int SomeInt { get; set; } 65 | public MyChild OneChild { get; set; } 66 | public List ManyChildren { get; set; } 67 | } 68 | 69 | private class MyChild 70 | { 71 | public MyGrandChild GrandChild { get; set; } 72 | public string SomeString { get; set; } 73 | public int SomeInt { get; set; } 74 | } 75 | 76 | private class MyGrandChild 77 | { 78 | public string SomeString { get; set; } 79 | public int SomeInt { get; set; } 80 | } 81 | } 82 | } -------------------------------------------------------------------------------- /src/UnitTests/StructureBuilderTests/StructureBuilderNullablesTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FluentAssertions; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using Structurizer; 5 | 6 | namespace UnitTests.StructureBuilderTests 7 | { 8 | [TestClass] 9 | public class StructureBuilderNullablesTests : StructureBuilderBaseTests 10 | { 11 | [TestMethod] 12 | public void CreateStructure_WhenItemWithNullablesHasValues_IndexesAreCreated() 13 | { 14 | Builder = StructureBuilder.Create(c => c.Register()); 15 | var item = TestItemWithNullables.CreatePopulated(); 16 | 17 | var structure = Builder.CreateStructure(item); 18 | 19 | Assert.AreEqual(2, structure.Indexes.Count); 20 | 21 | Assert.AreEqual("NullableInt", structure.Indexes[0].Path); 22 | Assert.AreEqual(typeof(int?), structure.Indexes[0].DataType); 23 | Assert.AreEqual(item.NullableInt, structure.Indexes[0].Value); 24 | 25 | Assert.AreEqual("NullableGuid", structure.Indexes[1].Path); 26 | Assert.AreEqual(typeof(Guid?), structure.Indexes[1].DataType); 27 | Assert.AreEqual(item.NullableGuid, structure.Indexes[1].Value); 28 | } 29 | 30 | [TestMethod] 31 | public void CreateStructure_WhenItemWithNullablesHasNullValues_NoIndexesAreCreated() 32 | { 33 | Builder = StructureBuilder.Create(c => c.Register()); 34 | var item = TestItemWithNullables.CreateWithNullValues(); 35 | 36 | var structure = Builder.CreateStructure(item); 37 | 38 | structure.Indexes.Should().BeEmpty(); 39 | } 40 | 41 | [TestMethod] 42 | public void CreateStructure_WhenChildItemWithInheritedNullablesHasValues_IndexesAreCreated() 43 | { 44 | Builder = StructureBuilder.Create(c => c.Register()); 45 | var item = ChildWithNullables.CreatePopulated(); 46 | 47 | var structure = Builder.CreateStructure(item); 48 | 49 | Assert.AreEqual(2, structure.Indexes.Count); 50 | 51 | Assert.AreEqual("NullableInt", structure.Indexes[0].Path); 52 | Assert.AreEqual(typeof(int?), structure.Indexes[0].DataType); 53 | Assert.AreEqual(item.NullableInt, structure.Indexes[0].Value); 54 | 55 | Assert.AreEqual("NullableGuid", structure.Indexes[1].Path); 56 | Assert.AreEqual(typeof(Guid?), structure.Indexes[1].DataType); 57 | Assert.AreEqual(item.NullableGuid, structure.Indexes[1].Value); 58 | } 59 | 60 | [TestMethod] 61 | public void CreateStructure_WhenChildItemWithInheritedNullablesHasNullValues_NoIndexesAreCreated() 62 | { 63 | Builder = StructureBuilder.Create(c => c.Register()); 64 | var item = ChildWithNullables.CreateWithNullValues(); 65 | 66 | var structure = Builder.CreateStructure(item); 67 | 68 | structure.Indexes.Should().BeEmpty(); 69 | } 70 | 71 | private abstract class RootWithNullables 72 | { 73 | public int? NullableInt { get; set; } 74 | public Guid? NullableGuid { get; set; } 75 | } 76 | 77 | private class ChildWithNullables : RootWithNullables 78 | { 79 | public static ChildWithNullables CreateWithNullValues() 80 | { 81 | return new ChildWithNullables(); 82 | } 83 | 84 | public static ChildWithNullables CreatePopulated() 85 | { 86 | return new ChildWithNullables 87 | { 88 | NullableInt = 42, 89 | NullableGuid = Guid.Parse("e327e168-c6f5-415a-9a7b-fa82dc73c5d9") 90 | }; 91 | } 92 | } 93 | 94 | private class TestItemWithNullables 95 | { 96 | public int? NullableInt { get; set; } 97 | public Guid? NullableGuid { get; set; } 98 | 99 | public static TestItemWithNullables CreateWithNullValues() 100 | { 101 | return new TestItemWithNullables(); 102 | } 103 | 104 | public static TestItemWithNullables CreatePopulated() 105 | { 106 | return new TestItemWithNullables 107 | { 108 | NullableInt = 42, 109 | NullableGuid = Guid.Parse("e327e168-c6f5-415a-9a7b-fa82dc73c5d9") 110 | }; 111 | } 112 | } 113 | } 114 | } -------------------------------------------------------------------------------- /src/UnitTests/StructureBuilderTests/StructureBuilderTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using Structurizer; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | 7 | namespace UnitTests.StructureBuilderTests 8 | { 9 | [TestClass] 10 | public class StructureBuilderTests : StructureBuilderBaseTests 11 | { 12 | [TestMethod] 13 | public void CreateStructure_WhenIntOnFirstLevel_ReturnsSimpleValue() 14 | { 15 | Builder = StructureBuilder.Create(c => c.Register()); 16 | var item = new TestItemForFirstLevel { IntValue = 42 }; 17 | 18 | var structure = Builder.CreateStructure(item); 19 | 20 | var actual = structure.Indexes.Single(si => si.Path == "IntValue").Value; 21 | Assert.AreEqual(42, actual); 22 | } 23 | 24 | [TestMethod] 25 | public void CreateStructure_WhenUIntOnFirstLevel_ReturnsSimpleValue() 26 | { 27 | Builder = StructureBuilder.Create(c => c.Register()); 28 | var item = new TestItemForFirstLevel { UIntValue = 42 }; 29 | 30 | var structure = Builder.CreateStructure(item); 31 | 32 | var actual = structure.Indexes.Single(si => si.Path == "UIntValue").Value; 33 | 34 | actual.Should().Be((uint)42); 35 | } 36 | 37 | [TestMethod] 38 | public void CreateStructure_WhenIntOnSecondLevel_ReturnsSimpleValue() 39 | { 40 | Builder = StructureBuilder.Create(c => c.Register()); 41 | var item = new TestItemForSecondLevel { Container = new Container { IntValue = 42 } }; 42 | 43 | var structure = Builder.CreateStructure(item); 44 | 45 | var actual = structure.Indexes.Single(si => si.Path == "Container.IntValue").Value; 46 | Assert.AreEqual(42, actual); 47 | } 48 | 49 | [TestMethod] 50 | public void CreateStructure_WhenStructureContainsStructWithValue_ValueOfStructIsRepresentedInIndex() 51 | { 52 | Builder = StructureBuilder.Create(c => c.Register()); 53 | var item = new IHaveStruct { Content = "My content" }; 54 | 55 | var structure = Builder.CreateStructure(item); 56 | 57 | Assert.AreEqual(1, structure.Indexes.Count); 58 | Assert.AreEqual("Content", structure.Indexes[0].Path); 59 | Assert.AreEqual(typeof(MyText), structure.Indexes[0].DataType); 60 | Assert.AreEqual(new MyText("My content"), structure.Indexes[0].Value); 61 | } 62 | 63 | [TestMethod] 64 | public void CreateStructure_When_structure_has_null_collection_It_should_create_structure_with_index_for_other_members() 65 | { 66 | Builder = StructureBuilder.Create(c => c.Register()); 67 | var item = new WithNullCollection { Temp = "Foo", Values = null }; 68 | 69 | var structure = Builder.CreateStructure(item); 70 | 71 | Assert.AreEqual(1, structure.Indexes.Count); 72 | Assert.AreEqual("Temp", structure.Indexes[0].Path); 73 | } 74 | 75 | [TestMethod] 76 | public void CreateStructure_When_5thLevel_has_null_values_It_should_create_structure_with_index_for_other_members() 77 | { 78 | Builder = StructureBuilder.Create(c => c.Register()); 79 | var item = new TestItemForFifthLevel 80 | { 81 | Containers = new List() 82 | { 83 | new Container() 84 | { 85 | IntValue = 27, 86 | MyItem = new Item() 87 | } 88 | } 89 | }; 90 | 91 | var structure = Builder.CreateStructure(item); 92 | 93 | Assert.AreEqual(1, structure.Indexes.Count); 94 | Assert.AreEqual("Containers[0].IntValue", structure.Indexes[0].Path); 95 | } 96 | 97 | private class TestItemForFirstLevel 98 | { 99 | public int IntValue { get; set; } 100 | public uint UIntValue { get; set; } 101 | } 102 | 103 | private class TestItemForSecondLevel 104 | { 105 | public Container Container { get; set; } 106 | } 107 | 108 | private class TestItemForFifthLevel 109 | { 110 | public List Containers { get; set; } 111 | } 112 | 113 | private class Container 114 | { 115 | public int IntValue { get; set; } 116 | 117 | public Item MyItem { get; set; } 118 | } 119 | 120 | private class IHaveStruct 121 | { 122 | public MyText Content { get; set; } 123 | } 124 | 125 | public class WithNullCollection 126 | { 127 | public List Values { get; set; } 128 | public string Temp { get; set; } 129 | } 130 | 131 | public class Item 132 | { 133 | public string Value { get; set; } 134 | } 135 | 136 | private struct MyText 137 | { 138 | private readonly string _value; 139 | 140 | public MyText(string value) 141 | { 142 | _value = value; 143 | } 144 | 145 | public static MyText Parse(string value) 146 | { 147 | return value == null ? null : new MyText(value); 148 | } 149 | 150 | public static implicit operator MyText(string value) 151 | { 152 | return new MyText(value); 153 | } 154 | 155 | public static implicit operator string(MyText item) 156 | { 157 | return item._value; 158 | } 159 | 160 | public override string ToString() 161 | { 162 | return _value; 163 | } 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /src/UnitTests/StructureIndexesFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using FluentAssertions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using Structurizer; 6 | 7 | namespace UnitTests 8 | { 9 | [TestClass] 10 | public class StructureIndexesFactoryTests : UnitTests 11 | { 12 | [TestMethod] 13 | public void GetIndexes_WhenItemHasGuidId_ReturnsId() 14 | { 15 | var value = Guid.Parse("1F0E8C1D-7AF5-418F-A6F6-A40B7F31CB00"); 16 | var item = new WithGuid { GuidValue = value }; 17 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 18 | 19 | var factory = new StructureIndexesFactory(); 20 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 21 | 22 | Assert.AreEqual(value, indexes.Single(i => i.Path == "GuidValue").Value); 23 | } 24 | 25 | [TestMethod] 26 | public void GetIndexes_WhenItemHasNulledGuidId_ReturnsNoIndex() 27 | { 28 | var item = new WithNullableGuid { GuidValue = null }; 29 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 30 | 31 | var factory = new StructureIndexesFactory(); 32 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 33 | 34 | Assert.AreEqual(0, indexes.Count); 35 | } 36 | 37 | [TestMethod] 38 | public void GetIndexes_WhenNullableGuidIdHasValue_ReturnsId() 39 | { 40 | var value = Guid.Parse("1F0E8C1D-7AF5-418F-A6F6-A40B7F31CB00"); 41 | var item = new WithNullableGuid { GuidValue = value }; 42 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 43 | 44 | var factory = new StructureIndexesFactory(); 45 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 46 | 47 | Assert.AreEqual(value, indexes.Single(i => i.Path == "GuidValue").Value); 48 | } 49 | 50 | [TestMethod] 51 | public void GetIndexes_WhenItemWithNullString_ReturnsNoIndex() 52 | { 53 | var item = new WithNoArray { StringValue = null }; 54 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 55 | 56 | var factory = new StructureIndexesFactory(); 57 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 58 | 59 | Assert.IsNull(indexes.SingleOrDefault(i => i.Path == "StringValue")); 60 | } 61 | 62 | [TestMethod] 63 | public void GetIndexes_WhenItemWithAssignedString_ReturnsIndexWithStringValue() 64 | { 65 | var item = new WithNoArray { StringValue = "A" }; 66 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 67 | 68 | var factory = new StructureIndexesFactory(); 69 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 70 | 71 | Assert.AreEqual("A", indexes.Single(i => i.Path == "StringValue").Value); 72 | } 73 | 74 | [TestMethod] 75 | public void GetIndexes_WhenItemWithAssignedInt_ReturnsIndexWithIntValue() 76 | { 77 | var item = new WithNoArray { IntValue = 42 }; 78 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 79 | 80 | var factory = new StructureIndexesFactory(); 81 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 82 | 83 | Assert.AreEqual(42, indexes.Single(i => i.Path == "IntValue").Value); 84 | } 85 | 86 | [TestMethod] 87 | public void GetIndexes_WhenItemWithEnumerableWithOneNullInt_ReturnsNullIndex() 88 | { 89 | var item = new WithArray { NullableIntValues = new int?[] { null } }; 90 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 91 | 92 | var factory = new StructureIndexesFactory(); 93 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 94 | 95 | Assert.IsNull(indexes.SingleOrDefault(i => i.Path == "NullableIntValues")); 96 | } 97 | 98 | [TestMethod] 99 | public void GetIndexes_WhenItemWithEnumerableWithOneNullString_ReturnsNullIndex() 100 | { 101 | var item = new WithArray { StringValues = new string[] { null } }; 102 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 103 | 104 | var factory = new StructureIndexesFactory(); 105 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 106 | 107 | Assert.IsNull(indexes.SingleOrDefault(i => i.Path == "StringValues")); 108 | } 109 | 110 | [TestMethod] 111 | public void GetIndexes_WhenItemWithEnumerableWithOneString_ReturnsIndexWithString() 112 | { 113 | var item = new WithArray { StringValues = new[] { "A" } }; 114 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 115 | 116 | var factory = new StructureIndexesFactory(); 117 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 118 | 119 | Assert.AreEqual("A", indexes.Single(i => i.Path == "StringValues[0]").Value); 120 | } 121 | 122 | [TestMethod] 123 | public void GetIndexes_WhenItemWithEnumerableWithOneString_ReturnsIndexWithDataTypeOfStringElement() 124 | { 125 | var item = new WithArray { StringValues = new[] { "A" } }; 126 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 127 | 128 | var factory = new StructureIndexesFactory(); 129 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 130 | 131 | Assert.AreEqual(DataTypeCode.String, indexes.Single(i => i.Path == "StringValues[0]").DataTypeCode); 132 | } 133 | 134 | [TestMethod] 135 | public void GetIndexes_WhenItemWithComplexEnumerable_ReturnsIndexWithDataTypeOfStringElement() 136 | { 137 | var item = new WithComplexArray { Items = new[] { new Complex { Name = "Foo", Value = 42 } } }; 138 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 139 | 140 | var factory = new StructureIndexesFactory(); 141 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 142 | 143 | Assert.AreEqual(DataTypeCode.String, indexes.Single(i => i.Path == "Items[0].Name").DataTypeCode); 144 | Assert.AreEqual(DataTypeCode.IntegerNumber, indexes.Single(i => i.Path == "Items[0].Value").DataTypeCode); 145 | } 146 | 147 | [TestMethod] 148 | public void GetIndexes_WhenItemWithEnumerableWithOneInt_ReturnsIndexWithInt() 149 | { 150 | var item = new WithArray { IntValues = new[] { 42 } }; 151 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 152 | 153 | var factory = new StructureIndexesFactory(); 154 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 155 | 156 | Assert.AreEqual(42, indexes.Single(i => i.Path == "IntValues[0]").Value); 157 | } 158 | 159 | [TestMethod] 160 | public void GetIndexes_WhenItemWithEnumerableWithTwoDifferentStrings_ReturnsTwoStringIndexes() 161 | { 162 | var item = new WithArray { StringValues = new[] { "A", "B" } }; 163 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 164 | 165 | var factory = new StructureIndexesFactory(); 166 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 167 | 168 | Assert.AreEqual("A", indexes[0].Value); 169 | Assert.AreEqual("B", indexes[1].Value); 170 | } 171 | 172 | [TestMethod] 173 | public void GetIndexes_WhenItemWithEnumerableWithTwoDifferentInts_ReturnsTwoIntIndexes() 174 | { 175 | var item = new WithArray { IntValues = new[] { 42, 43 } }; 176 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 177 | 178 | var factory = new StructureIndexesFactory(); 179 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 180 | 181 | Assert.AreEqual(42, indexes[0].Value); 182 | Assert.AreEqual(43, indexes[1].Value); 183 | } 184 | 185 | [TestMethod] 186 | public void GetIndexes_WhenItemWithEnumerableWithTwoEqualElements_ReturnsTwoStringIndexes() 187 | { 188 | var item = new WithArray { StringValues = new[] { "A", "A" } }; 189 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 190 | 191 | var factory = new StructureIndexesFactory(); 192 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 193 | 194 | Assert.AreEqual("A", indexes[0].Value); 195 | Assert.AreEqual("A", indexes[1].Value); 196 | } 197 | 198 | [TestMethod] 199 | public void GetIndexes_WhenItemWithEnumerableWithTwoEqualElements_ReturnsTwoIntIndexes() 200 | { 201 | var item = new WithArray { IntValues = new[] { 42, 42 } }; 202 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 203 | 204 | var factory = new StructureIndexesFactory(); 205 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 206 | 207 | Assert.AreEqual(42, indexes[0].Value); 208 | Assert.AreEqual(42, indexes[1].Value); 209 | } 210 | 211 | [TestMethod] 212 | public void GetIndexes_WhenItemWithArraysBeingNull_ReturnesNoIndexes() 213 | { 214 | var item = new WithArray 215 | { 216 | IntValues = null, 217 | NullableIntValues = null, 218 | StringValues = null 219 | }; 220 | 221 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 222 | 223 | var factory = new StructureIndexesFactory(); 224 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 225 | 226 | indexes.Should().BeEmpty(); 227 | } 228 | 229 | [TestMethod] 230 | public void GetIndexes_WhenArrayOfComplexWithChildBeingNull_ReturnesNoIndexes() 231 | { 232 | var item = new WithComplexArray 233 | { 234 | Items = new[] { new Complex { Name = null, Value = 42 } } 235 | }; 236 | 237 | var schemaStub = StructureSchemaTestFactory.CreateRealFrom(); 238 | 239 | var factory = new StructureIndexesFactory(); 240 | var indexes = factory.CreateIndexes(schemaStub, item).ToList(); 241 | 242 | indexes.Count.Should().Be(1); 243 | indexes[0].Value.Should().Be(42); 244 | } 245 | 246 | private class WithGuid 247 | { 248 | public Guid GuidValue { get; set; } 249 | } 250 | 251 | private class WithNullableGuid 252 | { 253 | public Guid? GuidValue { get; set; } 254 | } 255 | 256 | private class WithNoArray 257 | { 258 | public string StringValue { get; set; } 259 | public int IntValue { get; set; } 260 | } 261 | 262 | private class WithArray 263 | { 264 | public string[] StringValues { get; set; } 265 | public int[] IntValues { get; set; } 266 | public int?[] NullableIntValues { get; set; } 267 | } 268 | 269 | private class WithComplexArray 270 | { 271 | public Complex[] Items { get; set; } 272 | } 273 | 274 | private class Complex 275 | { 276 | public string Name { get; set; } 277 | public int Value { get; set; } 278 | } 279 | } 280 | } -------------------------------------------------------------------------------- /src/UnitTests/StructureSchemaTestFactory.cs: -------------------------------------------------------------------------------- 1 | using Structurizer; 2 | 3 | namespace UnitTests 4 | { 5 | internal static class StructureSchemaTestFactory 6 | { 7 | internal static IStructureSchema CreateRealFrom() where T : class 8 | { 9 | var structureType = StructureTypeTestFactory.CreateFor(); 10 | var factory = new StructureSchemaFactory(); 11 | 12 | return factory.CreateSchema(structureType); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/UnitTests/StructureTypeTestFactory.cs: -------------------------------------------------------------------------------- 1 | using Structurizer; 2 | using Structurizer.Configuration; 3 | 4 | namespace UnitTests 5 | { 6 | internal static class StructureTypeTestFactory 7 | { 8 | internal static IStructureType CreateFor() where T : class 9 | { 10 | var configs = new StructureTypeConfigurations(); 11 | var factory = new StructureTypeFactory(); 12 | 13 | var typeConfig = configs.Register(); 14 | 15 | return factory.CreateFor(typeConfig); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/UnitTests/UnitTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | 3 | namespace UnitTests 4 | { 5 | [TestClass] 6 | public abstract class UnitTests {} 7 | 8 | [TestClass] 9 | public abstract class UnitTestsOf : UnitTests 10 | { 11 | public T UnitUnderTest { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/UnitTests/UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net452;netcoreapp3.1 5 | false 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | --------------------------------------------------------------------------------