This commit is contained in:
Orion Kindel 2024-12-01 17:11:10 -06:00
parent e2018dbbef
commit 3c4f732f2c
Signed by untrusted user who does not match committer: orion
GPG Key ID: 6D4165AE4C928719
24 changed files with 820 additions and 121 deletions

8
.prettierrc.cjs Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
tabWidth: 2,
trailingComma: 'all',
singleQuote: true,
semi: false,
arrowParens: 'avoid',
plugins: [],
}

121
.spec-results Normal file
View File

@ -0,0 +1,121 @@
[
[
"Axon Request Parts Body extracts a JSON body",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts Body extracts a string body from a buffer",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts Body extracts a string body from a cached string",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts Body extracts a string body from a readable stream",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts Path ... but does if ends in IgnoreRest",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts Path does not partially match a route ...",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts Path extracts an int",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts Path extracts an int and a string",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts Path matches a route matching literal",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts Path matches a route matching multiple literals",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts extracts a string body",
{
"timestamp": "1733092879598.0",
"success": true
}
],
[
"Axon Request Parts extracts a string body from a buffer",
{
"timestamp": "1733093315868.0",
"success": true
}
],
[
"Axon Request Parts extracts a string body from a cached string",
{
"timestamp": "1733093315868.0",
"success": true
}
],
[
"Axon Request Parts extracts a string body from a readable stream",
{
"timestamp": "1733093315868.0",
"success": true
}
],
[
"Axon Request Parts extracts method, path, JSON body",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Axon Request Parts extracts the whole request",
{
"timestamp": "1733094319834.0",
"success": true
}
],
[
"Parts extracts the whole request",
{
"timestamp": "1733092816548.0",
"success": true
}
]
]

1
README.md Normal file
View File

@ -0,0 +1 @@
# axon

View File

@ -12,8 +12,5 @@
"noEmit": true, "noEmit": true,
"strict": true "strict": true
}, },
"include": [ "include": ["./scripts/*.js", "src/**/*.js"]
"./scripts/*.js",
"src/**/*.js"
]
} }

20
scripts/common.js Normal file
View File

@ -0,0 +1,20 @@
import Fs from 'fs/promises'
import Path from 'path'
export const rootDir = Path.resolve(__dirname, '..')
export const packageDirs = async () => ['./src']
export const packageSources = async () => {
const packages = await packageDirs()
const sources = []
for (const p of packages) {
const files = await Fs.readdir(p, { recursive: true, withFileTypes: true })
sources.push(
...files.flatMap(e =>
e.isFile() ? [Path.resolve(rootDir, e.path, e.name)] : [],
),
)
}
return sources
}

38
scripts/fmt.js Normal file
View File

@ -0,0 +1,38 @@
import { $ } from 'bun'
import { packageSources } from './common.js'
const check = process.argv.includes('--check')
const sources = await packageSources()
const purs = sources.filter(f => f.endsWith('.purs'))
const js = sources
.filter(f => f.endsWith('.js'))
.concat(['./scripts/**/*.js', '.prettierrc.cjs'])
const json = ['package.json', 'jsconfig.json']
const yml = sources.filter(f => f.endsWith('.yaml')).concat(['spago.yaml'])
/** @type {(parser: string, ps: string[]) => import("bun").ShellPromise} */
const prettier = (parser, ps) =>
$`bun x prettier ${check ? '--check' : '--write'} '--parser' ${parser} ${ps}`
const procs = [
() => prettier('babel', js),
() => prettier('json', json),
() => prettier('yaml', yml),
() =>
prettier(
'markdown',
sources.filter(f => f.endsWith('.md')).concat(['README.md']),
),
() => $`bun x purs-tidy ${check ? 'check' : 'format-in-place'} ${purs}`,
]
.map(go => async () => {
const p = await go().nothrow().quiet()
if (p.exitCode === 0) return
process.stdout.write(p.stdout)
process.stderr.write(p.stderr)
process.exit(1)
})
.reduce((acc, go) => acc.then(() => go()), Promise.resolve())
await procs

View File

