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

Embed support for #[SimpleObject], #[ComplexObject] #533

Closed
Bajix opened this issue Jun 1, 2021 · 12 comments
Closed

Embed support for #[SimpleObject], #[ComplexObject] #533

Bajix opened this issue Jun 1, 2021 · 12 comments
Labels
enhancement New feature or request

Comments

@Bajix
Copy link

Bajix commented Jun 1, 2021

Currently there's no way to use wrapper types for output objects without redefining every field resolver or having a nested path. This makes type composition harder, especially when using libraries such as Diesel where extra fields cannot exist. It would be very useful if embed worked for output types and not just input types

@Bajix Bajix added the enhancement New feature or request label Jun 1, 2021
@sunli829
Copy link
Collaborator

sunli829 commented Jun 5, 2021

Can you provide a code to illustrate what you expect?

@Bajix
Copy link
Author

Bajix commented Jun 5, 2021

use async_graphql::*;

#[derive(SimpleObject)]
#[graphql(complex)]
pub struct DataNode<T> {
  #[embed]
  data: T,
  #[graphql(skip)]
  preloads: HashMap<TypeId, Box<dyn Any>>
}

#[ComplexObject]
impl DataNode<User> {
 async fn taggings(&self) -> FieldResult<Taggings> { ... } 
}

For my use case if there's support for #[embed] then I can use this to make a macro that will generate field resolvers for receivers stored in a preloads container. But I could see this being used broadly for other use cases as well, for instance anytime you have a through table and need to decorate a struct with extra fields.

@Bajix
Copy link
Author

Bajix commented Jun 6, 2021

Maybe #[flatten] would be a better annotation name. This also really simplifies type composition; you can build some behavior around your base types and then extend those types. In Diesel for instance, fields must correspond with query output and it is not optional to include extra fields at all, so you rely more on composition to get around this.

@sunli829
Copy link
Collaborator

sunli829 commented Jun 8, 2021

use async_graphql::*;

#[derive(SimpleObject)]
#[graphql(complex)]
pub struct DataNode<T> {
  #[embed]
  data: T,
  #[graphql(skip)]
  preloads: HashMap<TypeId, Box<dyn Any>>
}

#[ComplexObject]
impl DataNode<User> {
 async fn taggings(&self) -> FieldResult<Taggings> { ... } 
}

For my use case if there's support for #[embed] then I can use this to make a macro that will generate field resolvers for receivers stored in a preloads container. But I could see this being used broadly for other use cases as well, for instance anytime you have a through table and need to decorate a struct with extra fields.

I don't understand the purpose of this embed, can you explain it in more detail? 😁

@cptrodgers
Copy link
Contributor

cptrodgers commented Jun 12, 2021

I think Bajix means flatten term similar to serde flatten. This link can help: https://serde.rs/attr-flatten.html#struct-flattening.

Ex in serde:

#[derive(Serialize, Deserialize)]
struct Pagination {
    limit: u64,
    offset: u64,
    total: u64,
}

#[derive(Serialize, Deserialize)]
struct Users {
    users: Vec<User>,

    #[serde(flatten)]
    pagination: Pagination,
}

The output of serialization is:

{
  "limit": 100,
  "offset": 200,
  "total": 1053,
  "users": [
    {"id": "49824073-979f-4814-be10-5ea416ee1c2f", "username": "john_doe"},
    ...
  ]
}

The fields of Pagination will be represented to the parent level.

@Bajix
Copy link
Author

Bajix commented Jun 24, 2021

A good example use case is to have contextual nodes. For example, say we have Profile, and WorkspaceProfile, whereby Profile can be owned by a User or a Workspace, and a Workspace can have a number of Profiles using a through table to designate roles; should we embed Profile in WorkspaceProfile, then we build base resolvers in Profile and extend with contextual behavior in WorkspaceProfile. Without this, one is either to make wrapper field resolvers for each profile resolver, or else to nest the properties. Any time one would use a through table to add extra contextual fields, this is useful. This could be queried by joining profiles, workspaces, workspace_roles and including the role from workspace_roles, but generically this would apply any time you'd want to use a through table to include extra contextual columns as fields.

use async_graphql::*;

#[derive(SimpleObject)]
pub struct WorkspaceProfile {
  #[graphql(embed)]
  profile: Profile,
  role: Role
}

@DoumanAsh
Copy link
Contributor

There is already flatten attribute in Union derive so it would be good to provide it for SimpleObject/ComplexObject

@bbqsrc
Copy link

bbqsrc commented Dec 1, 2021

I also support this use case. @sunli829: we make an API where the Query object will have different fields enabled or disabled depending on which customer we're building the API for. Right now that means having a lot of #[cfg(feature = "some-customer")] inside our Query object. It would be nice if we could instead have a separate SomeCustomerQuery struct, that we could then flatten into the main Query object conditionally as described in the above issues.

It would indeed work the same as serde's flatten.

@bbqsrc
Copy link

bbqsrc commented Jan 17, 2022

@sunli829 I'd like to take a shot at implementing this. Do you have a recommendation on where I should start looking inside the derives? 😄

@sunli829
Copy link
Collaborator

sunli829 commented Jan 18, 2022

This actually already exists, but I forgot to update the documentation. 😂

Now there is just a flatten parameter on SimpleObject macro, but it is not used.
@bbqsrc I'll get it done now.

sunli829 added a commit that referenced this issue Jan 18, 2022
@sunli829
Copy link
Collaborator

Released in 3.0.22

SimpleObject:

#[graphql(flatten)]

ComplexObject:

async fn a(&self) -> &A {

Object:

async fn a(&self) -> A {

@bbqsrc
Copy link

bbqsrc commented Jan 20, 2022

Works as advertised. You are a wizard. :D

@Bajix Bajix closed this as completed Sep 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

5 participants