Class: Steppe::Responder

Inherits:
Plumb::Pipeline
  • Object
show all
Defined in:
lib/steppe/responder.rb

Overview

Handles response formatting for specific HTTP status codes and content types.

A Responder is a pipeline that processes a result and formats it into a Rack response. Each responder is registered for a specific range of status codes and content type, and includes a serializer to format the response body.

Examples:

Basic JSON responder

Responder.new(statuses: 200, accepts: :json) do |r|
  r.serialize do
    attribute :message, String
    def message = "Success"
  end
end

HTML responder with Papercraft

Responder.new(statuses: 200, accepts: :html) do |r|
  r.serialize do |result|
    html5 do
      h1 result.params[:title]
    end
  end
end

Responder for a range of status codes

Responder.new(statuses: 200..299, accepts: :json) do |r|
  r.description = "Successful responses"
  r.serialize SuccessSerializer
end

See Also:

Constant Summary collapse

DEFAULT_STATUSES =
(200..200).freeze
DEFAULT_SERIALIZER =
Types::Static[{}.freeze].freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(statuses: DEFAULT_STATUSES, accepts: ContentTypes::JSON, content_type: nil, serializer: nil) {|responder| ... } ⇒ Responder

Creates a new Responder instance.

Examples:

Basic responder

Responder.new(statuses: 200, accepts: :json)

With custom Content-Type header

Responder.new(statuses: 200, accepts: :json, content_type: 'application/vnd.api+json')

With inline serializer

Responder.new(statuses: 200, accepts: :json) do |r|
  r.serialize { attribute :data, Object }
end

Parameters:

  • statuses (Integer, Range) (defaults to: DEFAULT_STATUSES)

    HTTP status code(s) to handle (default: 200)

  • accepts (String, Symbol) (defaults to: ContentTypes::JSON)

    Content type to match from Accept header (default: :json)

  • content_type (String, Symbol, nil) (defaults to: nil)

    Specific Content-Type header for response (defaults to accepts value)

  • serializer (Class, Proc, nil) (defaults to: nil)

    Serializer to format response body

Yields:

  • (responder)

    Optional configuration block

Yield Parameters:

  • responder (Responder)

    self for configuration



89
90
91
92
93
94
95
96
97
98
# File 'lib/steppe/responder.rb', line 89

def initialize(statuses: DEFAULT_STATUSES, accepts: ContentTypes::JSON, content_type: nil, serializer: nil, &)
  @statuses = statuses.is_a?(Range) ? statuses : (statuses..statuses)
  @description = nil
  @accepts = ContentType.parse(accepts)
  @content_type = content_type ? ContentType.parse(content_type) : @accepts
  @content_type_subtype = @content_type.subtype.to_sym
  super(freeze_after: false, &)
  serialize(serializer) if serializer
  freeze
end

Instance Attribute Details

#acceptsContentType (readonly)

Returns The content type pattern this responder matches (from Accept header).

Returns:

  • (ContentType)

    The content type pattern this responder matches (from Accept header)



59
60
61
# File 'lib/steppe/responder.rb', line 59

def accepts
  @accepts
end

#content_typeContentType (readonly)

Returns The actual Content-Type header value set in the response.

Returns:

  • (ContentType)

    The actual Content-Type header value set in the response



62
63
64
# File 'lib/steppe/responder.rb', line 62

def content_type
  @content_type
end

#descriptionString?

Returns Optional description of this responder (used in documentation).

Returns:

  • (String, nil)

    Optional description of this responder (used in documentation)



68
69
70
# File 'lib/steppe/responder.rb', line 68

def description
  @description
end

#serializerSerializer, Proc (readonly)

Returns The serializer used to format the response body.

Returns:

  • (Serializer, Proc)

    The serializer used to format the response body



65
66
67
# File 'lib/steppe/responder.rb', line 65

def serializer
  @serializer
end

#statusesRange (readonly)

Returns The range of HTTP status codes this responder handles.

Returns:

  • (Range)

    The range of HTTP status codes this responder handles



56
57
58
# File 'lib/steppe/responder.rb', line 56

def statuses
  @statuses
end

Class Method Details

.inline_serializersObject



43
44
45
# File 'lib/steppe/responder.rb', line 43

def self.inline_serializers
  @inline_serializers ||= {}
end

Instance Method Details

#==(other) ⇒ Boolean

Compares two responders for equality.

Two responders are equal if they handle the same status codes and content type.

Parameters:

  • other (Object)

    Object to compare

Returns:

  • (Boolean)

    True if responders are equal



143
144
145
# File 'lib/steppe/responder.rb', line 143

def ==(other)
  other.is_a?(Responder) && other.statuses == statuses && other.content_type == content_type
end

#call(conn) ⇒ Result::Halt

Processes the result through the serializer pipeline and creates a Rack response.

Parameters:

  • conn (Result)

    The result object to process

Returns:



157
158
159
160
161
162
163
# File 'lib/steppe/responder.rb', line 157

def call(conn)
  conn = super(conn)
  conn.respond_with(conn.response.status) do |response|
    response[Rack::CONTENT_TYPE] = content_type.to_s
    Rack::Response.new(conn.value, response.status, response.headers)
  end
end

#inspectString

Returns Human-readable representation of the responder.

Returns:

  • (String)

    Human-readable representation of the responder



148
# File 'lib/steppe/responder.rb', line 148

def inspect = "<#{self.class}##{object_id} #{description} statuses:#{statuses} content_type:#{content_type}>"

#node_nameSymbol

Returns Node name for pipeline inspection.

Returns:

  • (Symbol)

    Node name for pipeline inspection



151
# File 'lib/steppe/responder.rb', line 151

def node_name = :responder

#serialize(serializer = nil) { ... } ⇒ void

This method returns an undefined value.

Registers a serializer for this responder.

The serializer is selected based on the content type’s subtype (:json or :html). For JSON responses, pass a Serializer class or a block that defines attributes. For HTML responses, pass a Papercraft template or block.

Examples:

JSON serializer with block

responder.serialize do
  attribute :users, [UserType]
  def users = result.params[:users]
end

JSON serializer with class

responder.serialize(UserListSerializer)

HTML serializer with Papercraft

responder.serialize do |result|
  html5 { h1 result.params[:title] }
end

Parameters:

  • serializer (Class, Proc, nil) (defaults to: nil)

    Serializer class or template

Yields:

  • Block to define inline serializer

Raises:

  • (ArgumentError)

    If responder already has a serializer



126
127
128
129
130
131
132
133
134
135
# File 'lib/steppe/responder.rb', line 126

def serialize(serializer = nil, &block)
  raise ArgumentError, "this responder already has a serializer" if @serializer

  builder = self.class.inline_serializers.fetch(@content_type_subtype)
  @serializer = builder.call(serializer || block)
  step do |conn|
    output = @serializer.render(conn)
    conn.copy(value: output)
  end
end