/// <summary> /// Combine two <see cref="Errorable{T}"/> values and return a result. /// </summary> /// <remarks>Any errors already present are preserved.</remarks> /// <typeparam name="T">Type of value possibly present in <paramref name="first"/>.</typeparam> /// <typeparam name="A">Type of value possibly present in <paramref name="second"/>.</typeparam> /// <typeparam name="R">Type of return value.</typeparam> /// <param name="first">First errorable value.</param> /// <param name="second">Second errorable value.</param> /// <param name="combinerFunc">Function to combine both values when available.</param> /// <returns>An errorable containing either the result of combining both available values, /// or a combined set of errors.</returns> public static Errorable <R> Combine <T, A, R>( this Errorable <T> first, Errorable <A> second, Func <T, A, R> combinerFunc) { if (first == null) { throw new ArgumentNullException(nameof(first)); } if (second == null) { throw new ArgumentNullException(nameof(second)); } if (combinerFunc == null) { throw new ArgumentNullException(nameof(combinerFunc)); } if (first.HasValue && second.HasValue) { return(Errorable.Success( combinerFunc(first.Value, second.Value))); } var allErrors = first.Errors.Union(second.Errors); return(Errorable.Failure <R>(allErrors)); }
/// <summary> /// Find a certificate based on the provided thumbprint /// </summary> /// <param name="purpose">A use for which the certificate is needed (for human consumption).</param> /// <param name="thumbprint">Thumbprint of the certificate we need.</param> /// <returns>Certificate, if found; null otherwise.</returns> public Errorable <X509Certificate2> FindByThumbprint(string purpose, CertificateThumbprint thumbprint) { var query = from name in _storeNames from location in _storeLocations select FindByThumbprint(thumbprint, name, location); var candidates = query.Where(cert => cert != null).ToList(); var certWithPrivateKey = candidates.Find(cert => cert.HasPrivateKey); if (certWithPrivateKey != null) { // We might have multiple copies of the same certificate available in different stores. // If so, prefer any copies that have their private key over those that do not // Certificates with private keys can be used to both encrypt/decrypt and to // sign/verify - copies without can only be used to encrypt and verify. return(Errorable.Success(certWithPrivateKey)); } var certificate = candidates.FirstOrDefault(); if (certificate != null) { return(Errorable.Success(certificate)); } return(Errorable.Failure <X509Certificate2>($"Did not find {purpose} certificate {thumbprint}")); }
/// <summary> /// Try to parse a string into a DateTimeOffset /// </summary> /// <param name="value">The string value to parse.</param> /// <param name="name">Name to use for the value if there is an error.</param> /// <returns>A succesfully parsed <see cref="DateTimeOffset"/> or errors detailing what /// went wrong.</returns> public Errorable <DateTimeOffset> TryParse(string value, string name) { if (DateTimeOffset.TryParseExact( value, ExpectedFormat, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out var result)) { // Correctly parsed in the expected format return(Errorable.Success(result)); } if (DateTimeOffset.TryParse( value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal, out result)) { // Correctly parsed with detected format return(Errorable.Success(result)); } if (DateTimeOffset.TryParse(value, out result)) { // Correctly parsed with detected format return(Errorable.Success(result)); } return(Errorable.Failure <DateTimeOffset>( $"Unable to parse {name} timestamp '{value}' (expected format is '{ExpectedFormat}')")); }