Skip to content

Commit dbadeca

Browse files
Merge pull request #211 from code0-tech/#210-new-emiter-sys
implemented taurus emitter pattern
2 parents b3ba790 + b2e4ba9 commit dbadeca

2 files changed

Lines changed: 157 additions & 48 deletions

File tree

adapter/rest/src/main.rs

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use base::{
22
runner::{ServerContext, ServerRunner},
3-
store::FlowIdentifyResult,
3+
store::{FlowExecutionResult, FlowIdentifyResult},
44
traits::Server as ServerTrait,
55
};
66
use http_body_util::{BodyExt, Full};
@@ -18,8 +18,7 @@ use tokio::net::TcpListener;
1818
use tonic::async_trait;
1919
use tucana::shared::{
2020
AdapterConfiguration, RuntimeFeature, Struct, Translation, ValidationFlow, Value,
21-
helper::{path::get_string, value::ToValue},
22-
value::Kind,
21+
helper::value::ToValue, value::Kind,
2322
};
2423

2524
use crate::response::{error_to_http_response, value_to_http_response};
@@ -79,36 +78,22 @@ async fn execute_flow_to_hyper_response(
7978
body: Value,
8079
store: Arc<base::store::AdapterStore>,
8180
) -> Response<Full<Bytes>> {
82-
match store.validate_and_execute_flow(flow, Some(body)).await {
83-
Some(result) => {
84-
log::debug!("Received Result: {:?}", result);
85-
86-
if let Value {
87-
kind:
88-
Some(Kind::StructValue(Struct {
89-
fields: result_fields,
90-
})),
91-
} = &result
92-
&& result_fields.contains_key("name")
93-
&& result_fields.contains_key("message")
94-
&& !result_fields.contains_key("payload")
95-
&& !result_fields.contains_key("headers")
96-
{
97-
log::debug!("Detected a RuntimeError");
98-
let name = get_string("name", &result);
99-
let message = get_string("message", &result);
100-
101-
return error_to_http_response(
102-
StatusCode::INTERNAL_SERVER_ERROR,
103-
format!("{}: {}", name.unwrap(), message.unwrap()).as_str(),
104-
);
105-
}
106-
81+
match store.execute_flow_with_emitter(flow, Some(body)).await {
82+
FlowExecutionResult::Ongoing(result) => {
83+
log::debug!("Received first ongoing response from emitter");
10784
value_to_http_response(result)
10885
}
109-
None => {
110-
log::error!("flow execution failed");
111-
error_to_http_response(StatusCode::INTERNAL_SERVER_ERROR, "Flow execution failed")
86+
FlowExecutionResult::Failed => {
87+
log::error!("Flow execution failed event received from emitter");
88+
error_to_http_response(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
89+
}
90+
FlowExecutionResult::FinishedWithoutOngoing => Response::builder()
91+
.status(StatusCode::NO_CONTENT)
92+
.body(Full::new(Bytes::new()))
93+
.unwrap(),
94+
FlowExecutionResult::TransportError => {
95+
log::error!("Flow execution transport error");
96+
error_to_http_response(StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")
11297
}
11398
}
11499
}

crates/base/src/store.rs

Lines changed: 141 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ use crate::traits::IdentifiableFlow;
22
use async_nats::jetstream::kv::Config;
33
use futures_lite::StreamExt;
44
use prost::Message;
5-
use tucana::shared::{ExecutionFlow, ValidationFlow, Value};
5+
use tucana::shared::{
6+
ExecutionFlow, Struct, ValidationFlow, Value,
7+
value::Kind::{self, StructValue},
8+
};
9+
10+
const EMITTER_TOPIC_PREFIX: &str = "runtime.emitter";
11+
const EMITTER_WAIT_TIMEOUT_SECONDS: u64 = 30;
612

713
pub struct AdapterStore {
814
client: async_nats::Client,
@@ -15,6 +21,13 @@ pub enum FlowIdentifyResult {
1521
Multiple(Vec<ValidationFlow>),
1622
}
1723

24+
pub enum FlowExecutionResult {
25+
Ongoing(Value),
26+
Failed,
27+
FinishedWithoutOngoing,
28+
TransportError,
29+
}
30+
1831
impl AdapterStore {
1932
pub async fn from_url(url: String, bucket: String) -> Self {
2033
let client = match async_nats::connect(url).await {
@@ -118,32 +131,109 @@ impl AdapterStore {
118131
flow: ValidationFlow,
119132
input_value: Option<Value>,
120133
) -> Option<Value> {
134+
match self.execute_flow_with_emitter(flow, input_value).await {
135+
FlowExecutionResult::Ongoing(value) => Some(value),
136+
FlowExecutionResult::Failed
137+
| FlowExecutionResult::FinishedWithoutOngoing
138+
| FlowExecutionResult::TransportError => None,
139+
}
140+
}
141+
142+
pub async fn execute_flow_with_emitter(
143+
&self,
144+
flow: ValidationFlow,
145+
input_value: Option<Value>,
146+
) -> FlowExecutionResult {
121147
// TODO: Replace body vaidation with triangulus when its ready
122-
let uuid = uuid::Uuid::new_v4().to_string();
148+
let execution_id = uuid::Uuid::new_v4().to_string();
123149
let flow_id = flow.flow_id;
124150
let execution_flow: ExecutionFlow =
125151
Self::convert_validation_flow(flow, input_value.clone());
126152
let bytes = execution_flow.encode_to_vec();
127-
let topic = format!("execution.{}", uuid);
153+
let execution_topic = format!("execution.{}", execution_id);
154+
let emitter_topic = format!("{}.{}", EMITTER_TOPIC_PREFIX, execution_id);
155+
128156
log::info!(
129157
"Requesting execution of flow {} with execution id {}",
130158
flow_id,
131-
uuid
159+
execution_id
132160
);
133-
log::debug!("Flow Input for Execution ({}) is {:?}", uuid, input_value);
134-
let result = self.client.request(topic, bytes.into()).await;
135-
136-
match result {
137-
Ok(message) => match Value::decode(message.payload) {
138-
Ok(value) => Some(value),
139-
Err(err) => {
140-
log::error!("Failed to decode response from NATS server: {:?}", err);
141-
None
142-
}
143-
},
161+
log::debug!(
162+
"Flow Input for Execution ({}) is {:?}",
163+
execution_id,
164+
input_value
165+
);
166+
167+
let mut subscriber = match self.client.subscribe(emitter_topic.clone()).await {
168+
Ok(subscriber) => subscriber,
144169
Err(err) => {
145-
log::error!("Failed to send request to NATS server: {:?}", err);
146-
None
170+
log::error!(
171+
"Failed to subscribe to emitter topic '{}' for flow {}: {:?}",
172+
emitter_topic,
173+
flow_id,
174+
err
175+
);
176+
return FlowExecutionResult::TransportError;
177+
}
178+
};
179+
180+
if let Err(err) = self
181+
.client
182+
.publish(execution_topic.clone(), bytes.into())
183+
.await
184+
{
185+
log::error!(
186+
"Failed to publish flow {} to execution topic '{}': {:?}",
187+
flow_id,
188+
execution_topic,
189+
err
190+
);
191+
return FlowExecutionResult::TransportError;
192+
}
193+
194+
loop {
195+
let next_message = tokio::time::timeout(
196+
std::time::Duration::from_secs(EMITTER_WAIT_TIMEOUT_SECONDS),
197+
subscriber.next(),
198+
)
199+
.await;
200+
201+
let message = match next_message {
202+
Ok(Some(message)) => message,
203+
Ok(None) => {
204+
log::error!(
205+
"Emitter subscription '{}' closed before execution completed",
206+
emitter_topic
207+
);
208+
return FlowExecutionResult::TransportError;
209+
}
210+
Err(_) => {
211+
log::error!(
212+
"Timed out waiting for emitter events on '{}' after {}s",
213+
emitter_topic,
214+
EMITTER_WAIT_TIMEOUT_SECONDS
215+
);
216+
return FlowExecutionResult::TransportError;
217+
}
218+
};
219+
220+
let Some((emit_type, payload)) = Self::decode_emit_message(message.payload.as_ref())
221+
else {
222+
continue;
223+
};
224+
225+
match emit_type.as_str() {
226+
"starting" => {}
227+
"ongoing" => return FlowExecutionResult::Ongoing(payload),
228+
"failed" => return FlowExecutionResult::Failed,
229+
"finished" => return FlowExecutionResult::FinishedWithoutOngoing,
230+
other => {
231+
log::warn!(
232+
"Received unknown emitter event '{}' on '{}'",
233+
other,
234+
emitter_topic
235+
);
236+
}
147237
}
148238
}
149239
}
@@ -174,4 +264,38 @@ impl AdapterStore {
174264
}
175265
true
176266
}
267+
268+
fn decode_emit_message(bytes: &[u8]) -> Option<(String, Value)> {
269+
let decoded = match Value::decode(bytes) {
270+
Ok(value) => value,
271+
Err(err) => {
272+
log::error!("Failed to decode emitter payload: {:?}", err);
273+
return None;
274+
}
275+
};
276+
277+
let Value {
278+
kind: Some(StructValue(Struct { fields })),
279+
} = decoded
280+
else {
281+
log::warn!("Emitter payload was not a struct value");
282+
return None;
283+
};
284+
285+
let Some(emit_type) = fields.get("emit_type") else {
286+
log::warn!("Emitter payload is missing 'emit_type'");
287+
return None;
288+
};
289+
let Some(payload) = fields.get("payload") else {
290+
log::warn!("Emitter payload is missing 'payload'");
291+
return None;
292+
};
293+
294+
let Some(Kind::StringValue(emit_type_str)) = emit_type.kind.as_ref() else {
295+
log::warn!("Emitter payload field 'emit_type' was not a string");
296+
return None;
297+
};
298+
299+
Some((emit_type_str.clone(), payload.clone()))
300+
}
177301
}

0 commit comments

Comments
 (0)