diff --git a/README.md b/README.md index 54d2d60..6d42ec9 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,26 @@ end Each handler method should return the appropriate Protobuf, or a `Twirp::Error`. -TODO: Give more examples of both +#### Packages and Namespacing + +Handlers can live in directories that reflect the service's package. For example, `haberdasher.proto` defines: + +```protobuf +package twirp.example.haberdasher; +``` + +You can use the full path, or because many projects have only one namespace, we also let you skip the namespace for simplicity: + +We look for the handler in either location: + +`app/handlers/twirp/example/haberdasher/haberdasher_service_handler.rb` defines `Twirp::Example::Haberdasher::HaberdasherServiceHandler` + +or + +`app/handlers/haberdasher_service_handler.rb` defines `HaberdasherServiceHandler` + + +TODO: Give more examples of handlers ### Familiar Callbacks diff --git a/lib/twirp/rails/dispatcher.rb b/lib/twirp/rails/dispatcher.rb index 514114e..2576af3 100644 --- a/lib/twirp/rails/dispatcher.rb +++ b/lib/twirp/rails/dispatcher.rb @@ -4,7 +4,13 @@ module Twirp module Rails class Dispatcher def initialize(service_class) - @service_handler = "#{service_class.service_name}Handler".constantize + # Check for a handler in the service's namespace, or in the root namespace + # e.g. Twirp::Example::Cobbler::CobblerHandler or ::CobblerHandler + @service_handler = if Object.const_defined?("#{service_class.module_parent}::#{service_class.service_name}Handler") + "#{service_class.module_parent}::#{service_class.service_name}Handler".constantize + else + "#{service_class.service_name}Handler".constantize + end end def respond_to_missing?(method, *) diff --git a/spec/rails_app/app/handlers/twirp/example/cobbler/cobbler_handler.rb b/spec/rails_app/app/handlers/twirp/example/cobbler/cobbler_handler.rb new file mode 100644 index 0000000..69581bf --- /dev/null +++ b/spec/rails_app/app/handlers/twirp/example/cobbler/cobbler_handler.rb @@ -0,0 +1,21 @@ +module Twirp + module Example + module Cobbler + class CobblerHandler < Twirp::Rails::Handler + def make_shoe + # We can return a Twirp::Error when appropriate + if request.inches < 12 + return Twirp::Error.invalid_argument("is too small", argument: "inches") + end + + # Build the reponse + Twirp::Example::Cobbler::Shoe.new( + inches: request.inches, + name: "Pork Pie", + color: "Tan" + ) + end + end + end + end +end diff --git a/spec/rails_app/lib/twirp/example/cobbler/cobbler_pb.rb b/spec/rails_app/lib/twirp/example/cobbler/cobbler_pb.rb new file mode 100644 index 0000000..9e0b3d7 --- /dev/null +++ b/spec/rails_app/lib/twirp/example/cobbler/cobbler_pb.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: twirp/example/cobbler/cobbler.proto + +require 'google/protobuf' + + +descriptor_data = "\n#twirp/example/cobbler/cobbler.proto\x12\x15twirp.example.cobbler\"\x16\n\x04Size\x12\x0e\n\x06inches\x18\x01 \x01(\x05\"3\n\x04Shoe\x12\x0e\n\x06inches\x18\x01 \x01(\x05\x12\r\n\x05\x63olor\x18\x02 \x01(\t\x12\x0c\n\x04name\x18\x03 \x01(\t2O\n\x07\x43obbler\x12\x44\n\x08MakeShoe\x12\x1b.twirp.example.cobbler.Size\x1a\x1b.twirp.example.cobbler.Shoeb\x06proto3" + +pool = Google::Protobuf::DescriptorPool.generated_pool +pool.add_serialized_file(descriptor_data) + +module Twirp + module Example + module Cobbler + Size = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("twirp.example.cobbler.Size").msgclass + Shoe = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("twirp.example.cobbler.Shoe").msgclass + end + end +end diff --git a/spec/rails_app/lib/twirp/example/cobbler/cobbler_twirp.rb b/spec/rails_app/lib/twirp/example/cobbler/cobbler_twirp.rb new file mode 100644 index 0000000..c05e46c --- /dev/null +++ b/spec/rails_app/lib/twirp/example/cobbler/cobbler_twirp.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# Generated by the protoc-gen-twirp_ruby gem v1.1.1. DO NOT EDIT! +# source: twirp/example/cobbler/cobbler.proto + +require "twirp" +require_relative "cobbler_pb" + +module Twirp + module Example + module Cobbler + class CobblerService < ::Twirp::Service + package "twirp.example.cobbler" + service "Cobbler" + rpc :MakeShoe, Size, Shoe, ruby_method: :make_shoe + end + + class CobblerClient < ::Twirp::Client + client_for CobblerService + end + end + end +end diff --git a/spec/rails_app/proto/twirp/example/cobbler/cobbler.proto b/spec/rails_app/proto/twirp/example/cobbler/cobbler.proto new file mode 100644 index 0000000..36b2048 --- /dev/null +++ b/spec/rails_app/proto/twirp/example/cobbler/cobbler.proto @@ -0,0 +1,23 @@ +// A close copy of haberdasher.proto +// We're putting this in /proto/twirp/example/ so that we can test namespace handling +syntax = "proto3"; + +package twirp.example.cobbler; + +// Cobbler service makes shoes for clients. +service Cobbler { + // MakeShoe produces a shoe of mysterious, randomly-selected color! + rpc MakeShoe(Size) returns (Shoe); +} + +// Size of a Shoe, in inches. +message Size { + int32 inches = 1; // must be > 0 +} + +// A Shoe is a piece of footwear made by a Cobbler. +message Shoe { + int32 inches = 1; + string color = 2; // anything but "invisible" + string name = 3; // i.e. "oxford" +}