Skip to content

Commit f6e0bfe

Browse files
Erik Bylundkirre-bylund
authored andcommitted
Add support for new informative error messages
Also refactored a lot of error handling in the code.
1 parent 5e6d547 commit f6e0bfe

9 files changed

+172
-187
lines changed

Runtime/Client/LootLockerBaseServerAPI.cs

Lines changed: 86 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ public void SwitchURL(LootLocker.LootLockerEnums.LootLockerCallerRole mainCaller
6060
callerRole = mainCallerRole;
6161
}
6262

63+
private bool WebRequestSucceeded(UnityWebRequest webRequest)
64+
{
65+
return !
66+
#if UNITY_2020_1_OR_NEWER
67+
(webRequest.result == UnityWebRequest.Result.ProtocolError || webRequest.result == UnityWebRequest.Result.ConnectionError || !string.IsNullOrEmpty(webRequest.error));
68+
#else
69+
(webRequest.isHttpError || webRequest.isNetworkError || !string.IsNullOrEmpty(webRequest.error));
70+
#endif
71+
}
72+
6373
public void SendRequest(LootLockerServerRequest request, System.Action<LootLockerResponse> OnServerResponse = null)
6474
{
6575
StartCoroutine(coroutine());
@@ -97,7 +107,7 @@ IEnumerator coroutine()
97107
if (!webRequest.isDone && timedOut)
98108
{
99109
LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Warning)("Exceeded maxTimeOut waiting for a response from " + request.httpMethod + " " + url);
100-
OnServerResponse?.Invoke(new LootLockerResponse() { hasError = true, statusCode = 408, text = "{\"error\": \"" + request.endpoint + " Timed out.\"}", Error = request.endpoint + " Timed out." });
110+
OnServerResponse?.Invoke(LootLockerResponseFactory.Error<LootLockerResponse>(request.endpoint + " Timed out."));
101111
yield break;
102112
}
103113

@@ -112,55 +122,17 @@ IEnumerator coroutine()
112122
LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Error)(ObfuscateJsonStringForLogging(webRequest.downloadHandler.text));
113123
}
114124

