Livecheck::Options: Rework as T::Struct

As suggested, this reworks `Options` to subclass `T::Struct`, which
simplifies the implementation and makes it easier to maintain.

One noteworthy difference in switching to `T::Struct` is that
`#serialize` omits `nil` values but I don't _think_ this should be a
problem for us. In terms of changes, I modified `#merge` to remove a
now-unnecessary `compact` call and updated related tests.

Co-authored-by: Douglas Eichelberger <697964+dduugg@users.noreply.github.com>
This commit is contained in:
Sam Ford 2025-02-13 21:32:51 -05:00
parent b6eb945320
commit 8afa354c35
No known key found for this signature in database
GPG Key ID: 7AF5CBEE1DD6F76D
3 changed files with 29 additions and 64 deletions

View File

@ -179,13 +179,9 @@ class Livecheck
def url(url = T.unsafe(nil), homebrew_curl: nil, post_form: nil, post_json: nil) def url(url = T.unsafe(nil), homebrew_curl: nil, post_form: nil, post_json: nil)
raise ArgumentError, "Only use `post_form` or `post_json`, not both" if post_form && post_json raise ArgumentError, "Only use `post_form` or `post_json`, not both" if post_form && post_json
if homebrew_curl || post_form || post_json @options.homebrew_curl = homebrew_curl unless homebrew_curl.nil?
@options = @options.merge({ @options.post_form = post_form unless post_form.nil?
homebrew_curl:, @options.post_json = post_json unless post_json.nil?
post_form:,
post_json:,
}.compact)
end
case url case url
when nil when nil

View File

@ -8,34 +8,15 @@ module Homebrew
# #
# Option values use a `nil` default to indicate that the value has not been # Option values use a `nil` default to indicate that the value has not been
# set. # set.
class Options class Options < T::Struct
# Whether to use brewed curl. # Whether to use brewed curl.
sig { returns(T.nilable(T::Boolean)) } prop :homebrew_curl, T.nilable(T::Boolean)
attr_reader :homebrew_curl
# Form data to use when making a `POST` request. # Form data to use when making a `POST` request.
sig { returns(T.nilable(T::Hash[Symbol, String])) } prop :post_form, T.nilable(T::Hash[Symbol, String])
attr_reader :post_form
# JSON data to use when making a `POST` request. # JSON data to use when making a `POST` request.
sig { returns(T.nilable(T::Hash[Symbol, String])) } prop :post_json, T.nilable(T::Hash[Symbol, String])
attr_reader :post_json
# @param homebrew_curl whether to use brewed curl
# @param post_form form data to use when making a `POST` request
# @param post_json JSON data to use when making a `POST` request
sig {
params(
homebrew_curl: T.nilable(T::Boolean),
post_form: T.nilable(T::Hash[Symbol, String]),
post_json: T.nilable(T::Hash[Symbol, String]),
).void
}
def initialize(homebrew_curl: nil, post_form: nil, post_json: nil)
@homebrew_curl = homebrew_curl
@post_form = post_form
@post_json = post_json
end
# Returns a `Hash` of options that are provided as arguments to `url`. # Returns a `Hash` of options that are provided as arguments to `url`.
sig { returns(T::Hash[Symbol, T.untyped]) } sig { returns(T::Hash[Symbol, T.untyped]) }
@ -47,25 +28,13 @@ module Homebrew
} }
end end
# Returns a `Hash` of all instance variables, using `Symbol` keys.
sig { returns(T::Hash[Symbol, T.untyped]) }
def to_h
{
homebrew_curl:,
post_form:,
post_json:,
}
end
# Returns a `Hash` of all instance variables, using `String` keys. # Returns a `Hash` of all instance variables, using `String` keys.
sig { returns(T::Hash[String, T.untyped]) } sig { returns(T::Hash[String, T.untyped]) }
def to_hash def to_hash = serialize
{
"homebrew_curl" => @homebrew_curl, # Returns a `Hash` of all instance variables, using `Symbol` keys.
"post_form" => @post_form, sig { returns(T::Hash[Symbol, T.untyped]) }
"post_json" => @post_json, def to_h = serialize.transform_keys(&:to_sym)
}
end
# Returns a new object formed by merging `other` values with a copy of # Returns a new object formed by merging `other` values with a copy of
# `self`. # `self`.
@ -78,7 +47,7 @@ module Homebrew
return dup if other.empty? return dup if other.empty?
this_hash = to_h this_hash = to_h
other_hash = other.is_a?(Options) ? other.to_h.compact : other other_hash = other.is_a?(Options) ? other.to_h : other
return dup if this_hash == other_hash return dup if this_hash == other_hash
new_options = this_hash.merge(other_hash) new_options = this_hash.merge(other_hash)
@ -96,15 +65,11 @@ module Homebrew
# Whether the object has only default values. # Whether the object has only default values.
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def empty? def empty? = serialize.empty?
@homebrew_curl.nil? && @post_form.nil? && @post_json.nil?
end
# Whether the object has any non-default values. # Whether the object has any non-default values.
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def present? def present? = !empty?
!@homebrew_curl.nil? || !@post_form.nil? || !@post_json.nil?
end
end end
end end
end end

View File

@ -39,21 +39,19 @@ RSpec.describe Homebrew::Livecheck::Options do
describe "#to_h" do describe "#to_h" do
it "returns a Hash of all instance variables" do it "returns a Hash of all instance variables" do
expect(options.new.to_h).to eq({ # `T::Struct.serialize` omits `nil` values
homebrew_curl: nil, expect(options.new.to_h).to eq({})
post_form: nil,
post_json: nil, expect(options.new(**args).to_h).to eq(args)
})
end end
end end
describe "#to_hash" do describe "#to_hash" do
it "returns a Hash of all instance variables, using String keys" do it "returns a Hash of all instance variables, using String keys" do
expect(options.new.to_hash).to eq({ # `T::Struct.serialize` omits `nil` values
"homebrew_curl" => nil, expect(options.new.to_hash).to eq({})
"post_form" => nil,
"post_json" => nil, expect(options.new(**args).to_hash).to eq(args.transform_keys(&:to_s))
})
end end
end end
@ -65,6 +63,8 @@ RSpec.describe Homebrew::Livecheck::Options do
.to eq(options.new(**merged_hash)) .to eq(options.new(**merged_hash))
expect(options.new(**args).merge(args)) expect(options.new(**args).merge(args))
.to eq(options.new(**args)) .to eq(options.new(**args))
expect(options.new(**args).merge({}))
.to eq(options.new(**args))
end end
end end
@ -82,6 +82,10 @@ RSpec.describe Homebrew::Livecheck::Options do
it "returns false if any instance variables differ" do it "returns false if any instance variables differ" do
expect(options.new == options.new(**args)).to be false expect(options.new == options.new(**args)).to be false
end end
it "returns false if other object is not the same class" do
expect(options.new == :other).to be false
end
end end
describe "#empty?" do describe "#empty?" do