Skip to content

Commit

Permalink
feat: throw BoxApiError in C# fetch, add additional data to ResponseI…
Browse files Browse the repository at this point in the history
  • Loading branch information
box-sdk-build authored Mar 1, 2024
1 parent 480a710 commit 6dce6d7
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 88 deletions.
2 changes: 1 addition & 1 deletion .codegen.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "engineHash": "905c6a0", "specHash": "b2f7568", "version": "0.1.0" }
{ "engineHash": "df5b5de", "specHash": "b2f7568", "version": "0.1.0" }
54 changes: 0 additions & 54 deletions Box.Sdk.Gen/Box/ApiException.cs

This file was deleted.

17 changes: 0 additions & 17 deletions Box.Sdk.Gen/Box/BoxSdkError.cs

This file was deleted.

2 changes: 1 addition & 1 deletion Box.Sdk.Gen/Box/CcgAuth/BoxCcgAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public BoxCcgAuth AsEnterprise(string enterpriseId, ITokenStorage? tokenStorage
public async System.Threading.Tasks.Task<AccessToken> DownscopeTokenAsync(IReadOnlyList<string> scopes, string? resource = null, string? sharedLink = null, NetworkSession? networkSession = null) {
AccessToken? token = await this.TokenStorage.GetAsync().ConfigureAwait(false);
if (token == null) {
throw new BoxSdkError(message: "No access token is available. Make an API call to retrieve a token before calling this method.");
throw new BoxSdkException(message: "No access token is available. Make an API call to retrieve a token before calling this method.");
}
AuthorizationManager authManager = networkSession != null ? new AuthorizationManager(networkSession: networkSession) : new AuthorizationManager();
AccessToken downscopedToken = await authManager.RequestAccessTokenAsync(requestBody: new PostOAuth2Token(grantType: PostOAuth2TokenGrantTypeField.UrnIetfParamsOauthGrantTypeTokenExchange) { SubjectToken = token.AccessTokenField, SubjectTokenType = PostOAuth2TokenSubjectTokenTypeField.UrnIetfParamsOauthTokenTypeAccessToken, Resource = resource, Scope = string.Join(" ", scopes), BoxSharedLink = sharedLink }).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public async System.Threading.Tasks.Task<AccessToken> RetrieveTokenAsync(Network
/// An object to keep network session state
/// </param>
public async System.Threading.Tasks.Task<AccessToken> RefreshTokenAsync(NetworkSession? networkSession = null) {
throw new BoxSdkError(message: "Developer token has expired. Please provide a new one.");
throw new BoxSdkException(message: "Developer token has expired. Please provide a new one.");
}

public async System.Threading.Tasks.Task<string> RetrieveAuthorizationHeaderAsync(NetworkSession? networkSession = null) {
Expand Down
17 changes: 17 additions & 0 deletions Box.Sdk.Gen/Box/Errors/BoxApiException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System;

namespace Errors
{
public class BoxApiException : BoxSdkException
{
public RequestInfo RequestInfo { get; set; }

public ResponseInfo ResponseInfo { get; set; }

public BoxApiException(string message, DateTimeOffset timeStamp, RequestInfo requestInfo, ResponseInfo responseInfo) : base(message, timeStamp, "BoxApiException")
{
RequestInfo = requestInfo;
ResponseInfo = responseInfo;
}
}
}
19 changes: 19 additions & 0 deletions Box.Sdk.Gen/Box/Errors/BoxSdkException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace Errors
{
public class BoxSdkException : Exception
{
public System.DateTimeOffset? Timestamp { get; set; } = default;

public string? Error { get; set; } = default;

public string Name { get; set; }

public BoxSdkException(string message, DateTimeOffset? timeStamp = null, string name = "BoxSdkException") : base(message)
{
Name = name;
Timestamp = timeStamp ?? DateTimeOffset.UtcNow;
}
}
}
26 changes: 26 additions & 0 deletions Box.Sdk.Gen/Box/Errors/RequestInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Errors
{
public class RequestInfo
{
public string Method { get; set; }

public string Url { get; set; }

public IReadOnlyDictionary<string, string> QueryParams { get; set; }

public IReadOnlyDictionary<string, string> Headers { get; set; }

public string? Body { get; set; } = default;

public RequestInfo(string method, string? url, IReadOnlyDictionary<string, string>? queryParams, IReadOnlyDictionary<string, string> headers)
{
Method = method;
Url = url ?? "";
QueryParams = queryParams ?? new ReadOnlyDictionary<string, string>(new Dictionary<string, string>());
Headers = headers;
}
}
}
56 changes: 56 additions & 0 deletions Box.Sdk.Gen/Box/Errors/ResponseInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using Serialization.Json;
using System.Collections.Generic;
using System.Text.Json.Serialization;

namespace Errors
{
public class ResponseInfo
{
public int StatusCode { get; set; }

public IReadOnlyDictionary<string, string> Headers { get; set; }

public SerializedData? Body { get; set; } = default;

public string? RawBody { get; set; } = default;

public string? Code { get; set; } = default;

public Dictionary<string, object>? ContextInfo { get; set; } = default;

public string? RequestId { get; set; } = default;

public string? HelpUrl { get; set; } = default;

public ResponseInfo(int statusCode, IReadOnlyDictionary<string, string> headers, SerializedData body, string rawBody,
string? code, Dictionary<string, object>? contextInfo, string? requestId, string? helpUrl)
{
StatusCode = statusCode;
Headers = headers;
Body = body;
RawBody = rawBody;
Code = code;
ContextInfo = contextInfo ?? new Dictionary<string, object>();
RequestId = requestId;
HelpUrl = helpUrl;
}
}

internal class BoxApiExceptionDetails
{
[JsonPropertyName("code")]
public string? Code { get; set; }

[JsonPropertyName("context_info")]
public Dictionary<string, object>? ContextInfo { get; set; }

[JsonPropertyName("request_id")]
public string? RequestId { get; set; }

[JsonPropertyName("help_url")]
public string? HelpUrl { get; set; }

public BoxApiExceptionDetails() { }
}

}
4 changes: 2 additions & 2 deletions Box.Sdk.Gen/Box/JwtAuth/BoxJwtAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public BoxJwtAuth(JwtConfig config) {
/// </param>
public async System.Threading.Tasks.Task<AccessToken> RefreshTokenAsync(NetworkSession? networkSession = null) {
if (Utils.IsBrowser()) {
throw new BoxSdkError(message: "JWT auth is not supported in browser environment.");
throw new BoxSdkException(message: "JWT auth is not supported in browser environment.");
}
JwtAlgorithm alg = this.Config.Algorithm != null ? NullableUtils.Unwrap(this.Config.Algorithm) : JwtAlgorithm.Rs256;
Dictionary<string, object> claims = new Dictionary<string, object>() { { "exp", Utils.GetEpochTimeInSeconds() + 30 }, { "box_sub_type", this.SubjectType } };
Expand Down Expand Up @@ -132,7 +132,7 @@ public BoxJwtAuth AsEnterprise(string userId, ITokenStorage? tokenStorage = defa
public async System.Threading.Tasks.Task<AccessToken> DownscopeTokenAsync(IReadOnlyList<string> scopes, string? resource = null, string? sharedLink = null, NetworkSession? networkSession = null) {
AccessToken? token = await this.TokenStorage.GetAsync().ConfigureAwait(false);
if (token == null) {
throw new BoxSdkError(message: "No access token is available. Make an API call to retrieve a token before calling this method.");
throw new BoxSdkException(message: "No access token is available. Make an API call to retrieve a token before calling this method.");
}
AuthorizationManager authManager = networkSession != null ? new AuthorizationManager(networkSession: networkSession) : new AuthorizationManager();
AccessToken downscopedToken = await authManager.RequestAccessTokenAsync(requestBody: new PostOAuth2Token(grantType: PostOAuth2TokenGrantTypeField.UrnIetfParamsOauthGrantTypeTokenExchange) { SubjectToken = token.AccessTokenField, SubjectTokenType = PostOAuth2TokenSubjectTokenTypeField.UrnIetfParamsOauthTokenTypeAccessToken, Resource = resource, Scope = string.Join(" ", scopes), BoxSharedLink = sharedLink }).ConfigureAwait(false);
Expand Down
4 changes: 2 additions & 2 deletions Box.Sdk.Gen/Box/Oauth/BoxOAuth.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public async System.Threading.Tasks.Task<AccessToken> GetTokensAuthorizationCode
public async System.Threading.Tasks.Task<AccessToken> RetrieveTokenAsync(NetworkSession? networkSession = null) {
AccessToken? token = await this.TokenStorage.GetAsync().ConfigureAwait(false);
if (token == null) {
throw new BoxSdkError(message: "Access and refresh tokens not available. Authenticate before making any API call first.");
throw new BoxSdkException(message: "Access and refresh tokens not available. Authenticate before making any API call first.");
}
return token;
}
Expand Down Expand Up @@ -119,7 +119,7 @@ public async System.Threading.Tasks.Task RevokeTokenAsync(NetworkSession? networ
public async System.Threading.Tasks.Task<AccessToken> DownscopeTokenAsync(IReadOnlyList<string> scopes, string? resource = null, string? sharedLink = null, NetworkSession? networkSession = null) {
AccessToken? token = await this.TokenStorage.GetAsync().ConfigureAwait(false);
if (token == null || token.AccessTokenField == null) {
throw new BoxSdkError(message: "No access token is available.");
throw new BoxSdkException(message: "No access token is available.");
}
AuthorizationManager authManager = networkSession != null ? new AuthorizationManager(networkSession: networkSession) : new AuthorizationManager();
AccessToken downscopedToken = await authManager.RequestAccessTokenAsync(requestBody: new PostOAuth2Token(grantType: PostOAuth2TokenGrantTypeField.UrnIetfParamsOauthGrantTypeTokenExchange) { SubjectToken = token.AccessTokenField, SubjectTokenType = PostOAuth2TokenSubjectTokenTypeField.UrnIetfParamsOauthTokenTypeAccessToken, Scope = string.Join(" ", scopes), Resource = resource, BoxSharedLink = sharedLink }).ConfigureAwait(false);
Expand Down
51 changes: 41 additions & 10 deletions Box.Sdk.Gen/Networking/Fetch.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Box.Sdk.Gen;
using Errors;
using Microsoft.Extensions.DependencyInjection;
using Serializer;
using System;
Expand Down Expand Up @@ -31,7 +32,7 @@ static HttpClientAdapter()
var httpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
if (httpClientFactory == null)
{
throw new ArgumentException("Unable to create HttpClient. Cannot get an IHttpClientFactory instance from a ServiceProvider.");
throw new BoxSdkException("Unable to create HttpClient. Cannot get an IHttpClientFactory instance from a ServiceProvider.");
}
_clientFactory = httpClientFactory;
}
Expand Down Expand Up @@ -67,7 +68,7 @@ public static async Task<FetchResponse> FetchAsync(string resource, FetchOptions

if (attempt >= networkSession.RetryAttempts)
{
throw await BuildApiException(response, statusCode, cancellationToken, "Max retry attempts excedeed.").ConfigureAwait(false);
throw await BuildApiException(request, response, options, statusCode, cancellationToken, "Max retry attempts excedeed.").ConfigureAwait(false);
}

if (statusCode == 401)
Expand All @@ -87,7 +88,7 @@ public static async Task<FetchResponse> FetchAsync(string resource, FetchOptions
}
else
{
throw await BuildApiException(response, statusCode, cancellationToken).ConfigureAwait(false);
throw await BuildApiException(request, response, options, statusCode, cancellationToken).ConfigureAwait(false);
}

response?.Dispose();
Expand All @@ -96,13 +97,43 @@ public static async Task<FetchResponse> FetchAsync(string resource, FetchOptions

}

private static async Task<Exception> BuildApiException(HttpResponseMessage? response, int statusCode, System.Threading.CancellationToken cancellationToken, string? message = null)
private static async Task<Exception> BuildApiException(HttpRequestMessage request, HttpResponseMessage? response, FetchOptions options,
int statusCode, System.Threading.CancellationToken cancellationToken, string? message = null)
{
var responseContent = response != null ? await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false) : "empty";
if (message != null)
{
return new BoxSdkException(message, DateTimeOffset.UtcNow);
}

string responseContent;
Dictionary<string, string> responseHeaders;

if (response != null)
{
responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
responseHeaders = response.Headers.Where(x => x.Value.Any()).ToDictionary(x => x.Key, x => x.Value.First());
}
else
{
responseContent = "empty";
responseHeaders = new Dictionary<string, string>();
}

response?.Dispose();
return message != null ?
new ApiException(statusCode, responseContent, ApiException.BuildApiExceptionMessage(statusCode, responseContent, message)) :
new ApiException(statusCode, responseContent);

var requestHeaders = request.Headers
.Where(x => x.Value.Any())
.ToDictionary(x => x.Key, x => x.Value.First());

var requestInfo = new RequestInfo(request.Method.ToString(), request.RequestUri?.ToString(), options.Parameters, requestHeaders);

var responseAsSerializedData = JsonUtils.JsonToSerializedData(responseContent);
var errorDetails = SimpleJsonSerializer.Deserialize<BoxApiExceptionDetails>(responseAsSerializedData);

var responseInfo = new ResponseInfo(statusCode, responseHeaders, responseAsSerializedData, responseContent, errorDetails.Code, errorDetails.ContextInfo,
errorDetails.RequestId, errorDetails.HelpUrl);

return new BoxApiException(responseContent, DateTimeOffset.UtcNow, requestInfo, responseInfo);
}

private static async Task<HttpRequestMessage> BuildHttpRequest(string resource, FetchOptions options)
Expand Down Expand Up @@ -177,7 +208,7 @@ private static async Task<HttpResponseMessage> ExecuteRequest(HttpClient client,

if (options.MultipartData == null)
{
throw new ArgumentException("Could not upload file. MultipartData on FetchOptions is null");
throw new BoxSdkException("Could not upload file. MultipartData on FetchOptions is null");
}

foreach (var part in options.MultipartData)
Expand All @@ -186,7 +217,7 @@ private static async Task<HttpResponseMessage> ExecuteRequest(HttpClient client,
new StreamContent(part.FileStream) :
part.Data != null ?
new StringContent(JsonUtils.SdToJson(part.Data)) :
throw new ArgumentException($"HttpContent for MultipartData {part} not found");
throw new BoxSdkException($"HttpContent for MultipartData {part} not found");

// for avatar upload
if (part.ContentType != null && part.FileName != null)
Expand Down

0 comments on commit 6dce6d7

Please sign in to comment.