cratestack_client_rust/runtime/
handle.rs1use std::sync::Arc;
2
3use cratestack_codec_cbor::CborCodec;
4#[cfg(feature = "codec-json")]
5use cratestack_codec_json::JsonCodec;
6use reqwest::Url;
7
8use crate::client::CratestackClient;
9use crate::config::ClientConfig;
10use crate::error::ClientError;
11use crate::runtime::sealing::{Sealing, bad_input};
12use crate::runtime::transport::RuntimeTransportClient;
13use crate::runtime::wire::{
14 RuntimeCodecConfig, RuntimeConfigWire, RuntimeErrorCode, RuntimeErrorWire, RuntimeRequestWire,
15 RuntimeResponseWire, RuntimeStateStoreConfig,
16};
17use crate::state::{
18 ClientStateStore, InMemoryStateStore, JsonFileStateStore, PersistedClientState,
19};
20use crate::streaming_callback::RuntimeChunkWire;
21
22pub struct RuntimeHandle {
23 runtime: tokio::runtime::Runtime,
24 pub(crate) client: RuntimeTransportClient,
25}
26
27impl RuntimeHandle {
28 pub fn new(config: RuntimeConfigWire) -> Result<Self, RuntimeErrorWire> {
32 Self::build(config, Sealing::None)
33 }
34
35 #[cfg(feature = "cose")]
46 pub fn with_envelope(
47 config: RuntimeConfigWire,
48 envelope: crate::envelope::ClientEnvelope,
49 contracts: cratestack_core::OpContracts,
50 ) -> Result<Self, RuntimeErrorWire> {
51 Self::build(config, Sealing::Cose(envelope, contracts))
52 }
53
54 fn build(config: RuntimeConfigWire, sealing: Sealing) -> Result<Self, RuntimeErrorWire> {
55 let base_url = Url::parse(&config.base_url).map_err(|error| {
56 bad_input(format!("invalid base URL '{}': {error}", config.base_url))
57 })?;
58 let state_store: Arc<dyn ClientStateStore> = match config.state_store {
59 RuntimeStateStoreConfig::InMemory => Arc::new(InMemoryStateStore::default()),
60 RuntimeStateStoreConfig::JsonFile { path } => Arc::new(JsonFileStateStore::new(path)),
61 };
62 sealing.agrees_with(&config.transport.envelope)?;
63 let client = match config.transport.codec {
64 RuntimeCodecConfig::Cbor => RuntimeTransportClient::Cbor(
65 sealing.apply(
66 CratestackClient::new(ClientConfig::new(base_url.clone()), CborCodec)
67 .with_state_store(state_store.clone()),
68 )?,
69 ),
70 #[cfg(feature = "codec-json")]
71 RuntimeCodecConfig::Json => RuntimeTransportClient::Json(
72 sealing.apply(
73 CratestackClient::new(ClientConfig::new(base_url), JsonCodec)
74 .with_state_store(state_store),
75 )?,
76 ),
77 #[cfg(not(feature = "codec-json"))]
78 RuntimeCodecConfig::Json => {
79 let _ = state_store;
80 return Err(bad_input(
81 "JSON codec is not compiled in (cratestack-client-rust built with \
82 `--no-default-features` or without the `codec-json` feature)"
83 .to_owned(),
84 ));
85 }
86 };
87 let runtime = tokio::runtime::Builder::new_current_thread()
88 .enable_all()
89 .build()
90 .map_err(|error| RuntimeErrorWire {
91 code: RuntimeErrorCode::State,
92 http_status: None,
93 message: format!("failed to build runtime: {error}"),
94 remote_code: None,
95 remote_body: None,
96 })?;
97
98 Ok(Self { runtime, client })
99 }
100
101 pub fn execute(
102 &self,
103 request: RuntimeRequestWire,
104 ) -> Result<RuntimeResponseWire, RuntimeErrorWire> {
105 self.runtime
106 .block_on(self.client.execute_raw(request))
107 .map_err(RuntimeErrorWire::from)
108 }
109
110 pub fn execute_streamed<F>(
121 &self,
122 request: RuntimeRequestWire,
123 on_chunk: F,
124 ) -> Result<(), RuntimeErrorWire>
125 where
126 F: FnMut(RuntimeChunkWire) -> bool + Send,
127 {
128 self.runtime
129 .block_on(self.client.execute_streamed(request, on_chunk))
130 }
131
132 pub fn state(&self) -> Result<PersistedClientState, ClientError> {
133 self.client.state()
134 }
135}