vs_client 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cf492a23cc489321334dca187cee0dca6ea6935647960daea046d5d40c4e701a
4
+ data.tar.gz: 0fe00a98dc3c6712dfcc107ac42d7f736fa01b0532e4222173a2fb98a34d8ac5
5
+ SHA512:
6
+ metadata.gz: 8ddba1f4b25484bd0a47760daeceec17b2d4bda05db0beed33e9ee6244712c3f41be208b5e6dc0d617ed88c0b8e6840a61fdcfd21855e17201069f93d2cc0d09
7
+ data.tar.gz: f4a4532720404b8e0152c695495da0ae372d573e21e1e395c111076184c36228df92fa32d226ac4acf62031fd0ff9699ad165107cd53c76bdb71a0aa962ecdc7
@@ -0,0 +1,34 @@
1
+ name: "CodeQL"
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ analyze:
11
+ name: Analyze
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ actions: read
15
+ contents: read
16
+ security-events: write
17
+
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ language: [ 'ruby' ]
22
+
23
+ steps:
24
+ - name: Checkout repository
25
+ uses: actions/checkout@v3
26
+
27
+ - name: Initialize CodeQL
28
+ uses: github/codeql-action/init@v2
29
+ with:
30
+ languages: ${{ matrix.language }}
31
+
32
+ - name: Perform CodeQL Analysis
33
+ uses: github/codeql-action/analyze@v2
34
+
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in vs_client.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,55 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ vs_client (0.8.8)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.7.0)
10
+ public_suffix (>= 2.0.2, < 5.0)
11
+ byebug (11.1.3)
12
+ concurrent-ruby (1.1.9)
13
+ crack (0.4.5)
14
+ rexml
15
+ diff-lcs (1.4.4)
16
+ faker (2.18.0)
17
+ i18n (>= 1.6, < 2)
18
+ hashdiff (1.0.1)
19
+ i18n (1.8.10)
20
+ concurrent-ruby (~> 1.0)
21
+ public_suffix (4.0.6)
22
+ rake (10.5.0)
23
+ rexml (3.2.5)
24
+ rspec (3.10.0)
25
+ rspec-core (~> 3.10.0)
26
+ rspec-expectations (~> 3.10.0)
27
+ rspec-mocks (~> 3.10.0)
28
+ rspec-core (3.10.1)
29
+ rspec-support (~> 3.10.0)
30
+ rspec-expectations (3.10.1)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.10.0)
33
+ rspec-mocks (3.10.2)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.10.0)
36
+ rspec-support (3.10.2)
37
+ webmock (3.13.0)
38
+ addressable (>= 2.3.6)
39
+ crack (>= 0.3.2)
40
+ hashdiff (>= 0.4.0, < 2.0.0)
41
+
42
+ PLATFORMS
43
+ ruby
44
+
45
+ DEPENDENCIES
46
+ bundler (~> 1.17)
47
+ byebug
48
+ faker
49
+ rake (~> 10.0)
50
+ rspec (~> 3.0)
51
+ vs_client!
52
+ webmock
53
+
54
+ BUNDLED WITH
55
+ 1.17.3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 TODO: Write your name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # VsClient
2
+
3
+ Provides a seamless integration with vendor search service for ruby.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'vs_client', git: 'https://github.com/HungerStation/vs-client-rb.git'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle update --source vs_client
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install vs_client
20
+ ## Setup
21
+ ```ruby
22
+ # if you are on Rails this block should be in:
23
+ # /config/initializers/vs_client.rb
24
+ VsClient.configure do |config|
25
+ config.url = 'service url goes here' # it should be the vendor search service host.
26
+ config.api_version = 'version path' # like '/api/v2'.
27
+ config.enable_http_debug_mode = true # helps you to see the request lifecycle, should be false over production.
28
+ config.use_ssl = true # enable connection over https.
29
+ config.keep_alive_timeout = 0 # for how long (in seconds) should the server keep tcp connection to be reused.
30
+ config.timeout = 60 # how long the client will wait till it gets a response.
31
+ end
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ ```ruby
37
+ # GET /filters
38
+ query = {
39
+ latitude: 24.828453624665187,
40
+ longitude: 46.640109869734395,
41
+ home_module_id: 10,
42
+ }
43
+
44
+ VsClient.get_filters(
45
+ query: payload,
46
+ headers: {
47
+ "Device-Uid" => "008FA000-0000-0000-0000-006E5B1A0000",
48
+ "App-Version" => "2570",
49
+ "Accept-Language" => "en"
50
+ },
51
+ fallback: -> () { fallback }
52
+ )
53
+
54
+ def fallback
55
+ # your logic to handle remote failures for consistency
56
+ # whenever the client failed to gets data this block will be executed as plan B.
57
+ end
58
+ ```
59
+ For practical example check this [Filters Example](https://github.com/HungerStation/vs-client-rb/tree/master/examples/filters.rb)
60
+ ## Run Test
61
+
62
+ $ rspec spec
63
+ ## Development
64
+
65
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
66
+
67
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
68
+
69
+ ## Contributing
70
+
71
+ Bug reports and pull requests are welcome on GitHub at https://github.com/HungerStation/vs-client-rb.
72
+
73
+ ## License
74
+
75
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "vs_client"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,101 @@
1
+ $: << File.expand_path('../lib', File.dirname(__FILE__))
2
+
3
+ require 'vs_client'
4
+
5
+ VsClient.configure do |config|
6
+ config.url = 'https://hs-staging.com'
7
+ config.api_version = '/api/v2'
8
+ config.enable_http_debug_mode = true
9
+ config.use_ssl = true
10
+ config.keep_alive_timeout = 0
11
+ config.open_timeout = 60
12
+ config.timeout = 60
13
+ end
14
+
15
+ payload = {
16
+ latitude: 24.828453624665187,
17
+ longitude: 46.640109869734395,
18
+ home_module_id: 10,
19
+ }
20
+
21
+ class FiltersService
22
+ def initialize(payload)
23
+ @payload = payload
24
+ init_in_memory_db
25
+ seed_data
26
+ end
27
+
28
+ def run
29
+ VsClient.get_filters(
30
+ query: @payload,
31
+ headers: {
32
+ "User-Agent" => 'HungerStation/1576 iPhone/12,3 iOS/14.0 CFNetwork/1197 Darwin/20.0.0',
33
+ "device-uid" => '008FA000-0000-0000-0000-006E5B1A0000',
34
+ "app-version" => '1576',
35
+ "Accept-Language" => 'en',
36
+ },
37
+ fallback: -> () { fallback }
38
+ )
39
+ end
40
+
41
+ private
42
+
43
+ def fallback
44
+ {
45
+ title: "Kitchens",
46
+ type: "kitchen_ids",
47
+ multi_selection: false,
48
+ filters: @in_memory_db[:filters][:kitchens].map do |kitchen|
49
+ {
50
+ id: kitchen[:id],
51
+ value: kitchen[:name],
52
+ icon_url: "https://assest.com/#{kitchen[:id]}/#{kitchen[:name]}.png",
53
+ }
54
+ end
55
+ }
56
+ end
57
+
58
+ def init_in_memory_db
59
+ @in_memory_db = {filters: {kitchens: []}}
60
+ end
61
+
62
+ def seed_data
63
+ @in_memory_db[:filters][:kitchens] = [
64
+ {
65
+ id: 1,
66
+ name: "Fast Food"
67
+ },
68
+ {
69
+ id: 2,
70
+ name: "Sandwiches"
71
+ },
72
+ {
73
+ id: 3,
74
+ name: "Asian"
75
+ },
76
+ {
77
+ id: 4,
78
+ name: "Italian"
79
+ },
80
+ {
81
+ id: 5,
82
+ name: "Dessert"
83
+ }
84
+ ]
85
+ end
86
+ end
87
+
88
+ response = if ENV["FAIL"]
89
+ payload.delete(:latitude)
90
+ FiltersService.new(payload).run
91
+ else
92
+ FiltersService.new(payload).run
93
+ end
94
+
95
+
96
+ puts "****************"
97
+ puts "* *"
98
+ puts "* RESPONSE *"
99
+ puts "* *"
100
+ puts "****************"
101
+ puts response
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VsClient
4
+ class Configuration
5
+ # url => represent remote service (vendor search url).
6
+ # use_ssl => must be true if the url start with "https".
7
+ # enable_http_debug_mode => enable tracing the request phases for debug use only.
8
+ # logger => represent Custom HSLogger we use in platform.
9
+ # we can use different logger that has the same .log implementation.
10
+ # keep_alive_timeout => represent the time in seconds to keep the current
11
+ # tcp session opened for reusing the same tcp connection later.
12
+ # open_timeout => time taken to open a connection with remote server.
13
+ # timeout => represent the time in seconds to read the data from remote server.
14
+ attr_accessor :url, :use_ssl, :enable_http_debug_mode,
15
+ :logger, :keep_alive_timeout, :open_timeout,
16
+ :ssl_timeout, :timeout, :api_version
17
+
18
+ # Set default values
19
+ def initialize
20
+ @logger = nil
21
+ @use_ssl = true
22
+ @enable_http_debug_mode = false
23
+ @keep_alive_timeout = 60
24
+ @open_timeout = 5
25
+ @timeout = 5
26
+ @api_version = ''
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'json'
5
+
6
+ module VsClient
7
+ class NetHttp
8
+ CONTENT_TYPE = 'application/json'
9
+
10
+ def initialize(config: VsClient::Configuration.new)
11
+ @uri = URI(config.url)
12
+ @enabled_debug_mode = config.enable_http_debug_mode
13
+ @use_ssl = config.use_ssl
14
+ @keep_alive_timeout = config.keep_alive_timeout
15
+ @read_timeout = config.timeout
16
+ @open_timeout = config.open_timeout
17
+ @default_api_version = config.api_version
18
+ end
19
+
20
+ # Establish a connection with remote server
21
+ # @return [Net::HTTP] with opened tcp connection
22
+ def start_session
23
+ return session if session&.started?
24
+
25
+ session.start
26
+ end
27
+
28
+ # Execute GET requests
29
+ # @param path[String] represent remote resource
30
+ # @param payload[Hash] represent params if get request have body
31
+ # Raise error incase of failure (exceptions, 3xx, 4xx, 5xx)
32
+ # @return [Hash] represent response after parsing the json (res body)
33
+ def get(path:, query: nil, payload: nil, headers: {}, desired_api_version: nil)
34
+ @uri.path = full_path(path: path, desired_api_version: desired_api_version)
35
+ @uri.query = URI.encode_www_form(query) if query
36
+
37
+ @request = Net::HTTP::Get.new(@uri)
38
+ @request.body = payload.to_json if payload
39
+
40
+ inject_headers!(headers)
41
+ execute
42
+ end
43
+
44
+ # Execute POST requests
45
+ def post(path:, payload: {}, headers: {}, desired_api_version: nil)
46
+ @uri.path = full_path(path: path, desired_api_version: desired_api_version)
47
+ @uri.query = nil
48
+
49
+ @request = Net::HTTP::Post.new(@uri)
50
+ @request.body = payload.to_json
51
+
52
+ inject_headers!(headers)
53
+ execute
54
+ end
55
+
56
+ # Execute DELETE requests
57
+ def delete(path:, headers: {}, desired_api_version: nil)
58
+ @uri.path = full_path(path: path, desired_api_version: desired_api_version)
59
+ @uri.query = nil
60
+
61
+ @request = Net::HTTP::Delete.new(@uri)
62
+ inject_headers!(headers)
63
+ execute
64
+ end
65
+
66
+ private
67
+
68
+ # Represent HTTP session over tcp connection
69
+ # with the remote server.
70
+ # @return [Net::HTTP]
71
+ def session
72
+ return @session if @session
73
+
74
+ @session = Net::HTTP.new(@uri.hostname, @uri.port)
75
+ @session.set_debug_output(STDOUT) if @enabled_debug_mode
76
+ @session.use_ssl = @use_ssl
77
+ @session.verify_mode = OpenSSL::SSL::VERIFY_NONE
78
+ @session.keep_alive_timeout = @keep_alive_timeout
79
+ @session.read_timeout = @read_timeout
80
+ @session.open_timeout = @open_timeout
81
+ @session
82
+ end
83
+
84
+ def inject_headers!(headers)
85
+ headers.each do |key, value|
86
+ @request[key] = value
87
+ end
88
+ end
89
+
90
+ # Conduct the request over the established tcp connection
91
+ # throught Net::HTTP (session).
92
+ # Net::HTTP handle gzip compress/uncompress automatically.
93
+ # Raise error incase of failure (exceptions, 3xx, 4xx, 5xx)
94
+ # @return [Hash] represent response after parsing the json (res body)
95
+ def execute
96
+ @response = @session.request(@request)
97
+ @response.body = JSON.parse(@response.body) if json_content? && @response.body
98
+
99
+ VsClient::Response.new(@response)
100
+ end
101
+
102
+ # Check if the body is in json format
103
+ # @return [Boolean]
104
+ def json_content?
105
+ @response&.header['Content-Type']&.include?(CONTENT_TYPE)
106
+ end
107
+
108
+ def full_path(path:, desired_api_version: nil)
109
+ "#{desired_api_version || @default_api_version}#{path}"
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VsClient
4
+ class Response
5
+ attr_reader :klass, :message, :code, :body
6
+
7
+ def initialize(net_http_response)
8
+ @klass = net_http_response.class.name
9
+ @message = net_http_response.message
10
+ @code = net_http_response.code
11
+ @body = net_http_response.body
12
+ @success = net_http_response.kind_of?(Net::HTTPSuccess)
13
+ end
14
+
15
+ def succeeded?
16
+ @success
17
+ end
18
+
19
+ def failed?
20
+ !succeeded?
21
+ end
22
+
23
+ def klass_name
24
+ @klass.name
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,3 @@
1
+ module VsClient
2
+ VERSION = "0.8.8"
3
+ end
data/lib/vs_client.rb ADDED
@@ -0,0 +1,409 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'vs_client/version'
4
+ require 'vs_client/configuration'
5
+ require 'vs_client/response'
6
+ require 'vs_client/net_http'
7
+
8
+ module VsClient
9
+ class << self
10
+ CLIENT_ERROR = 'client'
11
+ REMOTE_ERROR = 'remote'
12
+
13
+ # @return [VsClient::Configuration]
14
+ def configuration
15
+ @configuration ||= VsClient::Configuration.new
16
+ end
17
+
18
+ # Override the default configurations
19
+ def configure
20
+ yield configuration
21
+ setup_http_client
22
+ end
23
+
24
+ # GET /filters:
25
+ # @param query [Hash] keys:
26
+ # latitude, float, required
27
+ # longitude, float, required
28
+ # home_module_id, Integer
29
+ # @param headers [Hash] represent custom http headers.
30
+ # @param fallback [Lambda] executed on failure
31
+ # It should be provided by the initiator.
32
+ # @return [Hash] on success
33
+ # @return [NilClass] if fallback is nil
34
+ def get_filters(query:, headers: {}, fallback: nil)
35
+ path = '/filters'
36
+ @http_client.start_session
37
+ response = @http_client.get(path: path, query: query, headers: headers)
38
+ return response.body if response.succeeded?
39
+
40
+ handle_failure(fallback, REMOTE_ERROR, [path, query, response, headers])
41
+ rescue => e
42
+ handle_failure(fallback, CLIENT_ERROR, [path, query, e, headers])
43
+ end
44
+
45
+ # POST /search/recent:
46
+ # @param payload [Hash] keys:
47
+ # home_module_id, integer, required
48
+ # search_query, string, required
49
+ # @param headers [Hash] represent custom http headers.
50
+ # @param fallback [Lambda] executed on failure
51
+ # It should be provided by the initiator.
52
+ # @return [Hash] on success
53
+ # @return [NilClass] if fallback is nil
54
+ def create_recent_search(payload:, headers: {}, fallback: nil)
55
+ path = '/search/recent'
56
+ @http_client.start_session
57
+ response = @http_client.post(path: path, payload: payload, headers: headers)
58
+ return response.body if response.succeeded?
59
+
60
+ handle_failure(fallback, REMOTE_ERROR, [path, payload, response, headers])
61
+ rescue => e
62
+ handle_failure(fallback, CLIENT_ERROR, [path, payload, e, headers])
63
+ end
64
+
65
+ # GET /search/suggestions:
66
+ # @param query [Hash] keys:
67
+ # home_module_id, integer, optional
68
+ # latitude, float, required
69
+ # longitude, float, required
70
+ # @param headers [Hash] represent custom http headers.
71
+ # @param fallback [Lambda] executed on failure
72
+ # It should be provided by the initiator.
73
+ # @return [Hash] on success
74
+ # @return [NilClass] if fallback is nil
75
+ def get_search_suggestions(query:, headers: {}, fallback: nil)
76
+ path = '/search/suggestions'
77
+ @http_client.start_session
78
+ response = @http_client.get(path: path, query: query, headers: headers)
79
+ return response.body if response.succeeded?
80
+
81
+ handle_failure(fallback, REMOTE_ERROR, [path, query, response, headers])
82
+ rescue => e
83
+ handle_failure(fallback, CLIENT_ERROR, [path, query, e, headers])
84
+ end
85
+
86
+ # GET /search/autocomplete:
87
+ # @param query [Hash] keys:
88
+ # home_module_id, integer, optional
89
+ # latitude, float, required
90
+ # longitude, float, required
91
+ # search_query, string, required
92
+ # @param headers [Hash] represent custom http headers.
93
+ # @param fallback [Lambda] executed on failure
94
+ # It should be provided by the initiator.
95
+ # @return [Hash] on success
96
+ # @return [NilClass] if fallback is nil
97
+ def get_search_autocomplete(query:, headers: {}, fallback: nil)
98
+ path = '/search/autocomplete'
99
+ @http_client.start_session
100
+ response = @http_client.get(path: path, query: query, headers: headers)
101
+ return response.body if response.succeeded?
102
+
103
+ handle_failure(fallback, REMOTE_ERROR, [path, query, response, headers])
104
+ rescue => e
105
+ handle_failure(fallback, CLIENT_ERROR, [path, query, e, headers])
106
+ end
107
+
108
+ # POST /swimlanes:
109
+ # @param payload [Hash] keys:
110
+ # home_module_id, integer, optional
111
+ # config, string, required
112
+ # placement_screen, string, required
113
+ # location[:longitude], float, required
114
+ # location[:latitude], float, required
115
+ # @param headers [Hash] represent custom http headers:
116
+ # hs-email, string, optional
117
+ # hs-user-id, integer, optional
118
+ # perseus-client-id, string, required
119
+ # perseus-session-id, string, required
120
+ # @param fallback [Lambda] executed on failure
121
+ # It should be provided by the initiator.
122
+ # @return [Hash] on success
123
+ # @return [NilClass] if fallback is nil
124
+ def get_swimlanes(payload:, headers: {}, fallback: nil)
125
+ path = '/swimlanes'
126
+ @http_client.start_session
127
+ response = @http_client.post(path: path, payload: payload, headers: headers, desired_api_version: '/api/v3')
128
+ return response.body if response.succeeded?
129
+
130
+ handle_failure(fallback, REMOTE_ERROR, [path, payload, response, headers])
131
+ rescue => e
132
+ handle_failure(fallback, CLIENT_ERROR, [path, payload, e, headers])
133
+ end
134
+
135
+ # POST /vendors/search:
136
+ # @param payload [Hash] keys:
137
+ # home_module_id, integer, optional
138
+ # config, string, required
139
+ # search_query, string, required
140
+ # location[:longitude], float, required
141
+ # location[:latitude], float, required]
142
+ # pagination[:page], int, required
143
+ # pagination[:limit], int, required
144
+ #
145
+ # @param headers [Hash] represent custom http headers:
146
+ # hs-email, string, optional
147
+ # hs-user-id, integer, optional
148
+ # hs-device-uid, string, required
149
+ # user-agent, string, required
150
+ # hs-is-new-customer, bool, optional
151
+ # hs-email-is-verified, bool, optional
152
+ # accept-language, string, optional
153
+ # perseus-client-id, string, required
154
+ # perseus-session-id, string, required
155
+ # @param fallback [Lambda] executed on failure
156
+ # It should be provided by the initiator.
157
+ # @return [Hash] on success
158
+ # @return [NilClass] if fallback is nil
159
+ def get_vendors_search(payload:, headers: {}, fallback: nil)
160
+ path = '/vendors/search'
161
+ @http_client.start_session
162
+ response = @http_client.post(path: path, payload: payload, headers: headers, desired_api_version: '/api/v2')
163
+ return response.body if response.succeeded?
164
+
165
+ handle_failure(fallback, REMOTE_ERROR, [path, payload, response, headers])
166
+ rescue => e
167
+ handle_failure(fallback, CLIENT_ERROR, [path, payload, e, headers])
168
+ end
169
+
170
+ # POST /vendors:
171
+ # @param payload [Hash] keys:
172
+ # home_module_id, integer, optional
173
+ # config, string, required
174
+ # search_query, string, required
175
+ # location[:longitude], float, required
176
+ # location[:latitude], float, required
177
+ # pagination[:page], int, required
178
+ # pagination[:limit], int, required
179
+ # filters[][:type, :ids], slice, optional
180
+ # @param headers [Hash] represent custom http headers:
181
+ # hs-email, string, optional
182
+ # hs-user-id, integer, optional
183
+ # hs-device-uid, string, required
184
+ # user-agent, string, required
185
+ # hs-is-new-customer, bool, optional
186
+ # hs-email-is-verified, bool, optional
187
+ # accept-language, string, optional
188
+ # perseus-client-id, string, required
189
+ # perseus-session-id, string, required
190
+ # @param fallback [Lambda] executed on failure
191
+ # It should be provided by the initiator.
192
+ # @return [Hash] on success
193
+ # @return [NilClass] if fallback is nil
194
+ def get_vendors_listing(payload:, headers: {}, fallback: nil)
195
+ path = '/vendors'
196
+ @http_client.start_session
197
+ response = @http_client.post(path: path, payload: payload, headers: headers, desired_api_version: '/api/v2')
198
+ return response.body if response.succeeded?
199
+
200
+ handle_failure(fallback, REMOTE_ERROR, [path, payload, response, headers])
201
+ rescue => e
202
+ handle_failure(fallback, CLIENT_ERROR, [path, payload, e, headers])
203
+ end
204
+
205
+ # DELETE /search/recent:
206
+ # @param headers [Hash] represent custom http headers.
207
+ # @param fallback [Lambda] executed on failure
208
+ # It should be provided by the initiator.
209
+ # @return [Hash] on success
210
+ # @return [NilClass] if fallback is nil
211
+ def delete_recent_search(headers: {}, fallback: nil)
212
+ path = "/search/recent"
213
+ @http_client.start_session
214
+ response = @http_client.delete(path: path, headers: headers)
215
+ return response.body if response.succeeded?
216
+
217
+ handle_failure(fallback, REMOTE_ERROR, [path, nil, response, headers])
218
+ rescue => e
219
+ handle_failure(fallback, CLIENT_ERROR, [path, nil, e, headers])
220
+ end
221
+
222
+ # DELETE /search/recent/home_modules/{id}:
223
+ # @param home_module_id [Integer] represent ID.
224
+ # @param headers [Hash] represent custom http headers.
225
+ # @param fallback [Lambda] executed on failure
226
+ # It should be provided by the initiator.
227
+ # @return [Hash] on success
228
+ # @return [NilClass] if fallback is nil
229
+ def delete_recent_search_by_home_module_id(home_module_id:, headers: {}, fallback: nil)
230
+ path = "/search/recent/home_modules/#{home_module_id}"
231
+ @http_client.start_session
232
+ response = @http_client.delete(path: path, headers: headers)
233
+ return response.body if response.succeeded?
234
+
235
+ handle_failure(fallback, REMOTE_ERROR, [path, nil, response, headers])
236
+ rescue => e
237
+ handle_failure(fallback, CLIENT_ERROR, [path, nil, e, headers])
238
+ end
239
+
240
+ # POST /vendors/premium:
241
+ # @param payload [Hash] keys:
242
+ # home_module_id, integer, optional
243
+ # campaign_id, integer, optional
244
+ # location[:longitude], float, required
245
+ # location[:latitude], float, required
246
+ # placement[:screen], string, required
247
+ # placement[:place], string, required
248
+ # placement[:type], string, required
249
+ # filters[:filter_type], string, optional
250
+ # filters[:ids], slice, optional
251
+ # @param headers [Hash] represent custom http headers:
252
+ # hs-email, string, optional
253
+ # hs-user-id, integer, optional
254
+ # hs-device-uid, string, required
255
+ # user-agent, string, required
256
+ # hs-is-new-customer, bool, optional
257
+ # hs-email-is-verified, bool, optional
258
+ # accept-language, string, optional
259
+ # perseus-client-id, string, required
260
+ # perseus-session-id, string, required
261
+ # @return [Hash] on success
262
+ # @return [NilClass] if fallback is nil
263
+ def get_premium_vendors(payload:, headers: {}, fallback: nil)
264
+ path = '/vendors/premium'
265
+ @http_client.start_session
266
+ response = @http_client.post(path: path, payload: payload, headers: headers, desired_api_version: '/api/v2')
267
+ return response.body if response.succeeded?
268
+ handle_failure(fallback, REMOTE_ERROR, [path, payload, response, headers])
269
+ rescue => e
270
+ handle_failure(fallback, CLIENT_ERROR, [path, payload, e, headers])
271
+ end
272
+
273
+ # GET /vendor-coverage/vendors:
274
+ # @param query [Hash] keys:
275
+ # filter[location][lat], float, required
276
+ # filter[location][long], float, required
277
+ # filter[status][], Array{string}, optional # must be in ["OPEN", "CLOSED", "BUSY", "SOON"]
278
+ # filter[vertical_ids][], Array{int}, optional
279
+ # @param headers [Hash] represent custom http headers:
280
+ # Content-Type, string, required # must be application/vnd.api+json
281
+ # Accept, string, required # must be application/vnd.api+json
282
+ # @return [Hash] on success
283
+ # @return [NilClass] if fallback is nil
284
+ def get_vendors_coverage(query:, headers: {}, fallback: nil)
285
+ path = '/vendor-coverage/vendors'
286
+ @http_client.start_session
287
+ headers.merge!(json_api_headers)
288
+ response = @http_client.get(path: path, query: query, headers: headers)
289
+ return response.body if response.succeeded?
290
+ handle_failure(fallback, REMOTE_ERROR, [path, query, response, headers])
291
+ rescue => e
292
+ handle_failure(fallback, CLIENT_ERROR, [path, query, e, headers])
293
+ end
294
+
295
+ # GET /vendor-coverage/{vendor_id}:
296
+ # @param query [Hash] keys:
297
+ # filter[location][lat], float, required
298
+ # filter[location][long], float, required
299
+ # @param headers [Hash] represent custom http headers:
300
+ # Content-Type, string, required # must be application/vnd.api+json
301
+ # Accept, string, required # must be application/vnd.api+json
302
+ # @return [Hash] on success
303
+ # @return [NilClass] if fallback is nil
304
+ def get_vendor_coverage(vendor_id:, query:, headers: {}, fallback: nil)
305
+ path = "/vendor-coverage/#{vendor_id}"
306
+ @http_client.start_session
307
+ headers.merge!(json_api_headers)
308
+ response = @http_client.get(path: path, query: query, headers: headers)
309
+ return response.body if response.succeeded?
310
+ handle_failure(fallback, REMOTE_ERROR, [path, query, response, headers])
311
+ rescue => e
312
+ handle_failure(fallback, CLIENT_ERROR, [path, query, e, headers])
313
+ end
314
+
315
+ # GET /campaigns/{id}/vendors:
316
+ # @param id, integer represent campaign id
317
+ # @param query [Hash] keys:
318
+ # latitude, float, required
319
+ # longitude, float, required
320
+ # home_module_id, integer, optional
321
+ # pagination[page], integer
322
+ # pagination[limit], integer
323
+ # @param headers [Hash] represent custom http headers:
324
+ # hs-email, string, optional
325
+ # hs-user-id, integer, optional
326
+ # hs-device-uid, string, required
327
+ # user-agent, string, required
328
+ # hs-is-new-customer, bool, optional
329
+ # hs-email-is-verified, bool, optional
330
+ # accept-language, string, optional
331
+ # perseus-client-id, string, required
332
+ # perseus-session-id, string, required
333
+ # @return [Hash] on success
334
+ # @return [NilClass] if fallback is nil
335
+ def get_vendors_by_campaign_id(id:, query:, headers: {}, fallback: nil)
336
+ path = "/campaigns/#{id}/vendors"
337
+ @http_client.start_session
338
+ response = @http_client.get(path: path, query: query, headers: headers)
339
+ return response.body if response.succeeded?
340
+ handle_failure(fallback, REMOTE_ERROR, [path, query, response, headers])
341
+ rescue => e
342
+ handle_failure(fallback, CLIENT_ERROR, [path, query, e, headers])
343
+ end
344
+
345
+ private
346
+
347
+ # Initialize the http client
348
+ def setup_http_client
349
+ @http_client = VsClient::NetHttp.new(config: configuration)
350
+ end
351
+
352
+ # Static JSONAPI headers
353
+ def json_api_headers
354
+ {
355
+ "Content-Type" => "application/vnd.api+json",
356
+ "Accept" => "application/vnd.api+json"
357
+ }
358
+ end
359
+
360
+ # If the client has been initialized with no logger at all
361
+ # will not push any logs.
362
+ # Execute any given fallback
363
+ # @param fallback [proc/lambda]
364
+ # @param type [Constant] represent type of the error (client/remote).
365
+ # @param args [Array] represent the arguments
366
+ # of the log function will be executed, based on the type.
367
+ def handle_failure(fallback, type, args)
368
+ send("log_#{type}_error", *args) if logger
369
+ return fallback.call if fallback
370
+ end
371
+
372
+ # Represent the injected logger (Custom::HsLogger)
373
+ def logger
374
+ @logger ||= configuration.logger
375
+ end
376
+
377
+ # Log remote errors based on endpoints
378
+ # @param endpoint [String] represent path of the resource.
379
+ # @param response [VsClient::Response]
380
+ def log_remote_error(endpoint, payload, response, headers)
381
+ logger.log(
382
+ severity: 'ERROR',
383
+ title: '[Vendor Search Service]: Remote Failed',
384
+ custom_headers: headers,
385
+ endpoint: endpoint,
386
+ payload: payload,
387
+ error: response.klass,
388
+ error_msg: response.message,
389
+ error_code: response.code,
390
+ error_body: response.body,
391
+ )
392
+ end
393
+
394
+ # Log client errors based on endpoints
395
+ # @param endpoint [String] represent path of the resource.
396
+ # @param error [Exception]
397
+ def log_client_error(endpoint, payload, error, headers)
398
+ logger.log(
399
+ severity: 'ERROR',
400
+ title: '[Vendor Search Service]: Client Failed',
401
+ custom_headers: headers,
402
+ endpoint: endpoint,
403
+ payload: payload,
404
+ error: error.class.name,
405
+ error_msg: error.message,
406
+ )
407
+ end
408
+ end
409
+ end
data/vs_client.gemspec ADDED
@@ -0,0 +1,30 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "vs_client/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "vs_client"
8
+ spec.version = VsClient::VERSION
9
+ spec.authors = ["Rudy Zidan"]
10
+ spec.email = ["rudy.zidan@hungerstation.com"]
11
+
12
+ spec.summary = "Vendor search service client for ruby"
13
+ spec.license = "MIT"
14
+
15
+ # Specify which files should be added to the gem when it is released.
16
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
17
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ end
20
+ spec.bindir = "exe"
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.17"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_development_dependency "byebug"
28
+ spec.add_development_dependency "webmock"
29
+ spec.add_development_dependency "faker"
30
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vs_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.8
5
+ platform: ruby
6
+ authors:
7
+ - Rudy Zidan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-10-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: webmock
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: faker
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - rudy.zidan@hungerstation.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".github/workflows/codeql-analysis.yml"
105
+ - ".gitignore"
106
+ - ".rspec"
107
+ - Gemfile
108
+ - Gemfile.lock
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - bin/console
113
+ - bin/setup
114
+ - examples/filters.rb
115
+ - lib/vs_client.rb
116
+ - lib/vs_client/configuration.rb
117
+ - lib/vs_client/net_http.rb
118
+ - lib/vs_client/response.rb
119
+ - lib/vs_client/version.rb
120
+ - vs_client.gemspec
121
+ homepage:
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubygems_version: 3.2.3
141
+ signing_key:
142
+ specification_version: 4
143
+ summary: Vendor search service client for ruby
144
+ test_files: []