/// <summary>
        /// Generates an expression to deserialize incoming data to customer method input
        /// </summary>
        /// <param name="customerSerializerInstance">Instance of lambda input & output serializer.</param>
        /// <param name="dataType">Customer input type.</param>
        /// <param name="inStream">Input expression that defines customer input.</param>
        /// <returns>Expression that deserializes incoming data to customer method input.</returns>
        /// <exception cref="LambdaValidationException">Thrown when customer serializer doesn't match with expected serializer definition</exception>
        private Expression CreateDeserializeExpression(object customerSerializerInstance, Type dataType, Expression inStream)
        {
            // generic types, null for String and Stream converters
            Type[] genericTypes = null;
            var    converter    = customerSerializerInstance;

            if (dataType == Types.StreamType)
            {
                converter = StreamSerializer.Instance;
            }
            else
            {
                if (customerSerializerInstance == null)
                {
                    throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.DeserializeMissingAttribute,
                                                               _handler.AssemblyName, _handler.MethodName, dataType.FullName);
                }

                genericTypes = new[] { dataType };
            }

            // code: serializer
            var serializer = Expression.Constant(converter);
            // code: serializer.Deserializer[<T>](inStream)
            Expression deserializeCall = Expression.Call(
                serializer,    // variable
                "Deserialize", // method name
                genericTypes,  // generic type
                inStream);     // arg1 - input stream

            return(deserializeCall);
        }
Пример #2
0
        /// <summary>
        /// Throws exception if type is not one we can work with
        /// </summary>
        /// <param name="type">Container type of customer method.</param>
        /// <param name="method">Method information of customer method.</param>
        /// <exception cref="LambdaValidationException">Throws when customer type is not lambda compatible.</exception>
        internal static void ValidateCustomerType(Type type, MethodInfo method)
        {
            var typeInfo = type.GetTypeInfo();

            // generic customer type is not supported
            if (typeInfo.IsGenericType)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.HandlerTypeGeneric,
                                                           type.FullName);
            }

            // abstract customer type is not support if customer method is not static
            if (!method.IsStatic && typeInfo.IsAbstract)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.HandlerTypeAbstract,
                                                           method.ToString(), type.FullName);
            }

            var isClass  = typeInfo.IsClass;
            var isStruct = typeInfo.IsValueType && !typeInfo.IsPrimitive && !typeInfo.IsEnum;

            // customer type must be class or struct
            if (!isClass && !isStruct)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.HandlerNotClassOrStruct,
                                                           type.FullName);
            }
        }
        /// <summary>
        /// Creates an expression to convert incoming Stream to the customer method inputs.
        /// If customer method takes no inputs or only takes ILambdaContext, return null.
        /// </summary>
        /// <param name="customerSerializerInstance">Instance of lambda input & output serializer.</param>
        /// <param name="inStreamParameter">Input stream parameter.</param>
        /// <param name="iLambdaContextType">Type of context passed for the invocation.</param>
        /// <returns>Expression that deserializes incoming stream to the customer method inputs or null if customer method takes no input.</returns>
        /// <exception cref="LambdaValidationException">Thrown when customer method inputs don't meet lambda requirements.</exception>
        private Expression BuildInputExpressionOrNull(object customerSerializerInstance, Expression inStreamParameter, out Type iLambdaContextType)
        {
            Type inputType = null;

            iLambdaContextType = null;

            // get input types
            var inputTypes = _customerMethodInfo.GetParameters().Select(pi => pi.ParameterType).ToArray();

            // check if there are too many parameters
            if (inputTypes.Length > 2)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.MethodTooManyParams,
                                                           _customerMethodInfo.Name, _handler.TypeName);
            }

            // if two parameters, check that the second input is ILambdaContext
            if (inputTypes.Length == 2)
            {
                if (!Types.IsILambdaContext(inputTypes[1]))
                {
                    throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.MethodSecondParamNotContext,
                                                               _customerMethodInfo.Name, _handler.TypeName, Types.ILambdaContextTypeName);
                }

                iLambdaContextType = inputTypes[1];
                inputType          = inputTypes[0];
            }

            // if one input, check if input is ILambdaContext
            else if (inputTypes.Length == 1)
            {
                if (Types.IsILambdaContext(inputTypes[0]))
                {
                    iLambdaContextType = inputTypes[0];
                }
                else
                {
                    inputType = inputTypes[0];
                }
            }

            if (inputType != null)
            {
                // deserializer.Deserialize(inStream)
                return(CreateDeserializeExpression(customerSerializerInstance, inputType, inStreamParameter));
            }

            if (iLambdaContextType != null)
            {
                _logger.LogDebug($"UCL : Validating iLambdaContextType");
                UserCodeValidator.ValidateILambdaContextType(iLambdaContextType);
            }

            return(null);
        }
