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

FR: Consider adding support for multi-part form upload spec #441

Open
Dig-Doug opened this issue Dec 25, 2022 · 4 comments
Open

FR: Consider adding support for multi-part form upload spec #441

Dig-Doug opened this issue Dec 25, 2022 · 4 comments

Comments

@Dig-Doug
Copy link

This spec proposes an alternate request format to allow uploading files in graphql requests. It's supported by a lot of client/servers, e.g. async-graphql.

@Toasterson
Copy link

Toasterson commented Mar 19, 2024

For everybody coming here, this is doable with the current library and does not need a feature here. The trick is: Graphql uses a multipart map to replace variables with the file after the fact so the request body does not get analyzed during the reading of the request. A simple scalar like this is sufficient to work for async_graphql

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Upload(usize);

My full upload code is just a translated curl command to reqwest which ended up looking like this:

let vars = upload_component_file::Variables {
    name,
    version,
    revision,
    gate: gate_id,
    url: None,
    file: Some(Upload(0)),
    kind: kind.into(),
};
let request_body = UploadComponentFile::build_query(vars);
let request_str = serde_json::to_string(&request_body)?;
let client = reqwest::blocking::Client::new();

let file_map = "{ \"0\": [\"variables.file\"] }";

let some_file = reqwest::blocking::multipart::Part::file(file)?;

let form = reqwest::blocking::multipart::Form::new()
    .text("operations", request_str)
    .text("map", file_map)
    .part("0", some_file);

let res = client.post(forge_url).multipart(form).send()?;
res.json()?

@simbleau
Copy link

simbleau commented Apr 6, 2024

My issue is that I want to be able to derive GraphQLQuery on a example.graphql file like so, with async-graphql:

#[derive(GraphQLQuery)]
#[graphql(
    schema_path = "src/schema.json",
    query_path = "src/assets/upload.graphql",
)]
pub struct Upload;
mutation Upload($file: Upload!) {
    assets {
        upload(file: $file) {
            key
            version
        }
    }
}

How would I be able to derive that? Currently I'm getting a cryptic error:

Does this mean every type in .graphql files must be Serialize?

the trait bound `assets::Upload: ..::_::_serde::Serialize` is not satisfied
the following other types implement trait `...::_::_serde::Serialize`:
  bool
  char
  isize
  i8
  i16
...

@Toasterson
Copy link

You run into a name conflict with the type auto-generated and Your struct name. Rename mutation Upload($file: Upload!) { to something like mutation UploadFile($file: Upload!) { and your custom struct to pub struct UploadFile; Then add a separate

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Upload(usize);

like I did and it should work if no other types conflict with the name. Also yes all Scalar Types must implement the derives I have, as they do get serialized into the request, they get mapped over by convention.

Use cargo expand plugin or similar helpers to view the generated code and let an IDE check it. It should warn you about the duplication.

@simbleau
Copy link

simbleau commented Apr 6, 2024

Wow, that worked! Thank you @Toasterson !

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

3 participants