Skip to content

How to create an entity where (most) fields and the type are only known at runtime? #957

Open
@scott-wilson

Description

@scott-wilson

I have a project where I want to be able to have the database drive the entity types and (most) fields at runtime.

There's two questions I have. Firstly I get a compiler error when hooking up the entity type to the schema through the find_entity method in the query type (code and error below). I know I'm missing something, but is there an example of an entity where the type and the fields are known at runtime? Also, it looks like the meta method doesn't have any way to communicate with the database. The answer to this question will likely tie in with the main question, but is there a way for an entity to get its type from the context?

Thank you!

Entity type

use juniper::{
    marker::GraphQLObjectType, Arguments, DefaultScalarValue, ExecutionResult, Executor,
    GraphQLEnum, GraphQLInputObject, GraphQLObject, GraphQLType, GraphQLValue, GraphQLValueAsync,
    ScalarValue,
};
use std::collections::HashMap;

#[derive(Debug, Clone)]
pub enum Value {
    Int(i32),
    Float(f64),
    String(String),
    Boolean(bool),
    Entity(Box<Entity>),
    Entities(Vec<Entity>),
}

#[derive(Debug, Clone)]
pub enum Type {
    Int,
    Float,
    String,
    Boolean,
    Entity,
    Entities,
}

#[derive(Debug, Clone)]
pub struct TypeInfo {
    pub entity_type: String,
    pub fields: HashMap<String, Type>,
}

#[derive(Debug, Clone)]
pub struct Entity {
    pub id: u64,
    pub status: EntityStatus,
    pub fields: HashMap<String, Value>,
}

impl GraphQLType<DefaultScalarValue> for Entity {
    fn name(info: &Self::TypeInfo) -> Option<&str> {
        Some(&info.entity_type)
    }

    fn meta<'r>(
        info: &Self::TypeInfo,
        registry: &mut juniper::Registry<'r, DefaultScalarValue>,
    ) -> juniper::meta::MetaType<'r, DefaultScalarValue>
    where
        DefaultScalarValue: 'r,
    {
        let mut fields = Vec::with_capacity(info.fields.len());

        for (field_name, field_type) in &info.fields {
            fields.push(match field_type {
                Type::Int => registry.field::<&i32>(field_name, &()),
                Type::Float => registry.field::<&f64>(field_name, &()),
                Type::String => registry.field::<&String>(field_name, &()),
                Type::Boolean => registry.field::<&bool>(field_name, &()),
                Type::Entity => {
                    let field_info = TypeInfo {
                        entity_type: "how_to_get_entity_type".into(),
                        fields: HashMap::new(),
                    };
                    registry.field::<&Entity>(field_name, &field_info)
                }
                Type::Entities => {
                    let field_info = TypeInfo {
                        entity_type: "how_to_get_entity_type".into(),
                        fields: HashMap::new(),
                    };
                    registry.field::<&Vec<Entity>>(field_name, &field_info)
                }
            });
        }

        registry
            .build_object_type::<Entity>(info, &fields)
            .into_meta()
    }
}

impl GraphQLValue<DefaultScalarValue> for Entity {
    type Context = crate::Context;
    type TypeInfo = TypeInfo;

    fn type_name<'i>(&self, info: &'i Self::TypeInfo) -> Option<&'i str> {
        <Entity as GraphQLType>::name(info)
    }
}

impl<S> GraphQLObjectType<S> for Entity
where
    Self: GraphQLType<S>,
    S: ScalarValue,
{
}

impl<S> GraphQLValueAsync<S> for Entity
where
    Self: GraphQLValue<S> + Sync,
    Self::TypeInfo: Sync,
    Self::Context: Sync,
    S: ScalarValue + Send + Sync,
{
}

Schema

use crate::entities::Entity;

use super::context::Context;
use juniper::meta::ScalarMeta;
use juniper::{graphql_object, Executor, FieldResult, ScalarValue};
use std::{collections::HashMap, fmt::Display};

pub struct Query;

#[graphql_object(context = Context)]
impl Query {
    fn api_version() -> &'static str {
        "0.1.0"
    }

    fn find_entities(context: &Context, id: String) -> FieldResult<Entity> {
        let entity = Entity {
            id: "123".to_string().into(),
            status: EntityStatus::Active,
            fields: HashMap::new(),
        };
        Ok(entity)
    }

    fn with_executor(executor: &Executor) -> bool {
        let info = executor.look_ahead();

        true
    }
}

pub struct Mutation;

#[graphql_object(context = Context, Scalar = S)]
impl<S: ScalarValue + Display> Mutation {}

pub type Schema = juniper::RootNode<'static, Query, Mutation, juniper::EmptySubscription<Context>>;

Compiler Error

error[E0277]: the trait bound `Result<Entity, FieldError>: IntoResolvable<'_, __S, _, context::Context>` is not satisfied
  --> server/src/schema.rs:13:1
   |
13 | #[graphql_object(context = Context)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoResolvable<'_, __S, _, context::Context>` is not implemented for `Result<Entity, FieldError>`
   |
   = help: the following implementations were found:
             <Result<(&'a <T as GraphQLValue<S2>>::Context, T), FieldError<S1>> as IntoResolvable<'a, S2, T, C>>
             <Result<T, E> as IntoResolvable<'a, S, T, C>>
             <Result<std::option::Option<(&'a <T as GraphQLValue<S2>>::Context, T)>, FieldError<S1>> as IntoResolvable<'a, S2, std::option::Option<T>, C>>
   = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0277]: the trait bound `Entity: GraphQLValue<__S>` is not satisfied
  --> server/src/schema.rs:13:1
   |
13 | #[graphql_object(context = Context)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `GraphQLValue<__S>` is not implemented for `Entity`
   |
   = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider extending the `where` bound, but there might be an alternative better way to express this requirement
   |
13 | #[graphql_object(context = Context)], Entity: GraphQLValue<__S>
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0308]: mismatched types
  --> server/src/schema.rs:13:1
   |
13 | #[graphql_object(context = Context)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   | |
   | expected enum `DefaultScalarValue`, found type parameter `__S`
   | help: try using a variant of the expected enum: `Ok(#[graphql_object(context = Context)])`
   |
   = note: expected enum `Result<_, FieldError<DefaultScalarValue>>`
              found enum `Result<juniper::Value<__S>, FieldError<__S>>`
   = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: mismatched types
  --> server/src/schema.rs:13:1
   |
13 | #[graphql_object(context = Context)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   | |
   | expected type parameter `__S`, found enum `DefaultScalarValue`
   | expected `Result<juniper::Value<__S>, FieldError<__S>>` because of return type
   |
   = note: expected enum `Result<juniper::Value<__S>, FieldError<__S>>`
              found enum `Result<_, FieldError<DefaultScalarValue>>`
   = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

error[E0308]: `match` arms have incompatible types
  --> server/src/schema.rs:13:1
   |
13 | #[graphql_object(context = Context)]
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   | |
   | expected type parameter `__S`, found enum `DefaultScalarValue`
   | this is found to be of type `Result<juniper::Value<__S>, FieldError<__S>>`
   | this is found to be of type `Result<juniper::Value<__S>, FieldError<__S>>`
   | `match` arms have incompatible types
   |
   = note: expected enum `Result<juniper::Value<__S>, FieldError<__S>>`
              found enum `Result<_, FieldError<DefaultScalarValue>>`
   = note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)

error: aborting due to 5 previous errors; 4 warnings emitted

Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `server`

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions