diff --git a/lib/hub_kernel/exposes.rb b/lib/hub_kernel/exposes.rb index f61e62e..151c57a 100644 --- a/lib/hub_kernel/exposes.rb +++ b/lib/hub_kernel/exposes.rb @@ -12,16 +12,16 @@ class NotAllowed < StandardError; end class Refused < StandardError; end module Exposes - Exposed = Data.define(:name, :takes, :writes, :earlier_answers) do + Exposed = Data.define(:name, :takes, :writes, :earlier_answers, :answer_shape) do def earlier_answer(major) = earlier_answers.find { |earlier| earlier.major == major } end - EarlierAnswer = Data.define(:major, :translation) do + EarlierAnswer = Data.define(:major, :translation, :answer_shape) do def translate(answer) = translation.call(answer) end - def exposes(name, takes:, writes:, earlier_answers: {}) + def exposes(name, takes:, writes:, earlier_answers: {}, answer_shape: nil) Interface::ExposingHubs.add(self) - exposed_methods[name.to_s] = Exposed.new(name: name, takes: takes, writes: writes, earlier_answers: earlier_answers.map { |major, translation| EarlierAnswer.new(major: major, translation: translation) }) + exposed_methods[name.to_s] = Exposed.new(name: name, takes: takes, writes: writes, earlier_answers: earlier_answers.map { |major, declared| earlier_answer_from(major, declared) }, answer_shape: answer_shape) end def exposed(name) = exposed_methods[name.to_s] @@ -59,6 +59,12 @@ def exposed_methods = @exposed_methods ||= {} def exposing_hub = name.demodulize + def earlier_answer_from(major, declared) + return EarlierAnswer.new(major: major, translation: declared, answer_shape: nil) if declared.respond_to?(:call) + + EarlierAnswer.new(major: major, translation: declared[:translation], answer_shape: declared[:answer_shape]) + end + def refuse_without_caller(person, account) raise MissingArgumentError, "A call by name needs a person" if person.nil? raise MissingArgumentError, "A call by name needs an account" if account.nil? diff --git a/test/hub_kernel/exposes_test.rb b/test/hub_kernel/exposes_test.rb index 4337cce..ef761d6 100644 --- a/test/hub_kernel/exposes_test.rb +++ b/test/hub_kernel/exposes_test.rb @@ -43,6 +43,18 @@ module Baker exposes :price_of, takes: %i[item], writes: false, earlier_answers: { 1 => ->(answer) { answer[:price] }, 2 => ->(answer) { { price: answer[:cents] } } } end + module Butcher + extend HubKernel::Exposes + + exposes :weigh_cut, takes: %i[cut], writes: false, answer_shape: { "type" => "integer" } + end + + module Florist + extend HubKernel::Exposes + + exposes :price_of, takes: %i[item], writes: false, earlier_answers: { 1 => { translation: ->(answer) { answer.to_s }, answer_shape: { "type" => "string" } } } + end + test "an exposed method is found by its name with the values it takes" do assert_equal %i[supplier_id bought_on lines], Supplies.exposed("record_purchase").takes end @@ -71,6 +83,26 @@ module Baker assert_equal({ price: 300 }, Baker.exposed("price_of").earlier_answer(2).translate({ cents: 300 })) end + test "a method exposed without an answer shape reads as having none" do + assert_nil Supplies.exposed("record_purchase").answer_shape + end + + test "an earlier answer given only a translation reads as having no answer shape" do + assert_nil Grocer.exposed("price_of").earlier_answer(1).answer_shape + end + + test "a method is exposed with the JSON Schema of its answer as its answer shape" do + assert_equal({ "type" => "integer" }, Butcher.exposed("weigh_cut").answer_shape) + end + + test "an earlier answer is given the JSON Schema of the answer at its major beside its translation" do + assert_equal({ "type" => "string" }, Florist.exposed("price_of").earlier_answer(1).answer_shape) + end + + test "an earlier answer given a translation beside its answer shape translates the answer of the major after it" do + assert_equal "300", Florist.exposed("price_of").earlier_answer(1).translate(300) + end + test "a hub that declares an exposed method is recorded as a hub that exposes methods" do assert_includes HubKernel::Interface::ExposingHubs.list, Supplies end