115-
LootLockerResponse response = new LootLockerResponse();
116-
response.statusCode = (int)webRequest.responseCode;
117-
#if UNITY_2020_1_OR_NEWER
118-
if (webRequest.result == UnityWebRequest.Result.ProtocolError || webRequest.result == UnityWebRequest.Result.ConnectionError || !string.IsNullOrEmpty(webRequest.error))
119-
#else
120-
if (webRequest.isHttpError || webRequest.isNetworkError || !string.IsNullOrEmpty(webRequest.error))
121-
#endif
122-
125+
if(WebRequestSucceeded(webRequest))
123126
{
124-
switch (webRequest.responseCode)
125-
{
126-
case 200:
127-
response.Error = "";
128-
break;
129-
case 400:
130-
response.Error = "Bad Request -- Your request has an error";
131-
break;
132-
case 402:
133-
response.Error = "Payment Required -- Payment failed. Insufficient funds, etc.";
134-
break;
135-
case 401:
136-
response.Error = "Unauthorized -- Your session_token is invalid";
137-
break;
138-
case 403:
139-
response.Error = "Forbidden -- You do not have access";
140-
break;
141-
case 404:
142-
response.Error = "Not Found";
143-
break;
144-
case 405:
145-
response.Error = "Method Not Allowed";
146-
break;
147-
case 406:
148-
response.Error = "Not Acceptable -- Purchasing is disabled";
149-
break;
150-
case 409:
151-
response.Error = "Conflict -- Your state is most likely not aligned with the servers.";
152-
break;
153-
case 429:
154-
response.Error = "Too Many Requests -- You're being limited for sending too many requests too quickly.";
155-
break;
156-
case 500:
157-
response.Error = "Internal Server Error -- We had a problem with our server. Try again later.";
158-
break;
159-
case 503:
160-
response.Error = "Service Unavailable -- We're either offline for maintenance, or an error that should be solvable by calling again later was triggered.";
161-
break;
162-
}
163-
127+
LootLockerResponse response = new LootLockerResponse();
128+
response.statusCode = (int)webRequest.responseCode;
129+
response.success = true;
130+
response.text = webRequest.downloadHandler.text;
131+
response.errorData = null;
132+
OnServerResponse?.Invoke(response);
133+
}
134+
else
135+
{
164136
if ((webRequest.responseCode == 401 || webRequest.responseCode == 403) && LootLockerConfig.current.allowTokenRefresh && CurrentPlatform.Get() != Platforms.Steam && tries < maxRetry)
165137
{
166138
tries++;
@@ -170,23 +142,75 @@ IEnumerator coroutine()
170142
else
171143
{
172144
tries = 0;
173-
response.Error += " " + ObfuscateJsonStringForLogging(webRequest.downloadHandler.text);
145+
LootLockerResponse response = new LootLockerResponse();
174146
response.statusCode = (int)webRequest.responseCode;
175147
response.success = false;
176-
response.hasError = true;
177148
response.text = webRequest.downloadHandler.text;
178-
LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Error)(response.Error);
149+
150+
LootLockerErrorData errorData = LootLockerJson.DeserializeObject<LootLockerErrorData>(webRequest.downloadHandler.text);
151+
// Check if the error uses the "new" error style (https://docs.lootlocker.com/reference/error-codes)
152+
if (errorData != null && !string.IsNullOrEmpty(errorData.code))
153+
{
154+
response.errorData = errorData;
155+
}
156+
else
157+
{
158+
errorData = new LootLockerErrorData();
159+
switch (webRequest.responseCode)
160+
{
161+
case 400:
162+
errorData.message = "Bad Request -- Your request has an error.";
163+
errorData.code = "bad_request";
164+
break;
165+
case 401:
166+
errorData.message = "Unauthorized -- Your session_token is invalid.";
167+
errorData.code = "unauthorized";
168+
break;
169+
case 402:
170+
errorData.message = "Payment Required -- Payment failed. Insufficient funds, etc.";
171+
errorData.code = "payment_required";
172+
break;
173+
case 403:
174+
errorData.message = "Forbidden -- You do not have access to this resource.";
175+
errorData.code = "forbidden";
176+
break;
177+
case 404:
178+
errorData.message = "Not Found -- The requested resource could not be found.";
179+
errorData.code = "not_found";
180+
break;
181+
case 405:
182+
errorData.message = "Method Not Allowed -- The selected http method is invalid for this resource.";
183+
errorData.code = "method_not_allowed";
184+
break;
185+
case 406:
186+
errorData.message = "Not Acceptable -- Purchasing is disabled.";
187+
errorData.code = "not_acceptable";
188+
break;
189+
case 409:
190+
errorData.message = "Conflict -- Your state is most likely not aligned with the servers.";
191+
errorData.code = "conflict";
192+
break;
193+
case 429:
194+
errorData.message = "Too Many Requests -- You're being limited for sending too many requests too quickly.";
195+
errorData.code = "too_many_requests";
196+
break;
197+
case 500:
198+
errorData.message = "Internal Server Error -- We had a problem with our server. Try again later.";
199+
errorData.code = "internal_server_error";
200+
break;
201+
case 503:
202+
errorData.message = "Service Unavailable -- We're either offline for maintenance, or an error that should be solvable by calling again later was triggered.";
203+
errorData.code = "service_unavailable";
204+
break;
205+
default:
206+
errorData.message = "Unknown error.";
207+
break;
208+
}
209+
}
210+
errorData.message += " " + ObfuscateJsonStringForLogging(webRequest.downloadHandler.text);
211+
LootLockerLogger.GetForLogLevel(LootLockerLogger.LogLevel.Error)(errorData.message);
179212
OnServerResponse?.Invoke(response);
180213
}
181-
182-
}
183-
else
184-
{
185-
response.success = true;
186-
response.hasError = false;
187-
response.statusCode = (int)webRequest.responseCode;
188-
response.text = webRequest.downloadHandler.text;
189-
OnServerResponse?.Invoke(response);
190214
}
191215

192216
}
@@ -278,7 +302,7 @@ UnityWebRequest CreateWebRequest(string url, LootLockerServerRequest request)
278302
// Set the content type - NO QUOTES around the boundary
279303
string contentType = String.Concat("multipart/form-data; boundary=--", Encoding.UTF8.GetString(boundary));
280304

281-
// Make my request object and add the raw body. Set anything else you need here
305+
// Make my request object and add the raw text. Set anything else you need here
282306
webRequest = new UnityWebRequest();
283307
webRequest.SetRequestHeader("Content-Type", "multipart/form-data; boundary=--");
284308
webRequest.uri = new Uri(url);

Runtime/Client/LootLockerServerRequest.cs

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -90,35 +90,75 @@ public enum LootLockerHTTPMethod
9090
UPDATE_FILE = 9
9191
}
9292

