Renderer.pm 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. package Trog::Renderer;
  2. use strict;
  3. use warnings;
  4. no warnings 'experimental';
  5. use feature qw{signatures state};
  6. use Carp::Always;
  7. use Trog::Vars;
  8. use Trog::Log qw{:all};
  9. use Trog::Renderer::text;
  10. use Trog::Renderer::html;
  11. use Trog::Renderer::json;
  12. use Trog::Renderer::blob;
  13. use Trog::Renderer::css;
  14. use Trog::Renderer::email;
  15. =head1 Trog::Renderer
  16. Idea here is to have a renderer per known/supported content-type we need to output that is also theme-aware.
  17. We have an abstraction here, render() which you feed everything to.
  18. =cut;
  19. our %renderers = (
  20. text => \&Trog::Renderer::text::render,
  21. html => \&Trog::Renderer::html::render,
  22. json => \&Trog::Renderer::json::render,
  23. blob => \&Trog::Renderer::blob::render,
  24. xsl => \&Trog::Renderer::text::render,
  25. xml => \&Trog::Renderer::text::render,
  26. rss => \&Trog::Renderer::html::render,
  27. css => \&Trog::Renderer::css::render,
  28. email => \&Trog::Renderer::email::render,
  29. );
  30. =head2 Trog::Renderer->render(%options)
  31. Returns either the 3-arg arrayref suitable to emit at the end of a PSGI session or a response body if the component field of options is truthy.
  32. The idea is that components will be concatenated to other rendered templates until we finish having everything ready.
  33. =cut
  34. sub render ( $class, %options ) {
  35. local $@;
  36. my $renderer;
  37. return _yeet( $renderer, "Renderer requires a valid content type to be passed", %options ) unless $options{contenttype};
  38. my $rendertype = $Trog::Vars::byct{ $options{contenttype} };
  39. return _yeet( $renderer, "Renderer requires a known content type (used $options{contenttype}) to be passed", %options ) unless $rendertype;
  40. $renderer = $renderers{$rendertype};
  41. return _yeet( $renderer, "Renderer for $rendertype is not defined!", %options ) unless $renderer;
  42. return _yeet( $renderer, "Status code not provided", %options ) if !$options{code} && !$options{component};
  43. return _yeet( $renderer, "Template data not provided", %options ) unless $options{data};
  44. return _yeet( $renderer, "Template not provided", %options ) unless $options{template};
  45. #TODO future - save the components too and then compose them?
  46. my $data = $options{data};
  47. my $skip_save = $options{component} || $options{nocache} || !$data->{route} || $data->{has_query} || $data->{user} || ( $options{code} // 0 ) != 200 || Trog::Log::is_debug();
  48. my $ret;
  49. local $@;
  50. eval {
  51. $ret = $renderer->(%options);
  52. if (exists $data->{tpsgi} && !$skip_save) {
  53. $data->{tpsgi}->add_post_close_callback(sub {
  54. # We invalidate these in the individual routes themselves.
  55. $data->{tpsgi}->save_render( $data->{route}, $rendertype, $ret->[2][0] );
  56. });
  57. }
  58. 1;
  59. } or do {
  60. return _yeet( $renderer, $@, %options );
  61. };
  62. return $ret;
  63. }
  64. sub _yeet ( $renderer, $error, %options ) {
  65. WARN($error);
  66. # All-else fails error page
  67. my $ret;
  68. local $@;
  69. eval {
  70. $ret = $renderer->(
  71. code => 500,
  72. template => '500.tx',
  73. contenttype => 'text/html',
  74. data => { %options, content => "<h1>500 Internal Server Error</h1>$error" },
  75. );
  76. 1;
  77. } or do {
  78. my $msg = $error;
  79. $msg .= " and subsequently during render of error template, $@" if $renderer;
  80. #XXX bytes is probably not correct here
  81. INFO("$options{data}{method} 500 ".length($msg)." $options{data}{route}");
  82. FATAL($msg);
  83. };
  84. return $ret;
  85. }
  86. 1;