guibranco/Sankhya-SDK-dotnet

View on GitHub
Src/Sankhya/Helpers/ServiceRequestExtensions.cs

Summary

Maintainability
F
6 days
Test Coverage
// ***********************************************************************
// Assembly         : Sankhya
// Author           : GuilhermeStracini
// Created          : 10-07-2023
//
// Last Modified By : GuilhermeStracini
// Last Modified On : 10-08-2023
// ***********************************************************************
// <copyright file="ServiceRequestExtensions.cs" company="Guilherme Branco Stracini">
//     © 2023 Guilherme Branco Stracini. All rights reserved.
// </copyright>
// <summary></summary>
// ***********************************************************************

using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using CrispyWaffle.Extensions;
using CrispyWaffle.Log;
using CrispyWaffle.Utilities;
using Sankhya.Attributes;
using Sankhya.Enums;
using Sankhya.GoodPractices;
using Sankhya.Properties;
using Sankhya.Service;
using Sankhya.Transport;
using Sankhya.Validations;
using Sankhya.ValueObjects;

namespace Sankhya.Helpers;

/// <summary>
/// Implements service request extensions methods.
/// </summary>
public static class ServiceRequestExtensions
{
    /// <summary>
    /// Parse properties.
    /// </summary>
    /// <typeparam name="T">Generic type parameter.</typeparam>
    /// <param name="request">The Service Request.</param>
    /// <param name="criteria">The criteria.</param>
    /// <param name="maxLevel">(Optional) the maximum level.</param>
    /// <param name="currentLevel">(Optional) the current level.</param>
    /// <returns>A ParseResult.</returns>
    /// <exception cref="TooInnerLevelsException">Thrown when a Too Inner Levels error condition occurs.</exception>
    private static EntityResolverResult ParseProperties<T>(
        this ServiceRequest request,
        T criteria,
        ReferenceLevel maxLevel,
        ReferenceLevel currentLevel = ReferenceLevel.None
    )
    {
        var type = typeof(T);
        if (type == typeof(object))
        {
            type = criteria.GetType();
        }

        var currentEntityName = type.GetEntityName();
        if (maxLevel == ReferenceLevel.Fifth)
        {
            throw new TooInnerLevelsException(currentEntityName);
        }

        var result = new EntityResolverResult(currentEntityName);
        var ignoredFields = new List<string>();
        foreach (var propertyInfo in type.GetProperties())
        {
            ParseProperty(
                request,
                criteria,
                maxLevel,
                currentLevel,
                propertyInfo,
                ignoredFields,
                result,
                type,
                currentEntityName
            );
        }

        return result;
    }

    /// <summary>
    /// Parses the property.
    /// </summary>
    /// <typeparam name="T">The type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="criteriaEntity">The entity used as criteria.</param>
    /// <param name="maxLevel">The maximum level.</param>
    /// <param name="currentLevel">The current level.</param>
    /// <param name="propertyInfo">The property information.</param>
    /// <param name="ignoredFields">The ignored fields.</param>
    /// <param name="result">The result.</param>
    /// <param name="type">The type.</param>
    /// <param name="currentEntityName">Name of the current entity.</param>
    private static void ParseProperty<T>(
        ServiceRequest request,
        T criteriaEntity,
        ReferenceLevel maxLevel,
        ReferenceLevel currentLevel,
        PropertyInfo propertyInfo,
        ICollection<string> ignoredFields,
        EntityResolverResult result,
        Type type,
        string currentEntityName
    )
    {
        var model = new ParsePropertyModel();

        ParseCustomAttributes(propertyInfo, model);

        if (CheckIfElementIsIgnored(propertyInfo, ignoredFields, model))
        {
            return;
        }

        if (
            !model.IsEntityReference
            && !model.IgnoreEntityReferenceInline
            && (
                EntityValidation.ReferenceFieldsFirstLevelPattern.IsMatch(model.PropertyName)
                || EntityValidation.ReferenceFieldsSecondLevelPattern.IsMatch(model.PropertyName)
            )
        )
        {
            model.IsEntityReferenceInline = true;
        }

        ProcessParse(
            request,
            criteriaEntity,
            maxLevel,
            currentLevel,
            propertyInfo,
            result,
            type,
            currentEntityName,
            model
        );
    }

