Chain basics
Chains run multiple Operations ("steps") in order, returning the success value of the last step.
 When any step returns a failure, the chain is stopped and that failure is returned.
Operations used as steps need to return result objects (implementing Teckel::Result).
Chains always return a result object including the name of the step they origin from.
 This is especially useful to switch error handling for failure results.
Example
Defining a simple Chain with three steps.
require "teckel/chain"
class CreateUser
  include ::Teckel::Operation
  result!
  input  Types::Hash.schema(name: Types::String, age: Types::Coercible::Integer.optional)
  output Types.Instance(User)
  error  Types::Hash.schema(message: Types::String, errors: Types::Array.of(Types::Hash))
  def call(input)
    user = User.new(name: input[:name], age: input[:age])
    if user.save
      success!(user)
    else
      fail!(message: "Could not save User", errors: user.errors)
    end
  end
end
class LogUser
  include ::Teckel::Operation
  result!
  input Types.Instance(User)
  error none
  output input
  def call(usr)
    Logger.new(File::NULL).info("User #{usr.name} created")
    success!(usr) # we need to return the correct output type
  end
end
class AddFriend
  include ::Teckel::Operation
  result!
  settings Struct.new(:fail_befriend)
  input Types.Instance(User)
  output Types::Hash.schema(user: Types.Instance(User), friend: Types.Instance(User))
  error  Types::Hash.schema(message: Types::String)
  def call(user)
    if settings&.fail_befriend == :fail
      fail!(message: "Did not find a friend.")
    else
      success!(user: user, friend: User.new(name: "A friend", age: 42))
    end
  end
end
class MyChain
  include Teckel::Chain
  step :create, CreateUser
  step :log, LogUser
  step :befriend, AddFriend
  finalize!
end
result = MyChain.call(name: "Bob", age: 23)
#=> #<Teckel::Chain::Result:<...>>
result.success[:user]
#=> #<User:<...> @age=23, @name="Bob">
result.success[:friend]
#=> #<User:<...> @age=42, @name="A friend">
failure_result = MyChain.with(befriend: :fail).call(name: "Bob", age: 23)
failure_result
#=> #<Teckel::Chain::Result:<...>>
# additional step information
failure_result.step                   
#=> :befriend
# behaves just like a normal +Result+
failure_result.failure?
#=> true
failure_result.failure
#=> {message: "Did not find a friend."}
Pattern matching
Hash style:
result = case MyChain.call(name: "Bob", age: 23)
in { success: false, step: :befriend, value: value }
  ["Failed", value]
in { success: true, value: value }
  ["Success result", value]
end
result
#=> ["Success result",
  {friend: #<User:<...> @age=42, @name="A friend">,
   user: #<User:<...> @age=23, @name="Bob">}]
Array style:
result = case MyChain.with(befriend: :fail).call(name: "Bob", age: 23)
in [false, :befriend, value]
  ["Failed", value]
in [true, value]
  ["Success result", value]
end
result
#=> ["Failed", {message: "Did not find a friend."}]