Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stacked 1/n] Add conjure-http components for codegen update #416

Open
wants to merge 1 commit into
base: top-level-mods
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 261 additions & 0 deletions conjure-http/src/client/conjure.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
// Copyright 2025 Palantir Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Implementations for Conjure-generated endpoints.
use std::convert::TryFrom;

use bytes::Bytes;
use conjure_error::Error;
use conjure_object::{Plain, ToPlain};
use futures_core::Stream;
use http::{header::CONTENT_TYPE, HeaderValue, Response, StatusCode};
use serde::de::{DeserializeOwned, IgnoredAny};

use crate::private::APPLICATION_OCTET_STREAM;

use super::{
AsyncDeserializeResponse, AsyncRequestBody, AsyncSerializeRequest, AsyncWriteBody,
BoxAsyncWriteBody, DeserializeResponse, EncodeHeader, EncodeParam, RequestBody,
SerializeRequest, StdResponseDeserializer, WriteBody,
};

/// A body serializer for streaming requests.
pub enum BinaryRequestSerializer {}

impl<'a, T, R> SerializeRequest<'a, T, R> for BinaryRequestSerializer
where
T: WriteBody<R> + 'a,
{
fn content_type(_: &T) -> HeaderValue {
APPLICATION_OCTET_STREAM
}

fn serialize(value: T) -> Result<RequestBody<'a, R>, Error> {
Ok(RequestBody::Streaming(Box::new(value)))
}
}

impl<'a, T, R> AsyncSerializeRequest<'a, T, R> for BinaryRequestSerializer
where
T: AsyncWriteBody<R> + Send + 'a,
{
fn content_type(_: &T) -> HeaderValue {
APPLICATION_OCTET_STREAM
}

fn serialize(value: T) -> Result<AsyncRequestBody<'a, R>, Error> {
Ok(AsyncRequestBody::Streaming(BoxAsyncWriteBody::new(value)))
}
}

/// A body deserializer for collection types.
pub enum CollectionResponseDeserializer {}

impl<T, R> DeserializeResponse<T, R> for CollectionResponseDeserializer
where
T: DeserializeOwned + Default,
R: Iterator<Item = Result<Bytes, Error>>,
{
fn accept() -> Option<HeaderValue> {
<StdResponseDeserializer as DeserializeResponse<T, R>>::accept()
}

fn deserialize(response: Response<R>) -> Result<T, Error> {
if response.status() == StatusCode::NO_CONTENT {
return Ok(T::default());
}

<StdResponseDeserializer as DeserializeResponse<T, R>>::deserialize(response)
}
}

impl<T, R> AsyncDeserializeResponse<T, R> for CollectionResponseDeserializer
where
T: DeserializeOwned + Default,
R: Stream<Item = Result<Bytes, Error>> + Send,
{
fn accept() -> Option<HeaderValue> {
<StdResponseDeserializer as AsyncDeserializeResponse<T, R>>::accept()
}

async fn deserialize(response: Response<R>) -> Result<T, Error> {
if response.status() == StatusCode::NO_CONTENT {
return Ok(T::default());
}

<StdResponseDeserializer as AsyncDeserializeResponse<T, R>>::deserialize(response).await
}
}

/// A body deserializer for binary types.
pub enum BinaryResponseDeserializer {}

impl<R> DeserializeResponse<R, R> for BinaryResponseDeserializer {
fn accept() -> Option<HeaderValue> {
Some(APPLICATION_OCTET_STREAM)
}

fn deserialize(response: Response<R>) -> Result<R, Error> {
if response.headers().get(CONTENT_TYPE) != Some(&APPLICATION_OCTET_STREAM) {
return Err(Error::internal_safe("invalid response Content-Type"));
}

Ok(response.into_body())
}
}

impl<R> AsyncDeserializeResponse<R, R> for BinaryResponseDeserializer
where
R: Send,
{
fn accept() -> Option<HeaderValue> {
Some(APPLICATION_OCTET_STREAM)
}

async fn deserialize(response: Response<R>) -> Result<R, Error> {
if response.headers().get(CONTENT_TYPE) != Some(&APPLICATION_OCTET_STREAM) {
return Err(Error::internal_safe("invalid response Content-Type"));
}

Ok(response.into_body())
}
}

/// A body deserializer for optional binary types.
pub enum OptionalBinaryResponseDeserializer {}

