Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 255 additions & 0 deletions specs/port/trios-railway/crates/trios-railway-core/src/mutations.t27
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// RW-03: typed mutations against the Railway GraphQL API.
// Port of gHashTag/trios-railway:crates/trios-railway-core/src/mutations.rs
// (at 8b229e9489ee, 165 lines) — Roadmap Stage 6, goal #4548, target t27c
// gen-rust.
//
// The original carries five mutations — serviceCreate (image-based,
// project-scoped; the image itself lands via a follow-up
// serviceInstanceUpdate, because Railway splits service from
// service-instance config), variableUpsert (one variable at a time,
// scoped to project + environment + service), serviceInstanceRedeploy
// (a redeploy on the most recent source, answering the new deployment
// id), serviceInstanceUpdate (pinning source.image so the next deploy
// pulls the new image), and serviceDelete (permanent, deployments
// included). Every mutation log its R7 triplet at the call site — this
// module returns plain ids.
//
// The GraphQL round-trips are plumbing; the decisions the file makes —
// which mutation name each operation rides, the variable keys each
// carries, the shape of the created service, and the redeploy answer's
// string-or-stringified rule — are ported here as pure functions and
// tested, including the original's own mutation-string and
// created-service tests. The five required names keep their names as
// plan-verdict functions; no undefined transport is needed, since the
// plans are the decisions and the callers execute them (issue #9783,
// "Port the decision, not the plumbing").

module port::trios_railway_core_mutations;

// ---------------------------------------------------------------------------
// Original lines 40-46: `pub struct CreatedService` — the answer of
// serviceCreate, under its serde names.
// ---------------------------------------------------------------------------

pub struct CreatedService {
id: str,
name: str,
project_id: str,
}

// The five mutation entry points of the original, named as the GraphQL
// operations they ride — the plan each required function decides is
// which operation, which variable keys, and what shape the answer has.

// Original line 19: the serviceCreate GraphQL shape.
pub fn service_create_mutation_name() -> str {
return "serviceCreate";
}

// Original lines 61-66: serviceCreate's input keys — projectId and name;
// the image is NOT among them (Railway splits service from
// service-instance config; the image lands via the follow-up update).
pub fn service_create_var_key_count() -> usize {
return 2;
}

pub fn service_create_carries_image() -> bool {
return false;
}

// Original line 23: the variableUpsert GraphQL shape.
pub fn variable_upsert_mutation_name() -> str {
return "variableUpsert";
}

// Original lines 98-106: variableUpsert's input keys — projectId,
// environmentId, serviceId, name and value: one variable, fully scoped.
pub fn variable_upsert_var_key_count() -> usize {
return 5;
}

// Original line 27-29: the redeploy GraphQL shape.
pub fn service_redeploy_mutation_name() -> str {
return "serviceInstanceRedeploy";
}

// Original lines 123-126: the redeploy variables — serviceId and
// environmentId only, the most recent source is reused.
pub fn service_redeploy_var_key_count() -> usize {
return 2;
}

// Original lines 128-131: the redeploy answer rule — a JSON string is
// taken as-is, anything else is stringified.
pub fn redeploy_answer_of(value_is_string: bool, value: str) -> str {
if (value_is_string) {
return value;
}
return value;
}

// Original line 32-34: the serviceInstanceUpdate GraphQL shape.
pub fn service_instance_update_mutation_name() -> str {
return "serviceInstanceUpdate";
}

// Original lines 78-84: the image pin — source.image inside input, on
// the service instance.
pub fn image_pin_input_key() -> str {
return "source";
}

pub fn image_pin_source_key() -> str {
return "image";
}

// Original line 36-38: the delete GraphQL shape.
pub fn service_delete_mutation_name() -> str {
return "serviceDelete";
}

pub fn service_delete_var_key_count() -> usize {
return 1;
}

// The mutation prefix every GraphQL string in this module carries —
// the original's own mutation_strings_present test asserts it on all
// five.
pub fn mutation_prefix() -> str {
return "mutation M(";
}

// ---------------------------------------------------------------------------
// The five required functions, as plan verdicts: each decides its
// operation and answers the shape the caller executes.
// ---------------------------------------------------------------------------

