hudu 0.3.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile +1 -1
- data/hudu.gemspec +2 -2
- data/lib/hudu/api.rb +4 -3
- data/lib/hudu/asset_helper.rb +45 -17
- data/lib/hudu/authentication.rb +8 -7
- data/lib/hudu/client.rb +95 -37
- data/lib/hudu/configuration.rb +5 -4
- data/lib/hudu/connection.rb +3 -5
- data/lib/hudu/error.rb +3 -2
- data/lib/hudu/pagination.rb +49 -9
- data/lib/hudu/rate_throttle_middleware.rb +55 -50
- data/lib/hudu/version.rb +1 -1
- data/lib/hudu.rb +4 -6
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ed87eec77a92013c3e0c83084432ad5a91a50762cdd19e0e24ea6b383481d2c0
|
4
|
+
data.tar.gz: '048caa2a4f7abb2e2b940fd9681a026669ab84ef7d67cfc7c3655736a41de75d'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 64307e6e97f707ad42b2736c4473fa178e9b4a8f86a62fb9840c65519ab5e4cb4f057d8d0f7f06e52c931f67ef0066f0ceb1b27f777c75dfda8566d4a77222f5
|
7
|
+
data.tar.gz: 14a41bde5270cd5e5710674ad9372ca9579a396ddb18d3ba6d517c4b89b9a7bad37608b5bae52fa6d496952589e9d6aed333f70727b0251d0202aa70007e3bbe
|
data/CHANGELOG.md
CHANGED
data/Gemfile
CHANGED
data/hudu.gemspec
CHANGED
@@ -29,9 +29,9 @@ Gem::Specification.new do |s|
|
|
29
29
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
30
30
|
s.platform = Gem::Platform::RUBY
|
31
31
|
s.add_runtime_dependency 'faraday'
|
32
|
-
s.add_runtime_dependency 'wrapi',
|
32
|
+
s.add_runtime_dependency 'wrapi', '>= 0.3.0'
|
33
33
|
s.add_development_dependency 'dotenv'
|
34
34
|
s.add_development_dependency 'minitest'
|
35
|
-
s.add_development_dependency 'simplecov'
|
36
35
|
s.add_development_dependency 'rubocop'
|
36
|
+
s.add_development_dependency 'simplecov'
|
37
37
|
end
|
data/lib/hudu/api.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wrapi'
|
2
4
|
require File.expand_path('authentication', __dir__)
|
3
5
|
require File.expand_path('configuration', __dir__)
|
4
6
|
require File.expand_path('connection', __dir__)
|
@@ -6,9 +8,8 @@ require File.expand_path('connection', __dir__)
|
|
6
8
|
module Hudu
|
7
9
|
# @private
|
8
10
|
class API
|
9
|
-
|
10
11
|
# @private
|
11
|
-
attr_accessor
|
12
|
+
attr_accessor(*Configuration::VALID_OPTIONS_KEYS)
|
12
13
|
|
13
14
|
# Creates a new API and copies settings from singleton
|
14
15
|
def initialize(options = {})
|
data/lib/hudu/asset_helper.rb
CHANGED
@@ -1,32 +1,60 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module Hudu
|
4
|
+
# The AssetHelper class contains helper methods for constructing and creating asset data.
|
3
5
|
class AssetHelper
|
4
|
-
#
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
# Constructs an asset for updates by extracting key attributes and formatting custom fields.
|
7
|
+
#
|
8
|
+
# @param asset [Object] An Hudu entity that represents an asset, expected to respond to `attributes` and `fields`.
|
9
|
+
# @return [Hash] A formatted hash representing the asset for update operations.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# asset = SomeAssetEntity.new(attributes: { id: 1, name: "Asset 1", company_id: 101 }, fields: [field1, field2])
|
13
|
+
# Hudu::AssetHelper.construct_asset(asset)
|
14
|
+
# # => { asset: { id: 1, company_id: 101, asset_layout_id: nil, slug: nil, name: "Asset 1", custom_fields: [...] } }
|
15
|
+
def self.construct_asset(asset)
|
16
|
+
custom_asset = asset.attributes.slice(*%w[
|
17
|
+
id company_id asset_layout_id slug name
|
18
|
+
primary_serial primary_model primary_mail
|
19
|
+
primary_manufacturer
|
20
|
+
]
|
10
21
|
)
|
11
|
-
custom_asset['custom_fields'] =
|
22
|
+
custom_asset['custom_fields'] = custom_fields(asset.fields)
|
12
23
|
{ asset: custom_asset }
|
13
24
|
end
|
14
25
|
|
15
|
-
#
|
16
|
-
|
17
|
-
|
26
|
+
# Creates a new asset from the given layout and fields.
|
27
|
+
#
|
28
|
+
# @param name [String] The name of the new asset.
|
29
|
+
# @param asset_layout_id [Integer] The ID of the asset layout to use.
|
30
|
+
# @param fields [Array<Object>] A collection of field objects representing the asset's custom fields.
|
31
|
+
# @return [Hash] A formatted hash representing the new asset.
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# fields = [Field.new(label: "Warranty", value: "2025"), Field.new(label: "Location", value: "NYC")]
|
35
|
+
# Hudu::AssetHelper.create_asset("New Asset", 10, fields)
|
36
|
+
# # => { asset: { name: "New Asset", asset_layout_id: 10, custom_fields: [...] } }
|
37
|
+
def self.create_asset(name, asset_layout_id, fields)
|
38
|
+
{
|
18
39
|
asset: {
|
19
40
|
name: name,
|
20
41
|
asset_layout_id: asset_layout_id,
|
21
|
-
custom_fields:
|
42
|
+
custom_fields: custom_fields(fields)
|
22
43
|
}
|
23
44
|
}
|
24
45
|
end
|
25
|
-
|
26
|
-
|
46
|
+
|
47
|
+
# Formats custom fields into a standardized hash structure.
|
48
|
+
#
|
49
|
+
# @param fields [Array<Object>] A collection of field objects, each expected to respond to `label` and `value`.
|
50
|
+
# @return [Array<Hash>] An array containing a single hash mapping field labels (downcased and underscored) to their values.
|
51
|
+
#
|
52
|
+
# @example
|
53
|
+
# fields = [Field.new(label: "Warranty", value: "2025"), Field.new(label: "Location", value: "NYC")]
|
54
|
+
# Hudu::AssetHelper.custom_fields(fields)
|
55
|
+
# # => [{ "warranty" => "2025", "location" => "NYC" }]
|
27
56
|
def self.custom_fields(fields)
|
28
|
-
[fields.map{|field| [field.label.downcase.gsub(' ','_'),field.value]}.to_h]
|
57
|
+
[fields.map { |field| [field.label.downcase.gsub(' ', '_'), field.value] }.to_h]
|
29
58
|
end
|
30
|
-
|
31
59
|
end
|
32
|
-
end
|
60
|
+
end
|
data/lib/hudu/authentication.rb
CHANGED
@@ -1,20 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('error', __dir__)
|
2
4
|
|
3
5
|
module Hudu
|
4
6
|
# Deals with authentication flow and stores it within global configuration
|
5
7
|
module Authentication
|
6
|
-
|
7
|
-
#
|
8
|
+
#
|
8
9
|
# Authorize to the Hudu portal and return access_token
|
9
10
|
def login(options = {})
|
10
|
-
raise ArgumentError,
|
11
|
-
|
12
|
-
|
11
|
+
raise ArgumentError, 'Accesstoken/api-key not set' unless api_key
|
12
|
+
|
13
|
+
connection_options.merge!({ headers: { "x-api-key": api_key } })
|
14
|
+
# only api key needed
|
13
15
|
# will do sanitty check if token if valid
|
14
|
-
get('/api/v1/api_info')
|
16
|
+
get('/api/v1/api_info', options)
|
15
17
|
rescue Faraday::Error => e
|
16
18
|
raise AuthenticationError, e
|
17
19
|
end
|
18
|
-
|
19
20
|
end
|
20
21
|
end
|
data/lib/hudu/client.rb
CHANGED
@@ -1,53 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('api', __dir__)
|
2
4
|
require File.expand_path('asset_helper', __dir__)
|
3
5
|
|
4
6
|
module Hudu
|
5
|
-
#
|
7
|
+
# The Client class serves as a wrapper for the Hudu REST API, providing methods to interact
|
8
|
+
# with various Hudu resources.
|
9
|
+
#
|
10
|
+
# This class dynamically defines methods to fetch, create, update, and manipulate Hudu resources
|
11
|
+
# such as companies, articles, assets, and more.
|
6
12
|
#
|
7
|
-
# @
|
13
|
+
# @example Basic Usage
|
14
|
+
# client.companies # Fetch all companies
|
15
|
+
# client.company(1) # Fetch a company by ID
|
16
|
+
# client.update_company(1, { name: "Updated Company" }) # Update a company
|
17
|
+
# client.create_company({ name: "New Company" }) # Create a new company
|
8
18
|
class Client < API
|
9
19
|
|
10
|
-
|
20
|
+
# Dynamically defines methods for interacting with Hudu API resources.
|
21
|
+
#
|
22
|
+
# Depending on the arguments, this will define methods to:
|
23
|
+
# - Fetch all records for a resource
|
24
|
+
# - Fetch a specific record by ID
|
25
|
+
# - Update a record
|
26
|
+
# - Create a new record
|
27
|
+
#
|
28
|
+
# @param method [Symbol] The method name for fetching all records.
|
29
|
+
# @param singular_method [Symbol, nil] The method name for fetching a single record by ID. Optional.
|
30
|
+
# @param path [String] The API path for the resource. Defaults to the method name.
|
31
|
+
#
|
32
|
+
# @example Defining endpoints
|
33
|
+
# api_endpoint :companies, :company
|
34
|
+
# # Defines:
|
35
|
+
# # - `companies(params = {})` to fetch all companies.
|
36
|
+
# # - `company(id, params = {})` to fetch a single company by ID.
|
37
|
+
# # - `update_companies(id, params = {})` to update a company.
|
38
|
+
# # - `create_companies(params = {})` to create a new company.
|
11
39
|
def self.api_endpoint(method, singular_method = nil, path = method)
|
12
|
-
# generate all and by id
|
13
40
|
if singular_method
|
14
|
-
# all records
|
15
|
-
|
41
|
+
# Define method to fetch all records and one by id
|
42
|
+
send(:define_method, method) do |params = {}|
|
16
43
|
r = get_paged(api_url(path), params)
|
17
|
-
|
44
|
+
hudu_data(r, method)
|
18
45
|
end
|
19
|
-
# record by
|
20
|
-
|
46
|
+
# Define method to fetch a single record by ID
|
47
|
+
send(:define_method, singular_method) do |id, params = {}|
|
21
48
|
r = get(api_url("#{path}/#{id}"), params)
|
22
|
-
|
49
|
+
hudu_data(r, singular_method)
|
23
50
|
end
|
24
51
|
else
|
25
|
-
#
|
26
|
-
|
52
|
+
# Define simple method to fetch data
|
53
|
+
send(:define_method, method) do |params = {}|
|
27
54
|
get(api_url(path), params)
|
28
55
|
end
|
29
56
|
end
|
30
|
-
|
31
|
-
|
57
|
+
|
58
|
+
# Define method to update a record
|
59
|
+
send(:define_method, "update_#{method}") do |id = nil, params = {}|
|
32
60
|
r = put(api_url("#{path}/#{id}"), params)
|
33
|
-
|
61
|
+
hudu_data(r, method)
|
34
62
|
end
|
35
|
-
|
36
|
-
|
63
|
+
|
64
|
+
# Define method to create a record
|
65
|
+
send(:define_method, "create_#{method}") do |id = nil, params = {}|
|
37
66
|
r = post(api_url("#{path}/#{id}"), params)
|
38
|
-
|
67
|
+
hudu_data(r, method)
|
39
68
|
end
|
40
|
-
|
41
69
|
end
|
42
70
|
|
43
|
-
|
71
|
+
|
72
|
+
# Define API endpoints for various resources
|
44
73
|
api_endpoint :api_info
|
45
|
-
|
46
|
-
# Activity logs can be filtered on
|
47
|
-
# user_id, user_email
|
48
|
-
# resource_id, resource_type
|
49
|
-
# action_message
|
50
|
-
# start_date - Must be in ISO 8601 format
|
51
74
|
api_endpoint :activity_logs
|
52
75
|
api_endpoint :companies, :company
|
53
76
|
api_endpoint :articles, :article
|
@@ -61,31 +84,66 @@ module Hudu
|
|
61
84
|
api_endpoint :relations
|
62
85
|
api_endpoint :magic_dashes, :magic_dash, 'magic_dash'
|
63
86
|
|
64
|
-
|
65
|
-
|
87
|
+
# Fetches all articles for a specific company.
|
88
|
+
#
|
89
|
+
# @param company_id [Integer] The ID of the company.
|
90
|
+
# @param params [Hash] Additional query parameters.
|
91
|
+
# @return [Array<Hash>] A list of articles.
|
92
|
+
def company_articles(company_id, params = {})
|
93
|
+
articles({ company_id: company_id }.merge(params))
|
66
94
|
end
|
67
|
-
|
95
|
+
|
96
|
+
# Fetches all assets for a specific company.
|
97
|
+
#
|
98
|
+
# @param id [Integer] The ID of the company.
|
99
|
+
# @param params [Hash] Additional query parameters.
|
100
|
+
# @return [Array<Hash>] A list of assets.
|
101
|
+
def company_assets(id, params = {})
|
68
102
|
get_paged(api_url("companies/#{id}/assets"), params)
|
69
103
|
end
|
70
|
-
|
104
|
+
|
105
|
+
# Fetches a specific asset for a company.
|
106
|
+
#
|
107
|
+
# @param company_id [Integer] The ID of the company.
|
108
|
+
# @param asset_id [Integer] The ID of the asset.
|
109
|
+
# @param params [Hash] Additional query parameters.
|
110
|
+
# @return [Hash] The asset details.
|
111
|
+
def company_asset(company_id, asset_id, params = {})
|
71
112
|
get(api_url("companies/#{company_id}/assets/#{asset_id}"), params)
|
72
113
|
end
|
73
114
|
|
115
|
+
# Updates an existing company asset.
|
116
|
+
#
|
117
|
+
# @param asset [Object] The asset object to update.
|
118
|
+
# @return [Hash] The updated asset data.
|
74
119
|
def update_company_asset(asset)
|
75
|
-
hudu_data(put(api_url("companies/#{asset.company_id}/assets/#{asset.id}"), AssetHelper.construct_asset(asset),false)
|
120
|
+
hudu_data(put(api_url("companies/#{asset.company_id}/assets/#{asset.id}"), AssetHelper.construct_asset(asset), false), :asset)
|
76
121
|
end
|
77
122
|
|
78
|
-
|
79
|
-
|
123
|
+
# Creates a new asset for a company.
|
124
|
+
#
|
125
|
+
# @param company_id [Integer] The ID of the company.
|
126
|
+
# @param asset_layout [Object] The asset layout object.
|
127
|
+
# @param fields [Array<Hash>] The custom fields for the asset.
|
128
|
+
# @return [Hash] The newly created asset data.
|
129
|
+
def create_company_asset(company_id, asset_layout, fields)
|
130
|
+
hudu_data(post(api_url("companies/#{company_id}/assets"), AssetHelper.create_asset(asset_layout.name, asset_layout.id, fields), false), :asset)
|
80
131
|
end
|
81
132
|
|
82
|
-
#
|
83
|
-
|
133
|
+
# Constructs the full API URL for a given path.
|
134
|
+
#
|
135
|
+
# @param path [String] The API path.
|
136
|
+
# @return [String] The full API URL.
|
137
|
+
def api_url(path)
|
84
138
|
"/api/v1/#{path}"
|
85
139
|
end
|
86
140
|
|
87
|
-
#
|
88
|
-
|
141
|
+
# Extracts resource data from the API response.
|
142
|
+
#
|
143
|
+
# @param result [Hash, Object] The API response.
|
144
|
+
# @param resource [Symbol] The name of the resource to extract.
|
145
|
+
# @return [Object] The resource data.
|
146
|
+
def hudu_data(result, resource)
|
89
147
|
if result.is_a?(WrAPI::Request::Entity) && result.attributes[resource.to_s]
|
90
148
|
result.send resource.to_s
|
91
149
|
else
|
data/lib/hudu/configuration.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'wrapi'
|
2
4
|
require File.expand_path('version', __dir__)
|
3
5
|
require File.expand_path('pagination', __dir__)
|
@@ -11,11 +13,11 @@ module Hudu
|
|
11
13
|
VALID_OPTIONS_KEYS = (WrAPI::Configuration::VALID_OPTIONS_KEYS + [:api_key]).freeze
|
12
14
|
|
13
15
|
# @private
|
14
|
-
attr_accessor
|
16
|
+
attr_accessor(*VALID_OPTIONS_KEYS)
|
15
17
|
|
16
|
-
DEFAULT_UA = "Ruby Hudu API wrapper #{Hudu::VERSION}"
|
18
|
+
DEFAULT_UA = "Ruby Hudu API wrapper #{Hudu::VERSION}"
|
17
19
|
DEFAULT_PAGINATION = RequestPagination::PagingInfoPager
|
18
|
-
DEFAULT_PAGE_SIZE =
|
20
|
+
DEFAULT_PAGE_SIZE = 100
|
19
21
|
|
20
22
|
# When this module is extended, set all configuration options to their default values
|
21
23
|
def self.extended(base)
|
@@ -40,4 +42,3 @@ module Hudu
|
|
40
42
|
end
|
41
43
|
end
|
42
44
|
end
|
43
|
-
|
data/lib/hudu/connection.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require File.expand_path('rate_throttle_middleware', __dir__)
|
2
4
|
|
3
5
|
module Hudu
|
4
|
-
# Create connection including
|
5
|
-
# By default
|
6
|
-
# - Bearer authorization is access_token is not nil override with @setup_authorization
|
7
|
-
# - Headers setup for client-id and client-secret when client_id and client_secret are not nil @setup_headers
|
8
|
-
# @private
|
6
|
+
# Create connection including and keep it persistent so we add the rate throtling middleware only once
|
9
7
|
module Connection
|
10
8
|
private
|
11
9
|
|
data/lib/hudu/error.rb
CHANGED
data/lib/hudu/pagination.rb
CHANGED
@@ -1,42 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'uri'
|
2
4
|
require 'json'
|
3
5
|
|
4
6
|
module Hudu
|
5
|
-
|
6
7
|
# Defines HTTP request methods
|
7
8
|
# @see https://support.hudu.com/hc/en-us/articles/11422780787735-REST-API#pagination-0-5
|
8
9
|
module RequestPagination
|
9
|
-
|
10
|
+
# The PagingInfoPager class provides a mechanism to handle pagination information for API responses.
|
11
|
+
#
|
12
|
+
# It manages the current page, page size, and provides utilities for determining if there are more pages to fetch.
|
13
|
+
#
|
14
|
+
# @example Basic Usage
|
15
|
+
# pager = PagingInfoPager.new(50)
|
16
|
+
# while pager.more_pages?
|
17
|
+
# response = api_client.get_data(pager.page_options)
|
18
|
+
# pager.next_page!(response.body)
|
19
|
+
# end
|
10
20
|
class PagingInfoPager
|
11
21
|
attr_reader :offset, :limit, :total
|
22
|
+
|
23
|
+
# Initializes a new PagingInfoPager instance.
|
24
|
+
#
|
25
|
+
# @param page_size [Integer] The number of records to fetch per page.
|
12
26
|
def initialize(page_size)
|
13
27
|
@page = 1
|
14
28
|
@page_total = @page_size = page_size
|
15
29
|
end
|
16
30
|
|
31
|
+
# Provides the current pagination parameter options for each rest request.
|
32
|
+
#
|
33
|
+
# @return [Hash] A hash containing the current page and page size.
|
34
|
+
#
|
35
|
+
# @example
|
36
|
+
# pager.page_options # => { page: 1, page_size: 50 }
|
17
37
|
def page_options
|
18
38
|
{ page: @page, page_size: @page_size }
|
19
39
|
end
|
20
40
|
|
41
|
+
# Advances to the next page based on the response body and updates internal pagination state.
|
42
|
+
#
|
43
|
+
# @param body [Hash] The response body from the API, expected to contain a paginated resource.
|
44
|
+
# @return [Integer] The updated page total, typically the count of items on the current page.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# response_body = { "items" => [...] }
|
48
|
+
# pager.next_page!(response_body)
|
21
49
|
def next_page!(body)
|
22
50
|
@page += 1
|
23
51
|
a = PagingInfoPager.data(body)
|
24
52
|
@page_total = a.is_a?(Array) ? a.count : 1
|
25
53
|
end
|
26
54
|
|
55
|
+
# Determines whether there are more pages to fetch.
|
56
|
+
#
|
57
|
+
# @return [Boolean] Returns `true` if the current page is full, indicating another page might exist.
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# pager.more_pages? # => true or false
|
27
61
|
def more_pages?
|
28
62
|
# while full page we have next page
|
29
63
|
@page_total == @page_size
|
30
64
|
end
|
31
65
|
|
32
|
-
|
66
|
+
# Extracts paginated data from the response body.
|
67
|
+
#
|
68
|
+
# @param body [Hash] The response body containing resource data, expected to be in a hash format.
|
69
|
+
# @return [Array, Hash, Object] Returns the extracted data, which could be an array, hash, or other object.
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
# response_body = { "items" => [1, 2, 3] }
|
73
|
+
# PagingInfoPager.data(response_body) # => [1, 2, 3]
|
74
|
+
def self.data(body)
|
33
75
|
# assume hash {"resource":[...]}, get first key and return array data
|
34
|
-
result = body
|
35
|
-
if result
|
36
|
-
|
37
|
-
if v.is_a?(Array) || v.is_a?(Hash)
|
38
|
-
result = v
|
39
|
-
end
|
76
|
+
result = body
|
77
|
+
if result.respond_to?(:first)
|
78
|
+
_k, v = body.first
|
79
|
+
result = v if v.is_a?(Array) || v.is_a?(Hash)
|
40
80
|
end
|
41
81
|
result
|
42
82
|
end
|
@@ -1,67 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'faraday'
|
2
|
-
require 'thread'
|
3
4
|
|
4
|
-
|
5
|
-
#
|
6
|
-
# This middleware ensures that the number of requests made through a Faraday connection
|
7
|
-
# does not exceed a specified limit within a given time period.
|
8
|
-
#
|
9
|
-
# @example Add middleware to a Faraday connection
|
10
|
-
# connection = Faraday.new(url: 'https://api.example.com') do |faraday|
|
11
|
-
# faraday.use RateThrottleMiddleware, limit: 300, period: 60
|
12
|
-
# faraday.adapter Faraday.default_adapter
|
13
|
-
# end
|
14
|
-
#
|
15
|
-
# @see https://github.com/lostisland/faraday Faraday Documentation
|
16
|
-
#
|
17
|
-
class RateThrottleMiddleware < Faraday::Middleware
|
18
|
-
# Initializes the RateThrottleMiddleware.
|
5
|
+
module Hudu
|
6
|
+
# A Faraday middleware for rate limiting requests.
|
19
7
|
#
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# @param period [Integer] The time period in seconds over which the limit applies. Default is 60 seconds.
|
8
|
+
# This middleware ensures that the number of requests made through a Faraday connection
|
9
|
+
# does not exceed a specified limit within a given time period.
|
23
10
|
#
|
24
|
-
# @example
|
25
|
-
#
|
11
|
+
# @example Add middleware to a Faraday connection
|
12
|
+
# connection = Faraday.new(url: 'https://api.example.com') do |faraday|
|
13
|
+
# faraday.use RateThrottleMiddleware, limit: 300, period: 60
|
14
|
+
# faraday.adapter Faraday.default_adapter
|
15
|
+
# end
|
26
16
|
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
@
|
33
|
-
@
|
34
|
-
|
17
|
+
# @see https://github.com/lostisland/faraday Faraday Documentation
|
18
|
+
#
|
19
|
+
class RateThrottleMiddleware < Faraday::Middleware
|
20
|
+
# Initializes the RateThrottleMiddleware.
|
21
|
+
#
|
22
|
+
# @param app [#call] The next middleware or the actual Faraday adapter.
|
23
|
+
# @param limit [Integer] The maximum number of requests allowed within the specified period. Default is 300.
|
24
|
+
# @param period [Integer] The time period in seconds over which the limit applies. Default is 60 seconds.
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# middleware = RateThrottleMiddleware.new(app, limit: 300, period: 60)
|
28
|
+
#
|
29
|
+
def initialize(app, limit: 300, period: 60)
|
30
|
+
super(app)
|
31
|
+
@limit = limit
|
32
|
+
@period = period
|
33
|
+
@requests = []
|
34
|
+
@mutex = Mutex.new
|
35
|
+
@condition = ConditionVariable.new
|
36
|
+
end
|
35
37
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
38
|
+
def call(env)
|
39
|
+
throttle_request
|
40
|
+
@app.call(env)
|
41
|
+
end
|
40
42
|
|
41
|
-
private
|
43
|
+
private
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
def throttle_request
|
46
|
+
@mutex.synchronize do
|
47
|
+
now = Time.now.to_f
|
48
|
+
remove_expired_requests(now)
|
49
|
+
|
50
|
+
rate_limited(now)
|
51
|
+
|
52
|
+
# Record the new request
|
53
|
+
@requests.push(Time.now.to_f)
|
54
|
+
@condition.broadcast
|
49
55
|
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def remove_expired_requests(now)
|
59
|
+
# Clear requests older than the rate limit period
|
60
|
+
@requests.pop while !@requests.empty? && @requests[0] < (now - @period)
|
61
|
+
end
|
50
62
|
|
63
|
+
def rate_limited(now)
|
51
64
|
# Wait if the request limit is reached
|
52
65
|
while @requests.size >= @limit
|
53
66
|
sleep_time = @requests[0] + @period - now
|
54
|
-
|
55
|
-
|
56
|
-
now = Time.now.to_f
|
57
|
-
while !@requests.empty? && @requests[0] < (now - @period)
|
58
|
-
@requests.pop
|
59
|
-
end
|
67
|
+
@condition.wait(@mutex, sleep_time) if sleep_time.positive?
|
68
|
+
remove_expired_requests(Time.now.to_f)
|
60
69
|
end
|
61
|
-
|
62
|
-
# Record the new request
|
63
|
-
@requests.push(Time.now.to_f)
|
64
|
-
@condition.broadcast
|
65
70
|
end
|
66
71
|
end
|
67
72
|
end
|
data/lib/hudu/version.rb
CHANGED
data/lib/hudu.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wrapi'
|
2
4
|
require File.expand_path('hudu/configuration', __dir__)
|
3
5
|
require File.expand_path('hudu/client', __dir__)
|
4
6
|
|
7
|
+
# The Hudu module provides utilities to manage and manipulate assets within the Hudu api
|
5
8
|
module Hudu
|
6
9
|
extend Configuration
|
7
10
|
extend WrAPI::RespondTo
|
@@ -11,9 +14,4 @@ module Hudu
|
|
11
14
|
def self.client(options = {})
|
12
15
|
Hudu::Client.new(options)
|
13
16
|
end
|
14
|
-
|
15
|
-
def self.reset
|
16
|
-
super
|
17
|
-
|
18
|
-
end
|
19
17
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hudu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janco Tanis
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-11-
|
11
|
+
date: 2024-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: faraday
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name:
|
70
|
+
name: rubocop
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
84
|
+
name: simplecov
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|