Пример #4
0
        private Exception GetMultipleMethodsValidationException(TypeInfo typeInfo)
        {
            var signatureList = typeInfo.GetMethods(Constants.DefaultFlags)
                                .Where(mi => SignatureMatches(_handler.MethodName, mi) || NameMatches(_handler.MethodName, mi))
                                .Select(mi => mi.ToString()).ToList();
            var signatureListText = string.Join("\n", signatureList);

            throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.MethodHasOverloads,
                                                       _handler.MethodName, typeInfo.FullName, signatureListText);
        }
Пример #5
0
        /// <summary>
        /// Validates object serializer used for serialization and deserialization of input and output
        /// Throws exception if the specified ILambdaSerializer is a type we can work with
        /// </summary>
        /// <param name="type">Type of the customer's serializer.</param>
        /// <exception cref="LambdaValidationException">Thrown when customer serializer doesn't match with expected serializer definition</exception>
        internal static void ValidateILambdaSerializerType(Type type)
        {
            var typeInfo = type.GetTypeInfo();

            var mismatchReason = CheckILambdaSerializerType(typeInfo);

            if (!string.IsNullOrEmpty(mismatchReason))
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.TypeNotMatchingShape,
                                                           type.FullName, Types.ILambdaSerializerTypeName, mismatchReason);
            }
        }
Пример #6
0
        /// <summary>
        /// Constructs an instance of the customer-specified serializer
        /// </summary>
        /// <param name="serializerAttribute">Serializer attribute used to define the input/output serializer.</param>
        /// <returns></returns>
        /// <exception cref="LambdaValidationException">Thrown when serializer doesn't satisfy serializer type requirements.</exception>
        /// <exception cref="LambdaUserCodeException">Thrown when failed to instantiate serializer type.</exception>
        private object ConstructCustomSerializer(Attribute serializerAttribute)
        {
            var attributeType          = serializerAttribute.GetType();
            var serializerTypeProperty = attributeType.GetTypeInfo().GetProperty("SerializerType");

            if (serializerTypeProperty == null)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.InvalidClassNoSerializerType, attributeType.FullName);
            }

            if (!Types.TypeType.GetTypeInfo().IsAssignableFrom(serializerTypeProperty.PropertyType))
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.InvalidClassSerializerTypeWrongType,
                                                           attributeType.FullName, Types.TypeType.FullName);
            }

            var serializerType = serializerTypeProperty.GetValue(serializerAttribute) as Type;

            if (serializerType == null)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.SerializerTypeNotSet,
                                                           attributeType.FullName);
            }

            var serializerTypeInfo = serializerType.GetTypeInfo();

            var constructor = serializerTypeInfo.GetConstructor(Type.EmptyTypes);

            if (constructor == null)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.SerializerMissingConstructor, serializerType.FullName);
            }

            var iLambdaSerializerType = serializerTypeInfo.GetInterface(Types.ILambdaSerializerTypeName);

            if (iLambdaSerializerType == null)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.InvalidClassNoILambdaSerializer, serializerType.FullName);
            }

            _logger.LogDebug($"UCL : Validating type '{iLambdaSerializerType.FullName}'");
            UserCodeValidator.ValidateILambdaSerializerType(iLambdaSerializerType);

            object customSerializerInstance;

            customSerializerInstance = constructor.Invoke(null);

            return(customSerializerInstance);
        }
