Skip to content

Commit

Permalink
feat: Generate Raw and DateTimeOffset properties for google-datetime …
Browse files Browse the repository at this point in the history
…properties

We keep separate string and object representations for the sake of compatibility.
  • Loading branch information
jskeet committed Jun 26, 2023
1 parent e66b1f0 commit c769a2f
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 2 deletions.
92 changes: 92 additions & 0 deletions Google.Api.Generator.Rest.Tests/GeneratedCodeTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Apis.ManufacturerCenter.v1;
using Google.Apis.ManufacturerCenter.v1.Data;
using Google.Apis.Storage.v1;
using Google.Apis.Storage.v1.Data;
using System;
Expand All @@ -27,6 +29,7 @@ namespace Google.Apis.Tests.Apis.Services;
public class GeneratedCodeTest
{
#pragma warning disable CS0618 // Type or member is obsolete
// Tests around Discovery date-time format properties.
[Fact]
public void StorageBucketCreatedTime_Default()
{
Expand Down Expand Up @@ -83,6 +86,95 @@ private static void AssertStorageBucketCreatedTimeProperties(Bucket bucket)
var actualJson = new StorageService().SerializeObject(bucket);
Assert.Equal(expectedJson, actualJson);
}

// Tests around Discovery google-datetime format properties.
[Fact]
public void IssueTimestamp_Default()
{
var issue = new Issue();
Assert.Null(issue.TimestampRaw);
Assert.Null(issue.Timestamp);
Assert.Null(issue.TimestampDateTimeOffset);
}

[Fact]
public void IssueTimestamp_ParsedFromJson()
{
string json = "{'timestamp':'2023-06-14T12:34:45.123Z'}".Replace('\'', '"');
var issue = (Issue) new ManufacturerCenterService().Serializer.Deserialize(json, typeof(Issue));
AssertIssueTimestampProperties(issue);
}

[Fact]
public void Issue_SetTimestampDateTimeOffset_Utc()
{
var issue = new Issue();
issue.TimestampDateTimeOffset = new DateTimeOffset(2023, 6, 14, 12, 34, 45, 123, TimeSpan.Zero);
AssertIssueTimestampProperties(issue);
}

[Fact]
public void Issue_SetTimestampDateTimeOffset_NonUtc()
{
var issue = new Issue();
// This gets normalized to the UTC version.
issue.TimestampDateTimeOffset = new DateTimeOffset(2023, 6, 14, 13, 34, 45, 123, TimeSpan.FromHours(1));
AssertIssueTimestampProperties(issue);
}

[Fact]
public void Issue_SetTimestampRaw()
{
var issue = new Issue();
issue.TimestampRaw = "2023-06-14T12:34:45.123Z";
AssertIssueTimestampProperties(issue);
}

[Fact]
public void Issue_SetTimestamp_DateTime()
{
var issue = new Issue();
var dateTime = new DateTime(2023, 6, 14, 12, 34, 45, 123, DateTimeKind.Utc);
issue.Timestamp = dateTime;
AssertIssueTimestampProperties(issue);
}

[Fact]
public void Issue_SetTimestamp_String()
{
var issue = new Issue();
var text = "2023-06-14T12:34:45.123Z";
issue.Timestamp = text;
// Can't call AssertIssueTimestampProperties as Timestamp is a string.
Assert.Equal(text, issue.TimestampRaw);
Assert.Equal(text, issue.Timestamp);
Assert.Equal(new DateTimeOffset(2023, 6, 14, 12, 34, 45, 123, TimeSpan.Zero), issue.TimestampDateTimeOffset);
}

[Fact]
public void Issue_SetTimestamp_DateTimeOffset()
{
var issue = new Issue();
var dateTimeOffset = new DateTimeOffset(2023, 6, 14, 12, 34, 45, 123, TimeSpan.Zero);
issue.Timestamp = dateTimeOffset;
// Can't call AssertIssueTimestampProperties as Timestamp is a DTO, and the string representation
// uses +00:00 instead of Z. This unfortunately means that TimestampDateTimeOffset will fail,
// but it matches the current behavior - basically setting a google-datetime to a DateTimeOffset
// (via the object property) in a request will cause a failure (unless the server-side parsing is lenient).
Assert.Equal("2023-06-14T12:34:45.123+00:00", issue.TimestampRaw);
Assert.Equal(dateTimeOffset, issue.Timestamp);
Assert.Throws<FormatException>(() => issue.TimestampDateTimeOffset);
}

private static void AssertIssueTimestampProperties(Issue issue)
{
Assert.Equal("2023-06-14T12:34:45.123Z", issue.TimestampRaw);
Assert.Equal(new DateTime(2023, 6, 14, 12, 34, 45, 123, DateTimeKind.Utc), issue.Timestamp);
Assert.Equal(new DateTimeOffset(2023, 6, 14, 12, 34, 45, 123, TimeSpan.Zero), issue.TimestampDateTimeOffset);
string expectedJson = "{'timestamp':'2023-06-14T12:34:45.123Z'}".Replace('\'', '"');
var actualJson = new ManufacturerCenterService().SerializeObject(issue);
Assert.Equal(expectedJson, actualJson);
}
#pragma warning restore CS0618 // Type or member is obsolete

}
Original file line number Diff line number Diff line change
Expand Up @@ -1119,9 +1119,42 @@ public class Issue : Google.Apis.Requests.IDirectResponseSchema
[Newtonsoft.Json.JsonPropertyAttribute("severity")]
public virtual string Severity { get; set; }

