public void GetMatchingMethod_ListParam_Match_Snake_Case(string parameterNameCase) { DefaultRequestMatcher matcher = this.GetMatcher(); IEnumerable <KeyValuePair <string, RpcParameterType> > parameters = new[] { new KeyValuePair <string, RpcParameterType>(parameterNameCase, RpcParameterType.String) }; string methodName = nameof(MethodMatcherController.SnakeCaseParams); var requestSignature = RpcRequestSignature.Create(methodName, parameters); RpcMethodInfo methodInfo = matcher.GetMatchingMethod(requestSignature); Assert.NotNull(methodInfo); MethodInfo expectedMethodInfo = typeof(MethodMatcherController).GetMethod(methodName) !; Assert.Equal(expectedMethodInfo, methodInfo.MethodInfo); Assert.Single(methodInfo.Parameters); Assert.False(methodInfo.Parameters[0].IsOptional); Assert.Equal(typeof(string), methodInfo.Parameters[0].RawType); Assert.Equal(RpcParameterType.String, methodInfo.Parameters[0].Type); Assert.True(RpcUtil.NamesMatch(methodInfo.Parameters[0].Name, parameterNameCase)); }
private IRpcMethodInfo[] FilterMatchesByCaseSensitiveMethod(RpcRequestSignature requestSignature, Span <IRpcMethodInfo> matches) { //Try to remove ambiguity with case sensitive check IRpcMethodInfo[] caseSensitiveMatches = ArrayPool <IRpcMethodInfo> .Shared.Rent(matches.Length); try { int caseSensitiveCount = 0; for (int i = 0; i < matches.Length; i++) { IRpcMethodInfo m = matches[i]; Memory <char> requestMethodName = requestSignature.GetMethodName(); if (m.Name.Length == requestMethodName.Length) { if (!RpcUtil.NamesMatch(m.Name.AsSpan(), requestMethodName.Span)) { //TODO do we care about the case where 2+ parameters have very similar names and types? continue; } caseSensitiveMatches[caseSensitiveCount++] = m; } } return(caseSensitiveMatches.AsSpan(0, caseSensitiveCount).ToArray()); } finally { ArrayPool <IRpcMethodInfo> .Shared.Return(caseSensitiveMatches, clearArray : false); } }
private IRpcMethodInfo[] FilterBySimilarParams(RpcRequestSignature requestSignature, Span <IRpcMethodInfo> methodsWithSameName) { IRpcMethodInfo[] potentialMatches = ArrayPool <IRpcMethodInfo> .Shared.Rent(methodsWithSameName.Length); try { int potentialMatchCount = 0; for (int i = 0; i < methodsWithSameName.Length; i++) { IRpcMethodInfo m = methodsWithSameName[i]; bool isMatch = this.ParametersMatch(requestSignature, m.Parameters); if (isMatch) { potentialMatches[potentialMatchCount++] = m; } } if (potentialMatchCount <= 1) { return(potentialMatches.AsSpan(0, potentialMatchCount).ToArray()); } return(this.FilterMatchesByCaseSensitiveMethod(requestSignature, potentialMatches.AsSpan(0, potentialMatchCount))); } finally { ArrayPool <IRpcMethodInfo> .Shared.Return(potentialMatches, clearArray : false); } }
private IRpcMethodInfo[] GetMatchingMethods(RpcRequestSignature requestSignature, IReadOnlyList <IRpcMethodInfo> methods) { IRpcMethodInfo[] methodsWithSameName = ArrayPool <IRpcMethodInfo> .Shared.Rent(methods.Count); try { //Case insenstive check for hybrid approach. Will check for case sensitive if there is ambiguity int methodsWithSameNameCount = 0; for (int i = 0; i < methods.Count; i++) { IRpcMethodInfo methodInfo = methods[i]; if (RpcUtil.NamesMatch(methodInfo.Name.AsSpan(), requestSignature.GetMethodName().Span)) { methodsWithSameName[methodsWithSameNameCount++] = methodInfo; } } if (methodsWithSameNameCount < 1) { return(Array.Empty <IRpcMethodInfo>()); } return(this.FilterBySimilarParams(requestSignature, methodsWithSameName.AsSpan(0, methodsWithSameNameCount))); } finally { ArrayPool <IRpcMethodInfo> .Shared.Return(methodsWithSameName, clearArray : false); } }
public RpcMethodInfo GetMatchingMethod(RpcRequestSignature requestSignature) { this.logger.AttemptingToMatchMethod(new string(requestSignature.GetMethodName().Span)); IReadOnlyList <MethodInfo> methods = this.methodProvider.Get(); if (methods == null || !methods.Any()) { throw new RpcException(RpcErrorCode.MethodNotFound, $"No methods found for route"); } RpcMethodInfo[] compiledMethods = ArrayPool <RpcMethodInfo> .Shared.Rent(methods.Count); Span <RpcMethodInfo> matches; try { this.FillRpcMethodInfos(methods, compiledMethods); matches = this.FilterAndBuildMethodInfoByRequest(compiledMethods.AsMemory(0, methods.Count), requestSignature); } finally { ArrayPool <RpcMethodInfo> .Shared.Return(compiledMethods, clearArray : true); } if (matches.Length == 1) { this.logger.RequestMatchedMethod(); return(matches[0]); } string errorMessage; if (matches.Length > 1) { var methodInfoList = new List <string>(); foreach (RpcMethodInfo matchedMethod in matches) { var parameterTypeList = new List <string>(); foreach (ParameterInfo parameterInfo in matchedMethod.MethodInfo.GetParameters()) { string parameterType = parameterInfo.Name + ": " + parameterInfo.ParameterType.Name; if (parameterInfo.IsOptional) { parameterType += "(Optional)"; } parameterTypeList.Add(parameterType); } string parameterString = string.Join(", ", parameterTypeList); methodInfoList.Add($"{{Name: '{matchedMethod.MethodInfo.Name}', Parameters: [{parameterString}]}}"); } errorMessage = "More than one method matched the rpc request. Unable to invoke due to ambiguity. Methods that matched the same name: " + string.Join(", ", methodInfoList); } else { //Log diagnostics this.logger.MethodsInRoute(methods); errorMessage = "No methods matched request."; } throw new RpcException(RpcErrorCode.MethodNotFound, errorMessage); }
public void Create_NullDictParam_Valid() { string methodName = "Test"; var signature = RpcRequestSignature.Create(methodName, parameters: (IEnumerable <KeyValuePair <string, RpcParameterType> >?)null); Assert.Equal(methodName, signature.GetMethodName().ToString()); Assert.False(signature.HasParameters); Assert.False(signature.IsDictionary); Assert.Empty(signature.ParametersAsList); }
public void SimpleIterationSetup() { var parameters = new Dictionary <string, RpcParameterType> { { "a", RpcParameterType.Number }, { "b", RpcParameterType.Boolean }, { "c", RpcParameterType.String } }; this.requestsignature = RpcRequestSignature.Create(nameof(MethodClass.SimpleParamsNoReturn), parameters); }
public void Create_EmptyDictParam_Valid() { string methodName = "Test"; var parameters = new Dictionary <string, RpcParameterType>(); var signature = RpcRequestSignature.Create(methodName, parameters); Assert.Equal(methodName, signature.GetMethodName().ToString()); Assert.False(signature.HasParameters); Assert.True(signature.IsDictionary); this.AssertDictsEqual(parameters, signature.ParametersAsDict); }
public void Create_MultiListParam_Valid() { string methodName = "Test"; RpcParameterType[] parameters = new[] { RpcParameterType.String, RpcParameterType.Boolean, RpcParameterType.Null, RpcParameterType.Number, RpcParameterType.Object }; var signature = RpcRequestSignature.Create(methodName, parameters); Assert.Equal(methodName, signature.GetMethodName().ToString()); Assert.True(signature.HasParameters); Assert.False(signature.IsDictionary); Assert.Equal(parameters, signature.ParametersAsList); }
public void Create_EmptyListParam_Valid() { string methodName = "Test"; RpcParameterType[] parameters = new RpcParameterType[0]; var signature = RpcRequestSignature.Create(methodName, parameters); Assert.Equal(methodName, signature.GetMethodName().ToString()); Assert.False(signature.HasParameters); Assert.False(signature.IsDictionary); Assert.Equal(parameters, signature.ParametersAsList); }
public IRpcMethodInfo GetMatchingMethod(RpcRequestSignature requestSignature) { this.logger.AttemptingToMatchMethod(new string(requestSignature.GetMethodName().Span)); RpcContext context = this.contextAccessor.Get(); IReadOnlyList <IRpcMethodInfo>?methods = this.methodProvider.GetByPath(context.Path); if (methods == null || !methods.Any()) { throw new RpcException(RpcErrorCode.MethodNotFound, $"No methods found for route"); } Span <IRpcMethodInfo> matches = this.FilterAndBuildMethodInfoByRequest(methods, requestSignature); if (matches.Length == 1) { this.logger.RequestMatchedMethod(); return(matches[0]); } string errorMessage; if (matches.Length > 1) { var methodInfoList = new List <string>(); foreach (IRpcMethodInfo matchedMethod in matches) { var parameterTypeList = new List <string>(); foreach (IRpcParameterInfo parameterInfo in matchedMethod.Parameters) { string parameterType = parameterInfo.Name + ": " + parameterInfo.Type; if (parameterInfo.IsOptional) { parameterType += "(Optional)"; } parameterTypeList.Add(parameterType); } string parameterString = string.Join(", ", parameterTypeList); methodInfoList.Add($"{{Name: '{matchedMethod.Name}', Parameters: [{parameterString}]}}"); } errorMessage = "More than one method matched the rpc request. Unable to invoke due to ambiguity. Methods that matched the same name: " + string.Join(", ", methodInfoList); } else { //Log diagnostics this.logger.MethodsInRoute(methods); errorMessage = "No methods matched request."; } throw new RpcException(RpcErrorCode.MethodNotFound, errorMessage); }
public void GetMatchingMethod_SimpleMulitParam_DictMatch() { DefaultRequestMatcher matcher = this.GetMatcher(); var parameters = new Dictionary <string, RpcParameterType> { { "a", RpcParameterType.Number }, { "b", RpcParameterType.Boolean }, { "c", RpcParameterType.String }, { "d", RpcParameterType.Object }, { "e", RpcParameterType.Null } }; string methodName = nameof(MethodMatcherController.SimpleMulitParam); var requestSignature = RpcRequestSignature.Create(methodName, parameters); RpcMethodInfo methodInfo = matcher.GetMatchingMethod(requestSignature); Assert.NotNull(methodInfo); MethodInfo expectedMethodInfo = typeof(MethodMatcherController).GetMethod(methodName) !; Assert.Equal(expectedMethodInfo, methodInfo.MethodInfo); Assert.Equal(5, methodInfo.Parameters.Length); Assert.False(methodInfo.Parameters[0].IsOptional); Assert.Equal(typeof(int), methodInfo.Parameters[0].RawType); Assert.Equal(RpcParameterType.Number, methodInfo.Parameters[0].Type); Assert.Equal("a", methodInfo.Parameters[0].Name); Assert.False(methodInfo.Parameters[1].IsOptional); Assert.Equal(typeof(bool), methodInfo.Parameters[1].RawType); Assert.Equal(RpcParameterType.Boolean, methodInfo.Parameters[1].Type); Assert.Equal("b", methodInfo.Parameters[1].Name); Assert.False(methodInfo.Parameters[2].IsOptional); Assert.Equal(typeof(string), methodInfo.Parameters[2].RawType); Assert.Equal(RpcParameterType.String, methodInfo.Parameters[2].Type); Assert.Equal("c", methodInfo.Parameters[2].Name); Assert.False(methodInfo.Parameters[3].IsOptional); Assert.Equal(typeof(object), methodInfo.Parameters[3].RawType); Assert.Equal(RpcParameterType.Object, methodInfo.Parameters[3].Type); Assert.Equal("d", methodInfo.Parameters[3].Name); Assert.True(methodInfo.Parameters[4].IsOptional); Assert.Equal(typeof(int?), methodInfo.Parameters[4].RawType); Assert.Equal(RpcParameterType.Object, methodInfo.Parameters[4].Type); Assert.Equal("e", methodInfo.Parameters[4].Name); }
public void GetMatchingMethod_GuidParameter_Match() { string methodName = nameof(MethodMatcherController.GuidTypeMethod); DefaultRequestMatcher matcher = this.GetMatcher(path: typeof(MethodMatcherController).GetTypeInfo().Name); var requestSignature = RpcRequestSignature.Create(methodName, new[] { RpcParameterType.String }); IRpcMethodInfo methodInfo = matcher.GetMatchingMethod(requestSignature); Assert.NotNull(methodInfo); Assert.Equal(methodName, methodInfo.Name); Assert.Single(methodInfo.Parameters); Assert.False(methodInfo.Parameters[0].IsOptional); Assert.Equal(typeof(Guid), methodInfo.Parameters[0].RawType); Assert.Equal(RpcParameterType.Object, methodInfo.Parameters[0].Type); Assert.Equal("guid", methodInfo.Parameters[0].Name); }
public void GetMatchingMethod_WithRpcRoute() { string methodName = nameof(MethodMatcherController.GuidTypeMethod); RpcRequestSignature requestSignature = RpcRequestSignature.Create(methodName, new[] { RpcParameterType.String }); DefaultRequestMatcher path1Matcher = this.GetMatcher(path: typeof(MethodMatcherController).GetTypeInfo().Name); IRpcMethodInfo path1Match = path1Matcher.GetMatchingMethod(requestSignature); Assert.NotNull(path1Match); DefaultRequestMatcher path2Matcher = this.GetMatcher(path: typeof(MethodMatcherDuplicatesController).GetTypeInfo().Name); IRpcMethodInfo path2Match = path2Matcher.GetMatchingMethod(requestSignature); Assert.NotNull(path2Match); Assert.NotSame(path1Match, path2Match); }
public void Create_MultiDictParam_Valid() { string methodName = "Test"; var parameters = new Dictionary <string, RpcParameterType> { ["String"] = RpcParameterType.String, ["Boolean"] = RpcParameterType.Boolean, ["Null"] = RpcParameterType.Null, ["Number"] = RpcParameterType.Number, ["Object"] = RpcParameterType.Object, }; var signature = RpcRequestSignature.Create(methodName, parameters); Assert.Equal(methodName, signature.GetMethodName().ToString()); Assert.True(signature.HasParameters); Assert.True(signature.IsDictionary); this.AssertDictsEqual(parameters, signature.ParametersAsDict); }
public void GetMatchingMethod_ListParam_Match() { DefaultRequestMatcher matcher = this.GetMatcher(path: typeof(MethodMatcherController).GetTypeInfo().Name); RpcParameterType[] parameters = new[] { RpcParameterType.Object }; string methodName = nameof(MethodMatcherController.List); var requestSignature = RpcRequestSignature.Create(methodName, parameters); IRpcMethodInfo methodInfo = matcher.GetMatchingMethod(requestSignature); Assert.NotNull(methodInfo); Assert.Equal(methodName, methodInfo.Name); Assert.Single(methodInfo.Parameters); Assert.False(methodInfo.Parameters[0].IsOptional); Assert.Equal(typeof(List <string>), methodInfo.Parameters[0].RawType); Assert.Equal(RpcParameterType.Object, methodInfo.Parameters[0].Type); Assert.Equal("values", methodInfo.Parameters[0].Name); }
public void GetMatchingMethod_CulturallyInvariantComparison() { DefaultRequestMatcher matcher = this.GetMatcher(path: typeof(MethodMatcherController).GetTypeInfo().Name); RpcParameterType[] parameters = Array.Empty <RpcParameterType>(); string methodName = nameof(MethodMatcherController.IsLunchTime); // Use lowercase version of method name when making request. var methodNameLower = methodName.ToLowerInvariant(); var requestSignature = RpcRequestSignature.Create(methodNameLower, parameters); var previousCulture = System.Globalization.CultureInfo.CurrentCulture; // Switch to a culture that would result in lowercasing 'I' to // U+0131, if not done with invariant culture. System.Globalization.CultureInfo.CurrentCulture = new System.Globalization.CultureInfo("az"); IRpcMethodInfo methodInfo = matcher.GetMatchingMethod(requestSignature); Assert.NotNull(methodInfo); Assert.Equal(methodName, methodInfo.Name); System.Globalization.CultureInfo.CurrentCulture = previousCulture; }
public void GetMatchingMethod_SimpleMulitParam_ListMatch() { DefaultRequestMatcher matcher = this.GetMatcher(path: typeof(MethodMatcherController).GetTypeInfo().Name); RpcParameterType[] parameters = new[] { RpcParameterType.Number, RpcParameterType.Boolean, RpcParameterType.String, RpcParameterType.Object, RpcParameterType.Null }; string methodName = nameof(MethodMatcherController.SimpleMulitParam); var requestSignature = RpcRequestSignature.Create(methodName, parameters); IRpcMethodInfo methodInfo = matcher.GetMatchingMethod(requestSignature); Assert.NotNull(methodInfo); Assert.Equal(methodName, methodInfo.Name); Assert.Equal(5, methodInfo.Parameters.Count); Assert.False(methodInfo.Parameters[0].IsOptional); Assert.Equal(typeof(int), methodInfo.Parameters[0].RawType); Assert.Equal(RpcParameterType.Number, methodInfo.Parameters[0].Type); Assert.Equal("a", methodInfo.Parameters[0].Name); Assert.False(methodInfo.Parameters[1].IsOptional); Assert.Equal(typeof(bool), methodInfo.Parameters[1].RawType); Assert.Equal(RpcParameterType.Boolean, methodInfo.Parameters[1].Type); Assert.Equal("b", methodInfo.Parameters[1].Name); Assert.False(methodInfo.Parameters[2].IsOptional); Assert.Equal(typeof(string), methodInfo.Parameters[2].RawType); Assert.Equal(RpcParameterType.String, methodInfo.Parameters[2].Type); Assert.Equal("c", methodInfo.Parameters[2].Name); Assert.False(methodInfo.Parameters[3].IsOptional); Assert.Equal(typeof(object), methodInfo.Parameters[3].RawType); Assert.Equal(RpcParameterType.Object, methodInfo.Parameters[3].Type); Assert.Equal("d", methodInfo.Parameters[3].Name); Assert.True(methodInfo.Parameters[4].IsOptional); Assert.Equal(typeof(int?), methodInfo.Parameters[4].RawType); Assert.Equal(RpcParameterType.Object, methodInfo.Parameters[4].Type); Assert.Equal("e", methodInfo.Parameters[4].Name); }
public void ComplexIterationSetup() { this.requestsignature = RpcRequestSignature.Create(nameof(MethodClass.ComplexParamNoReturn), new[] { RpcParameterType.Object }); }
public void IterationSetup() { this.requestsignature = RpcRequestSignature.Create(nameof(MethodClass.NoParamsNoReturn)); }
/// <summary> /// Call the incoming Rpc request method and gives the appropriate response /// </summary> /// <param name="request">Rpc request</param> /// <param name="path">Rpc path that applies to the current request</param> /// <param name="routeContext">The context of the current rpc request</param> /// <returns>An Rpc response for the request</returns> public async Task <RpcResponse?> InvokeRequestAsync(RpcRequest request) { if (request == null) { throw new ArgumentNullException(nameof(request)); } this.logger.InvokingRequest(request.Id); RpcResponse rpcResponse; try { RpcMethodInfo rpcMethod; using (var requestSignature = RpcRequestSignature.Create(request)) { rpcMethod = this.rpcRequestMatcher.GetMatchingMethod(requestSignature); } bool isAuthorized = await this.authorizationHandler.IsAuthorizedAsync(rpcMethod.MethodInfo); if (isAuthorized) { object[] realParameters = this.ParseParameters(request.Parameters, rpcMethod.Parameters); this.logger.InvokeMethod(request.Method); IRpcContext routeContext = this.contextAccessor.Value !; object? result = await this.InvokeAsync(rpcMethod.MethodInfo, realParameters, request, routeContext.RequestServices); this.logger.InvokeMethodComplete(request.Method); if (result is IRpcMethodResult methodResult) { rpcResponse = methodResult.ToRpcResponse(request.Id); } else { rpcResponse = new RpcResponse(request.Id, result); } } else { var authError = new RpcError(RpcErrorCode.InvalidRequest, "Unauthorized"); rpcResponse = new RpcResponse(request.Id, authError); } } catch (Exception ex) { const string errorMessage = "An Rpc error occurred while trying to invoke request."; this.logger.LogException(ex, errorMessage); RpcError error; if (ex is RpcException rpcException) { error = rpcException.ToRpcError(this.serverConfig.Value.ShowServerExceptions); } else { error = new RpcError(RpcErrorCode.InternalError, errorMessage, ex); } rpcResponse = new RpcResponse(request.Id, error); } if (request.Id.HasValue) { this.logger.FinishedRequest(request.Id.ToString()); //Only give a response if there is an id return(rpcResponse); } //TODO make no id run in a non-blocking way this.logger.FinishedRequestNoId(); return(null); }
private bool ParametersMatch(RpcRequestSignature requestSignature, IReadOnlyList <IRpcParameterInfo> parameters) { if (!requestSignature.HasParameters) { return(parameters == null || !parameters.Any(p => !p.IsOptional)); } int parameterCount = 0; if (requestSignature.IsDictionary) { foreach ((Memory <char> name, RpcParameterType type) in requestSignature.ParametersAsDict) { bool found = false; for (int paramIndex = 0; paramIndex < parameters.Count; paramIndex++) { IRpcParameterInfo parameter = parameters[paramIndex]; if (!RpcUtil.NamesMatch(parameter.Name.AsSpan(), name.Span) || !RpcParameterUtil.TypesCompatible(parameter.Type, type)) { continue; } found = true; break; } if (!found) { return(false); } parameterCount++; } } else { foreach (RpcParameterType parameterType in requestSignature.ParametersAsList) { if (parameters.Count <= parameterCount) { return(false); } IRpcParameterInfo info = parameters[parameterCount]; if (!RpcParameterUtil.TypesCompatible(info.Type, parameterType)) { return(false); } parameterCount++; } for (int i = parameterCount; i < parameters.Count; i++) { //Only if the last parameters in the method are optional does the request match //Will be skipped if they are equal length if (!parameters[i].IsOptional) { return(false); } } } if (parameterCount != parameters.Count) { return(false); } return(true); }
private IRpcMethodInfo[] FilterAndBuildMethodInfoByRequest(IReadOnlyList <IRpcMethodInfo> methods, RpcRequestSignature requestSignature) { //If the request signature is found, it means we have the methods cached already var rpcPath = this.contextAccessor.Get()?.Path; var rpcPathMethodsCache = DefaultRequestMatcher.requestToMethodCache.GetOrAdd(rpcPath, path => new ConcurrentDictionary <RpcRequestSignature, IRpcMethodInfo[]>()); return(rpcPathMethodsCache.GetOrAdd(requestSignature, BuildMethodCache)); IRpcMethodInfo[] BuildMethodCache(RpcRequestSignature s) { return(this.GetMatchingMethods(s, methods)); } }