Skip to content

C# 8 phase 1 #472

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Oct 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ExchangeSharp.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27130.2027
# Visual Studio Version 16
VisualStudioVersion = 16.0.29318.209
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExchangeSharp", "ExchangeSharp\ExchangeSharp.csproj", "{B4ADDAEF-95BF-4FDA-8B2F-8B899EDA3F45}"
EndProject
Expand Down
44 changes: 19 additions & 25 deletions ExchangeSharp/API/Common/APIRequestMaker.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
MIT LICENSE

Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com
Expand All @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
Expand All @@ -33,7 +33,7 @@ private class InternalHttpWebRequest : IHttpWebRequest

public InternalHttpWebRequest(Uri fullUri)
{
request = HttpWebRequest.Create(fullUri) as HttpWebRequest;
request = (HttpWebRequest.Create(fullUri) as HttpWebRequest ?? throw new NullReferenceException("Failed to create HttpWebRequest"));
request.KeepAlive = false;
}

Expand Down Expand Up @@ -145,22 +145,18 @@ public APIRequestMaker(IAPIRequestHandler api)
/// The encoding of payload is API dependant but is typically json.</param>
/// <param name="method">Request method or null for default. Example: 'GET' or 'POST'.</param>
/// <returns>Raw response</returns>
public async Task<string> MakeRequestAsync(string url, string baseUrl = null, Dictionary<string, object> payload = null, string method = null)
public async Task<string> MakeRequestAsync(string url, string? baseUrl = null, Dictionary<string, object>? payload = null, string? method = null)
{
await new SynchronizationContextRemover();

await api.RateLimit.WaitToProceedAsync();
if (string.IsNullOrWhiteSpace(url))
{
return null;
}
else if (url[0] != '/')

if (url[0] != '/')
{
url = "/" + url;
}

string fullUrl = (baseUrl ?? api.BaseUrl) + url;
method = method ?? api.RequestMethod;
method ??= api.RequestMethod;
Uri uri = api.ProcessRequestUrl(new UriBuilder(fullUrl), payload, method);
InternalHttpWebRequest request = new InternalHttpWebRequest(uri)
{
Expand All @@ -171,8 +167,8 @@ public async Task<string> MakeRequestAsync(string url, string baseUrl = null, Di
request.AddHeader("user-agent", BaseAPI.RequestUserAgent);
request.Timeout = request.ReadWriteTimeout = (int)api.RequestTimeout.TotalMilliseconds;
await api.ProcessRequestAsync(request, payload);
HttpWebResponse response = null;
string responseString = null;
HttpWebResponse? response = null;
string responseString;

try
{
Expand All @@ -194,21 +190,19 @@ public async Task<string> MakeRequestAsync(string url, string baseUrl = null, Di
}
}
using (Stream responseStream = response.GetResponseStream())
using (StreamReader responseStreamReader = new StreamReader(responseStream))
responseString = responseStreamReader.ReadToEnd();
if (response.StatusCode != HttpStatusCode.OK)
{
responseString = new StreamReader(responseStream).ReadToEnd();
if (response.StatusCode != HttpStatusCode.OK)
// 404 maybe return empty responseString
if (string.IsNullOrWhiteSpace(responseString))
{
// 404 maybe return empty responseString
if (string.IsNullOrWhiteSpace(responseString))
{
throw new APIException(string.Format("{0} - {1}",
response.StatusCode.ConvertInvariant<int>(), response.StatusCode));
}
throw new APIException(responseString);
throw new APIException(string.Format("{0} - {1}", response.StatusCode.ConvertInvariant<int>(), response.StatusCode));
}
api.ProcessResponse(new InternalHttpWebResponse(response));
RequestStateChanged?.Invoke(this, RequestMakerState.Finished, responseString);
throw new APIException(responseString);
}
api.ProcessResponse(new InternalHttpWebResponse(response));
RequestStateChanged?.Invoke(this, RequestMakerState.Finished, responseString);
}
catch (Exception ex)
{
Expand All @@ -225,6 +219,6 @@ public async Task<string> MakeRequestAsync(string url, string baseUrl = null, Di
/// <summary>
/// An action to execute when a request has been made (this request and state and object (response or exception))
/// </summary>
public Action<IAPIRequestMaker, RequestMakerState, object> RequestStateChanged { get; set; }
public Action<IAPIRequestMaker, RequestMakerState, object>? RequestStateChanged { get; set; }
}
}
62 changes: 27 additions & 35 deletions ExchangeSharp/API/Common/BaseAPI.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
MIT LICENSE

Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com
Expand All @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
Expand Down Expand Up @@ -148,28 +148,28 @@ public IAPIRequestMaker RequestMaker
/// <summary>
/// Base URL for the API for web sockets
/// </summary>
public virtual string BaseUrlWebSocket { get; set; }
public virtual string BaseUrlWebSocket { get; set; } = string.Empty;

/// <summary>
/// Gets the name of the API
/// </summary>
public virtual string Name { get; private set; }
public virtual string Name { get; private set; } = string.Empty;

/// <summary>
/// Public API key - only needs to be set if you are using private authenticated end points. Please use CryptoUtility.SaveUnprotectedStringsToFile to store your API keys, never store them in plain text!
/// </summary>
public System.Security.SecureString PublicApiKey { get; set; }
public System.Security.SecureString? PublicApiKey { get; set; }

/// <summary>
/// Private API key - only needs to be set if you are using private authenticated end points. Please use CryptoUtility.SaveUnprotectedStringsToFile to store your API keys, never store them in plain text!
/// </summary>
public System.Security.SecureString PrivateApiKey { get; set; }
public System.Security.SecureString? PrivateApiKey { get; set; }

/// <summary>
/// Pass phrase API key - only needs to be set if you are using private authenticated end points. Please use CryptoUtility.SaveUnprotectedStringsToFile to store your API keys, never store them in plain text!
/// Most services do not require this, but Coinbase is an example of one that does
/// </summary>
public System.Security.SecureString Passphrase { get; set; }
public System.Security.SecureString? Passphrase { get; set; }

/// <summary>
/// Rate limiter - set this to a new limit if you are seeing your ip get blocked by the API
Expand Down Expand Up @@ -209,12 +209,12 @@ public IAPIRequestMaker RequestMaker
/// <summary>
/// The nonce end point for pulling down a server timestamp - override OnGetNonceOffset if you need custom handling
/// </summary>
public string NonceEndPoint { get; protected set; }
public string? NonceEndPoint { get; protected set; }

/// <summary>
/// The field in the json returned by the nonce end point to parse out - override OnGetNonceOffset if you need custom handling
/// </summary>
public string NonceEndPointField { get; protected set; }
public string? NonceEndPointField { get; protected set; }

/// <summary>
/// The type of value in the nonce end point field - override OnGetNonceOffset if you need custom handling.
Expand Down Expand Up @@ -297,7 +297,7 @@ public BaseAPI()
}
else
{
Name = (nameAttributes[0] as ApiNameAttribute).Name;
Name = (nameAttributes[0] as ApiNameAttribute)?.Name ?? string.Empty;
}
}

