use axum::body::Body;
use http::StatusCode;
use prost_reflect::DescriptorPool;
use structured_proxy::config::ProxyConfig;
use structured_proxy::transcode::error::ErrorDetailsPolicy;
use structured_proxy::ProxyServer;
use tower::ServiceExt;
const HTTP_PROTO: &str = r#"
syntax = "proto3";
package google.api;
message HttpRule {
string selector = 1;
oneof pattern {
string get = 2;
string put = 3;
string post = 4;
string delete = 5;
string patch = 6;
}
string body = 7;
string response_body = 12;
repeated HttpRule additional_bindings = 11;
}
"#;
const ANNOTATIONS_PROTO: &str = r#"
syntax = "proto3";
package google.api;
import "google/api/http.proto";
import "google/protobuf/descriptor.proto";
extend google.protobuf.MethodOptions {
HttpRule http = 72295728;
}
"#;
struct TestProtos {
name: &'static str,
source: &'static str,
}
impl protox::file::FileResolver for TestProtos {
fn open_file(&self, name: &str) -> Result<protox::file::File, protox::Error> {
let source = match name {
"google/api/http.proto" => HTTP_PROTO,
"google/api/annotations.proto" => ANNOTATIONS_PROTO,
_ if name == self.name => self.source,
_ => return protox::file::GoogleFileResolver::new().open_file(name),
};
protox::file::File::from_source(name, source)
}
}
pub fn compile(name: &'static str, source: &'static str) -> DescriptorPool {
protox::Compiler::with_file_resolver(TestProtos { name, source })
.open_file(name)
.expect("test protos compile")
.descriptor_pool()
}
pub async fn serve<S>(service: S) -> String
where
S: tower::Service<
http::Request<tonic::body::Body>,
Response = http::Response<tonic::body::Body>,
Error = std::convert::Infallible,
> + tonic::server::NamedService
+ Clone
+ Send
+ Sync
+ 'static,
S::Future: Send + 'static,
{
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
let incoming = futures::stream::unfold(listener, |listener| async move {
let conn = listener.accept().await.map(|(stream, _)| stream);
Some((conn, listener))
});
tokio::spawn(
tonic::transport::Server::builder()
.add_service(service)
.serve_with_incoming(incoming),
);
format!("http://{addr}")
}
pub fn proxy(
upstream: &str,
pool: DescriptorPool,
error_details: ErrorDetailsPolicy,
) -> axum::Router {
let config =
ProxyConfig::from_yaml_str(&format!("upstream:\n default: \"{upstream}\"\n")).unwrap();
ProxyServer::from_config(config)
.with_descriptors(pool)
.with_error_details(error_details)
.router()
.unwrap()
}
pub async fn send(app: &axum::Router, request: http::Request<Body>) -> (StatusCode, String) {
let resp = app.clone().oneshot(request).await.unwrap();
let status = resp.status();
let bytes = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap();
(status, String::from_utf8(bytes.to_vec()).unwrap())
}