diff --git a/Criipto.Signatures.UnitTests/ConverterTests.cs b/Criipto.Signatures.UnitTests/ConverterTests.cs new file mode 100644 index 0000000..b251ac2 --- /dev/null +++ b/Criipto.Signatures.UnitTests/ConverterTests.cs @@ -0,0 +1,38 @@ +using Xunit; +using Newtonsoft.Json; +using Criipto.Signatures.Models; + +namespace Criipto.Signatures.UnitTests; + +public class ConverterTests +{ + [Fact] + public void SignatoryDocumentEdgeIsTolerant() + { + var json = """ +{ + "status": "SIGNED_IN_THE_FUTURE" +} +""".Trim(); + + var actual = JsonConvert.DeserializeObject(json); + + Assert.NotNull(actual); + Assert.Equal(SignatoryDocumentStatus.FUTURE_ADDED_VALUE, actual!.status); + } + + [Fact] + public void SignatoryDocumentEdgeIsCorrect() + { + var json = """ +{ + "status": "SIGNED" +} +""".Trim(); + + var actual = JsonConvert.DeserializeObject(json); + + Assert.NotNull(actual); + Assert.Equal(SignatoryDocumentStatus.SIGNED, actual!.status); + } +} \ No newline at end of file diff --git a/Criipto.Signatures/Converters.cs b/Criipto.Signatures/Converters.cs index bcd9307..b1b1652 100644 --- a/Criipto.Signatures/Converters.cs +++ b/Criipto.Signatures/Converters.cs @@ -112,4 +112,71 @@ public override void WriteJson(JsonWriter writer, object? value, JsonSerializer throw new NotImplementedException("Tried to write a GQL Composition type list to JSON"); } } + + public class TolerantEnumConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + if (objectType == null) return false; + Type? type = IsNullableType(objectType) ? Nullable.GetUnderlyingType(objectType) : objectType; + if (type == null) return false; + return type.IsEnum; + } + + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) + { + bool isNullable = IsNullableType(objectType); + Type? enumType = isNullable ? Nullable.GetUnderlyingType(objectType) : objectType; + + if (enumType == null) return null; + + string[] names = Enum.GetNames(enumType); + + if (reader.TokenType == JsonToken.String) + { + string? enumText = reader.Value?.ToString(); + Console.WriteLine(enumText); + + if (!string.IsNullOrEmpty(enumText)) + { + string? match = names + .Where(n => string.Equals(n, enumText, StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); + + if (match != null) + { + return Enum.Parse(enumType, match); + } + + string? defaultName = names + .Where(n => string.Equals(n, "FUTURE_ADDED_VALUE", StringComparison.OrdinalIgnoreCase)) + .FirstOrDefault(); + + if (defaultName == null) + { + defaultName = names.First(); + } + + return Enum.Parse(enumType, defaultName); + } + } + + return null; + } + + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) + { + if (value == null) + { + writer.WriteValue("null"); + return; + } + writer.WriteValue(value.ToString()); + } + + private static bool IsNullableType(Type t) + { + return (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>)); + } + } } \ No newline at end of file diff --git a/Criipto.Signatures/Models.cs b/Criipto.Signatures/Models.cs index 0049065..2165f0a 100644 --- a/Criipto.Signatures/Models.cs +++ b/Criipto.Signatures/Models.cs @@ -17,6 +17,7 @@ #pragma warning disable CA1720 #pragma warning disable CA1052 #pragma warning disable CA1819 +#pragma warning disable CA1716 using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -173,6 +174,10 @@ public class Application : Viewer { [JsonProperty("verifyApplication")] public VerifyApplication verifyApplication { get; set; } + + [JsonProperty("webhookLogs")] + [JsonConverter(typeof(CompositionTypeListConverter))] + public List webhookLogs { get; set; } #endregion } #endregion @@ -190,6 +195,7 @@ public class ApplicationApiKey { public string id { get; set; } [JsonProperty("mode")] + [JsonConverter(typeof(TolerantEnumConverter))] public ApplicationApiKeyMode mode { get; set; } [JsonProperty("note")] @@ -199,7 +205,8 @@ public class ApplicationApiKey { #endregion public enum ApplicationApiKeyMode { READ_ONLY, - READ_WRITE + READ_WRITE, + FUTURE_ADDED_VALUE = 999 } @@ -403,6 +410,7 @@ public class CreateApplicationApiKeyInput { [JsonRequired] public string applicationId { get; set; } + [JsonConverter(typeof(TolerantEnumConverter))] public ApplicationApiKeyMode? mode { get; set; } public string note { get; set; } @@ -461,6 +469,7 @@ public class CreateApplicationInput { [Required] [JsonRequired] + [JsonConverter(typeof(TolerantEnumConverter))] public VerifyApplicationEnvironment verifyApplicationEnvironment { get; set; } [Required] @@ -665,6 +674,7 @@ public class CreateSignatureOrderUiInput { /// /// The language of texts rendered to the signatory. /// + [JsonConverter(typeof(TolerantEnumConverter))] public Language? language { get; set; } /// @@ -981,7 +991,8 @@ public enum DocumentStorageMode { /// /// Temporary documents will be deleted once completed. /// - Temporary + Temporary, + FUTURE_ADDED_VALUE = 999 } @@ -1210,7 +1221,8 @@ public enum EvidenceValidationStage { /// /// Require the signatory to be validated before viewing documents /// - VIEW + VIEW, + FUTURE_ADDED_VALUE = 999 } @@ -1280,7 +1292,8 @@ public enum Language { DA_DK, EN_US, NB_NO, - SV_SE + SV_SE, + FUTURE_ADDED_VALUE = 999 } @@ -1371,6 +1384,9 @@ public class Mutation { [JsonProperty("rejectSignatureOrder")] public RejectSignatureOrderOutput rejectSignatureOrder { get; set; } + [JsonProperty("retrySignatureOrderWebhook")] + public RetrySignatureOrderWebhookOutput retrySignatureOrderWebhook { get; set; } + /// /// Used by Signatory frontends to sign the documents in a signature order. /// @@ -1545,6 +1561,7 @@ public class PadesDocumentInput { [Required] [JsonRequired] + [JsonConverter(typeof(TolerantEnumConverter))] public DocumentStorageMode storageMode { get; set; } [Required] @@ -1622,6 +1639,7 @@ public class PdfDocument : Document { public string reference { get; set; } [JsonProperty("signatoryViewerStatus")] + [JsonConverter(typeof(TolerantEnumConverter))] public SignatoryDocumentStatus? signatoryViewerStatus { get; set; } [JsonProperty("signatures")] @@ -1801,6 +1819,52 @@ public class RejectSignatureOrderOutput { } #endregion + #region RetrySignatureOrderWebhookInput + public class RetrySignatureOrderWebhookInput { + #region members + [Required] + [JsonRequired] + public string retryPayload { get; set; } + + [Required] + [JsonRequired] + public string signatureOrderId { get; set; } + #endregion + + #region methods + public dynamic GetInputObject() + { + IDictionary d = new System.Dynamic.ExpandoObject(); + + var properties = GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); + foreach (var propertyInfo in properties) + { + var value = propertyInfo.GetValue(this); + var defaultValue = propertyInfo.PropertyType.IsValueType ? Activator.CreateInstance(propertyInfo.PropertyType) : null; + + var requiredProp = propertyInfo.GetCustomAttributes(typeof(JsonRequiredAttribute), false).Length > 0; + + if (requiredProp || value != defaultValue) + { + d[propertyInfo.Name] = value; + } + } + return d; + } + #endregion + } + #endregion + + #region RetrySignatureOrderWebhookOutput + public class RetrySignatureOrderWebhookOutput { + #region members + [JsonProperty("invocation")] + [JsonConverter(typeof(CompositionTypeConverter))] + public WebhookInvocation invocation { get; set; } + #endregion + } + #endregion + #region SignActingAsInput public class SignActingAsInput { #region members @@ -2041,6 +2105,7 @@ public class Signatory { /// The current status of the signatory. /// [JsonProperty("status")] + [JsonConverter(typeof(TolerantEnumConverter))] public SignatoryStatus status { get; set; } /// @@ -2117,6 +2182,7 @@ public class SignatoryDocumentEdge { public Document node { get; set; } [JsonProperty("status")] + [JsonConverter(typeof(TolerantEnumConverter))] public SignatoryDocumentStatus? status { get; set; } #endregion } @@ -2164,7 +2230,9 @@ public enum SignatoryDocumentStatus { APPROVED, OPENED, PREAPPROVED, - REJECTED + REJECTED, + SIGNED, + FUTURE_ADDED_VALUE = 999 } @@ -2237,7 +2305,8 @@ public dynamic GetInputObject() #endregion public enum SignatoryFrontendEvent { DOWNLOAD_LINK_OPENED, - SIGN_LINK_OPENED + SIGN_LINK_OPENED, + FUTURE_ADDED_VALUE = 999 } public enum SignatoryStatus { @@ -2245,7 +2314,8 @@ public enum SignatoryStatus { ERROR, OPEN, REJECTED, - SIGNED + SIGNED, + FUTURE_ADDED_VALUE = 999 } @@ -2272,12 +2342,14 @@ public class SignatoryViewer : Viewer { public string signatoryId { get; set; } [JsonProperty("signatureOrderStatus")] + [JsonConverter(typeof(TolerantEnumConverter))] public SignatureOrderStatus signatureOrderStatus { get; set; } [JsonProperty("signer")] public bool signer { get; set; } [JsonProperty("status")] + [JsonConverter(typeof(TolerantEnumConverter))] public SignatoryStatus status { get; set; } [JsonProperty("ui")] @@ -2458,6 +2530,7 @@ public class SignatureOrder { public List signatories { get; set; } [JsonProperty("status")] + [JsonConverter(typeof(TolerantEnumConverter))] public SignatureOrderStatus status { get; set; } /// @@ -2474,6 +2547,9 @@ public class SignatureOrder { [JsonProperty("ui")] public SignatureOrderUI ui { get; set; } + + [JsonProperty("webhook")] + public SignatureOrderWebhook webhook { get; set; } #endregion } #endregion @@ -2529,7 +2605,8 @@ public enum SignatureOrderStatus { CANCELLED, CLOSED, EXPIRED, - OPEN + OPEN, + FUTURE_ADDED_VALUE = 999 } @@ -2540,6 +2617,7 @@ public class SignatureOrderUI { public bool disableRejection { get; set; } [JsonProperty("language")] + [JsonConverter(typeof(TolerantEnumConverter))] public Language language { get; set; } [JsonProperty("logo")] @@ -2606,6 +2684,19 @@ public dynamic GetInputObject() } #endregion + #region SignatureOrderWebhook + public class SignatureOrderWebhook { + #region members + [JsonProperty("logs")] + [JsonConverter(typeof(CompositionTypeListConverter))] + public List logs { get; set; } + + [JsonProperty("url")] + public string url { get; set; } + #endregion + } + #endregion + #region Tenant public class Tenant { #region members @@ -2614,6 +2705,10 @@ public class Tenant { [JsonProperty("id")] public string id { get; set; } + + [JsonProperty("webhookLogs")] + [JsonConverter(typeof(CompositionTypeListConverter))] + public List webhookLogs { get; set; } #endregion } #endregion @@ -2623,6 +2718,7 @@ public class TrackSignatoryInput { #region members [Required] [JsonRequired] + [JsonConverter(typeof(TolerantEnumConverter))] public SignatoryFrontendEvent @event { get; set; } #endregion @@ -2694,6 +2790,7 @@ public class UpdateSignatoryDocumentStatusInput { [Required] [JsonRequired] + [JsonConverter(typeof(TolerantEnumConverter))] public SignatoryDocumentStatus status { get; set; } #endregion @@ -2756,6 +2853,7 @@ public class VerifyApplication { public string domain { get; set; } [JsonProperty("environment")] + [JsonConverter(typeof(TolerantEnumConverter))] public VerifyApplicationEnvironment environment { get; set; } [JsonProperty("realm")] @@ -2765,12 +2863,229 @@ public class VerifyApplication { #endregion public enum VerifyApplicationEnvironment { PRODUCTION, - TEST + TEST, + FUTURE_ADDED_VALUE = 999 } + #region VerifyApplicationQueryInput + public class VerifyApplicationQueryInput { + #region members + [Required] + [JsonRequired] + public string domain { get; set; } + + [Required] + [JsonRequired] + public string realm { get; set; } + + [Required] + [JsonRequired] + public string tenantId { get; set; } + #endregion + + #region methods + public dynamic GetInputObject() + { + IDictionary d = new System.Dynamic.ExpandoObject(); + + var properties = GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public); + foreach (var propertyInfo in properties) + { + var value = propertyInfo.GetValue(this); + var defaultValue = propertyInfo.PropertyType.IsValueType ? Activator.CreateInstance(propertyInfo.PropertyType) : null; + + var requiredProp = propertyInfo.GetCustomAttributes(typeof(JsonRequiredAttribute), false).Length > 0; + + if (requiredProp || value != defaultValue) + { + d[propertyInfo.Name] = value; + } + } + return d; + } + #endregion + } + #endregion + public interface Viewer { [JsonProperty("id")] string id { get; set; } } + + #region WebhookExceptionInvocation + public class WebhookExceptionInvocation : WebhookInvocation { + #region members + [JsonProperty("correlationId")] + public string correlationId { get; set; } + + [JsonProperty("event")] + [JsonConverter(typeof(TolerantEnumConverter))] + public WebhookInvocationEvent? @event { get; set; } + + [JsonProperty("exception")] + public string exception { get; set; } + + [JsonProperty("requestBody")] + public string requestBody { get; set; } + + [JsonProperty("responseBody")] + public string responseBody { get; set; } + + [JsonProperty("retryPayload")] + public string retryPayload { get; set; } + + [JsonProperty("retryingAt")] + public string retryingAt { get; set; } + + [JsonProperty("signatureOrderId")] + public string signatureOrderId { get; set; } + + [JsonProperty("timestamp")] + public string timestamp { get; set; } + + [JsonProperty("url")] + public string url { get; set; } + #endregion + } + #endregion + + #region WebhookHttpErrorInvocation + public class WebhookHttpErrorInvocation : WebhookInvocation { + #region members + [JsonProperty("correlationId")] + public string correlationId { get; set; } + + [JsonProperty("event")] + [JsonConverter(typeof(TolerantEnumConverter))] + public WebhookInvocationEvent? @event { get; set; } + + [JsonProperty("requestBody")] + public string requestBody { get; set; } + + [JsonProperty("responseBody")] + public string responseBody { get; set; } + + [JsonProperty("responseStatusCode")] + public int responseStatusCode { get; set; } + + [JsonProperty("retryPayload")] + public string retryPayload { get; set; } + + [JsonProperty("retryingAt")] + public string retryingAt { get; set; } + + [JsonProperty("signatureOrderId")] + public string signatureOrderId { get; set; } + + [JsonProperty("timestamp")] + public string timestamp { get; set; } + + [JsonProperty("url")] + public string url { get; set; } + #endregion + } + #endregion + + public interface WebhookInvocation { + [JsonProperty("correlationId")] + string correlationId { get; set; } + + [JsonProperty("event")] + WebhookInvocationEvent? @event { get; set; } + + [JsonProperty("requestBody")] + string requestBody { get; set; } + + [JsonProperty("responseBody")] + string responseBody { get; set; } + + [JsonProperty("signatureOrderId")] + string signatureOrderId { get; set; } + + [JsonProperty("timestamp")] + string timestamp { get; set; } + + [JsonProperty("url")] + string url { get; set; } + } + public enum WebhookInvocationEvent { + SIGNATORY_DOCUMENT_STATUS_CHANGED, + SIGNATORY_DOWNLOAD_LINK_OPENED, + SIGNATORY_REJECTED, + SIGNATORY_SIGNED, + SIGNATORY_SIGN_ERROR, + SIGNATORY_SIGN_LINK_OPENED, + SIGNATURE_ORDER_EXPIRED, + FUTURE_ADDED_VALUE = 999 + } + + + #region WebhookSuccessfulInvocation + public class WebhookSuccessfulInvocation : WebhookInvocation { + #region members + [JsonProperty("correlationId")] + public string correlationId { get; set; } + + [JsonProperty("event")] + [JsonConverter(typeof(TolerantEnumConverter))] + public WebhookInvocationEvent? @event { get; set; } + + [JsonProperty("requestBody")] + public string requestBody { get; set; } + + [JsonProperty("responseBody")] + public string responseBody { get; set; } + + [JsonProperty("responseStatusCode")] + public int responseStatusCode { get; set; } + + [JsonProperty("signatureOrderId")] + public string signatureOrderId { get; set; } + + [JsonProperty("timestamp")] + public string timestamp { get; set; } + + [JsonProperty("url")] + public string url { get; set; } + #endregion + } + #endregion + + #region WebhookTimeoutInvocation + public class WebhookTimeoutInvocation : WebhookInvocation { + #region members + [JsonProperty("correlationId")] + public string correlationId { get; set; } + + [JsonProperty("event")] + [JsonConverter(typeof(TolerantEnumConverter))] + public WebhookInvocationEvent? @event { get; set; } + + [JsonProperty("requestBody")] + public string requestBody { get; set; } + + [JsonProperty("responseBody")] + public string responseBody { get; set; } + + [JsonProperty("responseTimeout")] + public int responseTimeout { get; set; } + + [JsonProperty("retryPayload")] + public string retryPayload { get; set; } + + [JsonProperty("retryingAt")] + public string retryingAt { get; set; } + + [JsonProperty("signatureOrderId")] + public string signatureOrderId { get; set; } + + [JsonProperty("timestamp")] + public string timestamp { get; set; } + + [JsonProperty("url")] + public string url { get; set; } + #endregion + } + #endregion } \ No newline at end of file diff --git a/fix-typings.js b/fix-typings.js index 238da1b..15fbca0 100644 --- a/fix-typings.js +++ b/fix-typings.js @@ -18,7 +18,8 @@ const typesSupressions = [ 'CA1707', 'CA1720', 'CA1052', - 'CA1819' + 'CA1819', + 'CA1716' ] const operationsSupressions = [ @@ -47,6 +48,24 @@ for (const compositionType of compositionTypes) { }); } +let enumTypes = [...types.matchAll(/public enum (.+) \{/g)].map(result => result[1]); +for (const enumType of enumTypes) { + const typeRegex = new RegExp(`^(.*?)(public|private|protected) enum ${enumType} {((.|\n)*?)}$`, 'gm'); + const [match] = Array.from(types.match(typeRegex)); + const lines = match.split('\n'); + var indent = lines[lines.length - 2].match(/^\s+/); + lines[lines.length - 2] = lines[lines.length - 2] + ','; + lines.splice(lines.length - 1, undefined, indent + 'FUTURE_ADDED_VALUE = 999'); + types = types.replace(match, lines.join('\n')); + + const converterRegex = new RegExp(`^(.*?)(public|private|protected) ${enumType}(.*?)$`, 'gm'); + types = types.replace(converterRegex, function (match) { + if (match.includes('class') || match.includes('interface') || match.includes('enum')) return match; + var indent = match.match(/^\s+/); + return (indent ? indent[0] : '') + '[JsonConverter(typeof(TolerantEnumConverter))]\n' + match; + }); +} + types = types.replace('namespace Criipto.Signatures {', 'namespace Criipto.Signatures.Models {'); types = types.replace('public class Types {', ''); types = types.replace(/}(?:\s+)}(?:\s+)$/, '}');