Highly ambitious ATProtocol AppView service and sdks

add $type key to blob and lexicon ref fields in graphql mutations

Automatically adds $type key when transforming GraphQL data to AT Protocol
format in mutations:
- Blob fields: adds "$type": "blob"
- Lexicon refs: adds "$type": "{ref_nsid}" (e.g., "community.lexicon.location.hthree#main")
- Handles single values, arrays, and nested objects recursively

Renamed transform_blob_fields_for_atproto to transform_fields_for_atproto
to reflect expanded functionality.

Changed files
+64 -18
api
src
+64 -18
api/src/graphql/schema_builder.rs
··· 3522 3522 input 3523 3523 } 3524 3524 3525 - /// Transforms blob fields in record data from GraphQL format to AT Protocol format 3525 + /// Transforms fields in record data from GraphQL format to AT Protocol format 3526 3526 /// 3527 - /// GraphQL format: `{ref: "bafyrei...", mimeType: "...", size: 123}` 3528 - /// AT Protocol format: `{ref: {$link: "bafyrei..."}, mimeType: "...", size: 123}` 3529 - fn transform_blob_fields_for_atproto( 3527 + /// Blob fields: 3528 + /// - GraphQL format: `{ref: "bafyrei...", mimeType: "...", size: 123}` 3529 + /// - AT Protocol format: `{$type: "blob", ref: {$link: "bafyrei..."}, mimeType: "...", size: 123}` 3530 + /// 3531 + /// Lexicon ref fields: 3532 + /// - Adds `$type: "{ref_nsid}"` to objects (e.g., `{$type: "community.lexicon.location.hthree#main", ...}`) 3533 + /// 3534 + /// Nested objects: 3535 + /// - Recursively processes nested objects and arrays 3536 + fn transform_fields_for_atproto( 3530 3537 mut data: serde_json::Value, 3531 3538 fields: &[GraphQLField], 3532 3539 ) -> serde_json::Value { ··· 3537 3544 GraphQLType::Blob => { 3538 3545 // Transform single blob field 3539 3546 if let Some(blob_obj) = field_value.as_object_mut() { 3547 + // Add $type: "blob" 3548 + blob_obj.insert("$type".to_string(), serde_json::Value::String("blob".to_string())); 3549 + 3540 3550 // Check if ref is a string (GraphQL format) 3541 3551 if let Some(serde_json::Value::String(cid)) = blob_obj.get("ref") { 3542 3552 // Transform to {$link: "cid"} (AT Protocol format) ··· 3547 3557 } 3548 3558 } 3549 3559 } 3550 - GraphQLType::Array(inner) if matches!(inner.as_ref(), GraphQLType::Blob) => { 3551 - // Transform array of blobs 3552 - if let Some(arr) = field_value.as_array_mut() { 3553 - for blob_value in arr { 3554 - if let Some(blob_obj) = blob_value.as_object_mut() { 3555 - if let Some(serde_json::Value::String(cid)) = blob_obj.get("ref") { 3556 - let link_obj = serde_json::json!({ 3557 - "$link": cid 3558 - }); 3559 - blob_obj.insert("ref".to_string(), link_obj); 3560 + GraphQLType::LexiconRef(ref_nsid) => { 3561 + // Transform lexicon ref field by adding $type 3562 + if let Some(ref_obj) = field_value.as_object_mut() { 3563 + ref_obj.insert("$type".to_string(), serde_json::Value::String(ref_nsid.clone())); 3564 + } 3565 + } 3566 + GraphQLType::Object(nested_fields) => { 3567 + // Recursively transform nested objects 3568 + *field_value = transform_fields_for_atproto(field_value.clone(), nested_fields); 3569 + } 3570 + GraphQLType::Array(inner) => { 3571 + match inner.as_ref() { 3572 + GraphQLType::Blob => { 3573 + // Transform array of blobs 3574 + if let Some(arr) = field_value.as_array_mut() { 3575 + for blob_value in arr { 3576 + if let Some(blob_obj) = blob_value.as_object_mut() { 3577 + // Add $type: "blob" 3578 + blob_obj.insert("$type".to_string(), serde_json::Value::String("blob".to_string())); 3579 + 3580 + if let Some(serde_json::Value::String(cid)) = blob_obj.get("ref") { 3581 + let link_obj = serde_json::json!({ 3582 + "$link": cid 3583 + }); 3584 + blob_obj.insert("ref".to_string(), link_obj); 3585 + } 3586 + } 3560 3587 } 3561 3588 } 3562 3589 } 3590 + GraphQLType::LexiconRef(ref_nsid) => { 3591 + // Transform array of lexicon refs 3592 + if let Some(arr) = field_value.as_array_mut() { 3593 + for ref_value in arr { 3594 + if let Some(ref_obj) = ref_value.as_object_mut() { 3595 + ref_obj.insert("$type".to_string(), serde_json::Value::String(ref_nsid.clone())); 3596 + } 3597 + } 3598 + } 3599 + } 3600 + GraphQLType::Object(nested_fields) => { 3601 + // Transform array of objects recursively 3602 + if let Some(arr) = field_value.as_array_mut() { 3603 + for item in arr { 3604 + *item = transform_fields_for_atproto(item.clone(), nested_fields); 3605 + } 3606 + } 3607 + } 3608 + _ => {} // Other array types don't need transformation 3563 3609 } 3564 3610 } 3565 3611 _ => {} // Other field types don't need transformation ··· 3612 3658 let mut record_data: serde_json::Value = input.deserialize() 3613 3659 .map_err(|e| Error::new(format!("Failed to deserialize input: {:?}", e)))?; 3614 3660 3615 - // Transform blob fields from GraphQL format (ref: "cid") to AT Protocol format (ref: {$link: "cid"}) 3616 - record_data = transform_blob_fields_for_atproto(record_data, &fields); 3661 + // Transform fields from GraphQL to AT Protocol format (adds $type, transforms blob refs) 3662 + record_data = transform_fields_for_atproto(record_data, &fields); 3617 3663 3618 3664 // Optional rkey argument 3619 3665 let rkey = ctx.args.get("rkey") ··· 3784 3830 let mut record_data: serde_json::Value = input.deserialize() 3785 3831 .map_err(|e| Error::new(format!("Failed to deserialize input: {:?}", e)))?; 3786 3832 3787 - // Transform blob fields from GraphQL format (ref: "cid") to AT Protocol format (ref: {$link: "cid"}) 3788 - record_data = transform_blob_fields_for_atproto(record_data, &fields); 3833 + // Transform fields from GraphQL to AT Protocol format (adds $type, transforms blob refs) 3834 + record_data = transform_fields_for_atproto(record_data, &fields); 3789 3835 3790 3836 // Verify OAuth token and get user info 3791 3837 let user_info = crate::auth::verify_oauth_token_cached(