    /// <summary>
    /// Processes the parse.
    /// </summary>
    /// <typeparam name="T">The type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="criteriaEntity">The criteria entity.</param>
    /// <param name="maxLevel">The maximum level.</param>
    /// <param name="currentLevel">The current level.</param>
    /// <param name="propertyInfo">The property information.</param>
    /// <param name="result">The result.</param>
    /// <param name="type">The type.</param>
    /// <param name="currentEntityName">Name of the current entity.</param>
    /// <param name="model">The model.</param>
    private static void ProcessParse<T>(
        ServiceRequest request,
        T criteriaEntity,
        ReferenceLevel maxLevel,
        ReferenceLevel currentLevel,
        PropertyInfo propertyInfo,
        EntityResolverResult result,
        Type type,
        string currentEntityName,
        ParsePropertyModel model
    )
    {
        if (!model.IsEntityReference && !model.IsEntityReferenceInline)
        {
            ProcessFieldsAndCriteria(
                request,
                criteriaEntity,
                currentLevel,
                propertyInfo,
                result,
                type,
                currentEntityName,
                model
            );
        }
        else if (model.IsEntityReferenceInline)
        {
            ProcessEntityReferenceInline(result, type, currentEntityName, model);
        }
        else
        {
            ProcessEntityReference(
                request,
                criteriaEntity,
                maxLevel,
                currentLevel,
                propertyInfo,
                result,
                model
            );
        }
    }

    /// <summary>
    /// Processes the fields and criteria.
    /// </summary>
    /// <typeparam name="T">The type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="criteriaEntity">The criteria entity.</param>
    /// <param name="currentLevel">The current level.</param>
    /// <param name="propertyInfo">The property information.</param>
    /// <param name="result">The result.</param>
    /// <param name="type">The type.</param>
    /// <param name="currentEntityName">Name of the current entity.</param>
    /// <param name="model">The model.</param>
    private static void ProcessFieldsAndCriteria<T>(
        ServiceRequest request,
        T criteriaEntity,
        ReferenceLevel currentLevel,
        PropertyInfo propertyInfo,
        EntityResolverResult result,
        Type type,
        string currentEntityName,
        ParsePropertyModel model
    )
    {
        result.Fields.Add(new() { Name = model.PropertyName });

        var shouldSerializePropertyName = propertyInfo.Name.EndsWith(
            @"Internal",
            StringComparison.OrdinalIgnoreCase
        )
            ? propertyInfo.Name.Substring(0, propertyInfo.Name.Length - 8)
            : propertyInfo.Name;

        var shouldSerializeMethod = type.GetMethod(
            string.Concat(@"ShouldSerialize", shouldSerializePropertyName)
        );

        if (shouldSerializeMethod != null && shouldSerializeMethod.ReturnType == typeof(bool))
        {
            model.IsCriteria = (bool)shouldSerializeMethod.Invoke(criteriaEntity, null);
        }
        else
        {
            LogConsumer.Handle(
                new MissingSerializerHelperEntityException(
                    propertyInfo.Name,
                    currentEntityName,
                    type.FullName
                )
            );
        }

        if (
            !model.IsCriteria
            && (
                !model.IsEntityKey
                || currentLevel != ReferenceLevel.None
                || request.Service == ServiceName.CrudServiceSave
                || request.Service == ServiceName.CrudSave
            )
        )
        {
            return;
        }

        var possibleValue = propertyInfo.GetValue(criteriaEntity, null);
        if (possibleValue == null)
        {
            return;
        }

        var value = possibleValue.ToString();
        if (model.CustomData.MaxLength > 0 && value.Length > model.CustomData.MaxLength)
        {
            value = value.Abbreviate(model.CustomData.MaxLength, false);
        }

        if (model.IsEntityKey)
        {
            result.Keys.Add(new() { Name = model.PropertyName, Value = value });
        }

        if (model.IsCriteria)
        {
            result.Criteria.Add(new() { Name = model.PropertyName, Value = value });
            var isNumberType = propertyInfo.PropertyType.IsNumericType();
            var condition = string.Concat(
                model.PropertyName,
                @" = ",
                isNumberType ? string.Empty : @"'",
                value,
                isNumberType ? string.Empty : @"'"
            );
            result.LiteralCriteria.Expression =
                result.Criteria.Count == 1
                    ? condition
                    : string.Concat(result.LiteralCriteria.Expression, @" AND ", condition);
        }

        result.FieldValues.Add(new() { Name = model.PropertyName, Value = value });
    }

