diff --git a/app/concerns/liquid_droppable.rb b/app/concerns/liquid_droppable.rb index 84ee49b4..4be1e03b 100644 --- a/app/concerns/liquid_droppable.rb +++ b/app/concerns/liquid_droppable.rb @@ -18,6 +18,11 @@ module LiquidDroppable yield [name, __send__(name)] } end + + def as_json + return {} unless defined?(self.class::METHODS) + Hash[self.class::METHODS.map { |m| [m, send(m).as_json]}] + end end included do @@ -33,12 +38,10 @@ module LiquidDroppable self.class::Drop.new(self) end - class MatchDataDrop < Liquid::Drop - def initialize(object) - @object = object - end + class MatchDataDrop < Drop + METHODS = %w[pre_match post_match names size] - %w[pre_match post_match names size].each { |attr| + METHODS.each { |attr| define_method(attr) { @object.__send__(attr) } @@ -64,7 +67,9 @@ module LiquidDroppable require 'uri' class URIDrop < Drop - URI::Generic::COMPONENT.each { |attr| + METHODS = URI::Generic::COMPONENT + + METHODS.each { |attr| define_method(attr) { @object.__send__(attr) } diff --git a/app/concerns/liquid_interpolatable.rb b/app/concerns/liquid_interpolatable.rb index de3d70e3..f3242692 100644 --- a/app/concerns/liquid_interpolatable.rb +++ b/app/concerns/liquid_interpolatable.rb @@ -92,7 +92,9 @@ module LiquidInterpolatable def interpolate_string(string, self_object = nil) interpolate_with(self_object) do - Liquid::Template.parse(string).render!(interpolation_context) + catch :as_object do + Liquid::Template.parse(string).render!(interpolation_context) + end end end @@ -225,6 +227,25 @@ module LiquidInterpolatable JSON.dump(input) end + # Returns a Ruby object + # + # It can be used as a JSONPath replacement for Agents that only support Liquid: + # + # Event: {"something": {"nested": {"data": 1}}} + # Liquid: {{something.nested | as_object}} + # Returns: {"data": 1} + # + # Splitting up a string with Liquid filters and return the Array: + # + # Event: {"data": "A,B,C"}} + # Liquid: {{data | split: ',' | as_object}} + # Returns: ['A', 'B', 'C'] + # + # as_object ALWAYS has be the last filter in a Liquid expression! + def as_object(object) + throw :as_object, object.as_json + end + private def logger diff --git a/app/models/agent.rb b/app/models/agent.rb index e39e106e..37916fdb 100644 --- a/app/models/agent.rb +++ b/app/models/agent.rb @@ -443,7 +443,7 @@ class AgentDrop @object.short_type end - [ + METHODS = [ :name, :type, :options, @@ -456,7 +456,9 @@ class AgentDrop :disabled, :keep_events_for, :propagate_immediately, - ].each { |attr| + ] + + METHODS.each { |attr| define_method(attr) { @object.__send__(attr) } unless method_defined?(attr) diff --git a/app/models/event.rb b/app/models/event.rb index df3011ce..eb5260a4 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -119,4 +119,8 @@ class EventDrop def _location_ @object.location end + + def as_json + {location: _location_.as_json, agent: @object.agent.to_liquid.as_json, payload: @payload.as_json, created_at: created_at.as_json} + end end diff --git a/spec/concerns/liquid_interpolatable_spec.rb b/spec/concerns/liquid_interpolatable_spec.rb index bec52b0c..dcb3181e 100644 --- a/spec/concerns/liquid_interpolatable_spec.rb +++ b/spec/concerns/liquid_interpolatable_spec.rb @@ -264,4 +264,59 @@ describe LiquidInterpolatable::Filters do expect(agent.interpolated['cleaned']).to eq('FOObar ZOObar') end end + + context 'as_object' do + let(:agent) { Agents::InterpolatableAgent.new(name: "test") } + + it 'returns an array that was splitted in liquid tags' do + agent.interpolation_context['something'] = 'test,string,abc' + agent.options['array'] = "{{something | split: ',' | as_object}}" + expect(agent.interpolated['array']).to eq(['test', 'string', 'abc']) + end + + it 'returns an object that was not modified in liquid' do + agent.interpolation_context['something'] = {'nested' => {'abc' => 'test'}} + agent.options['object'] = "{{something.nested | as_object}}" + expect(agent.interpolated['object']).to eq({"abc" => 'test'}) + end + + context 'as_json' do + def ensure_safety(obj) + JSON.parse(JSON.dump(obj)) + end + + it 'it converts "complex" objects' do + agent.interpolation_context['something'] = {'nested' => Service.new} + agent.options['object'] = "{{something | as_object}}" + expect(agent.interpolated['object']).to eq({'nested'=> ensure_safety(Service.new.as_json)}) + end + + it 'works with AgentDrops' do + agent.interpolation_context['something'] = agent + agent.options['object'] = "{{something | as_object}}" + expect(agent.interpolated['object']).to eq(ensure_safety(agent.to_liquid.as_json.stringify_keys)) + end + + it 'works with EventDrops' do + event = Event.new(payload: {some: 'payload'}, agent: agent, created_at: Time.now) + agent.interpolation_context['something'] = event + agent.options['object'] = "{{something | as_object}}" + expect(agent.interpolated['object']).to eq(ensure_safety(event.to_liquid.as_json.stringify_keys)) + end + + it 'works with MatchDataDrops' do + match = "test string".match(/\A(?\w+)\s(.+?)\z/) + agent.interpolation_context['something'] = match + agent.options['object'] = "{{something | as_object}}" + expect(agent.interpolated['object']).to eq(ensure_safety(match.to_liquid.as_json.stringify_keys)) + end + + it 'works with URIDrops' do + uri = URI.parse("https://google.com?q=test") + agent.interpolation_context['something'] = uri + agent.options['object'] = "{{something | as_object}}" + expect(agent.interpolated['object']).to eq(ensure_safety(uri.to_liquid.as_json.stringify_keys)) + end + end + end end