All Downloads are FREE. Search and download functionalities are using the official Maven repository.

csharp.libraries.unityWebRequest.ApiClient.mustache Maven / Gradle / Ivy

There is a newer version: 7.6.0
Show newest version
{{>partial_header}}

using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Text;
using System.Threading;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using ErrorEventArgs = Newtonsoft.Json.Serialization.ErrorEventArgs;
using System.Net.Http;
using System.Net.Http.Headers;
using UnityEngine.Networking;
using UnityEngine;

namespace {{packageName}}.Client
{
    /// 
    /// To Serialize/Deserialize JSON using our custom logic, but only when ContentType is JSON.
    /// 
    internal class CustomJsonCodec
    {
        private readonly IReadableConfiguration _configuration;
        private static readonly string _contentType = "application/json";
        private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings
        {
            // OpenAPI generated types generally hide default constructors.
            ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
            ContractResolver = new DefaultContractResolver
            {
                NamingStrategy = new CamelCaseNamingStrategy
                {
                    OverrideSpecifiedNames = false
                }
            }
        };

        public CustomJsonCodec(IReadableConfiguration configuration)
        {
            _configuration = configuration;
        }

        public CustomJsonCodec(JsonSerializerSettings serializerSettings, IReadableConfiguration configuration)
        {
            _serializerSettings = serializerSettings;
            _configuration = configuration;
        }

