dotnet / dotnet/aspnetcore

OpenApi does not set readOnly: true for get-only properties

Open
#65,403 3 comments 3 reactions 0 assignees View on GitHub
area-minimal feature-openapi
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [x] I have searched the existing issues

### Describe the bug

Get-only properties are not marked as "readOnly: true".
I assume set-only properties as well not marked as "writeOnly: true".

See "Steps to reproduce" for more information and examples.

### Expected Behavior

In object schema (for POST endpoints):
\- and -
In operation schema (for GET endpoints with FromQuery dto object):

Get-only properties should not be writable, set-only properties should not be readable.

Get-only property should not generate input fields in SwaggerUI for query params.
Get-only property should not be part of json in SwaggerUI for body params.

Set-only property should not be shown in responses.

### Steps To Reproduce

```csharp
public record PaginatedRequest(
string? Sorting = "Id",
int PageNumber = 1,
int PageSize = 50
)
{
public int Skip => (PageNumber - 1) * PageSize;

public int Take => PageSize;
}
```

For endpoint that accepts this record as body parameter:
```csharp
[HttpPost("search")]
public async Task GetAllPost(
[FromBody] GetAllBlogsPaginatedRequest request,
CancellationToken cancellationToken = default) { ... }
```

Result is:
```json
{
"components": {
"schemas": {
"GetAllBlogsPaginatedRequest": {
"type": "object",
"properties": {
// ...
"skip": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": [
"integer",
"string"
],
"format": "int32"
},
"take": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": [
"integer",
"string"
],
"format": "int32"
}
}
}
}
}
}
```

For endpoint that accepts this record as query parameter:
```csharp
[HttpGet]
public async Task GetAll(
[FromQuery] GetAllBlogsPaginatedRequest request,
CancellationToken cancellationToken = default) { ... }
```

```json
{
"paths": {
"/Blogs": {
"get": {
"tags": [
"Blogs"
],
"operationId": "GetAll",
"parameters": [
// ...
{
"name": "Skip",
"in": "query",
"schema": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": [
"integer",
"string"
],
"format": "int32"
}
},
{
"name": "Take",
"in": "query",
"schema": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": [
"integer",
"string"
],
"format": "int32"
}
}
],
"responses": {
"200": {
"description": "OK"
}
},
"security": [
{
"Bearer": []
},
{
"Client Credentials Keycloak": []
}
]
}
}
}
}
```

### Exceptions (if any)

None

### .NET Version

10.0.102

### Anything else?

``````

```csharp
builder.Services.AddOpenApi("v1",
api =>
{
api.AddDocumentTransformer(); // to add bearer auth
api.AddOperationTransformer(); // to use controler action name as operation id
});
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.