< Summary

Class:Microsoft.Azure.Search.FieldBuilder
Assembly:Microsoft.Azure.Search.Service
File(s):C:\Git\azure-sdk-for-net\sdk\search\Microsoft.Azure.Search.Service\src\Customizations\Indexes\FieldBuilder\FieldBuilder.cs
Covered lines:127
Uncovered lines:1
Coverable lines:128
Total lines:377
Line coverage:99.2% (127 of 128)
Covered branches:54
Total branches:54
Branch coverage:100% (54 of 54)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.cctor()-100%100%
get_CamelCaseResolver()-100%100%
get_DefaultResolver()-100%100%
BuildForType()-100%100%
BuildForType(...)-100%100%
BuildForType(...)-0%100%
BuildForType(...)-100%100%
BuildForTypeRecursive(...)-100%100%
GetDataTypeInfo(...)-100%100%
TryGetEnumerableElementType(...)-100%100%
get_Unknown()-100%100%
Simple(...)-100%100%
Complex(...)-100%100%
AsCollection(...)-100%100%
.ctor()-100%100%
Match(...)-100%100%
.ctor(...)-100%100%
Match(...)-100%100%
.ctor(...)-100%100%
Match(...)-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\search\Microsoft.Azure.Search.Service\src\Customizations\Indexes\FieldBuilder\FieldBuilder.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License. See License.txt in the project root for
 3// license information.
 4
 5using System;
 6using System.Collections.Generic;
 7using System.Linq;
 8using System.Reflection;
 9using Microsoft.Azure.Search.Models;
 10using Microsoft.Spatial;
 11using Newtonsoft.Json.Serialization;
 12using Newtonsoft.Json;
 13using System.Collections.ObjectModel;
 14
 15namespace Microsoft.Azure.Search
 16{
 17    /// <summary>
 18    /// Builds field definitions for a search index by reflecting over a user-defined model type.
 19    /// </summary>
 20    public static class FieldBuilder
 21    {
 222        private static readonly IReadOnlyDictionary<Type, DataType> PrimitiveTypeMap =
 223            new ReadOnlyDictionary<Type, DataType>(
 224                new Dictionary<Type, DataType>()
 225                {
 226                    [typeof(string)] = DataType.String,
 227                    [typeof(int)] = DataType.Int32,
 228                    [typeof(long)] = DataType.Int64,
 229                    [typeof(double)] = DataType.Double,
 230                    [typeof(bool)] = DataType.Boolean,
 231                    [typeof(DateTime)] = DataType.DateTimeOffset,
 232                    [typeof(DateTimeOffset)] = DataType.DateTimeOffset,
 233                    [typeof(GeographyPoint)] = DataType.GeographyPoint
 234                });
 35
 836        private static IContractResolver CamelCaseResolver { get; } = new CamelCasePropertyNamesContractResolver();
 37
 43238        private static IContractResolver DefaultResolver { get; } = new DefaultContractResolver();
 39
 40        /// <summary>
 41        /// Creates a collection of <see cref="Field"/> objects corresponding to
 42        /// the properties of the type supplied.
 43        /// </summary>
 44        /// <typeparam name="T">
 45        /// The type for which fields will be created, based on its properties.
 46        /// </typeparam>
 47        /// <returns>A collection of fields.</returns>
 1848        public static IList<Field> BuildForType<T>() => BuildForType(typeof(T));
 49
 50        /// <summary>
 51        /// Creates a collection of <see cref="Field"/> objects corresponding to
 52        /// the properties of the type supplied.
 53        /// </summary>
 54        /// <param name="modelType">
 55        /// The type for which fields will be created, based on its properties.
 56        /// </param>
 57        /// <returns>A collection of fields.</returns>
 58        public static IList<Field> BuildForType(Type modelType)
 59        {
 43660            bool useCamelCase = SerializePropertyNamesAsCamelCaseAttribute.IsDefinedOnType(modelType);
 43661            IContractResolver resolver = useCamelCase
 43662                ? CamelCaseResolver
 43663                : DefaultResolver;
 43664            return BuildForType(modelType, resolver);
 65        }
 66
 67        /// <summary>
 68        /// Creates a collection of <see cref="Field"/> objects corresponding to
 69        /// the properties of the type supplied.
 70        /// </summary>
 71        /// <typeparam name="T">
 72        /// The type for which fields will be created, based on its properties.
 73        /// </typeparam>
 74        /// <param name="contractResolver">
 75        /// Contract resolver that the SearchIndexClient will use.
 76        /// This ensures that the field names are generated in a way that is
 77        /// consistent with the way the model will be serialized.
 78        /// </param>
 79        /// <returns>A collection of fields.</returns>
 080        public static IList<Field> BuildForType<T>(IContractResolver contractResolver) => BuildForType(typeof(T), contra
 81
 82        /// <summary>
 83        /// Creates a collection of <see cref="Field"/> objects corresponding to
 84        /// the properties of the type supplied.
 85        /// </summary>
 86        /// <param name="modelType">
 87        /// The type for which fields will be created, based on its properties.
 88        /// </param>
 89        /// <param name="contractResolver">
 90        /// Contract resolver that the SearchIndexClient will use.
 91        /// This ensures that the field names are generated in a way that is
 92        /// consistent with the way the model will be serialized.
 93        /// </param>
 94        /// <returns>A collection of fields.</returns>
 95        public static IList<Field> BuildForType(Type modelType, IContractResolver contractResolver)
 96        {
 97            ArgumentException FailOnNonObjectDataType()
 98            {
 2099                string errorMessage =
 20100                    $"Type '{modelType}' does not have properties which map to fields of an Azure Search index. Please u
 20101                    "class or struct with public properties.";
 102
 20103                throw new ArgumentException(errorMessage, nameof(modelType));
 104            }
 105
 822106            if (contractResolver.ResolveContract(modelType) is JsonObjectContract contract)
 107            {
 804108                if (contract.Properties.Count == 0)
 109                {
 2110                    throw FailOnNonObjectDataType();
 111                }
 112
 113                // Use Stack to avoid a dependency on ImmutableStack for now.
 802114                return BuildForTypeRecursive(modelType, contract, contractResolver, new Stack<Type>(new[] { modelType })
 115            }
 116
 18117            throw FailOnNonObjectDataType();
 118        }
 119
 120        private static IList<Field> BuildForTypeRecursive(
 121            Type modelType,
 122            JsonObjectContract contract,
 123            IContractResolver contractResolver,
 124            Stack<Type> processedTypes)
 125        {
 126            Field BuildField(JsonProperty prop)
 127            {
 128                bool ShouldIgnore(Attribute attribute) =>
 78554129                    attribute is JsonIgnoreAttribute || attribute is FieldBuilderIgnoreAttribute;
 130
 73236131                IList<Attribute> attributes = prop.AttributeProvider.GetAttributes(true);
 73236132                if (attributes.Any(ShouldIgnore))
 133                {
 776134                    return null;
 135                }
 136
 137                Field CreateComplexField(DataType dataType, Type underlyingClrType, JsonObjectContract jsonObjectContrac
 138                {
 139                    try
 140                    {
 9164141                        if (processedTypes.Contains(underlyingClrType))
 142                        {
 143                            // Skip recursive types.
 4144                            return null;
 145                        }
 146
 9160147                        processedTypes.Push(underlyingClrType);
 9160148                        IList<Field> subFields =
 9160149                            BuildForTypeRecursive(underlyingClrType, jsonObjectContract, contractResolver, processedType
 9160150                        return new Field(prop.PropertyName, dataType, subFields);
 151                    }
 152                    finally
 153                    {
 9164154                        processedTypes.Pop();
 9164155                    }
 9164156                }
 157
 158                Field CreateSimpleField(DataType dataType)
 159                {
 63288160                    var field = new Field(prop.PropertyName, dataType);
 161
 200024162                    foreach (Attribute attribute in attributes)
 163                    {
 164                        switch (attribute)
 165                        {
 166                            case IsSearchableAttribute _:
 10678167                                field.IsSearchable = true;
 10678168                                break;
 169
 170                            case IsFilterableAttribute _:
 9974171                                field.IsFilterable = true;
 9974172                                break;
 173
 174                            case IsSortableAttribute _:
 784175                                field.IsSortable = true;
 784176                                break;
 177
 178                            case IsFacetableAttribute _:
 5358179                                field.IsFacetable = true;
 5358180                                break;
 181
 182                            case IsRetrievableAttribute isRetrievableAttribute:
 1520183                                field.IsRetrievable = isRetrievableAttribute.IsRetrievable;
 1520184                                break;
 185
 186                            case AnalyzerAttribute analyzerAttribute:
 5328187                                field.Analyzer = analyzerAttribute.Name;
 5328188                                break;
 189
 190                            case SearchAnalyzerAttribute searchAnalyzerAttribute:
 760191                                field.SearchAnalyzer = searchAnalyzerAttribute.Name;
 760192                                break;
 193
 194                            case IndexAnalyzerAttribute indexAnalyzerAttribute:
 760195                                field.IndexAnalyzer = indexAnalyzerAttribute.Name;
 760196                                break;
 197
 198                            case SynonymMapsAttribute synonymMapsAttribute:
 760199                                field.SynonymMaps = synonymMapsAttribute.SynonymMaps;
 760200                                break;
 201
 202                            default:
 802203                                Type attributeType = attribute.GetType();
 204
 205                                // Match on name to avoid dependency - don't want to force people not using
 206                                // this feature to bring in the annotations component.
 207                                //
 208                                // Also, ignore key attributes on sub-fields.
 802209                                if (attributeType.FullName == "System.ComponentModel.DataAnnotations.KeyAttribute" &&
 802210                                    processedTypes.Count <= 1)
 211                                {
 798212                                    field.IsKey = true;
 213                                }
 214                                break;
 215                        }
 216                    }
 217
 63288218                    return field;
 219                }
 220
 221                ArgumentException FailOnUnknownDataType()
 222                {
 8223                    string errorMessage =
 8224                        $"Property '{prop.PropertyName}' is of type '{prop.PropertyType}', which does not map to an " +
 8225                        "Azure Search data type. Please use a supported data type or mark the property with [JsonIgnore]
 8226                        "[FieldBuilderIgnore] and define the field by creating a Field object.";
 227
 8228                    return new ArgumentException(errorMessage, nameof(modelType));
 229                }
 230
 72460231                IDataTypeInfo dataTypeInfo = GetDataTypeInfo(prop.PropertyType, contractResolver);
 232
 72460233                return dataTypeInfo.Match(
 72468234                    onUnknownDataType: () => throw FailOnUnknownDataType(),
 72460235                    onSimpleDataType: CreateSimpleField,
 72460236                    onComplexDataType: CreateComplexField);
 237            }
 238
 83190239            return contract.Properties.Select(BuildField).Where(field => field != null).ToArray();
 240        }
 241
 242        private static IDataTypeInfo GetDataTypeInfo(Type propertyType, IContractResolver contractResolver)
 243        {
 244            bool IsNullableType(Type type) =>
 44584245                type.IsConstructedGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
 246
 107872247            if (PrimitiveTypeMap.TryGetValue(propertyType, out DataType dataType))
 248            {
 63288249                return DataTypeInfo.Simple(dataType);
 250            }
 44584251            else if (IsNullableType(propertyType))
 252            {
 1162253                return GetDataTypeInfo(propertyType.GenericTypeArguments[0], contractResolver);
 254            }
 43422255            else if (TryGetEnumerableElementType(propertyType, out Type elementType))
 256            {
 34250257                IDataTypeInfo elementTypeInfo = GetDataTypeInfo(elementType, contractResolver);
 34250258                return DataTypeInfo.AsCollection(elementTypeInfo);
 259            }
 9172260            else if (contractResolver.ResolveContract(propertyType) is JsonObjectContract jsonContract)
 261            {
 9164262                return DataTypeInfo.Complex(DataType.Complex, propertyType, jsonContract);
 263            }
 264            else
 265            {
 8266                return DataTypeInfo.Unknown;
 267            }
 268        }
 269
 270        private static bool TryGetEnumerableElementType(Type candidateType, out Type elementType)
 271        {
 272            Type GetElementTypeIfIEnumerable(Type t) =>
 208132273                t.IsConstructedGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)
 208132274                    ? t.GenericTypeArguments[0]
 208132275                    : null;
 276
 43422277            elementType = GetElementTypeIfIEnumerable(candidateType);
 43422278            if (elementType != null)
 279            {
 6842280                return true;
 281            }
 282            else
 283            {
 36580284                TypeInfo ti = candidateType.GetTypeInfo();
 36580285                var listElementTypes = ti
 36580286                    .ImplementedInterfaces
 36580287                    .Select(GetElementTypeIfIEnumerable)
 201290288                    .Where(p => p != null)
 36580289                    .ToList();
 290
 36580291                if (listElementTypes.Count == 1)
 292                {
 27408293                    elementType = listElementTypes[0];
 27408294                    return true;
 295                }
 296                else
 297                {
 9172298                    return false;
 299                }
 300            }
 301        }
 302
 303        private interface IDataTypeInfo
 304        {
 305            T Match<T>(
 306                Func<T> onUnknownDataType,
 307                Func<DataType, T> onSimpleDataType,
 308                Func<DataType, Type, JsonObjectContract, T> onComplexDataType);
 309        }
 310
 311        private static class DataTypeInfo
 312        {
 14313            public static IDataTypeInfo Unknown { get; } = new UnknownDataTypeInfo();
 314
 93720315            public static IDataTypeInfo Simple(DataType dataType) => new SimpleDataTypeInfo(dataType);
 316
 317            public static IDataTypeInfo Complex(DataType dataType, Type underlyingClrType, JsonObjectContract jsonContra
 12978318                new ComplexDataTypeInfo(dataType, underlyingClrType, jsonContract);
 319
 320            public static IDataTypeInfo AsCollection(IDataTypeInfo dataTypeInfo) =>
 34250321                dataTypeInfo.Match(
 34254322                    onUnknownDataType: () => Unknown,
 64682323                    onSimpleDataType: dataType => Simple(DataType.Collection(dataType)),
 34250324                    onComplexDataType: (dataType, underlyingClrType, jsonContract) =>
 38064325                        Complex(DataType.Collection(dataType), underlyingClrType, jsonContract));
 326
 327            private sealed class UnknownDataTypeInfo : IDataTypeInfo
 328            {
 2329                public UnknownDataTypeInfo()
 330                {
 2331                }
 332
 333                public T Match<T>(
 334                    Func<T> onUnknownDataType,
 335                    Func<DataType, T> onSimpleDataType,
 336                    Func<DataType, Type, JsonObjectContract, T> onComplexDataType)
 12337                    => onUnknownDataType();
 338            }
 339
 340            private sealed class SimpleDataTypeInfo : IDataTypeInfo
 341            {
 342                private readonly DataType _dataType;
 343
 93720344                public SimpleDataTypeInfo(DataType dataType)
 345                {
 93720346                    _dataType = dataType;
 93720347                }
 348
 349                public T Match<T>(
 350                    Func<T> onUnknownDataType,
 351                    Func<DataType, T> onSimpleDataType,
 352                    Func<DataType, Type, JsonObjectContract, T> onComplexDataType)
 93720353                    => onSimpleDataType(_dataType);
 354            }
 355
 356            private sealed class ComplexDataTypeInfo : IDataTypeInfo
 357            {
 358                private readonly DataType _dataType;
 359                private readonly Type _underlyingClrType;
 360                private readonly JsonObjectContract _jsonContract;
 361
 12978362                public ComplexDataTypeInfo(DataType dataType, Type underlyingClrType, JsonObjectContract jsonContract)
 363                {
 12978364                    _dataType = dataType;
 12978365                    _underlyingClrType = underlyingClrType;
 12978366                    _jsonContract = jsonContract;
 12978367                }
 368
 369                public T Match<T>(
 370                    Func<T> onUnknownDataType,
 371                    Func<DataType, T> onSimpleDataType,
 372                    Func<DataType, Type, JsonObjectContract, T> onComplexDataType)
 12978373                    => onComplexDataType(_dataType, _underlyingClrType, _jsonContract);
 374            }
 375        }
 376    }
 377}