93+
public class LootLockerErrorData
94+
{
95+
/// <summary>
96+
/// A descriptive code identifying the error.
97+
/// </summary>
98+
public string code { get; set; }
99+
100+
/// <summary>
101+
/// A link to further documentation on the error.
102+
/// </summary>
103+
public string doc_url { get; set; }
104+
105+
/// <summary>
106+
/// A unique identifier of the request to use in contact with support.
107+
/// </summary>
108+
public string request_id { get; set; }
109+
110+
/// <summary>
111+
/// A unique identifier for tracing the request through LootLocker systems, use this in contact with support.
112+
/// </summary>
113+
public string trace_id { get; set; }
114+
115+
/// <summary>
116+
/// If the request was not a success this property will hold any error messages
117+
/// </summary>
118+
public string message { get; set; }
119+
}
120+
93121
/// <summary>
94122
/// All ServerAPI.SendRequest responses will invoke the callback using an instance of this class for easier handling in client code.
95123
/// </summary>
96124
public class LootLockerResponse
97125
{
98126
/// <summary>
99-
/// TRUE if http error OR server returns an error status
127+
/// HTTP Status Code
100128
/// </summary>
101-
public bool hasError { get; set; }
129+
public int statusCode { get; set; }
102130

103131
/// <summary>
104-
/// HTTP Status Code
132+
/// Whether this request was a success
105133
/// </summary>
106-
public int statusCode { get; set; }
134+
public bool success { get; set; }
107135

108136
/// <summary>
109-
/// Raw text response from the server
110-
/// <para>If hasError = true, this will contain the error message.</para>
137+
/// Raw text/http body from the server response
111138
/// </summary>
112139
public string text { get; set; }
113140

114-
public bool success { get; set; }
141+
/// <summary>
142+
/// If this request was not a success, this structure holds all the information needed to identify the problem
143+
/// </summary>
144+
public LootLockerErrorData errorData { get; set; }
115145

146+
/// <summary>
147+
/// TRUE if http error OR server returns an error status
148+
/// </summary>
149+
[Obsolete("This property is deprecated, use success instead")]
150+
public bool hasError { get { return !success; } }
116151

117-
public string Error { get; set; }
152+
/// <summary>
153+
/// If the request was not a success this property will hold any error messages
154+
/// </summary>
155+
[Obsolete("This property is deprecated, replaced by the errorData.message property")]
156+
public string Error { get { return errorData?.message; } }
118157

119158
/// <summary>
120159
/// A texture downloaded in the webrequest, if applicable, otherwise this will be null.
121160
/// </summary>
161+
[Obsolete("This property is deprecated")]
122162
public Texture2D texture { get; set; }
123163

124164
/// <summary>
@@ -150,18 +190,18 @@ public static T Deserialize<T>(LootLockerResponse serverResponse,
150190
{
151191
if (serverResponse == null)
152192
{
153-
return new T() { success = false, Error = "Unknown error, please check your internet connection." };
193+
return LootLockerResponseFactory.Error<T>("Unknown error, please check your internet connection.");
154194
}
155-
else if (!string.IsNullOrEmpty(serverResponse.Error))
195+
else if (serverResponse.errorData != null && !string.IsNullOrEmpty(serverResponse.errorData.code))
156196
{
157-
return new T() { success = false, Error = serverResponse.Error, statusCode = serverResponse.statusCode };
197+
return new T() { success = false, errorData = serverResponse.errorData, statusCode = serverResponse.statusCode };
158198
}
159199

160200
var response = LootLockerJson.DeserializeObject<T>(serverResponse.text, options ?? LootLockerJsonSettings.Default) ?? new T();
161201

162202
response.text = serverResponse.text;
163203
response.success = serverResponse.success;
164-
response.Error = serverResponse.Error;
204+
response.errorData = serverResponse.errorData;
165205
response.statusCode = serverResponse.statusCode;
166206

167207
return response;
@@ -182,14 +222,14 @@ public class LootLockerResponseFactory
182222
/// <summary>
183223
/// Construct an error response to send to the client.
184224
/// </summary>
185-
public static T Error<T>(string errorMessage) where T : LootLockerResponse, new()
225+
public static T Error<T>(string errorMessage, int statusCode = 0) where T : LootLockerResponse, new()
186226
{
187227
return new T()
188228
{
189229
success = false,
190-
hasError = true,
191-
Error = errorMessage,
192-
text = errorMessage
230+
text = errorMessage,
231+
statusCode = statusCode,
232+
errorData = new LootLockerErrorData() { message = errorMessage }
193233
};
194234
}
195235

0 commit comments

Comments
 (0)