Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

disussion from 1248 #11

Open
smmoosavi opened this issue Mar 15, 2023 · 2 comments
Open

disussion from 1248 #11

smmoosavi opened this issue Mar 15, 2023 · 2 comments

Comments

@smmoosavi
Copy link
Owner

smmoosavi commented Mar 15, 2023

@smmoosavi Thanks. I have checked first scenario but I wasn't able to fit it into my case. The main problem is that I have part of GQL application that is defined as in above example with MyObj (not MySimpleObj). Could you provide example scenario for that?

As far as I understand I need to rewrite all Object defined with async_graphql to be defined with dynamic_graphql? Then it's hard to go from one library to second and still keep compatible. For example below will not compile:

#[derive(Debug, async_graphql::SimpleObject, dynamic_graphql::SimpleObject)]
#[graphql(rename_fields = "camelCase")]
#[graphql(shareable)] // this will cause compiler error 
pub struct Version {
     version: String,
     name: String,
}

Also in my scenario I don't know how to use states correctly with dynamic-graphql crate ExtendedObject don't accept self as parameter.

Below I share code that compile but panic at runtime with

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: SchemaError("Type \"MapperFromDynamicGraphqlToAsyncGraphql\" not found")', src/bin/gql-dynamic.rs:137:54

I would be glad if you could help me understand how can I fix it.

use async_graphql::{
    dynamic::ResolverContext,
    http::{playground_source, GraphQLPlaygroundConfig},
    Context, Object,
};

use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
use axum::{
    extract::Extension,
    response::{self, IntoResponse},
    routing::get,
    Router, Server,
};

pub type GqlResult<T> = async_graphql::Result<T>;
use dynamic_graphql::{
    dynamic, experimental::GetSchemaData, internal::Registry, ExpandObject, ExpandObjectFields,
    SimpleObject,
};

#[derive(Debug, async_graphql::SimpleObject, dynamic_graphql::SimpleObject)]
#[graphql(rename_fields = "camelCase")]
// FIXME this not works because of `dynamic_graphql::SimpleObject` #[graphql(shareable)]
pub struct Version {
    /// aplication name
    pub name: String,
    /// schematic version (semver.org)
    pub version: String,
    /// date of compilation
    pub compilation_date: Option<String>,
    /// unique hash that represent source code that was used to build
    pub compilation_hash: Option<String>,
}

pub struct AsyncGraphGqlObj {
    // inner_state will be connection to DB or other service
    inner_state: String,
}

#[Object]
impl AsyncGraphGqlObj {
    async fn name<'a>(&self, _ctx: &Context<'a>) -> &str {
        &self.inner_state
    }

    pub async fn version<'a>(&self, ctx: &Context<'a>) -> GqlResult<Vec<Version>> {
        let name = format!(
            "{}+{}",
            env!("CARGO_PKG_NAME"),
            self.name(ctx).await.unwrap().to_lowercase(),
        );
        let version = env!("CARGO_PKG_VERSION").to_string();
        let compilation_date = option_env!("VERGEN_BUILD_TIMESTAMP").map(String::from);
        let compilation_hash = option_env!("VERGEN_GIT_SHA").map(String::from);

        let gql_api_ver = Version {
            name,
            version,
            compilation_date,
            compilation_hash,
        };

        // TODO: query sth from DB where connection is keep in with &self (so inner state).

        Ok(vec![gql_api_ver])
    }
}

// this is just to make compiler happy
#[derive(SimpleObject)]
pub struct UselessButAllowCompile {}

#[derive(ExpandObject)]
struct MapperFromDynamicGraphqlToAsyncGraphql(UselessButAllowCompile);

