Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ test-client-dotnet: build-client-dotnet build-dotnet-multi-image

.PHONY: build-client-dotnet
build-client-dotnet: build-dotnet-multi-image
make build-client sdk_language=dotnet tmpdir=${TMP_DIR} OPENAPI_GENERATOR_CLI_DOCKER_TAG=v7.11.0
make build-client-streamed sdk_language=dotnet tmpdir=${TMP_DIR} OPENAPI_GENERATOR_CLI_DOCKER_TAG=v7.11.0

make run-in-docker sdk_language=dotnet image=openfga/dotnet-multi:${DOTNET_DOCKER_TAG} command="/bin/sh -c 'dotnet build --configuration Release'"
# Workaround for dotnet format issue: https://github.com/dotnet/format/issues/1634
Expand Down Expand Up @@ -202,7 +202,7 @@ build-client-streamed: build-openapi-streamed
.PHONY: build-openapi-streamed
build-openapi-streamed: init get-openapi-doc
cat "${DOCS_CACHE_DIR}/openfga.openapiv2.raw.json" | \
jq '(.. | .tags? | select(.)) |= ["OpenFga"] | (.tags? | select(.)) |= [{"name":"OpenFga"}] | del(.definitions.ReadTuplesParams, .definitions.ReadTuplesResponse, .paths."/stores/{store_id}/read-tuples")' > \
jq '(.. | .tags? | select(.)) |= ["OpenFga"] | (.tags? | select(.)) |= [{"name":"OpenFga"}] | del(.definitions.ReadTuplesParams, .definitions.ReadTuplesResponse, .paths."/stores/{store_id}/read-tuples") | .paths."/stores/{store_id}/streamed-list-objects".post["x-streaming"] = true | .paths."/stores/{store_id}/streamed-list-objects".post["x-streaming-response-type"] = "StreamedListObjectsResponse"' > \
${DOCS_CACHE_DIR}/openfga.openapiv2.json
sed -i -e 's/"Object"/"FgaObject"/g' ${DOCS_CACHE_DIR}/openfga.openapiv2.json
sed -i -e 's/#\/definitions\/Object"/#\/definitions\/FgaObject"/g' ${DOCS_CACHE_DIR}/openfga.openapiv2.json
Expand Down
1 change: 1 addition & 0 deletions config/clients/dotnet/config.overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"hashCodeBasePrimeNumber": 9661,
"hashCodeMultiplierPrimeNumber": 9923,
"openTelemetryDocumentation": "OpenTelemetry.md",
"supportsStreamedListObjects": true,
"files": {
"constants.mustache": {
"destinationFilename": "src/OpenFga.Sdk/Constants/FgaConstants.cs",
Expand Down
34 changes: 34 additions & 0 deletions config/clients/dotnet/template/README_calling_api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,40 @@ var response = await fgaClient.ListObjects(body, options);
// response.Objects = ["document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"]
```

{{#supportsStreamedListObjects}}
##### Streamed List Objects

List objects of a particular type that the user has access to, using the streaming API.

The Streamed ListObjects API is very similar to the ListObjects API, with two key differences:
1. **Streaming Results**: Instead of collecting all objects before returning a response, it streams them to the client as they are collected.
2. **No Pagination Limit**: Returns all results without the 1000-object limit of the standard ListObjects API.

This is particularly useful when querying **computed relations** that may return large result sets.

[API Documentation]({{apiDocsUrl}}#/Relationship%20Queries/StreamedListObjects)

```csharp
var options = new ClientListObjectsOptions {
AuthorizationModelId = "01GXSA8YR785C4FYS3C0RTG7B1",
Consistency = ConsistencyPreference.HIGHERCONSISTENCY
};

var objects = new List<string>();
await foreach (var response in fgaClient.StreamedListObjects(
new ClientListObjectsRequest {
User = "user:anne",
Relation = "can_read",
Type = "document"
},
options)) {
objects.Add(response.Object);
}

// objects = ["document:0192ab2a-d83f-756d-9397-c5ed9f3cb69a"]
```

{{/supportsStreamedListObjects}}
##### List Relations

List the relations a user has on an object.
Expand Down
67 changes: 67 additions & 0 deletions config/clients/dotnet/template/api.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ using {{packageName}}.{{modelPackage}};
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -31,6 +32,71 @@ public class {{classname}} : IDisposable {

{{#operations}}
{{#operation}}
{{#vendorExtensions.x-streaming}}
/// <summary>
/// {{summary}} {{notes}}
/// </summary>
/// <exception cref="ApiException">Thrown when fails to make API call</exception>
{{#allParams}}
/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}{{#isDeprecated}} (deprecated){{/isDeprecated}}</param>
{{/allParams}}
/// <param name="options">Request options.</param>
/// <param name="cancellationToken">Cancellation Token to cancel the request.</param>
/// <returns>IAsyncEnumerable of {{vendorExtensions.x-streaming-response-type}}</returns>
{{#isDeprecated}}
[Obsolete]
{{/isDeprecated}}
public async IAsyncEnumerable<{{vendorExtensions.x-streaming-response-type}}> {{operationId}}({{#allParams}}{{{dataType}}} {{paramName}}{{^required}}{{#optionalMethodArgument}} = default({{{dataType}}}){{/optionalMethodArgument}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#allParams.0}}, {{/allParams.0}}IRequestOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) {
var pathParams = new Dictionary<string, string> { };
{{#pathParams.0}}
if (string.IsNullOrWhiteSpace(storeId)) {
throw new FgaRequiredParamError("{{operationId}}", "StoreId");
}
{{/pathParams.0}}

{{#pathParams}}
if ({{paramName}} != null) {
{{#isDateTime}}
// call the specific date time stringify
pathParams.Add("{{baseName}}", {{paramName}}?.ToString("0"));
{{/isDateTime}}
{{^isDateTime}}
pathParams.Add("{{baseName}}", {{paramName}}.ToString());
{{/isDateTime}}
}
{{/pathParams}}
var queryParams = new Dictionary<string, string>();
{{#queryParams}}
if ({{paramName}} != null) {
{{#isDateTime}}
// call the specific date time stringify
queryParams.Add("{{baseName}}", {{paramName}}?.ToString("O"));
{{/isDateTime}}
{{^isDateTime}}
queryParams.Add("{{baseName}}", {{paramName}}.ToString());
{{/isDateTime}}
}
{{/queryParams}}

var requestBuilder = new RequestBuilder<{{#bodyParam}}{{{dataType}}}{{/bodyParam}}{{^bodyParam}}Any{{/bodyParam}}> {
Method = new HttpMethod("{{httpMethod}}"),
BasePath = _configuration.BasePath,
PathTemplate = "{{path}}",
PathParameters = pathParams,
{{#bodyParam}}
Body = {{paramName}},
{{/bodyParam}}
QueryParameters = queryParams,
};

await foreach (var response in _apiClient.SendStreamingRequestAsync<{{#bodyParam}}{{{dataType}}}{{/bodyParam}}{{^bodyParam}}Any{{/bodyParam}}, {{vendorExtensions.x-streaming-response-type}}>(requestBuilder,
"{{operationId}}", options, cancellationToken)) {
yield return response;
}
}

{{/vendorExtensions.x-streaming}}
{{^vendorExtensions.x-streaming}}
/// <summary>
/// {{summary}} {{notes}}
/// </summary>
Expand Down Expand Up @@ -91,6 +157,7 @@ public class {{classname}} : IDisposable {
"{{operationId}}", options, cancellationToken);
}

{{/vendorExtensions.x-streaming}}
{{/operation}}
{{/operations}}

Expand Down
Loading