/* * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ using System; using System.Collections.Generic; using System.Reflection; using Amazon.Lambda.RuntimeSupport.ExceptionHandling; using Amazon.Lambda.RuntimeSupport.Helpers; namespace Amazon.Lambda.RuntimeSupport.Bootstrap { internal static class UserCodeValidator { /// /// Throws exception if type is not one we can work with /// /// Container type of customer method. /// Method information of customer method. /// Throws when customer type is not lambda compatible. 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); } } /// /// Validate customer method signature /// Throws exception if method is not one we can work with /// /// MethodInfo of customer method /// Thrown when customer method doesn't satisfy method requirements 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()); } } /// /// 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 /// /// Type of the customer's serializer. /// Thrown when customer serializer doesn't match with expected serializer definition 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); } } /// /// 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. /// /// TypeInfo of the customer serializer. /// Error string if validation fails else null. 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; } /// /// 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. /// /// Type of context passed for the invocation. /// Thrown when context doesn't contain required properties & methods. 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(iLambdaContextType, "MemoryLimitInMB"); ValidateInterfaceProperty(iLambdaContextType, "RemainingTime"); var clientContextProperty = ValidateInterfaceProperty(iLambdaContextType, "ClientContext", Types.IClientContextTypeName); var iClientContextType = clientContextProperty.PropertyType; ValidateInterfaceProperty>(iClientContextType, "Environment"); ValidateInterfaceProperty>(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); } } private static void ValidateInterfaceStringProperty(Type type, string propName) { ValidateInterfaceProperty(type, propName); } private static void ValidateInterfaceProperty(Type type, string propName) { var propType = typeof(T); ValidateInterfaceProperty(type, propName, propType.FullName); } 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; } } }