diff --git a/src/onedrive.d b/src/onedrive.d index 2fd04674..61165359 100644 --- a/src/onedrive.d +++ b/src/onedrive.d @@ -1018,7 +1018,7 @@ class OneDriveApi { string[string] requestHeaders; const(char)[] url = driveByIdUrl ~ driveId ~ "/items/" ~ id; if (eTag) requestHeaders["If-Match"] = to!string(eTag); - return patch(url, data.toString(), requestHeaders); + return patch(url, data.toString(), false, requestHeaders); } // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_delete @@ -1158,7 +1158,7 @@ class OneDriveApi { const JSONValue request = [ "expirationDateTime": expirationDateTime.toISOExtString() ]; - return patch(url, request.toString()); + return patch(url, request.toString(), true); } // Delete Webhook subscription @@ -1761,8 +1761,8 @@ class OneDriveApi { }, validateJSONResponse, callingFunction, lineno); } - private JSONValue patch(const(char)[] url, const(char)[] patchData, string[string] requestHeaders=null, const(char)[] contentType = "application/json", string callingFunction=__FUNCTION__, int lineno=__LINE__) { - bool validateJSONResponse = true; + private JSONValue patch(const(char)[] url, const(char)[] patchData, bool validateJSONResponseInput, string[string] requestHeaders=null, const(char)[] contentType = "application/json", string callingFunction=__FUNCTION__, int lineno=__LINE__) { + bool validateJSONResponse = validateJSONResponseInput; return oneDriveErrorHandlerWrapper((CurlResponse response) { connect(HTTP.Method.patch, url, false, response, requestHeaders); curlEngine.setContent(contentType, patchData); @@ -1857,8 +1857,20 @@ class OneDriveApi { // Do we need to validate the JSON response? if (validateJSONResponse) { - if (result.type() != JSONType.object) { - throw new OneDriveException(0, "Caller request a non null JSON response, get null instead", response); + const code = response.statusLine.code; + + // 204 = No Content is a valid success response for some Graph operations (e.g. PATCH/DELETE). + // In that case, there is no JSON payload to validate. + if (code != 204) { + // If caller expects JSON, an empty body is not acceptable + if (response.content.length == 0) { + throw new OneDriveException( 0, "Caller requested a JSON object response, but the response body was empty", response); + } + + // Body is present: it must be a JSON object + if (result.type() != JSONType.object) { + throw new OneDriveException(0, "Caller requested a JSON object response, but the response was not a JSON object", response); + } } } diff --git a/src/sync.d b/src/sync.d index 87ca1995..bbe8394f 100644 --- a/src/sync.d +++ b/src/sync.d @@ -4950,9 +4950,13 @@ class SyncEngine { // Everything else should remain the same .. and then save this DB record to the DB .. // However, we did this, for the local modified file right before calling this function to update the online timestamp ... so .. do we need to do this again, effectively performing a double DB write for the same data? if ((originItem.type != ItemType.remote) && (originItem.remoteType != ItemType.file)) { - // Save the response JSON - // Is the response a valid JSON object - validation checking done in saveItem - saveItem(response); + if (response.type() == JSONType.object) { + // Save the response JSON + saveItem(response); + } else { + // Log why we are not saving + if (debugLogging) {addLogEntry("uploadLastModifiedTime: updateById returned no JSON payload (likely HTTP 204); skipping saveItem()", ["debug"]);} + } } } catch (OneDriveException exception) { // Handle a 409 - ETag does not match current item's value @@ -10539,9 +10543,17 @@ class SyncEngine { addLogEntry("ERROR: " ~ sanitiseJSONItem(jsonItem)); } } else { - // log error - addLogEntry("ERROR: An error was returned from OneDrive and the resulting response is not a valid JSON object that can be processed."); - addLogEntry("ERROR: Increase logging verbosity to assist determining why."); + // Log that the provided JSON could not be processed + addLogEntry("ERROR: Invalid JSON object - the provided data cannot be processed or stored in the database."); + + // What level of next message is provided? + if (appConfig.verbosityCount == 0) { + // Standard error message + addLogEntry("ERROR: Please rerun the application with --verbose enabled to obtain additional diagnostic information."); + } else { + // verbose or debug + addLogEntry("ERROR: The following JSON data failed validation and could not be saved: " ~ to!string(jsonItem)); + } } // Display function processing time if configured to do so @@ -11976,9 +11988,13 @@ class SyncEngine { // Perform Garbage Collection GC.collect(); - // save the move response from OneDrive in the database - // Is the response a valid JSON object - validation checking done in saveItem - saveItem(response); + // Save the move response from OneDrive in the database + if (isMoveSuccess && response.type() == JSONType.object) { + saveItem(response); + } else { + // Log why we are not saving + if (debugLogging) {addLogEntry("uploadMoveItem: skipping saveItem() (no JSON payload returned or move not successful)", ["debug"]);} + } } } else { // Moved item is unwanted