Closed9

MojoliciousでCpanel::JSON::XS::Typeを組み合わせるワークログ

ピン留めされたアイテム
kfly8kfly8

結論

検証3の方向性にした。理由は、Mojoliciousに入門したばかりなのであんまりレールが外れることはしたくないから。

具体的な実装はこんな感じ。

https://github.com/kfly8/experimental-blog-app/blob/e6bbfde6e901bb792b74abc99f9851f15cd57c78/lib/Blog/Web/Controller/Entry.pm#L24-L27

https://github.com/kfly8/experimental-blog-app/blob/e6bbfde6e901bb792b74abc99f9851f15cd57c78/lib/Blog/Web/Plugin/JSON.pm#L14-L24

蛇足。上記の実装では、JSON::UnblessObjectで、blessされたオブジェクトを渡しても良いようにしている。specに基づいている分、TO_JSONよりも柔軟性があると思う。

kfly8kfly8

Mojoliciousでこんな具合に、Cpanel::JSON::XS::Typeを組み合わせたい

$c->render(
  json => { hello => 'WORLD!!' },
  spec => { hello => JSON_TYPE_STRING }
);
kfly8kfly8

検証1: Mojolicious::Renderer#renderを上書きする

まずは、renderを上書きしたRendererを用意する。テンプレートエンジンを利用なども上書きしてる

package MyApp::Renderer;
use Mojo::Base 'Mojolicious::Renderer';

use Cpanel::JSON::XS;

sub render {
    my ($self, $c) = @_;

    my $stash = $c->stash;

    my $JSON_SELIALIZER = Cpanel::JSON::XS->new->ascii(0);
    return $JSON_SELIALIZER->encode($stash->{json}, $stash->{spec}), 'json';
}

1;

そして、$appのrendererを上書きしてみた。が、「Renderer生成時にhelpers以外にも食わせるべきものがあるんじゃないの?」とか脆い感じ。

sub startup($self) {

    $self->app->renderer(Blog::Web::Renderer->new(
        helpers => $self->app->renderer->helpers, # helpers以外にも食わせるべきものあるだろう・・
    ));

    ...

所感

  • Rendererのインスタンスを作成際、何を食わせるべきか明確でない。脆そう。
kfly8kfly8

検証1に関して。もしrenderer_classだけを食わせる形であれば、心配は減りそう?

$self->app->renderer_class('Blog::Web::Renderer')
kfly8kfly8

検証2: before_renderで迂回する

Renderer#renderは、$stash->{data}であれば、何もすることなく素通りするよう。ドキュメントはこちら。コードはこちら↓

https://github.com/mojolicious/mojo/blob/934db081ead5cbafba261c683075bce5d1a0fb93/lib/Mojolicious/Renderer.pm#L97-L98

before_renderは、文字通りrender処理前に処理をフックしてくれる。ドキュメントはこちら。コードはこちら↓

https://github.com/mojolicious/mojo/blob/934db081ead5cbafba261c683075bce5d1a0fb93/lib/Mojolicious/Controller.pm#L132

この2つを組み合わせて迂回する。具体的には、before_dispatch内で、json encodeして、encodeした値をdataに詰め込む。

package MyApp::Plugin::JSON;
use v5.36;
use Mojo::Base 'Mojolicious::Plugin';

use Cpanel::JSON::XS;

my $JSON_SELIALIZER = Cpanel::JSON::XS->new->ascii(0);

sub register($self, $app, $config) {
    $app->hook(before_render => sub($c, $stash) {
        if (exists $stash->{json} && exists $stash->{spec}) {
            my $json = delete $stash->{json};
            my $spec = delete $stash->{spec};
            my $data = $JSON_SELIALIZER->encode($json, $spec);
            $stash->{data} = $data; 
        }
    });
}

所感

  • Mojoliciousの仕様に沿って動かしているので検証1よりは脆くはなさそう
  • だけど、before_renderでrenderの実処理をしているのは非直感的
kfly8kfly8

検証3: json_encodeするhelperで迂回する

検証2の亜種。json_encodeするhelperを用意して、コントローラーのアクション内で、encodeされた値をdataに詰め込む。

sub startup($self) {
    $self->app->helper(json_encode => sub($c, $json, $spec) {
        my $JSON_SELIALIZER = Cpanel::JSON::XS->new->ascii(0);
        my $data = $JSON_SELIALIZER->encode($json, $spec);
    });

    my $r = $self->routes;

    $r->get('/' => sub ($c) {
        $c->render(
            status => HTTP_OK,
            data => $c->json_encode({ foo => "HELLO WORLD" }, { foo => JSON_TYPE_STRING }),
        );
    });
}

所感

  • dataのMojoliciousの仕様に沿って動かしているので検証2同様脆さは少なそう
  • encodeをどこで行っているか直感的にはわかる。
  • 強いて言えば、json encodeの生々しさが、コントローラーアクションに露出してるのが気になる?
kfly8kfly8

検証4: render_jsonをhelperで生やして迂回する

検証3の生々しさを隠蔽するrender_json helperを用意した

sub startup($self) {
    $self->app->helper(render_json => sub($c, $status, $json, $spec) {
        my $stash = { status => $status, json => $json, spec => $spec };
        my $plugins = $c->app->plugins->emit_hook(before_render => $self, $stash);

        my $JSON_SELIALIZER = Cpanel::JSON::XS->new->ascii(0);
        my $output = $JSON_SELIALIZER->encode($json, $spec);
        my $format = 'json';

        $plugins->emit_hook(after_render => $c, \$output, $format);

        $c->app->renderer->respond($c, $output, $format, $status);
    });

    my $r = $self->routes;

    $r->get('/' => sub ($c) {
        $c->render_json(HTTP_OK, { foo => "HELLO WORLD" }, { foo => JSON_TYPE_STRING });
    });
}

所感

  • render_json($status, $json, $spec) のI/Fは慣れているので、気持ちは良い
  • が、Mojoliciousのrenderの内部処理が変化したとき、追従が面倒
このスクラップは2022/12/27にクローズされました