From 306237c30613ac0b66daa16b4565da1a46c77eec Mon Sep 17 00:00:00 2001 From: Andrew Cantino Date: Thu, 8 Aug 2013 09:56:22 -0700 Subject: [PATCH] Allow escaping of JSONPath outputs; demonstrate in the formatting agent. --- app/models/agents/event_formatting_agent.rb | 14 +- lib/utils.rb | 15 +- spec/lib/utils_spec.rb | 4 + .../agents/event_formatting_agent_spec.rb | 225 +++++++++--------- 4 files changed, 146 insertions(+), 112 deletions(-) diff --git a/app/models/agents/event_formatting_agent.rb b/app/models/agents/event_formatting_agent.rb index 7ba5f2ad..c302e658 100644 --- a/app/models/agents/event_formatting_agent.rb +++ b/app/models/agents/event_formatting_agent.rb @@ -24,7 +24,7 @@ module Agents subject: "$.data" } - JSONPaths must be between < and > . Make sure that you dont use these symbols anywhere else. + JSONPaths must be between < and > . Make sure that you don't use these symbols anywhere else. Events generated by this possible Event Formatting Agent will look like: @@ -35,7 +35,13 @@ module Agents If you want to retain original contents of events and only add new keys, then set `mode` to `merge`, otherwise set it to `clean`. - By default, the output event will have `agent` and `created_at` fields as well, reflecting the original Agent type and Event creation time. You can skip these outputs by setting `skip_agent` and `skip_created_at` to `true`. + By default, the output event will have `agent` and `created_at` fields added as well, reflecting the original Agent type and Event creation time. You can skip these outputs by setting `skip_agent` and `skip_created_at` to `true`. + + To CGI escape output (for example when creating a link), prefix with `escape`, like so: + + { + :message => "A peak was on Twitter in <$.group_by>. Search: https://twitter.com/search?q=" + } MD event_description <<-MD @@ -62,7 +68,9 @@ module Agents end def value_constructor(value, payload) - value.gsub(/<[^>]+>/).each {|jsonpath| Utils.values_at(payload,jsonpath[1..-2]).first.to_s } + value.gsub(/<[^>]+>/).each { |jsonpath| + Utils.values_at(payload, jsonpath[1..-2]).first.to_s + } end def receive(incoming_events) diff --git a/lib/utils.rb b/lib/utils.rb index fc7a0d82..d9c10fd4 100644 --- a/lib/utils.rb +++ b/lib/utils.rb @@ -1,4 +1,5 @@ require 'jsonpath' +require 'cgi' module Utils # Unindents if the indentation is 2 or more characters. @@ -22,6 +23,18 @@ module Utils end def self.values_at(data, path) - JsonPath.new(path, :allow_eval => false).on(data.is_a?(String) ? data : data.to_json) + if path =~ /\Aescape / + path.gsub!(/\Aescape /, '') + escape = true + else + escape = false + end + + result = JsonPath.new(path, :allow_eval => false).on(data.is_a?(String) ? data : data.to_json) + if escape + result.map {|r| CGI::escape r } + else + result + end end end \ No newline at end of file diff --git a/spec/lib/utils_spec.rb b/spec/lib/utils_spec.rb index 7cc5eda1..66552a37 100644 --- a/spec/lib/utils_spec.rb +++ b/spec/lib/utils_spec.rb @@ -24,5 +24,9 @@ describe Utils do Utils.values_at({ :foo => [ { :bar => :baz }, { :bar => :bing } ]}, "foo[*].bar").should == %w[baz bing] Utils.values_at({ :foo => [ { :bar => :baz }, { :bar => :bing } ]}, "foo[*].bar").should == %w[baz bing] end + + it "should allow escaping" do + Utils.values_at({ :foo => { :bar => "escape this!?" }}, "escape $.foo.bar").should == ["escape+this%21%3F"] + end end end \ No newline at end of file diff --git a/spec/models/agents/event_formatting_agent_spec.rb b/spec/models/agents/event_formatting_agent_spec.rb index 72cb77c2..2702e611 100644 --- a/spec/models/agents/event_formatting_agent_spec.rb +++ b/spec/models/agents/event_formatting_agent_spec.rb @@ -1,119 +1,128 @@ require 'spec_helper' describe Agents::EventFormattingAgent do - before do - @valid_params = { - :name => "somename", - :options => { - :instructions => { - :message => "Received <$.content.text.*> from <$.content.name> .", - :subject => "Weather looks like <$.conditions>" - }, - :mode => "clean", - :skip_agent => "false", - :skip_created_at => "false" - } - } - @checker = Agents::EventFormattingAgent.new(@valid_params) - @checker.user = users(:jane) - @checker.save! - - @event = Event.new - @event.agent = agents(:jane_weather_agent) - @event.created_at = Time.now - @event.payload = { - :content => { - :text => "Some Lorem Ipsum", - :name => "somevalue" + before do + @valid_params = { + :name => "somename", + :options => { + :instructions => { + :message => "Received <$.content.text.*> from <$.content.name> .", + :subject => "Weather looks like <$.conditions>" }, - :conditions => "someothervalue" + :mode => "clean", + :skip_agent => "false", + :skip_created_at => "false" } + } + @checker = Agents::EventFormattingAgent.new(@valid_params) + @checker.user = users(:jane) + @checker.save! + + @event = Event.new + @event.agent = agents(:jane_weather_agent) + @event.created_at = Time.now + @event.payload = { + :content => { + :text => "Some Lorem Ipsum", + :name => "somevalue" + }, + :conditions => "someothervalue" + } + end + + describe "#receive" do + it "should accept clean mode" do + @checker.receive([@event]) + Event.last.payload[:content].should == nil end - describe "#receive" do - it "checks if clean mode is working fine" do - @checker.receive([@event]) - Event.last.payload[:content].should == nil - end - - it "checks if merge mode is working fine" do - @checker.options[:mode] = "merge" - @checker.receive([@event]) - Event.last.payload[:content].should_not == nil - end - - it "checks if skip_agent is working fine" do - @checker.receive([@event]) - Event.last.payload[:agent].should == "WeatherAgent" - @checker.options[:skip_agent] = "true" - @checker.receive([@event]) - Event.last.payload[:agent].should == nil - end - - it "checks if skip_created_at is working fine" do - @checker.receive([@event]) - Event.last.payload[:created_at].should_not == nil - @checker.options[:skip_created_at] = "true" - @checker.receive([@event]) - Event.last.payload[:created_at].should == nil - end - - it "checks if instructions are working fine" do - @checker.receive([@event]) - Event.last.payload[:message].should == "Received Some Lorem Ipsum from somevalue ." - Event.last.payload[:subject].should == "Weather looks like someothervalue" - end - - it "checks if it can handle multiple events" do - event1 = Event.new - event1.agent = agents(:bob_weather_agent) - event1.payload = { - :content => { - :text => "Some Lorem Ipsum", - :name => "somevalue" - }, - :conditions => "someothervalue" - } - - event2 = Event.new - event2.agent = agents(:bob_weather_agent) - event2.payload = { - :content => { - :text => "Some Lorem Ipsum", - :name => "somevalue" - }, - :conditions => "someothervalue" - } - - lambda { - @checker.receive([event2,event1]) - }.should change { Event.count }.by(2) - end + it "should accept merge mode" do + @checker.options[:mode] = "merge" + @checker.receive([@event]) + Event.last.payload[:content].should_not == nil end - describe "validation" do - before do - @checker.should be_valid - end - - it "should validate presence of instructions" do - @checker.options[:instructions] = "" - @checker.should_not be_valid - end - - it "should validate presence of mode" do - @checker.options[:mode] = "" - @checker.should_not be_valid - end - - it "should validate presence of skip_agent" do - @checker.options[:skip_agent] = "" - @checker.should_not be_valid - end - - it "should validate presence of skip_created_at" do - @checker.options[:skip_created_at] = "" - @checker.should_not be_valid - end + it "should accept skip_agent" do + @checker.receive([@event]) + Event.last.payload[:agent].should == "WeatherAgent" + @checker.options[:skip_agent] = "true" + @checker.receive([@event]) + Event.last.payload[:agent].should == nil end + + it "should accept skip_created_at" do + @checker.receive([@event]) + Event.last.payload[:created_at].should_not == nil + @checker.options[:skip_created_at] = "true" + @checker.receive([@event]) + Event.last.payload[:created_at].should == nil + end + + it "should handle JSONPaths in instructions" do + @checker.receive([@event]) + Event.last.payload[:message].should == "Received Some Lorem Ipsum from somevalue ." + Event.last.payload[:subject].should == "Weather looks like someothervalue" + end + + it "should allow escaping" do + @event.payload[:content][:name] = "escape this!?" + @event.save! + @checker.options[:instructions][:message] = "Escaped: \nNot escaped: <$.content.name>" + @checker.save! + @checker.receive([@event]) + Event.last.payload[:message].should == "Escaped: escape+this%21%3F\nNot escaped: escape this!?" + end + + it "should handle multiple events" do + event1 = Event.new + event1.agent = agents(:bob_weather_agent) + event1.payload = { + :content => { + :text => "Some Lorem Ipsum", + :name => "somevalue" + }, + :conditions => "someothervalue" + } + + event2 = Event.new + event2.agent = agents(:bob_weather_agent) + event2.payload = { + :content => { + :text => "Some Lorem Ipsum", + :name => "somevalue" + }, + :conditions => "someothervalue" + } + + lambda { + @checker.receive([event2, event1]) + }.should change { Event.count }.by(2) + end + end + + describe "validation" do + before do + @checker.should be_valid + end + + it "should validate presence of instructions" do + @checker.options[:instructions] = "" + @checker.should_not be_valid + end + + it "should validate presence of mode" do + @checker.options[:mode] = "" + @checker.should_not be_valid + end + + it "should validate presence of skip_agent" do + @checker.options[:skip_agent] = "" + @checker.should_not be_valid + end + + it "should validate presence of skip_created_at" do + @checker.options[:skip_created_at] = "" + @checker.should_not be_valid + end + end end \ No newline at end of file