httpdisk 0.2.0 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d311812fa4d8d034c9eda6b9b18df36e21eb14ebc9d632cd1fe071268ca77786
4
- data.tar.gz: 90e891451c1805d6d8ba5ad268bc2682528954a2a8acd37546f69ab43d493311
3
+ metadata.gz: 0e3b6af20bf5a6b76a23a6d170328c040cf3f0715e012c392739b70ab6911bee
4
+ data.tar.gz: 46385301b89bc19f2e8862bcc9bf48b0f1b14fafbd2fa4fc8542b7bf4dcfe930
5
5
  SHA512:
6
- metadata.gz: c61e162e26d8a7b86fe00165e095d72f99415e8e5a4245513758731a009186999d5cf171db45c8b83115dae8c376e8391aa0bfbd00d3a2fd086c0451f77269ad
7
- data.tar.gz: 1ad5bdb0a51f2b84822a6164379a724a7b03338b30f9c427b956bbe4263b0326720aea3bcb02b1fa37110a8aa5adc24dc8f3f257733b11667df913b863da0d52
6
+ metadata.gz: c4b163648a7adf00ae2ae103ccef899f5a3b958d8e38eeb56fc0d609f0bae73a19eed4fb7d5921c583b4f6d2755bd70d5cf1da748a6d8e858f5195da1683d009
7
+ data.tar.gz: 3270ce507204023e25451048fd0ddecc713ce6c637abfe795c2f159d619328892393bb8fa73249f08199a30e9db895fb2e2b6c440bfae0dcf07abd277851d050
data/.rubocop.yml CHANGED
@@ -2,14 +2,19 @@ AllCops:
2
2
  NewCops: enable
3
3
  SuggestExtensions: false
4
4
 
5
+ # this is buggy in 2.7.0
6
+ Style/HashTransformValues: { Enabled: false }
7
+
5
8
  # minimal personal preference
6
9
  Layout/CaseIndentation: { Enabled: false }
7
10
  Layout/EndAlignment: { EnforcedStyleAlignWith: variable }
8
11
  Lint/AssignmentInCondition: { Enabled: false }
12
+ Lint/NonLocalExitFromIterator: { Enabled: false }
9
13
  Metrics: { Enabled: false }
10
14
  Naming/MethodParameterName: { Enabled: false }
11
15
  Naming/VariableNumber: { Enabled: false }
12
16
  Style/Documentation: { Enabled: false }
17
+ Style/DoubleNegation: { Enabled: false }
13
18
  Style/FrozenStringLiteralComment: { Enabled: false }
14
19
  Style/IfUnlessModifier: { Enabled: false }
15
20
  Style/NegatedIf: { Enabled: false }
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- httpdisk (0.2.0)
4
+ httpdisk (0.3.0)
5
5
  faraday (~> 1.4)
6
6
  faraday-cookie_jar (~> 0.0)
7
7
  faraday_middleware (~> 1.0)
@@ -63,7 +63,7 @@ GEM
63
63
  parser (>= 3.0.1.1)
64
64
  ruby-progressbar (1.11.0)
65
65
  ruby2_keywords (0.0.4)
66
- slop (4.8.2)
66
+ slop (4.9.0)
67
67
  unf (0.1.4)
68
68
  unf_ext
69
69
  unf_ext (0.0.7.7)
data/README.md CHANGED
@@ -128,6 +128,7 @@ httpdisk supports a few options:
128
128
  - `expires_in:` when to expire cached requests, default is nil (never expire)
129
129
  - `force:` don't read anything from cache (but still write)
130
130
  - `force_errors:` don't read errors from cache (but still write)
131
+ - `ignore_params:` array of query params to ignore when calculating cache_key
131
132
  - `logger`: log requests to stderr, or pass your own logger
132
133
 
133
134
  Pass these in when setting up Faraday:
@@ -176,10 +177,17 @@ Specific to httpdisk:
176
177
 
177
178
  ## Changelog
178
179
 