        /// 
        /// Serialize the object into a JSON string.
        /// 
        /// Object to be serialized.
        /// A JSON string.
        public string Serialize(object obj)
        {
            if (obj != null && obj is {{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema)
            {
                // the object to be serialized is an oneOf/anyOf schema
                return (({{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema)obj).ToJson();
            }
            else
            {
                return JsonConvert.SerializeObject(obj, _serializerSettings);
            }
        }

        public T Deserialize(UnityWebRequest request)
        {
            var result = (T) Deserialize(request, typeof(T));
            return result;
        }

        /// 
        /// Deserialize the JSON string into a proper object.
        /// 
        /// The UnityWebRequest after it has a response.
        /// Object type.
        /// Object representation of the JSON string.
        internal object Deserialize(UnityWebRequest request, Type type)
        {
            if (type == typeof(byte[])) // return byte array
            {
                return request.downloadHandler.data;
            }

            // TODO: ? if (type.IsAssignableFrom(typeof(Stream)))
            if (type == typeof(Stream))
            {
                // NOTE: Ignoring Content-Disposition filename support, since not all platforms
                // have a location on disk to write arbitrary data (tvOS, consoles).
                return new MemoryStream(request.downloadHandler.data);
            }

            if (type.Name.StartsWith("System.Nullable`1[[System.DateTime")) // return a datetime object
            {
                return DateTime.Parse(request.downloadHandler.text, null, System.Globalization.DateTimeStyles.RoundtripKind);
            }

            if (type == typeof(string) || type.Name.StartsWith("System.Nullable")) // return primitive type
            {
                return Convert.ChangeType(request.downloadHandler.text, type);
            }

            var contentType = request.GetResponseHeader("Content-Type");

            if (!string.IsNullOrEmpty(contentType) && contentType.Contains("application/json"))
            {
                var text = request.downloadHandler?.text;

                // Generated APIs that don't expect a return value provide System.Object as the type
                if (type == typeof(System.Object) && (string.IsNullOrEmpty(text) || text.Trim() == "null"))
                {
                    return null;
                }

                if (request.responseCode >= 200 && request.responseCode < 300)
                {
                    try
                    {
                        // Deserialize as a model
                        return JsonConvert.DeserializeObject(text, type, _serializerSettings);
                    }
                    catch (Exception e)
                    {
                        throw new UnexpectedResponseException(request, type, e.ToString());
                    }
                }
                else
                {
                    throw new ApiException((int)request.responseCode, request.error, text);
                }
            }
            
            if (type != typeof(System.Object) && request.responseCode >= 200 && request.responseCode < 300)
            {
                throw new UnexpectedResponseException(request, type);
            }

            return null;

        }

        public string RootElement { get; set; }
        public string Namespace { get; set; }
        public string DateFormat { get; set; }

        public string ContentType
        {
            get { return _contentType; }
            set { throw new InvalidOperationException("Not allowed to set content type."); }
        }
    }
    /// 
    /// Provides a default implementation of an Api client (both synchronous and asynchronous implementations),
    /// encapsulating general REST accessor use cases.
    /// 
    /// 
    /// The Dispose method will manage the HttpClient lifecycle when not passed by constructor.
    /// 
    {{>visibility}} partial class ApiClient : IDisposable, ISynchronousClient{{#supportsAsync}}, IAsynchronousClient{{/supportsAsync}}
    {
        private readonly string _baseUrl;

        /// 
        /// Specifies the settings on a  object.
        /// These settings can be adjusted to accommodate custom serialization rules.
        /// 
        public JsonSerializerSettings SerializerSettings { get; set; } = new JsonSerializerSettings
        {
            // OpenAPI generated types generally hide default constructors.
            ConstructorHandling = ConstructorHandling.AllowNonPublicDefaultConstructor,
            ContractResolver = new DefaultContractResolver
            {
                NamingStrategy = new CamelCaseNamingStrategy
                {
                    OverrideSpecifiedNames = false
                }
            }
        };

        /// 
        /// Initializes a new instance of the , defaulting to the global configurations' base url.
        /// 
        public ApiClient() :
                 this({{packageName}}.Client.GlobalConfiguration.Instance.BasePath)
        {
        }

        /// 
        /// Initializes a new instance of the .
        /// 
        /// The target service's base path in URL format.
        /// 
        public ApiClient(string basePath)
        {
            if (string.IsNullOrEmpty(basePath)) throw new ArgumentException("basePath cannot be empty");

            _baseUrl = basePath;
        }

        /// 
        /// Disposes resources if they were created by us
        /// 
        public void Dispose()
        {
        }

        /// 
        /// Provides all logic for constructing a new UnityWebRequest.
        /// At this point, all information for querying the service is known. Here, it is simply
        /// mapped into the UnityWebRequest.
        /// 
        /// The http verb.
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// [private] A new UnityWebRequest instance.
        /// 
        private UnityWebRequest NewRequest(
            string method,
            string path,
            RequestOptions options,
            IReadableConfiguration configuration)
        {
            if (path == null) throw new ArgumentNullException("path");
            if (options == null) throw new ArgumentNullException("options");
            if (configuration == null) throw new ArgumentNullException("configuration");

            WebRequestPathBuilder builder = new WebRequestPathBuilder(_baseUrl, path);

            builder.AddPathParameters(options.PathParameters);

            builder.AddQueryParameters(options.QueryParameters);

            string contentType = null;
            if (options.HeaderParameters != null && options.HeaderParameters.ContainsKey("Content-Type"))
            {
                var contentTypes = options.HeaderParameters["Content-Type"];
                contentType = contentTypes.FirstOrDefault();
            }

            var uri = builder.GetFullUri();
            UnityWebRequest request = null;

            if (contentType == "multipart/form-data")
            {
                var formData = new List();
                foreach (var formParameter in options.FormParameters)
                {
                    formData.Add(new MultipartFormDataSection(formParameter.Key, formParameter.Value));
                }

                request = UnityWebRequest.Post(uri, formData);
                request.method = method;
            }
            else if (contentType == "application/x-www-form-urlencoded")
            {
                var form = new WWWForm();
                foreach (var kvp in options.FormParameters)
                {
                    form.AddField(kvp.Key, kvp.Value);
                }

                request = UnityWebRequest.Post(uri, form);
                request.method = method;
            }
            else if (options.Data != null)
            {
                var serializer = new CustomJsonCodec(SerializerSettings, configuration);
                var jsonData = serializer.Serialize(options.Data);

                // Making a post body application/json encoded is whack with UnityWebRequest.
                // See: https://stackoverflow.com/questions/68156230/unitywebrequest-post-not-sending-body
                request = UnityWebRequest.Put(uri, jsonData);
                request.method = method;
                request.SetRequestHeader("Content-Type", "application/json");
            }
            else
            {
                request = new UnityWebRequest(builder.GetFullUri(), method);
            }

            if (request.downloadHandler == null && typeof(T) != typeof(System.Object))
            {
                request.downloadHandler = new DownloadHandlerBuffer();
            }

#if UNITY_EDITOR || !UNITY_WEBGL
            if (configuration.UserAgent != null)
            {
                request.SetRequestHeader("User-Agent", configuration.UserAgent);
            }
#endif

            if (configuration.DefaultHeaders != null)
            {
                foreach (var headerParam in configuration.DefaultHeaders)
                {
                    request.SetRequestHeader(headerParam.Key, headerParam.Value);
                }
            }

            if (options.HeaderParameters != null)
            {
                foreach (var headerParam in options.HeaderParameters)
                {
                    foreach (var value in headerParam.Value)
                    {
                        // Todo make content headers actually content headers
                        request.SetRequestHeader(headerParam.Key, value);
                    }
                }
            }

            if (options.Cookies != null && options.Cookies.Count > 0)
            {
                #if UNITY_WEBGL
                throw new System.InvalidOperationException("UnityWebRequest does not support setting cookies in WebGL");
                #else
                if (options.Cookies.Count != 1)
                {
                    UnityEngine.Debug.LogError("Only one cookie supported, ignoring others");
                }

                request.SetRequestHeader("Cookie", options.Cookies[0].ToString());
                #endif
            }

            return request;

        }

        partial void InterceptRequest(UnityWebRequest req, string path, RequestOptions options, IReadableConfiguration configuration);
        partial void InterceptResponse(UnityWebRequest req, string path, RequestOptions options, IReadableConfiguration configuration, ref object responseData);

        private ApiResponse ToApiResponse(UnityWebRequest request, object responseData)
        {
            T result = (T) responseData;

            var transformed = new ApiResponse((HttpStatusCode)request.responseCode, new Multimap({{#caseInsensitiveResponseHeaders}}StringComparer.OrdinalIgnoreCase{{/caseInsensitiveResponseHeaders}}), result, request.downloadHandler?.text ?? "")
            {
                ErrorText = request.error,
                Cookies = new List()
            };

            // process response headers, e.g. Access-Control-Allow-Methods
            var responseHeaders = request.GetResponseHeaders();
            if (responseHeaders != null)
            {
                foreach (var responseHeader in request.GetResponseHeaders())
                {
                    transformed.Headers.Add(responseHeader.Key, ClientUtils.ParameterToString(responseHeader.Value));
                }
            }

            return transformed;
        }

        private async Task> ExecAsync(
            UnityWebRequest request,
            string path,
            RequestOptions options,
            IReadableConfiguration configuration,
            System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
        {
            var deserializer = new CustomJsonCodec(SerializerSettings, configuration);

            using (request)
            {
                if (configuration.Timeout > 0)
                {
                    request.timeout = (int)Math.Ceiling(configuration.Timeout / 1000.0f);
                }

                if (configuration.Proxy != null)
                {
                    throw new InvalidOperationException("Configuration `Proxy` not supported by UnityWebRequest");
                }

                if (configuration.ClientCertificates != null)
                {
                    // Only Android/iOS/tvOS/Standalone players can support certificates, and this
                    // implementation is intended to work on all platforms.
                    //
                    // TODO: Could optionally allow support for this on these platforms.
                    //
                    // See: https://docs.unity3d.com/ScriptReference/Networking.CertificateHandler.html
                    throw new InvalidOperationException("Configuration `ClientCertificates` not supported by UnityWebRequest on all platforms");
                }

                InterceptRequest(request, path, options, configuration);

                var asyncOp = request.SendWebRequest();

                TaskCompletionSource tsc = new TaskCompletionSource();
                asyncOp.completed += (_) => tsc.TrySetResult(request.result);

                using (var tokenRegistration = cancellationToken.Register(request.Abort, true))
                {
                    await tsc.Task;
                }
                
                if (request.result == UnityWebRequest.Result.ConnectionError ||
                    request.result == UnityWebRequest.Result.DataProcessingError)
                {
                    throw new ConnectionException(request);
                }

                object responseData = deserializer.Deserialize(request);

                // if the response type is oneOf/anyOf, call FromJSON to deserialize the data
                if (typeof({{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema).IsAssignableFrom(typeof(T)))
                {
                    responseData = (T) typeof(T).GetMethod("FromJson").Invoke(null, new object[] { new ByteArrayContent(request.downloadHandler.data) });
                }
                else if (typeof(T).Name == "Stream") // for binary response
                {
                    responseData = (T) (object) new MemoryStream(request.downloadHandler.data);
                }

                InterceptResponse(request, path, options, configuration, ref responseData);

                return ToApiResponse(request, responseData);
            }
        }

        {{#supportsAsync}}
        #region IAsynchronousClient
        /// 
        /// Make a HTTP GET request (async).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// Token that enables callers to cancel the request.
        /// A Task containing ApiResponse
        public Task> GetAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
        {
            var config = configuration ?? GlobalConfiguration.Instance;
            return ExecAsync(NewRequest("GET", path, options, config), path, options, config, cancellationToken);
        }

        /// 
        /// Make a HTTP POST request (async).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// Token that enables callers to cancel the request.
        /// A Task containing ApiResponse
        public Task> PostAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
        {
            var config = configuration ?? GlobalConfiguration.Instance;
            return ExecAsync(NewRequest("POST", path, options, config), path, options, config, cancellationToken);
        }

        /// 
        /// Make a HTTP PUT request (async).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// Token that enables callers to cancel the request.
        /// A Task containing ApiResponse
        public Task> PutAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
        {
            var config = configuration ?? GlobalConfiguration.Instance;
            return ExecAsync(NewRequest("PUT", path, options, config), path, options, config, cancellationToken);
        }

        /// 
        /// Make a HTTP DELETE request (async).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// Token that enables callers to cancel the request.
        /// A Task containing ApiResponse
        public Task> DeleteAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
        {
            var config = configuration ?? GlobalConfiguration.Instance;
            return ExecAsync(NewRequest("DELETE", path, options, config), path, options, config, cancellationToken);
        }

        /// 
        /// Make a HTTP HEAD request (async).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// Token that enables callers to cancel the request.
        /// A Task containing ApiResponse
        public Task> HeadAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
        {
            var config = configuration ?? GlobalConfiguration.Instance;
            return ExecAsync(NewRequest("HEAD", path, options, config), path, options, config, cancellationToken);
        }

        /// 
        /// Make a HTTP OPTION request (async).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// Token that enables callers to cancel the request.
        /// A Task containing ApiResponse
        public Task> OptionsAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
        {
            var config = configuration ?? GlobalConfiguration.Instance;
            return ExecAsync(NewRequest("OPTIONS", path, options, config), path, options, config, cancellationToken);
        }

        /// 
        /// Make a HTTP PATCH request (async).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// Token that enables callers to cancel the request.
        /// A Task containing ApiResponse
        public Task> PatchAsync(string path, RequestOptions options, IReadableConfiguration configuration = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken))
        {
            var config = configuration ?? GlobalConfiguration.Instance;
            return ExecAsync(NewRequest("PATCH", path, options, config), path, options, config, cancellationToken);
        }
        #endregion IAsynchronousClient
        {{/supportsAsync}}

        #region ISynchronousClient
        /// 
        /// Make a HTTP GET request (synchronous).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// A Task containing ApiResponse
        public ApiResponse Get(string path, RequestOptions options, IReadableConfiguration configuration = null)
        {
            throw new System.NotImplementedException("UnityWebRequest does not support synchronous operation");
        }

        /// 
        /// Make a HTTP POST request (synchronous).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// A Task containing ApiResponse
        public ApiResponse Post(string path, RequestOptions options, IReadableConfiguration configuration = null)
        {
            throw new System.NotImplementedException("UnityWebRequest does not support synchronous operation");
        }

        /// 
        /// Make a HTTP PUT request (synchronous).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// A Task containing ApiResponse
        public ApiResponse Put(string path, RequestOptions options, IReadableConfiguration configuration = null)
        {
            throw new System.NotImplementedException("UnityWebRequest does not support synchronous operation");
        }

        /// 
        /// Make a HTTP DELETE request (synchronous).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// A Task containing ApiResponse
        public ApiResponse Delete(string path, RequestOptions options, IReadableConfiguration configuration = null)
        {
            throw new System.NotImplementedException("UnityWebRequest does not support synchronous operation");
        }

        /// 
        /// Make a HTTP HEAD request (synchronous).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// A Task containing ApiResponse
        public ApiResponse Head(string path, RequestOptions options, IReadableConfiguration configuration = null)
        {
            throw new System.NotImplementedException("UnityWebRequest does not support synchronous operation");
        }

        /// 
        /// Make a HTTP OPTION request (synchronous).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// A Task containing ApiResponse
        public ApiResponse Options(string path, RequestOptions options, IReadableConfiguration configuration = null)
        {
            throw new System.NotImplementedException("UnityWebRequest does not support synchronous operation");
        }

        /// 
        /// Make a HTTP PATCH request (synchronous).
        /// 
        /// The target path (or resource).
        /// The additional request options.
        /// A per-request configuration object. It is assumed that any merge with
        /// GlobalConfiguration has been done before calling this method.
        /// A Task containing ApiResponse
        public ApiResponse Patch(string path, RequestOptions options, IReadableConfiguration configuration = null)
        {
            throw new System.NotImplementedException("UnityWebRequest does not support synchronous operation");
        }
        #endregion ISynchronousClient
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy