googleapis / google-api-dotnet-client

Google APIs Client Library for .NET
https://developers.google.com/api-client-library/dotnet
Apache License 2.0
1.36k stars 526 forks source link

Deserealization problem in Google.Apis.Groupssettings #2841

Closed Gurio1 closed 1 month ago

Gurio1 commented 1 month ago

Hello. Im trying to set access settings to a group, so i use GroupssettingsService.

Problem is that in that line - var groupSettings = _service.Groups.Get(groupEmail).Execute(); As a response i get xml file and get this error :ERROR: Failed to parse response from server as json [<?xml version="1.0" encoding="UTF-8"?>

and all xml file. So my question is, does it work as expected or i should somehow to specify to use altEnum json?
amanda-tarafa commented 1 month ago

There over 400 packages that we maintain on this repo.

It's not clear which package you are trying to use, which version of that package, etc. We certainly don't seem to have a Google.Apis.GroupSettings package or similar. The package name is unfortunately Google.Apis.Groupssettings.v1 which didn't show up when I searchd for Google.Apis.Groupsettings and Google.Apis.GroupSettings (two s vs. one s).

Can you please provide a small but complete console application, including the .csproj file so we can be clear on dependencies, that reproduces the issue you are seeing?

Gurio1 commented 1 month ago

Hello.

I use Google.Apis.Groupssettings.v1(https://www.nuget.org/packages/Google.Apis.Groupssettings.v1) Version : 1.68.0.2721

using Google.Apis.Auth.OAuth2;
using Google.Apis.Groupssettings.v1;
using Google.Apis.Services;

UserCredential credential;

await using (var stream = new FileStream("clientSecretFilePath", FileMode.Open, FileAccess.Read))
{
    credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
        (await GoogleClientSecrets.FromStreamAsync(stream)).Secrets,
        new[]
        {
            GroupssettingsService.Scope.AppsGroupsSettings,    // For managing group access settings
        },
        "test", // User ID
        CancellationToken.None);
}

var service = new GroupssettingsService(new BaseClientService.Initializer()
{
    HttpClientInitializer = credential,
    ApplicationName = "AppName",
});

var groupEmail = "name";

var groupSettings = service.Groups.Get(groupEmail).Execute();

and .csproj file

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
      <PackageReference Include="Google.Apis.Groupssettings.v1" Version="1.68.0.2721" />
      <PackageReference Include="Google.Apis.Oauth2.v2" Version="1.68.0.1869" />
    </ItemGroup>

</Project>
amanda-tarafa commented 1 month ago

Thanks for the repor, I'll take a look and report back.

amanda-tarafa commented 1 month ago

Two different thigns are happening here at the same time.

  1. The service is responding with an error, that we cannot see because
  2. The service response is in an unexpected format, XML where we are expecting JSON, so we cannot parse the response.

We don't have a solution for 2, because some services do sometines return error responses as XML, but not necessarily with a known schema, etc., so we cannot extract significant information from it.

But, we do include in the exception the full response content as returned by the service. If you wrap your service call in a try/catch as follows, then you can see the error description:

try
{
    var groupSettings = service.Groups.Get(groupEmail).Execute();
}
catch(GoogleApiException exception)
{
    Console.WriteLine("Service raw error:");
    Console.WriteLine(exception.Error?.ErrorResponseContent);
}

Once you know what the underlying service error is, let us know if you need help with it.

As a side note, I've changed your repro slightly so that you don't need to manipulate streams directly for creating the credential. And you don't need the dependency to Google.Apis.OAuth2.v2, the recommende OAuth package for .NET is Google.Apis.Auth, that you will get as a transitive dependency from Google.Apis.Groupssettings.v1. The full modified code and project file are as follows:

using Google;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Auth.OAuth2.Flows;
using Google.Apis.Groupssettings.v1;
using Google.Apis.Services;

var clientSecretsPath = Environment.GetEnvironmentVariable("TEST_CLIENT_SECRET_FILENAME");
var clientSecrets = await GoogleClientSecrets.FromFileAsync(clientSecretsPath);

UserCredential credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
    new GoogleAuthorizationCodeFlow.Initializer
    {
        ClientSecrets = clientSecrets.Secrets,
    },
    new[] { GroupssettingsService.Scope.AppsGroupsSettings },
    "user-issue-2841",
    CancellationToken.None);

var service = new GroupssettingsService(new BaseClientService.Initializer()
{
    HttpClientInitializer = credential,
    ApplicationName = "AppName",
});

var groupEmail = "name";

try
{
    var groupSettings = service.Groups.Get(groupEmail).Execute();
}
catch(GoogleApiException exception)
{
    Console.WriteLine("Service raw error:");
    Console.WriteLine(exception.Error?.ErrorResponseContent);
}
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Google.Apis.Groupssettings.v1" Version="1.68.0.2721" />
  </ItemGroup>

