diff --git a/samples/Example.General/Program.cs b/samples/Example.General/Program.cs index 27d5fd1..aed3933 100644 --- a/samples/Example.General/Program.cs +++ b/samples/Example.General/Program.cs @@ -41,7 +41,7 @@ class Test001 : ITest001 public async Task Login(LoginRequestMsg login) { Console.WriteLine($"Worker ({_uuid}) - Username: {login.Username} Password: {login.Password}"); RpcContext context = RpcContext.Current; - + return "Wow"; } @@ -85,8 +85,12 @@ static async Task AsyncMain(string[] args) { Console.WriteLine(); } - // detaches the service - await TestNode.DetachAsync(service).ConfigureAwait(false); + var proxy = TestNode.Proxy("auth:login"); + string a = await proxy.Login(new LoginRequestMsg() + { + Username = "wow", + Password = "wow" + }); await Task.Delay(50000); } diff --git a/src/Holon/Holon.csproj b/src/Holon/Holon.csproj index 6cef4bd..8deceaa 100644 --- a/src/Holon/Holon.csproj +++ b/src/Holon/Holon.csproj @@ -1,8 +1,8 @@ - + netstandard1.6 - 0.2.0 + 0.2.1 Alan Doherty Alan Doherty A minimal service and event bus with additional support for RPC @@ -10,11 +10,11 @@ https://github.com/alandoherty/holon-net https://github.com/alandoherty/holon-net git - 0.2.0.0 + 0.2.1.0 https://github.com/alandoherty/holon-net/blob/master/LICENSE true https://s3-eu-west-1.amazonaws.com/assets.alandoherty.co.uk/github/holon-net-nuget.png - 0.2.0.0 + 0.2.1.0 diff --git a/src/Holon/Remoting/RpcBehaviour.cs b/src/Holon/Remoting/RpcBehaviour.cs index a9eee01..0f11f3c 100644 --- a/src/Holon/Remoting/RpcBehaviour.cs +++ b/src/Holon/Remoting/RpcBehaviour.cs @@ -78,9 +78,9 @@ private async Task ApplyAsync(RpcHeader header, Envelope envelope) { try { req = serializer.DeserializeRequest(envelope.Body, (i, o) => RpcArgument.FromMember(GetMember(i, o))); } catch (KeyNotFoundException) { - res = new RpcResponse("InterfaceNotFound", "The interface or operation could not be found"); + res = new RpcResponse("InterfaceNotFound", "The interface or operation could not be found", null); } catch(Exception ex) { - res = new RpcResponse("ArgumentInvalid", string.Format("The request format is invalid: {0}", ex.Message)); + res = new RpcResponse("ArgumentInvalid", string.Format("The request format is invalid: {0}", ex.Message), null); } // apply single request @@ -90,7 +90,7 @@ private async Task ApplyAsync(RpcHeader header, Envelope envelope) { if (req != null) memberInfo = GetMember(req.Interface, req.Operation); } catch (KeyNotFoundException) { - res = new RpcResponse("OperationNotFound", "The interface or operation could not be found"); + res = new RpcResponse("OperationNotFound", "The interface or operation could not be found", null); } // get operation information @@ -107,7 +107,7 @@ private async Task ApplyAsync(RpcHeader header, Envelope envelope) { // check if they have a response ID if no reply isn't enabled if (!noReply && envelope.ID == Guid.Empty) - res = new RpcResponse("InvalidOperation", "The envelope does not specify a correlation ID"); + res = new RpcResponse("InvalidOperation", "The envelope does not specify a correlation ID", null); // apply request if we don't have a response already, typically an error if (res == null) { @@ -192,7 +192,7 @@ private MemberInfo GetMember(string @interface, string operation) { private async Task ApplyRequestAsync(RpcRequest req, MemberInfo member) { // find interface behaviour if (!_behaviours.TryGetValue(req.Interface, out Binding binding)) - return new RpcResponse("InterfaceNotFound", "The interface binding could not be found"); + return new RpcResponse("InterfaceNotFound", "The interface binding could not be found", null); // get method info MethodInfo operationMethod = member as MethodInfo; @@ -205,7 +205,7 @@ private async Task ApplyRequestAsync(RpcRequest req, MemberInfo mem for (int i = 0; i < methodArgs.Length; i++) { if (!req.Arguments.TryGetValue(methodParams[i].Name, out methodArgs[i])) { if (!methodParams[i].IsOptional) - return new RpcResponse("ArgumentRequired", string.Format("The argument {0} is not optional", methodParams[i].Name)); + return new RpcResponse("ArgumentRequired", string.Format("The argument {0} is not optional", methodParams[i].Name), null); } } @@ -216,9 +216,9 @@ private async Task ApplyRequestAsync(RpcRequest req, MemberInfo mem methodResult = operationMethod.Invoke(binding.Behaviour, methodArgs); await ((Task)methodResult).ConfigureAwait(false); } catch (RpcException ex) { - return new RpcResponse(ex.Code, ex.Message); + return new RpcResponse(ex.Code, ex.Message, ex.ToString()); } catch (Exception ex) { - return new RpcResponse("Exception", ex.ToString()); + return new RpcResponse("Exception", ex.Message, ex.ToString()); } // check if the operation returns anything @@ -416,7 +416,7 @@ public Task GetInterfaceInfo(string @interface) { lock (_behaviour._behaviours) { if (!_behaviour._behaviours.TryGetValue(@interface, out binding) || !binding.AllowIntrospection) - throw new RpcException("InterfaceNotFound", "The interface does not exist"); + throw new RpcException("InterfaceNotFound", "The interface does not exist", null); } return Task.FromResult(binding.Introspection); diff --git a/src/Holon/Remoting/RpcError.cs b/src/Holon/Remoting/RpcError.cs index 4d6a597..3fff115 100644 --- a/src/Holon/Remoting/RpcError.cs +++ b/src/Holon/Remoting/RpcError.cs @@ -12,6 +12,7 @@ public sealed class RpcError #region Fields private string _code; private string _message; + private string _details; #endregion #region Properties @@ -32,6 +33,15 @@ public string Message { return _message; } } + + /// + /// Gets the error details. + /// + public string Details { + get { + return _details ?? _message; + } + } #endregion #region Constructors @@ -40,9 +50,11 @@ public string Message { /// /// The code. /// The message. - internal RpcError(string code, string message) { + /// The details. + internal RpcError(string code, string message, string details) { _code = code; _message = message; + _details = details; } #endregion } diff --git a/src/Holon/Remoting/RpcException.cs b/src/Holon/Remoting/RpcException.cs index eb8011f..1c8ff08 100644 --- a/src/Holon/Remoting/RpcException.cs +++ b/src/Holon/Remoting/RpcException.cs @@ -11,6 +11,7 @@ public class RpcException : Exception { #region Fields private string _code; + private string _details; #endregion #region Properties @@ -22,28 +23,28 @@ public string Code { return _code; } } - #endregion - #region Constructors /// - /// Creates a new RPC excpetion with the provided code and message. + /// Gets the details. /// - /// The code. - /// The message. - public RpcException(string code, string message) - : base(message) { - _code = code; + public string Details { + get { + return _details; + } } + #endregion + #region Constructors /// - /// Creates a new RPC exception with the provided code, message and inner exception. + /// Creates a new RPC excpetion with the provided code and message. /// /// The code. /// The message. - /// The inner exception. - public RpcException(string code, string message, Exception innerException) - : base(message, innerException) { + /// The details. + public RpcException(string code, string message, string details) + : base(message) { _code = code; + _details = details; } /// @@ -51,16 +52,7 @@ public RpcException(string code, string message, Exception innerException) /// /// The error. public RpcException(RpcError error) - : this(error.Code, error.Message) { - } - - /// - /// Creates a new RPC exception with the RPC error object and inner exception. - /// - /// The error. - /// The inner exception. - public RpcException(RpcError error, Exception innerException) - : this(error.Code, error.Message, innerException) { + : this(error.Code, error.Message, error.Details) { } #endregion } diff --git a/src/Holon/Remoting/RpcResponse.cs b/src/Holon/Remoting/RpcResponse.cs index 8a89b98..9699802 100644 --- a/src/Holon/Remoting/RpcResponse.cs +++ b/src/Holon/Remoting/RpcResponse.cs @@ -70,8 +70,9 @@ internal RpcResponse(object data, Type type) { /// /// The code. /// The message. - internal RpcResponse(string code, string message) { - _error = new RpcError(code, message); + /// The details. + internal RpcResponse(string code, string message, string details) { + _error = new RpcError(code, message, details); _data = null; } diff --git a/src/Holon/Remoting/Serializers/ProtobufRpcSerializer.cs b/src/Holon/Remoting/Serializers/ProtobufRpcSerializer.cs index 54eaca5..5649141 100644 --- a/src/Holon/Remoting/Serializers/ProtobufRpcSerializer.cs +++ b/src/Holon/Remoting/Serializers/ProtobufRpcSerializer.cs @@ -63,7 +63,7 @@ public RpcResponse DeserializeResponse(byte[] body, Type responseType) { if (msg.IsSuccess) return new RpcResponse(msg.Data.GetData(responseType), responseType); else - return new RpcResponse(msg.Error.Code, msg.Error.Message); + return new RpcResponse(msg.Error.Code, msg.Error.Message, msg.Error.Details); } } @@ -106,7 +106,8 @@ public byte[] SerializeResponse(RpcResponse response) { } else { res.Error = new ErrorMsg() { Code = response.Error.Code, - Message = response.Error.Message + Message = response.Error.Message, + Details = response.Error.Details }; } @@ -281,5 +282,8 @@ class ErrorMsg [ProtoMember(2, IsRequired = true)] public string Message { get; set; } + + [ProtoMember(3)] + public string Details { get; set; } } } diff --git a/src/Holon/Remoting/Serializers/XmlRpcSerializer.cs b/src/Holon/Remoting/Serializers/XmlRpcSerializer.cs index 30c888e..f3eb5f7 100644 --- a/src/Holon/Remoting/Serializers/XmlRpcSerializer.cs +++ b/src/Holon/Remoting/Serializers/XmlRpcSerializer.cs @@ -141,12 +141,13 @@ public RpcResponse DeserializeResponse(byte[] body, Type dataType) { // find the code and message XElement errCode = err.Element(XName.Get("Code")); XElement errMsg = err.Element(XName.Get("Message")); + XElement errDetails = err.Element(XName.Get("Details")); if (errCode == null || errMsg == null) throw new InvalidDataException("Invalid XML document, error missing Code and Message elements"); // get data - return new RpcResponse(errCode.Value, errMsg.Value); + return new RpcResponse(errCode.Value, errMsg.Value, errDetails == null ? null : errDetails.Value); } } } @@ -223,6 +224,12 @@ public byte[] SerializeResponse(RpcResponse response) { writer.WriteEndElement(); writer.WriteStartElement("Message"); writer.WriteValue(response.Error.Message); + + if (response.Error.Details != null) + { + writer.WriteStartElement("Details"); + writer.WriteValue(response.Error.Details); + } } // finish document