    /// <summary>
    /// Processes the entity reference inline.
    /// </summary>
    /// <param name="result">The result.</param>
    /// <param name="type">The type.</param>
    /// <param name="currentEntityName">Name of the current entity.</param>
    /// <param name="model">The model.</param>
    private static void ProcessEntityReferenceInline(
        EntityResolverResult result,
        Type type,
        string currentEntityName,
        ParsePropertyModel model
    )
    {
        var secondLevel = EntityValidation.ReferenceFieldsSecondLevelPattern.IsMatch(
            model.PropertyName
        );
        var match = secondLevel
            ? EntityValidation.ReferenceFieldsSecondLevelPattern.Match(model.PropertyName)
            : EntityValidation.ReferenceFieldsFirstLevelPattern.Match(model.PropertyName);
        var referenceEntity = secondLevel
            ? $@"{match.Groups[@"parentEntity"].Value}.{match.Groups[@"entity"].Value}"
            : match.Groups[@"entity"].Value;
        var referenceField = new Field { Name = match.Groups[@"field"].Value };

        if (result.References.ContainsKey(referenceEntity))
        {
            result.References[referenceEntity].Add(referenceField);
        }
        else
        {
            result.References.Add(referenceEntity, new() { referenceField });
        }

        LogConsumer.Warning(
            Resources.ServiceRequestExtensions_ParseProperty,
            referenceEntity,
            referenceField.Name,
            model.PropertyName,
            currentEntityName,
            type.Name
        );
    }

