public void EnsureStorageContextEquivalenceViaCreateSafe( string contextStringIn, bool?setMute, sc contextObjectIn, string contextStringOut, sc contextObjectOut) { sc sc1 = null; if (contextStringIn != null) { try { sc1 = sc.CreateSafe(contextStringIn, setMute); } catch (Exception e) { e.ShouldBeNull($"CreateSafe shouldn't get an exception, got {e.Message}"); } if (contextStringOut != null) { // confirm result via string. var sc1Str = sc1.ClientRequestID; contextStringOut.ShouldNotBeNullOrWhiteSpace(); sc1Str.ShouldNotBeNullOrWhiteSpace(); sc1Str.ShouldBe(contextStringOut); } if (contextObjectOut != null) { // confirm result via object. AreEquivalent(contextObjectOut, sc1, out string reason).ShouldBeTrue(reason); } } if (contextObjectIn != null) { sc1 = contextObjectIn; if (contextStringOut != null) { // confirm result via string. var sc1Str = sc1.ClientRequestID; contextStringOut.ShouldNotBeNull(); contextStringOut.ShouldBe(sc1Str); } if (contextObjectOut != null) { // confirm result via object. contextObjectOut.ShouldBeEquivalentTo(sc1); AreEquivalent(contextObjectOut, sc1, out string failedBecause).ShouldBeTrue(failedBecause); } } }
/// <summary> /// True if the instance is one of the "read-only" special cases - None or NoneMuted. /// </summary> /// <remarks> /// This is used to fail property sets so as not to "corrupt" what are supposed to /// be immutable instances. If other such instances are added in future, be sure to /// add them here. /// </remarks> private static bool IsReadOnlyInstance(StorageClientProviderContext scpc) { // Special case, don't let muting be reset for either of the static instances. // // Note: the null checks are included because the constructor uses the // IsMuted property which uses this method. During static construction of // these two special instances, the values under construction will not yet // have been assigned, so those constructors using IsMuted will work as // hoped. var result = ((None != null) && object.ReferenceEquals(scpc, None)) || ((NoneMuted != null) && object.ReferenceEquals(scpc, NoneMuted)); return(result); }
/// <summary>True if two StorageContexts have the same value.</summary> private static bool AreEquivalent(sc lhs, sc rhs, out string reason) { reason = string.Empty; if (object.ReferenceEquals(lhs, rhs)) { return(true); } if (lhs.ClientRequestID != rhs.ClientRequestID) { reason = $"ClientRequestID: \"{lhs.ClientRequestID}\" != \"{rhs.ClientRequestID}\""; return(false); } var lhsObj = lhs.ClientRequestIdAsJObject; var rhsObj = rhs.ClientRequestIdAsJObject; if (lhs == null || rhs == null) { reason = "ClientRequestIdAsJObject: one is null"; return(false); } if (!JToken.DeepEquals(lhsObj, rhsObj)) { reason = "DeepEquals failed"; return(false); } if (lhs.IsMuted != rhs.IsMuted) { reason = $"IsMuted: {lhs.IsMuted} != {rhs.IsMuted}"; return(false); } if (lhs.TrackingETag != lhs.TrackingETag) { reason = $"TrackingETag: {lhs.TrackingETag} != {rhs.TrackingETag}"; return(false); } if (lhs.ETag != rhs.ETag) { reason = $"ETag: {lhs.ETag} != {rhs.ETag}"; return(false); } return(true); }
/// <summary> /// Reset the fields of the current instance to those in the argument context. /// </summary> /// <remarks> /// At first glance, this method may appear unneeded. It is here to avoid the /// temptation for a user to try directly assigning context instances. /// /// That would not go well due to the way that the HTTP pipeline policy tied to the /// SDK client instance must have a predictable place from which to obtain desired /// header values. Thus, the values must be updated within this instance. Simply /// "pointing" to a different StorageClientProviderContext instance wouldn't work. /// /// See Gridwich.SagaParticipants.Storage.AzureStorage/BlobClientPipelinePolicy.cs for /// more information on the HTTP pipeline policy used by Blob and Container SDK clients. /// /// Note: using this method to try to create equivalents of None or NoneMuted will /// not work exactly as one might expect. While the result /// </remarks> /// <param name="ctx">The source context.</param> /// <exception cref="System.ArgumentNullException">if source instance is null.</exception> /// <exception cref="System.ArgumentException">if target instance is read-only.</exception> public void ResetTo(StorageClientProviderContext ctx) { _ = ctx ?? throw new ArgumentNullException(nameof(ctx)); // If happens to be resetting to self if (object.ReferenceEquals(this, ctx)) { return; } // If trying something like None.ResetTo(anotherInstance) if (IsReadOnlyInstance(this)) { throw new ArgumentException("Cannot use ResetTo to alter read-only StorageClientProviderContext instance"); } this.ETag = ctx.ETag; this.TrackingETag = ctx.TrackingETag; this.contextObjectAsString = ctx.contextObjectAsString; this.contextObject = ctx.contextObject?.DeepClone() as JObject; }
public void EnsureStorageContextEquivalenceViaStringInits( string contextStringIn, bool?setMute, sc contextObjectIn, string contextStringOut, sc contextObjectOut, Type exceptionTypeExpected) { sc sc1 = null; sc scImplicitMuting = null; if (contextStringIn != null) { bool gotException = false; try { sc1 = new sc(contextStringIn, setMute); scImplicitMuting = new sc(contextStringIn, null); } catch (Exception e) { e.ShouldBeOfType(exceptionTypeExpected); gotException = true; } if (exceptionTypeExpected != null) { gotException.ShouldBeTrue($"Should have gotten exception for: '{contextStringIn}'"); } if (contextStringOut != null) { // confirm result via string. var sc1Str = sc1.ClientRequestID; contextStringOut.ShouldNotBeNullOrWhiteSpace(); sc1Str.ShouldNotBeNullOrWhiteSpace(); sc1Str.ShouldBe(contextStringOut); } if (contextObjectOut != null) { // confirm result via object. contextObjectOut.ShouldBeEquivalentTo(sc1); AreEquivalent(contextObjectOut, sc1, out string reason).ShouldBeTrue(reason); } } if (contextObjectIn != null) { sc1 = contextObjectIn; if (contextStringOut != null) { // confirm result via string. var sc1Str = sc1.ClientRequestID; contextStringOut.ShouldNotBeNull(); contextStringOut.ShouldBe(sc1Str); } if (contextObjectOut != null) { // confirm result via object. contextObjectOut.ShouldBeEquivalentTo(sc1); AreEquivalent(contextObjectOut, sc1, out string failedBecause).ShouldBeTrue(failedBecause); } } }
/// <summary> /// Initializes a new instance of the <see cref="StorageClientProviderContext"/> class, /// based on another context. i.e., like a copy constructor. /// </summary> /// <param name="ctx">The source context.</param> public StorageClientProviderContext(StorageClientProviderContext ctx) { ResetTo(ctx); }
/// <summary> /// Create a context instance, being more lenient than the constructors regarding /// the input value. In addition to the normal JSON input, this method will also /// tolerate null/empty strings, GUID strings and other non-JSON strings. It /// produces a context for each. For any of the tolerated cases, it will produce /// either an empty context or a wrap-up of the passed value. Muting, etc. are /// processed/included in those results as dictated by the parameters. /// /// If the caller provides either logging Action argument, they will be used to /// records errors encountered. /// </summary> /// <remarks> /// This method does not throw an exception for non-JSON cases, as the constructors /// tend to (thus "Safe" in the name). But, it retains a code path that will /// log/throw an exception on bad input. This should never happen in real use /// since if the code reaches that point, it will be that the method itself constructed /// JSON that was unparseable. The code remains to ensure that will be logged if /// logger Actions were given and the problem somehow occurs. Generally, application /// code wouldn't bother using a try/catch around calls to CreateSafe - otherwise, a /// constructor may suffice. /// </remarks> /// If there is an error (e.g. JSON parsing or otherwise), and the corresponging log*Error /// argument is non-null, invoke that to let the caller log the error. /// </summary> /// <param name="operationContextString"> /// A string representing the Operation Context as a single JSON object. /// e.g. { "a" : "b" }</param> /// <param name="muteContext">If true, context should be set as muted -- i.e., intended for /// internal operations whose resulting notifications (if any) are not expected to be routed /// to callers (e.g., Requestor).</param> /// <param name="trackETag">If true, send the eTag value on each request (if it's not currently /// empty).</param> /// <param name="eTag">The current eTag string. With each response, this value is updated to /// reflect the value returned last from Azure Storage (regardless of trackETag's value). /// For each HTTP request, the eTag value is only set if trackETag is true and the value of eTag /// is not empty.</param> /// <param name="logParseError">A callback invoked (if non-null) should a JSON parse error /// occur in processing operationContextString. Second argument to this callback is string that /// was being parsed.</param> /// <param name="logOtherError">A callback invoked (if non-null) should an exception, other /// than a JSON parse error occur in processing operationContextString.</param> /// <returns>An instance of StorageProviderContext.</returns> public static StorageClientProviderContext CreateSafe( string operationContextString, bool?muteContext = null, bool?trackETag = null, string eTag = null, Action <Exception, string> logParseError = null, Action <Exception> logOtherError = null) { var muteContextSpecified = muteContext.HasValue; if (!trackETag.HasValue) { trackETag = false; // default, if none given } JObject contextObject = null; bool haveLoggedParseIssues = false; var emptyInput = string.IsNullOrWhiteSpace(operationContextString); // #1 -- handle case where there's some string to try as JSON if (!emptyInput) { try { contextObject = JsonHelpers.DeserializeOperationContext(operationContextString); } catch (Exception ep) { logParseError?.Invoke(ep, $"Error parsing Storage operation context from: '{operationContextString}'"); haveLoggedParseIssues = true; // and continue... } } // #2 -- didn't work as JSON, check it for GUID, etc. if (contextObject == null) { try { contextObject = StringContextToJObject(operationContextString, out bool _); } catch (Exception es) { if (!haveLoggedParseIssues) { logParseError?.Invoke(es, $"Error converting Storage operation context from: '{operationContextString}'"); haveLoggedParseIssues = true; // and continue... } // Something (really) went wrong trying to create the StorageContext instance. // It could be being passed incomplete JSON or someone sending in a random sentence of text. // Rather than stop the Gridwich with an exception, wrap whatever was given into // a blank OperationContext as the value of a known JSON property. contextObject = new JObject(); contextObject.Add(GeneralPropertyName, operationContextString); } } // #3 -- we finally have something to use as an OperationContext, now just wrap it up in a StorageContext. StorageClientProviderContext result = null; try { result = new StorageClientProviderContext(contextObject, muteContext, trackETag, eTag); } catch (Exception eo) { // If we get here, it's not the fault of the caller. It means that this // method has somehow manipulated the input string into invalid JSON. // This should not occur in the real world. logOtherError?.Invoke(eo); throw; } return(result); }