-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Several improvements #20
Comments
Just ran into the empty schema for PatchDocument in swagger, would love to have this supported properly OOTB. |
Yeah we could quite easily improve it to a point where swagger schema gets generated as path - string, op - string, but what can we do for the value property as that could be anything? |
Good question, I've chosen to use a string as well at the moment, as suggested here: https://stackoverflow.com/a/65607728/510149 /// <summary>
/// A swagger document filter to support json patch better.
/// </summary>
public class JsonPatchDocumentFilter : IDocumentFilter
{
/// <inheritdoc />
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
// Replace schemas for Operation and JsonPatchDocument
var schemas = swaggerDoc.Components.Schemas.ToList();
foreach (var item in schemas)
{
if (item.Key.StartsWith("Operation") || item.Key.StartsWith("JsonPatchDocument"))
{
swaggerDoc.Components.Schemas.Remove(item.Key);
}
}
var jsonPatchDocumentOperationTypes = Enum.GetValues<PatchOperationType>()
.Where(x => x != PatchOperationType.Invalid)
.Select(x => new OpenApiString(x.ToString().ToLower())).ToList();
swaggerDoc.Components.Schemas.Add("Operation", new OpenApiSchema
{
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
{
{ "op", new OpenApiSchema { Type = "string", Enum = new List<IOpenApiAny>(jsonPatchDocumentOperationTypes) } },
{ "value", new OpenApiSchema { Type = "string" } },
{ "path", new OpenApiSchema { Type = "string" } },
},
});
swaggerDoc.Components.Schemas.Add("JsonPatchDocument", new OpenApiSchema
{
Type = "array",
Items = new OpenApiSchema
{
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "Operation" },
},
Description = "Array of operations to perform",
});
// Alter the content type for patch requests
foreach (var path in swaggerDoc.Paths
.SelectMany(p => p.Value.Operations)
.Where(p => p.Key == OperationType.Patch))
{
path.Value.RequestBody.Content = new Dictionary<string, OpenApiMediaType>
{
{ "application/json-patch+json", new OpenApiMediaType { Schema = new OpenApiSchema { Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "JsonPatchDocument" } } } },
};
}
}
} |
Maybe a new Nuget package needs to be created to avoid dependency to Swashbuckle / NSwag |
Currently I'm also struggling getting a bulk patch endpoint to work, with a |
This works when used with a parameter like /// <summary>
/// A swagger document filter to support json patch better.
/// </summary>
public class JsonPatchDocumentFilter : IDocumentFilter
{
/// <inheritdoc />
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
// Replace schemas for Operation and JsonPatchDocument
swaggerDoc.Components.Schemas
.Where(x => x.Key.EndsWith("Operation") || x.Key.EndsWith("JsonPatchDocument"))
.ToList()
.ForEach(x => swaggerDoc.Components.Schemas.Remove(x));
var jsonPatchDocumentOperationTypes = Enum.GetValues<PatchOperationType>()
.Where(x => x != PatchOperationType.Invalid)
.Select(x => new OpenApiString(x.ToString().ToLower())).ToList();
swaggerDoc.Components.Schemas.Add("Operation", new OpenApiSchema
{
Type = "object",
Properties = new Dictionary<string, OpenApiSchema>
{
{ "op", new OpenApiSchema { Type = "string", Enum = new List<IOpenApiAny>(jsonPatchDocumentOperationTypes) } },
{ "value", new OpenApiSchema { Type = "string" } },
{ "path", new OpenApiSchema { Type = "string" } },
},
});
swaggerDoc.Components.Schemas.Add("JsonPatchDocument", new OpenApiSchema
{
Type = "array",
Items = new OpenApiSchema
{
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "Operation" },
},
Description = "Array of operations to perform",
});
swaggerDoc.Components.Schemas.Add("BulkJsonPatchDocument", new OpenApiSchema
{
Type = "object",
AdditionalProperties = new OpenApiSchema
{
Type = "array",
Items = new OpenApiSchema
{
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "Operation" },
},
},
Description = "A dictionary/map using the entity id (Guid) as key and JsonPatchDocument as value.",
});
// Alter the content type and schema for patch requests
foreach (var path in swaggerDoc.Paths
.SelectMany(p => p.Value.Operations)
.Where(p => p.Key == OperationType.Patch))
{
var schemaReferenceId = "JsonPatchDocument";
if (path.Value.RequestBody.Content.First().Value.Schema.AdditionalProperties != null)
{
// When AdditionalProperties is not null, it means a dictionary is used and thus it's a bulk request
schemaReferenceId = "BulkJsonPatchDocument";
}
path.Value.RequestBody.Content = new Dictionary<string, OpenApiMediaType>
{
{
"application/json-patch+json",
new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference
{
Type = ReferenceType.Schema,
Id = schemaReferenceId,
},
},
}
},
};
}
}
} |
Here's the IDocumentFilter that we use successfully, inspired by the code examples on this Issue and in the linked StackOverflow thread. public class JsonPatchDocumentFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
// Handle schemas
var keysToRemove = swaggerDoc.Components.Schemas
.Where(s =>
(s.Key.EndsWith("Operation", StringComparison.OrdinalIgnoreCase) && s.Value.Properties.All(p => new string[] { "op", "path", "from", "value" }.Contains(p.Key))) ||
(s.Key.EndsWith("JsonPatchDocument", StringComparison.OrdinalIgnoreCase))
)
.Select(s => s.Key)
.ToList();
foreach (var key in keysToRemove)
{
swaggerDoc.Components.Schemas.Remove(key);
}
swaggerDoc.Components.Schemas.Add("JsonPatchOperation", new OpenApiSchema
{
Type = "object",
Description = "Describes a single operation in a JSON Patch document. Includes the operation type, the target property path, and the value to be used.",
Required = new HashSet<string> { "op", "path", "value" },
Properties = new Dictionary<string, OpenApiSchema>
{
{
"op", new OpenApiSchema
{
Type = "string",
Description = "The operation type. Allowed values: 'add', 'remove', 'replace', 'move', 'copy', 'test'.",
Enum = new List<IOpenApiAny>
{
new OpenApiString("add"),
new OpenApiString("remove"),
new OpenApiString("replace"),
new OpenApiString("move"),
new OpenApiString("copy"),
new OpenApiString("test")
}
}
},
{
"path", new OpenApiSchema
{
Type = "string",
Description = "The JSON Pointer path to the property in the target document where the operation is to be applied.",
}
},
{
"from", new OpenApiSchema
{
Type = "string",
Description = "Should be a path, required when using move, copy",
}
},
{
"value", new OpenApiSchema
{
Nullable = true,
Description = "The value to apply for 'add', 'replace', or 'test' operations. Not required for 'remove', 'move', or 'copy'.",
}
},
},
});
swaggerDoc.Components.Schemas.Add("JsonPatchDocument", new OpenApiSchema
{
Type = "array",
Items = new OpenApiSchema
{
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "JsonPatchOperation" }
},
Description = "Array of operations to perform"
});
// Handle paths
foreach (var path in swaggerDoc.Paths)
{
if (path.Value.Operations.TryGetValue(OperationType.Patch, out var patchOperation) && patchOperation.RequestBody != null)
{
foreach (var key in patchOperation.RequestBody.Content.Keys)
{
patchOperation.RequestBody.Content.Remove(key);
}
patchOperation.RequestBody.Content.Add("application/json-patch+json", new OpenApiMediaType
{
Schema = new OpenApiSchema
{
Reference = new OpenApiReference { Type = ReferenceType.Schema, Id = "JsonPatchDocument" },
},
});
}
}
} |
The
JsonPatchDocument<T>
as an argument to aPATCH
method on server is not recognized on swagger/redoc page and shown as{}
.The way to overcome this issue it to use
List<Operation<T>>
as argument instead and initializeJsonPatchDocument<T>
from it in patch method:Sending collection of operations as
application/json-patch+json
works fine too. The problem is: initializing list ofOperation
s isn't very convenient. Instead of:We have to write such code:
The problems of this code are obvious: we have to rely on
string
values when creating the operations (while it could have been safer to useOperationType
enum) and we have to rely onstring
when resolvingpath
.Probably a static methods for
Operation<T>
class could be implemented, so the usage would look similar to this:These static methods would also simplify related calls in
JsonPatchDocumentOfT
:SystemTextJsonPatch/SystemTextJsonPatch/JsonPatchDocumentOfT.cs
Line 189 in c7ffbaf
The text was updated successfully, but these errors were encountered: