module Servicy class Transport # The Message class contains messages passed between client and server, as # well as utility methods for making messages to be sent. class Message include Comparable attr_reader :struct # Create a new message that will be sent over some transport. # @param [Hash] stuff A hash that will constitute the message. You should # likely never call this directy (i.e. Message.new), and instead use the # class methods {#registration}, {#query}, etc. def initialize(stuff) if stuff.is_a?(Hash) @struct = stuff else # TODO: should this be a formatter that I can swap out? @struct = JSON.parse(stuff || stuff.to_s) end end def method_missing(name, *args, &block) return struct[name] if struct.keys.include?(name) return struct[name.to_s] if struct.keys.include?(name.to_s) super end # The body of the message for cases where you send the message over the # wire, or through a database. # @return [String] A JSON representation of the message def body struct.to_json end # The response code. # @return [Integer] 200 if successful, 404 if not-found, 500 if error. def response_code !!struct[:success] ? 200 : struct[:response_code] end # Create a service registration message # @param [{Servicy::Service}] service The service that is being registered. # @return [{Message}] A {Message} that can be sent through a # {Servicy::Transport} def self.registration(service) new({ message: "registration", service: service.as_json }) end # Create a service discovery message. # @param [Hash] args A hash that defines the query. (see Servicy::Server#find) # @return [{Message}] A {Message} that can be sent through a # {Servicy::Transport} def self.query(args, only_one=false) raise ArgumentError.new("Invalid search query") unless query_valid?(args) new({ message: "query", query: args, only_one: only_one }) end # Create a service discover message where I only want one provider. # (see .query) def self.query_one(args) query(args, true) end # Create a services search response message # @param [Array<{Servicy::Service}>] services The services that were # found based on a {Servicy::Server} query # @return [{Message}] A {Message} that can be sent through a # {Servicy::Transport} def self.services(services) new({ success: true, message: "services", services: services.map { |s| s.as_json } }) end # Create an error response message # @param [String] error_message The error message to report # @param [Integer] response_code The response code for the error. # Defaults to 500 # @return [{Message}] A {Message} that can be sent through a # {Servicy::Transport} def self.error(error_message, response_code=500) new({ success: false, error: error_message }) end # A utility method for 404 Not-Found error message # (see .error) def self.not_found error "No results found", 404 end # A generic success message def self.success new({ success: true }) end # A message sent to a server to gather statistics about the server, also # used by the server to send the response back to the client. # @param [Hash,nil] stats The stats to report, if any. def self.statistics(stats=nil) new({ message: "stats", stats: stats }) end # Make a remote API call. # @param [String] method The method to call on the remote implementor. # @param [Hash{String => Object}] args The arguments to pass to the method. # These are expected to be pre-formatted by an appropriate Formatter def self.api(method, args) new({ message: 'api', method_name: method, args: args }) end def self.api_response(result) new({ message: 'api_response', result: result }) end # Allow comparing of messages. This doesn't make for useful sorting, at # the moment, but does make it easy to know if two things are saying the # same thing. def <=>(b) self.struct == b.struct ? 0 : -1 end private def self.query_valid?(args) return false if !args[:name] && !args[:api] if args[:min_version] && args[:max_version] min = Servicy::Service.version_as_number(args[:min_version]) max = Servicy::Service.version_as_number(args[:max_version]) return false if min > max end return false if args[:version] && (args[:min_version] || args[:max_version]) true end end end end