Error Handling for Licensing

Strategy

  • Errors will be created in the different parts where an error related to Licensing can appear:

    • Licensing Module

    • Licensing API

  • Errors will be propagated to the Controller and then to the UI

  • The errors will include all the relevant information needed to create a concise error message

    • For instance, the errors related to the expiration of a license will also include the expiration date as data. Instead of sending a string, the object will contain concrete information such as:

      • The kind of error "ExpiredLicense"

      • The exact date of the expiration

  • There are different levels of severity for errors related to licensing

    • Information

    • Warning

    • Error

  • Each error carries a message, but that message is intented to be read by developers and written to logs. That message is not meant to be read by the user. The message that will be read by the user is created in the UI with the information provided by the Controller (which originally comes from the Licensing API or Licensing Module)

  • There are two moments in which the errors need to be serialized and de-serialized

    • Communication between the Licensing API and the Licensing Module

    • Communication between the Controller and the Generic UI

Technical Details

ILicensingError, LicensingException and LicensingErrorFactory

ILicensingError

The ILicensingError interface provides:

  • Message

  • Kind

  • Severity

This object is propagated from the place where it originates, to the Generic UI. There is a concrete implementation known as LicensingError. There are also subclasses of LicensingError which represent more specific errors such as:

  • ExpiredLicenseError: Includes the date in which the license expired

  • MaxUnitsExceeded: Includes the type of units that were exceeded. Includes the amount of units that are left, and the amount of units needed to execute the conversion/assessment.

  • Many others

When dealing with the de-serialization of the subclasses, there are two different methods used;

  • The one used for the communication between the API and the Licensing Module

    • The LicensingError is de-serialized as a concrete LicensingError. Based on the Kind of that LicensingError, the same content can be de-serialized into a more specific class. Example of code:

private static LicensingError DeserializeLicensingError(string content)
{
    LicensingError genericError = JsonConvert.DeserializeObject<LicensingError>(content);

    switch (genericError.Kind)
    {
        case LicensingErrorKind.LicenseIsNotValidYet:
            return JsonConvert.DeserializeObject<EarlyLicenseUsageError>(content);
        case LicensingErrorKind.ExecutionModeNotSupported:
            return JsonConvert.DeserializeObject<ExecutionModeNotSupportedError>(content);
        case LicensingErrorKind.ExpiredLicense:
            return JsonConvert.DeserializeObject<ExpiredLicenseError>(content);
        case LicensingErrorKind.FeatureNotSupported:
            return JsonConvert.DeserializeObject<FeatureNotSupportedError>(content);
        case LicensingErrorKind.InconsistentDates:
            return JsonConvert.DeserializeObject<InconsistentDatesLicensingError>(content);
        case LicensingErrorKind.NotEnoughAccumulatedUnits:
            return JsonConvert.DeserializeObject<MaxLicenseUnitsExceededError>(content);
        case LicensingErrorKind.UnexpectedError:
            return JsonConvert.DeserializeObject<UnexpectedLicensingError>(content);
        default:
            return genericError;
    }
}
  • The one used for the communication between the Controller and the Generic UI:

    • There is a class named LicensingErrorDTO which contains all properties that can be contained by any subclass of LicensingError.

    • An object of this class can be created from a LicensingError (or any object that inherits from LicensingError), and only the necessary properties will be assigned.

    • The LicensingErrorDto is sent to the Generic UI and the UI will handle the error.

Licensing Exception

This is a custom exception that contains an ILicensingError instance. The message for the exception is created based on the message of the error (the exception is created directy from the error).

LicensingErrorFactory

This class has the resposibility of creating new instances of the LicensingError class (or any of its subclasses). The right way to create a LicensingError is by using this LicensingErrorFactory, in order to ensure the consistency between the LicensingError.Kind property and the type of the LicensingError. For instance, an error with type ExpiredLicenseError must have Kind = LicensingErrorKind.ExpiredLicense.This factory instantiantes all types of errors in a safe way. The method CreateLicensingError only supports some values of LicensingErrorKind. Some kinds are not supported and will throw an exception. For instance, passing the LicensingErrorKind.ExpiredLicense to this method will result in a NotSupportedException.

public static LicensingError CreateLicensingError(LicensingErrorKind errorKind)
{
    switch (errorKind)
    {
        case LicensingErrorKind.FailedModelReading:
            return new LicensingError(errorKind, $"License Model failed reading license.");
        case LicensingErrorKind.FailedModelWriting:
            return new LicensingError(errorKind, $"Failed activating the license: Couldn't write the license key file.");
        case LicensingErrorKind.InvalidFingerprint:
            return new LicensingError(errorKind, "The fingerprint of the selected input does not match with the fingerprint of the license.");
        case LicensingErrorKind.NoActiveLicense:
            return new LicensingError(errorKind, "No active license available.");
        case LicensingErrorKind.NoInternetConnection:
            return new LicensingError(errorKind, "There's no internet connection.");
        case LicensingErrorKind.FailedDownload:
            return new LicensingError(errorKind, "Failed downloading license.");
        case LicensingErrorKind.MaxDownloadsReached:
            return new LicensingError(errorKind, "This license has reached the maximum downloads.");
        case LicensingErrorKind.LicenseDoesNotExist:
            return new LicensingError(errorKind, "The license was not found.");
        case LicensingErrorKind.ApplicationLicenseMismatch:
        case LicensingErrorKind.ExpiredLicense:
        case LicensingErrorKind.LicenseIsNotValidYet:
        case LicensingErrorKind.InconsistentDates:
        case LicensingErrorKind.NotEnoughAccumulatedUnits:
        case LicensingErrorKind.FeatureNotSupported:
        case LicensingErrorKind.UnexpectedError:
        case LicensingErrorKind.ExecutionModeNotSupported:
        default:
            throw new NotSupportedException("A licensing error with the provided kind can't be created using this method.");
    }
}

The correct way of creating a new LicensingError with the Kind = LicensingErrorKind.ExpiredLicense is by using the CreateExpiredLicenseError method of the factory. This receives the expiration date of the license and correctly instantiates the object.

Last updated