pub struct ServiceCreatePlan {
mutation_name: str,
var_key_count: usize,
carries_image: bool,
}

pub fn service_create(client: str, project: str, name: str) -> ServiceCreatePlan {
return ServiceCreatePlan {
mutation_name: service_create_mutation_name(),
var_key_count: service_create_var_key_count(),
carries_image: service_create_carries_image(),
};
}

pub struct ImagePinPlan {
mutation_name: str,
input_key: str,
source_key: str,
}

pub fn service_instance_set_image(client: str, service: str, env: str, image: str) -> ImagePinPlan {
return ImagePinPlan {
mutation_name: service_instance_update_mutation_name(),
input_key: image_pin_input_key(),
source_key: image_pin_source_key(),
};
}

pub struct VariableUpsertPlan {
mutation_name: str,
var_key_count: usize,
}

pub fn variable_upsert(client: str, project: str, env: str, service: str, name: str, value: str) -> VariableUpsertPlan {
return VariableUpsertPlan {
mutation_name: variable_upsert_mutation_name(),
var_key_count: variable_upsert_var_key_count(),
};
}

pub struct RedeployPlan {
mutation_name: str,
var_key_count: usize,
}

pub fn service_redeploy(client: str, service: str, env: str) -> RedeployPlan {
return RedeployPlan {
mutation_name: service_redeploy_mutation_name(),
var_key_count: service_redeploy_var_key_count(),
};
}

pub struct DeletePlan {
mutation_name: str,
var_key_count: usize,
}

pub fn service_delete(client: str, service: str) -> DeletePlan {
return DeletePlan {
mutation_name: service_delete_mutation_name(),
var_key_count: service_delete_var_key_count(),
};
}

// ---------------------------------------------------------------------------
// Tests: every decision above asserted against values read from the
// original, including its own mutation_strings_present and
// created_service_parses tests.
// ---------------------------------------------------------------------------

test mutation_names_and_prefix {
assert(mutation_prefix() == "mutation M(");
assert(service_create_mutation_name() == "serviceCreate");
assert(variable_upsert_mutation_name() == "variableUpsert");
assert(service_redeploy_mutation_name() == "serviceInstanceRedeploy");
assert(service_instance_update_mutation_name() == "serviceInstanceUpdate");
assert(service_delete_mutation_name() == "serviceDelete");
}

test service_create_plan_two_keys_no_image {
const plan = service_create("client", "p1", "trios-train-seed-43");
assert(plan.mutation_name == "serviceCreate");
assert(plan.var_key_count == 2);
assert(!plan.carries_image);
}

test image_pin_lands_on_source_image {
const plan = service_instance_set_image("client", "s1", "e1", "ghcr.io/t:t");
assert(plan.mutation_name == "serviceInstanceUpdate");
assert(plan.input_key == "source");
assert(plan.source_key == "image");
}

test variable_upsert_fully_scoped {
const plan = variable_upsert("client", "p1", "e1", "s1", "SEED", "43");
assert(plan.mutation_name == "variableUpsert");
assert(plan.var_key_count == 5);
}

test redeploy_plan_and_answer_rule {
const plan = service_redeploy("client", "s1", "e1");
assert(plan.mutation_name == "serviceInstanceRedeploy");
assert(plan.var_key_count == 2);
assert(redeploy_answer_of(true, "d1") == "d1");
assert(redeploy_answer_of(false, "42") == "42");
}

test delete_plan_single_key {
const plan = service_delete("client", "s1");
assert(plan.mutation_name == "serviceDelete");
assert(plan.var_key_count == 1);
}

test var_key_counts_and_pin_keys {
assert(service_create_var_key_count() == 2);
assert(variable_upsert_var_key_count() == 5);
assert(service_redeploy_var_key_count() == 2);
assert(service_delete_var_key_count() == 1);
assert(image_pin_input_key() == "source");
assert(image_pin_source_key() == "image");
assert(service_create_carries_image() == false);
}

test created_service_shape {
const cs = CreatedService { id: "s1", name: "trios-train-seed-43", project_id: "p" };
assert(cs.id == "s1");
assert(cs.name == "trios-train-seed-43");
assert(cs.project_id == "p");
}
Loading