private string _timestampRaw;

private object _timestamp;

/// <summary>The timestamp when this issue appeared.</summary>
[Newtonsoft.Json.JsonPropertyAttribute("timestamp")]
public virtual object Timestamp { get; set; }
public virtual string TimestampRaw
{
get => _timestampRaw;
set
{
_timestamp = Google.Apis.Util.Utilities.DeserializeForGoogleFormat(value);
_timestampRaw = value;
}
}

/// <summary><seealso cref="object"/> representation of <see cref="TimestampRaw"/>.</summary>
[Newtonsoft.Json.JsonIgnoreAttribute]
[System.ObsoleteAttribute("This property is obsolete and may behave unexpectedly; please use TimestampDateTimeOffset instead.")]
public virtual object Timestamp
{
get => _timestamp;
set
{
_timestampRaw = Google.Apis.Util.Utilities.SerializeForGoogleFormat(value);
_timestamp = value;
}
}

/// <summary><seealso cref="System.DateTimeOffset"/> representation of <see cref="TimestampRaw"/>.</summary>
[Newtonsoft.Json.JsonIgnoreAttribute]
public virtual System.DateTimeOffset? TimestampDateTimeOffset
{
get => Google.Apis.Util.Utilities.GetDateTimeOffsetFromString(TimestampRaw);
set => TimestampRaw = Google.Apis.Util.Utilities.GetStringFromDateTimeOffset(value);
}

/// <summary>Short title describing the nature of the issue.</summary>
[Newtonsoft.Json.JsonPropertyAttribute("title")]
Expand Down
2 changes: 1 addition & 1 deletion Google.Api.Generator.Rest/Google.Api.Generator.Rest.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

<ItemGroup>
<PackageReference Include="Google.Apis.Discovery.v1" Version="1.60.0" />
<PackageReference Include="Google.Apis" Version="1.61.0-beta01" />
<PackageReference Include="Google.Apis" Version="1.61.0-beta02" />
<ProjectReference Include="..\Google.Api.Generator.Utils\Google.Api.Generator.Utils.csproj" />
</ItemGroup>

