From 805a37363bd0cac0ee1a5f57133490efb7abe878 Mon Sep 17 00:00:00 2001 From: gngpp Date: Tue, 21 Jul 2026 17:49:20 +0800 Subject: [PATCH] feat(cookie): expose idiomatic Ruby APIs --- lib/wreq_ruby/cookie.rb | 22 ++++++++++++++++++++-- src/cookie.rs | 31 ++++++++++++++++++++++++++----- test/cookie_test.rb | 27 ++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/lib/wreq_ruby/cookie.rb b/lib/wreq_ruby/cookie.rb index 5f004c5..1ec0295 100644 --- a/lib/wreq_ruby/cookie.rb +++ b/lib/wreq_ruby/cookie.rb @@ -3,6 +3,8 @@ module Wreq # Cookie SameSite attribute. # # Values follow the Rust enum exposed by the native extension. + # Constant names mirror the native SameSite variants. + # standard:disable Naming/ConstantName class SameSite # Strict same-site policy. Strict = nil @@ -38,6 +40,7 @@ def eql?(other) def hash end end + # standard:enable Naming/ConstantName # A single HTTP cookie. # @@ -116,6 +119,11 @@ def same_site_lax? def same_site_strict? end + # Returns the SameSite directive, or nil when it is not set. + # @return [Wreq::SameSite, nil] + def same_site + end + # @return [String, nil] Path attribute def path end @@ -142,6 +150,11 @@ def expires_at # @return [Float, nil] def expires end + + # Serializes the cookie as a Set-Cookie string. + # @return [String] + def to_s + end end # A cookie store (jar) used by the client to manage cookies across requests. @@ -156,10 +169,11 @@ def self.new def get_all end - # Add a cookie from a Set-Cookie string for the given URL. - # @param cookie [String, Wreq::Cookie] A Set-Cookie string + # Add a Cookie object or Set-Cookie string for the given URL. + # @param cookie [String, Wreq::Cookie] A Set-Cookie string or Cookie object # @param url [String] # @return [void] + # @raise [TypeError] if cookie is neither a String nor Wreq::Cookie def add(cookie, url) end @@ -180,6 +194,8 @@ def clear module Wreq class Cookie + # Returns a compact representation for debugging. + # @return [String] def inspect parts = ["#" end diff --git a/src/cookie.rs b/src/cookie.rs index d8ed256..b4110c0 100644 --- a/src/cookie.rs +++ b/src/cookie.rs @@ -184,6 +184,12 @@ impl Cookie { self.0.same_site() == Some(cookie::SameSite::Strict) } + /// Return the SameSite directive, if set. + #[inline] + pub fn same_site(&self) -> Option { + self.0.same_site().map(SameSite::from_ffi) + } + /// Returns the path directive of the cookie, if set. #[inline] pub fn path(&self) -> Option<&str> { @@ -216,6 +222,12 @@ impl Cookie { pub fn expires(&self) -> Option { self.0.expires_datetime().map(to_unix_timestamp) } + + /// Serialize the cookie as a Set-Cookie string. + #[inline] + pub fn to_s(&self) -> String { + self.to_string() + } } // ===== Native Cookie helpers ===== @@ -318,14 +330,21 @@ impl Jar { } /// Add a cookie to this jar. - pub fn add(&self, cookie: Value, url: String) { + /// + /// # Errors + /// + /// Returns `TypeError` when `cookie` is neither a [`Cookie`] nor a String. + pub fn add(&self, cookie: Value, url: String) -> Result<(), Error> { if let Ok(cookie) = Obj::::try_convert(cookie) { - return gvl::nogvl(|| self.0.add(cookie.clone_for_jar(), &url)); + gvl::nogvl(|| self.0.add(cookie.clone_for_jar(), &url)); + return Ok(()); } - if let Ok(cookie_str) = String::try_convert(cookie) { - gvl::nogvl(|| self.0.add(cookie_str.as_ref(), &url)) - } + let ruby = Ruby::get_with(cookie); + let cookie = String::try_convert(cookie) + .map_err(|_| type_error(&ruby, "cookie must be a Wreq::Cookie or String"))?; + gvl::nogvl(|| self.0.add(cookie.as_ref(), &url)); + Ok(()) } /// Remove a cookie from this jar by name and URL. @@ -466,11 +485,13 @@ pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> { cookie_class.define_method("secure?", method!(Cookie::secure, 0))?; cookie_class.define_method("same_site_lax?", method!(Cookie::same_site_lax, 0))?; cookie_class.define_method("same_site_strict?", method!(Cookie::same_site_strict, 0))?; + cookie_class.define_method("same_site", method!(Cookie::same_site, 0))?; cookie_class.define_method("path", method!(Cookie::path, 0))?; cookie_class.define_method("domain", method!(Cookie::domain, 0))?; cookie_class.define_method("max_age", method!(Cookie::max_age, 0))?; cookie_class.define_method("expires_at", method!(Cookie::expires_at, 0))?; cookie_class.define_method("expires", method!(Cookie::expires, 0))?; + cookie_class.define_method("to_s", method!(Cookie::to_s, 0))?; // Jar class let jar_class = gem_module.define_class("Jar", ruby.class_object())?; diff --git a/test/cookie_test.rb b/test/cookie_test.rb index f24ac51..53ed157 100644 --- a/test/cookie_test.rb +++ b/test/cookie_test.rb @@ -44,6 +44,14 @@ def test_add_and_get_all assert_equal true, (c.secure || c.secure?) end + def test_add_rejects_invalid_cookie_type + error = assert_raises(TypeError) do + @jar.add(Object.new, @base_url) + end + + assert_equal "cookie must be a Wreq::Cookie or String", error.message + end + def test_add_multiple_and_remove @jar.add("a=1; Path=/", @base_url) @jar.add("b=2; Path=/", @base_url) @@ -115,6 +123,8 @@ def test_cookie_new_minimal assert_equal false, (c.secure || c.secure?) assert_equal false, c.same_site_lax? assert_equal false, c.same_site_strict? + assert_nil c.same_site + assert_equal "sid=abc", c.to_s end def test_cookie_new_full_attributes @@ -144,6 +154,15 @@ def test_cookie_new_full_attributes assert_equal true, (c.secure || c.secure?) assert_equal true, c.same_site_lax? assert_equal false, c.same_site_strict? + assert_equal Wreq::SameSite::Lax, c.same_site + end + + def test_same_site_reader_distinguishes_none_from_unset + cookie = Wreq::Cookie.new("same-site", "value", same_site: Wreq::SameSite::None) + + assert_equal Wreq::SameSite::None, cookie.same_site + refute cookie.same_site_lax? + refute cookie.same_site_strict? end def test_cookie_new_uses_shared_keyword_validation @@ -328,10 +347,12 @@ def test_same_site_flags_from_parsed_header @jar.add("s2=1; Path=/; SameSite=Lax", @base_url) cookies = @jar.get_all - h = cookies.to_h { |ck| [ck.name, [ck.same_site_strict?, ck.same_site_lax?]] } + h = cookies.to_h do |cookie| + [cookie.name, [cookie.same_site, cookie.same_site_strict?, cookie.same_site_lax?]] + end - assert_equal [true, false], h["s1"] - assert_equal [false, true], h["s2"] + assert_equal [Wreq::SameSite::Strict, true, false], h["s1"] + assert_equal [Wreq::SameSite::Lax, false, true], h["s2"] end def test_request_uncompressed_cookies