#[ExpandObjectFields]
impl MapperFromDynamicGraphqlToAsyncGraphql {
    // just map query from dynamic-graphql to async-graphql implementation
    //
    // input parameters are here to test how they are handled by dynamic-graphql
    // more complicated requests will require parameters
    async fn version<'a>(address: String, ctx: &ResolverContext<'a>) -> GqlResult<Vec<Version>> {
        let d = ctx.get_schema_data();
        let o = d.get::<AsyncGraphGqlObj>().unwrap();

        // call async-graphql version
        o.version(ctx.ctx).await?
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    dotenvy::dotenv().ok();

    let registry = Registry::new()
        .register::<UselessButAllowCompile>()
        .register::<MapperFromDynamicGraphqlToAsyncGraphql>();

    let schema = dynamic::Schema::build("Query", None, None);
    let schema = registry.apply_into_schema_builder(schema);

    let query = dynamic::Object::new("Query");
    let query = query.field(dynamic::Field::new(
        "anyname",
        dynamic::TypeRef::named("MapperFromDynamicGraphqlToAsyncGraphql"),
        |_ctx| {
            /* What I am expecting here is to return Object that was defined with async_graphql*/
            // below is one of my tries to achieve it without success

            let o = MapperFromDynamicGraphqlToAsyncGraphql(UselessButAllowCompile {});
            dynamic::FieldFuture::new(async move {
                Ok(Some(
                    // depending on request this should return
                    /*
                    {
                        name: "my_inner_name_state"
                    }
                    or
                    {
                        version: {
                            name: "my_inner_name_state"
                        }
                    }
                     */
                    dynamic::FieldValue::owned_any(o).with_type("FooExpanded"),
                ))
            })
        },
    ));

    let o = AsyncGraphGqlObj {
        inner_state: String::from("my_inner_name_state"),
    };

    let schema = schema.data(o).register(query);

    let schema = schema.enable_federation().finish().unwrap();

    let app = Router::new()
        .route("/", get(graphql_playground).post(graphql_handler))
        .layer(Extension(schema));

    println!("Playground: http://localhost:8000");

    Server::bind(&"0.0.0.0:8000".parse()?)
        .serve(app.into_make_service())
        .await
        .unwrap();

    Ok(())
}

async fn graphql_handler(
    schema: Extension<async_graphql::dynamic::Schema>,
    req: GraphQLRequest,
) -> GraphQLResponse {
    let inner = req.into_inner();
    println!("req: {:?}", inner);
    schema.execute(inner).await.into()
}

async fn graphql_playground() -> impl IntoResponse {
    response::Html(playground_source(GraphQLPlaygroundConfig::new("/")))
}

Originally posted by @xoac in async-graphql/async-graphql#1248 (comment)

@smmoosavi smmoosavi changed the title @smmoosavi Thanks. I have checked first scenario but I wasn't able to fit it into my case. The main problem is that I have part of GQL application that is defined as in above example with MyObj (not MySimpleObj). Could you provide example scenario for that? disussion from 1248 Mar 15, 2023
@smmoosavi
Copy link
Owner Author

smmoosavi commented Mar 15, 2023

@xoac

dynamic graphql is not designed to work with the default async-graphql schema. only async-graphql::dynamic is supported. #[derive(Debug, async_graphql::SimpleObject, dynamic_graphql::SimpleObject)] may be not works.

federation attributes (shareable, tag, ...) are not supported yet in dynamic-graphql

dynamic-graphql::SimpleObject and async-graphql::SimpleObject are similar and defined graphql types based on structs.
ResolveObject and ResolvedObjectFields together are similar to #[Object] and allow users to define type based on the impl block
ExpandObject and ExpandObjectFields allow users to add fields to an existing object. If you want to access self in ExpandObjectFields, it should be defined like this:

use ExampleQuery<'a>(&'a Query) not ExampleQuery(Query)

    #[derive(SimpleObject)]
    #[graphql(root)]
    struct Query {
        foo: String,
        #[graphql(skip)]
        example: Option<Example>,
    }

    #[derive(SimpleObject)]
    struct Example {
        field: String,
    }

    #[derive(ExpandObject)]
    struct ExampleQuery<'a>(&'a Query); // expand Query object type

    #[ExpandObjectFields]
    impl<'a> ExampleQuery<'a> {
        fn example(&self) -> Option<&'a Example> { // define field example for query
            self.0.example.as_ref()
        }
    }

@smmoosavi
Copy link
Owner Author

Another piece of advice: make your graphql layer as thin as possible and move business logic, authorization, etc to the business logic layer, and graphql only call the business logic layer without conditions and logics

see more in here: graphql-python/graphene-django#79 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant