Skip to main content

gestalt/public/
client.rs

1//! Factory for the public Gestalt transport client.
2
3use std::sync::Arc;
4
5use crate::public::auth::Auth;
6use crate::public::generated::app_client::{
7    AgentClient, AppClient, AuthorizationClient, ExternalCredentialsClient, IdentityClient,
8    IndexedDBClient, WorkflowClient,
9};
10use crate::public::generated::rpc_support::GestaltError;
11use crate::public::grpc_transport::{GrpcTransport, dial_public_grpc};
12use crate::public::rest_transport::RestTransport;
13use crate::rpc_support::gestalt_error_code;
14
15/// Transport selector for [`create_gestalt_client`].
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub enum Transport {
18    /// REST over `/api/v2`.
19    Rest,
20    /// gRPC over the configured address.
21    Grpc,
22}
23
24/// Returns the REST transport selector.
25pub fn rest() -> Transport {
26    Transport::Rest
27}
28
29/// Returns the gRPC transport selector.
30pub fn grpc() -> Transport {
31    Transport::Grpc
32}
33
34/// App-only external public Gestalt client variants.
35pub enum GestaltClient {
36    /// REST-backed client.
37    Rest(AppClient<RestTransport>),
38    /// gRPC-backed client.
39    Grpc(Box<AppClient<GrpcTransport>>),
40}
41
42/// REST-backed public Gestalt client (five REST-capable services).
43pub struct RestGestaltClient {
44    /// App service client.
45    pub app: AppClient<RestTransport>,
46    /// Agent service client.
47    pub agent: AgentClient<RestTransport>,
48    /// Workflow service client.
49    pub workflow: WorkflowClient<RestTransport>,
50    /// Identity service client.
51    pub identity: IdentityClient<RestTransport>,
52    /// Authorization service client.
53    pub authorization: AuthorizationClient<RestTransport>,
54}
55
56impl RestGestaltClient {
57    /// Releases transport resources when the client owns them.
58    pub fn close(self) {}
59}
60
61/// gRPC-backed public Gestalt client (all seven public services).
62pub struct GrpcGestaltClient {
63    /// App service client.
64    pub app: AppClient<GrpcTransport>,
65    /// Agent service client.
66    pub agent: AgentClient<GrpcTransport>,
67    /// Workflow service client.
68    pub workflow: WorkflowClient<GrpcTransport>,
69    /// Identity service client.
70    pub identity: IdentityClient<GrpcTransport>,
71    /// Authorization service client.
72    pub authorization: AuthorizationClient<GrpcTransport>,
73    /// IndexedDB service client.
74    pub indexed_db: IndexedDBClient<GrpcTransport>,
75    /// External credentials service client.
76    pub external_credentials: ExternalCredentialsClient<GrpcTransport>,
77}
78
79impl GrpcGestaltClient {
80    /// Releases transport resources when the client owns them.
81    pub fn close(self) {}
82}
83
84/// Creates an App-only public Gestalt client for the requested transport.
85pub async fn create_gestalt_client<A: Auth + 'static>(
86    address: impl Into<String>,
87    auth: A,
88    transport: Transport,
89) -> Result<GestaltClient, GestaltError> {
90    let address = normalize_address(address.into())?;
91    let auth: Arc<dyn Auth> = Arc::new(auth);
92    match transport {
93        Transport::Rest => Ok(GestaltClient::Rest(AppClient::new(RestTransport::new(
94            address,
95            Arc::clone(&auth),
96        )))),
97        Transport::Grpc => {
98            let channel = dial_public_grpc(&address)?.connect_lazy();
99            Ok(GestaltClient::Grpc(Box::new(AppClient::new(
100                GrpcTransport::new(channel, auth),
101            ))))
102        }
103    }
104}
105
106/// Creates a REST public Gestalt client.
107pub async fn create_rest_gestalt_client<A: Auth + 'static>(
108    address: impl Into<String>,
109    auth: A,
110) -> Result<RestGestaltClient, GestaltError> {
111    let address = normalize_address(address.into())?;
112    let auth: Arc<dyn Auth> = Arc::new(auth);
113    Ok(bind_rest(RestTransport::new(address, auth)))
114}
115
116/// Creates a gRPC public Gestalt client.
117pub async fn create_grpc_gestalt_client<A: Auth + 'static>(
118    address: impl Into<String>,
119    auth: A,
120) -> Result<GrpcGestaltClient, GestaltError> {
121    let address = normalize_address(address.into())?;
122    let auth: Arc<dyn Auth> = Arc::new(auth);
123    let channel = dial_public_grpc(&address)?.connect_lazy();
124    Ok(bind_grpc(GrpcTransport::new(channel, auth)))
125}
126
127fn bind_rest(transport: RestTransport) -> RestGestaltClient {
128    RestGestaltClient {
129        app: AppClient::new(transport.clone()),
130        agent: AgentClient::new(transport.clone()),
131        workflow: WorkflowClient::new(transport.clone()),
132        identity: IdentityClient::new(transport.clone()),
133        authorization: AuthorizationClient::new(transport.clone()),
134    }
135}
136
137fn bind_grpc(transport: GrpcTransport) -> GrpcGestaltClient {
138    GrpcGestaltClient {
139        app: AppClient::new(transport.clone()),
140        agent: AgentClient::new(transport.clone()),
141        workflow: WorkflowClient::new(transport.clone()),
142        identity: IdentityClient::new(transport.clone()),
143        authorization: AuthorizationClient::new(transport.clone()),
144        indexed_db: IndexedDBClient::new(transport.clone()),
145        external_credentials: ExternalCredentialsClient::new(transport),
146    }
147}
148
149fn normalize_address(address: String) -> Result<String, GestaltError> {
150    let address = address.trim();
151    if address.is_empty() {
152        return Err(GestaltError::new(
153            gestalt_error_code::INVALID_ARGUMENT,
154            "address is required for external clients (use gestalt_from_context for bound provider access)",
155        ));
156    }
157    Ok(address.trim_end_matches('/').to_string())
158}