Пример #7
0
        private static PropertyInfo ValidateInterfaceProperty(Type type, string propName, string propTypeName)
        {
            var propertyInfo = type.GetTypeInfo().GetProperty(propName, Constants.DefaultFlags);

            if (propertyInfo == null || !string.Equals(propertyInfo.PropertyType.FullName, propTypeName, StringComparison.Ordinal))
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.TypeMissingExpectedProperty, type.FullName, propName, propTypeName);
            }

            if (!propertyInfo.CanRead)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.PropertyNotReadable, propName, type.FullName);
            }

            return(propertyInfo);
        }
Пример #8
0
        /// <summary>
        /// Attempts to find MethodInfo in given type
        /// Returns null if no matching method was found
        /// </summary>
        /// <param name="type">Type that contains customer method.</param>
        /// <returns>Method information of customer method.</returns>
        /// <exception cref="LambdaValidationException">Thrown when failed to find customer method in container type.</exception>
        private MethodInfo FindCustomerMethod(Type type)
        {
            // These are split because finding by name is slightly faster
            // and it's also the more common case.
            // RuntimeMethodInfo::ToString() always contains a ' ' character.
            // So one of the two lookup methods would always return null.
            var customerMethodInfo = FindCustomerMethodByName(type.GetTypeInfo()) ??
                                     FindCustomerMethodBySignature(type.GetTypeInfo());

            if (customerMethodInfo == null)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.NoMatchingMethod,
                                                           _handler.MethodName, _handler.TypeName, _handler.AssemblyName, _handler.MethodName);
            }

            return(customerMethodInfo);
        }
        /// <summary>
        /// Generates an expression to serialize customer method result into the output stream
        /// </summary>
        /// <param name="customerSerializerInstance">Instance of lambda input & output serializer.</param>
        /// <param name="dataType">Customer input type.</param>
        /// <param name="customerObject">Expression that define customer object.</param>
        /// <param name="outStreamParameter">Expression that defines customer output.</param>
        /// <returns>Expression that serializes returned object to output stream.</returns>
        /// <exception cref="LambdaValidationException">Thrown when customer input is serializable & serializer instance is null.</exception>
        private Expression CreateSerializeExpression(object customerSerializerInstance, Type dataType, Expression customerObject, Expression outStreamParameter)
        {
            // generic types, null for String and Stream converters
            Type[] genericTypes          = null;
            var    converter             = customerSerializerInstance;
            Type   iLambdaSerializerType = null;

            // subclasses of Stream are allowed as customer method output
            if (Types.StreamType.GetTypeInfo().IsAssignableFrom(dataType))
            {
                converter = StreamSerializer.Instance;
            }
            else
            {
                if (customerSerializerInstance == null)
                {
                    throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.SerializeMissingAttribute,
                                                               _handler.AssemblyName, _handler.MethodName, dataType.FullName);
                }

                iLambdaSerializerType = customerSerializerInstance
                                        .GetType()
                                        .GetTypeInfo()
                                        .GetInterface(Types.ILambdaSerializerTypeName);
                genericTypes = new[] { dataType };
            }

            // code: serializer
            Expression converterExpression = Expression.Constant(converter);

            if (iLambdaSerializerType != null)
            {
                // code: (ILambdaSerializer)serializer
                converterExpression = Expression.Convert(converterExpression, iLambdaSerializerType);
            }

            // code: [(ILambdaSerializer)]serializer.Serialize[<T>](handler(...), outStream)
            Expression serializeCall = Expression.Call(
                converterExpression, // variable
                "Serialize",         // method name
                genericTypes,        // generic type
                customerObject,      // arg1 - customer object
                outStreamParameter); // arg2 - out stream

            return(serializeCall);
        }
