parsec_client/core/
operation_client.rs

1// Copyright 2020 Contributors to the Parsec project.
2// SPDX-License-Identifier: Apache-2.0
3//! Operation-level client
4#![allow(dead_code)]
5
6use super::request_client::RequestClient;
7use crate::auth::Authentication;
8use crate::error::{ClientErrorKind, Error, Result};
9use derivative::Derivative;
10use parsec_interface::operations::{Convert, NativeOperation, NativeResult};
11use parsec_interface::operations_protobuf::ProtobufConverter;
12use parsec_interface::requests::{
13    request::RequestHeader, Opcode, ProviderId, Request, Response, ResponseStatus,
14};
15use std::convert::TryInto;
16
17/// Low-level client optimised for communicating with the Parsec service at an operation level.
18///
19/// Usage is recommended when fine control over how operations are wrapped and processed is needed.
20#[derive(Derivative)]
21#[derivative(Debug)]
22pub struct OperationClient {
23    /// Converter that manages request body conversions
24    ///
25    /// Defaults to a Protobuf converter
26    #[derivative(Debug = "ignore")]
27    pub content_converter: Box<dyn Convert + Send + Sync>,
28    /// Converter that manages response body conversions
29    ///
30    /// Defaults to a Protobuf converter
31    #[derivative(Debug = "ignore")]
32    pub accept_converter: Box<dyn Convert + Send + Sync>,
33    /// Client for request and response objects
34    pub request_client: RequestClient,
35}
36
37#[allow(clippy::new_without_default)]
38impl OperationClient {
39    /// Creates an OperationClient instance. The request client uses a timeout of 5
40    /// seconds on reads and writes on the socket. It uses the version 1.0 wire protocol
41    /// to form requests, the direct authentication method and protobuf format as
42    /// content type.
43    pub fn new() -> Result<OperationClient> {
44        Ok(OperationClient {
45            request_client: RequestClient::new()?,
46            ..Default::default()
47        })
48    }
49
50    fn operation_to_request(
51        &self,
52        operation: NativeOperation,
53        provider: ProviderId,
54        auth: &Authentication,
55    ) -> Result<Request> {
56        let opcode = operation.opcode();
57        let body = self
58            .content_converter
59            .operation_to_body(operation)
60            .map_err(ClientErrorKind::Interface)?;
61        let header = RequestHeader {
62            provider,
63            session: 0, // no provisioning of sessions yet
64            content_type: self.content_converter.body_type(),
65            accept_type: self.accept_converter.body_type(),
66            auth_type: auth.auth_type(),
67            opcode,
68        };
69
70        Ok(Request {
71            header,
72            body,
73            auth: auth.try_into()?,
74        })
75    }
76
77    fn response_to_result(
78        &self,
79        response: Response,
80        expected_opcode: Opcode,
81    ) -> Result<NativeResult> {
82        let status = response.header.status;
83        if status != ResponseStatus::Success {
84            return Err(Error::Service(status));
85        }
86        let opcode = response.header.opcode;
87        if opcode != expected_opcode {
88            return Err(Error::Client(ClientErrorKind::InvalidServiceResponseType));
89        }
90        Ok(self
91            .accept_converter
92            .body_to_result(response.body, opcode)
93            .map_err(ClientErrorKind::Interface)?)
94    }
95
96    /// Send an operation to a specific provider and get a result.
97    ///
98    /// # Errors
99    ///
100    /// If the conversions between operation to request or between response to result fail, returns
101    /// a serializing or deserializing error. Returns an error if the operation itself failed. If the
102    /// opcode is different between request and response, `InvalidServiceResponseType` is returned.
103    pub fn process_operation(
104        &self,
105        operation: NativeOperation,
106        provider: ProviderId,
107        auth: &Authentication,
108    ) -> Result<NativeResult> {
109        let req_opcode = operation.opcode();
110        let request = self.operation_to_request(operation, provider, auth)?;
111
112        let response = self.request_client.process_request(request)?;
113        self.response_to_result(response, req_opcode)
114    }
115}
116
117impl Default for OperationClient {
118    fn default() -> Self {
119        OperationClient {
120            content_converter: Box::from(ProtobufConverter {}),
121            accept_converter: Box::from(ProtobufConverter {}),
122            request_client: Default::default(),
123        }
124    }
125}
126
127/// Configuration methods for controlling communication with the service.
128impl crate::BasicClient {
129    /// Set the converter used for request bodies handled by this client.
130    ///
131    /// By default Protobuf will be used for this.
132    pub fn set_request_body_converter(
133        &mut self,
134        content_converter: Box<dyn Convert + Send + Sync>,
135    ) {
136        self.op_client.content_converter = content_converter;
137    }
138
139    /// Set the converter used for response bodies handled by this client.
140    ///
141    /// By default Protobuf will be used for this.
142    pub fn set_response_body_converter(
143        &mut self,
144        accept_converter: Box<dyn Convert + Send + Sync>,
145    ) {
146        self.op_client.accept_converter = accept_converter;
147    }
148}