From d64b538e3e3885fa9d1d681a2ebecb0bd9056bca Mon Sep 17 00:00:00 2001 From: Richard Ross Date: Fri, 13 Nov 2015 16:48:19 -0800 Subject: [PATCH 1/2] Remove hard-coded '/1/' from all API request URIs. This allows us to more easily do path redirection while performing tests. --- .../Controller/ParseAnalyticsController.cs | 4 ++-- .../Cloud/Controller/ParseCloudCodeController.cs | 2 +- .../Config/Controller/ParseConfigController.cs | 2 +- .../File/Controller/ParseFileController.cs | 2 +- .../Object/Controller/ParseObjectController.cs | 8 ++++---- .../Push/Controller/ParsePushController.cs | 2 +- .../Query/Controller/ParseQueryController.cs | 2 +- .../Session/Controller/ParseSessionController.cs | 6 +++--- .../User/Controller/ParseUserController.cs | 10 +++++----- Parse/ParseClient.cs | 2 +- ParseTest.Unit/AnalyticsControllerTests.cs | 6 +++--- ParseTest.Unit/CommandTests.cs | 14 +++++++------- ParseTest.Unit/ObjectControllerTests.cs | 2 +- ParseTest.Unit/SessionControllerTests.cs | 2 +- ParseTest.Unit/UserControllerTests.cs | 2 +- 15 files changed, 33 insertions(+), 33 deletions(-) diff --git a/Parse/Internal/Analytics/Controller/ParseAnalyticsController.cs b/Parse/Internal/Analytics/Controller/ParseAnalyticsController.cs index d49a20d3..326c287f 100644 --- a/Parse/Internal/Analytics/Controller/ParseAnalyticsController.cs +++ b/Parse/Internal/Analytics/Controller/ParseAnalyticsController.cs @@ -24,7 +24,7 @@ public Task TrackEventAsync(string name, data["dimensions"] = dimensions; } - var command = new ParseCommand("/1/events/" + name, + var command = new ParseCommand("events/" + name, method: "POST", sessionToken: sessionToken, data: PointerOrLocalIdEncoder.Instance.Encode(data) as IDictionary); @@ -42,7 +42,7 @@ public Task TrackAppOpenedAsync(string pushHash, data["push_hash"] = pushHash; } - var command = new ParseCommand("/1/events/AppOpened", + var command = new ParseCommand("events/AppOpened", method: "POST", sessionToken: sessionToken, data: PointerOrLocalIdEncoder.Instance.Encode(data) as IDictionary); diff --git a/Parse/Internal/Cloud/Controller/ParseCloudCodeController.cs b/Parse/Internal/Cloud/Controller/ParseCloudCodeController.cs index 22c13483..bd1239fe 100644 --- a/Parse/Internal/Cloud/Controller/ParseCloudCodeController.cs +++ b/Parse/Internal/Cloud/Controller/ParseCloudCodeController.cs @@ -17,7 +17,7 @@ public Task CallFunctionAsync(String name, IDictionary parameters, string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand(string.Format("/1/functions/{0}", Uri.EscapeUriString(name)), + var command = new ParseCommand(string.Format("functions/{0}", Uri.EscapeUriString(name)), method: "POST", sessionToken: sessionToken, data: NoObjectsEncoder.Instance.Encode(parameters) as IDictionary); diff --git a/Parse/Internal/Config/Controller/ParseConfigController.cs b/Parse/Internal/Config/Controller/ParseConfigController.cs index f3ffeea3..3e02b626 100644 --- a/Parse/Internal/Config/Controller/ParseConfigController.cs +++ b/Parse/Internal/Config/Controller/ParseConfigController.cs @@ -19,7 +19,7 @@ public ParseConfigController() { public IParseCurrentConfigController CurrentConfigController { get; internal set; } public Task FetchConfigAsync(String sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("/1/config", + var command = new ParseCommand("config", method: "GET", sessionToken: sessionToken, data: null); diff --git a/Parse/Internal/File/Controller/ParseFileController.cs b/Parse/Internal/File/Controller/ParseFileController.cs index f6ee6a5e..a766bc90 100644 --- a/Parse/Internal/File/Controller/ParseFileController.cs +++ b/Parse/Internal/File/Controller/ParseFileController.cs @@ -30,7 +30,7 @@ public Task SaveAsync(FileState state, } var oldPosition = dataStream.Position; - var command = new ParseCommand("/1/files/" + state.Name, + var command = new ParseCommand("files/" + state.Name, method: "POST", sessionToken: sessionToken, contentType: state.MimeType, diff --git a/Parse/Internal/Object/Controller/ParseObjectController.cs b/Parse/Internal/Object/Controller/ParseObjectController.cs index da6c86da..8dc899a8 100644 --- a/Parse/Internal/Object/Controller/ParseObjectController.cs +++ b/Parse/Internal/Object/Controller/ParseObjectController.cs @@ -17,7 +17,7 @@ internal ParseObjectController(IParseCommandRunner commandRunner) { public Task FetchAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand(string.Format("/1/classes/{0}/{1}", + var command = new ParseCommand(string.Format("classes/{0}/{1}", Uri.EscapeDataString(state.ClassName), Uri.EscapeDataString(state.ObjectId)), method: "GET", @@ -36,8 +36,8 @@ public Task SaveAsync(IObjectState state, var objectJSON = ParseObject.ToJSONObjectForSaving(operations); var command = new ParseCommand((state.ObjectId == null ? - string.Format("/1/classes/{0}", Uri.EscapeDataString(state.ClassName)) : - string.Format("/1/classes/{0}/{1}", Uri.EscapeDataString(state.ClassName), state.ObjectId)), + string.Format("classes/{0}", Uri.EscapeDataString(state.ClassName)) : + string.Format("classes/{0}/{1}", Uri.EscapeDataString(state.ClassName), state.ObjectId)), method: (state.ObjectId == null ? "POST" : "PUT"), sessionToken: sessionToken, data: objectJSON); @@ -78,7 +78,7 @@ public IList> SaveAllAsync(IList states, public Task DeleteAsync(IObjectState state, string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand(string.Format("/1/classes/{0}/{1}", + var command = new ParseCommand(string.Format("classes/{0}/{1}", state.ClassName, state.ObjectId), method: "DELETE", sessionToken: sessionToken, diff --git a/Parse/Internal/Push/Controller/ParsePushController.cs b/Parse/Internal/Push/Controller/ParsePushController.cs index de8bbfb9..26bf707e 100644 --- a/Parse/Internal/Push/Controller/ParsePushController.cs +++ b/Parse/Internal/Push/Controller/ParsePushController.cs @@ -8,7 +8,7 @@ namespace Parse.Internal { internal class ParsePushController : IParsePushController { public Task SendPushNotificationAsync(IPushState state, String sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("/1/push", + var command = new ParseCommand("push", method: "POST", sessionToken: sessionToken, data: ParsePushEncoder.Instance.Encode(state)); diff --git a/Parse/Internal/Query/Controller/ParseQueryController.cs b/Parse/Internal/Query/Controller/ParseQueryController.cs index 57a362d4..ad17bf71 100644 --- a/Parse/Internal/Query/Controller/ParseQueryController.cs +++ b/Parse/Internal/Query/Controller/ParseQueryController.cs @@ -59,7 +59,7 @@ private Task> FindAsync(string className, IDictionary parameters, string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand(string.Format("/1/classes/{0}?{1}", + var command = new ParseCommand(string.Format("classes/{0}?{1}", Uri.EscapeDataString(className), ParseClient.BuildQueryString(parameters)), method: "GET", diff --git a/Parse/Internal/Session/Controller/ParseSessionController.cs b/Parse/Internal/Session/Controller/ParseSessionController.cs index 966309f0..98b9e198 100644 --- a/Parse/Internal/Session/Controller/ParseSessionController.cs +++ b/Parse/Internal/Session/Controller/ParseSessionController.cs @@ -14,7 +14,7 @@ internal ParseSessionController(IParseCommandRunner commandRunner) { } public Task GetSessionAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("/1/sessions/me", + var command = new ParseCommand("sessions/me", method: "GET", sessionToken: sessionToken, data: null); @@ -25,7 +25,7 @@ public Task GetSessionAsync(string sessionToken, CancellationToken } public Task RevokeAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("/1/logout", + var command = new ParseCommand("logout", method: "POST", sessionToken: sessionToken, data: new Dictionary()); @@ -34,7 +34,7 @@ public Task RevokeAsync(string sessionToken, CancellationToken cancellationToken } public Task UpgradeToRevocableSessionAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("/1/upgradeToRevocableSession", + var command = new ParseCommand("upgradeToRevocableSession", method: "POST", sessionToken: sessionToken, data: new Dictionary()); diff --git a/Parse/Internal/User/Controller/ParseUserController.cs b/Parse/Internal/User/Controller/ParseUserController.cs index 83e46ec2..717453dd 100644 --- a/Parse/Internal/User/Controller/ParseUserController.cs +++ b/Parse/Internal/User/Controller/ParseUserController.cs @@ -18,7 +18,7 @@ public Task SignUpAsync(IObjectState state, CancellationToken cancellationToken) { var objectJSON = ParseObject.ToJSONObjectForSaving(operations); - var command = new ParseCommand("/1/classes/_User", + var command = new ParseCommand("classes/_User", method: "POST", data: objectJSON); @@ -39,7 +39,7 @@ public Task LogInAsync(string username, {"password", password} }; - var command = new ParseCommand(string.Format("/1/login?{0}", ParseClient.BuildQueryString(data)), + var command = new ParseCommand(string.Format("login?{0}", ParseClient.BuildQueryString(data)), method: "GET", data: null); @@ -58,7 +58,7 @@ public Task LogInAsync(string authType, var authData = new Dictionary(); authData[authType] = data; - var command = new ParseCommand("/1/users", + var command = new ParseCommand("users", method: "POST", data: new Dictionary { {"authData", authData} @@ -74,7 +74,7 @@ public Task LogInAsync(string authType, } public Task GetUserAsync(string sessionToken, CancellationToken cancellationToken) { - var command = new ParseCommand("/1/users/me", + var command = new ParseCommand("users/me", method: "GET", sessionToken: sessionToken, data: null); @@ -85,7 +85,7 @@ public Task GetUserAsync(string sessionToken, CancellationToken ca } public Task RequestPasswordResetAsync(string email, CancellationToken cancellationToken) { - var command = new ParseCommand("/1/requestPasswordReset", + var command = new ParseCommand("requestPasswordReset", method: "POST", data: new Dictionary { {"email", email} diff --git a/Parse/ParseClient.cs b/Parse/ParseClient.cs index 9d541056..897aaa3f 100644 --- a/Parse/ParseClient.cs +++ b/Parse/ParseClient.cs @@ -82,7 +82,7 @@ internal static string VersionString { /// public static void Initialize(string applicationId, string dotnetKey) { lock (mutex) { - HostName = HostName ?? new Uri("https://api.parse.com/"); + HostName = HostName ?? new Uri("https://api.parse.com/1/"); ApplicationId = applicationId; WindowsKey = dotnetKey; diff --git a/ParseTest.Unit/AnalyticsControllerTests.cs b/ParseTest.Unit/AnalyticsControllerTests.cs index a2a85071..5ef06a02 100644 --- a/ParseTest.Unit/AnalyticsControllerTests.cs +++ b/ParseTest.Unit/AnalyticsControllerTests.cs @@ -12,9 +12,9 @@ namespace ParseTest { [TestFixture] public class AnalyticsControllerTests { - [SetUp] - public void SetUp() { - ParseClient.HostName = new Uri("http://parse.com"); + [SetUp] + public void SetUp() { + ParseClient.HostName = new Uri("http://api.parse.local/1/"); } [TearDown] diff --git a/ParseTest.Unit/CommandTests.cs b/ParseTest.Unit/CommandTests.cs index 31ea6ae7..587f2ea7 100644 --- a/ParseTest.Unit/CommandTests.cs +++ b/ParseTest.Unit/CommandTests.cs @@ -15,7 +15,7 @@ namespace ParseTest { public class CommandTests { [SetUp] public void SetUp() { - ParseClient.HostName = new Uri("http://parse.com"); + ParseClient.HostName = new Uri("http://api.parse.local/1/"); } [TearDown] @@ -26,7 +26,7 @@ public void TearDown() { [Test] public void TestMakeCommand() { - ParseCommand command = new ParseCommand("/1/endpoint", + ParseCommand command = new ParseCommand("endpoint", method: "GET", sessionToken: "abcd", headers: null, @@ -49,7 +49,7 @@ public Task TestRunCommand() { It.IsAny())).Returns(fakeResponse); ParseCommandRunner commandRunner = new ParseCommandRunner(mockHttpClient.Object); - var command = new ParseCommand("/1/endpoint", method: "GET", data: null); + var command = new ParseCommand("endpoint", method: "GET", data: null); return commandRunner.RunCommandAsync(command).ContinueWith(t => { Assert.False(t.IsFaulted); Assert.False(t.IsCanceled); @@ -69,7 +69,7 @@ public Task TestRunCommandWithArrayResult() { It.IsAny())).Returns(fakeResponse); ParseCommandRunner commandRunner = new ParseCommandRunner(mockHttpClient.Object); - var command = new ParseCommand("/1/endpoint", method: "GET", data: null); + var command = new ParseCommand("endpoint", method: "GET", data: null); return commandRunner.RunCommandAsync(command).ContinueWith(t => { Assert.False(t.IsFaulted); Assert.False(t.IsCanceled); @@ -91,7 +91,7 @@ public Task TestRunCommandWithInvalidString() { It.IsAny())).Returns(fakeResponse); ParseCommandRunner commandRunner = new ParseCommandRunner(mockHttpClient.Object); - var command = new ParseCommand("/1/endpoint", method: "GET", data: null); + var command = new ParseCommand("endpoint", method: "GET", data: null); return commandRunner.RunCommandAsync(command).ContinueWith(t => { Assert.True(t.IsFaulted); Assert.False(t.IsCanceled); @@ -112,7 +112,7 @@ public Task TestRunCommandWithErrorCode() { It.IsAny())).Returns(fakeResponse); ParseCommandRunner commandRunner = new ParseCommandRunner(mockHttpClient.Object); - var command = new ParseCommand("/1/endpoint", method: "GET", data: null); + var command = new ParseCommand("endpoint", method: "GET", data: null); return commandRunner.RunCommandAsync(command).ContinueWith(t => { Assert.True(t.IsFaulted); Assert.False(t.IsCanceled); @@ -134,7 +134,7 @@ public Task TestRunCommandWithInternalServerError() { It.IsAny())).Returns(fakeResponse); ParseCommandRunner commandRunner = new ParseCommandRunner(mockHttpClient.Object); - var command = new ParseCommand("/1/endpoint", method: "GET", data: null); + var command = new ParseCommand("endpoint", method: "GET", data: null); return commandRunner.RunCommandAsync(command).ContinueWith(t => { Assert.True(t.IsFaulted); Assert.False(t.IsCanceled); diff --git a/ParseTest.Unit/ObjectControllerTests.cs b/ParseTest.Unit/ObjectControllerTests.cs index 25c7f545..95b6638a 100644 --- a/ParseTest.Unit/ObjectControllerTests.cs +++ b/ParseTest.Unit/ObjectControllerTests.cs @@ -15,7 +15,7 @@ namespace ParseTest { public class ObjectControllerTests { [SetUp] public void SetUp() { - ParseClient.HostName = new Uri("http://parse.com"); + ParseClient.HostName = new Uri("http://api.parse.local/1/"); } [TearDown] diff --git a/ParseTest.Unit/SessionControllerTests.cs b/ParseTest.Unit/SessionControllerTests.cs index 8db0736a..309a0ad3 100644 --- a/ParseTest.Unit/SessionControllerTests.cs +++ b/ParseTest.Unit/SessionControllerTests.cs @@ -15,7 +15,7 @@ namespace ParseTest { public class SessionControllerTests { [SetUp] public void SetUp() { - ParseClient.HostName = new Uri("http://parse.com"); + ParseClient.HostName = new Uri("http://api.parse.local/1/"); } [TearDown] diff --git a/ParseTest.Unit/UserControllerTests.cs b/ParseTest.Unit/UserControllerTests.cs index 79119392..a4b5a4a9 100644 --- a/ParseTest.Unit/UserControllerTests.cs +++ b/ParseTest.Unit/UserControllerTests.cs @@ -14,7 +14,7 @@ namespace ParseTest { public class UserControllerTests { [SetUp] public void SetUp() { - ParseClient.HostName = new Uri("http://parse.com"); + ParseClient.HostName = new Uri("http://api.parse.local/1/"); } [TearDown] From e4fdf0578d793f3123df9f4fea9071d011bdd959 Mon Sep 17 00:00:00 2001 From: Richard Ross Date: Fri, 13 Nov 2015 16:52:22 -0800 Subject: [PATCH 2/2] Make batch requests no longer require manual encoding. Now leverages the existing 'ParseCommand' class instead of constructing improperly typed dictionaries. TODO: Split this into its own class. --- Parse/Internal/Command/ParseCommand.cs | 17 ++++++- Parse/Internal/HttpRequest.cs | 5 +- .../Controller/ParseObjectController.cs | 51 ++++++++++++------- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/Parse/Internal/Command/ParseCommand.cs b/Parse/Internal/Command/ParseCommand.cs index d7056335..40989a7e 100644 --- a/Parse/Internal/Command/ParseCommand.cs +++ b/Parse/Internal/Command/ParseCommand.cs @@ -13,6 +13,20 @@ namespace Parse.Internal { internal class ParseCommand : HttpRequest { private const string revocableSessionTokenTrueValue = "1"; + public IDictionary DataObject { get; private set; } + public override Stream Data { + get { + if (base.Data != null) { + return base.Data; + } + + return base.Data = DataObject != null + ? new MemoryStream(Encoding.UTF8.GetBytes(Json.Encode(DataObject))) + : null; + } + internal set { base.Data = value; } + } + public ParseCommand(string relativeUri, string method, string sessionToken = null, @@ -21,8 +35,9 @@ public ParseCommand(string relativeUri, method: method, sessionToken: sessionToken, headers: headers, - stream: data != null ? new MemoryStream(UTF8Encoding.UTF8.GetBytes(Json.Encode(data))) : null, + stream: null, contentType: data != null ? "application/json" : null) { + DataObject = data; } public ParseCommand(string relativeUri, diff --git a/Parse/Internal/HttpRequest.cs b/Parse/Internal/HttpRequest.cs index 20126318..b08e51df 100644 --- a/Parse/Internal/HttpRequest.cs +++ b/Parse/Internal/HttpRequest.cs @@ -3,9 +3,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net; -using System.Threading; -using System.Threading.Tasks; namespace Parse.Internal { /// @@ -18,7 +15,7 @@ internal class HttpRequest { /// /// Data stream to be uploaded. /// - public Stream Data { get; internal set; } + public virtual Stream Data { get; internal set; } /// /// HTTP method. One of DELETE, GET, HEAD, POST or PUT diff --git a/Parse/Internal/Object/Controller/ParseObjectController.cs b/Parse/Internal/Object/Controller/ParseObjectController.cs index 8dc899a8..9a518646 100644 --- a/Parse/Internal/Object/Controller/ParseObjectController.cs +++ b/Parse/Internal/Object/Controller/ParseObjectController.cs @@ -55,14 +55,15 @@ public IList> SaveAllAsync(IList states, IList> operationsList, string sessionToken, CancellationToken cancellationToken) { - var requests = states.Zip(operationsList, (item, ops) => new Dictionary { - { "method", (item.ObjectId == null ? "POST" : "PUT") }, - { "path", (item.ObjectId == null ? - string.Format("/1/classes/{0}", Uri.EscapeDataString(item.ClassName)) : - string.Format("/1/classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), - Uri.EscapeDataString(item.ObjectId))) }, - { "body", ParseObject.ToJSONObjectForSaving(ops) } - }).Cast().ToList(); + + var requests = states + .Zip(operationsList, (item, ops) => new ParseCommand( + item.ObjectId == null + ? string.Format("classes/{0}", Uri.EscapeDataString(item.ClassName)) + : string.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), + method: item.ObjectId == null ? "POST" : "PUT", + data: ParseObject.ToJSONObjectForSaving(ops))) + .ToList(); var batchTasks = ExecuteBatchRequests(requests, sessionToken, cancellationToken); var stateTasks = new List>(); @@ -90,24 +91,25 @@ public Task DeleteAsync(IObjectState state, public IList DeleteAllAsync(IList states, string sessionToken, CancellationToken cancellationToken) { - var requests = states.Where(item => item.ObjectId != null).Select(item => new Dictionary { - { "method", "DELETE" }, - { "path", string.Format("/1/classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), - Uri.EscapeDataString(item.ObjectId)) } - }).Cast().ToList(); - + var requests = states + .Where(item => item.ObjectId != null) + .Select(item => new ParseCommand( + string.Format("classes/{0}/{1}", Uri.EscapeDataString(item.ClassName), Uri.EscapeDataString(item.ObjectId)), + method: "DELETE", + data: null)) + .ToList(); return ExecuteBatchRequests(requests, sessionToken, cancellationToken).Cast().ToList(); } // TODO (hallucinogen): move this out to a class to be used by Analytics private const int MaximumBatchSize = 50; - internal IList>> ExecuteBatchRequests(IList requests, + internal IList>> ExecuteBatchRequests(IList requests, string sessionToken, CancellationToken cancellationToken) { var tasks = new List>>(); int batchSize = requests.Count; - IEnumerable remaining = requests; + IEnumerable remaining = requests; while (batchSize > MaximumBatchSize) { var process = remaining.Take(MaximumBatchSize).ToList(); remaining = remaining.Skip(MaximumBatchSize); @@ -121,7 +123,7 @@ internal IList>> ExecuteBatchRequests(IList>> ExecuteBatchRequest(IList requests, + private IList>> ExecuteBatchRequest(IList requests, string sessionToken, CancellationToken cancellationToken) { var tasks = new List>>(); @@ -133,10 +135,21 @@ private IList>> ExecuteBatchRequest(IList { + var results = new Dictionary { + { "method", r.Method }, + { "path", r.Uri.AbsolutePath }, + }; + + if (r.DataObject != null) { + results["body"] = r.DataObject; + } + return results; + }).Cast().ToList(); + var command = new ParseCommand("batch", method: "POST", sessionToken: sessionToken, - data: new Dictionary { { "requests", requests } }); + data: new Dictionary { { "requests", encodedRequests } }); commandRunner.RunCommandAsync(command, cancellationToken: cancellationToken).ContinueWith(t => { if (t.IsFaulted || t.IsCanceled) {