From 49338c0de7174b0bebc3a30c56e9c4d379b00472 Mon Sep 17 00:00:00 2001
From: fern-api <115122769+fern-api[bot]@users.noreply.github.com>
Date: Thu, 18 Jun 2026 00:47:25 +0000
Subject: [PATCH 1/4] [fern-generated] Update SDK
Generated by Fern
CLI Version: unknown
Generators:
- fernapi/fern-csharp-sdk: 2.13.0
---
.fern/metadata.json | 2 +-
reference.md | 94 +++++++++++++++
.../Unit/MockServer/GetMetadataTest.cs | 96 +++++++++++++++
src/Square.Test/Unit/MockServer/LoadTest.cs | 80 +++++++++++++
src/Square/Core/Public/Version.cs | 2 +-
src/Square/ISquareClient.cs | 1 +
src/Square/Reporting/IReportingClient.cs | 21 ++++
src/Square/Reporting/ReportingClient.cs | 109 ++++++++++++++++++
src/Square/Reporting/Requests/LoadRequest.cs | 23 ++++
src/Square/Square.csproj | 2 +-
src/Square/SquareClient.cs | 5 +-
src/Square/Types/CacheMode.cs | 71 ++++++++++++
src/Square/Types/Cube.cs | 61 ++++++++++
src/Square/Types/CubeJoin.cs | 31 +++++
src/Square/Types/CubeType.cs | 63 ++++++++++
src/Square/Types/CustomNumericFormat.cs | 46 ++++++++
src/Square/Types/CustomTimeFormat.cs | 40 +++++++
src/Square/Types/Dimension.cs | 79 +++++++++++++
src/Square/Types/DimensionGranularity.cs | 43 +++++++
src/Square/Types/DimensionOrder.cs | 65 +++++++++++
src/Square/Types/Folder.cs | 31 +++++
src/Square/Types/FormatDescription.cs | 46 ++++++++
src/Square/Types/Hierarchy.cs | 40 +++++++
src/Square/Types/JoinSubquery.cs | 37 ++++++
src/Square/Types/LinkFormat.cs | 40 +++++++
src/Square/Types/LoadResponse.cs | 63 ++++++++++
src/Square/Types/LoadResultAnnotation.cs | 38 ++++++
src/Square/Types/LoadResultDataColumnar.cs | 40 +++++++
src/Square/Types/LoadResultDataCompact.cs | 40 +++++++
src/Square/Types/Measure.cs | 70 +++++++++++
src/Square/Types/MetadataResponse.cs | 31 +++++
src/Square/Types/NestedFolder.cs | 31 +++++
src/Square/Types/Query.cs | 64 ++++++++++
src/Square/Types/QueryFilterAnd.cs | 29 +++++
src/Square/Types/QueryFilterCondition.cs | 34 ++++++
src/Square/Types/QueryFilterOr.cs | 29 +++++
src/Square/Types/ReportingError.cs | 31 +++++
src/Square/Types/Segment.cs | 40 +++++++
src/Square/Types/SimpleFormat.cs | 81 +++++++++++++
src/Square/Types/TimeDimension.cs | 35 ++++++
40 files changed, 1780 insertions(+), 4 deletions(-)
create mode 100644 src/Square.Test/Unit/MockServer/GetMetadataTest.cs
create mode 100644 src/Square.Test/Unit/MockServer/LoadTest.cs
create mode 100644 src/Square/Reporting/IReportingClient.cs
create mode 100644 src/Square/Reporting/ReportingClient.cs
create mode 100644 src/Square/Reporting/Requests/LoadRequest.cs
create mode 100644 src/Square/Types/CacheMode.cs
create mode 100644 src/Square/Types/Cube.cs
create mode 100644 src/Square/Types/CubeJoin.cs
create mode 100644 src/Square/Types/CubeType.cs
create mode 100644 src/Square/Types/CustomNumericFormat.cs
create mode 100644 src/Square/Types/CustomTimeFormat.cs
create mode 100644 src/Square/Types/Dimension.cs
create mode 100644 src/Square/Types/DimensionGranularity.cs
create mode 100644 src/Square/Types/DimensionOrder.cs
create mode 100644 src/Square/Types/Folder.cs
create mode 100644 src/Square/Types/FormatDescription.cs
create mode 100644 src/Square/Types/Hierarchy.cs
create mode 100644 src/Square/Types/JoinSubquery.cs
create mode 100644 src/Square/Types/LinkFormat.cs
create mode 100644 src/Square/Types/LoadResponse.cs
create mode 100644 src/Square/Types/LoadResultAnnotation.cs
create mode 100644 src/Square/Types/LoadResultDataColumnar.cs
create mode 100644 src/Square/Types/LoadResultDataCompact.cs
create mode 100644 src/Square/Types/Measure.cs
create mode 100644 src/Square/Types/MetadataResponse.cs
create mode 100644 src/Square/Types/NestedFolder.cs
create mode 100644 src/Square/Types/Query.cs
create mode 100644 src/Square/Types/QueryFilterAnd.cs
create mode 100644 src/Square/Types/QueryFilterCondition.cs
create mode 100644 src/Square/Types/QueryFilterOr.cs
create mode 100644 src/Square/Types/ReportingError.cs
create mode 100644 src/Square/Types/Segment.cs
create mode 100644 src/Square/Types/SimpleFormat.cs
create mode 100644 src/Square/Types/TimeDimension.cs
diff --git a/.fern/metadata.json b/.fern/metadata.json
index a31e8da1..92156190 100644
--- a/.fern/metadata.json
+++ b/.fern/metadata.json
@@ -15,5 +15,5 @@
"inline-path-parameters": true,
"use-discriminated-unions": true
},
- "sdkVersion": "44.0.0"
+ "sdkVersion": "44.1.0-rc.0"
}
\ No newline at end of file
diff --git a/reference.md b/reference.md
index fc43fb96..2f57cbe2 100644
--- a/reference.md
+++ b/reference.md
@@ -12050,6 +12050,100 @@ await client.Vendors.UpdateAsync(
+
+
+
+
+## Reporting
+client.Reporting.GetMetadataAsync() -> MetadataResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Describes the data available to query: the cubes, views, measures, dimensions, and segments you can reference in a reporting query. Call this first to discover the schema, then pass the members you need to `load`.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```csharp
+await client.Reporting.GetMetadataAsync();
+```
+
+
+
+
+
+
+
+
+
+
+client.Reporting.LoadAsync(LoadRequest { ... }) -> LoadResponse
+
+-
+
+#### 📝 Description
+
+
+-
+
+
+-
+
+Runs a reporting query against the discovered schema and returns the aggregated results. Long-running queries may return a "Continue wait" response while processing — retry the same request until results are ready.
+
+
+
+
+
+#### 🔌 Usage
+
+
+-
+
+
+-
+
+```csharp
+await client.Reporting.LoadAsync(new LoadRequest());
+```
+
+
+
+
+
+#### ⚙️ Parameters
+
+
+-
+
+
+-
+
+**request:** `LoadRequest`
+
+
+
+
+
+
+
diff --git a/src/Square.Test/Unit/MockServer/GetMetadataTest.cs b/src/Square.Test/Unit/MockServer/GetMetadataTest.cs
new file mode 100644
index 00000000..5327c86f
--- /dev/null
+++ b/src/Square.Test/Unit/MockServer/GetMetadataTest.cs
@@ -0,0 +1,96 @@
+using NUnit.Framework;
+using Square;
+using Square.Core;
+
+namespace Square.Test.Unit.MockServer;
+
+[TestFixture]
+public class GetMetadataTest : BaseMockServerTest
+{
+ [NUnit.Framework.Test]
+ public async Task MockServerTest()
+ {
+ const string mockResponse = """
+ {
+ "cubes": [
+ {
+ "name": "name",
+ "title": "title",
+ "type": "cube",
+ "meta": {
+ "key": "value"
+ },
+ "description": "description",
+ "measures": [
+ {
+ "name": "name",
+ "type": "type"
+ }
+ ],
+ "dimensions": [
+ {
+ "name": "name",
+ "type": "type"
+ }
+ ],
+ "segments": [
+ {
+ "name": "name",
+ "title": "title",
+ "shortTitle": "shortTitle"
+ }
+ ],
+ "joins": [
+ {
+ "name": "name",
+ "relationship": "relationship"
+ }
+ ],
+ "folders": [
+ {
+ "name": "name",
+ "members": [
+ "members"
+ ]
+ }
+ ],
+ "nestedFolders": [
+ {
+ "name": "name",
+ "members": [
+ "members"
+ ]
+ }
+ ],
+ "hierarchies": [
+ {
+ "name": "name",
+ "levels": [
+ "levels"
+ ]
+ }
+ ]
+ }
+ ],
+ "compilerId": "compilerId"
+ }
+ """;
+
+ Server
+ .Given(
+ WireMock.RequestBuilders.Request.Create().WithPath("/reporting/v1/meta").UsingGet()
+ )
+ .RespondWith(
+ WireMock
+ .ResponseBuilders.Response.Create()
+ .WithStatusCode(200)
+ .WithBody(mockResponse)
+ );
+
+ var response = await Client.Reporting.GetMetadataAsync();
+ Assert.That(
+ response,
+ Is.EqualTo(JsonUtils.Deserialize(mockResponse)).UsingDefaults()
+ );
+ }
+}
diff --git a/src/Square.Test/Unit/MockServer/LoadTest.cs b/src/Square.Test/Unit/MockServer/LoadTest.cs
new file mode 100644
index 00000000..747b576c
--- /dev/null
+++ b/src/Square.Test/Unit/MockServer/LoadTest.cs
@@ -0,0 +1,80 @@
+using NUnit.Framework;
+using Square;
+using Square.Core;
+
+namespace Square.Test.Unit.MockServer;
+
+[TestFixture]
+public class LoadTest : BaseMockServerTest
+{
+ [NUnit.Framework.Test]
+ public async Task MockServerTest()
+ {
+ const string requestJson = """
+ {}
+ """;
+
+ const string mockResponse = """
+ {
+ "dataSource": "dataSource",
+ "annotation": {
+ "measures": {
+ "key": "value"
+ },
+ "dimensions": {
+ "key": "value"
+ },
+ "segments": {
+ "key": "value"
+ },
+ "timeDimensions": {
+ "key": "value"
+ }
+ },
+ "data": [
+ {
+ "key": "value"
+ }
+ ],
+ "lastRefreshTime": "lastRefreshTime",
+ "query": {
+ "key": "value"
+ },
+ "slowQuery": true,
+ "external": true,
+ "dbType": "dbType",
+ "refreshKeyValues": [
+ {
+ "key": "value"
+ }
+ ],
+ "pivotQuery": {
+ "key": "value"
+ },
+ "queryType": "queryType"
+ }
+ """;
+
+ Server
+ .Given(
+ WireMock
+ .RequestBuilders.Request.Create()
+ .WithPath("/reporting/v1/load")
+ .WithHeader("Content-Type", "application/json")
+ .UsingPost()
+ .WithBodyAsJson(requestJson)
+ )
+ .RespondWith(
+ WireMock
+ .ResponseBuilders.Response.Create()
+ .WithStatusCode(200)
+ .WithBody(mockResponse)
+ );
+
+ var response = await Client.Reporting.LoadAsync(new LoadRequest());
+ Assert.That(
+ response,
+ Is.EqualTo(JsonUtils.Deserialize(mockResponse)).UsingDefaults()
+ );
+ }
+}
diff --git a/src/Square/Core/Public/Version.cs b/src/Square/Core/Public/Version.cs
index 9fe80426..80664b5b 100644
--- a/src/Square/Core/Public/Version.cs
+++ b/src/Square/Core/Public/Version.cs
@@ -3,5 +3,5 @@ namespace Square;
[Serializable]
internal class Version
{
- public const string Current = "44.0.0";
+ public const string Current = "44.1.0-rc.0";
}
diff --git a/src/Square/ISquareClient.cs b/src/Square/ISquareClient.cs
index b165c01c..1f865272 100644
--- a/src/Square/ISquareClient.cs
+++ b/src/Square/ISquareClient.cs
@@ -38,6 +38,7 @@ public partial interface ISquareClient
public TerminalClient Terminal { get; }
public TransferOrdersClient TransferOrders { get; }
public VendorsClient Vendors { get; }
+ public ReportingClient Reporting { get; }
public CashDrawersClient CashDrawers { get; }
public WebhooksClient Webhooks { get; }
}
diff --git a/src/Square/Reporting/IReportingClient.cs b/src/Square/Reporting/IReportingClient.cs
new file mode 100644
index 00000000..8410e208
--- /dev/null
+++ b/src/Square/Reporting/IReportingClient.cs
@@ -0,0 +1,21 @@
+namespace Square;
+
+public partial interface IReportingClient
+{
+ ///
+ /// Describes the data available to query: the cubes, views, measures, dimensions, and segments you can reference in a reporting query. Call this first to discover the schema, then pass the members you need to `load`.
+ ///
+ Task GetMetadataAsync(
+ RequestOptions? options = null,
+ CancellationToken cancellationToken = default
+ );
+
+ ///
+ /// Runs a reporting query against the discovered schema and returns the aggregated results. Long-running queries may return a "Continue wait" response while processing — retry the same request until results are ready.
+ ///
+ Task LoadAsync(
+ LoadRequest request,
+ RequestOptions? options = null,
+ CancellationToken cancellationToken = default
+ );
+}
diff --git a/src/Square/Reporting/ReportingClient.cs b/src/Square/Reporting/ReportingClient.cs
new file mode 100644
index 00000000..35df698f
--- /dev/null
+++ b/src/Square/Reporting/ReportingClient.cs
@@ -0,0 +1,109 @@
+using System.Text.Json;
+using Square.Core;
+
+namespace Square;
+
+public partial class ReportingClient : IReportingClient
+{
+ private RawClient _client;
+
+ internal ReportingClient(RawClient client)
+ {
+ _client = client;
+ }
+
+ ///
+ /// Describes the data available to query: the cubes, views, measures, dimensions, and segments you can reference in a reporting query. Call this first to discover the schema, then pass the members you need to `load`.
+ ///
+ ///
+ /// await client.Reporting.GetMetadataAsync();
+ ///
+ public async Task GetMetadataAsync(
+ RequestOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var response = await _client
+ .SendRequestAsync(
+ new JsonRequest
+ {
+ BaseUrl = _client.Options.BaseUrl,
+ Method = HttpMethod.Get,
+ Path = "reporting/v1/meta",
+ Options = options,
+ },
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+ if (response.StatusCode is >= 200 and < 400)
+ {
+ var responseBody = await response.Raw.Content.ReadAsStringAsync();
+ try
+ {
+ return JsonUtils.Deserialize(responseBody)!;
+ }
+ catch (JsonException e)
+ {
+ throw new SquareException("Failed to deserialize response", e);
+ }
+ }
+
+ {
+ var responseBody = await response.Raw.Content.ReadAsStringAsync();
+ throw new SquareApiException(
+ $"Error with status code {response.StatusCode}",
+ response.StatusCode,
+ responseBody
+ );
+ }
+ }
+
+ ///
+ /// Runs a reporting query against the discovered schema and returns the aggregated results. Long-running queries may return a "Continue wait" response while processing — retry the same request until results are ready.
+ ///
+ ///
+ /// await client.Reporting.LoadAsync(new LoadRequest());
+ ///
+ public async Task LoadAsync(
+ LoadRequest request,
+ RequestOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ var response = await _client
+ .SendRequestAsync(
+ new JsonRequest
+ {
+ BaseUrl = _client.Options.BaseUrl,
+ Method = HttpMethod.Post,
+ Path = "reporting/v1/load",
+ Body = request,
+ ContentType = "application/json",
+ Options = options,
+ },
+ cancellationToken
+ )
+ .ConfigureAwait(false);
+ if (response.StatusCode is >= 200 and < 400)
+ {
+ var responseBody = await response.Raw.Content.ReadAsStringAsync();
+ try
+ {
+ return JsonUtils.Deserialize(responseBody)!;
+ }
+ catch (JsonException e)
+ {
+ throw new SquareException("Failed to deserialize response", e);
+ }
+ }
+
+ {
+ var responseBody = await response.Raw.Content.ReadAsStringAsync();
+ throw new SquareApiException(
+ $"Error with status code {response.StatusCode}",
+ response.StatusCode,
+ responseBody
+ );
+ }
+ }
+}
diff --git a/src/Square/Reporting/Requests/LoadRequest.cs b/src/Square/Reporting/Requests/LoadRequest.cs
new file mode 100644
index 00000000..811a2022
--- /dev/null
+++ b/src/Square/Reporting/Requests/LoadRequest.cs
@@ -0,0 +1,23 @@
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record LoadRequest
+{
+ [JsonPropertyName("queryType")]
+ public string? QueryType { get; set; }
+
+ [JsonPropertyName("cache")]
+ public CacheMode? Cache { get; set; }
+
+ [JsonPropertyName("query")]
+ public Query? Query { get; set; }
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Square.csproj b/src/Square/Square.csproj
index 7a215fd0..73def928 100644
--- a/src/Square/Square.csproj
+++ b/src/Square/Square.csproj
@@ -5,7 +5,7 @@
enable
12
enable
- 44.0.0
+ 44.1.0-rc.0
$(Version)
$(Version)
README.md
diff --git a/src/Square/SquareClient.cs b/src/Square/SquareClient.cs
index ce190f7e..5e3d40f6 100644
--- a/src/Square/SquareClient.cs
+++ b/src/Square/SquareClient.cs
@@ -22,7 +22,7 @@ public SquareClient(string? token = null, ClientOptions? clientOptions = null)
{ "X-Fern-Language", "C#" },
{ "X-Fern-SDK-Name", "Square" },
{ "X-Fern-SDK-Version", Version.Current },
- { "User-Agent", "Square/44.0.0" },
+ { "User-Agent", "Square/44.1.0-rc.0" },
}
);
clientOptions ??= new ClientOptions();
@@ -71,6 +71,7 @@ public SquareClient(string? token = null, ClientOptions? clientOptions = null)
Terminal = new TerminalClient(_client);
TransferOrders = new TransferOrdersClient(_client);
Vendors = new VendorsClient(_client);
+ Reporting = new ReportingClient(_client);
CashDrawers = new CashDrawersClient(_client);
Webhooks = new WebhooksClient(_client);
}
@@ -141,6 +142,8 @@ public SquareClient(string? token = null, ClientOptions? clientOptions = null)
public VendorsClient Vendors { get; }
+ public ReportingClient Reporting { get; }
+
public CashDrawersClient CashDrawers { get; }
public WebhooksClient Webhooks { get; }
diff --git a/src/Square/Types/CacheMode.cs b/src/Square/Types/CacheMode.cs
new file mode 100644
index 00000000..8a681cd4
--- /dev/null
+++ b/src/Square/Types/CacheMode.cs
@@ -0,0 +1,71 @@
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[JsonConverter(typeof(StringEnumSerializer))]
+[Serializable]
+public readonly record struct CacheMode : IStringEnum
+{
+ public static readonly CacheMode StaleIfSlow = new(Values.StaleIfSlow);
+
+ public static readonly CacheMode StaleWhileRevalidate = new(Values.StaleWhileRevalidate);
+
+ public static readonly CacheMode MustRevalidate = new(Values.MustRevalidate);
+
+ public static readonly CacheMode NoCache = new(Values.NoCache);
+
+ public CacheMode(string value)
+ {
+ Value = value;
+ }
+
+ ///
+ /// The string value of the enum.
+ ///
+ public string Value { get; }
+
+ ///
+ /// Create a string enum with the given value.
+ ///
+ public static CacheMode FromCustom(string value)
+ {
+ return new CacheMode(value);
+ }
+
+ public bool Equals(string? other)
+ {
+ return Value.Equals(other);
+ }
+
+ ///
+ /// Returns the string value of the enum.
+ ///
+ public override string ToString()
+ {
+ return Value;
+ }
+
+ public static bool operator ==(CacheMode value1, string value2) => value1.Value.Equals(value2);
+
+ public static bool operator !=(CacheMode value1, string value2) => !value1.Value.Equals(value2);
+
+ public static explicit operator string(CacheMode value) => value.Value;
+
+ public static explicit operator CacheMode(string value) => new(value);
+
+ ///
+ /// Constant strings for enum values
+ ///
+ [Serializable]
+ public static class Values
+ {
+ public const string StaleIfSlow = "stale-if-slow";
+
+ public const string StaleWhileRevalidate = "stale-while-revalidate";
+
+ public const string MustRevalidate = "must-revalidate";
+
+ public const string NoCache = "no-cache";
+ }
+}
diff --git a/src/Square/Types/Cube.cs b/src/Square/Types/Cube.cs
new file mode 100644
index 00000000..ad54366c
--- /dev/null
+++ b/src/Square/Types/Cube.cs
@@ -0,0 +1,61 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record Cube : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ [JsonPropertyName("title")]
+ public string? Title { get; set; }
+
+ [JsonPropertyName("type")]
+ public required CubeType Type { get; set; }
+
+ [JsonPropertyName("meta")]
+ public Dictionary? Meta { get; set; }
+
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+
+ [JsonPropertyName("measures")]
+ public IEnumerable Measures { get; set; } = new List();
+
+ [JsonPropertyName("dimensions")]
+ public IEnumerable Dimensions { get; set; } = new List();
+
+ [JsonPropertyName("segments")]
+ public IEnumerable Segments { get; set; } = new List();
+
+ [JsonPropertyName("joins")]
+ public IEnumerable? Joins { get; set; }
+
+ [JsonPropertyName("folders")]
+ public IEnumerable? Folders { get; set; }
+
+ [JsonPropertyName("nestedFolders")]
+ public IEnumerable? NestedFolders { get; set; }
+
+ [JsonPropertyName("hierarchies")]
+ public IEnumerable? Hierarchies { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/CubeJoin.cs b/src/Square/Types/CubeJoin.cs
new file mode 100644
index 00000000..af5eb705
--- /dev/null
+++ b/src/Square/Types/CubeJoin.cs
@@ -0,0 +1,31 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record CubeJoin : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ [JsonPropertyName("relationship")]
+ public required string Relationship { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/CubeType.cs b/src/Square/Types/CubeType.cs
new file mode 100644
index 00000000..3fdc0b6c
--- /dev/null
+++ b/src/Square/Types/CubeType.cs
@@ -0,0 +1,63 @@
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[JsonConverter(typeof(StringEnumSerializer))]
+[Serializable]
+public readonly record struct CubeType : IStringEnum
+{
+ public static readonly CubeType Cube = new(Values.Cube);
+
+ public static readonly CubeType View = new(Values.View);
+
+ public CubeType(string value)
+ {
+ Value = value;
+ }
+
+ ///
+ /// The string value of the enum.
+ ///
+ public string Value { get; }
+
+ ///
+ /// Create a string enum with the given value.
+ ///
+ public static CubeType FromCustom(string value)
+ {
+ return new CubeType(value);
+ }
+
+ public bool Equals(string? other)
+ {
+ return Value.Equals(other);
+ }
+
+ ///
+ /// Returns the string value of the enum.
+ ///
+ public override string ToString()
+ {
+ return Value;
+ }
+
+ public static bool operator ==(CubeType value1, string value2) => value1.Value.Equals(value2);
+
+ public static bool operator !=(CubeType value1, string value2) => !value1.Value.Equals(value2);
+
+ public static explicit operator string(CubeType value) => value.Value;
+
+ public static explicit operator CubeType(string value) => new(value);
+
+ ///
+ /// Constant strings for enum values
+ ///
+ [Serializable]
+ public static class Values
+ {
+ public const string Cube = "cube";
+
+ public const string View = "view";
+ }
+}
diff --git a/src/Square/Types/CustomNumericFormat.cs b/src/Square/Types/CustomNumericFormat.cs
new file mode 100644
index 00000000..d24fe861
--- /dev/null
+++ b/src/Square/Types/CustomNumericFormat.cs
@@ -0,0 +1,46 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+///
+/// Custom numeric format for numeric measures and dimensions
+///
+[Serializable]
+public record CustomNumericFormat : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ ///
+ /// Type of the format (must be 'custom-numeric')
+ ///
+ [JsonPropertyName("type")]
+ public string Type { get; set; } = "custom-numeric";
+
+ ///
+ /// d3-format specifier string (e.g., '.2f', ',.0f', '$,.2f', '.0%', '.2s'). See https://d3js.org/d3-format
+ ///
+ [JsonPropertyName("value")]
+ public required string Value { get; set; }
+
+ ///
+ /// Name of the predefined format (e.g., 'percent_2', 'currency_1'). Present only when a named format was used.
+ ///
+ [JsonPropertyName("alias")]
+ public string? Alias { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/CustomTimeFormat.cs b/src/Square/Types/CustomTimeFormat.cs
new file mode 100644
index 00000000..3def42b1
--- /dev/null
+++ b/src/Square/Types/CustomTimeFormat.cs
@@ -0,0 +1,40 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+///
+/// Custom time format for time dimensions
+///
+[Serializable]
+public record CustomTimeFormat : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ ///
+ /// Type of the format (must be 'custom-time')
+ ///
+ [JsonPropertyName("type")]
+ public string Type { get; set; } = "custom-time";
+
+ ///
+ /// POSIX strftime format string (IEEE Std 1003.1 / POSIX.1) with d3-time-format extensions (e.g., '%Y-%m-%d', '%d/%m/%Y %H:%M:%S'). See https://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html and https://d3js.org/d3-time-format
+ ///
+ [JsonPropertyName("value")]
+ public required string Value { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/Dimension.cs b/src/Square/Types/Dimension.cs
new file mode 100644
index 00000000..abe4c3d3
--- /dev/null
+++ b/src/Square/Types/Dimension.cs
@@ -0,0 +1,79 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using OneOf;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record Dimension : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ [JsonPropertyName("title")]
+ public string? Title { get; set; }
+
+ [JsonPropertyName("shortTitle")]
+ public string? ShortTitle { get; set; }
+
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+
+ [JsonPropertyName("type")]
+ public required string Type { get; set; }
+
+ ///
+ /// When dimension is defined in View, it keeps the original path: Cube.dimension
+ ///
+ [JsonPropertyName("aliasMember")]
+ public string? AliasMember { get; set; }
+
+ [JsonPropertyName("granularities")]
+ public IEnumerable? Granularities { get; set; }
+
+ [JsonPropertyName("meta")]
+ public Dictionary? Meta { get; set; }
+
+ [JsonPropertyName("format")]
+ public OneOf<
+ SimpleFormat,
+ LinkFormat,
+ CustomTimeFormat,
+ CustomNumericFormat
+ >? Format { get; set; }
+
+ [JsonPropertyName("formatDescription")]
+ public FormatDescription? FormatDescription { get; set; }
+
+ ///
+ /// ISO 4217 currency code in uppercase (3 characters, e.g. USD, EUR)
+ ///
+ [JsonPropertyName("currency")]
+ public string? Currency { get; set; }
+
+ [JsonPropertyName("order")]
+ public DimensionOrder? Order { get; set; }
+
+ ///
+ /// Key reference for the dimension
+ ///
+ [JsonPropertyName("key")]
+ public string? Key { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/DimensionGranularity.cs b/src/Square/Types/DimensionGranularity.cs
new file mode 100644
index 00000000..81b77af9
--- /dev/null
+++ b/src/Square/Types/DimensionGranularity.cs
@@ -0,0 +1,43 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record DimensionGranularity : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ [JsonPropertyName("title")]
+ public required string Title { get; set; }
+
+ [JsonPropertyName("interval")]
+ public string? Interval { get; set; }
+
+ [JsonPropertyName("sql")]
+ public string? Sql { get; set; }
+
+ [JsonPropertyName("offset")]
+ public string? Offset { get; set; }
+
+ [JsonPropertyName("origin")]
+ public string? Origin { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/DimensionOrder.cs b/src/Square/Types/DimensionOrder.cs
new file mode 100644
index 00000000..7bc0731d
--- /dev/null
+++ b/src/Square/Types/DimensionOrder.cs
@@ -0,0 +1,65 @@
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[JsonConverter(typeof(StringEnumSerializer))]
+[Serializable]
+public readonly record struct DimensionOrder : IStringEnum
+{
+ public static readonly DimensionOrder Asc = new(Values.Asc);
+
+ public static readonly DimensionOrder Desc = new(Values.Desc);
+
+ public DimensionOrder(string value)
+ {
+ Value = value;
+ }
+
+ ///
+ /// The string value of the enum.
+ ///
+ public string Value { get; }
+
+ ///
+ /// Create a string enum with the given value.
+ ///
+ public static DimensionOrder FromCustom(string value)
+ {
+ return new DimensionOrder(value);
+ }
+
+ public bool Equals(string? other)
+ {
+ return Value.Equals(other);
+ }
+
+ ///
+ /// Returns the string value of the enum.
+ ///
+ public override string ToString()
+ {
+ return Value;
+ }
+
+ public static bool operator ==(DimensionOrder value1, string value2) =>
+ value1.Value.Equals(value2);
+
+ public static bool operator !=(DimensionOrder value1, string value2) =>
+ !value1.Value.Equals(value2);
+
+ public static explicit operator string(DimensionOrder value) => value.Value;
+
+ public static explicit operator DimensionOrder(string value) => new(value);
+
+ ///
+ /// Constant strings for enum values
+ ///
+ [Serializable]
+ public static class Values
+ {
+ public const string Asc = "asc";
+
+ public const string Desc = "desc";
+ }
+}
diff --git a/src/Square/Types/Folder.cs b/src/Square/Types/Folder.cs
new file mode 100644
index 00000000..4558def0
--- /dev/null
+++ b/src/Square/Types/Folder.cs
@@ -0,0 +1,31 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record Folder : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ [JsonPropertyName("members")]
+ public IEnumerable Members { get; set; } = new List();
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/FormatDescription.cs b/src/Square/Types/FormatDescription.cs
new file mode 100644
index 00000000..07f92e00
--- /dev/null
+++ b/src/Square/Types/FormatDescription.cs
@@ -0,0 +1,46 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+///
+/// Resolved format description with the predefined name and d3-format specifier
+///
+[Serializable]
+public record FormatDescription : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ ///
+ /// Predefined format name (e.g., 'percent_2', 'currency_1') or a base name like 'number', or 'custom' for ad-hoc specifiers
+ ///
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ ///
+ /// d3-format specifier string (e.g., '.2f', ',.0f', '$,.2f'). See https://d3js.org/d3-format
+ ///
+ [JsonPropertyName("specifier")]
+ public required string Specifier { get; set; }
+
+ ///
+ /// ISO 4217 currency code in uppercase (e.g. USD, EUR). Present when a currency format is used.
+ ///
+ [JsonPropertyName("currency")]
+ public string? Currency { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/Hierarchy.cs b/src/Square/Types/Hierarchy.cs
new file mode 100644
index 00000000..a2d41385
--- /dev/null
+++ b/src/Square/Types/Hierarchy.cs
@@ -0,0 +1,40 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record Hierarchy : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ ///
+ /// When hierarchy is defined in Cube, it keeps the original path: Cube.hierarchy
+ ///
+ [JsonPropertyName("aliasMember")]
+ public string? AliasMember { get; set; }
+
+ [JsonPropertyName("title")]
+ public string? Title { get; set; }
+
+ [JsonPropertyName("levels")]
+ public IEnumerable Levels { get; set; } = new List();
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/JoinSubquery.cs b/src/Square/Types/JoinSubquery.cs
new file mode 100644
index 00000000..2c28d5f0
--- /dev/null
+++ b/src/Square/Types/JoinSubquery.cs
@@ -0,0 +1,37 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record JoinSubquery : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("sql")]
+ public required string Sql { get; set; }
+
+ [JsonPropertyName("on")]
+ public required string On { get; set; }
+
+ [JsonPropertyName("joinType")]
+ public required string JoinType { get; set; }
+
+ [JsonPropertyName("alias")]
+ public required string Alias { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/LinkFormat.cs b/src/Square/Types/LinkFormat.cs
new file mode 100644
index 00000000..6511254b
--- /dev/null
+++ b/src/Square/Types/LinkFormat.cs
@@ -0,0 +1,40 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+///
+/// Link format with label and type
+///
+[Serializable]
+public record LinkFormat : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ ///
+ /// Label for the link
+ ///
+ [JsonPropertyName("label")]
+ public required string Label { get; set; }
+
+ ///
+ /// Type of the format (must be 'link')
+ ///
+ [JsonPropertyName("type")]
+ public string Type { get; set; } = "link";
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/LoadResponse.cs b/src/Square/Types/LoadResponse.cs
new file mode 100644
index 00000000..6929afa8
--- /dev/null
+++ b/src/Square/Types/LoadResponse.cs
@@ -0,0 +1,63 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using OneOf;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record LoadResponse : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("dataSource")]
+ public string? DataSource { get; set; }
+
+ [JsonPropertyName("annotation")]
+ public LoadResultAnnotation? Annotation { get; set; }
+
+ [JsonPropertyName("data")]
+ public OneOf<
+ IEnumerable>,
+ LoadResultDataCompact,
+ LoadResultDataColumnar
+ >? Data { get; set; }
+
+ [JsonPropertyName("lastRefreshTime")]
+ public string? LastRefreshTime { get; set; }
+
+ [JsonPropertyName("query")]
+ public Dictionary? Query { get; set; }
+
+ [JsonPropertyName("slowQuery")]
+ public bool? SlowQuery { get; set; }
+
+ [JsonPropertyName("external")]
+ public bool? External { get; set; }
+
+ [JsonPropertyName("dbType")]
+ public string? DbType { get; set; }
+
+ [JsonPropertyName("refreshKeyValues")]
+ public IEnumerable>? RefreshKeyValues { get; set; }
+
+ [JsonPropertyName("pivotQuery")]
+ public Dictionary? PivotQuery { get; set; }
+
+ [JsonPropertyName("queryType")]
+ public string? QueryType { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/LoadResultAnnotation.cs b/src/Square/Types/LoadResultAnnotation.cs
new file mode 100644
index 00000000..5f8f9535
--- /dev/null
+++ b/src/Square/Types/LoadResultAnnotation.cs
@@ -0,0 +1,38 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record LoadResultAnnotation : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("measures")]
+ public Dictionary Measures { get; set; } = new Dictionary();
+
+ [JsonPropertyName("dimensions")]
+ public Dictionary Dimensions { get; set; } = new Dictionary();
+
+ [JsonPropertyName("segments")]
+ public Dictionary Segments { get; set; } = new Dictionary();
+
+ [JsonPropertyName("timeDimensions")]
+ public Dictionary TimeDimensions { get; set; } =
+ new Dictionary();
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/LoadResultDataColumnar.cs b/src/Square/Types/LoadResultDataColumnar.cs
new file mode 100644
index 00000000..467ef3b6
--- /dev/null
+++ b/src/Square/Types/LoadResultDataColumnar.cs
@@ -0,0 +1,40 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+///
+/// Columnar data format - members list paired with one primitive array per column. Returned when `responseFormat=columnar` is requested.
+///
+[Serializable]
+public record LoadResultDataColumnar : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ ///
+ /// Ordered list of member names. Element `i` of `columns` holds the values for `members[i]` across all rows.
+ ///
+ [JsonPropertyName("members")]
+ public IEnumerable Members { get; set; } = new List();
+
+ ///
+ /// One array per member, in the same order as `members`. Each inner array contains the primitive value of that member for every row (null, boolean, number, string).
+ ///
+ [JsonPropertyName("columns")]
+ public IEnumerable> Columns { get; set; } = new List>();
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/LoadResultDataCompact.cs b/src/Square/Types/LoadResultDataCompact.cs
new file mode 100644
index 00000000..ffc1b5b9
--- /dev/null
+++ b/src/Square/Types/LoadResultDataCompact.cs
@@ -0,0 +1,40 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+///
+/// Compact data format - a single object with the members list and a dataset of primitive arrays. Returned when `responseFormat=compact` is requested.
+///
+[Serializable]
+public record LoadResultDataCompact : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ ///
+ /// Ordered list of member names that correspond to each cell position in `dataset` rows.
+ ///
+ [JsonPropertyName("members")]
+ public IEnumerable Members { get; set; } = new List();
+
+ ///
+ /// Array of rows, where each row is an array of primitive values (null, boolean, number, string) aligned with `members`.
+ ///
+ [JsonPropertyName("dataset")]
+ public IEnumerable> Dataset { get; set; } = new List>();
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/Measure.cs b/src/Square/Types/Measure.cs
new file mode 100644
index 00000000..d7c7e72f
--- /dev/null
+++ b/src/Square/Types/Measure.cs
@@ -0,0 +1,70 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using OneOf;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record Measure : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ [JsonPropertyName("title")]
+ public string? Title { get; set; }
+
+ [JsonPropertyName("shortTitle")]
+ public string? ShortTitle { get; set; }
+
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+
+ [JsonPropertyName("type")]
+ public required string Type { get; set; }
+
+ [JsonPropertyName("aggType")]
+ public string? AggType { get; set; }
+
+ [JsonPropertyName("meta")]
+ public Dictionary? Meta { get; set; }
+
+ [JsonPropertyName("format")]
+ public OneOf<
+ SimpleFormat,
+ LinkFormat,
+ CustomTimeFormat,
+ CustomNumericFormat
+ >? Format { get; set; }
+
+ [JsonPropertyName("formatDescription")]
+ public FormatDescription? FormatDescription { get; set; }
+
+ ///
+ /// ISO 4217 currency code in uppercase (3 characters, e.g. USD, EUR)
+ ///
+ [JsonPropertyName("currency")]
+ public string? Currency { get; set; }
+
+ ///
+ /// When measure is defined in View, it keeps the original path: Cube.measure
+ ///
+ [JsonPropertyName("aliasMember")]
+ public string? AliasMember { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/MetadataResponse.cs b/src/Square/Types/MetadataResponse.cs
new file mode 100644
index 00000000..947bab36
--- /dev/null
+++ b/src/Square/Types/MetadataResponse.cs
@@ -0,0 +1,31 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record MetadataResponse : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("cubes")]
+ public IEnumerable? Cubes { get; set; }
+
+ [JsonPropertyName("compilerId")]
+ public string? CompilerId { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/NestedFolder.cs b/src/Square/Types/NestedFolder.cs
new file mode 100644
index 00000000..7f98a893
--- /dev/null
+++ b/src/Square/Types/NestedFolder.cs
@@ -0,0 +1,31 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record NestedFolder : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ [JsonPropertyName("members")]
+ public IEnumerable Members { get; set; } = new List();
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/Query.cs b/src/Square/Types/Query.cs
new file mode 100644
index 00000000..1eb76a67
--- /dev/null
+++ b/src/Square/Types/Query.cs
@@ -0,0 +1,64 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using OneOf;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record Query : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("measures")]
+ public IEnumerable? Measures { get; set; }
+
+ [JsonPropertyName("dimensions")]
+ public IEnumerable? Dimensions { get; set; }
+
+ [JsonPropertyName("segments")]
+ public IEnumerable? Segments { get; set; }
+
+ [JsonPropertyName("timeDimensions")]
+ public IEnumerable? TimeDimensions { get; set; }
+
+ [JsonPropertyName("order")]
+ public IEnumerable>? Order { get; set; }
+
+ [JsonPropertyName("limit")]
+ public int? Limit { get; set; }
+
+ [JsonPropertyName("offset")]
+ public int? Offset { get; set; }
+
+ [JsonPropertyName("filters")]
+ public IEnumerable<
+ OneOf
+ >? Filters { get; set; }
+
+ [JsonPropertyName("ungrouped")]
+ public bool? Ungrouped { get; set; }
+
+ [JsonPropertyName("subqueryJoins")]
+ public IEnumerable? SubqueryJoins { get; set; }
+
+ [JsonPropertyName("joinHints")]
+ public IEnumerable>? JoinHints { get; set; }
+
+ [JsonPropertyName("timezone")]
+ public string? Timezone { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/QueryFilterAnd.cs b/src/Square/Types/QueryFilterAnd.cs
new file mode 100644
index 00000000..0c184a43
--- /dev/null
+++ b/src/Square/Types/QueryFilterAnd.cs
@@ -0,0 +1,29 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record QueryFilterAnd : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("and")]
+ public IEnumerable> And { get; set; } =
+ new List>();
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/QueryFilterCondition.cs b/src/Square/Types/QueryFilterCondition.cs
new file mode 100644
index 00000000..8bbf9c66
--- /dev/null
+++ b/src/Square/Types/QueryFilterCondition.cs
@@ -0,0 +1,34 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record QueryFilterCondition : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("member")]
+ public required string Member { get; set; }
+
+ [JsonPropertyName("operator")]
+ public required string Operator { get; set; }
+
+ [JsonPropertyName("values")]
+ public IEnumerable? Values { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/QueryFilterOr.cs b/src/Square/Types/QueryFilterOr.cs
new file mode 100644
index 00000000..5e572be5
--- /dev/null
+++ b/src/Square/Types/QueryFilterOr.cs
@@ -0,0 +1,29 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record QueryFilterOr : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("or")]
+ public IEnumerable> Or { get; set; } =
+ new List>();
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/ReportingError.cs b/src/Square/Types/ReportingError.cs
new file mode 100644
index 00000000..92f97524
--- /dev/null
+++ b/src/Square/Types/ReportingError.cs
@@ -0,0 +1,31 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+///
+/// Error envelope returned by the Reporting API. Note: a 200 response whose body is `{ "error": "Continue wait" }` is not a failure — it signals that a long-running query is still processing and the request should be retried.
+///
+[Serializable]
+public record ReportingError : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("error")]
+ public required string Error { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/Segment.cs b/src/Square/Types/Segment.cs
new file mode 100644
index 00000000..acb8f00d
--- /dev/null
+++ b/src/Square/Types/Segment.cs
@@ -0,0 +1,40 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record Segment : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("name")]
+ public required string Name { get; set; }
+
+ [JsonPropertyName("title")]
+ public required string Title { get; set; }
+
+ [JsonPropertyName("description")]
+ public string? Description { get; set; }
+
+ [JsonPropertyName("shortTitle")]
+ public required string ShortTitle { get; set; }
+
+ [JsonPropertyName("meta")]
+ public Dictionary? Meta { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
diff --git a/src/Square/Types/SimpleFormat.cs b/src/Square/Types/SimpleFormat.cs
new file mode 100644
index 00000000..8c45c283
--- /dev/null
+++ b/src/Square/Types/SimpleFormat.cs
@@ -0,0 +1,81 @@
+using System.Text.Json.Serialization;
+using Square.Core;
+
+namespace Square;
+
+[JsonConverter(typeof(StringEnumSerializer))]
+[Serializable]
+public readonly record struct SimpleFormat : IStringEnum
+{
+ public static readonly SimpleFormat Percent = new(Values.Percent);
+
+ public static readonly SimpleFormat Currency = new(Values.Currency);
+
+ public static readonly SimpleFormat Number = new(Values.Number);
+
+ public static readonly SimpleFormat ImageUrl = new(Values.ImageUrl);
+
+ public static readonly SimpleFormat Id = new(Values.Id);
+
+ public static readonly SimpleFormat Link = new(Values.Link);
+
+ public SimpleFormat(string value)
+ {
+ Value = value;
+ }
+
+ ///
+ /// The string value of the enum.
+ ///
+ public string Value { get; }
+
+ ///
+ /// Create a string enum with the given value.
+ ///
+ public static SimpleFormat FromCustom(string value)
+ {
+ return new SimpleFormat(value);
+ }
+
+ public bool Equals(string? other)
+ {
+ return Value.Equals(other);
+ }
+
+ ///
+ /// Returns the string value of the enum.
+ ///
+ public override string ToString()
+ {
+ return Value;
+ }
+
+ public static bool operator ==(SimpleFormat value1, string value2) =>
+ value1.Value.Equals(value2);
+
+ public static bool operator !=(SimpleFormat value1, string value2) =>
+ !value1.Value.Equals(value2);
+
+ public static explicit operator string(SimpleFormat value) => value.Value;
+
+ public static explicit operator SimpleFormat(string value) => new(value);
+
+ ///
+ /// Constant strings for enum values
+ ///
+ [Serializable]
+ public static class Values
+ {
+ public const string Percent = "percent";
+
+ public const string Currency = "currency";
+
+ public const string Number = "number";
+
+ public const string ImageUrl = "imageUrl";
+
+ public const string Id = "id";
+
+ public const string Link = "link";
+ }
+}
diff --git a/src/Square/Types/TimeDimension.cs b/src/Square/Types/TimeDimension.cs
new file mode 100644
index 00000000..565d1bd9
--- /dev/null
+++ b/src/Square/Types/TimeDimension.cs
@@ -0,0 +1,35 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using OneOf;
+using Square.Core;
+
+namespace Square;
+
+[Serializable]
+public record TimeDimension : IJsonOnDeserialized
+{
+ [JsonExtensionData]
+ private readonly IDictionary _extensionData =
+ new Dictionary();
+
+ [JsonPropertyName("dimension")]
+ public required string Dimension { get; set; }
+
+ [JsonPropertyName("granularity")]
+ public string? Granularity { get; set; }
+
+ [JsonPropertyName("dateRange")]
+ public OneOf, Dictionary>? DateRange { get; set; }
+
+ [JsonIgnore]
+ public ReadOnlyAdditionalProperties AdditionalProperties { get; private set; } = new();
+
+ void IJsonOnDeserialized.OnDeserialized() =>
+ AdditionalProperties.CopyFromExtensionData(_extensionData);
+
+ ///
+ public override string ToString()
+ {
+ return JsonUtils.Serialize(this);
+ }
+}
From 199889f519b6b3541a508395cb98847f50098548 Mon Sep 17 00:00:00 2001
From: fern-support
Date: Wed, 17 Jun 2026 21:14:06 -0400
Subject: [PATCH 2/4] reporting: reapply polling helper + tests on regenerated
SDK; align tests to flat LoadResponse
---
.fernignore | 2 +
.github/workflows/ci.yml | 1 +
README.md | 69 +++++++
src/Square.Test/Integration/ReportingTests.cs | 121 +++++++++++++
src/Square.Test/Unit/ReportingHelperTests.cs | 169 ++++++++++++++++++
src/Square/Reporting/ReportingHelper.cs | 161 +++++++++++++++++
6 files changed, 523 insertions(+)
create mode 100644 src/Square.Test/Integration/ReportingTests.cs
create mode 100644 src/Square.Test/Unit/ReportingHelperTests.cs
create mode 100644 src/Square/Reporting/ReportingHelper.cs
diff --git a/.fernignore b/.fernignore
index 80286048..29aa5de4 100644
--- a/.fernignore
+++ b/.fernignore
@@ -14,10 +14,12 @@ SquareWithLegacy.sln
src/Square.sln
src/Square/Square.Custom.props
src/Square/WebhooksHelper.cs
+src/Square/Reporting/ReportingHelper.cs
src/Square/Core/Public/SquareApiException.cs
src/Square.Test/WebhooksHelperTests.cs
src/Square.Test/Unit/SquareErrorTest.cs
src/Square.Test/Unit/WebhooksHelperTests.cs
+src/Square.Test/Unit/ReportingHelperTests.cs
src/Square.Test/Unit/MockServer/BatchDeleteTest.cs
src/Square.Test/Integration
src/Square.Test/Files
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 4302c105..a10fcafc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -50,6 +50,7 @@ jobs:
continue-on-error: true
env:
TEST_SQUARE_TOKEN: ${{ secrets.TEST_SQUARE_TOKEN }}
+ TEST_SQUARE_REPORTING: ${{ secrets.TEST_SQUARE_REPORTING }}
- name: Publish to nuget.org
if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') && steps.build.outcome == 'success'
diff --git a/README.md b/README.md
index 91efc0dd..66af432f 100644
--- a/README.md
+++ b/README.md
@@ -187,6 +187,75 @@ public static async Task CheckWebhooksEvent(
In .NET 6 and above, there are also overloads using spans for allocation free webhook verification.
+## Reporting API
+
+The [Square Reporting API](https://developer.squareup.com/docs/reporting-api/overview) (beta)
+exposes a Cube-based analytics layer through two endpoints. Call `GetMetadataAsync` to discover
+the queryable schema (cubes, measures, dimensions, and segments), then `LoadAsync` to run a query:
+
+```csharp
+using Square;
+
+var client = new SquareClient();
+
+// Discover what you can query.
+var metadata = await client.Reporting.GetMetadataAsync();
+
+// Run a query.
+var response = await client.Reporting.LoadAsync(
+ new LoadRequest
+ {
+ Query = new Query { Measures = new List { "Orders.count" } },
+ }
+);
+```
+
+### Polling with `LoadAndWaitAsync`
+
+`/reporting/v1/load` is asynchronous: while a query is still being computed it returns an HTTP 200
+whose body is `{ "error": "Continue wait" }` instead of results. The expected behavior is to re-send
+the identical request, with backoff, until results arrive.
+
+`LoadAndWaitAsync` owns that retry loop for you and returns the resolved `LoadResponse` — it never
+hands back the "Continue wait" sentinel:
+
+```csharp
+using Square;
+
+var response = await client.Reporting.LoadAndWaitAsync(
+ new LoadRequest
+ {
+ Query = new Query { Measures = new List { "Orders.count" } },
+ }
+);
+
+// The resolved payload is exposed on the flat `Data` property.
+var data = response.Data;
+// ...
+```
+
+The backoff is configurable, and the loop honors a `CancellationToken`:
+
+```csharp
+var response = await client.Reporting.LoadAndWaitAsync(
+ request,
+ new LoadAndWaitOptions
+ {
+ MaxAttempts = 20, // give up after this many polls
+ InitialDelay = TimeSpan.FromSeconds(2), // delay before the first retry
+ MaxDelay = TimeSpan.FromSeconds(20), // upper bound on the backoff
+ BackoffFactor = 2, // multiplier applied after each attempt
+ },
+ cancellationToken
+);
+```
+
+If the query does not resolve within `MaxAttempts`, a `SquareException` is thrown. The same logic is
+also available as a static helper, `ReportingHelper.LoadAndWaitAsync(client.Reporting, request, ...)`.
+
+> **Note:** The Reporting API is served only from production (`connect.squareup.com/reporting`); it is
+> not available in the sandbox environment.
+
## Legacy SDK
While the new SDK has a lot of improvements, we at Square understand that it takes time to upgrade when there are breaking changes.
diff --git a/src/Square.Test/Integration/ReportingTests.cs b/src/Square.Test/Integration/ReportingTests.cs
new file mode 100644
index 00000000..7af4feee
--- /dev/null
+++ b/src/Square.Test/Integration/ReportingTests.cs
@@ -0,0 +1,121 @@
+using System.Text.Json;
+using NUnit.Framework;
+using Square;
+
+namespace Square.Test.Integration;
+
+// The Reporting API is a beta, bespoke offering served ONLY from production
+// (connect.squareup.com/reporting) — it is not routed on sandbox (returns 404 there).
+// Validating it live therefore needs a production, reporting-provisioned access token,
+// supplied via TEST_SQUARE_REPORTING (distinct from the sandbox TEST_SQUARE_TOKEN used by
+// the rest of the integration suite). This suite is gated behind TEST_SQUARE_REPORTING and
+// skips by default — keeping CI green. The endpoints are read-only (schema discovery +
+// queries). The polling *logic* is covered without a live account in
+// Unit/ReportingHelperTests.cs.
+//
+// Run it against a real prod account:
+// TEST_SQUARE_REPORTING= \
+// dotnet test --filter FullyQualifiedName~Integration.ReportingTests
+// # override the host with TEST_SQUARE_BASE_URL= if reporting moves.
+[TestFixture]
+public class ReportingTests
+{
+ private SquareClient _client = null!;
+
+ [SetUp]
+ public void SetUp()
+ {
+ var token = Environment.GetEnvironmentVariable("TEST_SQUARE_REPORTING");
+ if (string.IsNullOrEmpty(token))
+ {
+ Assert.Ignore(
+ "Set TEST_SQUARE_REPORTING to a production, reporting-provisioned access token to run the reporting integration suite."
+ );
+ }
+ // Reporting only exists on production; allow overriding the host via TEST_SQUARE_BASE_URL.
+ var baseUrl =
+ Environment.GetEnvironmentVariable("TEST_SQUARE_BASE_URL") ?? SquareEnvironment.Production;
+ TestContext.Out.WriteLine(
+ $"[reporting] base URL: {baseUrl} -> {baseUrl}/reporting/v1/{{meta,load}}"
+ );
+ _client = new SquareClient(token!, new ClientOptions { BaseUrl = baseUrl });
+ }
+
+ // Resolves the first queryable measure from the live schema, e.g. "Orders.count".
+ private async Task FirstMeasureNameAsync()
+ {
+ var metadata = await _client.Reporting.GetMetadataAsync();
+ var measure = metadata.Cubes?.FirstOrDefault()?.Measures.FirstOrDefault()?.Name;
+ if (string.IsNullOrEmpty(measure))
+ {
+ throw new Exception(
+ "No cubes/measures are available on the reporting schema for this account."
+ );
+ }
+ return measure!;
+ }
+
+ [Test]
+ public async Task GetMetadataReturnsTheQueryableSchema()
+ {
+ var metadata = await _client.Reporting.GetMetadataAsync();
+
+ Assert.That(metadata.Cubes, Is.Not.Null);
+ Assert.That(metadata.Cubes, Is.Not.Empty);
+
+ // Surface the live schema so a developer can see what is queryable.
+ var summary = (metadata.Cubes ?? new List())
+ .Take(5)
+ .Select(cube => new
+ {
+ cube = cube.Name,
+ measures = cube.Measures.Take(5).Select(measure => measure.Name).ToList(),
+ });
+ TestContext.Out.WriteLine(
+ "Reporting schema (first 5 cubes): "
+ + JsonSerializer.Serialize(summary, new JsonSerializerOptions { WriteIndented = true })
+ );
+ }
+
+ [Test]
+ public async Task LoadReturnsResultsOrTheContinueWaitSentinel()
+ {
+ var measure = await FirstMeasureNameAsync();
+ var response = await _client.Reporting.LoadAsync(
+ new LoadRequest { Query = new Query { Measures = new List { measure } } }
+ );
+
+ if (response.AdditionalProperties.TryGetValue("error", out var sentinel))
+ {
+ // Documented async behavior: a still-processing query comes back as HTTP 200
+ // with { "error": "Continue wait" } instead of results.
+ Assert.That(sentinel.GetString(), Is.EqualTo("Continue wait"));
+ }
+ else
+ {
+ // Flat LoadResponse: a resolved query carries its payload on `Data`.
+ Assert.That(response.Data, Is.Not.Null);
+ }
+ }
+
+ [Test]
+ public async Task LoadAndWaitResolvesAQueryEndToEndWithoutSurfacingContinueWait()
+ {
+ var measure = await FirstMeasureNameAsync();
+
+ var response = await _client.Reporting.LoadAndWaitAsync(
+ new LoadRequest { Query = new Query { Measures = new List { measure } } },
+ new LoadAndWaitOptions
+ {
+ MaxAttempts = 20,
+ InitialDelay = TimeSpan.FromSeconds(2),
+ MaxDelay = TimeSpan.FromSeconds(20),
+ }
+ );
+
+ // The polling helper must never hand back the raw "Continue wait" sentinel.
+ Assert.That(response.AdditionalProperties.ContainsKey("error"), Is.False);
+ // Flat LoadResponse: the resolved query carries its payload on `Data`.
+ Assert.That(response.Data, Is.Not.Null);
+ }
+}
diff --git a/src/Square.Test/Unit/ReportingHelperTests.cs b/src/Square.Test/Unit/ReportingHelperTests.cs
new file mode 100644
index 00000000..31099182
--- /dev/null
+++ b/src/Square.Test/Unit/ReportingHelperTests.cs
@@ -0,0 +1,169 @@
+using NUnit.Framework;
+using Square;
+using Square.Core;
+
+namespace Square.Test.Unit;
+
+///
+/// The Reporting API answers a still-processing /v1/load query with an HTTP 200
+/// whose body is { "error": "Continue wait" }.
+/// owns the retry loop around that sentinel. These tests exercise that loop without a
+/// network by scripting Reporting.LoadAsync, plus one test that proves the sentinel
+/// actually survives the generated client's deserialization.
+///
+[TestFixture]
+public class ReportingHelperTests
+{
+ private const string ContinueWait = "Continue wait";
+
+ /// An stub whose LoadAsync returns a scripted sequence.
+ private sealed class FakeReportingClient : IReportingClient
+ {
+ private readonly IReadOnlyList _sequence;
+ private int _i;
+
+ public FakeReportingClient(params LoadResponse[] sequence) => _sequence = sequence;
+
+ public int CallCount => _i;
+
+ public Task LoadAsync(
+ LoadRequest request,
+ RequestOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var response = _sequence[Math.Min(_i, _sequence.Count - 1)];
+ _i++;
+ return Task.FromResult(response);
+ }
+
+ public Task GetMetadataAsync(
+ RequestOptions? options = null,
+ CancellationToken cancellationToken = default
+ ) => throw new NotSupportedException();
+ }
+
+ private static LoadResponse ContinueWaitResponse() =>
+ JsonUtils.Deserialize($"{{\"error\":\"{ContinueWait}\"}}")!;
+
+ // A resolved response is simply any LoadResponse lacking the "Continue wait" sentinel.
+ private static LoadResponse ResolvedResponse() => new();
+
+ [Test]
+ public async Task PollsPastContinueWaitAndReturnsResolvedResult()
+ {
+ var resolved = ResolvedResponse();
+ var fake = new FakeReportingClient(
+ ContinueWaitResponse(),
+ ContinueWaitResponse(),
+ resolved
+ );
+
+ var response = await ReportingHelper.LoadAndWaitAsync(
+ fake,
+ new LoadRequest(),
+ new LoadAndWaitOptions
+ {
+ InitialDelay = TimeSpan.FromMilliseconds(1),
+ MaxDelay = TimeSpan.FromMilliseconds(1),
+ MaxAttempts = 5,
+ }
+ );
+
+ Assert.Multiple(() =>
+ {
+ // The helper must poll through the sentinels and hand back the resolved response.
+ Assert.That(response, Is.SameAs(resolved));
+ Assert.That(response.AdditionalProperties.ContainsKey("error"), Is.False);
+ Assert.That(fake.CallCount, Is.EqualTo(3));
+ });
+ }
+
+ [Test]
+ public async Task ReturnsImmediatelyWhenFirstResponseHasResults()
+ {
+ var resolved = ResolvedResponse();
+ var fake = new FakeReportingClient(resolved);
+
+ var response = await ReportingHelper.LoadAndWaitAsync(
+ fake,
+ options: new LoadAndWaitOptions { InitialDelay = TimeSpan.FromMilliseconds(1) }
+ );
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(response, Is.SameAs(resolved));
+ Assert.That(fake.CallCount, Is.EqualTo(1));
+ });
+ }
+
+ [Test]
+ public void ThrowsOnceMaxAttemptsIsExhaustedWhileStillContinueWait()
+ {
+ var fake = new FakeReportingClient(ContinueWaitResponse()); // never resolves
+
+ var ex = Assert.ThrowsAsync(
+ async () =>
+ await ReportingHelper.LoadAndWaitAsync(
+ fake,
+ new LoadRequest(),
+ new LoadAndWaitOptions
+ {
+ InitialDelay = TimeSpan.FromMilliseconds(1),
+ MaxDelay = TimeSpan.FromMilliseconds(1),
+ MaxAttempts = 3,
+ }
+ )
+ );
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(ex!.Message, Does.Contain("did not complete after 3 attempts"));
+ Assert.That(fake.CallCount, Is.EqualTo(3));
+ });
+ }
+
+ [Test]
+ public void RejectsPromptlyWhenCancellationTokenFires()
+ {
+ var fake = new FakeReportingClient(ContinueWaitResponse()); // would otherwise poll forever
+ using var cts = new CancellationTokenSource();
+
+ var pending = ReportingHelper.LoadAndWaitAsync(
+ fake,
+ new LoadRequest(),
+ new LoadAndWaitOptions
+ {
+ InitialDelay = TimeSpan.FromSeconds(30),
+ MaxAttempts = 10,
+ },
+ cts.Token
+ );
+ cts.Cancel();
+
+ // Task.Delay surfaces a TaskCanceledException, which derives from
+ // OperationCanceledException — match the base type, not the exact type.
+ Assert.That(
+ async () => await pending,
+ Throws.InstanceOf()
+ );
+ }
+
+ [Test]
+ public void RealDeserializerContinueWaitBodyIsARetrySignalNotAResult()
+ {
+ // The crux of the design: the generated LoadResponse deserializes with unknown-key
+ // passthrough (via [JsonExtensionData]), so the `error` sentinel survives onto
+ // AdditionalProperties while the flat `data` payload stays absent. If this ever stops
+ // being true, LoadAndWaitAsync would mistake "Continue wait" for a real result.
+ var parsed = JsonUtils.Deserialize($"{{\"error\":\"{ContinueWait}\"}}")!;
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(parsed.AdditionalProperties.TryGetValue("error", out var error), Is.True);
+ Assert.That(error.GetString(), Is.EqualTo(ContinueWait));
+ Assert.That(parsed.Data, Is.Null);
+ });
+ }
+}
diff --git a/src/Square/Reporting/ReportingHelper.cs b/src/Square/Reporting/ReportingHelper.cs
new file mode 100644
index 00000000..62298e0a
--- /dev/null
+++ b/src/Square/Reporting/ReportingHelper.cs
@@ -0,0 +1,161 @@
+using System.Text.Json;
+
+namespace Square;
+
+///
+/// Polling configuration for .
+///
+public class LoadAndWaitOptions
+{
+ /// Maximum poll attempts before giving up. Default 20.
+ public int MaxAttempts { get; set; } = 20;
+
+ /// Delay before the first retry. Default 2 seconds.
+ public TimeSpan InitialDelay { get; set; } = TimeSpan.FromSeconds(2);
+
+ /// Upper bound on the backoff delay. Default 20 seconds.
+ public TimeSpan MaxDelay { get; set; } = TimeSpan.FromSeconds(20);
+
+ /// Multiplier applied to the delay after each attempt. Default 2.
+ public double BackoffFactor { get; set; } = 2;
+
+ /// Forwarded to each underlying Reporting.LoadAsync call.
+ public RequestOptions? RequestOptions { get; set; }
+}
+
+///
+/// Utility code to help with the
+/// Square Reporting API.
+///
+///
+/// The /reporting/v1/load endpoint is asynchronous: a query that is still being
+/// computed comes back as an HTTP 200 whose body is { "error": "Continue wait" }
+/// rather than the results. Clients are expected to re-send the identical request, with
+/// backoff, until real results arrive. This helper owns that retry loop.
+///
+///
+public static class ReportingHelper
+{
+ ///
+ /// Sentinel returned by the Reporting API on an HTTP 200 while a /v1/load
+ /// query is still processing. It is NOT an error — the request should be retried.
+ ///
+ private const string ContinueWait = "Continue wait";
+
+ ///
+ /// Runs a reporting query and transparently polls until it resolves, returning the
+ /// final . Re-sends the identical request with exponential
+ /// backoff while the API answers "Continue wait".
+ ///
+ /// A configured reporting client, e.g. client.Reporting.
+ /// The reporting query (same shape as Reporting.LoadAsync).
+ /// Polling/backoff configuration.
+ /// Aborts the poll loop (and any in-flight wait) when signalled.
+ /// The resolved (never the "Continue wait" sentinel).
+ ///
+ /// Thrown if the query does not resolve within .
+ ///
+ ///
+ /// Thrown if is signalled while polling.
+ ///
+ ///
+ ///
+ ///
+ /// var response = await ReportingHelper.LoadAndWaitAsync(
+ /// client.Reporting,
+ /// new LoadRequest { Query = new Query { Measures = new List<string> { "Orders.count" } } }
+ /// );
+ ///
+ ///
+ ///
+ public static async Task LoadAndWaitAsync(
+ IReportingClient reporting,
+ LoadRequest? request = null,
+ LoadAndWaitOptions? options = null,
+ CancellationToken cancellationToken = default
+ )
+ {
+ if (reporting is null)
+ {
+ throw new ArgumentNullException(nameof(reporting));
+ }
+
+ request ??= new LoadRequest();
+ options ??= new LoadAndWaitOptions();
+ if (options.MaxAttempts < 1)
+ {
+ throw new ArgumentOutOfRangeException(
+ nameof(options),
+ "MaxAttempts must be at least 1."
+ );
+ }
+
+ var delay = options.InitialDelay;
+ for (var attempt = 1; attempt <= options.MaxAttempts; attempt++)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var response = await reporting
+ .LoadAsync(request, options.RequestOptions, cancellationToken)
+ .ConfigureAwait(false);
+
+ if (!IsContinueWait(response))
+ {
+ return response;
+ }
+
+ if (attempt == options.MaxAttempts)
+ {
+ break;
+ }
+
+ await Task.Delay(delay, cancellationToken).ConfigureAwait(false);
+ delay = TimeSpan.FromTicks(
+ Math.Min(
+ (long)(delay.Ticks * options.BackoffFactor),
+ options.MaxDelay.Ticks
+ )
+ );
+ }
+
+ throw new SquareException(
+ $"Reporting query did not complete after {options.MaxAttempts} attempts (\"{ContinueWait}\")."
+ );
+ }
+
+ ///
+ /// A "Continue wait" body deserializes into a (validation is
+ /// skipped) with the error field preserved on
+ /// and results absent. That's the signal to retry.
+ ///
+ private static bool IsContinueWait(LoadResponse response)
+ {
+ return response.AdditionalProperties.TryGetValue("error", out var error)
+ && error.ValueKind == JsonValueKind.String
+ && error.GetString() == ContinueWait;
+ }
+}
+
+public partial class ReportingClient
+{
+ ///
+ /// Runs a reporting query and transparently polls until it resolves, returning the
+ /// final . Convenience wrapper over
+ /// — re-sends the identical request with
+ /// exponential backoff while the Reporting API answers "Continue wait".
+ ///
+ /// The reporting query (same shape as ).
+ /// Polling/backoff configuration.
+ /// Aborts the poll loop (and any in-flight wait) when signalled.
+ /// The resolved (never the "Continue wait" sentinel).
+ ///
+ /// var response = await client.Reporting.LoadAndWaitAsync(
+ /// new LoadRequest { Query = new Query { Measures = new List<string> { "Orders.count" } } }
+ /// );
+ ///
+ public Task LoadAndWaitAsync(
+ LoadRequest? request = null,
+ LoadAndWaitOptions? options = null,
+ CancellationToken cancellationToken = default
+ ) => ReportingHelper.LoadAndWaitAsync(this, request, options, cancellationToken);
+}
From 4268bdb9f9464b44d81808c69a3b07e8a3d5112b Mon Sep 17 00:00:00 2001
From: fern-support
Date: Fri, 19 Jun 2026 19:55:49 -0400
Subject: [PATCH 3/4] build: derive AssemblyVersion/FileVersion from numeric
version core so RC prerelease builds compile (CS7034)
---
src/Square/Square.Custom.props | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/Square/Square.Custom.props b/src/Square/Square.Custom.props
index 2a798cef..94b7d49f 100644
--- a/src/Square/Square.Custom.props
+++ b/src/Square/Square.Custom.props
@@ -21,6 +21,14 @@
true
true
snupkg
+
+ $(Version.Substring(0, $(Version.IndexOf('-'))))
+ $(Version.Substring(0, $(Version.IndexOf('-'))))
From 53ba9a5b08a7834f09d2ccd49fd36f07b92c25eb Mon Sep 17 00:00:00 2001
From: fern-support
Date: Fri, 19 Jun 2026 20:04:27 -0400
Subject: [PATCH 4/4] docs: reference FER-11399 in AssemblyVersion workaround
comment
---
src/Square/Square.Custom.props | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Square/Square.Custom.props b/src/Square/Square.Custom.props
index 94b7d49f..c08fa69e 100644
--- a/src/Square/Square.Custom.props
+++ b/src/Square/Square.Custom.props
@@ -26,7 +26,7 @@
to $(Version), which breaks RC builds like 44.1.0-rc.0. Strip the prerelease so the
numeric core is used (44.1.0) while /PackageVersion keeps the full RC tag.
This block is imported AFTER the generated PropertyGroup, so it overrides it. Interim
- workaround — the durable fix belongs in the Fern C# generator (Linear follow-up). -->
+ workaround — the durable fix belongs in the Fern C# generator (FER-11399). -->
$(Version.Substring(0, $(Version.IndexOf('-'))))
$(Version.Substring(0, $(Version.IndexOf('-'))))