Пример #10
0
        /// <summary>
        /// Validate customer method signature
        /// Throws exception if method is not one we can work with
        /// </summary>
        /// <param name="method">MethodInfo of customer method</param>
        /// <exception cref="LambdaValidationException">Thrown when customer method doesn't satisfy method requirements</exception>
        internal static void ValidateCustomerMethod(MethodInfo method)
        {
            if (method.IsAbstract)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.HandlerMethodAbstract,
                                                           method.ToString());
            }

            if (method.IsGenericMethod)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.HandlerMethodGeneric,
                                                           method.ToString());
            }

            var asyncAttribute = method.GetCustomAttribute(Types.AsyncStateMachineAttributeType);
            var isAsync        = asyncAttribute != null;
            var isVoid         = method.ReturnType == Types.VoidType;

            if (isVoid && isAsync)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.HandlerMethodAsyncVoid,
                                                           method.ToString());
            }

            var inputParameters = method.GetParameters();

            if (inputParameters.Length > 0)
            {
                var lastParameter       = inputParameters[inputParameters.Length - 1];
                var paramArrayAttribute = lastParameter.GetCustomAttribute(Types.ParamArrayAttributeType);
                if (paramArrayAttribute != null)
                {
                    throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.HandlerMethodParams,
                                                               method.ToString());
                }
            }

            // detect VarArgs methods
            if ((method.CallingConvention & CallingConventions.VarArgs) == CallingConventions.VarArgs)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.HandlerMethodVararg,
                                                           method.ToString());
            }
        }
Пример #11
0
 internal static Lazy <CognitoClientContextInternal> GetCognitoClientContextInternalLazy(string text)
 {
     return(new Lazy <CognitoClientContextInternal>(() =>
     {
         CognitoClientContextInternal result = null;
         if (!string.IsNullOrEmpty(text))
         {
             try
             {
                 return CognitoClientContextInternal.FromJson(text);
             }
             catch (Exception innerException)
             {
                 throw LambdaExceptions.ValidationException(innerException, "Unable to parse client context JSON string '{0}'.", text);
             }
         }
         return result;
     }));
 }
Пример #12
0
        internal static void SetCustomerLoggerLogAction(Assembly coreAssembly, Action <string> customerLoggingAction, InternalLogger internalLogger)
        {
            if (coreAssembly == null)
            {
                throw new ArgumentNullException(nameof(coreAssembly));
            }

            if (customerLoggingAction == null)
            {
                throw new ArgumentNullException(nameof(customerLoggingAction));
            }

            internalLogger.LogDebug($"UCL : Retrieving type '{Types.LambdaLoggerTypeName}'");
            var lambdaILoggerType = coreAssembly.GetType(Types.LambdaLoggerTypeName);

            if (lambdaILoggerType == null)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.Internal.UnableToLocateType, Types.LambdaLoggerTypeName);
            }

            internalLogger.LogDebug($"UCL : Retrieving field '{LambdaLoggingActionFieldName}'");
            var loggingActionField = lambdaILoggerType.GetTypeInfo().GetField(LambdaLoggingActionFieldName, BindingFlags.NonPublic | BindingFlags.Static);

            if (loggingActionField == null)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.Internal.UnableToRetrieveField, LambdaLoggingActionFieldName, Types.LambdaLoggerTypeName);
            }

            internalLogger.LogDebug($"UCL : Setting field '{LambdaLoggingActionFieldName}'");
            try
            {
                loggingActionField.SetValue(null, customerLoggingAction);
            }
            catch (Exception e)
            {
                throw LambdaExceptions.ValidationException(e, Errors.UserCodeLoader.Internal.UnableToSetField,
                                                           Types.LambdaLoggerTypeName, LambdaLoggingActionFieldName);
            }
        }
