From f6a77d143ea73673234a3300ca66cd079537430b Mon Sep 17 00:00:00 2001 From: nojaf Date: Sun, 11 Jan 2026 11:46:20 +0100 Subject: [PATCH 1/2] Remodel formDataEntryValue --- src/FetchAPI.res | 3 ++- src/FetchAPI/FormData.res | 8 +------ src/FetchAPI/FormDataEntryValue.js | 22 ++++++++++++++++++ src/FetchAPI/FormDataEntryValue.res | 22 ++++++++++++++++++ src/FileAPI/File.js | 7 ++++++ src/FileAPI/File.res | 2 ++ tests/FetchAPI/FormData__test.js | 35 +++++++++++++++++++++++++---- tests/FetchAPI/FormData__test.res | 30 +++++++++++++++++++++++-- 8 files changed, 115 insertions(+), 14 deletions(-) create mode 100644 src/FetchAPI/FormDataEntryValue.js create mode 100644 src/FetchAPI/FormDataEntryValue.res diff --git a/src/FetchAPI.res b/src/FetchAPI.res index f7e4e9e..3e499f5 100644 --- a/src/FetchAPI.res +++ b/src/FetchAPI.res @@ -218,7 +218,8 @@ type formData = {} type requestInfo = any -type formDataEntryValue = any +@editor.completeFrom(FormDataEntryValue) +type formDataEntryValue type requestInit = { /** diff --git a/src/FetchAPI/FormData.res b/src/FetchAPI/FormData.res index d251705..0a43df1 100644 --- a/src/FetchAPI/FormData.res +++ b/src/FetchAPI/FormData.res @@ -31,13 +31,7 @@ external delete: (formData, string) => unit = "delete" [Read more on MDN](https://developer.mozilla.org/docs/Web/API/FormData/get) */ @send -external get: (formData, string) => null = "get" - -/** -[Read more on MDN](https://developer.mozilla.org/docs/Web/API/FormData/get) -*/ -@send -external getFile: (formData, string) => null = "get" +external get: (formData, string) => null = "get" /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/FormData/getAll) diff --git a/src/FetchAPI/FormDataEntryValue.js b/src/FetchAPI/FormDataEntryValue.js new file mode 100644 index 0000000..e4da9e8 --- /dev/null +++ b/src/FetchAPI/FormDataEntryValue.js @@ -0,0 +1,22 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as File$WebAPI from "../FileAPI/File.js"; + +function decode(t) { + if (File$WebAPI.isInstanceOf(t)) { + return { + TAG: "File", + _0: t + }; + } else { + return { + TAG: "String", + _0: t + }; + } +} + +export { + decode, +} +/* File-WebAPI Not a pure module */ diff --git a/src/FetchAPI/FormDataEntryValue.res b/src/FetchAPI/FormDataEntryValue.res new file mode 100644 index 0000000..d31ac57 --- /dev/null +++ b/src/FetchAPI/FormDataEntryValue.res @@ -0,0 +1,22 @@ +open Prelude +open FetchAPI +open FileAPI + +external fromString: string => formDataEntryValue = "%identity" +external fromFile: file => formDataEntryValue = "%identity" + +/** +Represents a decoded version of the abstract `formDataEntryValue` type. +A FormData entry value is either a string or a File. +*/ +type decoded = + | String(string) + | File(file) + +let decode = (t: formDataEntryValue): decoded => { + if File.isInstanceOf(t) { + File(unsafeConversation(t)) + } else { + String(unsafeConversation(t)) + } +} diff --git a/src/FileAPI/File.js b/src/FileAPI/File.js index e158bb8..138f7ed 100644 --- a/src/FileAPI/File.js +++ b/src/FileAPI/File.js @@ -4,4 +4,11 @@ import * as Blob$WebAPI from "./Blob.js"; Blob$WebAPI.Impl({}); +function isInstanceOf(param) { + return (param instanceof File); +} + +export { + isInstanceOf, +} /* Not a pure module */ diff --git a/src/FileAPI/File.res b/src/FileAPI/File.res index eb66d53..7bc365e 100644 --- a/src/FileAPI/File.res +++ b/src/FileAPI/File.res @@ -11,3 +11,5 @@ external make: ( ~fileName: string, ~options: filePropertyBag=?, ) => file = "File" + +let isInstanceOf = (_: 't): bool => %raw(`param instanceof File`) diff --git a/tests/FetchAPI/FormData__test.js b/tests/FetchAPI/FormData__test.js index 3cc97ef..e091c42 100644 --- a/tests/FetchAPI/FormData__test.js +++ b/tests/FetchAPI/FormData__test.js @@ -1,15 +1,42 @@ // Generated by ReScript, PLEASE EDIT WITH CARE +import * as FormDataEntryValue$WebAPI from "../../src/FetchAPI/FormDataEntryValue.js"; let formData = new FormData(document.forms.myForm); -let phone = formData.get("phone"); +let phoneEntry = formData.get("phone"); -let image = formData.get("image"); +if (phoneEntry !== null) { + let value = FormDataEntryValue$WebAPI.decode(phoneEntry); + if (value.TAG === "String") { + console.log(`Phone: ` + value._0); + } else { + console.log(`Unexpected file: ` + value._0.name); + } +} else { + console.log("No phone field"); +} + +let allImages = formData.getAll("images"); + +allImages.forEach(entry => { + let value = FormDataEntryValue$WebAPI.decode(entry); + if (value.TAG === "String") { + console.log(`String value: ` + value._0); + return; + } + console.log(`File: ` + value._0.name); +}); + +let fileEntry = new File([], "test.txt"); + +let stringEntry = "test value"; export { formData, - phone, - image, + phoneEntry, + allImages, + stringEntry, + fileEntry, } /* formData Not a pure module */ diff --git a/tests/FetchAPI/FormData__test.res b/tests/FetchAPI/FormData__test.res index 462a6ef..3bea5cc 100644 --- a/tests/FetchAPI/FormData__test.res +++ b/tests/FetchAPI/FormData__test.res @@ -3,5 +3,31 @@ external myForm: DOMAPI.htmlFormElement = "myForm" let formData = FormData.make(~form=myForm) -let phone: null = formData->FormData.get("phone") -let image: null = formData->FormData.getFile("image") + +// Get a form field - returns formDataEntryValue which could be string or File +let phoneEntry: null = formData->FormData.get("phone") + +// Decode the entry to handle both string and File cases +let _ = switch phoneEntry->Null.toOption { +| None => Console.log("No phone field") +| Some(entry) => + switch entry->FormDataEntryValue.decode { + | FormDataEntryValue.String(value) => Console.log(`Phone: ${value}`) + | FormDataEntryValue.File(file) => Console.log(`Unexpected file: ${file.name}`) + } +} + +// Get all values for a field (useful for multi-select or multiple file inputs) +let allImages: array = formData->FormData.getAll("images") + +// Process all entries +let _ = allImages->Array.forEach(entry => { + switch entry->FormDataEntryValue.decode { + | FormDataEntryValue.String(value) => Console.log(`String value: ${value}`) + | FormDataEntryValue.File(file) => Console.log(`File: ${file.name}`) + } +}) + +// Create formDataEntryValue from string or file +let stringEntry = FormDataEntryValue.fromString("test value") +let fileEntry = FormDataEntryValue.fromFile(File.make(~fileBits=[], ~fileName="test.txt")) From e93a430d5586384bd13529b4016e7c1adfc1cf77 Mon Sep 17 00:00:00 2001 From: nojaf Date: Sun, 11 Jan 2026 11:50:37 +0100 Subject: [PATCH 2/2] Add FormData.entries() --- src/FetchAPI/FormData.res | 6 ++++++ tests/FetchAPI/FormData__test.js | 13 +++++++++++++ tests/FetchAPI/FormData__test.res | 9 +++++++++ 3 files changed, 28 insertions(+) diff --git a/src/FetchAPI/FormData.res b/src/FetchAPI/FormData.res index 0a43df1..8e584cc 100644 --- a/src/FetchAPI/FormData.res +++ b/src/FetchAPI/FormData.res @@ -45,6 +45,12 @@ external getAll: (formData, string) => array = "getAll" @send external has: (formData, string) => bool = "has" +/** +[Read more on MDN](https://developer.mozilla.org/docs/Web/API/FormData/entries) +*/ +@send +external entries: formData => Iterator.t<(string, formDataEntryValue)> = "entries" + /** [Read more on MDN](https://developer.mozilla.org/docs/Web/API/FormData/keys) */ diff --git a/tests/FetchAPI/FormData__test.js b/tests/FetchAPI/FormData__test.js index e091c42..a407aba 100644 --- a/tests/FetchAPI/FormData__test.js +++ b/tests/FetchAPI/FormData__test.js @@ -30,6 +30,18 @@ allImages.forEach(entry => { let fileEntry = new File([], "test.txt"); +let entries = formData.entries(); + +entries.forEach(param => { + let key = param[0]; + let s = FormDataEntryValue$WebAPI.decode(param[1]); + if (s.TAG === "String") { + console.log(key + `: ` + s._0); + return; + } + console.log(key + `: [File] ` + s._0.name); +}); + let stringEntry = "test value"; export { @@ -38,5 +50,6 @@ export { allImages, stringEntry, fileEntry, + entries, } /* formData Not a pure module */ diff --git a/tests/FetchAPI/FormData__test.res b/tests/FetchAPI/FormData__test.res index 3bea5cc..6a5cb28 100644 --- a/tests/FetchAPI/FormData__test.res +++ b/tests/FetchAPI/FormData__test.res @@ -31,3 +31,12 @@ let _ = allImages->Array.forEach(entry => { // Create formDataEntryValue from string or file let stringEntry = FormDataEntryValue.fromString("test value") let fileEntry = FormDataEntryValue.fromFile(File.make(~fileBits=[], ~fileName="test.txt")) + +// Iterate over all entries in the FormData +let entries: Iterator.t<(string, FetchAPI.formDataEntryValue)> = formData->FormData.entries +let _ = entries->Iterator.forEach(((key, value)) => { + switch value->FormDataEntryValue.decode { + | FormDataEntryValue.String(s) => Console.log(`${key}: ${s}`) + | FormDataEntryValue.File(f) => Console.log(`${key}: [File] ${f.name}`) + } +})