diff --git a/lib/wreq.rb b/lib/wreq.rb index f577e08..da498ff 100644 --- a/lib/wreq.rb +++ b/lib/wreq.rb @@ -24,7 +24,7 @@ module Wreq # # @param method [Wreq::Method] HTTP method to use # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -55,7 +55,7 @@ def self.request(method, url, **options) # Send an HTTP GET request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -86,7 +86,7 @@ def self.get(url, **options) # Send an HTTP HEAD request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -117,7 +117,7 @@ def self.head(url, **options) # Send an HTTP POST request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -148,7 +148,7 @@ def self.post(url, **options) # Send an HTTP PUT request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -179,7 +179,7 @@ def self.put(url, **options) # Send an HTTP DELETE request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -210,7 +210,7 @@ def self.delete(url, **options) # Send an HTTP OPTIONS request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -241,7 +241,7 @@ def self.options(url, **options) # Send an HTTP TRACE request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -272,7 +272,7 @@ def self.trace(url, **options) # Send an HTTP PATCH request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters diff --git a/lib/wreq_ruby/client.rb b/lib/wreq_ruby/client.rb index 6eb4368..8f4771a 100644 --- a/lib/wreq_ruby/client.rb +++ b/lib/wreq_ruby/client.rb @@ -35,7 +35,7 @@ class Client # @param user_agent [String, nil] Custom User-Agent header value. # If not specified, a default user agent will be used. # - # @param headers [Hash{String=>String}, nil] Default headers to include + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Default headers to include # in every request. Header names are case-insensitive. These headers # can be overridden on a per-request basis. # @@ -236,7 +236,7 @@ def self.new(**options) # # @param method [Wreq::Method] HTTP method to use # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -267,7 +267,7 @@ def request(method, url, **options) # Send an HTTP GET request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -298,7 +298,7 @@ def get(url, **options) # Send an HTTP HEAD request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -329,7 +329,7 @@ def head(url, **options) # Send an HTTP POST request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -360,7 +360,7 @@ def post(url, **options) # Send an HTTP PUT request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -391,7 +391,7 @@ def put(url, **options) # Send an HTTP DELETE request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -422,7 +422,7 @@ def delete(url, **options) # Send an HTTP OPTIONS request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -453,7 +453,7 @@ def options(url, **options) # Send an HTTP TRACE request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters @@ -484,7 +484,7 @@ def trace(url, **options) # Send an HTTP PATCH request. # # @param url [String] Target URL - # @param headers [Hash{String=>String}, nil] Custom headers for this request + # @param headers [Wreq::Headers, Hash{String=>String}, nil] Custom headers for this request # @param orig_headers [Hash{String=>String}, nil] Original headers (raw, unmodified) # @param default_headers [Hash{String=>String}, nil] Default headers to merge # @param query [Hash, nil] URL query parameters diff --git a/src/client.rs b/src/client.rs index 421f122..e945bfa 100644 --- a/src/client.rs +++ b/src/client.rs @@ -12,7 +12,7 @@ use magnus::{ use serde::Deserialize; use wreq::{ Proxy, - header::{HeaderMap, HeaderValue, OrigHeaderMap}, + header::{HeaderValue, OrigHeaderMap}, }; use crate::{ @@ -22,6 +22,7 @@ use crate::{ error::wreq_error_to_magnus, extractor::Extractor, gvl, + header::Headers, http::Method, }; @@ -36,7 +37,7 @@ struct Builder { user_agent: Option, /// The headers to use for the client. #[serde(skip)] - headers: Option, + headers: Option, /// The original headers to use for the client. #[serde(skip)] orig_headers: Option, @@ -128,35 +129,33 @@ pub struct Client(wreq::Client); impl Builder { /// Create a new [`Builder`] from Ruby keyword arguments. fn new(ruby: &magnus::Ruby, keyword: &Value) -> Result { - if let Ok(hash) = RHash::try_convert(*keyword) { - let mut builder: Self = serde_magnus::deserialize(ruby, hash)?; - // extra emulation handling - if let Some(v) = hash.get(ruby.to_symbol(stringify!(emulation))) { - let emulation_obj = Obj::::try_convert(v)?; - builder.emulation = Some((*emulation_obj).clone()); - } + let Ok(hash) = RHash::try_convert(*keyword) else { + return Ok(Default::default()); + }; - // extra user agent handling - builder.user_agent = Extractor::::try_convert(*keyword)?.into_inner(); + let mut builder: Self = serde_magnus::deserialize(ruby, hash)?; - // extra headers handling - builder.headers = Extractor::::try_convert(*keyword)?.into_inner(); - - // extra original headers handling - builder.orig_headers = Extractor::::try_convert(*keyword)?.into_inner(); + if let Some(v) = hash.get(ruby.to_symbol(stringify!(emulation))) { + builder.emulation = Some((*Obj::::try_convert(v)?).clone()); + } - // extra proxy handling - builder.proxy = Extractor::::try_convert(*keyword)?.into_inner(); + if let Some(v) = hash.get(ruby.to_symbol(stringify!(headers))) { + builder.headers = Some(Headers::try_convert(v)?); + } - // extra cookie store handling - if let Some(jar) = hash.get(ruby.to_symbol(stringify!(cookie_provider))) { - builder.cookie_provider = Some((*Obj::::try_convert(jar)?).clone()); - } + if let Some(v) = hash.get(ruby.to_symbol(stringify!(cookie_provider))) { + builder.cookie_provider = Some((*Obj::::try_convert(v)?).clone()); + } - return Ok(builder); + if let Some(jar) = hash.get(ruby.to_symbol(stringify!(cookie_provider))) { + builder.cookie_provider = Some((*Obj::::try_convert(jar)?).clone()); } - Ok(Default::default()) + builder.user_agent = Extractor::::try_convert(*keyword)?.into_inner(); + builder.orig_headers = Extractor::::try_convert(*keyword)?.into_inner(); + builder.proxy = Extractor::::try_convert(*keyword)?.into_inner(); + + Ok(builder) } } @@ -164,9 +163,9 @@ impl Builder { impl Client { /// Create a new [`Client`] with the given keyword arguments. - pub fn new(ruby: &Ruby, kwargs: &[Value]) -> Result { - if let Some(kwargs) = kwargs.first() { - let mut params = Builder::new(ruby, kwargs)?; + pub fn new(ruby: &Ruby, keyword: &[Value]) -> Result { + if let Some(keyword) = keyword.first() { + let mut params = Builder::new(ruby, keyword)?; gvl::nogvl(|| { let mut builder = wreq::Client::builder(); @@ -177,7 +176,12 @@ impl Client { apply_option!(set_if_some, builder, params.user_agent, user_agent); // Default headers options. - apply_option!(set_if_some, builder, params.headers, default_headers); + apply_option!( + set_if_some_into_inner, + builder, + params.headers, + default_headers + ); apply_option!(set_if_some, builder, params.orig_headers, orig_headers); // Allow redirects options. diff --git a/src/client/req.rs b/src/client/req.rs index 20d8ea2..002a338 100644 --- a/src/client/req.rs +++ b/src/client/req.rs @@ -3,10 +3,7 @@ use std::{net::IpAddr, time::Duration}; use http::header; use magnus::{RHash, TryConvert, typed_data::Obj, value::ReprValue}; use serde::Deserialize; -use wreq::{ - Client, Proxy, Version, - header::{HeaderMap, OrigHeaderMap}, -}; +use wreq::{Client, Proxy, Version, header::OrigHeaderMap}; use super::body::{Body, Form, Json}; use crate::{ @@ -15,6 +12,7 @@ use crate::{ emulate::Emulation, error::wreq_error_to_magnus, extractor::Extractor, + header::Headers, http::Method, rt, }; @@ -47,17 +45,17 @@ pub struct Request { #[serde(skip)] version: Option, + /// The option enables default headers. + default_headers: Option, + /// The headers to use for the request. #[serde(skip)] - headers: Option, + headers: Option, /// The original headers to use for the request. #[serde(skip)] orig_headers: Option, - /// The option enables default headers. - default_headers: Option, - /// The cookies to use for the request. #[serde(skip)] cookies: Option, @@ -106,35 +104,30 @@ pub struct Request { impl Request { /// Create a new [`Request`] from Ruby keyword arguments. pub fn new(ruby: &magnus::Ruby, hash: RHash) -> Result { - let kwargs = hash.as_value(); - let mut builder: Self = serde_magnus::deserialize(ruby, kwargs)?; + let keyword = hash.as_value(); + let mut builder: Self = serde_magnus::deserialize(ruby, keyword)?; - // extra emulation handling if let Some(v) = hash.get(ruby.to_symbol(stringify!(emulation))) { - let emulation_obj = Obj::::try_convert(v)?; - builder.emulation = Some((*emulation_obj).clone()); + let obj = Obj::::try_convert(v)?; + builder.emulation = Some((*obj).clone()); } - // extra version handling - builder.version = Extractor::::try_convert(kwargs)?.into_inner(); - - // extra headers handling - builder.headers = Extractor::::try_convert(kwargs)?.into_inner(); - - // extra original headers handling - builder.orig_headers = Extractor::::try_convert(kwargs)?.into_inner(); - - // extra cookies handling - builder.cookies = Cookies::try_convert(kwargs).map(Some)?; + if let Some(v) = hash.get(ruby.to_symbol(stringify!(headers))) { + builder.headers = Some(Headers::try_convert(v)?); + } - // extra proxy handling - builder.proxy = Extractor::::try_convert(kwargs)?.into_inner(); + if let Some(v) = hash.get(ruby.to_symbol(stringify!(cookies))) { + builder.cookies = Some(Cookies::try_convert(v)?); + } - // extra body handling - if let Some(body) = hash.get(ruby.to_symbol(stringify!(body))) { - builder.body = Some(Body::try_convert(body)?); + if let Some(v) = hash.get(ruby.to_symbol(stringify!(body))) { + builder.body = Some(Body::try_convert(v)?); } + builder.proxy = Extractor::::try_convert(keyword)?.into_inner(); + builder.version = Extractor::::try_convert(keyword)?.into_inner(); + builder.orig_headers = Extractor::::try_convert(keyword)?.into_inner(); + Ok(builder) } } @@ -176,7 +169,7 @@ pub fn execute_request>( apply_option!(set_if_some, builder, request.interface, interface); // Headers options. - apply_option!(set_if_some, builder, request.headers, headers); + apply_option!(set_if_some_into_inner, builder, request.headers, headers); apply_option!(set_if_some, builder, request.orig_headers, orig_headers); apply_option!( set_if_some, @@ -185,6 +178,13 @@ pub fn execute_request>( default_headers ); + // Cookies options. + if let Some(cookies) = request.cookies.take() { + for cookie in cookies.0 { + builder = builder.header(header::COOKIE, cookie); + } + } + // Authentication options. apply_option!( set_if_some_map_ref, @@ -198,13 +198,6 @@ pub fn execute_request>( builder = builder.basic_auth(basic_auth.0, basic_auth.1); } - // Cookies options. - if let Some(cookies) = request.cookies.take() { - for cookie in cookies.0 { - builder = builder.header(header::COOKIE, cookie); - } - } - // Allow redirects options. match request.allow_redirects { Some(false) => { diff --git a/src/cookie.rs b/src/cookie.rs index 5500afc..8ce9cbe 100644 --- a/src/cookie.rs +++ b/src/cookie.rs @@ -206,16 +206,10 @@ impl fmt::Display for Cookie { impl TryConvert for Cookies { fn try_convert(value: magnus::Value) -> Result { - let ruby = Ruby::get_with(value); - let rhash = RHash::try_convert(value)?; - // try extract uncompressed cookies - if let Some(hash) = rhash - .get(ruby.to_symbol(stringify!(cookies))) - .and_then(RHash::from_value) - { + if let Some(rhash) = RHash::from_value(value) { let mut cookies = Vec::new(); - hash.foreach(|name: RString, value: RString| { + rhash.foreach(|name: RString, value: RString| { let cookie = format!("{name}={value}"); let header_value = HeaderValue::from_maybe_shared(Bytes::from(cookie)) .map_err(header_value_error_to_magnus)?; @@ -227,12 +221,9 @@ impl TryConvert for Cookies { } // try extract compressed cookies - if let Some(cookies) = rhash - .get(ruby.to_symbol(stringify!(cookies))) - .and_then(RString::from_value) - { + if let Some(cookies) = RString::from_value(value) { return Ok(Self(vec![ - HeaderValue::from_maybe_shared(Bytes::from(cookies.to_string()?)) + HeaderValue::from_maybe_shared(cookies.to_bytes()) .map_err(header_value_error_to_magnus)?, ])); } diff --git a/src/header.rs b/src/header.rs index bc54d2d..aa1cf33 100644 --- a/src/header.rs +++ b/src/header.rs @@ -3,8 +3,11 @@ use std::cell::RefCell; use bytes::Bytes; use http::{HeaderMap, HeaderName, HeaderValue}; use magnus::{ - Error, Module, Object, RArray, RModule, Ruby, block::Yield, function, method, - typed_data::Inspect, + Error, Module, Object, RArray, RHash, RModule, RString, Ruby, TryConvert, Value, + block::Yield, + function, method, + r_hash::ForEach, + typed_data::{Inspect, Obj}, }; use crate::error::{header_name_error_to_magnus, header_value_error_to_magnus}; @@ -15,7 +18,14 @@ use crate::error::{header_name_error_to_magnus, header_value_error_to_magnus}; /// accessing, modifying, and iterating over header name-value pairs. #[derive(Clone, Default)] #[magnus::wrap(class = "Wreq::Headers", free_immediately, size)] -pub struct Headers(RefCell); +pub struct Headers(pub RefCell); + +struct HeaderIter { + inner: http::header::IntoIter, + next_name: Option, +} + +// ===== impl Headers ===== impl Headers { /// Create a new empty Headers instance. @@ -132,11 +142,32 @@ impl From for Headers { } } -struct HeaderIter { - inner: http::header::IntoIter, - next_name: Option, +impl TryConvert for Headers { + fn try_convert(value: Value) -> Result { + if let Some(rhash) = RHash::from_value(value) { + let mut headers = HeaderMap::new(); + + rhash.foreach(|name: RString, value: RString| { + let name = HeaderName::from_bytes(&name.to_bytes()) + .map_err(header_name_error_to_magnus)?; + let value = HeaderValue::from_maybe_shared(value.to_bytes()) + .map_err(header_value_error_to_magnus)?; + headers.insert(name, value); + + Ok(ForEach::Continue) + })?; + + return Ok(Self::from(headers)); + } + + Obj::::try_convert(value) + .map(|headers| headers.0.clone()) + .map(Self) + } } +// ===== impl HeaderIter ===== + impl Iterator for HeaderIter { type Item = (Bytes, Bytes); fn next(&mut self) -> Option { diff --git a/src/macros.rs b/src/macros.rs index 3a8e414..9d22e55 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -14,6 +14,11 @@ macro_rules! apply_option { $builder = $builder.$method(value.0); } }; + (set_if_some_into_inner, $builder:expr, $option:expr, $method:ident) => { + if let Some(value) = $option.take() { + $builder = $builder.$method(value.0.into_inner()); + } + }; (set_if_some_map, $builder:expr, $option:expr, $method:ident, $transform:expr) => { if let Some(value) = $option.take() { $builder = $builder.$method($transform(value));