use serde_json::Value;
use super::constants::DEFAULT_RAILWAY_GRAPHQL_URL;
use super::error::ProvisioningError;
use super::payload_json::extract_connection_uri;
use super::types::{
RailwayConnectionParams, RailwayPluginCreateParams, RailwayPluginCreateResult,
RailwayProjectCreateParams, RailwayProjectCreateResult, RailwayServiceCreateParams,
RailwayServiceCreateResult,
};
pub async fn create_railway_project(
params: RailwayProjectCreateParams,
) -> Result<RailwayProjectCreateResult, ProvisioningError> {
let query = r#"
mutation projectCreate($input: ProjectCreateInput!) {
projectCreate(input: $input) {
id
baseEnvironmentId
name
}
}
"#;
let body: Value = railway_graphql_request(
¶ms.api_key,
params.graphql_url.as_deref(),
query,
serde_json::json!({ "input": params.project_input }),
)
.await?;
let data: &Value = body.get("data").unwrap_or(&body);
let project: &Value = data.get("projectCreate").unwrap_or(data);
let project_id: String = project
.get("id")
.and_then(Value::as_str)
.map(str::to_string)
.ok_or_else(|| {
ProvisioningError::Execution(format!(
"railway projectCreate response missing id: {}",
body
))
})?;
let base_environment_id: Option<String> = project
.get("baseEnvironmentId")
.and_then(Value::as_str)
.map(str::to_string);
Ok(RailwayProjectCreateResult {
project_id,
base_environment_id,
raw: body,
})
}
pub async fn create_railway_service(
params: RailwayServiceCreateParams,
) -> Result<RailwayServiceCreateResult, ProvisioningError> {
let query = r#"
mutation serviceCreate($input: ServiceCreateInput!) {
serviceCreate(input: $input) {
id
name
projectId
}
}
"#;
let body: Value = railway_graphql_request(
¶ms.api_key,
params.graphql_url.as_deref(),
query,
serde_json::json!({ "input": params.service_input }),
)
.await?;
let data: &Value = body.get("data").unwrap_or(&body);
let service: &Value = data.get("serviceCreate").unwrap_or(data);
let service_id: String = service
.get("id")
.and_then(Value::as_str)
.map(str::to_string)
.ok_or_else(|| {
ProvisioningError::Execution(format!(
"railway serviceCreate response missing id: {}",
body
))
})?;
Ok(RailwayServiceCreateResult {
service_id,
raw: body,
})
}
pub async fn create_railway_plugin(
params: RailwayPluginCreateParams,
) -> Result<RailwayPluginCreateResult, ProvisioningError> {
let query = r#"
mutation pluginCreate($input: PluginCreateInput!) {
pluginCreate(input: $input) {
id
name
status
}
}
"#;
let body = railway_graphql_request(
¶ms.api_key,
params.graphql_url.as_deref(),
query,
serde_json::json!({ "input": params.plugin_input }),
)
.await?;
let data = body.get("data").unwrap_or(&body);
let plugin = data.get("pluginCreate").unwrap_or(data);
let plugin_id = plugin
.get("id")
.and_then(Value::as_str)
.map(str::to_string)
.ok_or_else(|| {
ProvisioningError::Execution(format!(
"railway pluginCreate response missing id: {}",
body
))
})?;
Ok(RailwayPluginCreateResult {
plugin_id,
raw: body,
})
}
pub async fn fetch_railway_project_base_environment_id(
api_key: &str,
project_id: &str,
graphql_url: Option<&str>,
) -> Result<Option<String>, ProvisioningError> {
if api_key.trim().is_empty() {
return Err(ProvisioningError::InvalidInput(
"railway api_key must not be empty".to_string(),
));
}
if project_id.trim().is_empty() {
return Err(ProvisioningError::InvalidInput(
"railway project_id must not be empty".to_string(),
));
}
let query = r#"
query project($id: String!) {
project(id: $id) {
id
baseEnvironmentId
}
}
"#;
let body: Value = railway_graphql_request(
api_key,
graphql_url,
query,
serde_json::json!({ "id": project_id }),
)
.await?;
let base_environment_id = body
.pointer("/data/project/baseEnvironmentId")
.or_else(|| body.pointer("/project/baseEnvironmentId"))
.and_then(Value::as_str)
.map(str::to_string);
Ok(base_environment_id)
}
pub async fn fetch_railway_connection_uri(
params: RailwayConnectionParams,
) -> Result<String, ProvisioningError> {
if params.api_key.trim().is_empty() {
return Err(ProvisioningError::InvalidInput(
"railway api_key must not be empty".to_string(),
));
}
if params.project_id.trim().is_empty() {
return Err(ProvisioningError::InvalidInput(
"railway project_id must not be empty".to_string(),
));
}
if params.environment_id.trim().is_empty() {
return Err(ProvisioningError::InvalidInput(
"railway environment_id must not be empty".to_string(),
));
}
let graphql_url = params
.graphql_url
.unwrap_or_else(|| DEFAULT_RAILWAY_GRAPHQL_URL.to_string());
let query = r#"
query variables($environmentId: String!, $pluginId: String, $projectId: String!, $serviceId: String, $unrendered: Boolean) {
variables(
environmentId: $environmentId
pluginId: $pluginId
projectId: $projectId
serviceId: $serviceId
unrendered: $unrendered
)
}
"#;
let variables = serde_json::json!({
"environmentId": params.environment_id,
"pluginId": params.plugin_id,
"projectId": params.project_id,
"serviceId": params.service_id,
"unrendered": false
});
let body =
railway_graphql_request(¶ms.api_key, Some(&graphql_url), query, variables).await?;
let data = body.get("data").unwrap_or(&body);
extract_connection_uri(data).ok_or_else(|| {
ProvisioningError::Execution(format!(
"railway api response did not include a postgres connection URI: {}",
body
))
})
}
async fn railway_graphql_request(
api_key: &str,
graphql_url: Option<&str>,
query: &str,
variables: Value,
) -> Result<Value, ProvisioningError> {
if api_key.trim().is_empty() {
return Err(ProvisioningError::InvalidInput(
"railway api_key must not be empty".to_string(),
));
}
let url = graphql_url.unwrap_or(DEFAULT_RAILWAY_GRAPHQL_URL);
let payload = serde_json::json!({
"query": query,
"variables": variables,
});
let response = reqwest::Client::new()
.post(url)
.bearer_auth(api_key)
.json(&payload)
.send()
.await
.map_err(|err| {
ProvisioningError::Execution(format!("railway api request failed: {}", err))
})?;
let status = response.status();
let body: Value = response.json().await.map_err(|err| {
ProvisioningError::Execution(format!("failed to parse railway api response: {}", err))
})?;
if !status.is_success() {
return Err(ProvisioningError::Execution(format!(
"railway api returned status {}: {}",
status, body
)));
}
if let Some(errors) = body.get("errors") {
return Err(ProvisioningError::Execution(format!(
"railway api returned graphql errors: {}",
errors
)));
}
Ok(body)
}