obmarg / cynic

A bring your own types GraphQL client library for Rust
https://cynic-rs.dev
Mozilla Public License 2.0
378 stars 46 forks source link

invalid type: integer ..., expected a string #992

Closed PyroPM closed 3 months ago

PyroPM commented 3 months ago

I am currently experiencing an issue where I receive an integer value for an ID and Reqwest throws an error.

called `Result::unwrap()` on an `Err` value: ReqwestError(reqwest::Error { kind: Decode, source: Error("invalid type: integer `523946`, expected a string", line: 1, column: 34) })

The struct relevant here:

#[derive(cynic::QueryFragment, Debug)]
pub struct Tournament {
    pub id: Option<cynic::Id>,
    pub name: Option<String>,
    pub slug: Option<String>,
    pub short_slug: Option<String>,
    pub start_at: Option<Timestamp>,
    pub events: Option<Vec<Option<Event>>>,
}

where id is the GraphQL ID that comes in as an integer. I can provide the query and the schema if necessary.

I believe this issue is similar to #711, however this seems to be associated with cynic::id.

obmarg commented 3 months ago

Hey @PyroPM - the spec says this about IDs:

The ID type is serialized in the same way as a String; however, it is not intended to be human-readable. While it is often numeric, it should always serialize as a String.

So if your server is returning an integer then it's not following the specification correctly. If possible I'd try and fix in the server. If you can't for whatever reason then you can take steps similar to what I recommended in #711 and define a custom ID type to use instead:

#[derive(cynic::Scalar)]
#[cynic(graphql_type = "ID")]
struct StringId(String)
PyroPM commented 3 months ago

Thank you for the answer! I did attempt that and unfortunately I am getting the same error as presented... if this is truly a server-side issue I might just be stuck.

The server in question is a public, closed-source API at this endpoint so unfortunately I cannot touch the server at all. Oddly enough, using a crate like gql_client parsed it down to an integer rather than a string (though I'm trying to migrate to cynic).

obmarg commented 3 months ago

Can you share the code you tried that isn't working?

PyroPM commented 3 months ago

Absolutely!

//////////////////////////////////////////////////
// structs
//////////////////////////////////////////////////

#[cynic::schema("startgg")]
mod schema {}

#[derive(cynic::Scalar, Debug)]
#[cynic(graphql_type = "ID")]
struct StringId(String);

#[derive(cynic::QueryVariables, Debug)]
pub struct TournamentQueryVariables {
    pub slug: String,
}

#[derive(cynic::QueryFragment, Debug)]
#[cynic(graphql_type = "Query", variables = "TournamentQueryVariables")]
pub struct TournamentQuery {
    #[arguments(slug: $slug)]
    pub tournament: Option<Tournament>,
}

#[derive(cynic::QueryFragment, Debug)]
pub struct Tournament {
    pub id: Option<StringId>,
    pub name: Option<String>,
    pub slug: Option<String>,
    pub short_slug: Option<String>,
    pub start_at: Option<Timestamp>,
    pub events: Option<Vec<Option<Event>>>,
}

#[derive(cynic::QueryFragment, Debug)]
pub struct Event {
    pub id: Option<StringId>,
    pub name: Option<String>,
}

#[derive(cynic::Scalar, Debug, Clone)]
pub struct Timestamp(pub String);

fn main() {
    let result = run_query();
    println!("{:?}", result);
}

fn run_query() -> cynic::GraphQlResponse<TournamentQuery> {
    use cynic::http::ReqwestBlockingExt;

    let query = build_query();
    let token = std::env::var("STARTGG_TOKEN").expect("STARTGG_TOKEN must be set.");
    let response = reqwest::blocking::Client::new()
        .post("https://api.start.gg/gql/alpha/index")
        .header("Authorization", format!("Bearer {}", token))
        .run_graphql(query);

    match response {
        Ok(ref res) => match res.data {
            Some(TournamentQuery { tournament: Some(ref tournament) }) => {
                println!("Tournament: {:?} - {:?}", tournament.name, tournament.id)
            }
            _ => {
                println!("No tournament found.");
            }
        },
        _ => {
            println!("Request failed.");
        }
    }

    return response.unwrap();
}

fn build_query() -> cynic::Operation<TournamentQuery, TournamentQueryVariables> {
    use cynic::QueryBuilder;

    TournamentQuery::build(TournamentQueryVariables {
        slug: "evo-2023".to_string(),
    })
}

I can also provide the example query with the result if you'd like!

obmarg commented 3 months ago

Ok, so there's two things.

  1. My initial advice was slightly wrong - sorry, obviously I was still half asleep. The IDs are coming back as integers and not strings so what you want is actually
#[derive(cynic::Scalar, Debug)]
#[cynic(graphql_type = "ID")]
struct IntegerId(u32);
  1. Your timestamp type is wrong - you've defined it with a string when it's actually an integer. If you change it to the following then it should run fine:
#[derive(cynic::Scalar, Debug, Clone)]
pub struct Timestamp(pub u32);
PyroPM commented 3 months ago

Perfect, that did the trick! I got the timestamp code straight from the generator, but those both fixed it, thank you!

obmarg commented 3 months ago

Yeah, the generator is currently quite bad with custom scalars. It assumes they’re a string in a way that’s quite broken if they are not.

There is probably something better that it could do, but I don’t know exactly what.

On Sat, Jul 20, 2024 at 19:43, PyroPM @.***(mailto:On Sat, Jul 20, 2024 at 19:43, PyroPM < wrote:

Perfect, that did the trick! I got the timestamp code straight from the generator, but those both fixed it, thank you!

— Reply to this email directly, view it on GitHub, or unsubscribe. You are receiving this because you commented.Message ID: @.***>