Expand Down
57 changes: 57 additions & 0 deletions Google.Api.Generator.Rest/Models/DataPropertyModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ public IEnumerable<MemberDeclarationSyntax> GeneratePropertyDeclarations(SourceF
_schema.Format switch
{
"date-time" => GenerateDateTimeProperties(ctx),
"google-datetime" => GenerateGoogleDateTimeProperties(ctx),
_ => GenerateRegularProperty(ctx)
};

Expand Down Expand Up @@ -114,6 +115,62 @@ private IEnumerable<MemberDeclarationSyntax> GenerateDateTimeProperties(SourceFi
.WithSetBody(rawProperty.Assign(ctx.Type(typeof(Utilities)).Call(nameof(Utilities.GetStringFromDateTime))(valueParameter)));
}

private IEnumerable<MemberDeclarationSyntax> GenerateGoogleDateTimeProperties(SourceFileContext ctx)
{
if (_schema.Type != "string" || _schema.Required == true || _schema.Properties is object ||
_schema.AdditionalProperties is object || _schema.Repeated == true ||
_schema.Enum__ is object || _schema.Ref__ is object)
{
throw new ArgumentException("Unable to handle complex google-datetime properties");
}

// For google-datetime properties, we generate:
// - Three properties: Xyz (object), XyzRaw (string), XyzDateTimeOffset (DateTimeOffset?)
// - Two fields, to back Xyz and XyzRaw
//
// Each property setter will set both fields.

// The type of "value" is irrelevant to our uses of this, so it's okay that in the raw property it should be a string.
var valueParameter = Parameter(ctx.Type<object>(), "value");
var rawField = Field(Modifier.Private, ctx.Type<string>(), $"_{Name}Raw");
var objectField = Field(Modifier.Private, ctx.Type<object>(), $"_{Name}");

var rawProperty = Property(Modifier.Public | Modifier.Virtual, ctx.Type<string>(), PropertyName + "Raw")
.WithAttribute(ctx.Type<JsonPropertyAttribute>())(Name)
.WithGetBody(Return(rawField))
.WithSetBody(
objectField.Assign(ctx.Type(typeof(Utilities)).Call(nameof(Utilities.DeserializeForGoogleFormat))(valueParameter)),
rawField.Assign(valueParameter)
);
if (_schema.Description is object)
{
rawProperty = rawProperty.WithXmlDoc(XmlDoc.Summary(_schema.Description));
}

var dtoProperty = Property(Modifier.Public | Modifier.Virtual, ctx.Type<DateTimeOffset?>(), PropertyName + "DateTimeOffset")
.WithAttribute(ctx.Type<JsonIgnoreAttribute>())()
.WithXmlDoc(XmlDoc.Summary(XmlDoc.SeeAlso(ctx.Type<DateTimeOffset>()), " representation of ", rawProperty, "."))
.WithGetBody(Return(ctx.Type(typeof(Utilities)).Call(nameof(Utilities.GetDateTimeOffsetFromString))(rawProperty)))
.WithSetBody(rawProperty.Assign(ctx.Type(typeof(Utilities)).Call(nameof(Utilities.GetStringFromDateTimeOffset))(valueParameter)));

var objectProperty = Property(Modifier.Public | Modifier.Virtual, ctx.Type<object>(), PropertyName)
.WithAttribute(ctx.Type<JsonIgnoreAttribute>())()
.WithAttribute(ctx.Type<ObsoleteAttribute>())($"This property is obsolete and may behave unexpectedly; please use {PropertyName}DateTimeOffset instead.")
.WithXmlDoc(XmlDoc.Summary(XmlDoc.SeeAlso(ctx.Type<object>()), " representation of ", rawProperty, "."))
.WithGetBody(Return(objectField))
.WithSetBody(
rawField.Assign(ctx.Type(typeof(Utilities)).Call(nameof(Utilities.SerializeForGoogleFormat))(valueParameter)),
objectField.Assign(valueParameter)
);

yield return rawField;
yield return objectField;

yield return rawProperty;
yield return objectProperty;
yield return dtoProperty;
}

public IEnumerable<ClassDeclarationSyntax> GenerateAnonymousModels(SourceFileContext ctx)
{
if (_schema.AdditionalProperties is object && DataModel.GetProperties(_schema.AdditionalProperties) is object)
Expand Down

0 comments on commit c769a2f

Please sign in to comment.