kiota
kiota copied to clipboard
multipart/form-data request content type is not handled properly
The content serialization won't work as we don't have a serializer for it. Adding a serializer should be trivial using the natively available libraries on the platform to build the body so encoding is handled for us.
Some example to test https://docs.microsoft.com/en-us/graph/api/section-post-pages?view=graph-rest-1.0
when implemented, we should clear the appsettings.json to be able to ship self-contained executable
See spec https://www.rfc-editor.org/rfc/rfc7578.html
OpenAPI specification example of multipart forms. https://spec.openapis.org/oas/v3.1.0#encoding-object-example
In OpenAPI 3.0, JSON Schema isn't rich enough to describe multipart forms, so you may need a combination of schema object and encoding object. However, the encoding object is rarely used, so I suggest we wait until we add support for OpenAPI 3.1 as then all the information that is needed is in the JSON Schema. This will avoid us having to add support for the encoding object.
We should still be able to support multipart/form-data media types with scalar parts without support for the encoding object.
This one is a bit more complex to handle since multipart are effectively a composition of other serialization formats. This new serializer/deserializer would effectively need to be a composition of others (configurable, should be easily done). The breaking change comes where models need to carry the information somehow that property A is serialized as JSON and property B as YAML. We don't have a place holder to carry that information today and it'd probably represent a breaking change.
It'd also be a break of our promise that models being generated are serialization format agnostic.
@darrelmiller to think about that further and come up with guidance.
Another interesting point that @ddyett brought up is the fact that there's probably not even a way to describe the sub parts mime type in open API today. At the end of the day we're most likely blocked by that dependency besides giving customers access to the stream directly and some task design to compose the request body like we do for batches today.
Meeting notes: we don't need an additional task, we could code-gen them but use an aggregator of parts instead of a stream.
Example of usage:
var multipartFormBody = new MultipartFormBody(); // this would come from abstractions
multipartFormBody.AddStructuredPart("data", "application/json", oneNotPageParsable);
multipartFormBody.AddRawPart("picture", "image/jpeg", stream, optionalHeaders);
await client.Me.Notes.PostAsync(multipartFormBody);
//we'd new a new serialization/parse node library with a reference to the registry
var multipartFormBody = await client.Me.Notes.GetAsync(); // pass the parse node registry so it can be used when trying to get parts
var pageInfo = multipartFormBody.GetStructuredPart<PageInfo>("data");
var headers = multipartFormBody.GetPartHeaders("data")
var imageStream = multipartFormBody.GetRawPart("picture");
With this approach however we might be missing the complex types/models as nothing in the description with tie the schemas to the endpoints, more investigations needed.
@darrelmiller I believe we could use an extension to bring the types over automatically and maybe an extension rule as well.
paths:
/pets:
post:
requestBody:
required: true
content:
multipart/form-data:
x-ms-used-schemas:
- $ref: '#/components/schemas/Pet'
What do you think? We could also add a validation rule for when we find this content type but no extension. (and do exactly the same for responses)
We don't need an extension, the content of a multipart form data payload can in fact be entirely described today. https://github.com/OAI/OpenAPI-Specification/pull/3167#discussion_r1116340967
repos created for the occasion https://github.com/microsoft/kiota-serialization-multipart-dotnet https://github.com/microsoft/kiota-serialization-multipart-go
@andrueastman @rkodev I've finally been able to make progress on that... I still need to work on the generation part and on the Go/Java implementations but you can have a look at https://github.com/microsoft/kiota-abstractions-dotnet/pull/106 and https://github.com/microsoft/kiota-serialization-multipart-dotnet to start giving me feedback.
in terms of usage here is what it looks like
var mpBody = new MultipartBody();
mpBody.AddOrReplacePart("Presentation", "text/html", htmlPayload);
mpBody.AddOrReplacePart("imageBlock1", "image/png", imgStream);
await apiClient.Users["id"].OneNote.Pages.PostAsync(mpBody);
I've considered for a while trying to put all the parts as parameters of PostAsync, but that'd mean that adding a part in the schema would be binary breaking. Not ideal. Plus there are cases where we don't know all the parts in advance (e.g. one note, the number of images in any given page is variable). That kind of requires the user to know the part names (not an issue for OneNote since all the part names except for presentation depend on what's in presentation) and their mime types.
Andrew: would you mind sharing what API surface v4 was offering until now please?
@baywet
In V4, the API simply used MultipartFormDataContent
which is a derived instance of HttpContent. This would simply be set to the HttpRequestMessage instance by passing it as a parameter. This was however not generated by Typewriter as the metadata incorrectly sets the content as a json but added as an overload/extension to the generated sdk.
var multipartContent = new MultipartFormDataContent();
var htmlString =
"<!DOCTYPE html>\n<html>\n <head>\n <title>A page with <i>rendered</i> images and an <b>attached</b> file</title>\n <meta name=\"created\" content=\"2015-07-22T09:00:00-08:00\" />\n </head>\n <body>\n <p>Here's an image from an online source:</p>\n <img src=\"https://...\" alt=\"an image on the page\" width=\"500\" />\n <p>Here's an image uploaded as binary data:</p>\n <img src=\"name:imageBlock1\" alt=\"an image on the page\" width=\"300\" />\n <p>Here's a file attachment:</p>\n <object data-attachment=\"FileName.pdf\" data=\"name:fileBlock1\" type=\"application/pdf\" />\n </body>\n</html>";
var presentation = new StringContent(htmlString, Encoding.UTF8, "text/html");
multipartContent.Add(presentation,"Presentation");//needs a name
await graphClient.Users["id"].OneNote.Pages.Request().AddAsync(multipartContent);
Thanks for sharing the information. I've also created an issue in the conversion library so we can emit the right metadata. Also after internal discussions, we'll only implement serialization for now, not deserialization.
onenote example
'/users/{user-id}/onenote/pages':
post:
parameters:
- name: user-id
in: path
description: The unique identifier of user
required: true
style: simple
schema:
type: string
x-ms-docs-key-type: user
description: Create a new page in the specified section.
tags:
- pages.create
operationId: create_page
requestBody:
content:
'multipart/form-data':
schema:
properties:
Presentation:
type: string
encoding:
Presentation:
contentType: text/html
responses:
'204':
description: No Content