{{!

  Copyright (c) Facebook, Inc. and its 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.

}}
    {{#service:interactions}}{{>lib/client}}
    {{/service:interactions}}

    pub struct {{service:name}}Impl<P, T, S = ::fbthrift::NoopSpawner> {
        {{#service:extends}}
        parent: {{service:package}}::client::{{service:name}}Impl<P, T, S>,
        {{/service:extends}}
        {{^service:extends}}
        transport: T,
        _phantom: ::std::marker::PhantomData<fn() -> (P, S)>,
        {{/service:extends}}
    }

    impl<P, T, S> {{service: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,
        ) -> Self {
            {{#service:extends}}
            let parent = {{service:package}}::client::{{service:name}}Impl::<P, T, S>::new(transport);
            Self { parent }
            {{/service:extends}}
            {{^service:extends}}
            Self {
                transport,
                _phantom: ::std::marker::PhantomData,
            }
            {{/service:extends}}
        }

        pub fn transport(&self) -> &T {
            {{#service:extends}}
            self.parent.transport()
            {{/service:extends}}
            {{^service:extends}}
            &self.transport
            {{/service:extends}}
        }

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

        fn _{{function:rust_name}}_impl(
            &self,{{!
            }}{{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},{{!
            }}{{/function:args}}
            rpc_options: T::RpcOptions,
        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<{{!
            }}{{#function:return_type}}{{>lib/type}}{{/function:return_type}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + ::std::marker::Send + 'static>> {
            use ::const_cstr::const_cstr;
            use ::tracing::Instrument as _;
            use ::futures::FutureExt as _;

            const_cstr! {
                SERVICE_NAME = "{{service:name}}";
                METHOD_NAME = "{{service:name}}.{{function:name}}";
            }
            let args = self::Args_{{service:name}}_{{function:name}} {
                {{#function:args}}
                {{field:rust_name}}: arg_{{field:name}},
                {{/function:args}}
                _phantom: ::std::marker::PhantomData,
            };

            // 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(),
            };

            let call = self.transport()
                .call(SERVICE_NAME.as_cstr(), METHOD_NAME.as_cstr(), request_env, rpc_options)
                .instrument(::tracing::trace_span!("call", function = "{{service:name}}.{{function:name}}"));

            async move {
                let reply_env = call.await?;

                let de = P::deserializer(reply_env);
                let (res, _de): (::std::result::Result<{{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn, _>, _) =
                    ::fbthrift::help::async_deserialize_response_envelope::<P, _, S>(de).await?;

                match res {
                    ::std::result::Result::Ok(exn) => ::std::convert::From::from(exn),
                    ::std::result::Result::Err(aexn) =>
                        ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ApplicationException(aexn))
                }
            }
            .instrument(::tracing::info_span!("{{service:name}}.{{function:name}}"))
            .boxed()
        }
        {{/function:returns_streams?}}{{#function:returns_streams?}}

        fn _{{function:rust_name}}_impl(
            &self,{{!
            }}{{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},{{!
            }}{{/function:args}}
            rpc_options: T::RpcOptions,
        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<{{!
            }}{{#function:return_type}}{{>lib/type}}{{/function:return_type}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + ::std::marker::Send + 'static>> {
            use ::const_cstr::const_cstr;
            use ::futures::future::FutureExt as _;
            use ::tracing::Instrument as _;
            use ::futures::StreamExt as _;
            use ::fbthrift::Deserialize as _;

            const_cstr! {
                SERVICE_NAME = "{{service:name}}";
                METHOD_NAME = "{{service:name}}.{{function:name}}";
            }
            let args = self::Args_{{service:name}}_{{function:name}} {
                {{#function:args}}
                {{field:rust_name}}: arg_{{field:name}},
                {{/function:args}}
                _phantom: ::std::marker::PhantomData,
            };

            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(),
            };

            let call_stream = self.transport()
                .call_stream(SERVICE_NAME.as_cstr(), METHOD_NAME.as_cstr(), request_env, rpc_options)
                .instrument(::tracing::trace_span!("call_stream", method = "{{service:name}}.{{function:name}}"));

            async move {
                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) => {
                                let res = S::spawn(move || {
                                    let mut de = P::deserializer(item_enc);
                                    {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}StreamExn::read(&mut de)
                                }).await?;

                                let item: ::std::result::Result<{{!
                                    }}{{>lib/function_stream_elem_type}}, {{!
                                    }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}StreamError> =
                                    ::std::convert::From::from(res);
                                item
                            }
                        }
                    }
                })
                .boxed();

                {{#function:stream_has_first_response?}}
                let de = P::deserializer(_initial);
                let res: {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn =
                    ::fbthrift::help::async_deserialize_response_envelope::<P, _, S>(de).await?.0?;

                let initial: ::std::result::Result<{{>lib/function_stream_first_response_type}}, {{!
                    }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error> =
                    ::std::convert::From::from(res);
                initial.map(move |initial| (initial, new_stream))
                {{/function:stream_has_first_response?}}
                {{^function:stream_has_first_response?}}
                ::std::result::Result::Ok(new_stream)
                {{/function:stream_has_first_response?}}
            }
            .instrument(::tracing::info_span!("{{service:name}}.{{function:name}}"))
            .boxed()
        }
        {{/function:returns_streams?}}{{/function:starts_interaction?}}
        {{/service:rustFunctions}}
    }

    {{#service:extendedServices}}
    impl<P, T, S> {{!
        }}{{#extendedService:service}}{{!
        }}::std::convert::AsRef<dyn {{extendedService:packagePrefix}}::client::{{service:name}} + 'static> {{!
        }}{{/extendedService:service}}{{!
        }}for {{service: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}}::client::{{service:name}}{{!
            }}{{/extendedService:service}}{{!
            }} + 'static)
        {
            {{extendedService:asRefImpl}}
        }
    }

    {{/service:extendedServices}}
    {{#service:docs?}}
    #[doc = {{service:docs}}]
    {{/service:docs?}}
    pub trait {{service:name}}: {{!
        }}{{#service:extends}}{{service:package}}::client::{{service:name}} + {{/service:extends}}{{!
        }}::std::marker::Send {{>lib/block}}{{!

        }}{{#service:rustFunctions}}
        {{#function:docs?}}
        #[doc = {{function:docs}}]
        {{/function:docs?}}
        {{^function:starts_interaction?}}
        fn {{function:rust_name}}(
            &self,
            {{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},
            {{/function:args}}
        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<{{!
            }}{{#function:return_type}}{{>lib/type}}{{/function:return_type}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + ::std::marker::Send + 'static>>;
        {{/function:starts_interaction?}}{{#function:starts_interaction?}}
        fn {{function:rust_name}}(
            &self,
        ) -> ::std::result::Result<{{!
        }}::std::sync::Arc<dyn {{function:interaction_name}} + ::std::marker::Send + 'static>, {{!
        }}::anyhow::Error>;
        {{/function:starts_interaction?}}{{/service:rustFunctions}}
    }

    pub trait {{service:name}}Ext<T>: {{service:name}}
    where
        T: ::fbthrift::Transport,
    {{>lib/block}}

        {{#service:rustFunctions}}
        {{#function:docs?}}
        #[doc = {{function:docs}}]
        {{/function:docs?}}
        {{^function:starts_interaction?}}
        fn {{function:rust_name}}_with_rpc_opts(
            &self,
            {{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},
            {{/function:args}}
            rpc_options: T::RpcOptions,
        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<{{!
            }}{{#function:return_type}}{{>lib/type}}{{/function:return_type}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + ::std::marker::Send + 'static>>;
        {{/function:starts_interaction?}}
        {{/service:rustFunctions}}
    }
    {{#service:rustFunctions}}{{^function:starts_interaction?}}

    struct Args_{{service: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: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}}{{>lib/write}}{{/field:type}}(&self.{{field:rust_name}}, p);
            p.write_field_end();{{!
            }}{{/function:args}}
            p.write_field_stop();
            p.write_struct_end();
        }
    }
    {{/function:starts_interaction?}}{{/service:rustFunctions}}

    impl<P, T, S> {{service:name}} for {{service: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?}}
        fn {{function:rust_name}}(
            &self,{{!
            }}{{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},{{!
            }}{{/function:args}}
        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<{{!
            }}{{#function:return_type}}{{>lib/type}}{{/function:return_type}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + ::std::marker::Send + 'static>> {
            let rpc_options = T::RpcOptions::default();
            self._{{function:rust_name}}_impl(
                {{#function:args}}
                arg_{{field:name}},
                {{/function:args}}
                rpc_options,
            )
        }
        {{/function:starts_interaction?}}{{#function:starts_interaction?}}

        fn {{function:name}}(
            &self,
        ) -> ::std::result::Result<{{!
        }}::std::sync::Arc<dyn {{function:interaction_name}} + ::std::marker::Send + 'static>, {{!
        }}::anyhow::Error> {
            use ::const_cstr::const_cstr;
            const_cstr! {
                INTERACTION_NAME = "{{function:interaction_name}}";
            }
            Ok(
                ::std::sync::Arc::new(
                    {{function:interaction_name}}Impl::<P, T, S>::new(
                        self.transport().create_interaction(INTERACTION_NAME.as_cstr())?
                    )
                )
            )
        }
        {{/function:starts_interaction?}}{{/service:rustFunctions}}
    }

    impl<P, T, S> {{service:name}}Ext<T> for {{service: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?}}
        fn {{function:rust_name}}_with_rpc_opts(
            &self,{{!
            }}{{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},{{!
            }}{{/function:args}}
            rpc_options: T::RpcOptions,
        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<{{!
            }}{{#function:return_type}}{{>lib/type}}{{/function:return_type}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + ::std::marker::Send + 'static>> {
            self._{{function:rust_name}}_impl(
                {{#function:args}}
                arg_{{field:name}},
                {{/function:args}}
                rpc_options,
            )
        }
        {{/function:starts_interaction?}}
        {{/service:rustFunctions}}
    }

    impl<'a, T> {{service:name}} for T
    where
        T: ::std::convert::AsRef<dyn {{service:name}} + 'a>,
        {{#service:extendedServices}}
        {{#extendedService:service}}
        T: {{extendedService:packagePrefix}}::client::{{service:name}},
        {{/extendedService:service}}
        {{/service:extendedServices}}
        T: ::std::marker::Send,
    {
        {{#service:rustFunctions}}
        {{^function:starts_interaction?}}
        fn {{function:rust_name}}(
            &self,{{!
            }}{{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},{{!
            }}{{/function:args}}
        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<{{!
            }}{{#function:return_type}}{{>lib/type}}{{/function:return_type}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + ::std::marker::Send + 'static>> {
            self.as_ref().{{function:rust_name}}(
                {{#function:args}}
                arg_{{field:name}},
                {{/function:args}}
            )
        }
        {{/function:starts_interaction?}}{{#function:starts_interaction?}}
        fn {{function:rust_name}}(
            &self,
        ) -> ::std::result::Result<{{!
        }}::std::sync::Arc<dyn {{function:interaction_name}} + ::std::marker::Send + 'static>, {{!
        }}::anyhow::Error> {
            self.as_ref().{{function:rust_name}}()
        }
        {{/function:starts_interaction?}}{{/service:rustFunctions}}
    }

    #[derive(Clone)]
    pub struct make_{{service: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: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:name}} + ::std::marker::Send + '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:name}} + ::std::marker::Send + '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:name}}Impl::<P, T, S>::new(transport))
        }
    }

    impl<T> dyn {{service:name}}Ext<T>
    where
        T: ::fbthrift::Transport,
    {
        pub fn new<P>(
            protocol: P,
            transport: T,
        ) -> ::std::sync::Arc<impl {{service:name}}Ext<T> + ::std::marker::Send + '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:name}}Ext<T> + ::std::marker::Send + '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:name}}Impl::<P, T, S>::new(transport))
        }
    }

    pub type {{service:name}}DynClient = <make_{{service:name}} as ::fbthrift::ClientFactory>::Api;
    pub type {{service:name}}Client = ::std::sync::Arc<{{service: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:name}} {
        type Api = dyn {{service: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 + ::std::marker::Sync,
            P::Deserializer: ::std::marker::Send,
            S: ::fbthrift::help::Spawner,
        {
            <dyn {{service:name}}>::with_spawner(protocol, transport, spawner)
        }
    }

{{!newline}}