Expand Down Expand Up @@ -376,7 +376,7 @@ public async Task<object> GenerateNonceAsync()
{
// why an API would use a persistent incrementing counter for nonce is beyond me, ticks is so much better with a sliding window...
// making it required to increment by 1 is also a pain - especially when restarting a process or rebooting.
string tempFile = Path.Combine(Path.GetTempPath(), PublicApiKey.ToUnsecureString() + ".nonce");
string tempFile = Path.Combine(Path.GetTempPath(), (PublicApiKey?.ToUnsecureString() ?? "unknown_pub_key") + ".nonce");
if (!File.Exists(tempFile))
{
File.WriteAllText(tempFile, "0");
Expand Down Expand Up @@ -458,7 +458,7 @@ public void LoadAPIKeys(string encryptedFile)
/// <param name="publicApiKey">Public Api Key</param>
/// <param name="privateApiKey">Private Api Key</param>
/// <param name="passPhrase">Pass phrase, null for none</param>
public void LoadAPIKeysUnsecure(string publicApiKey, string privateApiKey, string passPhrase = null)
public void LoadAPIKeysUnsecure(string publicApiKey, string privateApiKey, string? passPhrase = null)
{
PublicApiKey = publicApiKey.ToSecureString();
PrivateApiKey = privateApiKey.ToSecureString();
Expand All @@ -474,7 +474,7 @@ public void LoadAPIKeysUnsecure(string publicApiKey, string privateApiKey, strin
/// The encoding of payload is API dependant but is typically json.
/// <param name="method">Request method or null for default</param>
/// <returns>Raw response</returns>
public Task<string> MakeRequestAsync(string url, string baseUrl = null, Dictionary<string, object> payload = null, string method = null) => requestMaker.MakeRequestAsync(url, baseUrl: baseUrl, payload: payload, method: method);
public Task<string> MakeRequestAsync(string url, string? baseUrl = null, Dictionary<string, object>? payload = null, string? method = null) => requestMaker.MakeRequestAsync(url, baseUrl: baseUrl, payload: payload, method: method);

/// <summary>
/// Make a JSON request to an API end point
Expand All @@ -485,7 +485,7 @@ public void LoadAPIKeysUnsecure(string publicApiKey, string privateApiKey, strin
/// <param name="payload">Payload, can be null. For private API end points, the payload must contain a 'nonce' key set to GenerateNonce value.</param>
/// <param name="requestMethod">Request method or null for default</param>
/// <returns>Result decoded from JSON response</returns>
public async Task<T> MakeJsonRequestAsync<T>(string url, string baseUrl = null, Dictionary<string, object> payload = null, string requestMethod = null)
public async Task<T> MakeJsonRequestAsync<T>(string url, string? baseUrl = null, Dictionary<string, object>? payload = null, string? requestMethod = null)
{
await new SynchronizationContextRemover();

Expand All @@ -509,8 +509,8 @@ public Task<IWebSocket> ConnectWebSocketAsync
(
string url,
Func<IWebSocket, byte[], Task> messageCallback,
WebSocketConnectionDelegate connectCallback = null,
WebSocketConnectionDelegate disconnectCallback = null
WebSocketConnectionDelegate? connectCallback = null,
WebSocketConnectionDelegate? disconnectCallback = null
)
{
if (messageCallback == null)
Expand Down Expand Up @@ -541,7 +541,7 @@ public Task<IWebSocket> ConnectWebSocketAsync
/// </summary>
/// <param name="payload">Payload to potentially send</param>
/// <returns>True if an authenticated request can be made with the payload, false otherwise</returns>
protected virtual bool CanMakeAuthenticatedRequest(IReadOnlyDictionary<string, object> payload)
protected virtual bool CanMakeAuthenticatedRequest(IReadOnlyDictionary<string, object>? payload)
{
return (PrivateApiKey != null && PublicApiKey != null && payload != null && payload.ContainsKey("nonce"));
}
Expand All @@ -552,7 +552,7 @@ protected virtual bool CanMakeAuthenticatedRequest(IReadOnlyDictionary<string, o
/// </summary>
/// <param name="request">Request</param>
/// <param name="payload">Payload</param>
protected virtual Task ProcessRequestAsync(IHttpWebRequest request, Dictionary<string, object> payload)
protected virtual Task ProcessRequestAsync(IHttpWebRequest request, Dictionary<string, object>? payload)
{
return Task.CompletedTask;
}
Expand All @@ -573,7 +573,7 @@ protected virtual void ProcessResponse(IHttpWebResponse response)
/// <param name="payload">Payload</param>
/// <param name="method">Method</param>
/// <returns>Updated url</returns>
protected virtual Uri ProcessRequestUrl(UriBuilder url, Dictionary<string, object> payload, string method)
protected virtual Uri ProcessRequestUrl(UriBuilder url, Dictionary<string, object>? payload, string? method)
{
return url.Uri;
}
Expand Down Expand Up @@ -656,22 +656,14 @@ protected virtual async Task OnGetNonceOffset()

try
{
JToken token = await MakeJsonRequestAsync<JToken>(NonceEndPoint);
JToken token = await MakeJsonRequestAsync<JToken>(NonceEndPoint!);
JToken value = token[NonceEndPointField];
DateTime serverDate;
switch (NonceEndPointStyle)
DateTime serverDate = NonceEndPointStyle switch
{
case NonceStyle.Iso8601:
serverDate = value.ToDateTimeInvariant();
break;

case NonceStyle.UnixMilliseconds:
serverDate = value.ConvertInvariant<long>().UnixTimeStampToDateTimeMilliseconds();
break;

default:
throw new ArgumentException("Invalid nonce end point style '" + NonceEndPointStyle + "' for exchange '" + Name + "'");
}
NonceStyle.Iso8601 => value.ToDateTimeInvariant(),
NonceStyle.UnixMilliseconds => value.ConvertInvariant<long>().UnixTimeStampToDateTimeMilliseconds(),
_ => throw new ArgumentException("Invalid nonce end point style '" + NonceEndPointStyle + "' for exchange '" + Name + "'"),
};
NonceOffset = (CryptoUtility.UtcNow - serverDate);
}
catch
Expand All @@ -681,7 +673,7 @@ protected virtual async Task OnGetNonceOffset()
}
}

async Task IAPIRequestHandler.ProcessRequestAsync(IHttpWebRequest request, Dictionary<string, object> payload)
async Task IAPIRequestHandler.ProcessRequestAsync(IHttpWebRequest request, Dictionary<string, object>? payload)
{
await ProcessRequestAsync(request, payload);
}
Expand All @@ -691,7 +683,7 @@ void IAPIRequestHandler.ProcessResponse(IHttpWebResponse response)
ProcessResponse(response);
}

Uri IAPIRequestHandler.ProcessRequestUrl(UriBuilder url, Dictionary<string, object> payload, string method)
Uri IAPIRequestHandler.ProcessRequestUrl(UriBuilder url, Dictionary<string, object>? payload, string? method)
{
return ProcessRequestUrl(url, payload, method);
}
Expand Down
15 changes: 8 additions & 7 deletions ExchangeSharp/API/Common/IAPIRequestMaker.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/*
/*
MIT LICENSE

Copyright 2017 Digital Ruby, LLC - http://www.digitalruby.com
Expand All @@ -9,7 +9,7 @@ The above copyright notice and this permission notice shall be included in all c

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

#nullable enable
using System;
using System.Collections.Generic;
using System.Net;
Expand Down Expand Up @@ -52,12 +52,13 @@ public interface IAPIRequestMaker
/// The encoding of payload is API dependant but is typically json.</param>
/// <param name="method">Request method or null for default</param>
/// <returns>Raw response</returns>
Task<string> MakeRequestAsync(string url, string baseUrl = null, Dictionary<string, object> payload = null, string method = null);
/// <exception cref="System.Exception">Request fails</exception>
Task<string> MakeRequestAsync(string url, string? baseUrl = null, Dictionary<string, object>? payload = null, string? method = null);

/// <summary>
/// An action to execute when a request has been made (this request and state and object (response or exception))
/// </summary>
Action<IAPIRequestMaker, RequestMakerState, object> RequestStateChanged { get; set; }
Action<IAPIRequestMaker, RequestMakerState, object>? RequestStateChanged { get; set; }
}

/// <summary>
Expand Down Expand Up @@ -130,7 +131,7 @@ public interface IAPIRequestHandler
/// </summary>
/// <param name="request">Request</param>
/// <param name="payload">Payload</param>
Task ProcessRequestAsync(IHttpWebRequest request, Dictionary<string, object> payload);
Task ProcessRequestAsync(IHttpWebRequest request, Dictionary<string, object>? payload);

/// <summary>
/// Additional handling for response
Expand All @@ -145,7 +146,7 @@ public interface IAPIRequestHandler
/// <param name="payload">Payload</param>
/// <param name="method">Method</param>
/// <returns>Updated url</returns>
Uri ProcessRequestUrl(UriBuilder url, Dictionary<string, object> payload, string method);
Uri ProcessRequestUrl(UriBuilder url, Dictionary<string, object>? payload, string method);

/// <summary>
/// Base url for the request
Expand Down Expand Up @@ -177,4 +178,4 @@ public interface IAPIRequestHandler
/// </summary>
RateGate RateLimit { get; }
}
}
}
Loading