    /// <summary>
    /// Processes the entity reference.
    /// </summary>
    /// <typeparam name="T">The type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="criteriaEntity">The criteria entity.</param>
    /// <param name="maxLevel">The maximum level.</param>
    /// <param name="currentLevel">The current level.</param>
    /// <param name="propertyInfo">The property information.</param>
    /// <param name="result">The result.</param>
    /// <param name="model">The model.</param>
    private static void ProcessEntityReference<T>(
        ServiceRequest request,
        T criteriaEntity,
        ReferenceLevel maxLevel,
        ReferenceLevel currentLevel,
        PropertyInfo propertyInfo,
        EntityResolverResult result,
        ParsePropertyModel model
    )
    {
        if ((int)maxLevel <= (int)currentLevel)
        {
            return;
        }

        var innerType = propertyInfo.PropertyType;
        var innerLevel = (ReferenceLevel)((int)currentLevel + 1);
        var innerTypeValue =
            Convert.ChangeType(
                propertyInfo.GetValue(criteriaEntity, null),
                innerType,
                CultureInfo.InvariantCulture
            ) ?? Activator.CreateInstance(innerType);
        var innerResult = request.ParseProperties(innerTypeValue, maxLevel, innerLevel);

        var innerName = string.IsNullOrWhiteSpace(model.CustomRelationName)
            ? innerType.GetEntityName()
            : model.CustomRelationName;

        foreach (var innerField in innerResult.Fields)
        {
            if (result.References.ContainsKey(innerName))
            {
                result.References[innerName].Add(innerField);
            }
            else
            {
                result.References.Add(innerName, new() { innerField });
            }
        }

        foreach (var innerReference in innerResult.References)
        {
            if (innerName.Equals(innerReference.Key, StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            var nestedInnerReference = $@"{innerName}.{innerReference.Key}";

            if (result.References.ContainsKey(nestedInnerReference))
            {
                result.References[nestedInnerReference].AddRange(innerReference.Value);
            }
            else
            {
                result.References.Add(nestedInnerReference, innerReference.Value);
            }
        }

        foreach (var criteria in innerResult.Criteria)
        {
            result.Criteria.Add(
                new() { Name = $@"{innerName}->{criteria.Name}", Value = criteria.Value }
            );
        }
    }

    /// <summary>
    /// Checks if element is ignored.
    /// </summary>
    /// <param name="propertyInfo">The property information.</param>
    /// <param name="ignoredFields">The ignored fields.</param>
    /// <param name="model">The model.</param>
    /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
    private static bool CheckIfElementIsIgnored(
        PropertyInfo propertyInfo,
        ICollection<string> ignoredFields,
        ParsePropertyModel model
    )
    {
        if (
            !model.IsIgnored
            && (
                !ignoredFields.Any(f =>
                    f.Equals(model.PropertyName, StringComparison.OrdinalIgnoreCase)
                ) || propertyInfo.Name.EndsWith(@"Internal", StringComparison.OrdinalIgnoreCase)
            )
        )
        {
            return false;
        }

        ignoredFields.Add(model.PropertyName);
        return true;
    }

    /// <summary>
    /// Parses the custom attributes.
    /// </summary>
    /// <param name="propertyInfo">The property information.</param>
    /// <param name="model">The model.</param>
    private static void ParseCustomAttributes(PropertyInfo propertyInfo, ParsePropertyModel model)
    {
        foreach (var customAttribute in propertyInfo.GetCustomAttributes(true))
        {
            switch (customAttribute)
            {
                case EntityIgnoreAttribute _:
                    model.IsIgnored = true;
                    continue;
                case EntityKeyAttribute _:
                    model.IsEntityKey = true;
                    continue;
                case EntityReferenceAttribute referenceAttribute:
                    model.IsEntityReference = true;
                    model.CustomRelationName = referenceAttribute.CustomRelationName;
                    continue;
                case EntityElementAttribute elementAttribute:
                    model.PropertyName = elementAttribute.ElementName;
                    model.IgnoreEntityReferenceInline = elementAttribute.IgnoreInlineReference;
                    continue;
                case EntityCustomDataAttribute customDataAttribute:
                    model.CustomData = customDataAttribute;
                    continue;
            }
        }

        if (string.IsNullOrWhiteSpace(model.PropertyName))
        {
            model.PropertyName = propertyInfo.Name;
        }
    }

    /// <summary>
    /// Request with type.
    /// </summary>
    /// <typeparam name="T">Generic type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <exception cref="ArgumentNullException">request.</exception>
    /// <exception cref="InvalidServiceRequestOperationException">Invalid service request operation.</exception>
    public static void Resolve<T>(this ServiceRequest request)
        where T : class, IEntity, new()
    {
        if (request == null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        if (
            request.Service != ServiceName.CrudFind
            && request.Service != ServiceName.CrudServiceFind
        )
        {
            throw new InvalidServiceRequestOperationException(request.Service);
        }

        var criteria = new T();
        var result = request.ParseProperties(criteria, ReferenceLevel.Third);

        switch (request.Service)
        {
            case ServiceName.CrudFind:
                request.RequestBody.Entity = new()
                {
                    Fields = result.Fields.ToArray(),
                    Name = result.Name,
                    IncludePresentationFields = false
                };
                if (!result.References.Any())
                {
                    return;
                }

                request.RequestBody.Entity.ReferencesFetch = result
                    .References.Select(reference => new ReferenceFetch
                    {
                        Field = reference.Value.ToArray(),
                        Name = reference.Key
                    })
                    .ToArray();
                break;

            case ServiceName.CrudServiceFind:
                request.RequestBody.DataSet = new()
                {
                    RootEntity = result.Name,
                    IncludePresentationFields = false,
                    ParallelLoader = true,
                    DataSetId = string.Concat(DateTime.Now.ToUnixTimeStamp(), @"_1")
                };
                var entities = new List<Entity>
                {
                    new()
                    {
                        Path = string.Empty,
                        Fieldset = new()
                        {
                            List = string.Join(@",", result.Fields.Select(f => f.Name))
                        }
                    }
                };
                if (!result.References.Any())
                {
                    request.RequestBody.DataSet.Entities = entities.ToArray();
                    return;
                }

                entities.AddRange(
                    result.References.Select(reference => new Entity
                    {
                        Path = reference.Key,
                        Fieldset = new()
                        {
                            List = string.Join(@",", reference.Value.Select(v => v.Name))
                        }
                    })
                );
                request.RequestBody.DataSet.Entities = entities.ToArray();
                break;
        }
    }

    /// <summary>
    /// Request with type.
    /// </summary>
    /// <typeparam name="T">Generic type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="criteria">The criteria.</param>
    /// <param name="maxReferenceLevel">The maximum reference level.</param>
    /// <exception cref="ArgumentNullException">request.</exception>
    /// <exception cref="ArgumentNullException">criteria.</exception>
    /// <exception cref="InvalidServiceRequestOperationException">Invalid service request operation.</exception>
    public static void Resolve<T>(
        this ServiceRequest request,
        T criteria,
        ReferenceLevel maxReferenceLevel = ReferenceLevel.Fourth
    )
        where T : class, IEntity, new()
    {
        if (request == null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        if (criteria == null)
        {
            throw new ArgumentNullException(nameof(criteria));
        }

        var maxReference = maxReferenceLevel;

        if (request.Service == ServiceName.CrudServiceFind && maxReference == ReferenceLevel.Fourth)
        {
            maxReference = ReferenceLevel.Third;
        }

        var result = request.ParseProperties(criteria, maxReference);
        if (
            request.Service == ServiceName.CrudFind
            || request.Service == ServiceName.CrudSave
            || request.Service == ServiceName.CrudRemove
        )
        {
            request.RequestBody.Entity = new() { Name = result.Name };
        }

        HandleService(request, result);
    }

    /// <summary>
    /// Handles the service.
    /// </summary>
    /// <param name="request">The request.</param>
    /// <param name="result">The result.</param>
    /// <exception cref="InvalidServiceRequestOperationException">Invalid Service Request Operation.</exception>
    private static void HandleService(ServiceRequest request, EntityResolverResult result)
    {
        switch (request.Service)
        {
            case ServiceName.CrudFind:

                request.RequestBody.Entity.Criteria = result.Criteria.ToArray();
                request.RequestBody.Entity.Fields = result.Fields.ToArray();
                request.RequestBody.Entity.IncludePresentationFields = false;
                if (!result.References.Any())
                {
                    return;
                }

                request.RequestBody.Entity.ReferencesFetch = result
                    .References.Select(reference => new ReferenceFetch
                    {
                        Field = reference.Value.ToArray(),
                        Name = reference.Key
                    })
                    .ToArray();

                break;

            case ServiceName.CrudSave:
            case ServiceName.CrudRemove:

                request.RequestBody.Entity.Campos = result.FieldValues.ToArray();

                break;

            case ServiceName.CrudServiceFind:

                request.RequestBody.DataSet = new()
                {
                    RootEntity = result.Name,
                    IncludePresentationFields = false,
                    ParallelLoader = true,
                    DataSetId = string.Concat(DateTime.Now.ToUnixTimeStamp(), @"_1")
                };
                var entities = new List<Entity>
                {
                    new()
                    {
                        Path = string.Empty,
                        Fieldset = new()
                        {
                            List = string.Join(@",", result.Fields.Select(f => f.Name))
                        }
                    }
                };
                request.RequestBody.DataSet.LiteralCriteria = result.LiteralCriteria;
                if (!result.References.Any())
                {
                    request.RequestBody.DataSet.Entities = entities.ToArray();
                    return;
                }

                entities.AddRange(
                    result.References.Select(reference => new Entity
                    {
                        Path = reference.Key,
                        Fieldset = new()
                        {
                            List = string.Join(@",", reference.Value.Select(v => v.Name))
                        }
                    })
                );
                request.RequestBody.DataSet.Entities = entities.ToArray();

                break;

            case ServiceName.CrudServiceSave:

                request.RequestBody.DataSet = new()
                {
                    RootEntity = result.Name,
                    IncludePresentationFields = false,
                    ParallelLoader = true,
                    DataSetId = string.Concat(DateTime.Now.ToUnixTimeStamp(), @"_1"),
                    Entities = new[]
                    {
                        new Entity
                        {
                            Path = string.Empty,
                            Fieldset = new() { List = @"*" }
                        }
                    },
                    DataRows = new[]
                    {
                        new DataRow { Keys = new(), LocalFields = new() }
                    }
                };
                result.Keys.ForEach(k =>
                    request.RequestBody.DataSet.DataRows.Single().Keys.SetMember(k.Name, k.Value)
                );
                result
                    .FieldValues.Except(result.Keys)
                    .ToList()
                    .ForEach(f =>
                        request
                            .RequestBody.DataSet.DataRows.Single()
                            .LocalFields.SetMember(f.Name, f.Value)
                    );

                break;

            case ServiceName.CrudServiceRemove:

                request.RequestBody.Entity = new()
                {
                    RootEntity = result.Name,
                    DataSetId = string.Concat(DateTime.Now.ToUnixTimeStamp(), @"_2"),
                    Ids = new[]
                    {
                        new EntityDynamicSerialization(DynamicSerializationOption.Uppercase)
                    }
                };
                result.Keys.ForEach(k =>
                    request.RequestBody.Entity.Ids[0].SetMember(k.Name, k.Value)
                );

                break;

            default:
                throw new InvalidServiceRequestOperationException(request.Service);
        }
    }

    /// <summary>
    /// Request with type.
    /// </summary>
    /// <typeparam name="T">Generic type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="criteriaList">The criteria list to create/update/save or exclude/remove.</param>
    /// <exception cref="ArgumentNullException">request.</exception>
    /// <exception cref="InvalidServiceRequestOperationException">Invalid service request operation.</exception>
    public static void Resolve<T>(this ServiceRequest request, ICollection<T> criteriaList)
        where T : class, IEntity, new()
    {
        if (request == null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        var results = criteriaList
            .Select(criteria => request.ParseProperties(criteria, ReferenceLevel.Third))
            .ToList();

        var sample = results[0];

        switch (request.Service)
        {
            case ServiceName.CrudServiceSave:
                request.RequestBody.DataSet = new()
                {
                    RootEntity = sample.Name,
                    IncludePresentationFields = false,
                    ParallelLoader = true,
                    DataSetId = string.Concat(DateTime.Now.ToUnixTimeStamp(), @"_1"),
                    Entities = new[]
                    {
                        new Entity
                        {
                            Path = string.Empty,
                            Fieldset = new() { List = @"*" }
                        }
                    }
                };
                var dataRows = new List<DataRow>();
                foreach (var result in results)
                {
                    var dataRow = new DataRow { Keys = new() };
                    dataRows.Add(dataRow);
                    result.Keys.ForEach(k => dataRow.Keys.SetMember(k.Name, k.Value));
                    if (request.Service != ServiceName.CrudServiceSave)
                    {
                        continue;
                    }

                    dataRow.LocalFields = new();
                    result
                        .FieldValues.Except(result.Keys)
                        .ToList()
                        .ForEach(v => dataRow.LocalFields.SetMember(v.Name, v.Value));
                }

                request.RequestBody.DataSet.DataRows = dataRows.ToArray();
                break;

            case ServiceName.CrudServiceRemove:
                var ids = new List<EntityDynamicSerialization>();
                foreach (var result in results)
                {
                    var id = new EntityDynamicSerialization(DynamicSerializationOption.Uppercase);
                    ids.Add(id);
                    result.Keys.ForEach(k => id.SetMember(k.Name, k.Value));
                }

                request.RequestBody.Entity = new()
                {
                    RootEntity = sample.Name,
                    DataSetId = string.Concat(DateTime.Now.ToUnixTimeStamp(), @"_2"),
                    Ids = ids.ToArray()
                };
                break;

            default:
                throw new InvalidServiceRequestOperationException(request.Service);
        }
    }

    /// <summary>
    /// Resolve <seealso cref="IEntity" /> to a <seealso cref="ServiceRequest" /> using a <seealso cref="ILiteralCriteria" /> as filter.
    /// This method is only used for retrieve (in CRUD, the R part) data requests, so, only ServiceName.CRUD_FIND or ServiceName.CRUD_SERVICE_FIND can be used.
    /// Any other service request will throw a exception of type <seealso cref="InvalidServiceRequestOperationException" />.
    /// If <paramref name="literalCriteria" /> is of type <seealso cref="LiteralCriteriaSql" />, then service must be ServiceName.CRUD_FIND or it will also throw a exception.
    /// </summary>
    /// <typeparam name="T">Generic type parameter. Must be a <seealso cref="IEntity" /> entity.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="literalCriteria">The literal criteria.</param>
    /// <exception cref="InvalidServiceRequestOperationException">Invalid operation.</exception>
    public static void Resolve<T>(this ServiceRequest request, ILiteralCriteria literalCriteria)
        where T : class, IEntity, new()
    {
        request.Resolve<T>();
        var literal = literalCriteria as LiteralCriteria;
        var literalSql = literalCriteria as LiteralCriteriaSql;
        if (literal != null && request.Service == ServiceName.CrudFind)
        {
            request.RequestBody.Entity.LiteralCriteria = literal;
        }
        else if (literal != null && request.Service == ServiceName.CrudServiceFind)
        {
            request.RequestBody.DataSet.LiteralCriteria = literal;
        }
        else if (literalSql != null && request.Service == ServiceName.CrudFind)
        {
            request.RequestBody.Entity.LiteralCriteriaSql = literalSql;
        }
        else
        {
            throw new InvalidServiceRequestOperationException(request.Service);
        }
    }

    /// <summary>
    /// Request with type.
    /// </summary>
    /// <typeparam name="T">Generic type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="literalCriteriaBuilder">The literal criteria builder.</param>
    /// <exception cref="ArgumentNullException">literalCriteriaBuilder.</exception>
    public static void Resolve<T>(this ServiceRequest request, StringBuilder literalCriteriaBuilder)
        where T : class, IEntity, new()
    {
        if (literalCriteriaBuilder == null)
        {
            throw new ArgumentNullException(nameof(literalCriteriaBuilder));
        }

        request.Resolve<T>(new LiteralCriteria(literalCriteriaBuilder.ToString()));
    }

    /// <summary>
    /// Request with type using predicate.
    /// </summary>
    /// <typeparam name="T">Generic type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="predicate">The predicate to use as literal criteria.</param>
    /// <exception cref="NotImplementedException">Method not implemented.</exception>
    // TODO: issue #29
    public static void Resolve<T>(this ServiceRequest request, Expression<Func<T, bool>> predicate)
        where T : class, IEntity, new() => throw new NotImplementedException();

    /// <summary>
    /// Resolves the specified entity.
    /// </summary>
    /// <typeparam name="T">The type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="entity">The entity.</param>
    /// <param name="options">The options.</param>
    /// <exception cref="ArgumentNullException">request.</exception>
    /// <exception cref="ArgumentNullException">options.</exception>
    /// <exception cref="InvalidServiceRequestOperationException">Invalid Service Request Operation Exception.</exception>
    public static void Resolve<T>(this ServiceRequest request, T entity, EntityQueryOptions options)
        where T : class, IEntity, new()
    {
        if (request == null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }

        request.Resolve(entity, options.MaxReferenceDepth ?? ReferenceLevel.Fourth);
        switch (request.Service)
        {
            case ServiceName.CrudFind:
                if (options.IncludePresentationFields.HasValue)
                {
                    request.RequestBody.Entity.IncludePresentationFields = options
                        .IncludePresentationFields
                        .Value;
                }

                if (options.IncludeReferences.HasValue && !options.IncludeReferences.Value)
                {
                    request.RequestBody.Entity.ReferencesFetch = null;
                }

                if (options.MaxResults.HasValue)
                {
                    request.RequestBody.Entity.RowsLimit = options.MaxResults.Value;
                }

                break;
            case ServiceName.CrudServiceFind:
                if (options.IncludePresentationFields.HasValue)
                {
                    request.RequestBody.DataSet.IncludePresentationFields = options
                        .IncludePresentationFields
                        .Value;
                }

                if (options.IncludeReferences.HasValue && !options.IncludeReferences.Value)
                {
                    request.RequestBody.DataSet.Entities = request
                        .RequestBody.DataSet.Entities.Where(e => string.IsNullOrWhiteSpace(e.Path))
                        .ToArray();
                }

                if (options.UseWildcardFields.HasValue && options.UseWildcardFields.Value)
                {
                    foreach (var dataSetEntity in request.RequestBody.DataSet.Entities)
                    {
                        dataSetEntity.Fieldset = new() { List = @"*" };
                        dataSetEntity.Fields = null;
                    }
                }

                break;
        }
    }

    /// <summary>
    /// Resolves the specified criteria.
    /// </summary>
    /// <typeparam name="T">The type parameter.</typeparam>
    /// <param name="request">The request.</param>
    /// <param name="criteria">The criteria.</param>
    /// <param name="options">The options.</param>
    /// <exception cref="ArgumentNullException">Argument is null when it was requested.</exception>
    /// <exception cref="InvalidServiceRequestOperationException">Invalid service request operation exception.</exception>
    public static void Resolve<T>(
        this ServiceRequest request,
        ILiteralCriteria criteria,
        EntityQueryOptions options
    )
        where T : class, IEntity, new()
    {
        if (request == null)
        {
            throw new ArgumentNullException(nameof(request));
        }

        if (options == null)
        {
            throw new ArgumentNullException(nameof(options));
        }

        request.Resolve<T>(criteria);
        switch (request.Service)
        {
            case ServiceName.CrudFind:

                ResolveCrudFindInternal(request, options);

                break;
            case ServiceName.CrudServiceFind:

                ResolveCrudServiceFindInternal(request, options);

                break;
        }
    }

    /// <summary>
    /// Resolves the crud service find internal.
    /// </summary>
    /// <param name="request">The request.</param>
    /// <param name="options">The options.</param>
    /// <exception cref="NotImplementedException">Method not implemented.</exception>
    private static void ResolveCrudServiceFindInternal(
        ServiceRequest request,
        EntityQueryOptions options
    )
    {
        if (options.IncludePresentationFields.HasValue)
        {
            request.RequestBody.DataSet.IncludePresentationFields = options
                .IncludePresentationFields
                .Value;
        }

        if (options.IncludeReferences.HasValue && !options.IncludeReferences.Value)
        {
            request.RequestBody.DataSet.Entities = request
                .RequestBody.DataSet.Entities.Where(e => string.IsNullOrWhiteSpace(e.Path))
                .ToArray();
        }

        if (options.MaxReferenceDepth.HasValue)
        {
            throw new NotImplementedException();
        }

        if (options.UseWildcardFields.HasValue && options.UseWildcardFields.Value)
        {
            foreach (var dataSetEntity in request.RequestBody.DataSet.Entities)
            {
                dataSetEntity.Fieldset = new() { List = @"*" };
                dataSetEntity.Fields = null;
            }
        }
    }

    /// <summary>
    /// Resolves the crud find internal.
    /// </summary>
    /// <param name="request">The request.</param>
    /// <param name="options">The options.</param>
    /// <exception cref="NotImplementedException">Method not implemented.</exception>
    private static void ResolveCrudFindInternal(ServiceRequest request, EntityQueryOptions options)
    {
        if (options.IncludePresentationFields.HasValue)
        {
            request.RequestBody.Entity.IncludePresentationFields = options
                .IncludePresentationFields
                .Value;
        }

        if (options.IncludeReferences.HasValue && !options.IncludeReferences.Value)
        {
            request.RequestBody.Entity.ReferencesFetch = null;
        }

        if (options.MaxReferenceDepth.HasValue)
        {
            throw new NotImplementedException();
        }

        if (options.MaxResults.HasValue)
        {
            request.RequestBody.Entity.RowsLimit = options.MaxResults.Value;
        }
    }
}