generated from tpl/purs
fix: form bug
This commit is contained in:
parent
fc33a076db
commit
1a1d5526b7
23
.spec-results
Normal file
23
.spec-results
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
[
|
||||||
|
"Form fromRaw",
|
||||||
|
{
|
||||||
|
"timestamp": "1732391355864.0",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Form ok",
|
||||||
|
{
|
||||||
|
"timestamp": "1732390464524.0",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"Form toRaw",
|
||||||
|
{
|
||||||
|
"timestamp": "1732391355864.0",
|
||||||
|
"success": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
2234
spago.lock
2234
spago.lock
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,11 @@ package:
|
|||||||
location:
|
location:
|
||||||
githubOwner: 'cakekindel'
|
githubOwner: 'cakekindel'
|
||||||
githubRepo: 'purescript-ezfetch'
|
githubRepo: 'purescript-ezfetch'
|
||||||
|
test:
|
||||||
|
main: 'Test.Main'
|
||||||
|
dependencies:
|
||||||
|
- spec
|
||||||
|
- spec-node
|
||||||
dependencies:
|
dependencies:
|
||||||
- aff: ">=7.1.0 <8.0.0"
|
- aff: ">=7.1.0 <8.0.0"
|
||||||
- aff-promise: ">=4.0.0 <5.0.0"
|
- aff-promise: ">=4.0.0 <5.0.0"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/** @type {(_: Record<string, Array<string | Blob>>) => () => FormData} */
|
/** @type {(_: Record<string, Array<string | File>>) => () => FormData} */
|
||||||
export const unsafeMakeFormData = o => () => {
|
export const unsafeMakeFormData = o => () => {
|
||||||
const form = new FormData()
|
const form = new FormData()
|
||||||
|
|
||||||
@ -11,20 +11,20 @@ export const unsafeMakeFormData = o => () => {
|
|||||||
return form
|
return form
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @typedef {{filename: string | null, mime: string, buf: ArrayBuffer}} FileRecord */
|
/** @typedef {{filename: string, mime: string, buf: ArrayBuffer}} FileRecord */
|
||||||
|
|
||||||
/** @type {(_: FileRecord) => () => Blob} */
|
/** @type {(_: FileRecord) => () => File} */
|
||||||
export const unsafeMakeBlob =
|
export const unsafeMakeFile =
|
||||||
({ mime, buf }) =>
|
({ mime, buf, filename }) =>
|
||||||
() =>
|
() =>
|
||||||
new Blob([buf], { type: mime })
|
new File([buf], filename, { type: mime })
|
||||||
|
|
||||||
/** @type {(_: FormData) => () => Promise<Record<string, Array<string | FileRecord>>>} */
|
/** @type {(_: FormData) => () => Promise<Record<string, Array<string | FileRecord>>>} */
|
||||||
export const unsafeUnmakeFormData = fd => async () => {
|
export const unsafeUnmakeFormData = fd => async () => {
|
||||||
/** @type {Record<string, Array<string | FileRecord>>} */
|
/** @type {Record<string, Array<string | FileRecord>>} */
|
||||||
const rec = {}
|
const rec = {}
|
||||||
for (const [k, ent_] of fd.entries()) {
|
for (const [k, ent_] of fd.entries()) {
|
||||||
/** @type {File | Blob | string} */
|
/** @type {File | string} */
|
||||||
const ent = ent_
|
const ent = ent_
|
||||||
|
|
||||||
/** @type {string | FileRecord} */
|
/** @type {string | FileRecord} */
|
||||||
@ -35,8 +35,6 @@ export const unsafeUnmakeFormData = fd => async () => {
|
|||||||
buf: await ent.arrayBuffer(),
|
buf: await ent.arrayBuffer(),
|
||||||
mime: ent.type,
|
mime: ent.type,
|
||||||
}
|
}
|
||||||
} else if (ent instanceof Blob) {
|
|
||||||
append = { filename: null, buf: await ent.arrayBuffer(), mime: ent.type }
|
|
||||||
} else {
|
} else {
|
||||||
append = ent
|
append = ent
|
||||||
}
|
}
|
||||||
@ -54,3 +52,29 @@ export const unsafeUnmakeFormData = fd => async () => {
|
|||||||
|
|
||||||
return rec
|
return rec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @type {(a: ArrayBuffer) => (b: ArrayBuffer) => boolean} */
|
||||||
|
export const unsafeEqArrayBuffer = a => b => {
|
||||||
|
try {
|
||||||
|
if (a.byteLength !== b.byteLength) return false
|
||||||
|
const ua = new Uint8Array(a)
|
||||||
|
const ub = new Uint8Array(b)
|
||||||
|
let pass = true
|
||||||
|
for (let i = 0; i < a.byteLength; i++) {
|
||||||
|
pass = pass && ua[i] === ub[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return pass
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {(a: ArrayBuffer) => string} */
|
||||||
|
export const unsafeShowArrayBuffer = a => {
|
||||||
|
try {
|
||||||
|
return Buffer.from(a).toString('base64url')
|
||||||
|
} catch (e) {
|
||||||
|
return e instanceof Error ? e.toString() : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,12 +10,12 @@ import Data.ArrayBuffer.Types (ArrayBuffer)
|
|||||||
import Data.Either (hush)
|
import Data.Either (hush)
|
||||||
import Data.FoldableWithIndex (foldlWithIndex)
|
import Data.FoldableWithIndex (foldlWithIndex)
|
||||||
import Data.Generic.Rep (class Generic)
|
import Data.Generic.Rep (class Generic)
|
||||||
|
import Data.MIME (MIME)
|
||||||
|
import Data.MIME as MIME
|
||||||
import Data.Map (Map)
|
import Data.Map (Map)
|
||||||
import Data.Map as Map
|
import Data.Map as Map
|
||||||
import Data.Maybe (Maybe(..))
|
import Data.Maybe (Maybe(..))
|
||||||
import Data.Newtype (class Newtype, unwrap, wrap)
|
import Data.Newtype (class Newtype, unwrap, wrap)
|
||||||
import Data.Nullable (Nullable)
|
|
||||||
import Data.Nullable as Nullable
|
|
||||||
import Data.Show.Generic (genericShow)
|
import Data.Show.Generic (genericShow)
|
||||||
import Data.Traversable (for)
|
import Data.Traversable (for)
|
||||||
import Effect (Effect)
|
import Effect (Effect)
|
||||||
@ -25,16 +25,16 @@ import Effect.Exception (error)
|
|||||||
import Foreign (Foreign, unsafeReadTagged, unsafeToForeign)
|
import Foreign (Foreign, unsafeReadTagged, unsafeToForeign)
|
||||||
import Foreign.Object (Object)
|
import Foreign.Object (Object)
|
||||||
import Foreign.Object as Object
|
import Foreign.Object as Object
|
||||||
import Data.MIME (MIME)
|
|
||||||
import Data.MIME as MIME
|
|
||||||
import Simple.JSON (readImpl, unsafeStringify)
|
import Simple.JSON (readImpl, unsafeStringify)
|
||||||
import Unsafe.Coerce (unsafeCoerce)
|
import Unsafe.Coerce (unsafeCoerce)
|
||||||
import Web.File.Blob (Blob)
|
import Web.File.File (File)
|
||||||
|
|
||||||
type FileRecord = { filename :: Nullable String, mime :: String, buf :: ArrayBuffer }
|
type FileRecord = { filename :: String, mime :: String, buf :: ArrayBuffer }
|
||||||
|
|
||||||
foreign import data RawFormData :: Type
|
foreign import data RawFormData :: Type
|
||||||
foreign import unsafeMakeBlob :: FileRecord -> Effect Blob
|
foreign import unsafeShowArrayBuffer :: ArrayBuffer -> String
|
||||||
|
foreign import unsafeEqArrayBuffer :: ArrayBuffer -> ArrayBuffer -> Boolean
|
||||||
|
foreign import unsafeMakeFile :: FileRecord -> Effect File
|
||||||
foreign import unsafeMakeFormData :: Object (Array Foreign) -> Effect RawFormData
|
foreign import unsafeMakeFormData :: Object (Array Foreign) -> Effect RawFormData
|
||||||
foreign import unsafeUnmakeFormData :: RawFormData -> Effect (Promise (Object (Array Foreign)))
|
foreign import unsafeUnmakeFormData :: RawFormData -> Effect (Promise (Object (Array Foreign)))
|
||||||
|
|
||||||
@ -50,16 +50,16 @@ derive newtype instance Ord Filename
|
|||||||
|
|
||||||
data Value
|
data Value
|
||||||
= ValueString String
|
= ValueString String
|
||||||
| ValueFile (Maybe Filename) ArrayBuffer MIME
|
| ValueFile Filename ArrayBuffer MIME
|
||||||
|
|
||||||
valueForeign :: Value -> Effect Foreign
|
valueForeign :: Value -> Effect Foreign
|
||||||
valueForeign (ValueString s) = pure $ unsafeToForeign s
|
valueForeign (ValueString s) = pure $ unsafeToForeign s
|
||||||
valueForeign (ValueFile filename buf mime) = unsafeToForeign <$> unsafeMakeBlob { filename: Nullable.toNullable $ unwrap <$> filename, buf, mime: MIME.toString mime }
|
valueForeign (ValueFile filename buf mime) = unsafeToForeign <$> unsafeMakeFile { filename: unwrap filename, buf, mime: MIME.toString mime }
|
||||||
|
|
||||||
valueFromForeign :: Foreign -> Effect Value
|
valueFromForeign :: Foreign -> Effect Value
|
||||||
valueFromForeign f = do
|
valueFromForeign f = do
|
||||||
let
|
let
|
||||||
file :: Maybe { filename :: Nullable String, buf :: Foreign, mime :: String }
|
file :: Maybe { filename :: String, buf :: Foreign, mime :: String }
|
||||||
file = hush $ runExcept $ readImpl f
|
file = hush $ runExcept $ readImpl f
|
||||||
string = hush $ runExcept $ unsafeReadTagged "String" f
|
string = hush $ runExcept $ unsafeReadTagged "String" f
|
||||||
|
|
||||||
@ -69,7 +69,7 @@ valueFromForeign f = do
|
|||||||
buf' :: ArrayBuffer
|
buf' :: ArrayBuffer
|
||||||
buf' = unsafeCoerce buf
|
buf' = unsafeCoerce buf
|
||||||
in
|
in
|
||||||
pure $ ValueFile (wrap <$> Nullable.toMaybe filename) buf' (MIME.fromString mime)
|
pure $ ValueFile (wrap filename) buf' (MIME.fromString mime)
|
||||||
Nothing -> do
|
Nothing -> do
|
||||||
s <- liftMaybe (error $ "invalid form value " <> unsafeStringify f) string
|
s <- liftMaybe (error $ "invalid form value " <> unsafeStringify f) string
|
||||||
pure $ ValueString s
|
pure $ ValueString s
|
||||||
@ -77,12 +77,21 @@ valueFromForeign f = do
|
|||||||
derive instance Generic Value _
|
derive instance Generic Value _
|
||||||
instance Show Value where
|
instance Show Value where
|
||||||
show (ValueString s) = "(ValueString " <> show s <> ")"
|
show (ValueString s) = "(ValueString " <> show s <> ")"
|
||||||
show (ValueFile filename _ mime) = "(ValueFile (" <> show filename <> ") <ArrayBuffer> (" <> show mime <> "))"
|
show (ValueFile filename buf mime) = "(ValueFile (" <> show filename <> ") (ArrayBuffer " <> unsafeShowArrayBuffer buf <> ") (" <> show mime <> "))"
|
||||||
|
|
||||||
|
instance Eq Value where
|
||||||
|
eq (ValueString a) (ValueString b) = a == b
|
||||||
|
eq (ValueFile namea bufa mimea) (ValueFile nameb bufb mimeb)
|
||||||
|
| namea /= nameb = false
|
||||||
|
| mimea /= mimeb = false
|
||||||
|
| otherwise = unsafeEqArrayBuffer bufa bufb
|
||||||
|
eq _ _ = false
|
||||||
|
|
||||||
newtype Form = Form (Map String (Array Value))
|
newtype Form = Form (Map String (Array Value))
|
||||||
|
|
||||||
derive instance Newtype Form _
|
derive instance Newtype Form _
|
||||||
derive newtype instance Show Form
|
derive newtype instance Show Form
|
||||||
|
derive newtype instance Eq Form
|
||||||
|
|
||||||
fromRaw :: forall m. MonadAff m => RawFormData -> m Form
|
fromRaw :: forall m. MonadAff m => RawFormData -> m Form
|
||||||
fromRaw f = do
|
fromRaw f = do
|
||||||
@ -101,3 +110,4 @@ toRawFormData =
|
|||||||
pure $ Object.insert k vs' o'
|
pure $ Object.insert k vs' o'
|
||||||
in
|
in
|
||||||
liftEffect <<< flip bind unsafeMakeFormData <<< foldlWithIndex collect (pure Object.empty) <<< unwrap
|
liftEffect <<< flip bind unsafeMakeFormData <<< foldlWithIndex collect (pure Object.empty) <<< unwrap
|
||||||
|
|
||||||
|
7
test/Test.Effect.Aff.HTTP.Form.js
Normal file
7
test/Test.Effect.Aff.HTTP.Form.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export const dummyForm = () => {
|
||||||
|
const form = new FormData()
|
||||||
|
form.set('foo', 'bar')
|
||||||
|
const hi = Buffer.from('hello, world!', 'utf8')
|
||||||
|
form.set('baz', new Blob([hi.buffer.slice(hi.byteOffset, hi.byteOffset + hi.byteLength)], {type: 'text/plain'}), 'foo.txt')
|
||||||
|
return form
|
||||||
|
}
|
32
test/Test.Effect.Aff.HTTP.Form.purs
Normal file
32
test/Test.Effect.Aff.HTTP.Form.purs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
module Test.Effect.Aff.HTTP.Form where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Data.MIME as MIME
|
||||||
|
import Data.Map as Map
|
||||||
|
import Data.Tuple.Nested ((/\))
|
||||||
|
import Effect (Effect)
|
||||||
|
import Effect.Aff.HTTP.Form (Form(..), RawFormData)
|
||||||
|
import Effect.Aff.HTTP.Form as Form
|
||||||
|
import Effect.Class (liftEffect)
|
||||||
|
import Node.Buffer as Buffer
|
||||||
|
import Node.Encoding (Encoding(..))
|
||||||
|
import Test.Spec (Spec, describe, it)
|
||||||
|
import Test.Spec.Assertions (shouldEqual)
|
||||||
|
|
||||||
|
foreign import dummyForm :: Effect RawFormData
|
||||||
|
|
||||||
|
spec :: Spec Unit
|
||||||
|
spec = describe "Form" do
|
||||||
|
it "fromRaw" do
|
||||||
|
dummy <- liftEffect dummyForm
|
||||||
|
f <- Form.fromRaw dummy
|
||||||
|
buf <- Buffer.fromString "hello, world!" UTF8 >>= Buffer.toArrayBuffer # liftEffect
|
||||||
|
f `shouldEqual` Form (Map.fromFoldable [ "foo" /\ [ Form.ValueString "bar" ], "baz" /\ [ Form.ValueFile (Form.Filename "foo.txt") buf MIME.Txt ] ])
|
||||||
|
it "toRaw" do
|
||||||
|
expect <- liftEffect dummyForm >>= Form.fromRaw
|
||||||
|
buf <- Buffer.fromString "hello, world!" UTF8 >>= Buffer.toArrayBuffer # liftEffect
|
||||||
|
let
|
||||||
|
f = Form (Map.fromFoldable [ "foo" /\ [ Form.ValueString "bar" ], "baz" /\ [ Form.ValueFile (Form.Filename "foo.txt") buf MIME.Txt ] ])
|
||||||
|
actual <- Form.toRawFormData f >>= Form.fromRaw
|
||||||
|
expect `shouldEqual` actual
|
12
test/Test.Main.purs
Normal file
12
test/Test.Main.purs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
module Test.Main where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Effect (Effect)
|
||||||
|
import Test.Spec.Reporter (specReporter)
|
||||||
|
import Test.Spec.Runner.Node (runSpecAndExitProcess)
|
||||||
|
import Test.Effect.Aff.HTTP.Form as Test.Form
|
||||||
|
|
||||||
|
main :: Effect Unit
|
||||||
|
main = runSpecAndExitProcess [ specReporter ] do
|
||||||
|
Test.Form.spec
|
Loading…
Reference in New Issue
Block a user