Skip to content

Commit

Permalink
Add conjure-http components for codegen update
Browse files Browse the repository at this point in the history
  • Loading branch information
sfackler committed Jan 21, 2025
1 parent 607e15e commit 70caaa7
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 18 deletions.
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

0 comments on commit 70caaa7

Please sign in to comment.