</Project>
Gurio1 commented 1 month ago

Hello. Thank you for your side note. I followed this guide : https://developers.google.com/api-client-library/dotnet/guide/aaa_oauth

About the error - line Console.WriteLine(exception.Error?.ErrorResponseContent); } returns null,but it hits the catch block with the following exception

The service groupssettings has thrown an exception.
No HttpStatusCode was specified.
No error details were specified.
Google.GoogleApiException: Failed to parse response from server as json [<?xml version="1.0" encoding="UTF-8"?>
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gd="http://schemas.google.com/g/2005" xmlns:apps="http://schemas.google.com/apps/2006">
    <id>tag:googleapis.com,2010:apps:groupssettings:GROUP:createdbyappgroup@unibit.bg</id>
    <title>Groups Resource Entry</title>
    <content type="text">C# App</content>
    <author>
        <name>Google</name>
    </author>
    <apps:email>createdbyappgroup@unibit.bg</apps:email>
    <apps:name>C# App</apps:name>
    <apps:description>This group created by C# app</apps:description>
    <apps:whoCanJoin>CAN_REQUEST_TO_JOIN</apps:whoCanJoin>
    <apps:whoCanViewMembership>ALL_MEMBERS_CAN_VIEW</apps:whoCanViewMembership>
    <apps:whoCanViewGroup>ALL_MEMBERS_CAN_VIEW</apps:whoCanViewGroup>
    <apps:whoCanInvite>ALL_MANAGERS_CAN_INVITE</apps:whoCanInvite>
    <apps:whoCanAdd>ALL_MANAGERS_CAN_ADD</apps:whoCanAdd>
    <apps:allowExternalMembers>false</apps:allowExternalMembers>
    <apps:whoCanPostMessage>ANYONE_CAN_POST</apps:whoCanPostMessage>
    <apps:allowWebPosting>true</apps:allowWebPosting>
    <apps:primaryLanguage>en_US</apps:primaryLanguage>
    <apps:maxMessageBytes>26214400</apps:maxMessageBytes>
    <apps:isArchived>false</apps:isArchived>
    <apps:archiveOnly>false</apps:archiveOnly>
    <apps:messageModerationLevel>MODERATE_NONE</apps:messageModerationLevel>
    <apps:spamModerationLevel>MODERATE</apps:spamModerationLevel>
    <apps:replyTo>REPLY_TO_IGNORE</apps:replyTo>
    <apps:includeCustomFooter>false</apps:includeCustomFooter>
    <apps:customFooterText></apps:customFooterText>
    <apps:sendMessageDenyNotification>false</apps:sendMessageDenyNotification>
    <apps:defaultMessageDenyNotificationText></apps:defaultMessageDenyNotificationText>
    <apps:showInGroupDirectory>true</apps:showInGroupDirectory>
    <apps:allowGoogleCommunication>false</apps:allowGoogleCommunication>
    <apps:membersCanPostAsTheGroup>false</apps:membersCanPostAsTheGroup>
    <apps:defaultSender>DEFAULT_SELF</apps:defaultSender>
    <apps:messageDisplayFont>DEFAULT_FONT</apps:messageDisplayFont>
    <apps:includeInGlobalAddressList>true</apps:includeInGlobalAddressList>
    <apps:whoCanLeaveGroup>ALL_MEMBERS_CAN_LEAVE</apps:whoCanLeaveGroup>
    <apps:whoCanContactOwner>ANYONE_CAN_CONTACT</apps:whoCanContactOwner>
    <apps:whoCanAddReferences>NONE</apps:whoCanAddReferences>
    <apps:whoCanAssignTopics>NONE</apps:whoCanAssignTopics>
    <apps:whoCanUnassignTopic>NONE</apps:whoCanUnassignTopic>
    <apps:whoCanTakeTopics>NONE</apps:whoCanTakeTopics>
    <apps:whoCanMarkDuplicate>NONE</apps:whoCanMarkDuplicate>
    <apps:whoCanMarkNoResponseNeeded>NONE</apps:whoCanMarkNoResponseNeeded>
    <apps:whoCanMarkFavoriteReplyOnAnyTopic>NONE</apps:whoCanMarkFavoriteReplyOnAnyTopic>
    <apps:whoCanMarkFavoriteReplyOnOwnTopic>NONE</apps:whoCanMarkFavoriteReplyOnOwnTopic>
    <apps:whoCanUnmarkFavoriteReplyOnAnyTopic>NONE</apps:whoCanUnmarkFavoriteReplyOnAnyTopic>
    <apps:whoCanEnterFreeFormTags>NONE</apps:whoCanEnterFreeFormTags>
    <apps:whoCanModifyTagsAndCategories>NONE</apps:whoCanModifyTagsAndCategories>
    <apps:favoriteRepliesOnTop>true</apps:favoriteRepliesOnTop>
    <apps:whoCanApproveMembers>ALL_MANAGERS_CAN_APPROVE</apps:whoCanApproveMembers>
    <apps:whoCanBanUsers>OWNERS_AND_MANAGERS</apps:whoCanBanUsers>
    <apps:whoCanModifyMembers>OWNERS_AND_MANAGERS</apps:whoCanModifyMembers>
    <apps:whoCanApproveMessages>OWNERS_AND_MANAGERS</apps:whoCanApproveMessages>
    <apps:whoCanDeleteAnyPost>OWNERS_AND_MANAGERS</apps:whoCanDeleteAnyPost>
    <apps:whoCanDeleteTopics>OWNERS_AND_MANAGERS</apps:whoCanDeleteTopics>
    <apps:whoCanLockTopics>OWNERS_AND_MANAGERS</apps:whoCanLockTopics>
    <apps:whoCanMoveTopicsIn>OWNERS_AND_MANAGERS</apps:whoCanMoveTopicsIn>
    <apps:whoCanMoveTopicsOut>OWNERS_AND_MANAGERS</apps:whoCanMoveTopicsOut>
    <apps:whoCanPostAnnouncements>OWNERS_AND_MANAGERS</apps:whoCanPostAnnouncements>
    <apps:whoCanHideAbuse>NONE</apps:whoCanHideAbuse>
    <apps:whoCanMakeTopicsSticky>NONE</apps:whoCanMakeTopicsSticky>
    <apps:whoCanModerateMembers>OWNERS_AND_MANAGERS</apps:whoCanModerateMembers>
    <apps:whoCanModerateContent>OWNERS_AND_MANAGERS</apps:whoCanModerateContent>
    <apps:whoCanAssistContent>NONE</apps:whoCanAssistContent>
    <apps:customRolesEnabledForSettingsToBeMerged>false</apps:customRolesEnabledForSettingsToBeMerged>
    <apps:enableCollaborativeInbox>false</apps:enableCollaborativeInbox>
    <apps:whoCanDiscoverGroup>ALL_IN_DOMAIN_CAN_DISCOVER</apps:whoCanDiscoverGroup>
</entry>]
 ---> Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: <. Path '', line 0, position 0.
   at Newtonsoft.Json.JsonTextReader.ParseValue()
   at Newtonsoft.Json.JsonTextReader.Read()
   at Newtonsoft.Json.JsonReader.ReadAndMoveToContent()
   at Newtonsoft.Json.JsonReader.ReadForType(JsonContract contract, Boolean hasConverter)
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader reader, Type objectType, Boolean checkAdditionalContent)
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader reader, Type objectType)
   at Newtonsoft.Json.JsonSerializer.Deserialize(JsonReader reader, Type objectType)
   at Google.Apis.Json.NewtonsoftJsonSerializer.Deserialize(String input, Type type)
   at Google.Apis.Json.NewtonsoftJsonSerializer.Deserialize[T](String input)
   at Google.Apis.Services.BaseClientService.DeserializeResponse[T](HttpResponseMessage response)
   --- End of inner exception stack trace ---
   at Google.Apis.Services.BaseClientService.DeserializeResponse[T](HttpResponseMessage response)
   at Google.Apis.Requests.ClientServiceRequest`1.ParseResponse(HttpResponseMessage response)
   at Google.Apis.Requests.ClientServiceRequest`1.Execute()
amanda-tarafa commented 1 month ago

For some reason, this API is responding with Atom/XML even on success, at least for some cases. We'd only seen that before for errors, so I though that you had an underlying error.

If you explicitly request JSON, things should work:

var request = service.Groups.Get(groupEmail);
request.Alt = GroupssettingsBaseServiceRequest<Google.Apis.Groupssettings.v1.Data.Groups>.AltEnum.Json;
var groupSettings = request.Execute();
Gurio1 commented 1 month ago

Thank you. Now it works.

amanda-tarafa commented 1 month ago

Glad to hear it. I'll try to reach out to the API team internally because the documentation does state that JSON is the default format.

Do you think it's possible you have a proxy requesting Atom/XML explicitly if a format has not been specified?

Gurio1 commented 1 month ago

No,i for sure dont have any proxy.

For example, i also use <PackageReference Include="Google.Apis.Admin.Directory.directory_v1" Version="1.68.0.3533" /> and it works as expected.

amanda-tarafa commented 1 month ago

Thanks for that. I'm reaching out internally to the API team, but you might want to report this through their Support Channels as well.

I'll close this issue now as there's not much more we can do about this.

Thanks!