impl<R> DeserializeResponse<Option<R>, R> for OptionalBinaryResponseDeserializer {
fn accept() -> Option<HeaderValue> {
<BinaryResponseDeserializer as DeserializeResponse<R, R>>::accept()
}

fn deserialize(response: Response<R>) -> Result<Option<R>, Error> {
if response.status() == StatusCode::NO_CONTENT {
return Ok(None);
}

<BinaryResponseDeserializer as DeserializeResponse<R, R>>::deserialize(response).map(Some)
}
}

impl<R> AsyncDeserializeResponse<Option<R>, R> for OptionalBinaryResponseDeserializer
where
R: Send,
{
fn accept() -> Option<HeaderValue> {
<BinaryResponseDeserializer as DeserializeResponse<R, R>>::accept()
}

async fn deserialize(response: Response<R>) -> Result<Option<R>, Error> {
if response.status() == StatusCode::NO_CONTENT {
return Ok(None);
}

<BinaryResponseDeserializer as AsyncDeserializeResponse<R, R>>::deserialize(response)
.await
.map(Some)
}
}

/// A body deserializer for unit types.
pub enum EmptyResponseDeserializer {}

impl<R> DeserializeResponse<(), R> for EmptyResponseDeserializer
where
R: Iterator<Item = Result<Bytes, Error>>,
{
fn accept() -> Option<HeaderValue> {
<StdResponseDeserializer as DeserializeResponse<(), R>>::accept()
}

fn deserialize(response: Response<R>) -> Result<(), Error> {
if response.status() == StatusCode::NO_CONTENT {
return Ok(());
}

<StdResponseDeserializer as DeserializeResponse<IgnoredAny, R>>::deserialize(response)?;

Ok(())
}
}

impl<R> AsyncDeserializeResponse<(), R> for EmptyResponseDeserializer
where
R: Stream<Item = Result<Bytes, Error>> + Send,
{
fn accept() -> Option<HeaderValue> {
<StdResponseDeserializer as AsyncDeserializeResponse<(), R>>::accept()
}

async fn deserialize(response: Response<R>) -> Result<(), Error> {
if response.status() == StatusCode::NO_CONTENT {
return Ok(());
}

<StdResponseDeserializer as AsyncDeserializeResponse<IgnoredAny, R>>::deserialize(response)
.await?;

Ok(())
}
}

/// An encoder which converts values via their `Plain` implementation.
pub enum PlainEncoder {}

impl<T> EncodeParam<T> for PlainEncoder
where
T: Plain,
{
fn encode(value: T) -> Result<Vec<String>, Error> {
Ok(vec![value.to_plain()])
}
}

impl<T> EncodeHeader<T> for PlainEncoder
where
T: Plain,
{
fn encode(value: T) -> Result<Vec<HeaderValue>, Error> {
HeaderValue::try_from(value.to_plain())
.map_err(Error::internal_safe)
.map(|v| vec![v])
}
}

/// An encoder which converts a sequence of values via their individual `Plain` implementations.
pub enum PlainSeqEncoder {}

impl<T, U> EncodeParam<T> for PlainSeqEncoder
where
T: IntoIterator<Item = U>,
U: Plain,
{
fn encode(value: T) -> Result<Vec<String>, Error> {
Ok(value.into_iter().map(|v| v.to_plain()).collect())
}
}

impl<T, U> EncodeHeader<T> for PlainSeqEncoder
where
T: IntoIterator<Item = U>,
U: Plain,
{
fn encode(value: T) -> Result<Vec<HeaderValue>, Error> {
value
.into_iter()
.map(|v| HeaderValue::try_from(v.to_plain()).map_err(Error::internal_safe))
.collect()
}
}
44 changes: 36 additions & 8 deletions conjure-http/src/client.rs → conjure-http/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ use std::convert::TryFrom;
use std::fmt::Display;
use std::future::Future;
use std::io::Write;
use std::marker::PhantomData;
use std::pin::Pin;

pub mod conjure;

#[allow(missing_docs)]
#[deprecated(note = "renamed to RequestBody", since = "3.5.0")]
pub type Body<'a, T> = RequestBody<'a, T>;
Expand Down Expand Up @@ -328,10 +331,10 @@ pub trait AsyncSerializeRequest<'a, T, W> {
fn serialize(value: T) -> Result<AsyncRequestBody<'a, W>, Error>;
}

/// A body serializer which acts like a Conjure-generated client would.
pub enum ConjureRequestSerializer {}
/// A body serializer for standard request types.
pub enum StdRequestSerializer {}

