{{!

  Copyright (c) Meta Platforms, Inc. and affiliates.

  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.

}}
{{>lib/client_trait}}

/// Client definitions for `{{service:rust_name}}`.
pub struct {{service:rust_name}}Impl<P, T, S = ::fbthrift::NoopSpawner> {
    {{#service:interaction?}}
    #[allow(dead_code)]
    names: &'static {{service:rust_name}}Names,
    {{/service:interaction?}}
    {{#service:extends}}
    parent: {{service:client_package}}::{{service:rust_name}}Impl<P, T, S>,
    {{/service:extends}}
    {{^service:extends}}
    transport: T,
    _phantom: ::std::marker::PhantomData<fn() -> (P, S)>,
    {{/service:extends}}
}

{{#service:interaction?}}
pub struct {{service:rust_name}}Names {
    pub service: &'static ::std::ffi::CStr,
    {{#service:rustFunctions}}{{^function:sink?}}
    pub method_{{function:name}}: &'static ::std::ffi::CStr,
    {{/function:sink?}}{{/service:rustFunctions}}
}
{{/service:interaction?}}

impl<P, T, S> {{service:rust_name}}Impl<P, T, S>
where
    P: ::fbthrift::Protocol,
    T: ::fbthrift::Transport,
    {{! require P::Frame and T to have compatible DecBuf and EncBuf::Final }}
    P::Frame: ::fbthrift::Framing<DecBuf = ::fbthrift::FramingDecoded<T>>,
    ::fbthrift::ProtocolEncoded<P>: ::fbthrift::BufMutExt<Final = ::fbthrift::FramingEncodedFinal<T>>,
    P::Deserializer: ::std::marker::Send,
    S: ::fbthrift::help::Spawner,
{
    pub fn new(
        transport: T,
        {{#service:interaction?}}
        names: &'static {{service:rust_name}}Names,
        {{/service:interaction?}}
    ) -> Self {
        Self {
            {{#service:interaction?}}
            names,
            {{/service:interaction?}}
            {{#service:extends}}
            parent: {{service:client_package}}::{{service:rust_name}}Impl::<P, T, S>::new(transport),
            {{/service:extends}}
            {{^service:extends}}
            transport,
            _phantom: ::std::marker::PhantomData,
            {{/service:extends}}
        }
    }

    pub fn transport(&self) -> &T {
        ::fbthrift::help::GetTransport::transport(self)
    }

    {{#service:interactions}}
    const NAMES_{{service:rust_name}}: {{service:client_package}}::{{service:rust_name}}Names = {{service:client_package}}::{{service:rust_name}}Names {
        service: c"{{service:parent_service_name}}",
        {{#service:rustFunctions}}{{^function:sink?}}
        method_{{function:name}}: c"{{service:parent_service_name}}.{{service:name}}.{{function:name}}",
        {{/function:sink?}}{{/service:rustFunctions}}
    };
    {{/service:interactions}}

    {{#service:rustFunctions}}
    {{^function:starts_interaction?}}{{^function:sink?}}

    fn _{{function:name}}_impl(
        &self,{{!
        }}{{#function:args}}
        arg_{{field:name}}: {{>lib/arg}},{{!
        }}{{/function:args}}
        rpc_options: T::RpcOptions,
    ) -> {{>lib/client_return_type}} {
        use ::tracing::Instrument as _;
        use ::futures::FutureExt as _;
        {{#function:stream?}}
        use ::futures::StreamExt as _;
        use ::fbthrift::Deserialize as _;
        {{/function:stream?}}

        let service_name = {{!
            }}{{^service:interaction?}}c"{{service:name}}"{{/service:interaction?}}{{!
            }}{{#service:interaction?}}self.names.service{{/service:interaction?}}{{!
        }};
        let service_method_name = {{!
            }}{{^service:interaction?}}c"{{service:name}}.{{function:name}}"{{/service:interaction?}}{{!
            }}{{#service:interaction?}}self.names.method_{{function:name}}{{/service:interaction?}}{{!
        }};

        {{#function:creates_interaction?}}
        const INTERACTION_NAME: &::std::ffi::CStr = c"{{function:interaction_name}}";
        {{/function:creates_interaction?}}
        let args = self::Args_{{service:rust_name}}_{{function:name}} {
            {{#function:args}}
            {{field:rust_name}}: arg_{{field:name}},
            {{/function:args}}
            _phantom: ::std::marker::PhantomData,
        };

        {{#function:creates_interaction?}}
        let interaction_transport = match self.transport().create_interaction(INTERACTION_NAME) {
            ::std::result::Result::Ok(res) => res,
            ::std::result::Result::Err(err) => return ::futures::future::err(err.into()).boxed(),
        };
        let interaction_impl = {{program:crate}}::{{function:interaction_name}}Impl::<P, T, S>::new(interaction_transport, &Self::NAMES_{{function:interaction_name}});
        let transport = interaction_impl.transport();
        {{/function:creates_interaction?}}{{^function:creates_interaction?}}
        let transport = self.transport();
        {{/function:creates_interaction?}}

        // need to do call setup outside of async block because T: Transport isn't Send
        let request_env = match ::fbthrift::help::serialize_request_envelope::<P, _>("{{#service:interaction?}}{{service:name}}.{{/service:interaction?}}{{function:name}}", &args) {
            ::std::result::Result::Ok(res) => res,
            ::std::result::Result::Err(err) => return ::futures::future::err(err.into()).boxed(),
        };

        {{#function:stream?}}
        let call_stream = transport
            .call_stream(service_name, service_method_name, request_env, rpc_options)
            .instrument(::tracing::trace_span!("call_stream", method = "{{service:name}}.{{function:name}}"));
        {{/function:stream?}}{{^function:stream?}}
        let call = transport
            .call(service_name, service_method_name, request_env, rpc_options)
            .instrument(::tracing::trace_span!("call", method = "{{service:name}}.{{function:name}}"));
        {{/function:stream?}}

        async move {
            {{#function:stream?}}{{!

            ******************************
            ******* STREAMING BODY *******
            ******************************

            }}
            let (initial, stream) = call_stream.await?;

            let new_stream = stream.then(|item_res| {
                async move {
                    match item_res {
                        ::std::result::Result::Err(err) =>
                            ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}StreamError::from(err)),
                        ::std::result::Result::Ok(item_enc) => {
                            S::spawn(move || {
                                match item_enc {
                                    ::fbthrift::ClientStreamElement::Reply(payload) => {
                                        let mut de = P::deserializer(payload);
                                        <{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}StreamReader as ::fbthrift::help::DeserializeExn>::read_result(&mut de)
                                    }
                                    ::fbthrift::ClientStreamElement::ApplicationEx(payload) => {
                                        let mut de = P::deserializer(payload);
                                        let aexn = ::fbthrift::ApplicationException::read(&mut de)?;
                                        ::std::result::Result::Ok(::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}StreamError::ApplicationException(aexn)))
                                    }
                                }
                            }).await.map_err(::anyhow::Error::from)??
                        }
                    }
                }
            })
            .boxed();

            let de = P::deserializer(initial);
            let res = ::fbthrift::help::async_deserialize_response_envelope::<P, {{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Reader, S>(de)
                .await??
                .map({{!
                    }}{{#function:stream_has_first_response?}}move |initial| (initial, new_stream){{/function:stream_has_first_response?}}{{!
                    }}{{^function:stream_has_first_response?}}move |_| new_stream{{/function:stream_has_first_response?}}{{!
                }});
            {{/function:stream?}}{{^function:stream?}}{{!

            ******************************
            **** SINGLE RESPONSE BODY ****
            ******************************

            }}
            let reply_env = call.await?;

            let de = P::deserializer(reply_env);
            let res = ::fbthrift::help::async_deserialize_response_envelope::<P, {{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Reader, S>(de).await?;

            let res = match res {
                ::std::result::Result::Ok(res) => res,
                ::std::result::Result::Err(aexn) => {
                    ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ApplicationException(aexn))
                }
            };
            {{/function:stream?}}
            {{#function:return_type}}{{#function:creates_interaction?}}{{!

            ***********************************
            **** Add interaction to result ****
            ***********************************

            }}
            let interaction_client: {{program:crate}}::client::{{function:interaction_name}}Client = ::std::sync::Arc::new(interaction_impl);
            {{#type:void?}}
            res?;
            ::std::result::Result::Ok(interaction_client)
            {{/type:void?}}{{^type:void?}}
            ::std::result::Result::Ok((interaction_client, res?))
            {{/type:void?}}{{!
            }}{{/function:creates_interaction?}}{{^function:creates_interaction?}}
            res
            {{/function:creates_interaction?}}{{/function:return_type}}
        }
        .instrument(::tracing::info_span!("stream", method = "{{service:name}}.{{function:name}}"))
        .boxed()
    }
    {{/function:sink?}}{{/function:starts_interaction?}}
    {{/service:rustFunctions}}
}

impl<P, T, S> ::fbthrift::help::GetTransport<T> for {{service:rust_name}}Impl<P, T, S>
where
    T: ::fbthrift::Transport,
{
    fn transport(&self) -> &T {
        {{#service:extends}}
        self.parent.transport()
        {{/service:extends}}
        {{^service:extends}}
        &self.transport
        {{/service:extends}}
    }
}

{{#service:extendedClients}}
#[allow(deprecated)]{{! `dependencies` modules are deprecated other than for packagePrefix to use }}
impl<'a, P, T, S> {{!
    }}{{#extendedService:service}}{{!
    }}::std::convert::AsRef<dyn {{extendedService:packagePrefix}}::{{service:rust_name}} + 'a> {{!
    }}{{/extendedService:service}}{{!
    }}for {{service:rust_name}}Impl<P, T, S>
where
    P: ::fbthrift::Protocol,
    T: ::fbthrift::Transport,
    {{! require P::Frame and T to have compatible DecBuf and EncBuf::Final }}
    P::Frame: ::fbthrift::Framing<DecBuf = ::fbthrift::FramingDecoded<T>>,
    ::fbthrift::ProtocolEncoded<P>: ::fbthrift::BufMutExt<Final = ::fbthrift::FramingEncodedFinal<T>>,
    P::Deserializer: ::std::marker::Send,
    S: ::fbthrift::help::Spawner,
{
    fn as_ref(&self) -> &(dyn {{#extendedService:service}}{{!
        }}{{extendedService:packagePrefix}}::{{service:rust_name}}{{!
        }}{{/extendedService:service}}{{!
        }} + 'a)
    {
        {{extendedService:asRefImpl}}
    }
}

#[allow(deprecated)]{{! `dependencies` modules are deprecated other than for packagePrefix to use }}
impl<'a, P, T, S> {{!
    }}{{#extendedService:service}}{{!
    }}::std::convert::AsRef<dyn {{extendedService:packagePrefix}}::{{service:rust_name}}Ext<T> + 'a> {{!
    }}{{/extendedService:service}}{{!
    }}for {{service:rust_name}}Impl<P, T, S>
where
    P: ::fbthrift::Protocol,
    T: ::fbthrift::Transport,
    {{! require P::Frame and T to have compatible DecBuf and EncBuf::Final }}
    P::Frame: ::fbthrift::Framing<DecBuf = ::fbthrift::FramingDecoded<T>>,
    ::fbthrift::ProtocolEncoded<P>: ::fbthrift::BufMutExt<Final = ::fbthrift::FramingEncodedFinal<T>>,
    P::Deserializer: ::std::marker::Send,
    S: ::fbthrift::help::Spawner,
{
    fn as_ref(&self) -> &(dyn {{#extendedService:service}}{{!
        }}{{extendedService:packagePrefix}}::{{service:rust_name}}Ext<T>{{!
        }}{{/extendedService:service}}{{!
        }} + 'a)
    {
        {{extendedService:asRefImpl}}
    }
}

{{/service:extendedClients}}

{{#service:rustFunctions}}{{^function:starts_interaction?}}{{^function:sink?}}

struct Args_{{service:rust_name}}_{{function:name}}<'a> {
    {{#function:args}}
    {{field:rust_name}}: {{>lib/arg_life}},
    {{/function:args}}
    _phantom: ::std::marker::PhantomData<&'a ()>,
}

impl<'a, P: ::fbthrift::ProtocolWriter> ::fbthrift::Serialize<P> for self::Args_{{service:rust_name}}_{{function:name}}<'a> {
    #[inline]{{! No cost because there's only one caller; with luck will mitigate move cost of args. }}
    #[::tracing::instrument(skip_all, level = "trace", name = "serialize_args", fields(method = "{{service:name}}.{{function:name}}"))]
    fn write(&self, p: &mut P) {
        p.write_struct_begin("args");{{!
        }}{{#function:args}}
        p.write_field_begin({{!
            }}"{{field:name}}", {{!
            }}{{#field:type}}{{>lib/ttype}}{{/field:type}}, {{!
            }}{{field:key}}i16{{!
        }});
        {{#field:type}}{{!
          }}{{#type:has_adapter?}}{{!
            }}::fbthrift::Serialize::write(&{{>lib/adapter/qualified}}::to_thrift_field::<fbthrift::metadata::NoThriftAnnotations>(&self.{{field:rust_name}}, {{field:key}}), p){{!
          }}{{/type:has_adapter?}}{{!
          }}{{^type:has_adapter?}}{{!
            }}{{>lib/write}}(&self.{{field:rust_name}}, p){{!
          }}{{/type:has_adapter?}}{{!
        }}{{/field:type}};
        p.write_field_end();{{!
        }}{{/function:args}}
        p.write_field_stop();
        p.write_struct_end();
    }
}
{{/function:sink?}}{{/function:starts_interaction?}}{{/service:rustFunctions}}

impl<P, T, S> {{service:rust_name}} for {{service:rust_name}}Impl<P, T, S>
where
    P: ::fbthrift::Protocol,
    T: ::fbthrift::Transport,
    {{! require P::Frame and T to have compatible DecBuf and EncBuf::Final }}
    P::Frame: ::fbthrift::Framing<DecBuf = ::fbthrift::FramingDecoded<T>>,
    ::fbthrift::ProtocolEncoded<P>: ::fbthrift::BufMutExt<Final = ::fbthrift::FramingEncodedFinal<T>>,
    P::Deserializer: ::std::marker::Send,
    S: ::fbthrift::help::Spawner,
{{>lib/block}}

    {{#service:rustFunctions}}
    {{^function:starts_interaction?}}{{^function:sink?}}
    fn {{function:rust_name}}(
        &self,{{!
        }}{{#function:args}}
        arg_{{field:name}}: {{>lib/arg}},{{!
        }}{{/function:args}}
    ) -> {{>lib/client_return_type}} {
        let rpc_options = T::RpcOptions::default();
        self._{{function:name}}_impl(
            {{#function:args}}
            arg_{{field:name}},
            {{/function:args}}
            rpc_options,
        )
    }
    {{/function:sink?}}{{/function:starts_interaction?}}{{#function:starts_interaction?}}

    fn {{function:name}}(
        &self,
    ) -> ::std::result::Result<{{!
    }}{{program:crate}}::client::{{function:interaction_name}}Client, {{!
    }}::anyhow::Error> {
        ::std::result::Result::Ok(
            ::std::sync::Arc::new(
                {{program:crate}}::client::{{function:interaction_name}}Impl::<P, T, S>::new(
                    self.transport().create_interaction(c"{{function:interaction_name}}")?,
                    &Self::NAMES_{{function:interaction_name}},
                )
            )
        )
    }
    {{/function:starts_interaction?}}{{/service:rustFunctions}}
}

impl<P, T, S> {{service:rust_name}}Ext<T> for {{service:rust_name}}Impl<P, T, S>
where
    P: ::fbthrift::Protocol,
    T: ::fbthrift::Transport,
    {{! require P::Frame and T to have compatible DecBuf and EncBuf::Final }}
    P::Frame: ::fbthrift::Framing<DecBuf = ::fbthrift::FramingDecoded<T>>,
    ::fbthrift::ProtocolEncoded<P>: ::fbthrift::BufMutExt<Final = ::fbthrift::FramingEncodedFinal<T>>,
    P::Deserializer: ::std::marker::Send,
    S: ::fbthrift::help::Spawner,
{{>lib/block}}

    {{#service:rustFunctions}}
    {{^function:starts_interaction?}}{{^function:sink?}}
    fn {{function:rust_name}}_with_rpc_opts(
        &self,{{!
        }}{{#function:args}}
        arg_{{field:name}}: {{>lib/arg}},{{!
        }}{{/function:args}}
        rpc_options: T::RpcOptions,
    ) -> {{>lib/client_return_type}} {
        self._{{function:name}}_impl(
            {{#function:args}}
            arg_{{field:name}},
            {{/function:args}}
            rpc_options,
        )
    }
    {{/function:sink?}}{{/function:starts_interaction?}}
    {{/service:rustFunctions}}
    {{^service:extendedClients}}

    fn transport(&self) -> &T {
        self.transport()
    }
    {{/service:extendedClients}}
}

{{#service:interaction?}}
pub type {{service:rust_name}}DynClient = dyn {{service:rust_name}} + ::std::marker::Send + ::std::marker::Sync + 'static;
pub type {{service:rust_name}}Client = ::std::sync::Arc<{{service:rust_name}}DynClient>;

{{/service:interaction?}}
{{^service:interaction?}}
#[derive(Clone)]
pub struct make_{{service:rust_name}};

/// To be called by user directly setting up a client. Avoids
/// needing ClientFactory trait in scope, avoids unidiomatic
/// make_Trait name.
///
/// ```
/// # const _: &str = stringify! {
/// use bgs::client::BuckGraphService;
///
/// let protocol = BinaryProtocol::new();
/// let transport = HttpClient::new();
/// let client = <dyn BuckGraphService>::new(protocol, transport);
/// # };
/// ```
impl dyn {{service:rust_name}} {
    {{#service:annotations}}
    {{#annotation:value?}}
    pub const {{annotation:rust_name}}: &'static ::std::primitive::str = {{annotation:rust_value}};
    {{/annotation:value?}}
    {{/service:annotations}}
    pub fn new<P, T>(
        protocol: P,
        transport: T,
    ) -> ::std::sync::Arc<impl {{service:rust_name}} + ::std::marker::Send + ::std::marker::Sync + 'static>
    where
        P: ::fbthrift::Protocol<Frame = T>,
        T: ::fbthrift::Transport,
        P::Deserializer: ::std::marker::Send,
    {
        let spawner = ::fbthrift::help::NoopSpawner;
        Self::with_spawner(protocol, transport, spawner)
    }

    pub fn with_spawner<P, T, S>(
        protocol: P,
        transport: T,
        spawner: S,
    ) -> ::std::sync::Arc<impl {{service:rust_name}} + ::std::marker::Send + ::std::marker::Sync + 'static>
    where
        P: ::fbthrift::Protocol<Frame = T>,
        T: ::fbthrift::Transport,
        P::Deserializer: ::std::marker::Send,
        S: ::fbthrift::help::Spawner,
    {
        let _ = protocol;
        let _ = spawner;
        ::std::sync::Arc::new({{service:rust_name}}Impl::<P, T, S>::new(transport))
    }
}

impl<T> dyn {{service:rust_name}}Ext<T>
where
    T: ::fbthrift::Transport,
{
    pub fn new<P>(
        protocol: P,
        transport: T,
    ) -> ::std::sync::Arc<impl {{service:rust_name}}Ext<T> + ::std::marker::Send + ::std::marker::Sync + 'static>
    where
        P: ::fbthrift::Protocol<Frame = T>,
        P::Deserializer: ::std::marker::Send,
    {
        let spawner = ::fbthrift::help::NoopSpawner;
        Self::with_spawner(protocol, transport, spawner)
    }

    pub fn with_spawner<P, S>(
        protocol: P,
        transport: T,
        spawner: S,
    ) -> ::std::sync::Arc<impl {{service:rust_name}}Ext<T> + ::std::marker::Send + ::std::marker::Sync + 'static>
    where
        P: ::fbthrift::Protocol<Frame = T>,
        P::Deserializer: ::std::marker::Send,
        S: ::fbthrift::help::Spawner,
    {
        let _ = protocol;
        let _ = spawner;
        ::std::sync::Arc::new({{service:rust_name}}Impl::<P, T, S>::new(transport))
    }
}

pub type {{service:rust_name}}DynClient = <make_{{service:rust_name}} as ::fbthrift::ClientFactory>::Api;
pub type {{service:rust_name}}Client = ::std::sync::Arc<{{service:rust_name}}DynClient>;

/// The same thing, but to be called from generic contexts where we are
/// working with a type parameter `C: ClientFactory` to produce clients.
impl ::fbthrift::ClientFactory for make_{{service:rust_name}} {
    type Api = dyn {{service:rust_name}} + ::std::marker::Send + ::std::marker::Sync + 'static;

    fn with_spawner<P, T, S>(protocol: P, transport: T, spawner: S) -> ::std::sync::Arc<Self::Api>
    where
        P: ::fbthrift::Protocol<Frame = T>,
        T: ::fbthrift::Transport,
        P::Deserializer: ::std::marker::Send,
        S: ::fbthrift::help::Spawner,
    {
        <dyn {{service:rust_name}}>::with_spawner(protocol, transport, spawner)
    }
}
{{/service:interaction?}}

{{!newline}}
