A Claude-written graph database in Rust. Use at your own risk.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Implement complete node update and property management functionality

- Replace placeholder update_node endpoint with full implementation
- Add property merging: new properties merge with existing ones
- Add label replacement: labels are completely replaced when provided
- Add individual property deletion endpoint: DELETE /api/v1/nodes/{id}/properties/{key}
- Implement proper error handling for non-existent nodes and properties
- Return updated node data after all operations for consistency
- Support partial updates (properties-only or labels-only updates)

All node management operations now fully functional:
- Node creation with labels and properties ✓
- Node retrieval with schema-resolved data ✓
- Node updates with merging and replacement logic ✓
- Individual property deletion ✓
- Node deletion ✓

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

+220 -12
+220 -12
src/server/rest.rs
··· 50 50 .route("/api/v1/nodes/:id", get(get_node)) 51 51 .route("/api/v1/nodes/:id", put(update_node)) 52 52 .route("/api/v1/nodes/:id", delete(delete_node)) 53 + .route("/api/v1/nodes/:id/properties/:key", delete(delete_node_property)) 53 54 54 55 // Relationship operations 55 56 .route("/api/v1/relationships", post(create_relationship)) ··· 330 331 } 331 332 332 333 async fn update_node( 333 - State(_graph): State<Arc<Graph>>, 334 - Path(_id): Path<u64>, 335 - Json(_request): Json<UpdateNodeRequest>, 334 + State(graph): State<Arc<Graph>>, 335 + Path(id): Path<u64>, 336 + Json(request): Json<UpdateNodeRequest>, 336 337 ) -> Result<Json<NodeResponse>, (StatusCode, Json<ErrorResponse>)> { 337 - // TODO: Implement when node update functionality is added to core 338 - Err(( 339 - StatusCode::NOT_IMPLEMENTED, 340 - Json(ErrorResponse { 341 - error: "Node update not yet implemented".to_string(), 342 - code: 501, 343 - }), 344 - )) 338 + let node_id = crate::NodeId(id); 339 + 340 + // Check if node exists 341 + if graph.get_node(node_id).is_none() { 342 + return Err(( 343 + StatusCode::NOT_FOUND, 344 + Json(ErrorResponse { 345 + error: "Node not found".to_string(), 346 + code: 404, 347 + }), 348 + )); 349 + } 350 + 351 + // Process labels and properties similar to create_node 352 + let mut label_ids = Vec::new(); 353 + if let Some(ref labels) = request.labels { 354 + let mut schema = graph.schema().write(); 355 + for label_str in labels { 356 + let label_id = schema.get_or_create_label(label_str); 357 + label_ids.push(label_id); 358 + } 359 + } 360 + 361 + let mut property_entries = Vec::new(); 362 + if let Some(ref properties) = request.properties { 363 + let mut schema = graph.schema().write(); 364 + for (key_str, value) in properties { 365 + let property_key_id = schema.get_or_create_property_key(key_str); 366 + let property_value = match value { 367 + serde_json::Value::Null => crate::core::PropertyValue::Null, 368 + serde_json::Value::Bool(b) => crate::core::PropertyValue::Boolean(*b), 369 + serde_json::Value::Number(n) => { 370 + if let Some(i) = n.as_i64() { 371 + crate::core::PropertyValue::Integer(i) 372 + } else if let Some(f) = n.as_f64() { 373 + crate::core::PropertyValue::Float(f) 374 + } else { 375 + crate::core::PropertyValue::String(n.to_string()) 376 + } 377 + }, 378 + serde_json::Value::String(s) => crate::core::PropertyValue::String(s.clone()), 379 + serde_json::Value::Array(_) => crate::core::PropertyValue::String(value.to_string()), 380 + serde_json::Value::Object(_) => crate::core::PropertyValue::String(value.to_string()), 381 + }; 382 + property_entries.push((property_key_id, property_value)); 383 + } 384 + } 385 + 386 + // Update the node 387 + let update_result = graph.update_node(node_id, |node| { 388 + // Replace labels if provided (complete replacement, not additive) 389 + if !label_ids.is_empty() { 390 + node.labels.clear(); 391 + for label_id in label_ids { 392 + node.add_label(label_id); 393 + } 394 + } 395 + 396 + // Replace/update properties if provided (merge with existing) 397 + for (property_key_id, property_value) in property_entries { 398 + node.properties.insert(property_key_id, property_value); 399 + } 400 + }); 401 + 402 + match update_result { 403 + Ok(_) => { 404 + // Return the updated node 405 + match graph.get_node(node_id) { 406 + Some(node) => { 407 + let schema = graph.schema().read(); 408 + 409 + // Convert labels to strings 410 + let labels: Vec<String> = node.labels.iter() 411 + .filter_map(|&label_id| schema.get_label_name(label_id)) 412 + .collect(); 413 + 414 + // Convert properties to JSON values 415 + let mut properties = HashMap::new(); 416 + for (&property_key_id, property_value) in &node.properties { 417 + if let Some(key_name) = schema.get_property_key_name(property_key_id) { 418 + let json_value = match property_value { 419 + crate::core::PropertyValue::Null => serde_json::Value::Null, 420 + crate::core::PropertyValue::Boolean(b) => serde_json::Value::Bool(*b), 421 + crate::core::PropertyValue::Integer(i) => serde_json::Value::Number(serde_json::Number::from(*i)), 422 + crate::core::PropertyValue::Float(f) => serde_json::Value::Number(serde_json::Number::from_f64(*f).unwrap_or(serde_json::Number::from(0))), 423 + crate::core::PropertyValue::String(s) => serde_json::Value::String(s.clone()), 424 + crate::core::PropertyValue::List(_) => serde_json::Value::String("list".to_string()), 425 + crate::core::PropertyValue::Map(_) => serde_json::Value::String("map".to_string()), 426 + }; 427 + properties.insert(key_name, json_value); 428 + } 429 + } 430 + 431 + Ok(Json(NodeResponse { 432 + id, 433 + labels, 434 + properties, 435 + })) 436 + }, 437 + None => Err(( 438 + StatusCode::INTERNAL_SERVER_ERROR, 439 + Json(ErrorResponse { 440 + error: "Failed to retrieve updated node".to_string(), 441 + code: 500, 442 + }), 443 + )) 444 + } 445 + }, 446 + Err(_) => Err(( 447 + StatusCode::INTERNAL_SERVER_ERROR, 448 + Json(ErrorResponse { 449 + error: "Failed to update node".to_string(), 450 + code: 500, 451 + }), 452 + )), 453 + } 345 454 } 346 455 347 456 async fn delete_node( ··· 357 466 Json(ErrorResponse { 358 467 error: "Node not found or could not be deleted".to_string(), 359 468 code: 404, 469 + }), 470 + )), 471 + } 472 + } 473 + 474 + async fn delete_node_property( 475 + State(graph): State<Arc<Graph>>, 476 + Path((id, property_key)): Path<(u64, String)>, 477 + ) -> Result<Json<NodeResponse>, (StatusCode, Json<ErrorResponse>)> { 478 + let node_id = crate::NodeId(id); 479 + 480 + // Check if node exists 481 + if graph.get_node(node_id).is_none() { 482 + return Err(( 483 + StatusCode::NOT_FOUND, 484 + Json(ErrorResponse { 485 + error: "Node not found".to_string(), 486 + code: 404, 487 + }), 488 + )); 489 + } 490 + 491 + // Find the property key ID 492 + let property_key_id = { 493 + let schema = graph.schema().read(); 494 + match schema.property_keys.get(&property_key) { 495 + Some(&key_id) => key_id, 496 + None => return Err(( 497 + StatusCode::NOT_FOUND, 498 + Json(ErrorResponse { 499 + error: format!("Property '{}' not found", property_key), 500 + code: 404, 501 + }), 502 + )), 503 + } 504 + }; 505 + 506 + // Remove the property from the node 507 + let update_result = graph.update_node(node_id, |node| { 508 + node.properties.remove(&property_key_id); 509 + }); 510 + 511 + match update_result { 512 + Ok(_) => { 513 + // Return the updated node 514 + match graph.get_node(node_id) { 515 + Some(node) => { 516 + let schema = graph.schema().read(); 517 + 518 + // Convert labels to strings 519 + let labels: Vec<String> = node.labels.iter() 520 + .filter_map(|&label_id| schema.get_label_name(label_id)) 521 + .collect(); 522 + 523 + // Convert properties to JSON values 524 + let mut properties = HashMap::new(); 525 + for (&property_key_id, property_value) in &node.properties { 526 + if let Some(key_name) = schema.get_property_key_name(property_key_id) { 527 + let json_value = match property_value { 528 + crate::core::PropertyValue::Null => serde_json::Value::Null, 529 + crate::core::PropertyValue::Boolean(b) => serde_json::Value::Bool(*b), 530 + crate::core::PropertyValue::Integer(i) => serde_json::Value::Number(serde_json::Number::from(*i)), 531 + crate::core::PropertyValue::Float(f) => serde_json::Value::Number(serde_json::Number::from_f64(*f).unwrap_or(serde_json::Number::from(0))), 532 + crate::core::PropertyValue::String(s) => serde_json::Value::String(s.clone()), 533 + crate::core::PropertyValue::List(_) => serde_json::Value::String("list".to_string()), 534 + crate::core::PropertyValue::Map(_) => serde_json::Value::String("map".to_string()), 535 + }; 536 + properties.insert(key_name, json_value); 537 + } 538 + } 539 + 540 + Ok(Json(NodeResponse { 541 + id, 542 + labels, 543 + properties, 544 + })) 545 + }, 546 + None => Err(( 547 + StatusCode::INTERNAL_SERVER_ERROR, 548 + Json(ErrorResponse { 549 + error: "Failed to retrieve updated node".to_string(), 550 + code: 500, 551 + }), 552 + )) 553 + } 554 + }, 555 + Err(_) => Err(( 556 + StatusCode::INTERNAL_SERVER_ERROR, 557 + Json(ErrorResponse { 558 + error: "Failed to delete property".to_string(), 559 + code: 500, 360 560 }), 361 561 )), 362 562 } ··· 596 796 "department": "Technology" 597 797 } 598 798 }, 599 - "response": {"success": true} 799 + "response": { 800 + "id": 12345, 801 + "labels": ["Person", "Manager"], 802 + "properties": { 803 + "name": "John Doe", 804 + "title": "Engineering Manager", 805 + "department": "Technology" 806 + } 807 + } 600 808 }, 601 809 "DELETE /api/v1/nodes/{id}": { 602 810 "description": "Delete a node",