Пример #13
0
        /// <summary>
        /// Constructs an instance of HandlerInfo for a given handler string.
        /// </summary>
        /// <param name="handler"></param>
        public HandlerInfo(string handler)
        {
            if (string.IsNullOrEmpty(handler))
            {
                throw LambdaExceptions.ValidationException(Errors.HandlerInfo.EmptyHandler, HandlerSeparator, HandlerSeparator);
            }

            var parts = handler.Split(new[] { HandlerSeparator }, 3, StringSplitOptions.None);

            if (parts.Length != 3)
            {
                throw LambdaExceptions.ValidationException(Errors.HandlerInfo.InvalidHandler, handler, HandlerSeparator, HandlerSeparator);
            }

            var assemblyName = parts[0].Trim();

            if (string.IsNullOrEmpty(assemblyName))
            {
                throw LambdaExceptions.ValidationException(Errors.HandlerInfo.MissingAssembly, handler, HandlerSeparator, HandlerSeparator);
            }

            var typeName = parts[1].Trim();

            if (string.IsNullOrEmpty(typeName))
            {
                throw LambdaExceptions.ValidationException(Errors.HandlerInfo.MissingType, handler, HandlerSeparator, HandlerSeparator);
            }

            var methodName = parts[2].Trim();

            if (string.IsNullOrEmpty(methodName))
            {
                throw LambdaExceptions.ValidationException(Errors.HandlerInfo.MissingMethod, handler, HandlerSeparator, HandlerSeparator);
            }

            AssemblyName = new AssemblyName(assemblyName);
            TypeName     = typeName;
            MethodName   = methodName;
        }
Пример #14
0
        /// <summary>
        /// Validates ILambdaContext properties and methods
        /// Throws exception if context type is not one we can work with
        /// This checks the set of members on the first version of ILambdaContext type.
        /// DO NOT update this code when new members are added to ILambdaContext type,
        /// it will break existing Lambda deployment packages which still use older version of ILambdaContext.
        /// </summary>
        /// <param name="iLambdaContextType">Type of context passed for the invocation.</param>
        /// <exception cref="LambdaValidationException">Thrown when context doesn't contain required properties & methods.</exception>
        internal static void ValidateILambdaContextType(Type iLambdaContextType)
        {
            if (iLambdaContextType == null)
            {
                return;
            }

            ValidateInterfaceStringProperty(iLambdaContextType, "AwsRequestId");
            ValidateInterfaceStringProperty(iLambdaContextType, "FunctionName");
            ValidateInterfaceStringProperty(iLambdaContextType, "FunctionVersion");
            ValidateInterfaceStringProperty(iLambdaContextType, "InvokedFunctionArn");
            ValidateInterfaceStringProperty(iLambdaContextType, "LogGroupName");
            ValidateInterfaceStringProperty(iLambdaContextType, "LogStreamName");
            ValidateInterfaceProperty <int>(iLambdaContextType, "MemoryLimitInMB");
            ValidateInterfaceProperty <TimeSpan>(iLambdaContextType, "RemainingTime");

            var clientContextProperty = ValidateInterfaceProperty(iLambdaContextType, "ClientContext", Types.IClientContextTypeName);
            var iClientContextType    = clientContextProperty.PropertyType;

            ValidateInterfaceProperty <IDictionary <string, string> >(iClientContextType, "Environment");
            ValidateInterfaceProperty <IDictionary <string, string> >(iClientContextType, "Custom");
            ValidateInterfaceProperty(iClientContextType, "Client", Types.IClientApplicationTypeName);

            var identityProperty     = ValidateInterfaceProperty(iLambdaContextType, "Identity", Types.ICognitoIdentityTypeName);
            var iCognitoIdentityType = identityProperty.PropertyType;

            ValidateInterfaceStringProperty(iCognitoIdentityType, "IdentityId");
            ValidateInterfaceStringProperty(iCognitoIdentityType, "IdentityPoolId");

            var loggerProperty    = ValidateInterfaceProperty(iLambdaContextType, "Logger", Types.ILambdaLoggerTypeName);
            var iLambdaLoggerType = loggerProperty.PropertyType;
            var logMethod         = iLambdaLoggerType.GetTypeInfo().GetMethod("Log", new[] { Types.StringType }, null);

            if (logMethod == null)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.TypeMissingLogMethod, iLambdaLoggerType.FullName);
            }
        }