180
+ #### 0.3 (unreleased)
181
+
182
+ - added :ignore_params, for ignoring query params when generating cache keys
183
+ - HTTP 40x & 50x responses return :error status (and respond to `force_error`)
184
+
179
185
  #### 0.2 - May 2020
186
+
180
187
  - added `response.env[:httpdisk]`, which will be true if the response came from the cache
181
- - `:logger` option
188
+ - added `:logger` option
182
189
  - rake rubocop
183
190
 
184
191
  #### 0.1 - April 2020
192
+
185
193
  - Original release
data/Rakefile CHANGED
@@ -13,9 +13,10 @@ spec = Gem::Specification.load('httpdisk.gemspec')
13
13
  Rake::TestTask.new { _1.libs << 'test' }
14
14
  task default: :test
15
15
 
16
- # Watch files, run tests whenever something changes
16
+ # Watch rb files, run tests whenever something changes
17
17
  task :watch do
18
- system('find . | entr -c rake test')
18
+ # https://superuser.com/a/665208 / https://unix.stackexchange.com/a/42288
19
+ system("while true; do find . -name '*.rb' | entr -c -d rake; test $? -gt 128 && break; done")
19
20
  end
20
21
 
21
22
  #
@@ -31,7 +32,7 @@ end
31
32
  #
32
33
 
33
34
  task :rubocop do
34
- system('bundle exec rubocop -A .', exception: true)
35
+ sh 'bundle exec rubocop -A .'
35
36
  end
36
37
 
37
38
  #
@@ -39,17 +40,17 @@ end
39
40
  #
40
41
 
41
42
  task :build do
42
- system('gem build --quiet httpdisk.gemspec', exception: true)
43
+ sh 'gem build --quiet httpdisk.gemspec'
43
44
  end
44
45
 
45
46
  task install: :build do
46
- system("gem install --quiet httpdisk-#{spec.version}.gem", exception: true)
47
+ sh "gem install --quiet httpdisk-#{spec.version}.gem"
47
48
  end
48
49
 
49
- task release: %i[test build] do
50
+ task release: %i[rubocop test build] do
50
51
  raise "looks like git isn't clean" unless `git status --porcelain`.empty?
51
52
 
52
- system("git tag -a #{spec.version} -m 'Tagging #{spec.version}'", exception: true)
53
- system('git push --tags', exception: true)
54
- system("gem push httpdisk-#{spec.version}.gem", exception: true)
53
+ sh "git tag -a #{spec.version} -m 'Tagging #{spec.version}'"
54
+ sh 'git push --tags'
55
+ sh "gem push httpdisk-#{spec.version}.gem"
55
56
  end
data/lib/httpdisk.rb CHANGED
@@ -5,6 +5,7 @@ require 'httpdisk/cli'
5
5
  require 'httpdisk/client'
6
6
  require 'httpdisk/error'
7
7
  require 'httpdisk/payload'
8
+ require 'httpdisk/sloptions'
8
9
  require 'httpdisk/version'
9
10
 
10
11
  module HTTPDisk
@@ -7,21 +7,6 @@ module HTTPDisk
7
7
 
8
8
  def initialize(options)
9
9
  @options = options
10
-
11
- # heavy sanity checking on arguments here
12
- if !dir.is_a?(String)
13
- raise ArgumentError, "expected :dir to be a string, not #{dir.inspect}"
14
- end
15
- if expires_in && !expires_in.is_a?(Integer)
16
- raise ArgumentError, "expected :expires_in to be an integer, not #{expires_in.inspect}"
17
- end
18
-
19
- %i[force force_errors].each do
20
- value = send(_1)
21
- if ![nil, true, false].include?(value)
22
- raise ArgumentError, "expected #{_1} to be a boolean, not #{value.inspect}"
23
- end
24
- end
25
10
  end
26
11
 
27
12
  %i[dir expires_in force force_errors].each do |method|
@@ -43,7 +28,7 @@ module HTTPDisk
43
28
  payload_or_status = read0(cache_key, peek: true)
44
29
  return payload_or_status if payload_or_status.is_a?(Symbol)
45
30
 
