< Summary

Class:Azure.Search.Documents.SearchFilter
Assembly:Azure.Search.Documents
File(s):C:\Git\azure-sdk-for-net\sdk\search\Azure.Search.Documents\src\SearchFilter.cs
Covered lines:60
Uncovered lines:2
Coverable lines:62
Total lines:222
Line coverage:96.7% (60 of 62)
Covered branches:46
Total branches:48
Branch coverage:95.8% (46 of 48)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
Create(...)-100%100%
Create(...)-98.08%97.62%
Quote(...)-88.89%83.33%

File(s)

C:\Git\azure-sdk-for-net\sdk\search\Azure.Search.Documents\src\SearchFilter.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Globalization;
 6using System.Text;
 7#if EXPERIMENTAL_SPATIAL
 8using Azure.Core;
 9using Azure.Core.Spatial;
 10#endif
 11
 12namespace Azure.Search.Documents
 13{
 14    /// <summary>
 15    /// The SearchFilter class is used to help construct valid OData filter
 16    /// expressions, like the kind used by <see cref="SearchOptions.Filter"/>,
 17    /// by automatically replacing, quoting, and escaping interpolated
 18    /// parameters.
 19    /// For more information, see <see href="https://docs.microsoft.com/azure/search/search-filters">Filters in Azure Co
 20    /// </summary>
 21    public static class SearchFilter
 22    {
 23        /// <summary>
 24        /// Create an OData filter expression from an interpolated string.  The
 25        /// interpolated values will be quoted and escaped as necessary.
 26        /// </summary>
 27        /// <param name="filter">An interpolated filter string.</param>
 28        /// <returns>A valid OData filter expression.</returns>
 29        public static string Create(FormattableString filter) =>
 6230            Create(filter, null);
 31
 32        /// <summary>
 33        /// Create an OData filter expression from an interpolated string.  The
 34        /// interpolated values will be quoted and escaped as necessary.
 35        /// </summary>
 36        /// <param name="filter">An interpolated filter string.</param>
 37        /// <param name="formatProvider">
 38        /// Format provider used to convert values to strings.
 39        /// <see cref="CultureInfo.InvariantCulture"/> is used as a default.
 40        /// </param>
 41        /// <returns>A valid OData filter expression.</returns>
 42        public static string Create(FormattableString filter, IFormatProvider formatProvider)
 43        {
 044            if (filter == null) { return null; }
 6245            formatProvider ??= CultureInfo.InvariantCulture;
 46
 6247            string[] args = new string[filter.ArgumentCount];
 26448            for (int i = 0; i < filter.ArgumentCount; i++)
 49            {
 7150                args[i] = filter.GetArgument(i) switch
 7151                {
 7152                    // Null
 7253                    null => "null",
 7154
 7155                    // Boolean
 7556                    bool x => x.ToString(formatProvider).ToLowerInvariant(),
 7157
 7158                    // Numeric
 7459                    sbyte x => x.ToString(formatProvider),
 7360                    byte x => x.ToString(formatProvider),
 7461                    short x => x.ToString(formatProvider),
 7362                    ushort x => x.ToString(formatProvider),
 9063                    int x => x.ToString(formatProvider),
 7364                    uint x => x.ToString(formatProvider),
 7465                    long x => x.ToString(formatProvider),
 7366                    ulong x => x.ToString(formatProvider),
 7567                    decimal x => x.ToString(formatProvider),
 7168
 7169                    // Floating point
 7970                    float x => JsonSerialization.Float(x, formatProvider),
 7971                    double x => JsonSerialization.Double(x, formatProvider),
 7172
 7173                    // Dates as 8601 with a time zone
 7274                    DateTimeOffset x => JsonSerialization.Date(x, formatProvider),
 7275                    DateTime x => JsonSerialization.Date(x, formatProvider),
 7176
 7177#if EXPERIMENTAL_SPATIAL
 7178                    // Points
 7179                    GeometryPosition x => EncodeGeometry(x),
 7180                    PointGeometry x => EncodeGeometry(x),
 7181
 7182                    // Polygons
 7183                    LineGeometry x => EncodeGeometry(x),
 7184                    PolygonGeometry x => EncodeGeometry(x),
 7185#endif
 7186
 7187                    // Text
 7488                    string x => Quote(x),
 7489                    char x => Quote(x.ToString(formatProvider)),
 7290                    StringBuilder x => Quote(x.ToString()),
 7191
 7192                    // Everything else
 7293                    object x => throw new ArgumentException(
 7294                        $"Unable to convert argument {i} from type {x.GetType()} to an OData literal.")
 7195                };
 96            }
 6197            string text = string.Format(formatProvider, filter.Format, args);
 6198            return text;
 99        }
 100
 101        /// <summary>
 102        /// Quote and escape OData strings.
 103        /// </summary>
 104        /// <param name="text">The text to quote.</param>
 105        /// <returns>The quoted text.</returns>
 106        private static string Quote(string text)
 107        {
 0108            if (text == null) { return "null"; }
 109
 110            // Optimistically allocate an extra 5% for escapes
 7111            StringBuilder builder = new StringBuilder(2 + (int)(text.Length * 1.05));
 7112            builder.Append("'");
 52113            foreach (char ch in text)
 114            {
 19115                builder.Append(ch);
 19116                if (ch == '\'')
 117                {
 2118                    builder.Append(ch);
 119                }
 120            }
 7121            builder.Append("'");
 7122            return builder.ToString();
 123        }
 124
 125#if EXPERIMENTAL_SPATIAL
 126        /// <summary>
 127        /// Convert a <see cref="GeometryPosition"/> to an OData value.
 128        /// </summary>
 129        /// <param name="position">The position.</param>
 130        /// <returns>The OData representation of the position.</returns>
 131        private static string EncodeGeometry(GeometryPosition position)
 132        {
 133            const int maxLength =
 134                19 +       // "geography'POINT( )'".Length
 135                2 *        // Lat and Long each have:
 136                   (15 +   //     Maximum precision for a double (without G17)
 137                     1 +   //     Optional decimal point
 138                     1);   //     Optional negative sign
 139            StringBuilder odata = new StringBuilder(maxLength);
 140            odata.Append("geography'POINT(");
 141            odata.Append(JsonSerialization.Double(position.Longitude, CultureInfo.InvariantCulture));
 142            odata.Append(" ");
 143            odata.Append(JsonSerialization.Double(position.Latitude, CultureInfo.InvariantCulture));
 144            odata.Append(")'");
 145            return odata.ToString();
 146        }
 147
 148        /// <summary>
 149        /// Convert a <see cref="PointGeometry"/> to an OData value.
 150        /// </summary>
 151        /// <param name="point">The point.</param>
 152        /// <returns>The OData representation of the point.</returns>
 153        private static string EncodeGeometry(PointGeometry point)
 154        {
 155            Argument.AssertNotNull(point, nameof(point));
 156            return EncodeGeometry(point.Position);
 157        }
 158
 159        /// <summary>
 160        /// Convert a <see cref="LineGeometry"/> forming a polygon to an OData
 161        /// value.  A LineGeometry must have at least four
 162        /// <see cref="LineGeometry.Positions"/> and the first and last must
 163        /// match to form a searchable polygon.
 164        /// </summary>
 165        /// <param name="line">The line forming a polygon.</param>
 166        /// <returns>The OData representation of the line.</returns>
 167        private static string EncodeGeometry(LineGeometry line)
 168        {
 169            Argument.AssertNotNull(line, nameof(line));
 170            Argument.AssertNotNull(line.Positions, $"{nameof(line)}.{nameof(line.Positions)}");
 171            if (line.Positions.Count < 4)
 172            {
 173                throw new ArgumentException(
 174                    $"A {nameof(LineGeometry)} must have at least four {nameof(LineGeometry.Positions)} to form a search
 175                    $"{nameof(line)}.{nameof(line.Positions)}");
 176            }
 177            else if (line.Positions[0] != line.Positions[line.Positions.Count - 1])
 178            {
 179                throw new ArgumentException(
 180                    $"A {nameof(LineGeometry)} must have matching first and last {nameof(LineGeometry.Positions)} to for
 181                    $"{nameof(line)}.{nameof(line.Positions)}");
 182            }
 183
 184            Argument.AssertInRange(line.Positions?.Count ?? 0, 4, int.MaxValue, $"{nameof(line)}.{nameof(line.Positions)
 185
 186            StringBuilder odata = new StringBuilder();
 187            odata.Append("geography'POLYGON((");
 188            bool first = true;
 189            foreach (GeometryPosition position in line.Positions)
 190            {
 191                if (!first) { odata.Append(","); }
 192                first = false;
 193                odata.Append(JsonSerialization.Double(position.Longitude, CultureInfo.InvariantCulture));
 194                odata.Append(" ");
 195                odata.Append(JsonSerialization.Double(position.Latitude, CultureInfo.InvariantCulture));
 196            }
 197            odata.Append("))'");
 198            return odata.ToString();
 199        }
 200
 201        /// <summary>
 202        /// Convert a <see cref="PolygonGeometry"/> to an OData value.  A
 203        /// PolygonGeometry must have exactly one <see cref="PolygonGeometry.Rings"/>
 204        /// to form a searchable polygon.
 205        /// </summary>
 206        /// <param name="polygon">The polygon.</param>
 207        /// <returns>The OData representation of the polygon.</returns>
 208        private static string EncodeGeometry(PolygonGeometry polygon)
 209        {
 210            Argument.AssertNotNull(polygon, nameof(polygon));
 211            Argument.AssertNotNull(polygon.Rings, $"{nameof(polygon)}.{nameof(polygon.Rings)}");
 212            if (polygon.Rings.Count != 1)
 213            {
 214                throw new ArgumentException(
 215                    $"A {nameof(PolygonGeometry)} must have exactly one {nameof(PolygonGeometry.Rings)} to form a search
 216                    $"{nameof(polygon)}.{nameof(polygon.Rings)}");
 217            }
 218            return EncodeGeometry(polygon.Rings[0]);
 219        }
 220#endif
 221    }
 222}

Methods/Properties

Create(...)
Create(...)
Quote(...)