impl<'a, T, W> SerializeRequest<'a, T, W> for ConjureRequestSerializer
impl<'a, T, W> SerializeRequest<'a, T, W> for StdRequestSerializer
where
T: Serialize,
{
Expand All @@ -345,7 +348,7 @@ where
}
}

impl<'a, T, W> AsyncSerializeRequest<'a, T, W> for ConjureRequestSerializer
impl<'a, T, W> AsyncSerializeRequest<'a, T, W> for StdRequestSerializer
where
T: Serialize,
{
Expand Down Expand Up @@ -405,10 +408,10 @@ where
}
}

/// A response deserializer which acts like a Conjure-generated client would.
pub enum ConjureResponseDeserializer {}
/// A response deserializer for standard body types.
pub enum StdResponseDeserializer {}

impl<T, R> DeserializeResponse<T, R> for ConjureResponseDeserializer
impl<T, R> DeserializeResponse<T, R> for StdResponseDeserializer
where
T: DeserializeOwned,
R: Iterator<Item = Result<Bytes, Error>>,
Expand All @@ -426,7 +429,7 @@ where
}
}

impl<T, R> AsyncDeserializeResponse<T, R> for ConjureResponseDeserializer
impl<T, R> AsyncDeserializeResponse<T, R> for StdResponseDeserializer
where
T: DeserializeOwned,
R: Stream<Item = Result<Bytes, Error>> + Send,
Expand Down Expand Up @@ -512,3 +515,28 @@ where
Ok(value.into_iter().map(|v| v.to_string()).collect())
}
}

/// An encoder which delegates to another with [`AsRef::as_ref`].
pub struct AsRefEncoder<D, U> {
_p: PhantomData<(D, U)>,
}

impl<T, D, U> EncodeHeader<T> for AsRefEncoder<D, U>
where
T: AsRef<U>,
for<'a> D: EncodeHeader<&'a U>,
{
fn encode(value: T) -> Result<Vec<HeaderValue>, Error> {
D::encode(value.as_ref())
}
}

impl<T, D, U> EncodeParam<T> for AsRefEncoder<D, U>
where
T: AsRef<U>,
for<'a> D: EncodeParam<&'a U>,
{
fn encode(value: T) -> Result<Vec<String>, Error> {
D::encode(value.as_ref())
}
}
2 changes: 1 addition & 1 deletion conjure-macros/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ fn create_request(
};

let serializer = arg.attr.serializer.as_ref().map_or_else(
|| quote!(conjure_http::client::ConjureRequestSerializer),
|| quote!(conjure_http::client::StdRequestSerializer),
|t| quote!(#t),
);
let ident = &arg.ident;
Expand Down
8 changes: 4 additions & 4 deletions conjure-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ mod path;
///
/// Parameters:
/// * `serializer` - A type implementing `SerializeRequest` which will be used to serialize the
/// value into a body. Defaults to `ConjureRequestSerializer`.
/// value into a body. Defaults to `StdRequestSerializer`.
///
/// # Async
///
Expand All @@ -113,7 +113,7 @@ mod path;
/// use conjure_error::Error;
/// use conjure_http::{conjure_client, endpoint};
/// use conjure_http::client::{
/// AsyncClient, AsyncService, Client, ConjureResponseDeserializer, DeserializeResponse,
/// AsyncClient, AsyncService, Client, StdResponseDeserializer, DeserializeResponse,
/// DisplaySeqEncoder, RequestBody, SerializeRequest, Service, WriteBody,
/// };
/// use conjure_object::BearerToken;
Expand All @@ -123,7 +123,7 @@ mod path;
///
/// #[conjure_client]
/// trait MyService {
/// #[endpoint(method = GET, path = "/yaks/{yak_id}", accept = ConjureResponseDeserializer)]
/// #[endpoint(method = GET, path = "/yaks/{yak_id}", accept = StdResponseDeserializer)]
/// fn get_yak(&self, #[auth] auth: &BearerToken, #[path] yak_id: i32) -> Result<String, Error>;
///
/// #[endpoint(method = POST, path = "/yaks")]
Expand All @@ -144,7 +144,7 @@ mod path;
///
/// #[conjure_client]
/// trait MyServiceAsync {
/// #[endpoint(method = GET, path = "/yaks/{yak_id}", accept = ConjureResponseDeserializer)]
/// #[endpoint(method = GET, path = "/yaks/{yak_id}", accept = StdResponseDeserializer)]
/// async fn get_yak(
/// &self,
/// #[auth] auth: &BearerToken,
Expand Down
Loading