Closed BesnardFlorian closed 1 year ago
Aujourd'hui ça marche déjà plus ou moins côté C# et JS s'il y a au moins un paramètre dans le endpoint avec un domaine qui a explicitement IFormFile
(C#, équivalent de de MultipartFile
) ou File
(JS) (ce domaine n'est pas une composition et donc ne trigger par l'erreur "plusieurs body params").
Par exemple, j'ai ça dans une de mes applis :
params:
- composition: DocumentAdd
name: document
comment: Document.
- name: file
domain: DO_FILE
comment: Contenu du fichier.
C#
// [FromForm] c'est équivalent à `@RequestPart` et j'ai pas besoin de mettre l'annotation sur le IFormFile parce que c'est par défaut pour le type)
public int AddDocument([FromForm] DocumentAdd document, IFormFile file)
JS
export function addDocument(document: DocumentAdd, file: File, options: RequestInit = {}): Promise<number> {
const body = new FormData();
fillFormData( // fonction générée par topmodel en bas de fichier qui remplit correctement le FormData
{
...document,
file
},
body
);
return fetch("POST", `./api/document`, {body}, options);
}
Pour moi le problème c'est juste que le générateur Spring n'implémente pas cette logique correctement...
Je suis OK par contre pour dire qu'on pourrait ajouter un attribut multipartFormData
sur le domaine qui force l'utilisation d'un multipart/form-data
pour tous les endpoints qui utilisent au moins un paramètre de ce domaine, ce qui :
multipart/form-data
sur d'autres endpoints, indépendemment de la présence d'un fichier dedans (ce qui existe tout à fait dans certaines APIs).Voici un snippet présentant un code pouvant être généré par TopModel dans l'univers JS / Java. Dans cet exemple il est question de créer une Commune qui contiendrait, en plus d'autres propriétés, plusieurs fichiers "chartes", et un fichier "demography".
commune.service.ts
createCommune(communeEtrangereCreate: CommuneEtrangereCreate): Observable<void> {
const formData = new FormData();
formData.set("data", new Blob([JSON.stringify({...communeEtrangereCreate, demography:null, chartes: null})], {type: "application/json"}));
formData.set("demography", communeEtrangereCreate.demography!);
if(communeEtrangereCreate.chartes) {
communeEtrangereCreate.chartes.forEach((charte) => { formData.append('chartes', charte); });
}
return this.http.post<void>(`/communes`, formData, {headers: { "Content-Type": "multipart/form-data" }});
}
CommuneController.java
@PostMapping(path = "communes", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE })
void createCommune(@RequestPart(value = "data", required = false) CommuneEtrangereCreate communeEtrangereCreate, @RequestPart(value = "demography", required = false) MultipartFile demography, @RequestPart(value = "chartes", required = false) MultipartFile[] chartes) {
// DO SOMETHING
}
J'ai un besoin de requête Multipart qui contient à la fois du json et un binaire dans le body de la requête.
Je déclare donc comme params de mon endpoint :
Ça me génère une méthode java où le json est annoté @RequestBody et le champ binaire annoté @RequestParam. Or pour fonctionner, ce type de requête a plutôt besoin des 2 annotés avec @Requestpart.
J'ai donc pensé 2 évolutions possibles pour répondre à ce besoin :
"1 - Détection automatique du besoin de multipart"
Ajouter un nouveau paramétrage "binary" sur les domaines pour préciser s'ils sont binaires ou non (default false).
Si un endpoint n'a qu'un seul param de type binaire : ->on garde le fonctionnement actuel, le param binaire est annoté @requestParam, consumes = le médiatype du domaine.
Si un endpoint a un param de type binaire + un param de type champ de domaine non binaire : -> le param binaire est annoté @RequestPart et le param champ est annoté par @RequestParam, consumes = "multipart/form-data"
Si un endpoint a un param de type binaire + un param de type composition : -> le param binaire est annoté @RequestPart et le param champ est annoté par @RequestPart , consumes = "multipart/form-data"
Si un endpoint a plusieurs params de type binaire, ou plusieurs params de type composition, ou mélangés : -> les param binaires et composition sont annotés @RequestPart, consumes = "multipart/form-data" (ça couvre a priori le cas de plusieurs params de type compositions qui jette actuellement une erreur : {TMD0000} L'endpoint 'XXX' doit avoir une seule propriété dans le body)
Pour le JS, si on a mis un @Multipart côté back, il faudrait que le body du fetch soit un objet dont les clefs sont les noms des différentes part et les valeurs celles de la part.
"2 - Indication manuelle du besoin de multipart"