From 6a6ec1b8f8a5c70d21f3772ce637b763e8ab21ea Mon Sep 17 00:00:00 2001 From: nsfisis Date: Mon, 31 Aug 2026 00:15:52 +0900 Subject: feat(plugin): carry Http\Response across as a materialized value MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A response is built for one request and the graph never retains it, so there is no entity for a handle to point at. The child holds a real instance instead, revived from the object record the wire carries, and collect() frees the copy each world holds — which is what that method is for. The value-object rule rejects the class only because collect() assigns to $this, so the category comes from an overrides.list entry. HttpDownloader::get() and copy() answer with one. Two gaps stay: decodeJson() reaches Composer\Json\JsonFile, which a guard shadows, and Composer answers a curl request with the CurlResponse subclass where this port flattens the value into a Response. Co-Authored-By: Claude Opus 5 (1M context) --- crates/shirabe/src/plugin/php_plugin_value.rs | 44 +++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) (limited to 'crates/shirabe/src/plugin/php_plugin_value.rs') diff --git a/crates/shirabe/src/plugin/php_plugin_value.rs b/crates/shirabe/src/plugin/php_plugin_value.rs index cb9b990e..80415eef 100644 --- a/crates/shirabe/src/plugin/php_plugin_value.rs +++ b/crates/shirabe/src/plugin/php_plugin_value.rs @@ -14,6 +14,7 @@ //! constraint) has no faithful constructor call. use crate::package::Link; +use crate::util::http::Response; use chrono::{DateTime, NaiveDateTime, TimeZone, Utc}; use shirabe_php_rpc::{PhpObject, PhpThrow, PluginValue}; use shirabe_semver::constraint::{ @@ -21,6 +22,7 @@ use shirabe_semver::constraint::{ }; const LINK_CLASS: &str = "Composer\\Package\\Link"; +const RESPONSE_CLASS: &str = "Composer\\Util\\Http\\Response"; const CONSTRAINT_CLASS: &str = "Composer\\Semver\\Constraint\\Constraint"; const MULTI_CONSTRAINT_CLASS: &str = "Composer\\Semver\\Constraint\\MultiConstraint"; const MATCH_ALL_CLASS: &str = "Composer\\Semver\\Constraint\\MatchAllConstraint"; @@ -197,6 +199,48 @@ fn constraint_from_wire(value: &PluginValue) -> Result }) } +/// A `Response` is built for one request and never retained by the object graph, so the child +/// holds a real instance rather than a handle, and `collect()` frees the copy it holds. +/// +/// TODO(type-model): PHP's `$request` is the whole request array (`url`, `options`, `copyTo`) +/// and this port keeps only the url, which is all `decodeJson()` reads back out of it. +/// +/// TODO(port): Composer answers a curl request with a `Composer\Util\Http\CurlResponse` +/// carrying the transfer info; this port flattens that subclass into `Response` before the +/// value leaves `HttpDownloader`, so `getCurlInfo()` is gone and `get_class()` differs. +pub(crate) fn response_to_wire(response: &Response) -> PluginValue { + let mut object = PhpObject::new(RESPONSE_CLASS); + let mut request = indexmap::IndexMap::new(); + request.insert(b"url".to_vec(), PluginValue::string(response.request_url())); + object.set_private(RESPONSE_CLASS, "request", PluginValue::Array(request)); + object.set_private( + RESPONSE_CLASS, + "code", + PluginValue::Int(response.get_status_code()), + ); + object.set_private( + RESPONSE_CLASS, + "headers", + PluginValue::List( + response + .get_headers() + .iter() + .cloned() + .map(PluginValue::string) + .collect(), + ), + ); + object.set_private( + RESPONSE_CLASS, + "body", + match response.get_body() { + Some(body) => PluginValue::string(body), + None => PluginValue::Null, + }, + ); + PluginValue::PhpObject(object) +} + pub(crate) fn link_to_wire(link: &Link) -> PluginValue { let mut object = PhpObject::new(LINK_CLASS); object.set_protected("source", PluginValue::string(link.get_source())); -- cgit v1.3.1-4-g156e