Class: ActsAsCalculator::RenderLiquid

Inherits:
Object
  • Object
show all
Defined in:
lib/acts_as_calculator/render_liquid.rb

Overview

The security boundary. Template bodies are authored by non-developers (eventually through Phase 5's API), so this deliberately renders inside a private Liquid::Environment rather than Liquid's process-global default, which a host app is free to have repointed at a real file system or loaded its own tags into.

Defined Under Namespace

Classes: BoundedFor, IterationBudget

Constant Summary collapse

FILE_SYSTEM_TAGS =

include/render are the only tags that read from outside the assigns. A BlankFileSystem already refuses them; dropping the tags means the template fails at parse time with a syntax error instead of at render time with a file system error.

%w[include render].freeze
ITERATING_TAGS =

tablerow iterates a collection the same way for does, but through its own render_to_output_buffer rather than a subclassable collection_segment. It is a storefront grid-layout tag with no use in rendering a calculation, so it is dropped rather than given a second, version-fragile bounded subclass.

%w[tablerow].freeze
RESOURCE_LIMITS =

Bounds output size and assign churn. These do NOT bound iteration on their own — render_score counts rendered nodes, so an empty loop body scores zero per pass. That gap is closed by BoundedFor/MAX_ITERATIONS below, not here.

{
  render_length_limit: 2_000_000,
  render_score_limit: 200_000,
  assign_score_limit: 2_000_000
}.freeze
MAX_ITERATIONS =

Cumulative across every loop in one render, so nested loops multiply into the same budget instead of each staying under a per-loop cap. Generous for presenting a calculation; a template needing more than this is not a template.

10_000
SANDBOX_TAGS =
Liquid::Tags::STANDARD_TAGS
.reject { |name, _| (FILE_SYSTEM_TAGS + ITERATING_TAGS).include?(name) }
.merge("for" => BoundedFor)
.freeze
SANDBOX =
Liquid::Environment.build(
  error_mode: :strict,
  file_system: Liquid::BlankFileSystem.new,
  tags: SANDBOX_TAGS
) do |environment|
  environment.default_resource_limits = RESOURCE_LIMITS
  environment.register_filter(LiquidFilters)
end

Class Method Summary collapse

Class Method Details

.call(source:, assigns: {}) ⇒ Object



103
104
105
106
107
108
109
110
111
# File 'lib/acts_as_calculator/render_liquid.rb', line 103

def self.call(source:, assigns: {})
  Liquid::Template
    .parse(source.to_s, environment: SANDBOX, line_numbers: true)
    .render!(assigns,
             registers: { iteration_budget: IterationBudget.new(MAX_ITERATIONS) },
             strict_filters: true)
rescue Liquid::Error => e
  raise TemplateRenderError, e.to_s
end