Пример #15
0
        /// <summary>
        /// Loads customer assembly, type, and method.
        /// After this call returns without errors, it is possible to invoke
        /// the customer method through the Invoke method.
        /// </summary>
        public void Init(Action <string> customerLoggingAction)
        {
            Assembly customerAssembly = null;

            try
            {
                _logger.LogDebug($"UCL : Parsing handler string '{_handlerString}'");
                _handler = new HandlerInfo(_handlerString);

                // Set the logging action private field on the Amazon.Lambda.Core.LambdaLogger type which is part of the
                // public Amazon.Lambda.Core package when it is loaded.
                AppDomain.CurrentDomain.AssemblyLoad += (sender, args) =>
                {
                    _logger.LogInformation($"UCL : Loaded assembly {args.LoadedAssembly.FullName} into default ALC.");
                    if (!_customerLoggerSetUpComplete && string.Equals(LambdaCoreAssemblyName, args.LoadedAssembly.GetName().Name, StringComparison.Ordinal))
                    {
                        _logger.LogDebug(
                            $"UCL : Load context loading '{LambdaCoreAssemblyName}', attempting to set {Types.LambdaLoggerTypeName}.{LambdaLoggingActionFieldName} to logging action.");
                        SetCustomerLoggerLogAction(args.LoadedAssembly, customerLoggingAction, _logger);
                        _customerLoggerSetUpComplete = true;
                    }
                };

                _logger.LogDebug($"UCL : Attempting to load assembly '{_handler.AssemblyName}'");
                customerAssembly = AssemblyLoadContext.Default.LoadFromAssemblyName(_handler.AssemblyName);
            }
            catch (FileNotFoundException fex)
            {
                _logger.LogError(fex, "An error occured on UCL Init");
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.CouldNotFindHandlerAssembly, fex.FileName);
            }
            catch (LambdaValidationException validationException)
            {
                _logger.LogError(validationException, "An error occured on UCL Init");
                throw;
            }
            catch (Exception exception)
            {
                _logger.LogError(exception, "An error occured on UCL Init");
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.UnableToLoadAssembly, _handler.AssemblyName);
            }

            _logger.LogDebug($"UCL : Attempting to load type '{_handler.TypeName}'");
            var customerType = customerAssembly.GetType(_handler.TypeName);

            if (customerType == null)
            {
                throw LambdaExceptions.ValidationException(Errors.UserCodeLoader.UnableToLoadType, _handler.TypeName, _handler.AssemblyName);
            }

            _logger.LogDebug($"UCL : Attempting to find method '{_handler.MethodName}' in type '{_handler.TypeName}'");
            CustomerMethodInfo = FindCustomerMethod(customerType);
            _logger.LogDebug($"UCL : Located method '{CustomerMethodInfo}'");

            _logger.LogDebug($"UCL : Validating method '{CustomerMethodInfo}'");
            UserCodeValidator.ValidateCustomerMethod(CustomerMethodInfo);

            var customerObject = GetCustomerObject(customerType);

            var customerSerializerInstance = GetSerializerObject(customerAssembly);

            _logger.LogDebug($"UCL : Constructing invoke delegate");

            var isPreJit = UserCodeInit.IsCallPreJit();
            var builder  = new InvokeDelegateBuilder(_logger, _handler, CustomerMethodInfo);

            _invokeDelegate = builder.ConstructInvokeDelegate(customerObject, customerSerializerInstance, isPreJit);
            if (isPreJit)
            {
                _logger.LogInformation("PreJit: PrepareDelegate");
                RuntimeHelpers.PrepareDelegate(_invokeDelegate);
            }
        }
