diff --git a/lib/appoptics/metrics.rb b/lib/appoptics/metrics.rb index 3d36684..4e9b0bf 100644 --- a/lib/appoptics/metrics.rb +++ b/lib/appoptics/metrics.rb @@ -7,6 +7,7 @@ require 'appoptics/metrics/collection' require 'appoptics/metrics/connection' require 'appoptics/metrics/errors' +require 'appoptics/metrics/params_encoder' require 'appoptics/metrics/persistence' require 'appoptics/metrics/queue' require 'appoptics/metrics/smart_json' diff --git a/lib/appoptics/metrics/connection.rb b/lib/appoptics/metrics/connection.rb index 0dfe37b..3dae6b6 100644 --- a/lib/appoptics/metrics/connection.rb +++ b/lib/appoptics/metrics/connection.rb @@ -32,7 +32,7 @@ def transport raise(NoClientProvided, "No client provided.") unless @client @transport ||= Faraday::Connection.new( url: api_endpoint + "/v1/", - request: {open_timeout: 20, timeout: 30}) do |f| + request: {open_timeout: 20, timeout: 30, params_encoder: AppOptics::Metrics::ParamsEncoder}) do |f| f.use AppOptics::Metrics::Middleware::RequestBody f.use AppOptics::Metrics::Middleware::Retry diff --git a/lib/appoptics/metrics/params_encoder.rb b/lib/appoptics/metrics/params_encoder.rb new file mode 100644 index 0000000..dd8104e --- /dev/null +++ b/lib/appoptics/metrics/params_encoder.rb @@ -0,0 +1,68 @@ +module AppOptics + module Metrics + module ParamsEncoder + SUBKEY_REGEXP = /\[|\]/ + + def self.encode(params) + return encode_tags(params) if params["tags"] + Faraday::NestedParamsEncoder.encode(params) + end + + def self.encode_tags(params) + query_string = "" + # Helper proc + to_query = proc do |parent, obj, key| + obj.keys.zip(obj.values).each do |k, v| + if v.is_a?(Hash) + to_query.call(parent, v, k) + elsif v.is_a?(Array) && key + v.each do |vv| + query_string << nested_key(key, k, vv) + end + else + if key + query_string << nested_key(key, k, v) + else + query_string << "#{k}=#{v}&" + end + end + end + query_string + end + result = to_query.call(query_string, params) + if result[-1] == "&" + result = result[0..-2] + end + result + end + + def self.decode(query) + params = {} + query.split("&").each do |pair| + key, value = pair.split("=", 2) + keys = key.split(SUBKEY_REGEXP) + if keys.size > 1 # is subkey + params[keys[0]] ||= {} + if !params[keys[0]][keys[1]] + params[keys[0]][keys[1]] = value + else + old_value = params[keys[0]][keys[1]] + if !old_value.is_a?(Array) + params[keys[0]][keys[1]] = [] + params[keys[0]][keys[1]] << old_value + end + params[keys[0]][keys[1]] << value + end + else + params[key] = value + end + end + params + end + + def self.nested_key(key, subkey, value) + "#{key}[#{subkey}]=#{value}&" + end + end + end +end diff --git a/spec/unit/metrics/params_encoder_spec.rb b/spec/unit/metrics/params_encoder_spec.rb new file mode 100644 index 0000000..112035f --- /dev/null +++ b/spec/unit/metrics/params_encoder_spec.rb @@ -0,0 +1,50 @@ +require "spec_helper" + +module AppOptics + module Metrics + describe ParamsEncoder do + let(:single_tag) { + { + "resolution" => "3600", + "duration" => "60", + "tags" => { + "hostname" => "app1" + } + } + } + + let(:multi_tag) { + { + "resolution" => "3600", + "duration" => "60", + "tags" => { + "hostname" => ['app1', 'app2'] + } + } + } + context "encode" do + it "single value tag" do + result = described_class.encode(single_tag) + expect(result).to eq("resolution=3600&duration=60&tags[hostname]=app1") + end + + it "array value tag" do + result = described_class.encode(multi_tag) + expect(result).to eq("resolution=3600&duration=60&tags[hostname]=app1&tags[hostname]=app2") + end + end + + context "decode" do + it "single value tag" do + result = described_class.decode("resolution=3600&duration=60&tags[hostname]=app1") + expect(result).to eq(single_tag) + end + + it "array value tag" do + result = described_class.decode("resolution=3600&duration=60&tags[hostname]=app1&tags[hostname]=app2") + expect(result).to eq(multi_tag) + end + end + end + end +end