@ -1,7 +1,7 @@
{ {
"workspace": { "workspace": {
"packages": { "packages": {
"tower": { "axon": {
"path": "./", "path": "./",
"core": { "core": {
"dependencies": [ "dependencies": [
@ -101,8 +101,84 @@
] ]
}, },
"test": { "test": {
"dependencies": [], "dependencies": [
"build_plan": [] "spec",
"spec-node"
],
"build_plan": [
"aff",
"ansi",
"argonaut-codecs",
"argonaut-core",
"arraybuffer-types",
"arrays",
"avar",
"bifunctors",
"catenable-lists",
"console",
"const",
"contravariant",
"control",
"datetime",
"distributive",
"effect",
"either",
"enums",
"exceptions",
"exists",
"exitcodes",
"foldable-traversable",
"foreign",
"foreign-object",
"fork",
"free",
"functions",
"functors",
"gen",
"identity",
"integers",
"invariant",
"js-date",
"lazy",
"lists",
"maybe",
"mmorph",
"newtype",
"node-buffer",
"node-event-emitter",
"node-fs",
"node-path",
"node-process",
"node-streams",
"nonempty",
"now",
"nullable",
"numbers",
"open-memoize",
"optparse",
"ordered-collections",
"orders",
"parallel",
"partial",
"pipes",
"posix-types",
"prelude",
"profunctor",
"record",
"refs",
"safe-coerce",
"spec",
"spec-node",
"st",
"strings",
"tailrec",
"transformers",
"tuples",
"type-equality",
"typelevel-prelude",
"unfoldable",
"unsafe-coerce"
]
} }
} }
}, },
@ -658,6 +734,16 @@
"foreign" "foreign"
] ]
}, },
"ansi": {
"type": "registry",
"version": "7.0.0",
"integrity": "sha256-ZMB6HD+q9CXvn9fRCmJ8dvuDrOVHcjombL3oNOerVnE=",
"dependencies": [
"foldable-traversable",
"lists",
"strings"
]
},
"argonaut-codecs": { "argonaut-codecs": {
"type": "registry", "type": "registry",
"version": "9.1.0", "version": "9.1.0",
@ -721,6 +807,19 @@
"unsafe-coerce" "unsafe-coerce"
] ]
}, },
"avar": {
"type": "registry",
"version": "5.0.0",
"integrity": "sha256-e7hf0x4hEpcygXP0LtvfvAQ49Bbj2aWtZT3gqM///0A=",
"dependencies": [
"aff",
"effect",
"either",
"exceptions",
"functions",
"maybe"
]
},
"b64": { "b64": {
"type": "registry", "type": "registry",
"version": "0.0.8", "version": "0.0.8",
@ -749,6 +848,20 @@
"tuples" "tuples"
] ]
}, },
"catenable-lists": {
"type": "registry",
"version": "7.0.0",
"integrity": "sha256-76vYENhwF4BWTBsjeLuErCH2jqVT4M3R1HX+4RwSftA=",
"dependencies": [
"control",
"foldable-traversable",
"lists",
"maybe",
"prelude",
"tuples",
"unfoldable"
]
},
"console": { "console": {
"type": "registry", "type": "registry",
"version": "6.1.0", "version": "6.1.0",
@ -892,6 +1005,14 @@
"unsafe-coerce" "unsafe-coerce"
] ]
}, },
"exitcodes": {
"type": "registry",
"version": "4.0.0",
"integrity": "sha256-4wxViTbyOoyKJ/WaRGI6+hZmgMKI5Miv16lSwefiLSM=",
"dependencies": [
"enums"
]
},
"ezfetch": { "ezfetch": {
"type": "registry", "type": "registry",
"version": "1.1.0", "version": "1.1.0",
@ -994,6 +1115,35 @@
"unfoldable" "unfoldable"
] ]
}, },
"fork": {
"type": "registry",
"version": "6.0.0",
"integrity": "sha256-X7u0SuCvFbLbzuNEKLBNuWjmcroqMqit4xEzpQwAP7E=",
"dependencies": [
"aff"
]
},
"free": {
"type": "registry",
"version": "7.1.0",
"integrity": "sha256-JAumgEsGSzJCNLD8AaFvuX7CpqS5yruCngi6yI7+V5k=",
"dependencies": [
"catenable-lists",
"control",
"distributive",
"either",
"exists",
"foldable-traversable",
"invariant",
"lazy",
"maybe",
"prelude",
"tailrec",
"transformers",
"tuples",
"unsafe-coerce"
]
},
"functions": { "functions": {
"type": "registry", "type": "registry",
"version": "6.0.0", "version": "6.0.0",
@ -1145,6 +1295,16 @@
"prelude" "prelude"
] ]
}, },
"mmorph": {
"type": "registry",
"version": "7.0.0",
"integrity": "sha256-urZlZNNqGeQFe5D/ClHlR8QgGBNHTMFPtJ5S5IpflTQ=",
"dependencies": [
"free",
"functors",
"transformers"
]
},
"newtype": { "newtype": {
"type": "registry", "type": "registry",
"version": "5.0.0", "version": "5.0.0",
@ -1232,6 +1392,22 @@
"effect" "effect"
] ]
}, },
"node-process": {
"type": "registry",
"version": "11.2.0",
"integrity": "sha256-+2MQDYChjGbVbapCyJtuWYwD41jk+BntF/kcOTKBMVs=",
"dependencies": [
"effect",
"foreign",
"foreign-object",
"maybe",
"node-event-emitter",
"node-streams",
"posix-types",
"prelude",
"unsafe-coerce"
]
},
"node-streams": { "node-streams": {
"type": "registry", "type": "registry",
"version": "9.0.0", "version": "9.0.0",
@ -1288,6 +1464,22 @@
"maybe" "maybe"
] ]
}, },
"open-memoize": {
"type": "registry",
"version": "6.2.0",
"integrity": "sha256-p1m7wF3aHQ80yUvqMs20OTMl496WS6YpKlmI2Nkg9j0=",
"dependencies": [
"either",
"integers",
"lazy",
"lists",
"maybe",
"partial",
"prelude",
"strings",
"tuples"
]
},
"options": { "options": {
"type": "registry", "type": "registry",
"version": "7.0.0", "version": "7.0.0",
@ -1300,6 +1492,43 @@
"tuples" "tuples"
] ]
}, },
"optparse": {
"type": "registry",
"version": "5.0.1",
"integrity": "sha256-cEzEkNW4q0gZlXl4z0zn+H2vs6l2UAp7NPHCsois73k=",
"dependencies": [
"aff",
"arrays",
"bifunctors",
"console",
"control",
"effect",
"either",
"enums",
"exists",
"exitcodes",
"foldable-traversable",
"free",
"gen",
"integers",
"lazy",
"lists",
"maybe",
"newtype",
"node-buffer",
"node-process",
"node-streams",
"nonempty",
"numbers",
"open-memoize",
"partial",
"prelude",
"strings",
"tailrec",
"transformers",
"tuples"
]
},
"ordered-collections": { "ordered-collections": {
"type": "registry", "type": "registry",
"version": "3.2.0", "version": "3.2.0",
@ -1351,6 +1580,29 @@
"integrity": "sha256-fwXerld6Xw1VkReh8yeQsdtLVrjfGiVuC5bA1Wyo/J4=", "integrity": "sha256-fwXerld6Xw1VkReh8yeQsdtLVrjfGiVuC5bA1Wyo/J4=",
"dependencies": [] "dependencies": []
}, },
"pipes": {
"type": "registry",
"version": "8.0.0",
"integrity": "sha256-kvfqGM4cPA/wCcBHbp5psouFw5dZGvku2462x7ZBwSY=",
"dependencies": [
"aff",
"lists",
"mmorph",
"prelude",
"tailrec",
"transformers",
"tuples"
]
},
"posix-types": {
"type": "registry",
"version": "6.0.0",
"integrity": "sha256-ZfFz8RR1lee/o/Prccyeut3Q+9tYd08mlR72sIh6GzA=",
"dependencies": [
"maybe",
"prelude"
]
},
"prelude": { "prelude": {
"type": "registry", "type": "registry",
"version": "6.0.1", "version": "6.0.1",
@ -1415,6 +1667,72 @@
"variant" "variant"
] ]
}, },
"spec": {
"type": "registry",
"version": "8.1.1",
"integrity": "sha256-EM7UfQIaSgiw13LJ4ZASkfYmmRDKIlec3nYbGKFqGhk=",
"dependencies": [
"aff",
"ansi",
"arrays",
"avar",
"bifunctors",
"control",
"datetime",
"effect",
"either",
"exceptions",
"foldable-traversable",
"fork",
"identity",
"integers",
"lists",
"maybe",
"newtype",
"now",
"ordered-collections",
"parallel",
"pipes",
"prelude",
"refs",
"strings",
"tailrec",
"transformers",
"tuples"
]
},
"spec-node": {
"type": "registry",
"version": "0.0.3",
"integrity": "sha256-Bjzg6l4uOfMN/FV0SKuT1Mm8eMP9sloLGVcY/0MeMnI=",
"dependencies": [
"aff",
"argonaut-codecs",
"argonaut-core",
"arrays",
"control",
"datetime",
"effect",
"either",
"foldable-traversable",
"identity",
"integers",
"maybe",
"newtype",
"node-buffer",
"node-fs",
"node-process",
"now",
"numbers",
"optparse",
"ordered-collections",
"partial",
"prelude",
"spec",
"strings",
"tuples"
]
},
"st": { "st": {
"type": "registry", "type": "registry",
"version": "6.2.0", "version": "6.2.0",

View File

@ -19,7 +19,9 @@ package:
- web-streams - web-streams
test: test:
main: Test.Main main: Test.Main
dependencies: [] dependencies:
- spec
- spec-node
workspace: workspace:
packageSet: packageSet:
registry: 61.2.0 registry: 61.2.0

View File

@ -8,9 +8,11 @@ import Data.Show.Generic (genericShow)
import Data.String as String import Data.String as String
data Method = GET | POST | PUT | PATCH | DELETE | OPTIONS | TRACE | CONNECT data Method = GET | POST | PUT | PATCH | DELETE | OPTIONS | TRACE | CONNECT
derive instance Generic Method _ derive instance Generic Method _
derive instance Eq Method derive instance Eq Method
instance Show Method where show = genericShow instance Show Method where
show = genericShow
methodToString :: Method -> String methodToString :: Method -> String
methodToString GET = "GET" methodToString GET = "GET"

View File

@ -7,6 +7,7 @@ import Data.Newtype (class Newtype)
import Node.Stream as Stream import Node.Stream as Stream
newtype Json a = Json a newtype Json a = Json a
derive instance Generic (Json a) _ derive instance Generic (Json a) _
derive instance Newtype (Json a) _ derive instance Newtype (Json a) _
derive newtype instance (Eq a) => Eq (Json a) derive newtype instance (Eq a) => Eq (Json a)
@ -14,5 +15,6 @@ derive newtype instance (Ord a) => Ord (Json a)
derive newtype instance (Show a) => Show (Json a) derive newtype instance (Show a) => Show (Json a)
newtype Stream = Stream (Stream.Readable ()) newtype Stream = Stream (Stream.Readable ())
derive instance Generic Stream _ derive instance Generic Stream _
derive instance Newtype Stream _ derive instance Newtype Stream _

View File

@ -2,6 +2,18 @@ module Axon.Request.Parts.Class (class RequestParts, extractRequestParts, module
import Prelude import Prelude
import Axon.Request (Request)
import Axon.Request as Request
import Axon.Request.Method (Method)
import Axon.Request.Method as Method
import Axon.Request.Parts.Body (Json(..), Stream(..))
import Axon.Request.Parts.Body (Json(..), Stream(..)) as Parts.Body
import Axon.Request.Parts.Method (Connect, Delete, Get, Options, Patch, Post, Put, Trace)
import Axon.Request.Parts.Method (Get(..), Post(..), Put(..), Patch(..), Delete(..), Trace(..), Options(..), Connect(..)) as Parts.Method
import Axon.Request.Parts.Path (Path(..)) as Path.Parts
import Axon.Request.Parts.Path (class DiscardTupledUnits, class PathParts, Path(..), discardTupledUnits, extractPathParts)
import Axon.Response (Response)
import Axon.Response as Response
import Control.Alternative (guard) import Control.Alternative (guard)
import Control.Monad.Except (ExceptT(..), runExceptT) import Control.Monad.Except (ExceptT(..), runExceptT)
import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT) import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT)
@ -17,18 +29,6 @@ import Data.URL as URL
import Effect.Aff (Aff) import Effect.Aff (Aff)
import Effect.Class (liftEffect) import Effect.Class (liftEffect)
import Node.Buffer (Buffer) import Node.Buffer (Buffer)
import Axon.Request (Request)
import Axon.Request as Request
import Axon.Request.Method (Method)
import Axon.Request.Method as Method
import Axon.Request.Parts.Body (Json(..), Stream(..))
import Axon.Request.Parts.Body (Json(..), Stream(..)) as Parts.Body
import Axon.Request.Parts.Method (Connect, Delete, Get, Options, Patch, Post, Put, Trace)
import Axon.Request.Parts.Method (Get(..), Post(..), Put(..), Patch(..), Delete(..), Trace(..), Options(..), Connect(..)) as Parts.Method
import Axon.Request.Parts.Path (Path(..)) as Path.Parts
import Axon.Request.Parts.Path (class PathParts, Path(..), extractPathParts)
import Axon.Response (Response)
import Axon.Response as Response
extractMethod :: forall @t a. RequestParts a => Newtype t a => Method -> Request -> Aff (Either Response (Maybe t)) extractMethod :: forall @t a. RequestParts a => Newtype t a => Method -> Request -> Aff (Either Response (Maybe t))
extractMethod method r = extractMethod method r =
@ -54,13 +54,19 @@ instance RequestParts Request where
instance RequestParts String where instance RequestParts String where
extractRequestParts r = extractRequestParts r =
Request.bodyString r Request.bodyString r
<#> lmap (const $ Response.fromStatus 500) <#> lmap (const $ Response.fromStatus 500)
# ExceptT # ExceptT
# lift # lift
# runMaybeT # runMaybeT
# runExceptT # runExceptT
instance PathParts a b => RequestParts (Path a b) where instance RequestParts (Either Request.BodyStringError String) where
extractRequestParts r =
Request.bodyString r
<#> Just
<#> Right
instance (PathParts a b, DiscardTupledUnits b c) => RequestParts (Path a c) where
extractRequestParts r = extractRequestParts r =
let let
segments = Request.url r # URL.path # case _ of segments = Request.url r # URL.path # case _ of
@ -75,6 +81,7 @@ instance PathParts a b => RequestParts (Path a b) where
# Right # Right
# MaybeT # MaybeT
>>= ensureConsumed >>= ensureConsumed
<#> discardTupledUnits
<#> Path <#> Path
# runMaybeT # runMaybeT
# pure # pure

View File

@ -6,6 +6,7 @@ import Data.Generic.Rep (class Generic)
import Data.Newtype (class Newtype) import Data.Newtype (class Newtype)
newtype Get a = Get a newtype Get a = Get a
derive instance Generic (Get a) _ derive instance Generic (Get a) _
derive instance Newtype (Get a) _ derive instance Newtype (Get a) _
derive newtype instance (Eq a) => Eq (Get a) derive newtype instance (Eq a) => Eq (Get a)
@ -13,6 +14,7 @@ derive newtype instance (Ord a) => Ord (Get a)
derive newtype instance (Show a) => Show (Get a) derive newtype instance (Show a) => Show (Get a)
newtype Post a = Post a newtype Post a = Post a
derive instance Generic (Post a) _ derive instance Generic (Post a) _
derive instance Newtype (Post a) _ derive instance Newtype (Post a) _
derive newtype instance (Eq a) => Eq (Post a) derive newtype instance (Eq a) => Eq (Post a)
@ -20,6 +22,7 @@ derive newtype instance (Ord a) => Ord (Post a)
derive newtype instance (Show a) => Show (Post a) derive newtype instance (Show a) => Show (Post a)
newtype Put a = Put a newtype Put a = Put a
derive instance Generic (Put a) _ derive instance Generic (Put a) _
derive instance Newtype (Put a) _ derive instance Newtype (Put a) _
derive newtype instance (Eq a) => Eq (Put a) derive newtype instance (Eq a) => Eq (Put a)
@ -27,6 +30,7 @@ derive newtype instance (Ord a) => Ord (Put a)
derive newtype instance (Show a) => Show (Put a) derive newtype instance (Show a) => Show (Put a)
newtype Patch a = Patch a newtype Patch a = Patch a
derive instance Generic (Patch a) _ derive instance Generic (Patch a) _
derive instance Newtype (Patch a) _ derive instance Newtype (Patch a) _
derive newtype instance (Eq a) => Eq (Patch a) derive newtype instance (Eq a) => Eq (Patch a)
@ -34,6 +38,7 @@ derive newtype instance (Ord a) => Ord (Patch a)
derive newtype instance (Show a) => Show (Patch a) derive newtype instance (Show a) => Show (Patch a)
newtype Delete a = Delete a newtype Delete a = Delete a
derive instance Generic (Delete a) _ derive instance Generic (Delete a) _
derive instance Newtype (Delete a) _ derive instance Newtype (Delete a) _
derive newtype instance (Eq a) => Eq (Delete a) derive newtype instance (Eq a) => Eq (Delete a)
@ -41,6 +46,7 @@ derive newtype instance (Ord a) => Ord (Delete a)
derive newtype instance (Show a) => Show (Delete a) derive newtype instance (Show a) => Show (Delete a)
newtype Options a = Options a newtype Options a = Options a
derive instance Generic (Options a) _ derive instance Generic (Options a) _
derive instance Newtype (Options a) _ derive instance Newtype (Options a) _
derive newtype instance (Eq a) => Eq (Options a) derive newtype instance (Eq a) => Eq (Options a)
@ -48,6 +54,7 @@ derive newtype instance (Ord a) => Ord (Options a)
derive newtype instance (Show a) => Show (Options a) derive newtype instance (Show a) => Show (Options a)
newtype Trace a = Trace a newtype Trace a = Trace a
derive instance Generic (Trace a) _ derive instance Generic (Trace a) _
derive instance Newtype (Trace a) _ derive instance Newtype (Trace a) _
derive newtype instance (Eq a) => Eq (Trace a) derive newtype instance (Eq a) => Eq (Trace a)
@ -55,6 +62,7 @@ derive newtype instance (Ord a) => Ord (Trace a)
derive newtype instance (Show a) => Show (Trace a) derive newtype instance (Show a) => Show (Trace a)
newtype Connect a = Connect a newtype Connect a = Connect a
derive instance Generic (Connect a) _ derive instance Generic (Connect a) _
derive instance Newtype (Connect a) _ derive instance Newtype (Connect a) _
derive newtype instance (Eq a) => Eq (Connect a) derive newtype instance (Eq a) => Eq (Connect a)

View File

@ -4,26 +4,46 @@ import Prelude
import Control.Alternative (guard) import Control.Alternative (guard)
import Data.Array as Array import Data.Array as Array
import Data.Generic.Rep (class Generic)
import Data.Int as Int import Data.Int as Int
import Data.Maybe (Maybe(..), fromMaybe) import Data.Maybe (Maybe(..), fromMaybe)
import Data.Show.Generic (genericShow)
import Data.Symbol (class IsSymbol, reflectSymbol) import Data.Symbol (class IsSymbol, reflectSymbol)
import Data.Tuple.Nested (type (/\), (/\)) import Data.Tuple.Nested (type (/\), (/\))
import Data.URL (URL) import Data.URL (URL)
import Type.Prelude (Proxy(..)) import Type.Prelude (Proxy(..))
newtype Path :: Type -> Type -> Type data Path :: forall k. k -> Type -> Type
newtype Path a b = Path b data Path a b = Path b
data Sep :: Type -> Type -> Type derive instance Generic (Path a b) _
derive instance Eq b => Eq (Path a b)
instance Show b => Show (Path a b) where
show = genericShow
data Sep :: forall ka kb. ka -> kb -> Type
data Sep a b data Sep a b
data IgnoreRest :: Type data IgnoreRest :: Type
data IgnoreRest data IgnoreRest
infixl 9 type Sep as / infixr 9 type Sep as /
infixl 9 type IgnoreRest as ... infixl 9 type IgnoreRest as ...
class PathParts :: forall a. a -> Type -> Constraint class DiscardTupledUnits :: Type -> Type -> Constraint
class DiscardTupledUnits a b | a -> b where
discardTupledUnits :: a -> b
instance (DiscardTupledUnits a b) => DiscardTupledUnits (Unit /\ a) b where
discardTupledUnits (_ /\ a) = discardTupledUnits a
else instance (DiscardTupledUnits a b) => DiscardTupledUnits (a /\ Unit) b where
discardTupledUnits (a /\ _) = discardTupledUnits a
else instance (DiscardTupledUnits aa ab, DiscardTupledUnits ba bb) => DiscardTupledUnits (aa /\ ba) (ab /\ bb) where
discardTupledUnits (a /\ b) = discardTupledUnits a /\ discardTupledUnits b
else instance DiscardTupledUnits a a where
discardTupledUnits = identity
class PathParts :: forall k. k -> Type -> Constraint
class PathParts a b | a -> b where class PathParts a b | a -> b where
extractPathParts :: URL -> Array String -> Maybe (Array String /\ b) extractPathParts :: URL -> Array String -> Maybe (Array String /\ b)
@ -32,7 +52,7 @@ instance (PathParts aa ab, PathParts ba bb) => PathParts (aa / ba) (ab /\ bb) wh
segments' /\ ab <- extractPathParts @aa u segments segments' /\ ab <- extractPathParts @aa u segments
segments'' /\ bb <- extractPathParts @ba u segments' segments'' /\ bb <- extractPathParts @ba u segments'
pure $ segments'' /\ ab /\ bb pure $ segments'' /\ ab /\ bb
else instance PathParts (...) Unit where else instance PathParts IgnoreRest Unit where
extractPathParts _ _ = Just $ [] /\ unit extractPathParts _ _ = Just $ [] /\ unit
else instance PathParts String String where else instance PathParts String String where
extractPathParts _ segments = do extractPathParts _ segments = do

View File

@ -1,7 +1,8 @@
module Axon.Request (Request, BodyReadableError(..), BodyStringError(..), BodyJSONError(..), BodyBufferError(..), bodyReadable, bodyString, bodyJSON, bodyBuffer, headers, method, address, url, contentType, accept, contentLength, lookupHeader) where module Axon.Request (Request, Body(..), BodyReadableError(..), BodyStringError(..), BodyJSONError(..), BodyBufferError(..), bodyReadable, bodyString, bodyJSON, bodyBuffer, headers, method, address, url, contentType, accept, contentLength, lookupHeader, make) where
import Prelude import Prelude
import Axon.Request.Method (Method)
import Control.Monad.Error.Class (throwError, try) import Control.Monad.Error.Class (throwError, try)
import Control.Monad.Except (ExceptT(..), runExceptT) import Control.Monad.Except (ExceptT(..), runExceptT)
import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Class (lift)
@ -9,14 +10,15 @@ import Data.Argonaut.Core (Json)
import Data.Argonaut.Core (stringify) as JSON import Data.Argonaut.Core (stringify) as JSON
import Data.Argonaut.Parser (jsonParser) as JSON import Data.Argonaut.Parser (jsonParser) as JSON
import Data.Bifunctor (lmap) import Data.Bifunctor (lmap)
import Data.Either (Either, note) import Data.Either (Either)
import Data.FoldableWithIndex (foldlWithIndex)
import Data.Generic.Rep (class Generic) import Data.Generic.Rep (class Generic)
import Data.Int as Int import Data.Int as Int
import Data.MIME (MIME) import Data.MIME (MIME)
import Data.MIME as 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.Show.Generic (genericShow) import Data.Show.Generic (genericShow)
import Data.String.Lower (StringLower) import Data.String.Lower (StringLower)
import Data.String.Lower as String.Lower import Data.String.Lower as String.Lower
@ -35,7 +37,6 @@ import Node.Encoding (Encoding(..))
import Node.Net.Types (IPv4, IPv6, SocketAddress) import Node.Net.Types (IPv4, IPv6, SocketAddress)
import Node.Stream as Stream import Node.Stream as Stream
import Node.Stream.Aff as Stream.Aff import Node.Stream.Aff as Stream.Aff
import Axon.Request.Method (Method)
data BodyReadableError data BodyReadableError
= BodyReadableErrorHasBeenConsumed = BodyReadableErrorHasBeenConsumed
@ -55,6 +56,7 @@ instance Eq BodyBufferError where
eq (BodyBufferErrorReadable a) (BodyBufferErrorReadable b) = a == b eq (BodyBufferErrorReadable a) (BodyBufferErrorReadable b) = a == b
eq (BodyBufferErrorReading a) (BodyBufferErrorReading b) = Error.message a == Error.message b eq (BodyBufferErrorReading a) (BodyBufferErrorReading b) = Error.message a == Error.message b
eq _ _ = false eq _ _ = false
instance Show BodyBufferError where instance Show BodyBufferError where
show = genericShow show = genericShow
@ -93,6 +95,18 @@ data Request =
, bodyRef :: Effect.Ref Body , bodyRef :: Effect.Ref Body
} }
make
:: { headers :: Map String String
, address :: Either (SocketAddress IPv4) (SocketAddress IPv6)
, url :: URL
, method :: Method
, body :: Body
}
-> Effect Request
make a = do
bodyRef <- Ref.new a.body
pure $ Request { bodyRef: bodyRef, headers: foldlWithIndex (\k m v -> Map.insert (String.Lower.fromString k) v m) Map.empty a.headers, address: a.address, url: a.url, method: a.method }
headers :: Request -> Map StringLower String headers :: Request -> Map StringLower String
headers (Request a) = a.headers headers (Request a) = a.headers
@ -118,7 +132,7 @@ url :: Request -> URL
url (Request a) = a.url url (Request a) = a.url
bodyReadable :: Request -> Effect (Either BodyReadableError (Stream.Readable ())) bodyReadable :: Request -> Effect (Either BodyReadableError (Stream.Readable ()))
bodyReadable (Request {bodyRef}) = runExceptT do bodyReadable (Request { bodyRef }) = runExceptT do
body <- liftEffect $ Ref.read bodyRef body <- liftEffect $ Ref.read bodyRef
case body of case body of
BodyEmpty -> throwError BodyReadableErrorEmpty BodyEmpty -> throwError BodyReadableErrorEmpty
@ -130,38 +144,38 @@ bodyReadable (Request {bodyRef}) = runExceptT do
BodyCachedJSON json -> json # JSON.stringify # flip Buffer.fromString UTF8 >>= Stream.readableFromBuffer # lift BodyCachedJSON json -> json # JSON.stringify # flip Buffer.fromString UTF8 >>= Stream.readableFromBuffer # lift
bodyBuffer :: Request -> Aff (Either BodyBufferError Buffer) bodyBuffer :: Request -> Aff (Either BodyBufferError Buffer)
bodyBuffer r@(Request {bodyRef}) = bodyBuffer r@(Request { bodyRef }) =
let let
stream = stream =
bodyReadable r bodyReadable r
# liftEffect # liftEffect
<#> lmap BodyBufferErrorReadable <#> lmap BodyBufferErrorReadable
# ExceptT # ExceptT
readAll s = readAll s =
Stream.Aff.readAll s Stream.Aff.readAll s
# liftAff # liftAff
# try # try
<#> lmap BodyBufferErrorReading <#> lmap BodyBufferErrorReading
# ExceptT # ExceptT
>>= (liftEffect <<< Buffer.concat) >>= (liftEffect <<< Buffer.concat)
in in
runExceptT do runExceptT do
body <- Ref.read bodyRef # liftEffect body <- Ref.read bodyRef # liftEffect
case body of case body of
BodyCached buf -> pure buf BodyCached buf -> pure buf
BodyCachedString str -> Buffer.fromString str UTF8 # liftEffect BodyCachedString str -> Buffer.fromString str UTF8 # liftEffect
BodyCachedJSON json -> Buffer.fromString (JSON.stringify json) UTF8 # liftEffect BodyCachedJSON json -> Buffer.fromString (JSON.stringify json) UTF8 # liftEffect
_ -> do _ -> do
buf <- stream >>= readAll buf <- stream >>= readAll
Ref.write (BodyCached buf) bodyRef $> buf # liftEffect Ref.write (BodyCached buf) bodyRef $> buf # liftEffect
bodyString :: Request -> Aff (Either BodyStringError String) bodyString :: Request -> Aff (Either BodyStringError String)
bodyString r@(Request {bodyRef}) = bodyString r@(Request { bodyRef }) =
let let
buf = buf =
bodyBuffer r bodyBuffer r
<#> lmap BodyStringErrorBuffer <#> lmap BodyStringErrorBuffer
# ExceptT # ExceptT
bufString b = bufString b =
Buffer.toString UTF8 b Buffer.toString UTF8 b
# liftEffect # liftEffect
@ -169,32 +183,32 @@ bodyString r@(Request {bodyRef}) =
<#> lmap (const BodyStringErrorNotUTF8) <#> lmap (const BodyStringErrorNotUTF8)
# ExceptT # ExceptT
in in
runExceptT do runExceptT do
body <- Ref.read bodyRef # liftEffect body <- Ref.read bodyRef # liftEffect
case body of case body of
BodyCachedString str -> pure str BodyCachedString str -> pure str
BodyCachedJSON json -> JSON.stringify json # pure BodyCachedJSON json -> JSON.stringify json # pure
_ -> do _ -> do
str <- buf >>= bufString str <- buf >>= bufString
Ref.write (BodyCachedString str) bodyRef $> str # liftEffect Ref.write (BodyCachedString str) bodyRef $> str # liftEffect
bodyJSON :: Request -> Aff (Either BodyJSONError Json) bodyJSON :: Request -> Aff (Either BodyJSONError Json)
bodyJSON r@(Request {bodyRef}) = bodyJSON r@(Request { bodyRef }) =
let let
str = str =
bodyString r bodyString r
<#> lmap BodyJSONErrorString <#> lmap BodyJSONErrorString
# ExceptT # ExceptT
parse s = parse s =
JSON.jsonParser s JSON.jsonParser s
# lmap BodyJSONErrorParsing # lmap BodyJSONErrorParsing
# pure # pure
# ExceptT # ExceptT
in in
runExceptT do runExceptT do
body <- Ref.read bodyRef # liftEffect body <- Ref.read bodyRef # liftEffect
case body of case body of
BodyCachedJSON j -> pure j BodyCachedJSON j -> pure j
_ -> do _ -> do
j <- str >>= parse j <- str >>= parse
Ref.write (BodyCachedJSON j) bodyRef $> j # liftEffect Ref.write (BodyCachedJSON j) bodyRef $> j # liftEffect

