use crate::common::ApiState;
use crate::common::{ApiError, ApiResult, Json, Query, State};
use std::collections::HashMap;
#[utoipa::path(
post,
path = "/v1/cortical_mapping/afferents",
tag = "cortical_mapping"
)]
pub async fn post_afferents(
State(_state): State<ApiState>,
Json(_req): Json<HashMap<String, String>>,
) -> ApiResult<Json<Vec<String>>> {
Err(ApiError::internal("Not yet implemented"))
}
#[utoipa::path(
post,
path = "/v1/cortical_mapping/efferents",
tag = "cortical_mapping"
)]
pub async fn post_efferents(
State(_state): State<ApiState>,
Json(_req): Json<HashMap<String, String>>,
) -> ApiResult<Json<Vec<String>>> {
Err(ApiError::internal("Not yet implemented"))
}
#[utoipa::path(
post,
path = "/v1/cortical_mapping/mapping_properties",
tag = "cortical_mapping",
responses(
(status = 200, description = "Cortical mapping connections", body = Vec<serde_json::Value>),
(status = 500, description = "Internal server error")
)
)]
pub async fn post_mapping_properties(
State(state): State<ApiState>,
Json(req): Json<HashMap<String, serde_json::Value>>,
) -> ApiResult<Json<Vec<serde_json::Value>>> {
use tracing::debug;
let src_area = req
.get("src_cortical_area")
.and_then(|v| v.as_str())
.ok_or_else(|| ApiError::invalid_input("Missing src_cortical_area"))?;
let dst_area = req
.get("dst_cortical_area")
.and_then(|v| v.as_str())
.ok_or_else(|| ApiError::invalid_input("Missing dst_cortical_area"))?;
debug!(target: "feagi-api", "Getting mapping properties: {} -> {}", src_area, dst_area);
let connectome_service = state.connectome_service.as_ref();
let src_area_info = connectome_service
.get_cortical_area(src_area)
.await
.map_err(|e| {
ApiError::not_found("Cortical area", &format!("Source area {}: {}", src_area, e))
})?;
let mapping_dst = src_area_info
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object());
if mapping_dst.is_none() {
debug!(target: "feagi-api", "No cortical_mapping_dst found for {}", src_area);
return Ok(Json(vec![]));
}
let connections = mapping_dst
.unwrap()
.get(dst_area)
.and_then(|v| v.as_array());
if connections.is_none() {
debug!(target: "feagi-api", "No connections found from {} to {}", src_area, dst_area);
return Ok(Json(vec![]));
}
let mut formatted = Vec::new();
for conn in connections.unwrap() {
if let Some(arr) = conn.as_array() {
if arr.len() < 8 {
return Err(ApiError::invalid_input(format!(
"Invalid dstmap rule array (expected 8 elements including plasticity_window), got {}: {:?}",
arr.len(),
arr
)));
}
let morphology_id = arr[0]
.as_str()
.ok_or_else(|| ApiError::invalid_input("morphology_id must be a string"))?;
let morphology_scalar = arr[1].clone();
let psc_multiplier = arr[2].as_i64().ok_or_else(|| {
ApiError::invalid_input("postSynapticCurrent_multiplier must be an integer")
})?;
let plasticity_flag = arr[3]
.as_bool()
.ok_or_else(|| ApiError::invalid_input("plasticity_flag must be a boolean"))?;
let plasticity_constant = arr[4]
.as_i64()
.ok_or_else(|| ApiError::invalid_input("plasticity_constant must be an integer"))?;
let ltp_multiplier = arr[5]
.as_i64()
.ok_or_else(|| ApiError::invalid_input("ltp_multiplier must be an integer"))?;
let ltd_multiplier = arr[6]
.as_i64()
.ok_or_else(|| ApiError::invalid_input("ltd_multiplier must be an integer"))?;
let plasticity_window = arr[7]
.as_i64()
.ok_or_else(|| ApiError::invalid_input("plasticity_window must be an integer"))?;
let synaptic_delay_bursts: u64 = if arr.len() >= 9 {
arr[8]
.as_u64()
.or_else(|| arr[8].as_i64().map(|i| i as u64))
.ok_or_else(|| {
ApiError::invalid_input(
"synaptic_delay_bursts must be a non-negative integer",
)
})?
} else {
1
}
.max(1);
formatted.push(serde_json::json!({
"morphology_id": morphology_id,
"morphology_scalar": morphology_scalar,
"postSynapticCurrent_multiplier": psc_multiplier,
"plasticity_flag": plasticity_flag,
"plasticity_constant": plasticity_constant,
"ltp_multiplier": ltp_multiplier,
"ltd_multiplier": ltd_multiplier,
"plasticity_window": plasticity_window,
"synaptic_delay_bursts": synaptic_delay_bursts,
}));
} else if let Some(obj) = conn.as_object() {
let morphology_id = obj
.get("morphology_id")
.and_then(|v| v.as_str())
.ok_or_else(|| ApiError::invalid_input("morphology_id must be a string"))?;
let morphology_scalar = obj
.get("morphology_scalar")
.cloned()
.ok_or_else(|| ApiError::invalid_input("morphology_scalar missing"))?;
let psc_multiplier = obj
.get("postSynapticCurrent_multiplier")
.and_then(|v| v.as_i64())
.ok_or_else(|| {
ApiError::invalid_input("postSynapticCurrent_multiplier must be an integer")
})?;
let plasticity_flag = obj
.get("plasticity_flag")
.and_then(|v| v.as_bool())
.ok_or_else(|| ApiError::invalid_input("plasticity_flag must be a boolean"))?;
let plasticity_constant = obj
.get("plasticity_constant")
.and_then(|v| v.as_i64())
.ok_or_else(|| ApiError::invalid_input("plasticity_constant must be an integer"))?;
let ltp_multiplier = obj
.get("ltp_multiplier")
.and_then(|v| v.as_i64())
.ok_or_else(|| ApiError::invalid_input("ltp_multiplier must be an integer"))?;
let ltd_multiplier = obj
.get("ltd_multiplier")
.and_then(|v| v.as_i64())
.ok_or_else(|| ApiError::invalid_input("ltd_multiplier must be an integer"))?;
let plasticity_window = obj
.get("plasticity_window")
.and_then(|v| v.as_i64())
.ok_or_else(|| ApiError::invalid_input("plasticity_window must be an integer"))?;
let synaptic_delay_bursts: u64 = obj
.get("synaptic_delay_bursts")
.and_then(|v| v.as_u64().or_else(|| v.as_i64().map(|i| i as u64)))
.unwrap_or(1)
.max(1);
formatted.push(serde_json::json!({
"morphology_id": morphology_id,
"morphology_scalar": morphology_scalar,
"postSynapticCurrent_multiplier": psc_multiplier,
"plasticity_flag": plasticity_flag,
"plasticity_constant": plasticity_constant,
"ltp_multiplier": ltp_multiplier,
"ltd_multiplier": ltd_multiplier,
"plasticity_window": plasticity_window,
"synaptic_delay_bursts": synaptic_delay_bursts,
}));
}
}
debug!(target: "feagi-api", "Returning {} mapping connections from {} to {}", formatted.len(), src_area, dst_area);
Ok(Json(formatted))
}
#[utoipa::path(
put,
path = "/v1/cortical_mapping/mapping_properties",
tag = "cortical_mapping",
responses(
(status = 200, description = "Cortical mapping updated successfully", body = HashMap<String, serde_json::Value>),
(status = 404, description = "Cortical area not found"),
(status = 500, description = "Internal server error")
)
)]
pub async fn put_mapping_properties(
State(state): State<ApiState>,
Json(req): Json<HashMap<String, serde_json::Value>>,
) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
use tracing::{debug, info};
let src_area = req
.get("src_cortical_area")
.and_then(|v| v.as_str())
.ok_or_else(|| ApiError::invalid_input("Missing src_cortical_area"))?;
let dst_area = req
.get("dst_cortical_area")
.and_then(|v| v.as_str())
.ok_or_else(|| ApiError::invalid_input("Missing dst_cortical_area"))?;
let mapping_string = req
.get("mapping_string")
.and_then(|v| v.as_array())
.ok_or_else(|| ApiError::invalid_input("Missing mapping_string"))?;
info!(
target: "feagi-api",
"PUT cortical mapping: {} -> {} with {} connections",
src_area,
dst_area,
mapping_string.len()
);
debug!(target: "feagi-api", "Mapping data: {:?}", mapping_string);
let connectome_service = state.connectome_service.as_ref();
let synapse_count = connectome_service
.update_cortical_mapping(
src_area.to_string(),
dst_area.to_string(),
mapping_string.clone(),
)
.await
.map_err(|e| match e {
feagi_services::types::ServiceError::InvalidInput(msg) => ApiError::invalid_input(msg),
feagi_services::types::ServiceError::Conflict(msg) => ApiError::conflict(msg),
_ => ApiError::internal(format!("Failed to update cortical mapping: {}", e)),
})?;
info!(target: "feagi-api", "Cortical mapping updated successfully: {} synapses created", synapse_count);
let mut response = HashMap::new();
response.insert(
"message".to_string(),
serde_json::json!(format!(
"Cortical mapping properties updated successfully from {} to {}",
src_area, dst_area
)),
);
response.insert(
"synapse_count".to_string(),
serde_json::json!(synapse_count),
);
response.insert("src_region".to_string(), serde_json::json!(null)); response.insert("dst_region".to_string(), serde_json::json!(null));
Ok(Json(response))
}
#[utoipa::path(
get,
path = "/v1/cortical_mapping/mapping",
tag = "cortical_mapping",
params(
("src_cortical_area" = String, Query, description = "Source cortical area ID"),
("dst_cortical_area" = String, Query, description = "Destination cortical area ID")
),
responses(
(status = 200, description = "Mapping properties", body = HashMap<String, serde_json::Value>)
)
)]
pub async fn get_mapping(
State(state): State<ApiState>,
Query(params): Query<HashMap<String, String>>,
) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
let src_area = params
.get("src_cortical_area")
.ok_or_else(|| ApiError::invalid_input("src_cortical_area required"))?;
let dst_area = params
.get("dst_cortical_area")
.ok_or_else(|| ApiError::invalid_input("dst_cortical_area required"))?;
let connectome_service = state.connectome_service.as_ref();
let src_area_info = connectome_service
.get_cortical_area(src_area)
.await
.map_err(|e| {
ApiError::not_found("Cortical area", &format!("Source area {}: {}", src_area, e))
})?;
let mapping_dst = src_area_info
.properties
.get("cortical_mapping_dst")
.and_then(|v| v.as_object());
if mapping_dst.is_none() {
return Ok(Json(HashMap::new()));
}
let connections = mapping_dst
.unwrap()
.get(dst_area)
.and_then(|v| v.as_array());
let mut response = HashMap::new();
response.insert(
"connections".to_string(),
serde_json::json!(connections.unwrap_or(&vec![])),
);
Ok(Json(response))
}
#[utoipa::path(
get,
path = "/v1/cortical_mapping/mapping_list",
tag = "cortical_mapping",
responses(
(status = 200, description = "List of all mappings", body = Vec<Vec<String>>)
)
)]
pub async fn get_mapping_list(State(state): State<ApiState>) -> ApiResult<Json<Vec<Vec<String>>>> {
let connectome_service = state.connectome_service.as_ref();
let areas = connectome_service
.list_cortical_areas()
.await
.map_err(|e| ApiError::internal(format!("Failed to list areas: {}", e)))?;
let mut mappings = Vec::new();
for area in &areas {
if let Ok(area_detail) = connectome_service
.get_cortical_area(&area.cortical_id)
.await
{
if let Some(mapping_dst) = area_detail.properties.get("cortical_mapping_dst") {
if let Some(dst_map) = mapping_dst.as_object() {
for dst_area_id in dst_map.keys() {
mappings.push(vec![area.cortical_id.clone(), dst_area_id.clone()]);
}
}
}
}
}
Ok(Json(mappings))
}
#[utoipa::path(
delete,
path = "/v1/cortical_mapping/mapping",
tag = "cortical_mapping",
responses(
(status = 200, description = "Mapping deleted", body = HashMap<String, String>)
)
)]
pub async fn delete_mapping(
State(_state): State<ApiState>,
Json(_request): Json<HashMap<String, String>>,
) -> ApiResult<Json<HashMap<String, String>>> {
Ok(Json(HashMap::from([(
"message".to_string(),
"Mapping deletion not yet implemented".to_string(),
)])))
}
#[utoipa::path(
post,
path = "/v1/cortical_mapping/batch_update",
tag = "cortical_mapping",
responses(
(status = 200, description = "Batch update completed", body = HashMap<String, serde_json::Value>)
)
)]
pub async fn post_batch_update(
State(_state): State<ApiState>,
Json(_request): Json<Vec<HashMap<String, String>>>,
) -> ApiResult<Json<HashMap<String, serde_json::Value>>> {
let mut response = HashMap::new();
response.insert(
"message".to_string(),
serde_json::json!("Batch update not yet implemented"),
);
response.insert("updated_count".to_string(), serde_json::json!(0));
Ok(Json(response))
}
#[utoipa::path(post, path = "/v1/cortical_mapping/mapping", tag = "cortical_mapping")]
pub async fn post_mapping(
State(_state): State<ApiState>,
Json(_req): Json<HashMap<String, serde_json::Value>>,
) -> ApiResult<Json<HashMap<String, String>>> {
Ok(Json(HashMap::from([(
"message".to_string(),
"Not yet implemented".to_string(),
)])))
}
#[utoipa::path(put, path = "/v1/cortical_mapping/mapping", tag = "cortical_mapping")]
pub async fn put_mapping(
State(_state): State<ApiState>,
Json(_req): Json<HashMap<String, serde_json::Value>>,
) -> ApiResult<Json<HashMap<String, String>>> {
Ok(Json(HashMap::from([(
"message".to_string(),
"Not yet implemented".to_string(),
)])))
}