Search code examples
jsonrustresponseserdereqwest

I'm having a hard time deserializing a rust reqwests::response into json


I'm building a webserver that authenticates via oauth2. With the example from axum/oauth I got it to work with my nextcloud-instance. To get the UserId and store it in a database, I'd like to make a request to the OCS-Api from my nextcloud instance. The request works and I get the expected response, but I can't get it to serialize. Here's the OCS-Api documentation with an expected example of a response.

{
  "ocs": {
    "meta": {
      "status": "string",
      "statuscode": 0,
      "message": "string",
      "totalitems": "string",
      "itemsperpage": "string"
    },
    "data": {
      "userId": "string",
      "message": "string",
      "icon": "string",
      "clearAt": 0,
      "status": "online",
      "messageId": "string",
      "messageIsPredefined": true,
      "statusIsUserDefined": true
    }
  }
}

https://docs.nextcloud.com/server/latest/developer_manual/_static/openapi.html#/operations/user_status-user_status-get-status

I tried to serialize the Json from the Response into my OCSUser struct

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct OCSUser {
    user_id: String,
}

Here's the function in question:

let token = oauth_client
        .exchange_code(AuthorizationCode::new(query.code.clone()))
        .request_async(async_http_client)
        .await
        .context("failed in sending request request to authorization server")?;

    // Fetch user data from discord
    let client = reqwest::Client::new();
    let client2 = client
        // https://discord.com/developers/docs/resources/user#get-current-user
        .get("https://nextcloud.apo-riesinger.de/ocs/v2.php/apps/user_status/api/v1/user_status")
        .bearer_auth(token.access_token().secret())
        .header("OCS-APIREQUEST", "true")
        .header(ACCEPT, "application/json")
        .send()
        .await
        .context("failed in sending request to target Url")?;
    println!("{:?}", &client2);
    let user_data = client2
        .json::<OCSUser>()
        .await
        .context("failed to deserialize response as JSON")?;
    println!("{:?}", &user_data);

It always reaches the print statement with &client2, but can't reach the last one. To test the waters, I tried to model the structure of the expected response according to the documentation and got an output via the text method from Structure. The output was the following

"{\"ocs\":{\"meta\":{\"status\":\"ok\",\"statuscode\":200,\"message\":\"OK\"},\"data\":{\"userId\":\"chef\",\"message\":null,\"messageId\":null,\"messageIsPredefined\":false,\"icon\":null,\"clearAt\":null,\"status\":\"offline\",\"statusIsUserDefined\":false}}}"

The last idea I had was that the textencoding was different, but I'm not sure. For completeness, here's the content of the printstatement with client2

Response { url: Url { scheme: "https", cannot_be_a_base: false, username: "", password: None, host: Some(Domain("nextcloud.apo-riesinger.de")), port: None, path: "/ocs/v2.php/apps/user_status/api/v1/user_status", query: None, fragment: None }, status: 200, headers: {"server": "openresty", "date": "Wed, 24 Jan 2024 23:51:35 GMT", "content-type": "application/json; charset=utf-8", "content-length": "223", "connection": "keep-alive", "referrer-policy": "no-referrer", "x-content-type-options": "nosniff", "x-frame-options": "SAMEORIGIN", "x-permitted-cross-domain-policies": "none", "x-robots-tag": "noindex, nofollow", "x-xss-protection": "1; mode=block", "x-powered-by": "PHP/8.2.15", "set-cookie": "oc5ny1g9qec3=53d44aaeaa6fd48dccad3e27f7525d44; path=/; secure; HttpOnly; SameSite=Lax", "set-cookie": "oc_sessionPassphrase=FhePXWSLltK4IXpDmcTi%2Fk0pT%2FDEcDRXQjW%2Fu1jcew%2BjvxiUeEnMQl11pRIRNDq3nHncV6xcDLJ26zNR2%2BStn1dcx%2F684OWV6tzK0Dg92Hxg%2BxjCMDbxAShCWB9Mmp5O; path=/; secure; HttpOnly; SameSite=Lax", "set-cookie": "oc5ny1g9qec3=53d44aaeaa6fd48dccad3e27f7525d44; path=/; secure; HttpOnly; SameSite=Lax", "set-cookie": "__Host-nc_sameSiteCookielax=true; path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=lax", "set-cookie": "__Host-nc_sameSiteCookiestrict=true; path=/; httponly;secure; expires=Fri, 31-Dec-2100 23:59:59 GMT; SameSite=strict", "set-cookie": "oc5ny1g9qec3=53d44aaeaa6fd48dccad3e27f7525d44; path=/; secure; HttpOnly; SameSite=Lax", "set-cookie": "oc5ny1g9qec3=53d44aaeaa6fd48dccad3e27f7525d44; path=/; secure; HttpOnly; SameSite=Lax", "set-cookie": "oc5ny1g9qec3=53d44aaeaa6fd48dccad3e27f7525d44; path=/; secure; HttpOnly; SameSite=Lax", "set-cookie": "oc5ny1g9qec3=53d44aaeaa6fd48dccad3e27f7525d44; path=/; secure; HttpOnly; SameSite=Lax", "set-cookie": "oc5ny1g9qec3=53d44aaeaa6fd48dccad3e27f7525d44; path=/; secure; HttpOnly; SameSite=Lax", "set-cookie": "oc5ny1g9qec3=53d44aaeaa6fd48dccad3e27f7525d44; path=/; secure; HttpOnly; SameSite=Lax", "set-cookie": "oc5ny1g9qec3=53d44aaeaa6fd48dccad3e27f7525d44; path=/; secure; HttpOnly; SameSite=Lax", "expires": "Thu, 19 Nov 1981 08:52:00 GMT", "cache-control": "no-cache, no-store, must-revalidate", "pragma": "no-cache", "content-security-policy": "default-src 'none';base-uri 'none';manifest-src 'self';frame-ancestors 'none'", "x-request-id": "F1j2zkPIca8Z5fS66ktF", "feature-policy": "autoplay 'none';camera 'none';fullscreen 'none';geolocation 'none';microphone 'none';payment 'none'", "strict-transport-security": "max-age=63072000; preload", "x-served-by": "nextcloud.apo-riesinger.de"} }

As this is my first rust project, I'd really appreciate the help. It is a really difficult but rather rewarding language. Does anyone have an idea? Thank you very much.


Solution

  • Your struct needs to include the same nested structure as the JSON.

    #[derive(Debug, Serialize, Deserialize)]
    struct OCSUser {
        ocs: Ocs,
    }
    
    #[derive(Debug, Serialize, Deserialize)]
    struct Ocs {
        data: OcsData,
    }
    
    #[derive(Debug, Serialize, Deserialize)]
    #[serde(rename_all = "camelCase")]
    struct OcsData {
        user_id: String,
    }