View File

@ -16,6 +16,13 @@ data Body
| BodyFormData HTTP.RawFormData | BodyFormData HTTP.RawFormData
| BodyReadable (Stream.Readable ()) | BodyReadable (Stream.Readable ())
instance Show Body where
show BodyEmpty = "BodyEmpty"
show (BodyString s) = "BodyString " <> show s
show (BodyBuffer _) = "BodyBuffer _"
show (BodyFormData _) = "BodyFormData _"
show (BodyReadable _) = "BodyReadable _"
formBody :: HTTP.Form -> Effect Body formBody :: HTTP.Form -> Effect Body
formBody f = HTTP.Form.toRawFormData f <#> BodyFormData formBody f = HTTP.Form.toRawFormData f <#> BodyFormData

View File

@ -1,19 +1,25 @@
module Axon.Response (Response, response, body, status, headers, withHeader, withBody, withStatus, fromStatus, ok, module Body) where module Axon.Response (Response(..), response, body, status, headers, withHeader, withBody, withStatus, fromStatus, ok, module Body) where
import Prelude import Prelude
import Data.FoldableWithIndex (foldlWithIndex)
import Data.Map (Map)
import Data.Map as Map
import Data.String.Lower (StringLower)
import Data.String.Lower as String.Lower
import Axon.Response.Body (Body(..)) import Axon.Response.Body (Body(..))
import Axon.Response.Body (Body(..), formBody) as Body import Axon.Response.Body (Body(..), formBody) as Body
import Data.FoldableWithIndex (foldlWithIndex)
import Data.Generic.Rep (class Generic)
import Data.Map (Map)
import Data.Map as Map
import Data.Show.Generic (genericShow)
import Data.String.Lower (StringLower)
import Data.String.Lower as String.Lower
data Response = Response {body :: Body, headers :: Map StringLower String, status :: Int} data Response = Response { body :: Body, headers :: Map StringLower String, status :: Int }
derive instance Generic Response _
instance Show Response where
show = genericShow
response :: Int -> Body -> Map String String -> Response response :: Int -> Body -> Map String String -> Response
response s b h = Response {status: s, body: b, headers: h # foldlWithIndex (\k m v -> Map.insert (String.Lower.fromString k) v m) Map.empty} response s b h = Response { status: s, body: b, headers: h # foldlWithIndex (\k m v -> Map.insert (String.Lower.fromString k) v m) Map.empty }
status :: Response -> Int status :: Response -> Int
status (Response a) = a.status status (Response a) = a.status
@ -25,16 +31,16 @@ headers :: Response -> Map StringLower String
headers (Response a) = a.headers headers (Response a) = a.headers
withHeader :: String -> String -> Response -> Response withHeader :: String -> String -> Response -> Response
withHeader k v (Response a) = Response $ a {headers = Map.insert (String.Lower.fromString k) v a.headers} withHeader k v (Response a) = Response $ a { headers = Map.insert (String.Lower.fromString k) v a.headers }
withStatus :: Int -> Response -> Response withStatus :: Int -> Response -> Response
withStatus s (Response a) = Response $ a {status = s} withStatus s (Response a) = Response $ a { status = s }
withBody :: Body -> Response -> Response withBody :: Body -> Response -> Response
withBody b (Response a) = Response $ a {body = b} withBody b (Response a) = Response $ a { body = b }
fromStatus :: Int -> Response fromStatus :: Int -> Response
fromStatus s = Response {body: BodyEmpty, headers: Map.empty, status: s} fromStatus s = Response { body: BodyEmpty, headers: Map.empty, status: s }
ok :: Response ok :: Response
ok = fromStatus 200 ok = fromStatus 200

View File

@ -2,4 +2,8 @@
/// <reference lib="dom.iterable" /> /// <reference lib="dom.iterable" />
/** @type {(_: {tuple: <A, B>(a: A) => (b: B) => unknown}) => (h: Headers) => () => Array<unknown>} */ /** @type {(_: {tuple: <A, B>(a: A) => (b: B) => unknown}) => (h: Headers) => () => Array<unknown>} */
export const headerEntries = ({tuple}) => hs => () => Array.from(hs.entries()).map(([a, b]) => tuple(a)(b)) export const headerEntries =
({ tuple }) =>
hs =>
() =>
Array.from(hs.entries()).map(([a, b]) => tuple(a)(b))

View File

@ -4,4 +4,4 @@ import Data.Tuple.Nested (type (/\))
import Effect (Effect) import Effect (Effect)
foreign import data WebHeaders :: Type foreign import data WebHeaders :: Type
foreign import headerEntries :: {tuple :: forall a b. a -> b -> a /\ b} -> WebHeaders -> Effect (Array (String /\ String)) foreign import headerEntries :: { tuple :: forall a b. a -> b -> a /\ b } -> WebHeaders -> Effect (Array (String /\ String))

View File

@ -20,15 +20,15 @@ export const headers = r => () => r.headers
/** @type {(r: ReadableStream<Uint8Array>) => () => Stream.Readable} */ /** @type {(r: ReadableStream<Uint8Array>) => () => Stream.Readable} */
export const readableFromWeb = r => () => { export const readableFromWeb = r => () => {
const reader = r.getReader(); const reader = r.getReader()
return new Stream.Readable({ return new Stream.Readable({
read: function() { read: function () {
(async () => { ;(async () => {
/** @type {ReadableStreamReadResult<Uint8Array> | undefined} */ /** @type {ReadableStreamReadResult<Uint8Array> | undefined} */
let res = undefined; let res = undefined
try { try {
res = await reader.read() res = await reader.read()
} catch(e) { } catch (e) {
if (typeof e === 'undefined' || e instanceof Error) { if (typeof e === 'undefined' || e instanceof Error) {
this.destroy(e) this.destroy(e)
return return
@ -37,8 +37,8 @@ export const readableFromWeb = r => () => {
} }
} }
if (res.value) this.push(res.value); if (res.value) this.push(res.value)
if (res.done) this.push(null); if (res.done) this.push(null)
})() })()
}, },
}) })

View File

@ -1,10 +1,10 @@
module Axon.Web.Request where module Axon.Web.Request where
import Data.ArrayBuffer.Types (Uint8Array) import Data.ArrayBuffer.Types (Uint8Array)
import Axon.Web.Headers (WebHeaders)
import Data.Nullable (Nullable) import Data.Nullable (Nullable)
import Effect (Effect) import Effect (Effect)
import Node.Stream as Stream import Node.Stream as Stream
import Axon.Request.Web (WebHeaders)
import Web.Streams.ReadableStream (ReadableStream) import Web.Streams.ReadableStream (ReadableStream)
foreign import data WebRequest :: Type foreign import data WebRequest :: Type

View File

@ -1,10 +0,0 @@
module Main where
import Prelude
import Effect (Effect)
import Effect.Console (log)
main :: Effect Unit
main = do
log "🍝"

View File

@ -0,0 +1,123 @@
module Test.Axon.Request.Parts where
import Prelude
import Axon.Request (Request)
import Axon.Request as Request
import Axon.Request.Method (Method(..))
import Axon.Request.Parts.Class (Json(..), Patch(..), Path(..), Post(..), extractRequestParts)
import Axon.Request.Parts.Path (type (...), type (/), IgnoreRest)
import Control.Monad.Error.Class (liftEither, liftMaybe)
import Data.Bifunctor (lmap)
import Data.Either (Either(..))
import Data.Map as Map
import Data.Maybe (Maybe(..), fromJust)
import Data.Tuple.Nested (type (/\), (/\))
import Data.URL as URL
import Effect.Class (liftEffect)
import Effect.Exception (error)
import Effect.Unsafe (unsafePerformEffect)
import Node.Buffer as Buffer
import Node.Encoding (Encoding(..))
import Node.Net.SocketAddress as SocketAddress
import Node.Stream as Stream
import Partial.Unsafe (unsafePartial)
import Test.Spec (Spec, describe, it)
import Test.Spec.Assertions (shouldEqual)
spec :: Spec Unit
spec = describe "Parts" do
it "extracts the whole request" do
req <- liftEffect $ Request.make {body: Request.BodyEmpty, url: URL.fromString "http://localhost:80/foo" # unsafePartial fromJust, headers: Map.empty, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}, method: GET}
void $ extractRequestParts @Request req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
it "extracts method, path, JSON body" do
stream <- Buffer.fromString """{"firstName": "henry"}""" UTF8 >>= Stream.readableFromBuffer # liftEffect
req <- liftEffect $ Request.make {body: Request.BodyReadable stream, url: URL.fromString "http://localhost:80/users/12" # unsafePartial fromJust, headers: Map.empty, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}, method: PATCH}
a <- extractRequestParts @(Patch ((Path ("users" / Int) _) /\ Json {firstName :: String})) req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a `shouldEqual` Patch (Path 12 /\ Json {firstName: "henry"})
describe "Path" do
it "matches a route matching literal" do
req <- liftEffect $ Request.make {body: Request.BodyCachedString "foo", url: URL.fromString "http://localhost:80/foo" # unsafePartial fromJust, headers: Map.empty, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}, method: GET}
a <- extractRequestParts @(Path "foo" _) req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a `shouldEqual` (Path unit)
it "matches a route matching multiple literals" do
req <- liftEffect $ Request.make {body: Request.BodyCachedString "foo", url: URL.fromString "http://localhost:80/foo/bar/baz" # unsafePartial fromJust, headers: Map.empty, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}, method: GET}
a <- extractRequestParts @(Path ("foo" / "bar" / "baz") _) req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a `shouldEqual` (Path unit)
it "does not partially match a route ..." do
req <- liftEffect $ Request.make
{ body: Request.BodyCachedString "foo"
, url: URL.fromString "http://localhost:80/foo/bar/baz" # unsafePartial fromJust
, headers: Map.empty
, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}
, method: GET
}
a <- extractRequestParts @(Path ("foo" / "bar") _) req <#> lmap (error <<< show) >>= liftEither
a `shouldEqual` Nothing
it "... but does if ends in IgnoreRest" do
req <- liftEffect $ Request.make
{ body: Request.BodyCachedString "foo"
, url: URL.fromString "http://localhost:80/foo/bar/baz" # unsafePartial fromJust
, headers: Map.empty
, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}
, method: GET
}
a <- extractRequestParts @(Path ("foo" / "bar" / IgnoreRest) _) req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a `shouldEqual` (Path unit)
it "extracts an int" do
req <- liftEffect $ Request.make
{ body: Request.BodyCachedString "foo"
, url: URL.fromString "http://localhost:80/foo/123/bar" # unsafePartial fromJust
, headers: Map.empty
, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}
, method: GET
}
a <- extractRequestParts @(Path ("foo" / Int / "bar") _) req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a `shouldEqual` (Path 123)
it "extracts an int and a string" do
req <- liftEffect $ Request.make
{ body: Request.BodyCachedString "foo"
, url: URL.fromString "http://localhost:80/foo/123/bar/baz" # unsafePartial fromJust
, headers: Map.empty
, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}
, method: GET
}
a <- extractRequestParts @(Path ("foo" / Int / "bar" / String) _) req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a `shouldEqual` (Path $ 123 /\ "baz")
describe "Body" do
it "extracts a string body from a cached string" do
req <- liftEffect $ Request.make {body: Request.BodyCachedString "foo", url: URL.fromString "http://localhost:80/foo" # unsafePartial fromJust, headers: Map.empty, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}, method: GET}
a <- extractRequestParts @(Either Request.BodyStringError String) req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a `shouldEqual` (Right "foo")
it "extracts a string body from a readable stream" do
stream <- Buffer.fromString "foo" UTF8 >>= Stream.readableFromBuffer # liftEffect
req <- liftEffect $ Request.make {body: Request.BodyReadable stream, url: URL.fromString "http://localhost:80/foo" # unsafePartial fromJust, headers: Map.empty, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}, method: GET}
a <- extractRequestParts @(Either Request.BodyStringError String) req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a `shouldEqual` (Right "foo")
a' <- extractRequestParts @String req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a' `shouldEqual` "foo"
it "extracts a string body from a buffer" do
buf <- Buffer.fromString "foo" UTF8 # liftEffect
req <- liftEffect $ Request.make {body: Request.BodyCached buf, url: URL.fromString "http://localhost:80/foo" # unsafePartial fromJust, headers: Map.empty, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}, method: GET}
a <- extractRequestParts @(Either Request.BodyStringError String) req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a `shouldEqual` (Right "foo")
a' <- extractRequestParts @String req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a' `shouldEqual` "foo"
it "extracts a JSON body" do
stream <- Buffer.fromString """{"foo": 123, "bar": "abc"}""" UTF8 >>= Stream.readableFromBuffer # liftEffect
req <- liftEffect $ Request.make {body: Request.BodyReadable stream, url: URL.fromString "http://localhost:80/foo" # unsafePartial fromJust, headers: Map.empty, address: Left $ unsafePerformEffect $ SocketAddress.newIpv4 {address: "127.0.0.1", port: 81}, method: POST}
a <- extractRequestParts @(Post (Json {foo :: Int, bar :: String})) req <#> lmap (error <<< show) >>= liftEither >>= liftMaybe (error "was nothing")
a `shouldEqual` Post (Json {foo: 123, bar: "abc"})

View File

@ -0,0 +1,10 @@
module Test.Axon.Request where
import Prelude
import Test.Axon.Request.Parts as Parts
import Test.Spec (Spec, describe)
spec :: Spec Unit
spec = describe "Request" do
Parts.spec

View File

@ -3,10 +3,11 @@ module Test.Main where
import Prelude import Prelude
import Effect (Effect) import Effect (Effect)
import Effect.Class.Console (log) import Test.Axon.Request as Test.Request
import Test.Spec (describe)
import Test.Spec.Reporter (specReporter)
import Test.Spec.Runner.Node (runSpecAndExitProcess)
main :: Effect Unit main :: Effect Unit
main = do main = runSpecAndExitProcess [ specReporter ] $ describe "Axon" do
log "🍕" Test.Request.spec
log "You should add some tests."