Пример #16
0
        /// <summary>
        /// Checks that the ILambdaSerializer type is correct, returning null if type is as expected
        /// or a non-null string with the reason if type is not correct.
        /// </summary>
        /// <param name="typeInfo">TypeInfo of the customer serializer.</param>
        /// <returns>Error string if validation fails else null.</returns>
        private static string CheckILambdaSerializerType(TypeInfo typeInfo)
        {
            if (!typeInfo.IsInterface)
            {
                return(LambdaExceptions.FormatMessage(Errors.UserCodeLoader.ILambdaSerializerMismatch_TypeNotInterface, typeInfo.FullName));
            }

            // check that the Deserialize method exists and is generic
            var deserializeMethodInfo = typeInfo.GetMethod("Deserialize");

            if (deserializeMethodInfo == null)
            {
                return(Errors.UserCodeLoader.ILambdaSerializerMismatch_DeserializeMethodNotFound);
            }

            if (!deserializeMethodInfo.IsGenericMethod)
            {
                return(Errors.UserCodeLoader.ILambdaSerializerMismatch_DeserializeMethodNotGeneric);
            }

            // verify that Stream is the only input
            var deserializeInputs = deserializeMethodInfo.GetParameters();

            if (deserializeInputs.Length != 1)
            {
                return(LambdaExceptions.FormatMessage(Errors.UserCodeLoader.ILambdaSerializerMismatch_DeserializeMethodHasTooManyParams, deserializeInputs.Length));
            }

            if (deserializeInputs[0].ParameterType != Types.StreamType)
            {
                return(LambdaExceptions.FormatMessage(Errors.UserCodeLoader.ILambdaSerializerMismatch_DeserializeMethodHasWrongParam, deserializeInputs[0].ParameterType.FullName,
                                                      Types.StreamType.FullName));
            }

            // verify that T is the return type
            var deserializeOutputType       = deserializeMethodInfo.ReturnType;
            var deserializeGenericArguments = deserializeMethodInfo.GetGenericArguments();

            if (deserializeGenericArguments.Length != 1)
            {
                return(LambdaExceptions.FormatMessage(Errors.UserCodeLoader.ILambdaSerializerMismatch_DeserializeMethodHasWrongNumberGenericArgs,
                                                      deserializeGenericArguments.Length));
            }

            if (deserializeGenericArguments[0] != deserializeOutputType)
            {
                return(LambdaExceptions.FormatMessage(Errors.UserCodeLoader.ILambdaSerializerMismatch_DeserializeMethodHasWrongReturn, deserializeOutputType.FullName));
            }

            // check that the Serialize method exists, is generic, and returns void
            var serializeMethodInfo = typeInfo.GetMethod("Serialize");

            if (serializeMethodInfo == null)
            {
                return(Errors.UserCodeLoader.ILambdaSerializerMismatch_SerializeMethodNotFound);
            }

            if (!serializeMethodInfo.IsGenericMethod)
            {
                return(Errors.UserCodeLoader.ILambdaSerializerMismatch_SerializeMethodNotGeneric);
            }

            if (serializeMethodInfo.ReturnType != Types.VoidType)
            {
                return(LambdaExceptions.FormatMessage(Errors.UserCodeLoader.ILambdaSerializerMismatch_SerializeMethodHasWrongReturn, serializeMethodInfo.ReturnType.FullName));
            }

            // verify that T is the first input and Stream is the second input
            var serializeInputs           = serializeMethodInfo.GetParameters();
            var serializeGenericArguments = serializeMethodInfo.GetGenericArguments();

            if (serializeInputs.Length != 2)
            {
                return(LambdaExceptions.FormatMessage(Errors.UserCodeLoader.ILambdaSerializerMismatch_SerializeMethodHasWrongNumberOfParameters, serializeInputs.Length));
            }

            if (serializeGenericArguments.Length != 1)
            {
                return(LambdaExceptions.FormatMessage(Errors.UserCodeLoader.ILambdaSerializerMismatch_SerializeMethodHasWrongNumberGenericArgs, serializeGenericArguments.Length));
            }

            if (serializeInputs[0].ParameterType != serializeGenericArguments[0])
            {
                return(LambdaExceptions.FormatMessage(Errors.UserCodeLoader.ILambdaSerializerMismatch_SerializeMethodHasWrongFirstParam, serializeInputs[0].ParameterType.FullName));
            }

            if (serializeInputs[1].ParameterType != Types.StreamType)
            {
                return(LambdaExceptions.FormatMessage(Errors.UserCodeLoader.ILambdaSerializerMismatch_SerializeMethodHasWrongSecondParam, serializeInputs[1].ParameterType.FullName,
                                                      Types.StreamType.FullName));
            }

            // all good!
            return(null);
        }