46
- payload_or_status.error_999? ? :error : :hit
31
+ payload_or_status.error? ? :error : :hit
47
32
  end
48
33
 
49
34
  # Write response to the disk cache
@@ -69,7 +54,7 @@ module HTTPDisk
69
54
  return :force if force?
70
55
 
71
56
  payload = Zlib::GzipReader.open(path) { Payload.read(_1, peek: peek) }
72
- return :force if force_errors? && payload.error_999?
57
+ return :force if force_errors? && payload.error?
73
58
 
74
59
  payload
75
60
  end
@@ -4,10 +4,10 @@ require 'uri'
4
4
 
5
5
  module HTTPDisk
6
6
  class CacheKey
7
- attr_reader :env
7
+ attr_reader :env, :ignore_params
8
8
 
9
- def initialize(env)
10
- @env = env
9
+ def initialize(env, ignore_params: [])
10
+ @env, @ignore_params = env, ignore_params
11
11
 
12
12
  # sanity checks
13
13
  raise 'http/https required' if env.url.scheme !~ /^https?$/
@@ -79,7 +79,16 @@ module HTTPDisk
79
79
 
80
80
  # Calculate canonical key for a query
81
81
  def querykey(q)
82
- q.split('&').sort.join('&')
82
+ parts = q.split('&').sort
83
+ if !ignore_params.empty?
84
+ parts = parts.map do |part|
85
+ key, value = part.split('=', 2)
86
+ next if ignore_params.include?(key)
87
+
88
+ "#{key}=#{value}"
89
+ end.compact
90
+ end
91
+ parts.join('&')
83
92
  end
84
93
 
85
94
  def default_port?
@@ -2,25 +2,26 @@ require 'faraday'
2
2
  require 'logger'
3
3
 
4
4
  module HTTPDisk
5
- OPTIONS = {
6
- dir: File.join(ENV['HOME'], 'httpdisk'),
7
- expires_in: nil,
8
- force: false,
9
- force_errors: false,
10
- logger: false,
11
- }.freeze
12
-
13
5
  # Middleware and main entry point.
14
6
  class Client < Faraday::Middleware
15
7
  attr_reader :cache, :options
16
8
 
17
9
  def initialize(app, options = {})
18
- super(app, options = OPTIONS.merge(options.compact))
10
+ options = Sloptions.parse(options) do
11
+ _1.string :dir, default: File.join(ENV['HOME'], 'httpdisk')
12
+ _1.integer :expires_in
13
+ _1.boolean :force
14
+ _1.boolean :force_errors
15
+ _1.array :ignore_params, default: []
16
+ _1.on :logger, type: [:boolean, Logger]
17
+ end
18
+
19
+ super(app, options)
19
20
  @cache = Cache.new(options)
20
21
  end
21
22
 
22
23
  def call(env)
23
- cache_key = CacheKey.new(env)
24
+ cache_key = CacheKey.new(env, ignore_params: ignore_params)
24
25
  logger&.info("#{env.method.upcase} #{env.url} (#{cache.status(cache_key)})")
25
26
 
26
27
  if cached_response = read(cache_key, env)
@@ -100,10 +101,18 @@ module HTTPDisk
100
101
  err.to_s =~ /#{proxy.host}.*#{proxy.port}/
101
102
  end
102
103
 
104
+ #
105
+ # options
106
+ #
107
+
108
+ def ignore_params
109
+ @ignore_params ||= options[:ignore_params].map { CGI.escape(_1.to_s) }.to_set
110
+ end
111
+
103
112
  def logger
104
- return @logger if defined?(@logger)
113
+ return if !options[:logger]
105
114
 
106
- @logger = case options[:logger]
115
+ @logger ||= case options[:logger]
107
116
  when true then Logger.new($stderr)
108
117
  when Logger then options[:logger]
109
118
  end
@@ -39,8 +39,8 @@ module HTTPDisk
39
39
  @headers = Faraday::Utils::Headers.new
40
40
  end
41
41
 
42
- def error_999?
43
- status == HTTPDisk::ERROR_STATUS
42
+ def error?
43
+ status >= 400
44
44
  end
