From 012e14888fb884de42401074c119d4b3b0694e35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michael=20R=C3=A4tzel?= Date: Mon, 11 May 2020 19:40:36 +0000 Subject: [PATCH] Support set Elm app state using CLI Also, fix a problem in the server implementation to check if the serialized representation is compatible with the Elm type: Do not depend on the formatting in the JSON. --- ...PersistentProcessVolatileRepresentation.cs | 16 ++- implement/elm-fullstack/Program.cs | 111 +++++++++++++++++- 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/implement/PersistentProcess/PersistentProcess.WebHost/PersistentProcessVolatileRepresentation.cs b/implement/PersistentProcess/PersistentProcess.WebHost/PersistentProcessVolatileRepresentation.cs index 3994697a..29ff652f 100644 --- a/implement/PersistentProcess/PersistentProcess.WebHost/PersistentProcessVolatileRepresentation.cs +++ b/implement/PersistentProcess/PersistentProcess.WebHost/PersistentProcessVolatileRepresentation.cs @@ -359,7 +359,7 @@ Composition.TreeComponent loadComponentFromStoreAndAssertIsTree(string component var resultingElmAppState = processBefore.lastElmAppVolatileProcess.GetSerializedState(); var lastSetElmAppStateResult = - resultingElmAppState == projectedElmAppState + applyCommonFormattingToJson(resultingElmAppState) == applyCommonFormattingToJson(projectedElmAppState) ? new Result { @@ -645,7 +645,7 @@ Ok valueToEncodeOk -> var resultingState = testProcess.GetSerializedState(); - if (resultingState != elmAppStateMigratedSerialized) + if (applyCommonFormattingToJson(resultingState) != applyCommonFormattingToJson(elmAppStateMigratedSerialized)) return new Result { Err = "Failed to load the migrated serialized state with the destination public app configuration. resulting State:\n" + resultingState @@ -707,6 +707,18 @@ public string ProcessElmAppEvent(IProcessStoreWriter storeWriter, string seriali } } + static string applyCommonFormattingToJson(string originalJson) + { + try + { + return Newtonsoft.Json.JsonConvert.SerializeObject(Newtonsoft.Json.JsonConvert.DeserializeObject(originalJson)); + } + catch + { + return originalJson; + } + } + public void Dispose() => lastElmAppVolatileProcess?.Dispose(); public ProvisionalReductionRecordInFile StoreReductionRecordForCurrentState(IProcessStoreWriter storeWriter) diff --git a/implement/elm-fullstack/Program.cs b/implement/elm-fullstack/Program.cs index 6925abaa..bc281ecf 100644 --- a/implement/elm-fullstack/Program.cs +++ b/implement/elm-fullstack/Program.cs @@ -200,6 +200,30 @@ CommandOption verboseLogOptionFromCommand(CommandLineApplication command) => }); }); + app.Command("set-elm-app-state", setElmAppStateCmd => + { + setElmAppStateCmd.Description = "Attempt to set the state of a backend Elm app using the common serialized representation."; + + var siteAndPasswordFromCmd = siteAndSitePasswordOptionsOnCommand(setElmAppStateCmd); + + var sourceOption = setElmAppStateCmd.Option("--source", "Source to load the serialized state representation from.", CommandOptionType.SingleValue).IsRequired(allowEmptyStrings: false); + + setElmAppStateCmd.OnExecute(() => + { + var (site, sitePassword) = siteAndPasswordFromCmd(); + + var attemptReport = + setElmAppState( + site: site, + sitePassword: sitePassword, + source: sourceOption.Value()); + + writeReportToFileInReportDirectory( + reportContent: Newtonsoft.Json.JsonConvert.SerializeObject(attemptReport, Newtonsoft.Json.Formatting.Indented), + reportKind: "set-elm-app-state.json"); + }); + }); + app.Command("truncate-process-history", truncateProcessHistoryCmd => { var siteAndPasswordFromCmd = siteAndSitePasswordOptionsOnCommand(truncateProcessHistoryCmd); @@ -358,6 +382,91 @@ static DeployAppConfigReport deployAppConfig(string site, string sitePassword, b }; } + class SetElmAppStateReport + { + public string beginTime; + + public string elmAppStateSource; + + public string elmAppStateId; + + public string site; + + public ResponseFromServerStruct responseFromServer; + + public int totalTimeSpentMilli; + + public class ResponseFromServerStruct + { + public int? statusCode; + + public object body; + } + } + + static SetElmAppStateReport setElmAppState(string site, string sitePassword, string source) + { + var beginTime = DateTimeOffset.UtcNow.ToString("yyyy-MM-ddTHH-mm-ss"); + + var totalStopwatch = System.Diagnostics.Stopwatch.StartNew(); + + // For now only support a file path as source. + + var elmAppStateSerialized = File.ReadAllBytes(source); + + var elmAppStateComponent = Composition.Component.Blob(elmAppStateSerialized); + + var elmAppStateId = CommonConversion.StringBase16FromByteArray(Composition.GetHash(elmAppStateComponent)); + + SetElmAppStateReport.ResponseFromServerStruct responseFromServer = null; + + using (var httpClient = new System.Net.Http.HttpClient()) + { + httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue( + "Basic", + Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes( + Kalmit.PersistentProcess.WebHost.Configuration.BasicAuthenticationForAdminRoot(sitePassword)))); + + var httpContent = new System.Net.Http.ByteArrayContent(elmAppStateSerialized); + + httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); + + var httpResponse = httpClient.PostAsync( + site.TrimEnd('/') + StartupAdminInterface.PathApiElmAppState, httpContent).Result; + + var responseContentString = httpResponse.Content.ReadAsStringAsync().Result; + + Console.WriteLine( + "Server response: " + httpResponse.StatusCode + "\n" + + responseContentString); + + object responseBodyReport = responseContentString; + + try + { + responseBodyReport = + Newtonsoft.Json.JsonConvert.DeserializeObject(responseContentString); + } + catch { } + + responseFromServer = new SetElmAppStateReport.ResponseFromServerStruct + { + statusCode = (int)httpResponse.StatusCode, + body = responseBodyReport, + }; + } + + return new SetElmAppStateReport + { + beginTime = beginTime, + elmAppStateSource = source, + elmAppStateId = elmAppStateId, + site = site, + responseFromServer = responseFromServer, + totalTimeSpentMilli = (int)totalStopwatch.ElapsedMilliseconds, + }; + } + class TruncateProcessHistoryReport { public string site; @@ -603,7 +712,7 @@ static void writeReportToFileInReportDirectory(string reportContent, string repo Directory.CreateDirectory(Path.GetDirectoryName(filePath)); - File.WriteAllText(filePath, reportContent, System.Text.Encoding.UTF8); + File.WriteAllBytes(filePath, System.Text.Encoding.UTF8.GetBytes(reportContent)); Console.WriteLine("Saved report to file '" + filePath + "'."); }