# Custom \`body\_parser\` from Hanami 2.1 guides

**URL:** <https://discourse.hanakai.org/t/custom-body-parser-from-hanami-2-1-guides/929>\
**Category:** questions\
**Created:** [March 23, 2024, 4:58pm UTC](https://discourse.hanakai.org/t/custom-body-parser-from-hanami-2-1-guides/929 "2024-03-23T16:58:11Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![komidore64](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.hanakai.org/komidore64/32/700_2.png) [@komidore64](https://discourse.hanakai.org/u/komidore64)\
**Post date:** [March 23, 2024, 4:58pm UTC](https://discourse.hanakai.org/t/custom-body-parser-from-hanami-2-1-guides/929/1 "2024-03-23T16:58:11Z")

</div>

Hi Hanami friends!

I have a Hanami app I’ve been working on, but I’m having trouble setting up a custom `body_parser` as described in [Hanami Guides](https://guides.hanamirb.org/v2.1/actions/parameters/#body-parsers).

I have defined a trivial example parser in `lib/simple_json_parser.rb` of my app, similar to the guide’s example.

```ruby
# in lib/simple_json_parser.rb

require 'json'

class SimpleJSONParser
  def mime_types
    ['application/json']
  end

  def parse(body)
    JSON.parse(body)
  end
end

```

Next, I hooked up the parser into my app via `config/app.rb`, as described in the example.

```ruby
require 'hanami'
require 'simple_json_parser'

module FancyApp
  class App < Hanami::App
    config.middleware.use :body_parser, SimpleJSONParser.new
  end
end

```

Unfortunately this doesn’t seem to work. I get the following stacktrace upon receiving a request.

```auto
NoMethodError: undefined method `empty?' for an instance of SimpleJSONParser
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-router-2.1.0/lib/hanami/middleware/body_parser/class_interface.rb:56:in `build_parsers'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-router-2.1.0/lib/hanami/middleware/body_parser/class_interface.rb:32:in `new'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/builder.rb:158:in `block in use'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/builder.rb:235:in `block in to_app'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/builder.rb:235:in `each'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/builder.rb:235:in `inject'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/builder.rb:235:in `to_app'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-router-2.1.0/lib/hanami/middleware/app.rb:30:in `block in initialize'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-router-2.1.0/lib/hanami/middleware/app.rb:21:in `each'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-router-2.1.0/lib/hanami/middleware/app.rb:21:in `initialize'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-2.1.0/lib/hanami/slice/routing/middleware/stack.rb:128:in `new'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-2.1.0/lib/hanami/slice/routing/middleware/stack.rb:128:in `to_rack_app'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-2.1.0/lib/hanami/slice/router.rb:82:in `to_rack_app'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-2.1.0/lib/hanami/slice.rb:741:in `rack_app'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-2.1.0/lib/hanami/slice.rb:758:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/static.rb:161:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/tempfile_reaper.rb:15:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/lint.rb:50:in `_call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/lint.rb:38:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/show_exceptions.rb:23:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/common_logger.rb:38:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/content_length.rb:17:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/configuration.rb:272:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/request.rb:100:in `block in handle_request'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/thread_pool.rb:378:in `with_force_shutdown'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/request.rb:99:in `handle_request'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/server.rb:464:in `process_client'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/server.rb:245:in `block in run'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/thread_pool.rb:155:in `block in spawn_thread'
::1 - - [23/Mar/2024:12:39:01 -0400] "POST /api/discord-interactions HTTP/1.1" 500 3781 0.0266

```

I poked around Hanami’s `router` repo finding some of the files referenced in the stacktrack ([[1]](https://github.com/hanami/router/blob/main/lib/hanami/middleware/body_parser/class_interface.r) and [[2]](https://github.com/hanami/router/blob/main/lib/hanami/middleware/app.rb)), but nothing directly leaps out at me.

I also had a look at `router`’s [README](https://github.com/hanami/router?tab=readme-ov-file#custom-parsers) which shows similar instructions (the difference is that the class inherits from `Hanami::Middleware::BodyParser::Parser`) for custom parsers, but no joy from that either.

I would deeply appreciate any direction in fixing this problem. Have I stumbled across a bug?

Thank you in advance for any help! Have a great weekend!

---

<div class="post-metadata">

**Author:** ![krzykamil](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.hanakai.org/krzykamil/32/765_2.png) [@krzykamil](https://discourse.hanakai.org/u/krzykamil)\
**Post date:** [March 25, 2024, 9:55am UTC](https://discourse.hanakai.org/t/custom-body-parser-from-hanami-2-1-guides/929/2 "2024-03-25T09:55:02Z")

</div>

> [@komidore64](#):
>
> ` config.middleware.use :body_parser, SimpleJSONParser.new`

I believe you should not pass an instance of the parser, but the class itself.

`config.middleware.use :body_parser, SimpleJSONParser`

A hint is that the files you linked, do actually build the parser, initialize it and all:

> <https://github.com/hanami/router/blob/main/lib/hanami/middleware/body_parser/class_interface.rb#L37>

---

<div class="post-metadata">

**Author:** ![komidore64](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.hanakai.org/komidore64/32/700_2.png) [@komidore64](https://discourse.hanakai.org/u/komidore64)\
**Post date:** [March 25, 2024, 10:39am UTC](https://discourse.hanakai.org/t/custom-body-parser-from-hanami-2-1-guides/929/3 "2024-03-25T10:39:27Z")

</div>

> [@krzykamil](#):
>
> I believe you should not pass an instance of the parser, but the class itself.
> 
> `config.middleware.use :body_parser, SimpleJSONParser`

I tried this as well, but I get a similar stacktrace.

```ruby
module FancyApp
  class App < Hanami::App
    config.middleware.use :body_parser, SimpleJSONParser
  end
end

```

results:

```auto
NoMethodError: undefined method `empty?' for class SimpleJSONParser
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-router-2.1.0/lib/hanami/middleware/body_parser/class_interface.rb:56:in `build_parsers'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-router-2.1.0/lib/hanami/middleware/body_parser/class_interface.rb:32:in `new'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/builder.rb:158:in `block in use'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/builder.rb:235:in `block in to_app'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/builder.rb:235:in `each'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/builder.rb:235:in `inject'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/builder.rb:235:in `to_app'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-router-2.1.0/lib/hanami/middleware/app.rb:30:in `block in initialize'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-router-2.1.0/lib/hanami/middleware/app.rb:21:in `each'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-router-2.1.0/lib/hanami/middleware/app.rb:21:in `initialize'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-2.1.0/lib/hanami/slice/routing/middleware/stack.rb:128:in `new'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-2.1.0/lib/hanami/slice/routing/middleware/stack.rb:128:in `to_rack_app'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-2.1.0/lib/hanami/slice/router.rb:82:in `to_rack_app'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-2.1.0/lib/hanami/slice.rb:741:in `rack_app'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/hanami-2.1.0/lib/hanami/slice.rb:758:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/static.rb:161:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/tempfile_reaper.rb:15:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/lint.rb:50:in `_call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/lint.rb:38:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/show_exceptions.rb:23:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/common_logger.rb:38:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/rack-2.2.8.1/lib/rack/content_length.rb:17:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/configuration.rb:272:in `call'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/request.rb:100:in `block in handle_request'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/thread_pool.rb:378:in `with_force_shutdown'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/request.rb:99:in `handle_request'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/server.rb:464:in `process_client'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/server.rb:245:in `block in run'
        /home/user/.asdf/installs/ruby/3.3.0/lib/ruby/gems/3.3.0/gems/puma-6.4.2/lib/puma/thread_pool.rb:155:in `block in spawn_thread'
::1 - - [25/Mar/2024:06:32:25 -0400] "POST /api/discord-interactions HTTP/1.1" 500 3772 0.0271

```

---

<div class="post-metadata">

**Author:** ![krzykamil](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.hanakai.org/krzykamil/32/765_2.png) [@krzykamil](https://discourse.hanakai.org/u/krzykamil)\
**Post date:** [March 25, 2024, 3:27pm UTC](https://discourse.hanakai.org/t/custom-body-parser-from-hanami-2-1-guides/929/4 "2024-03-25T15:27:32Z")

</div>

Interesting. Looking deeper into the source code, it seems that it does expect something does react to `.empty?`  
If you wrap the class in an array, it does not crash anymore.  
I’ve tried the example with xml [GitHub - hanami/router: Ruby/Rack HTTP router](https://github.com/hanami/router?tab=readme-ov-file#custom-parsers) from here, and it produces the same error.  
I think simply passing the class should be the expected interface for this. So this does indeed look like a bug to me.

@timriley sorry for tagging you again, twice in one day 🙂 if this is a bug, would you be interested in some help in handling this?

---

<div class="post-metadata">

**Author:** ![krzykamil](https://yyz2.discourse-cdn.com/flex030/user_avatar/discourse.hanakai.org/krzykamil/32/765_2.png) [@krzykamil](https://discourse.hanakai.org/u/krzykamil)\
**Post date:** [April 22, 2024, 11:27am UTC](https://discourse.hanakai.org/t/custom-body-parser-from-hanami-2-1-guides/929/5 "2024-04-22T11:27:40Z")

</div>

I went back to this since it is bugging me. I do not think this is exactly a bug, but rather a problem of incomplete/erroneous documentation.

Few things:

Both examples ([GitHub - hanami/router: Ruby/Rack HTTP router](https://github.com/hanami/router?tab=readme-ov-file#custom-parsers) and [V2.1: Parameters | Hanami Guides](https://guides.hanamirb.org/v2.1/actions/parameters/#body-parsers)) are broken when you try them.

I forked hanami project and added those two specs:

```ruby
#spec/integration/action/format_config_spec.rb
 it "handles a custom body_parser" do
    write "lib/simple_json_parser.rb" , <<~RUBY
      require "json"
      
      class SimpleJSONParser
        def mime_types
          ['application/json']
        end
      
        def parse(body)
          JSON.parse(body).reverse
        end
      end
    RUBY

    write "config/app.rb", <<~RUBY
      require "hanami"
      require "simple_json_parser"

      module TestApp
        class App < Hanami::App
          config.logger.stream = StringIO.new

          config.middleware.use :body_parser, SimpleJSONParser
        end
      end
    RUBY

    write "config/routes.rb", <<~RUBY
      module TestApp
        class Routes < Hanami::Routes
          post "/users", to: "users.create"
        end
      end
    RUBY

    write "app/action.rb", <<~RUBY
      # auto_register: false

      module TestApp
        class Action < Hanami::Action
        end
      end
    RUBY

    write "app/actions/users/create.rb", <<~RUBY
      module TestApp
        module Actions
          module Users
            class Create < TestApp::Action
              def handle(req, res)
                res.body = req.params[:users].join("-")
              end
            end
          end
        end
      end
    RUBY

    require "hanami/boot"

    post(
      "/users",
      JSON.generate("users" => %w[jane john jade joe]),
      "CONTENT_TYPE" => "application/json"
    )

    expect(last_response).to be_successful
    expect(last_response.body).to eql(["joe", "jade", "john", "jane"])
  end

  it "handles a custom body_parser that is initialized" do
    write "lib/simple_json_parser.rb" , <<~RUBY
      require "json"
      
      class SimpleJSONParser
        def mime_types
          ['application/json']
        end
      
        def parse(body)
          JSON.parse(body).reverse
        end
      end
    RUBY

    write "config/app.rb", <<~RUBY
      require "hanami"
      require "simple_json_parser"

      module TestApp
        class App < Hanami::App
          config.logger.stream = StringIO.new

          config.middleware.use :body_parser, SimpleJSONParser.new
        end
      end
    RUBY

    write "config/routes.rb", <<~RUBY
      module TestApp
        class Routes < Hanami::Routes
          post "/users", to: "users.create"
        end
      end
    RUBY

    write "app/action.rb", <<~RUBY
      # auto_register: false

      module TestApp
        class Action < Hanami::Action
        end
      end
    RUBY

    write "app/actions/users/create.rb", <<~RUBY
      module TestApp
        module Actions
          module Users
            class Create < TestApp::Action
              def handle(req, res)
                res.body = req.params[:users].join("-")
              end
            end
          end
        end
      end
    RUBY

    require "hanami/boot"

    post(
      "/users",
      JSON.generate("users" => %w[jane john jade joe]),
      "CONTENT_TYPE" => "application/json"
    )

    expect(last_response).to be_successful
    expect(last_response.body).to eql(["joe", "jade", "john", "jane")
  end

```

Both fail with

```ruby

     NoMethodError:
       undefined method `empty?' for #<SimpleJSONParser:0x0000763855b3ef80>
     # ./lib/hanami/slice/routing/middleware/stack.rb:128:in `new'
     # ./lib/hanami/slice/routing/middleware/stack.rb:128:in `to_rack_app'

```

Now this is already a problem, since those examples are exactly like the one that are presented in the documentation, so this is confusing.

But the initial solution I mentioned above, of putting the class/instance in an array does not always pass the test.

What does makes the tests pass is passing:  
`[json: SimpleJSONParser]`

(for the json example)

Inspecting the code I can’t really understand why the `.empty?` `lib/hanami/middleware/body_parser/class_interface.rb:56` always expects an array. Two examples I listed, clearly show that no array is required to handle custom parsers, for custom formats or not (one example shows XML, other one shows custom format FOO).

I think better explanation of the DSL required in this feature would clear this stuff out, and probably an update to the documentation?

@timriley I know you probably got a lot going on, but would greatly appreciate your input here.

Also moved to [Custom body parser issue · Issue #1377 · hanami/hanami · GitHub](https://github.com/hanami/hanami/issues/1377) some time ago