45
45
 
46
46
  def write(f)
@@ -0,0 +1,105 @@
1
+ module HTTPDisk
2
+ # Like Slop, but for sanity checking method options. Useful for library entry
3
+ # points that want to be strict. Example usage:
4
+ #
5
+ # options = Sloptions.new(options) do
6
+ # _1.boolean :force
7
+ # _1.integer :retries, required: true
8
+ # _1.string :hello, default: 'world'
9
+ # ...
10
+ # end
11
+ class Sloptions
12
+ attr_reader :flags
13
+
14
+ def self.parse(options, &block)
15
+ Sloptions.new(&block).parse(options)
16
+ end
17
+
18
+ def initialize
19
+ @flags = {}
20
+ yield(self)
21
+ end
22
+
23
+ #
24
+ # _1.on and friends
25
+ #
26
+
27
+ def on(flag, foptions = {})
28
+ raise ":#{flag} already defined" if flags[flag]
29
+
30
+ flags[flag] = foptions
31
+ end
32
+
33
+ %i[array boolean float hash integer string symbol].each do |method|
34
+ define_method(method) do |flag, foptions = {}|
35
+ on(flag, { type: method }.merge(foptions))
36
+ end
37
+ end
38
+ alias bool boolean
39
+
40
+ #
41
+ # return parsed options
42
+ #
43
+
44
+ def parse(options)
45
+ # defaults
46
+ options = defaults.merge(options.compact)
47
+
48
+ flags.each do |flag, foptions|
49
+ # nil check
50
+ value = options[flag]
51
+ if value.nil?
52
+ raise ArgumentError, ":#{flag} is required" if foptions[:required]
53
+
54
+ next
55
+ end
56
+
57
+ # type cast (for boolean)
58
+ if foptions[:type] == :boolean
59
+ value = options[flag] = !!options[flag]
60
+ end
61
+
62
+ # type check
63
+ types = Array(foptions[:type])
64
+ raise ArgumentError, error_message(flag, value, types) if !valid?(value, types)
65
+ end
66
+
67
+ # return
68
+ options
69
+ end
70
+
71
+ protected
72
+
73
+ def defaults
74
+ flags.map { |flag, foptions| [flag, foptions[:default]] }.to_h.compact
75
+ end
76
+
77
+ # does value match valid?
78
+ def valid?(value, types)
79
+ types.any? do
80
+ case _1
81
+ when :array then true if value.is_a?(Array)
82
+ when :boolean then true # in Ruby everything is a boolean
83
+ when :float then true if value.is_a?(Float) || value.is_a?(Integer)
84
+ when :hash then true if value.is_a?(Hash)
85
+ when :integer then true if value.is_a?(Integer)
86
+ when :string then true if value.is_a?(String)
87
+ when :symbol then true if value.is_a?(Symbol)
88
+ when Class then true if value.is_a?(_1) # for custom checks
89
+ else
90
+ raise "unknown flag type #{_1.inspect}"
91
+ end
92
+ end
93
+ end
94
+
95
+ # nice error message for when value is invalid
96
+ def error_message(flag, value, valid)
97
+ classes = valid.compact.map do
98
+ s = _1.to_s
99
+ s = s.downcase if s =~ /\b(Array|Float|Hash|Integer|String|Symbol)\b/
100
+ s
101
+ end.join('/')
102
+ "expected :#{flag} to be #{classes}, not #{value.inspect}"
103
+ end
104
+ end
105
+ end
@@ -1,3 +1,3 @@
1
1
  module HTTPDisk
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: httpdisk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Doppelt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-05-03 00:00:00.000000000 Z
11
+ date: 2021-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -92,6 +92,7 @@ files:
92
92
  - lib/httpdisk/client.rb
93
93
  - lib/httpdisk/error.rb
94
94
  - lib/httpdisk/payload.rb
95
+ - lib/httpdisk/sloptions.rb
95
96
  - lib/httpdisk/version.rb
96
97
  - logo.svg
97
98
  homepage: http://github.com/gurgeous/httpdisk