You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让含EdgeDB专属类型的结构体兼容async-graphql OutputType

解决EdgeDB类型兼容async-graphql OutputType的单结构体方案

方案一:直接为EdgeDB原生类型实现async-graphql的ScalarType trait

无需额外定义结构体,直接给edgedb_protocol::model下的类型实现ScalarType,既能保留EdgeDB Queryable所需的解码能力,又满足async-graphql的OutputType要求。

实现Uuid的ScalarType

use async_graphql::{Scalar, Value, ScalarType, Error};
use edgedb_protocol::model::Uuid;

#[Scalar]
impl ScalarType for Uuid {
    // 从GraphQL输入解析为Uuid
    fn parse(value: Value) -> Result<Self, Error> {
        match value {
            Value::String(s) => Uuid::parse_str(&s)
                .map_err(|e| Error::new(format!("无效的UUID格式: {}", e))),
            _ => Err(Error::new("UUID字段需传入字符串类型")),
        }
    }

    // 将Uuid序列化为GraphQL输出值
    fn to_value(&self) -> Value {
        Value::String(self.to_string())
    }
}

实现Datetime的ScalarType

use async_graphql::{Scalar, Value, ScalarType, Error};
use edgedb_protocol::model::Datetime;
use chrono::{DateTime, Utc, ParseError};

#[Scalar]
impl ScalarType for Datetime {
    fn parse(value: Value) -> Result<Self, Error> {
        match value {
            Value::String(s) => {
                // 先解析为chrono的UTC时间,再转换为EdgeDB的Datetime
                let dt: DateTime<Utc> = DateTime::parse_from_rfc3339(&s)
                    .map_err(|e: ParseError| Error::new(format!("无效的时间格式: {}", e)))?
                    .with_timezone(&Utc);
                Ok(Datetime::from(dt))
            }
            _ => Err(Error::new("时间字段需传入RFC3339格式的字符串")),
        }
    }

    fn to_value(&self) -> Value {
        // 将EdgeDB Datetime序列化为RFC3339字符串
        Value::String(self.to_rfc3339())
    }
}

使用单结构体同时派生Queryable和SimpleObject

现在结构体可以直接同时使用两个派生宏,无需额外修改:

use edgedb_derive::Queryable;
use async_graphql::SimpleObject;
use edgedb_protocol::model::{Uuid, Datetime};

#[derive(Debug, Queryable, SimpleObject)]
pub struct OtpPhoneRequest {
    pub id: Uuid,
    pub created_at: Option<Datetime>,
    // 其他业务字段...
}

方案二:使用透明包装类型(避免直接修改外部类型)

如果不想直接为第三方库的类型实现trait(担心版本冲突或命名空间问题),可以用newtype包装EdgeDB类型,通过#[edgedb(transparent)]让Queryable自动透传内部类型的解码逻辑,同时为包装类型实现ScalarType。

定义包装类型

use async_graphql::{Scalar, Value, ScalarType, Error};
use edgedb_protocol::model::Uuid;
use edgedb_derive::Queryable;

#[derive(Clone, Copy, Debug, Queryable)]
#[edgedb(transparent)] // 告诉EdgeDB派生宏直接使用内部类型的解码逻辑
pub struct GqlUuid(pub Uuid);

#[Scalar]
impl ScalarType for GqlUuid {
    fn parse(value: Value) -> Result<Self, Error> {
        let uuid = Uuid::parse_str(&value.to_string())
            .map_err(|e| Error::new(format!("无效的UUID格式: {}", e)))?;
        Ok(Self(uuid))
    }

    fn to_value(&self) -> Value {
        Value::String(self.0.to_string())
    }
}

// 同理包装Datetime
#[derive(Clone, Debug, Queryable)]
#[edgedb(transparent)]
pub struct GqlDatetime(pub Datetime);

#[Scalar]
impl ScalarType for GqlDatetime {
    // 解析和序列化逻辑同方案一,内部调用Datetime的方法
    fn parse(value: Value) -> Result<Self, Error> {
        match value {
            Value::String(s) => {
                let dt: DateTime<Utc> = DateTime::parse_from_rfc3339(&s)
                    .map_err(|e| Error::new(format!("无效的时间格式: {}", e)))?
                    .with_timezone(&Utc);
                Ok(Self(Datetime::from(dt)))
            }
            _ => Err(Error::new("时间字段需传入RFC3339格式的字符串")),
        }
    }

    fn to_value(&self) -> Value {
        Value::String(self.0.to_rfc3339())
    }
}

使用包装后的结构体

#[derive(Debug, Queryable, SimpleObject)]
pub struct OtpPhoneRequest {
    pub id: GqlUuid,
    pub created_at: Option<GqlDatetime>,
    // 其他业务字段...
}

关键说明

  • 方案一最简洁,直接复用EdgeDB原生类型,无需额外包装,适合大多数场景。
  • 方案二通过包装类型隔离第三方依赖,适合对依赖侵入性有要求的项目。
  • 两种方案都无需维护两个结构体,避免代码重复;同时Option类型会自动继承内部类型的OutputType实现,无需额外处理。

内容的提问来源于stack exchange,提问作者FoxPro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 05:33:09