Skip to content

Commit 944e78e

Browse files
committed
refactor: Make submit return ok if parameters are the same
1 parent acedfe3 commit 944e78e

4 files changed

Lines changed: 120 additions & 61 deletions

File tree

proto/obelisk.proto

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,12 @@ message SubmitRequest {
175175
}
176176

177177
message SubmitResponse {
178+
enum Outcome {
179+
CREATED = 0;
180+
EXISTS_WITH_SAME_PARAMETERS = 1;
181+
}
182+
183+
Outcome outcome = 1;
178184
}
179185

180186
message StubRequest {

src/command/server.rs

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ use concepts::component_id::InputContentDigest;
5454
use concepts::storage::CreateRequest;
5555
use concepts::storage::DbConnection;
5656
use concepts::storage::DbErrorWrite;
57+
use concepts::storage::DbErrorWriteNonRetriable;
5758
use concepts::storage::DbExecutor;
5859
use concepts::storage::DbPool;
5960
use concepts::storage::DbPoolCloseable;
@@ -81,7 +82,6 @@ use grpc::grpc_gen::GetStatusResponse;
8182
use grpc::grpc_gen::get_status_response::Message;
8283
use grpc::grpc_mapping::TonicServerResultExt;
8384
use grpc::grpc_mapping::db_error_read_to_status;
84-
use grpc::grpc_mapping::db_error_write_to_status;
8585
use hashbrown::HashMap;
8686
use itertools::Either;
8787
use serde_json::json;
@@ -192,20 +192,15 @@ pub(crate) enum SubmitError {
192192
FunctionNotFound,
193193
#[error("{0}")]
194194
ParamsInvalid(String),
195+
#[error("execution already exists with the same id and different parameters")]
196+
Conflict,
195197
#[error(transparent)]
196198
DbErrorWrite(DbErrorWrite),
197199
}
198-
impl From<SubmitError> for tonic::Status {
199-
fn from(value: SubmitError) -> Self {
200-
match value {
201-
SubmitError::ExecutionIdMustBeTopLevel => tonic::Status::invalid_argument(
202-
"argument `execution_id` must be a top-level execution id",
203-
),
204-
SubmitError::FunctionNotFound => tonic::Status::not_found("function not found"),
205-
SubmitError::ParamsInvalid(reason) => tonic::Status::invalid_argument(reason),
206-
SubmitError::DbErrorWrite(db_err) => db_error_write_to_status(&db_err),
207-
}
208-
}
200+
201+
pub(crate) enum SubmitOutcome {
202+
Created,
203+
ExistsWithSameParameters,
209204
}
210205

211206
pub(crate) async fn submit(
@@ -214,7 +209,7 @@ pub(crate) async fn submit(
214209
ffqn: FunctionFqn,
215210
mut params: Vec<serde_json::Value>,
216211
component_registry_ro: &ComponentConfigRegistryRO,
217-
) -> Result<(), SubmitError> {
212+
) -> Result<SubmitOutcome, SubmitError> {
218213
let span = Span::current();
219214
span.record("execution_id", tracing::field::display(&execution_id));
220215
if !execution_id.is_top_level() {
@@ -317,21 +312,34 @@ pub(crate) async fn submit(
317312

318313
// Associate the (root) request execution with the request span. Makes possible to find the trace by execution id.
319314
let metadata = concepts::ExecutionMetadata::from_parent_span(&span);
320-
db_connection
315+
let res = db_connection
321316
.create(CreateRequest {
322317
created_at,
323318
execution_id: execution_id.clone(),
324319
metadata,
325320
ffqn: ffqn.clone(),
326-
params,
321+
params: params.clone(),
327322
parent: None,
328323
scheduled_at,
329324
component_id: component_id.clone(),
330325
scheduled_by: None,
331326
})
332-
.await
333-
.map_err(SubmitError::DbErrorWrite)?;
334-
Ok(())
327+
.await;
328+
match res {
329+
Ok(_) => Ok(SubmitOutcome::Created),
330+
Err(DbErrorWrite::NonRetriable(DbErrorWriteNonRetriable::Conflict)) => {
331+
let create_req = db_connection
332+
.get_create_request(&execution_id)
333+
.await
334+
.map_err(|err| SubmitError::DbErrorWrite(err.into()))?;
335+
if create_req.params == params {
336+
Ok(SubmitOutcome::ExistsWithSameParameters)
337+
} else {
338+
Err(SubmitError::Conflict)
339+
}
340+
}
341+
Err(err) => Err(SubmitError::DbErrorWrite(err)),
342+
}
335343
}
336344

337345
pub(crate) async fn poll_status(

src/server/grpc.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::command::server;
22
use crate::command::server::ComponentConfigRegistryRO;
33
use crate::command::server::ComponentSourceMap;
4+
use crate::command::server::SubmitError;
45
use chrono::DateTime;
56
use chrono::Utc;
67
use concepts::ComponentId;
@@ -30,6 +31,7 @@ use grpc::grpc_gen::get_status_response::Message;
3031
use grpc::grpc_mapping::TonicServerOptionExt;
3132
use grpc::grpc_mapping::TonicServerResultExt;
3233
use grpc::grpc_mapping::convert_length;
34+
use grpc::grpc_mapping::db_error_write_to_status;
3335
use grpc::grpc_mapping::from_execution_event_to_grpc;
3436
use grpc_gen::ExecutionSummary;
3537
use serde::Deserialize;
@@ -131,7 +133,7 @@ impl grpc_gen::execution_repository_server::ExecutionRepository for GrpcServer {
131133
.vec
132134
};
133135

134-
server::submit(
136+
let outcome = server::submit(
135137
self.db_pool.connection().as_ref(),
136138
execution_id,
137139
ffqn,
@@ -140,7 +142,15 @@ impl grpc_gen::execution_repository_server::ExecutionRepository for GrpcServer {
140142
)
141143
.await?;
142144

143-
let resp = grpc_gen::SubmitResponse {};
145+
let resp = grpc_gen::SubmitResponse {
146+
outcome: match outcome {
147+
server::SubmitOutcome::Created => grpc_gen::submit_response::Outcome::Created,
148+
server::SubmitOutcome::ExistsWithSameParameters => {
149+
grpc_gen::submit_response::Outcome::ExistsWithSameParameters
150+
}
151+
}
152+
.into(),
153+
};
144154
Ok(tonic::Response::new(resp))
145155
}
146156

@@ -755,3 +765,17 @@ fn list_fns(functions: Vec<FunctionMetadata>) -> Vec<grpc_gen::FunctionDetail> {
755765
}
756766
vec
757767
}
768+
769+
impl From<SubmitError> for tonic::Status {
770+
fn from(value: SubmitError) -> Self {
771+
match value {
772+
SubmitError::ExecutionIdMustBeTopLevel => tonic::Status::invalid_argument(
773+
"argument `execution_id` must be a top-level execution id",
774+
),
775+
SubmitError::FunctionNotFound => tonic::Status::not_found("function not found"),
776+
SubmitError::ParamsInvalid(reason) => tonic::Status::invalid_argument(reason),
777+
err @ SubmitError::Conflict => tonic::Status::already_exists(err.to_string()),
778+
SubmitError::DbErrorWrite(db_err) => db_error_write_to_status(&db_err),
779+
}
780+
}
781+
}

src/server/web_api.rs

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
command::server::{self, ComponentConfigRegistryRO, SubmitError},
2+
command::server::{self, ComponentConfigRegistryRO, SubmitError, SubmitOutcome},
33
server::web_api::components::{component_wit, components_list},
44
};
55
use axum::{
@@ -15,9 +15,9 @@ use concepts::{
1515
component_id::InputContentDigest,
1616
prefixed_ulid::{DelayId, ExecutionIdDerived},
1717
storage::{
18-
self, CancelOutcome, DbErrorGeneric, DbErrorRead, DbErrorWrite, DbErrorWriteNonRetriable,
19-
DbPool, ExecutionListPagination, ExecutionRequest, ExecutionWithState, Pagination,
20-
PendingState, Version, VersionType,
18+
self, CancelOutcome, DbErrorGeneric, DbErrorRead, DbErrorWrite, DbPool,
19+
ExecutionListPagination, ExecutionRequest, ExecutionWithState, Pagination, PendingState,
20+
Version, VersionType,
2121
},
2222
time::{ClockFn as _, Now},
2323
};
@@ -49,7 +49,7 @@ fn v1_router() -> Router<Arc<WebApiState>> {
4949
.route("/delays/{delay-id}/cancel", routing::put(delay_cancel))
5050
.route("/execution-id", routing::get(execution_id_generate))
5151
.route("/executions", routing::get(executions_list))
52-
.route("/executions/submit", routing::post(execution_submit_post))
52+
.route("/executions", routing::post(execution_submit_post))
5353
.route(
5454
"/executions/{execution-id}/cancel",
5555
routing::put(execution_cancel),
@@ -513,13 +513,7 @@ async fn execution_submit_put(
513513
accept: AcceptHeader,
514514
Json(payload): Json<ExecutionPutPayload>,
515515
) -> Result<Response, HttpResponse> {
516-
execution_submit(execution_id.clone(), state, payload, accept).await?;
517-
Ok(HttpResponse {
518-
status: StatusCode::CREATED,
519-
message: execution_id.to_string(),
520-
accept,
521-
}
522-
.into_response())
516+
execution_submit(execution_id, state, payload, accept).await
523517
}
524518

525519
async fn execution_submit_post(
@@ -528,43 +522,40 @@ async fn execution_submit_post(
528522
Json(payload): Json<ExecutionPutPayload>,
529523
) -> Result<Response, HttpResponse> {
530524
let execution_id = ExecutionId::generate();
531-
execution_submit(execution_id.clone(), state, payload, accept).await?;
532-
Ok(HttpResponse {
533-
status: StatusCode::CREATED,
534-
message: execution_id.to_string(),
535-
accept,
536-
}
537-
.into_response())
525+
execution_submit(execution_id, state, payload, accept).await
538526
}
539527

540528
async fn execution_submit(
541529
execution_id: ExecutionId,
542530
state: State<Arc<WebApiState>>,
543531
payload: ExecutionPutPayload,
544532
accept: AcceptHeader,
545-
) -> Result<(), HttpResponse> {
546-
server::submit(
547-
state.db_pool.connection().as_ref(),
548-
execution_id,
533+
) -> Result<Response, HttpResponse> {
534+
let conn = state.db_pool.connection();
535+
let res = server::submit(
536+
conn.as_ref(),
537+
execution_id.clone(),
549538
payload.ffqn,
550539
payload.params,
551540
&state.component_registry_ro,
552541
)
553-
.await
554-
.map_err(|err| match err {
555-
SubmitError::DbErrorWrite(DbErrorWrite::NonRetriable(
556-
DbErrorWriteNonRetriable::Conflict,
557-
)) => HttpResponse {
558-
status: StatusCode::CONFLICT,
559-
message: "already exists".to_string(),
542+
.await;
543+
544+
match res {
545+
Ok(SubmitOutcome::Created) => Ok(HttpResponse {
546+
status: StatusCode::CREATED,
547+
message: execution_id.to_string(),
560548
accept,
561-
},
562-
err => HttpResponse {
563-
status: StatusCode::INTERNAL_SERVER_ERROR,
564-
message: err.to_string(),
549+
}
550+
.into_response()),
551+
Ok(SubmitOutcome::ExistsWithSameParameters) => Ok(HttpResponse {
552+
status: StatusCode::OK,
553+
message: execution_id.to_string(),
565554
accept,
566-
},
567-
})
555+
}
556+
.into_response()),
557+
Err(err) => Err(ErrorWrapper(err, accept).into()),
558+
}
568559
}
569560

570561
pub(crate) mod components {
@@ -601,7 +592,10 @@ pub(crate) mod components {
601592
state: State<Arc<WebApiState>>,
602593
) -> Result<Response, HttpResponse> {
603594
let Some(wit) = state.component_registry_ro.get_wit(&digest) else {
604-
return Err(HttpResponse::not_found(AcceptHeader::Text));
595+
return Err(HttpResponse::not_found(
596+
AcceptHeader::Text,
597+
Some("component"),
598+
));
605599
};
606600
Ok(if let Some(wit) = wit {
607601
wit.to_string().into_response()
@@ -778,10 +772,14 @@ impl HttpResponse {
778772
}
779773
}
780774

781-
fn not_found(accept: AcceptHeader) -> Self {
775+
fn not_found(accept: AcceptHeader, what: Option<&str>) -> Self {
782776
HttpResponse {
783777
status: StatusCode::NOT_FOUND,
784-
message: "not found".to_string(),
778+
message: if let Some(what) = what {
779+
format!("{what} not found")
780+
} else {
781+
"not found".to_string()
782+
},
785783
accept,
786784
}
787785
}
@@ -817,7 +815,7 @@ impl From<ErrorWrapper<DbErrorRead>> for HttpResponse {
817815
fn from(value: ErrorWrapper<DbErrorRead>) -> Self {
818816
let accept = value.1;
819817
let (status, message) = match value.0 {
820-
DbErrorRead::NotFound => return HttpResponse::not_found(accept),
818+
DbErrorRead::NotFound => return HttpResponse::not_found(accept, None),
821819
DbErrorRead::Generic(err) => (StatusCode::SERVICE_UNAVAILABLE, err.to_string()),
822820
};
823821
HttpResponse {
@@ -831,7 +829,7 @@ impl From<ErrorWrapper<DbErrorWrite>> for HttpResponse {
831829
fn from(value: ErrorWrapper<DbErrorWrite>) -> Self {
832830
let accept = value.1;
833831
let (status, message) = match value.0 {
834-
DbErrorWrite::NotFound => return HttpResponse::not_found(accept),
832+
DbErrorWrite::NotFound => return HttpResponse::not_found(accept, None),
835833
DbErrorWrite::Generic(err) => (StatusCode::SERVICE_UNAVAILABLE, err.to_string()),
836834
DbErrorWrite::NonRetriable(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()),
837835
};
@@ -842,3 +840,26 @@ impl From<ErrorWrapper<DbErrorWrite>> for HttpResponse {
842840
}
843841
}
844842
}
843+
impl From<ErrorWrapper<SubmitError>> for HttpResponse {
844+
fn from(value: ErrorWrapper<SubmitError>) -> Self {
845+
let accept = value.1;
846+
match value.0 {
847+
err @ SubmitError::Conflict => HttpResponse {
848+
status: StatusCode::CONFLICT,
849+
message: err.to_string(),
850+
accept,
851+
},
852+
SubmitError::FunctionNotFound => HttpResponse::not_found(accept, Some("ffqn")),
853+
SubmitError::DbErrorWrite(db_error_write) => {
854+
HttpResponse::from(ErrorWrapper(db_error_write, accept))
855+
}
856+
err @ (SubmitError::ExecutionIdMustBeTopLevel | SubmitError::ParamsInvalid(_)) => {
857+
HttpResponse {
858+
status: StatusCode::BAD_REQUEST,
859+
message: err.to_string(),
860+
accept,
861+
}
862+
}
863+
}
864+
}
865+
}

0 commit comments

Comments
 (0)