git.iko.soy/rowtype-yoga/purescript-yoga-http-api
on feat/custom-type-errors help sign in
refactor: Remove Request wrapper for cleaner syntax

Removed the Request newtype wrapper that was adding unnecessary noise to route definitions. Routes now use bare records directly. Before: Route PUT ("users" / "id" : Int) (Request { headers :: { authorization :: BearerToken } , body :: JSON UpdateUserRequest }) (ok :: { body :: User }) After: Route PUT ("users" / "id" : Int) { headers :: { authorization :: BearerToken } , body :: JSON UpdateUserRequest } (ok :: { body :: User }) Changes: - Removed Request data type from Handler.purs - Updated all typeclass instances to work with bare records - Updated all test files (9 test files + 7 compile-fail tests) - Removed unused Unsafe.Coerce import All 168 tests passing, 0 warnings.

commit7cce927b9fa56b7421cef64856efb9690cc62009 authorMark <mark.eibes@gmail.com> dateSun 8 Feb 2026, 09:23 (8 months ago) parentc02d9d1
20 files changed+92−111
src/Yoga/HTTP/API/Route.purs+1−1 src/Yoga/HTTP/API/Route/Handler.purs+6−21 src/Yoga/HTTP/API/Route/Route.purs+5−6 src/Yoga/HTTP/API/Route/RouteHandler.purs+1−2 test-compile-fail/cases/BodyTypeMismatch.purs+2−2 test-compile-fail/cases/DuplicateCaptureNames.purs+2−2 test-compile-fail/cases/HandlerSignatureMismatch.purs+2−2 test-compile-fail/cases/InvalidResponseRecordStructure.purs+2−2 test-compile-fail/cases/MissingRequiredQueryParam.purs+2−2 test-compile-fail/cases/SchemaNameCollision.purs+3−3 test-compile-fail/cases/WrongResponseVariantLabel.purs+2−2 test/Test/APIRecordTest.purs+4−4 test/Test/BearerAuthTest.purs+8−8 test/Test/ComprehensiveExample.purs+13−15 test/Test/ContentTypeTest.purs+16−16 test/Test/CookieAuthTest.purs+4−4 test/Test/ExamplesTest.purs+6−6 test/Test/OpenAPIInfoTest.purs+2−2 test/Test/SchemaComponentTest.purs+4−4 test/Test/VariantResponseTest.purs+7−7
src/Yoga/HTTP/API/Route.purs+1−1
@@ -19,7 +19,7 @@ import Yoga.HTTP.API.Path (Path, Root, Lit, Capture, PathCons, Param, QueryParam
1919import Yoga.HTTP.API.Route.Auth (BearerToken(..), BasicAuth(..), ApiKeyHeader(..), ApiKeyCookie(..), DigestAuth(..))
2020import Yoga.HTTP.API.Route.BearerToken (BearerToken(..))
2121import Yoga.HTTP.API.Route.Encoding (JSON, FormData, MultipartFormData, PlainText, XML, CustomContentType, NoBody)
22−import Yoga.HTTP.API.Route.Handler (HandlerFn, Request)
22+import Yoga.HTTP.API.Route.Handler (HandlerFn)
2323import Yoga.HTTP.API.Route.HeaderError (HeaderError(..))
2424import Yoga.HTTP.API.Route.HeaderValue (class HeaderValue, class HeaderValueType, headerValueType, parseHeader, printHeader)
2525import Yoga.HTTP.API.Route.Method (DELETE, GET, PATCH, POST, PUT)
src/Yoga/HTTP/API/Route/Handler.purs+6−21
@@ -1,6 +1,5 @@
11module Yoga.HTTP.API.Route.Handler
22 ( HandlerFn
3− , Request(..)
43 , class DefaultRequestFields
54 , class DefaultRequestFieldsRL
65 , class SegmentPathParams
@@ -22,20 +21,6 @@ import Prim.RowList as RL
2221import Yoga.HTTP.API.Path (Path, PathCons, Capture, Param, QueryParams, Required)
2322import Yoga.HTTP.API.Route.Encoding (JSON, NoBody)
2423
25−--------------------------------------------------------------------------------
26−-- Request Data Type
27−--------------------------------------------------------------------------------
28−
29−-- | Request data combining headers and body
30−-- |
31−-- | Usage:
32−-- | Request { body :: JSON User } -- body only
33−-- | Request { headers :: { auth :: String } } -- headers only
34−-- | Request { body :: JSON User, headers :: { auth :: String } } -- both
35−-- | Request {} -- neither
36−data Request :: Type -> Type
37−data Request r = Request r
38−
3924--------------------------------------------------------------------------------
4025-- Handler Type
4126--------------------------------------------------------------------------------
@@ -162,7 +147,7 @@ instance encodingBodyNoBody :: EncodingBody NoBody Unit
162147-- RequestHeaders: Extract headers row from a request type
163148--------------------------------------------------------------------------------
164149
165−-- | Extract the headers row from a Request type.
150+-- | Extract the headers row from a request record type.
166151-- |
167152-- | The request is expected to have a `headers` field.
168153class RequestHeaders (request :: Type) (headers :: Row Type) | request -> headers
@@ -170,13 +155,13 @@ class RequestHeaders (request :: Type) (headers :: Row Type) | request -> header
170155instance requestHeadersRequest ::
171156 ( Row.Cons "headers" (Record headers) _rest requestRow
172157 ) =>
173− RequestHeaders (Request (Record requestRow)) headers
158+ RequestHeaders (Record requestRow) headers
174159
175160--------------------------------------------------------------------------------
176161-- RequestCookies: Extract cookies row from a request type
177162--------------------------------------------------------------------------------
178163
179−-- | Extract the cookies row from a Request type.
164+-- | Extract the cookies row from a request record type.
180165-- |
181166-- | The request is expected to have a `cookies` field.
182167class RequestCookies (request :: Type) (cookies :: Row Type) | request -> cookies
@@ -184,13 +169,13 @@ class RequestCookies (request :: Type) (cookies :: Row Type) | request -> cookie
184169instance requestCookiesRequest ::
185170 ( Row.Cons "cookies" (Record cookies) _rest requestRow
186171 ) =>
187− RequestCookies (Request (Record requestRow)) cookies
172+ RequestCookies (Record requestRow) cookies
188173
189174--------------------------------------------------------------------------------
190175-- RequestBody: Extract body encoding from a request type
191176--------------------------------------------------------------------------------
192177
193−-- | Extract the body encoding type from a Request type.
178+-- | Extract the body encoding type from a request record type.
194179-- |
195180-- | The request is expected to have a `body` field.
196181class RequestBody (request :: Type) (encoding :: Type) | request -> encoding
@@ -198,7 +183,7 @@ class RequestBody (request :: Type) (encoding :: Type) | request -> encoding
198183instance requestBodyRequest ::
199184 ( Row.Cons "body" encoding _rest requestRow
200185 ) =>
201− RequestBody (Request (Record requestRow)) encoding
186+ RequestBody (Record requestRow) encoding
202187
203188--------------------------------------------------------------------------------
204189-- DefaultRequestFields: Compute defaults for missing request fields
src/Yoga/HTTP/API/Route/Route.purs+5−6
@@ -12,9 +12,8 @@ import Prim.Row (class Cons, class Lacks, class Union)
1212import Prim.RowList (class RowToList, RowList)
1313import Prim.RowList as RL
1414import Type.Proxy (Proxy(..))
15−import Unsafe.Coerce (unsafeCoerce)
1615import Yoga.HTTP.API.Path (class PathPattern, pathPattern)
17−import Yoga.HTTP.API.Route.Handler (Request, class DefaultRequestFields, class SegmentPathParams, class SegmentQueryParams)
16+import Yoga.HTTP.API.Route.Handler (class DefaultRequestFields, class SegmentPathParams, class SegmentQueryParams)
1817import Yoga.HTTP.API.Route.OpenAPI (class CollectOperations, class RenderHeadersSchema, renderHeadersSchema, class RenderCookieParamsSchema, renderCookieParamsSchema, class RenderPathParamsSchema, renderPathParamsSchema, class RenderQueryParamsSchema, renderQueryParamsSchema, class RenderRequestBodySchema, renderRequestBodySchema, class RenderVariantResponseSchemaRL, renderVariantResponseSchemaRL, class DetectSecurity, detectSecurity, class DetectCookieSecurity, detectCookieSecurity, class ToOpenAPI, toOpenAPIImpl, class CollectSchemas, collectSchemas, class CollectVariantSchemasRL, collectVariantSchemasRL, class CollectRouteSchemas, class CollectSchemaNames, class CollectVariantSchemaNames, class CollectRouteSchemaNames)
1918import Yoga.HTTP.API.Route.OpenAPIMetadata (class HasOperationMetadata, operationMetadata)
2019import Yoga.HTTP.API.Route.RenderMethod (class RenderMethod, renderMethod)
@@ -67,9 +66,9 @@ instance
6766 , RenderRequestBodySchema encoding
6867 , RowToList userResp rl
6968 , RenderVariantResponseSchemaRL rl
70− , HasOperationMetadata (Route method segments (Request (Record partialRequest)) userResp)
69+ , HasOperationMetadata (Route method segments (Record partialRequest) userResp)
7170 ) =>
72− ToOpenAPI (Route method segments (Request (Record partialRequest)) userResp) where
71+ ToOpenAPI (Route method segments (Record partialRequest) userResp) where
7372 toOpenAPIImpl proxy =
7473 let
7574 methodStr = renderMethod (Proxy :: Proxy method)
@@ -122,7 +121,7 @@ instance
122121 , RowToList userResp rl
123122 , CollectVariantSchemasRL rl
124123 ) =>
125− CollectRouteSchemas (Route method segments (Request (Record partialRequest)) userResp) where
124+ CollectRouteSchemas (Route method segments (Record partialRequest) userResp) where
126125 collectRouteSchemas _ =
127126 let
128127 requestSchemas = collectSchemas (Proxy :: Proxy encoding)
@@ -138,4 +137,4 @@ instance
138137 , CollectVariantSchemaNames rl respNames
139138 , Union reqNames respNames names
140139 ) =>
141− CollectRouteSchemaNames (Route method segments (Request (Record partialRequest)) userResp) names
140+ CollectRouteSchemaNames (Route method segments (Record partialRequest) userResp) names
src/Yoga/HTTP/API/Route/RouteHandler.purs+1−2
@@ -14,7 +14,6 @@ import Prim.RowList (class RowToList)
1414import Unsafe.Coerce (unsafeCoerce)
1515import Yoga.HTTP.API.Route.Handler
1616 ( HandlerFn
17− , Request
1817 , class DefaultRequestFields
1918 , class EncodingBody
2019 , class SegmentPathParams
@@ -42,7 +41,7 @@ instance
4241 , ConvertResponseVariant userResp respVariant
4342 ) =>
4443 RouteHandler
45− (Route method segments (Request (Record partialRequest)) userResp)
44+ (Route method segments (Record partialRequest) userResp)
4645 pathParams
4746 queryParams
4847 fullHeaders
test-compile-fail/cases/BodyTypeMismatch.purs+2−2
@@ -5,14 +5,14 @@ import Prelude
55import Data.Variant (Variant)
66import Effect.Aff (Aff)
77import Yoga.HTTP.API.Path (Path, Lit)
8−import Yoga.HTTP.API.Route.Handler (Request)
8+import Yoga.HTTP.API.Route.Handler
99import Yoga.HTTP.API.Route.Method (GET)
1010import Yoga.HTTP.API.Route.Response (Response, respondNoHeaders)
1111import Yoga.HTTP.API.Route.Route (Route)
1212import Yoga.HTTP.API.Route.RouteHandler (Handler, mkHandler)
1313
1414-- Route has no body (defaults to NoBody/Unit), but handler uses body as String
15−type MyRoute = Route GET (Path (Lit "health")) (Request {}) (ok :: { body :: String })
15+type MyRoute = Route GET (Path (Lit "health")) {} (ok :: { body :: String })
1616
1717test :: Handler MyRoute
1818test = mkHandler \{ body } ->
test-compile-fail/cases/DuplicateCaptureNames.purs+2−2
@@ -7,14 +7,14 @@ import Data.Variant (Variant)
77import Effect.Aff (Aff)
88import Yoga.HTTP.API.Path (Path, Lit, Capture, type (/), type (:))
99import Yoga.HTTP.API.Route.Encoding (NoBody)
10−import Yoga.HTTP.API.Route.Handler (Request)
10+import Yoga.HTTP.API.Route.Handler
1111import Yoga.HTTP.API.Route.Method (GET)
1212import Yoga.HTTP.API.Route.Response (Response, respondNoHeaders)
1313import Yoga.HTTP.API.Route.Route (Route)
1414import Yoga.HTTP.API.Route.RouteHandler (Handler, mkHandler)
1515
1616-- Two captures both named "id" with different types
17−type MyRoute = Route GET (Path (Capture "id" Int / Capture "id" String)) (Request {}) (ok :: { body :: String })
17+type MyRoute = Route GET (Path (Capture "id" Int / Capture "id" String)) {} (ok :: { body :: String })
1818
1919test :: Handler MyRoute
2020test = mkHandler \{ path: { id } } ->
test-compile-fail/cases/HandlerSignatureMismatch.purs+2−2
@@ -7,13 +7,13 @@ import Data.Variant (Variant)
77import Effect.Aff (Aff)
88import Yoga.HTTP.API.Path (Path, Lit, Capture, type (/), type (:))
99import Yoga.HTTP.API.Route.Encoding (NoBody)
10−import Yoga.HTTP.API.Route.Handler (Request)
10+import Yoga.HTTP.API.Route.Handler
1111import Yoga.HTTP.API.Route.Method (GET)
1212import Yoga.HTTP.API.Route.Response (Response, respondNoHeaders)
1313import Yoga.HTTP.API.Route.Route (Route)
1414import Yoga.HTTP.API.Route.RouteHandler (Handler, mkHandler)
1515
16−type MyRoute = Route GET (Path (Lit "users" / Capture "id" Int)) (Request {}) (ok :: { body :: String })
16+type MyRoute = Route GET (Path (Lit "users" / Capture "id" Int)) {} (ok :: { body :: String })
1717
1818-- Handler takes { id :: String } but route has Capture "id" Int
1919test :: Handler MyRoute
test-compile-fail/cases/InvalidResponseRecordStructure.purs+2−2
@@ -3,14 +3,14 @@ module Test.CompileFail.InvalidResponseRecordStructure where
33
44import Prelude
55import Yoga.HTTP.API.Path (Path, Lit)
6−import Yoga.HTTP.API.Route.Handler (Request)
6+import Yoga.HTTP.API.Route.Handler
77import Yoga.HTTP.API.Route.Method (GET)
88import Yoga.HTTP.API.Route.Route (Route)
99import Yoga.HTTP.API.Route.RouteHandler (Handler, mkHandler)
1010import Yoga.HTTP.API.Route.Response (respondNoHeaders)
1111
1212-- Response record has "status" field which is not "body" or "headers"
13−type MyRoute = Route GET (Path (Lit "health")) (Request {}) (ok :: { body :: String, status :: Int })
13+type MyRoute = Route GET (Path (Lit "health")) {} (ok :: { body :: String, status :: Int })
1414
1515test :: Handler MyRoute
1616test = mkHandler \_ ->
test-compile-fail/cases/MissingRequiredQueryParam.purs+2−2
@@ -5,7 +5,7 @@ import Prelude
55import Data.Variant (Variant)
66import Effect.Aff (Aff)
77import Yoga.HTTP.API.Path (Path, Lit, QueryParams, type (:?))
8−import Yoga.HTTP.API.Route.Handler (Request)
8+import Yoga.HTTP.API.Route.Handler
99import Yoga.HTTP.API.Route.Method (GET)
1010import Yoga.HTTP.API.Route.Response (Response, respondNoHeaders)
1111import Yoga.HTTP.API.Route.Route (Route)
@@ -13,7 +13,7 @@ import Yoga.HTTP.API.Route.RouteHandler (Handler, mkHandler)
1313
1414-- Query param "limit" is optional (no Required wrapper), so handler gets Maybe Int
1515-- But handler tries to use it as plain Int
16−type MyRoute = Route GET (Path (Lit "items") :? (limit :: Int)) (Request {}) (ok :: { body :: String })
16+type MyRoute = Route GET (Path (Lit "items") :? (limit :: Int)) {} (ok :: { body :: String })
1717
1818test :: Handler MyRoute
1919test = mkHandler \{ query: { limit } } ->
test-compile-fail/cases/SchemaNameCollision.purs+3−3
@@ -4,7 +4,7 @@ module Test.CompileFail.SchemaNameCollision where
44import Data.Maybe (Maybe(..))
55import Yoga.HTTP.API.Path (Path, Lit)
66import Yoga.HTTP.API.Route.Encoding (JSON)
7−import Yoga.HTTP.API.Route.Handler (Request)
7+import Yoga.HTTP.API.Route.Handler
88import Yoga.HTTP.API.Route.Method (GET, POST)
99import Yoga.HTTP.API.Route.OpenAPIMetadata (Schema)
1010import Yoga.HTTP.API.Route.OpenAPI (buildOpenAPISpec, OpenAPISpec)
@@ -14,8 +14,8 @@ type UserV1 = { id :: Int, name :: String }
1414type UserV2 = { id :: Int, email :: String, age :: Int }
1515
1616type CollisionAPI =
17− { getUser :: Route GET (Path (Lit "users")) (Request {}) (ok :: { body :: Schema "User" UserV1 })
18− , createUser :: Route POST (Path (Lit "users")) (Request { body :: JSON (Schema "User" UserV2) }) (created :: { body :: Schema "User" UserV2 })
17+ { getUser :: Route GET (Path (Lit "users")) {} (ok :: { body :: Schema "User" UserV1 })
18+ , createUser :: Route POST (Path (Lit "users")) { body :: JSON (Schema "User" UserV2) } (created :: { body :: Schema "User" UserV2 })
1919 }
2020
2121spec :: OpenAPISpec
test-compile-fail/cases/WrongResponseVariantLabel.purs+2−2
@@ -5,14 +5,14 @@ import Prelude
55import Data.Variant (Variant)
66import Effect.Aff (Aff)
77import Yoga.HTTP.API.Path (Path, Lit)
8−import Yoga.HTTP.API.Route.Handler (Request)
8+import Yoga.HTTP.API.Route.Handler
99import Yoga.HTTP.API.Route.Method (GET)
1010import Yoga.HTTP.API.Route.Response (Response, respondNoHeaders)
1111import Yoga.HTTP.API.Route.Route (Route)
1212import Yoga.HTTP.API.Route.RouteHandler (Handler, mkHandler)
1313
1414-- Route only has "ok" response, no "notFound"
15−type MyRoute = Route GET (Path (Lit "health")) (Request {}) (ok :: { body :: String })
15+type MyRoute = Route GET (Path (Lit "health")) {} (ok :: { body :: String })
1616
1717test :: Handler MyRoute
1818test = mkHandler \_ ->
test/Test/APIRecordTest.purs+4−4
@@ -9,7 +9,7 @@ import Effect (Effect)
99import Effect.Aff (Aff)
1010import Type.Proxy (Proxy(..))
1111import Yoga.HTTP.API.Path (type (/), type (:))
12−import Yoga.HTTP.API.Route (GET, POST, Route, Request, JSON, respondNoHeaders, buildOpenAPISpec, collectOperations, mkHandler, apiHandlers)
12+import Yoga.HTTP.API.Route (GET, POST, Route, JSON, respondNoHeaders, buildOpenAPISpec, collectOperations, mkHandler, apiHandlers)
1313import Yoga.JSON (writeJSON)
1414import ViTest (ViTest, describe, test)
1515import ViTest.Expect (expectToBe)
@@ -24,9 +24,9 @@ expectToEqual expected actual = expectToBe true (expected == actual)
2424type User = { id :: Int, name :: String }
2525
2626type TestAPI =
27− { getUser :: Route GET ("users" / "id" : Int) (Request {}) (ok :: { body :: User })
28− , listUsers :: Route GET "users" (Request {}) (ok :: { body :: Array User })
29− , createUser :: Route POST "users" (Request { body :: JSON User }) (created :: { body :: User })
27+ { getUser :: Route GET ("users" / "id" : Int) {} (ok :: { body :: User })
28+ , listUsers :: Route GET "users" {} (ok :: { body :: Array User })
29+ , createUser :: Route POST "users" { body :: JSON User } (created :: { body :: User })
3030 }
3131
3232--------------------------------------------------------------------------------
test/Test/BearerAuthTest.purs+8−8
@@ -6,7 +6,7 @@ import Data.Array as Array
66import Data.Maybe (Maybe(..))
77import Data.String as String
88import Effect (Effect)
9−import Yoga.HTTP.API.Route (GET, Route, Request, buildOpenAPISpec)
9+import Yoga.HTTP.API.Route (GET, Route, buildOpenAPISpec)
1010import Yoga.HTTP.API.Route.Auth (BearerToken, BasicAuth, ApiKeyHeader, DigestAuth)
1111import Yoga.JSON (writeJSON)
1212import Test.OpenAPIValidation (validate)
@@ -18,25 +18,25 @@ import ViTest.Expect (expectToBe)
1818--------------------------------------------------------------------------------
1919
2020type BearerAuthAPI =
21− { getProtected :: Route GET "protected" (Request { headers :: { authorization :: BearerToken } }) (ok :: { body :: { message :: String } })
21+ { getProtected :: Route GET "protected" { headers :: { authorization :: BearerToken } } (ok :: { body :: { message :: String } })
2222 }
2323
2424type BasicAuthAPI =
25− { getProtected :: Route GET "protected" (Request { headers :: { authorization :: BasicAuth } }) (ok :: { body :: { message :: String } })
25+ { getProtected :: Route GET "protected" { headers :: { authorization :: BasicAuth } } (ok :: { body :: { message :: String } })
2626 }
2727
2828type ApiKeyAPI =
29− { getProtected :: Route GET "protected" (Request { headers :: { apiKey :: ApiKeyHeader } }) (ok :: { body :: { message :: String } })
29+ { getProtected :: Route GET "protected" { headers :: { apiKey :: ApiKeyHeader } } (ok :: { body :: { message :: String } })
3030 }
3131
3232type DigestAuthAPI =
33− { getProtected :: Route GET "protected" (Request { headers :: { authorization :: DigestAuth } }) (ok :: { body :: { message :: String } })
33+ { getProtected :: Route GET "protected" { headers :: { authorization :: DigestAuth } } (ok :: { body :: { message :: String } })
3434 }
3535
3636type MultiAuthAPI =
37− { bearerEndpoint :: Route GET "bearer" (Request { headers :: { authorization :: BearerToken } }) (ok :: { body :: { message :: String } })
38− , basicEndpoint :: Route GET "basic" (Request { headers :: { authorization :: BasicAuth } }) (ok :: { body :: { message :: String } })
39− , apiKeyEndpoint :: Route GET "apikey" (Request { headers :: { xApiKey :: ApiKeyHeader } }) (ok :: { body :: { message :: String } })
37+ { bearerEndpoint :: Route GET "bearer" { headers :: { authorization :: BearerToken } } (ok :: { body :: { message :: String } })
38+ , basicEndpoint :: Route GET "basic" { headers :: { authorization :: BasicAuth } } (ok :: { body :: { message :: String } })
39+ , apiKeyEndpoint :: Route GET "apikey" { headers :: { xApiKey :: ApiKeyHeader } } (ok :: { body :: { message :: String } })
4040 }
4141
4242--------------------------------------------------------------------------------
test/Test/ComprehensiveExample.purs+13−15
@@ -7,7 +7,7 @@ import Data.Maybe (Maybe(..))
77import Effect (Effect)
88import Type.Function (type (#))
99import Yoga.HTTP.API.Path (type (:), type (/), type (:?))
10−import Yoga.HTTP.API.Route (GET, POST, PUT, DELETE, Route, Request, buildOpenAPISpec)
10+import Yoga.HTTP.API.Route (GET, POST, PUT, DELETE, Route, buildOpenAPISpec)
1111import Yoga.HTTP.API.Route.Auth (BearerToken, ApiKeyHeader, BasicAuth)
1212import Yoga.HTTP.API.Route.Encoding (JSON, PlainText, XML)
1313import Yoga.HTTP.API.Route.OpenAPIMetadata (Description, Example, ExampleValue, Examples, Schema)
@@ -21,65 +21,63 @@ type ComprehensiveAPI =
2121 { -- Simple GET with path parameter and authentication
2222 getUser ::
2323 Route GET ("users" / "id" : Int)
24− (Request { headers :: { authorization :: BearerToken } })
24+ { headers :: { authorization :: BearerToken } }
2525 (ok :: { body :: Schema "User" User })
2626
2727 , -- List users with query parameters
2828 listUsers ::
2929 Route GET ("users" :? { limit :: Int # Example "10", offset :: Int # Example "0" })
30− (Request {})
30+ {}
3131 (ok :: { body :: Array User })
3232
3333 , -- POST with JSON body and schema
3434 createUser ::
3535 Route POST "users"
36− (Request { body :: JSON (Schema "CreateUserRequest" CreateUserRequest) })
36+ { body :: JSON (Schema "CreateUserRequest" CreateUserRequest) }
3737 (created :: { body :: Schema "User" User })
3838
3939 , -- PUT with authentication and JSON body
4040 updateUser ::
4141 Route PUT ("users" / "id" : Int)
42− ( Request
43− { headers :: { authorization :: BearerToken }
44− , body :: JSON UpdateUserRequest
45− }
46− )
42+ { headers :: { authorization :: BearerToken }
43+ , body :: JSON UpdateUserRequest
44+ }
4745 (ok :: { body :: User })
4846
4947 , -- DELETE with authentication
5048 deleteUser ::
5149 Route DELETE ("users" / "id" : Int)
52− (Request { headers :: { authorization :: BearerToken } })
50+ { headers :: { authorization :: BearerToken } }
5351 (noContent :: { body :: {} })
5452
5553 , -- Multiple content types - XML
5654 exportUser ::
5755 Route GET ("users" / "id" : Int / "export")
58− (Request {})
56+ {}
5957 (ok :: { body :: XML User })
6058
6159 , -- Plain text response
6260 healthCheck ::
6361 Route GET "health"
64− (Request {})
62+ {}
6563 (ok :: { body :: PlainText String })
6664
6765 , -- API Key authentication
6866 getStats ::
6967 Route GET "stats"
70− (Request { headers :: { xApiKey :: ApiKeyHeader } })
68+ { headers :: { xApiKey :: ApiKeyHeader } }
7169 (ok :: { body :: Stats })
7270
7371 , -- Basic auth
7472 login ::
7573 Route POST "login"
76− (Request { headers :: { authorization :: BasicAuth } })
74+ { headers :: { authorization :: BasicAuth } }
7775 (ok :: { body :: { token :: String # Description "JWT access token" } })
7876
7977 , -- Path with examples
8078 getUserPosts ::
8179 Route GET ("users" / "userId" : (Int # Examples (alice :: ExampleValue "1", bob :: ExampleValue "2")) / "posts")
82− (Request {})
80+ {}
8381 (ok :: { body :: Array Post })
8482 }
8583
test/Test/ContentTypeTest.purs+16−16
@@ -5,7 +5,7 @@ import Prelude
55import Data.Maybe (Maybe(..))
66import Data.String as String
77import Effect (Effect)
8−import Yoga.HTTP.API.Route (GET, POST, PUT, Route, Request, buildOpenAPISpec)
8+import Yoga.HTTP.API.Route (GET, POST, PUT, Route, buildOpenAPISpec)
99import Yoga.HTTP.API.Route.Encoding (JSON, FormData, MultipartFormData, PlainText, XML, CustomContentType)
1010import Yoga.JSON (writeJSON)
1111import ViTest (ViTest, describe, test)
@@ -21,7 +21,7 @@ testRequestContentTypes :: Effect ViTest
2121testRequestContentTypes = describe "Request Content Types" do
2222 _ <- test "JSON request body generates application/json" do
2323 let
24− spec = buildOpenAPISpec @{ createUser :: Route POST "users" (Request { body :: JSON User }) (ok :: { body :: User }) }
24+ spec = buildOpenAPISpec @{ createUser :: Route POST "users" { body :: JSON User } (ok :: { body :: User }) }
2525 { title: "API"
2626 , version: "1.0.0"
2727 , description: Nothing
@@ -34,7 +34,7 @@ testRequestContentTypes = describe "Request Content Types" do
3434
3535 _ <- test "FormData request body generates application/x-www-form-urlencoded" do
3636 let
37− spec = buildOpenAPISpec @{ createUser :: Route POST "users" (Request { body :: FormData User }) (ok :: { body :: JSON User }) }
37+ spec = buildOpenAPISpec @{ createUser :: Route POST "users" { body :: FormData User } (ok :: { body :: JSON User }) }
3838 { title: "API"
3939 , version: "1.0.0"
4040 , description: Nothing
@@ -47,7 +47,7 @@ testRequestContentTypes = describe "Request Content Types" do
4747
4848 _ <- test "MultipartFormData request body generates multipart/form-data" do
4949 let
50− spec = buildOpenAPISpec @{ uploadFile :: Route POST "upload" (Request { body :: MultipartFormData FileUpload }) (ok :: { body :: { success :: Boolean } }) }
50+ spec = buildOpenAPISpec @{ uploadFile :: Route POST "upload" { body :: MultipartFormData FileUpload } (ok :: { body :: { success :: Boolean } }) }
5151 { title: "API"
5252 , version: "1.0.0"
5353 , description: Nothing
@@ -59,7 +59,7 @@ testRequestContentTypes = describe "Request Content Types" do
5959
6060 _ <- test "PlainText request body generates text/plain" do
6161 let
62− spec = buildOpenAPISpec @{ echo :: Route POST "echo" (Request { body :: PlainText String }) (ok :: { body :: PlainText String }) }
62+ spec = buildOpenAPISpec @{ echo :: Route POST "echo" { body :: PlainText String } (ok :: { body :: PlainText String }) }
6363 { title: "API"
6464 , version: "1.0.0"
6565 , description: Nothing
@@ -71,7 +71,7 @@ testRequestContentTypes = describe "Request Content Types" do
7171
7272 _ <- test "XML request body generates application/xml" do
7373 let
74− spec = buildOpenAPISpec @{ updateDoc :: Route PUT "document" (Request { body :: XML XmlDocument }) (ok :: { body :: XML XmlDocument }) }
74+ spec = buildOpenAPISpec @{ updateDoc :: Route PUT "document" { body :: XML XmlDocument } (ok :: { body :: XML XmlDocument }) }
7575 { title: "API"
7676 , version: "1.0.0"
7777 , description: Nothing
@@ -83,7 +83,7 @@ testRequestContentTypes = describe "Request Content Types" do
8383
8484 test "CustomContentType request body generates custom MIME type" do
8585 let
86− spec = buildOpenAPISpec @{ customEndpoint :: Route POST "custom" (Request { body :: CustomContentType "application/vnd.api+json" User }) (ok :: { body :: User }) }
86+ spec = buildOpenAPISpec @{ customEndpoint :: Route POST "custom" { body :: CustomContentType "application/vnd.api+json" User } (ok :: { body :: User }) }
8787 { title: "API"
8888 , version: "1.0.0"
8989 , description: Nothing
@@ -97,7 +97,7 @@ testResponseContentTypes :: Effect ViTest
9797testResponseContentTypes = describe "Response Content Types" do
9898 _ <- test "JSON response body generates application/json" do
9999 let
100− spec = buildOpenAPISpec @{ getUser :: Route GET "users" (Request {}) (ok :: { body :: JSON User }) }
100+ spec = buildOpenAPISpec @{ getUser :: Route GET "users" {} (ok :: { body :: JSON User }) }
101101 { title: "API"
102102 , version: "1.0.0"
103103 , description: Nothing
@@ -109,7 +109,7 @@ testResponseContentTypes = describe "Response Content Types" do
109109
110110 _ <- test "PlainText response body generates text/plain" do
111111 let
112− spec = buildOpenAPISpec @{ getText :: Route GET "text" (Request {}) (ok :: { body :: PlainText String }) }
112+ spec = buildOpenAPISpec @{ getText :: Route GET "text" {} (ok :: { body :: PlainText String }) }
113113 { title: "API"
114114 , version: "1.0.0"
115115 , description: Nothing
@@ -121,7 +121,7 @@ testResponseContentTypes = describe "Response Content Types" do
121121
122122 _ <- test "XML response body generates application/xml" do
123123 let
124− spec = buildOpenAPISpec @{ getDoc :: Route GET "document" (Request {}) (ok :: { body :: XML XmlDocument }) }
124+ spec = buildOpenAPISpec @{ getDoc :: Route GET "document" {} (ok :: { body :: XML XmlDocument }) }
125125 { title: "API"
126126 , version: "1.0.0"
127127 , description: Nothing
@@ -133,7 +133,7 @@ testResponseContentTypes = describe "Response Content Types" do
133133
134134 _ <- test "CustomContentType response body generates custom MIME type" do
135135 let
136− spec = buildOpenAPISpec @{ customGet :: Route GET "custom" (Request {}) (ok :: { body :: CustomContentType "application/vnd.api+json" User }) }
136+ spec = buildOpenAPISpec @{ customGet :: Route GET "custom" {} (ok :: { body :: CustomContentType "application/vnd.api+json" User }) }
137137 { title: "API"
138138 , version: "1.0.0"
139139 , description: Nothing
@@ -145,7 +145,7 @@ testResponseContentTypes = describe "Response Content Types" do
145145
146146 test "Unwrapped response body defaults to application/json for backward compatibility" do
147147 let
148− spec = buildOpenAPISpec @{ getUser :: Route GET "users" (Request {}) (ok :: { body :: User }) }
148+ spec = buildOpenAPISpec @{ getUser :: Route GET "users" {} (ok :: { body :: User }) }
149149 { title: "API"
150150 , version: "1.0.0"
151151 , description: Nothing
@@ -161,10 +161,10 @@ testMixedContentTypes = describe "Mixed Content Types in API" do
161161 let
162162 spec = buildOpenAPISpec
163163 @
164− { createUser :: Route POST "users" (Request { body :: JSON User }) (ok :: { body :: JSON User })
165− , uploadFile :: Route POST "upload" (Request { body :: MultipartFormData FileUpload }) (ok :: { body :: { success :: Boolean } })
166− , echo :: Route POST "echo" (Request { body :: PlainText String }) (ok :: { body :: PlainText String })
167− , getDoc :: Route GET "document" (Request {}) (ok :: { body :: XML XmlDocument })
164+ { createUser :: Route POST "users" { body :: JSON User } (ok :: { body :: JSON User })
165+ , uploadFile :: Route POST "upload" { body :: MultipartFormData FileUpload } (ok :: { body :: { success :: Boolean } })
166+ , echo :: Route POST "echo" { body :: PlainText String } (ok :: { body :: PlainText String })
167+ , getDoc :: Route GET "document" {} (ok :: { body :: XML XmlDocument })
168168 }
169169 { title: "Multi-Content API"
170170 , version: "1.0.0"
test/Test/CookieAuthTest.purs+4−4
@@ -5,7 +5,7 @@ import Prelude
55import Data.Maybe (Maybe(..))
66import Data.String as String
77import Effect (Effect)
8−import Yoga.HTTP.API.Route (GET, Route, Request, buildOpenAPISpec)
8+import Yoga.HTTP.API.Route (GET, Route, buildOpenAPISpec)
99import Yoga.HTTP.API.Route.Auth (ApiKeyCookie)
1010import Yoga.JSON (writeJSON)
1111import ViTest (ViTest, describe, test)
@@ -16,15 +16,15 @@ import ViTest.Expect (expectToBe)
1616--------------------------------------------------------------------------------
1717
1818type ApiKeyCookieAPI =
19− { getProtected :: Route GET "protected" (Request { cookies :: { sessionId :: ApiKeyCookie } }) (ok :: { body :: { message :: String } })
19+ { getProtected :: Route GET "protected" { cookies :: { sessionId :: ApiKeyCookie } } (ok :: { body :: { message :: String } })
2020 }
2121
2222type RegularCookieParamsAPI =
23− { getData :: Route GET "data" (Request { cookies :: { preference :: String, theme :: String } }) (ok :: { body :: { data :: String } })
23+ { getData :: Route GET "data" { cookies :: { preference :: String, theme :: String } } (ok :: { body :: { data :: String } })
2424 }
2525
2626type MixedCookieAPI =
27− { getResource :: Route GET "resource" (Request { cookies :: { sessionId :: ApiKeyCookie, userId :: String } }) (ok :: { body :: { resource :: String } })
27+ { getResource :: Route GET "resource" { cookies :: { sessionId :: ApiKeyCookie, userId :: String } } (ok :: { body :: { resource :: String } })
2828 }
2929
3030--------------------------------------------------------------------------------
test/Test/ExamplesTest.purs+6−6
@@ -8,7 +8,7 @@ import Effect (Effect)
88import Type.Function (type (#))
99import Type.Proxy (Proxy(..))
1010import Yoga.HTTP.API.Path (type (/), type (:))
11−import Yoga.HTTP.API.Route (GET, POST, Route, Request, buildOpenAPISpec)
11+import Yoga.HTTP.API.Route (GET, POST, Route, buildOpenAPISpec)
1212import Yoga.HTTP.API.Route.Encoding (JSON)
1313import Yoga.HTTP.API.Route.OpenAPIMetadata (Example, Examples, ExampleValue, ExampleWithSummary, ExampleObject, example, examples)
1414import Yoga.JSON (writeJSON)
@@ -36,7 +36,7 @@ testExamplesMetadataExtraction = describe "Examples Metadata - Type-level extrac
3636 expectToBe true (isJust result)
3737
3838type TestAPIWithPathExamples =
39− { getUser :: Route GET ("users" / "userId" : (Int # Examples (basic :: ExampleValue "123", premium :: ExampleWithSummary "456" "Premium user"))) (Request {}) (ok :: { body :: { id :: Int, name :: String } })
39+ { getUser :: Route GET ("users" / "userId" : (Int # Examples (basic :: ExampleValue "123", premium :: ExampleWithSummary "456" "Premium user"))) {} (ok :: { body :: { id :: Int, name :: String } })
4040 }
4141
4242testExamplesInParameters :: Effect ViTest
@@ -59,7 +59,7 @@ type User =
5959 }
6060
6161type TestAPIWithRequestBodyExamples =
62− { createUser :: Route POST "users" (Request { body :: JSON User }) (ok :: { body :: { id :: Int } })
62+ { createUser :: Route POST "users" { body :: JSON User } (ok :: { body :: { id :: Int } })
6363 }
6464
6565testExamplesInRequestBody :: Effect ViTest
@@ -77,7 +77,7 @@ testExamplesInRequestBody = describe "Examples in Request Body" do
7777 expectToBe true (String.contains (String.Pattern "users") json)
7878
7979type TestAPIWithComplexExamples =
80− { getItem :: Route GET ("items" / "itemId" : (Int # Examples (basic :: ExampleWithSummary "42" "A basic item", special :: ExampleObject "999" "Special item" "A very special item" ""))) (Request {}) (ok :: { body :: { id :: Int } })
80+ { getItem :: Route GET ("items" / "itemId" : (Int # Examples (basic :: ExampleWithSummary "42" "A basic item", special :: ExampleObject "999" "Special item" "A very special item" ""))) {} (ok :: { body :: { id :: Int } })
8181 }
8282
8383testExamplesWithSummary :: Effect ViTest
@@ -111,7 +111,7 @@ testExamplesBackwardCompatibility = describe "Backward Compatibility - Single Ex
111111 expectToBe true (isJust exsResult)
112112
113113type TestAPIWithQueryExamples =
114− { searchItems :: Route GET "items" (Request { query :: { limit :: Int # Examples (small :: ExampleValue "10", large :: ExampleValue "100") } }) (ok :: { body :: { items :: Array String } })
114+ { searchItems :: Route GET "items" { query :: { limit :: Int # Examples (small :: ExampleValue "10", large :: ExampleValue "100") } } (ok :: { body :: { items :: Array String } })
115115 }
116116
117117testExamplesInQueryParams :: Effect ViTest
@@ -129,7 +129,7 @@ testExamplesInQueryParams = describe "Examples in Query Parameters" do
129129 expectToBe true (String.contains (String.Pattern "items") json)
130130
131131type TestAPIWithExternalExample =
132− { getResource :: Route GET ("resource" / "id" : (String # Examples (inline :: ExampleObject "abc123" "Inline example" "An example with inline value" "", external :: ExampleObject "" "External example" "An example from external URL" "https://example.com/resource.json"))) (Request {}) (ok :: { body :: { data :: String } })
132+ { getResource :: Route GET ("resource" / "id" : (String # Examples (inline :: ExampleObject "abc123" "Inline example" "An example with inline value" "", external :: ExampleObject "" "External example" "An example from external URL" "https://example.com/resource.json"))) {} (ok :: { body :: { data :: String } })
133133 }
134134
135135testComplexExampleObject :: Effect ViTest
test/Test/OpenAPIInfoTest.purs+2−2
@@ -5,13 +5,13 @@ import Prelude
55import Data.Maybe (Maybe(..))
66import Data.String as String
77import Effect (Effect)
8−import Yoga.HTTP.API.Route (GET, Route, Request, buildOpenAPISpec)
8+import Yoga.HTTP.API.Route (GET, Route, buildOpenAPISpec)
99import Yoga.JSON (writeJSON)
1010import ViTest (ViTest, describe, test)
1111import ViTest.Expect (expectToBe)
1212
1313type SimpleAPI =
14− { getRoot :: Route GET "" (Request {}) (ok :: { body :: { message :: String } })
14+ { getRoot :: Route GET "" {} (ok :: { body :: { message :: String } })
1515 }
1616
1717testInfoDescription :: Effect ViTest
test/Test/SchemaComponentTest.purs+4−4
@@ -6,7 +6,7 @@ import Data.Maybe (Maybe(..))
66import Data.String as String
77import Effect (Effect)
88import Yoga.HTTP.API.Path (type (:), type (/))
9−import Yoga.HTTP.API.Route (GET, POST, Route, Request, JSON, Schema, buildOpenAPISpec)
9+import Yoga.HTTP.API.Route (GET, POST, Route, JSON, Schema, buildOpenAPISpec)
1010import Yoga.JSON (writeJSON)
1111import ViTest (ViTest, describe, test)
1212import ViTest.Expect (expectToBe)
@@ -17,13 +17,13 @@ type Post = { id :: Int, title :: String, authorId :: Int }
1717type SchemaAPI =
1818 { getUser ::
1919 Route GET ("users" / "id" : Int)
20− (Request {})
20+ {}
2121 (ok :: { body :: Schema "User" User })
2222 , createUser ::
2323 Route POST "users"
24− (Request { body :: JSON (Schema "User" User) })
24+ { body :: JSON (Schema "User" User) }
2525 (created :: { body :: Schema "User" User })
26− , listPosts :: Route GET "posts" (Request {}) (ok :: { body :: Array (Schema "Post" Post) })
26+ , listPosts :: Route GET "posts" {} (ok :: { body :: Array (Schema "Post" Post) })
2727 }
2828
2929testSchemaComponents :: Effect ViTest
test/Test/VariantResponseTest.purs+7−7
@@ -10,7 +10,7 @@ import Effect.Aff (Aff)
1010import Type.Proxy (Proxy(..))
1111import Yoga.HTTP.API.Route.StatusCode (StatusCode(..))
1212import Yoga.HTTP.API.Path (Root)
13−import Yoga.HTTP.API.Route (GET, POST, PUT, Route, Request, Response(..), respondNoHeaders, respondWith, toOpenAPI, statusCodeFor, statusCodeToString)
13+import Yoga.HTTP.API.Route (GET, POST, PUT, Route, Response(..), respondNoHeaders, respondWith, toOpenAPI, statusCodeFor, statusCodeToString)
1414import Yoga.HTTP.API.Route.Response (respond) as Response
1515import ViTest (ViTest, describe, test)
1616import ViTest.Expect (expectToBe)
@@ -25,14 +25,14 @@ expectToEqual expected actual = expectToBe true (expected == actual)
2525
2626-- Simple variant route with ok and notFound responses
2727type SimpleVariantRoute = Route GET Root
28− (Request {})
28+ {}
2929 ( ok :: { body :: String }
3030 , notFound :: { body :: String }
3131 )
3232
3333-- Variant route with multiple status codes
3434type MultiStatusRoute = Route POST Root
35− (Request { headers :: { authorization :: String } })
35+ { headers :: { authorization :: String } }
3636 ( created :: { headers :: { "Location" :: String }, body :: User }
3737 , badRequest :: { body :: ErrorMessage }
3838 , unauthorized :: { body :: ErrorMessage }
@@ -40,7 +40,7 @@ type MultiStatusRoute = Route POST Root
4040
4141-- Variant route with many response types
4242type ComplexVariantRoute = Route PUT Root
43− (Request {})
43+ {}
4444 ( ok :: { body :: User }
4545 , created :: { headers :: { "Location" :: String }, body :: User }
4646 , badRequest :: { body :: ErrorMessage }
@@ -242,7 +242,7 @@ testSimpleVariantOpenAPI = describe "OpenAPI Generation - Simple Variant" $ do
242242 let
243243 result = toOpenAPI
244244 @( Route GET Root
245− (Request {})
245+ {}
246246 ( ok :: { body :: String }
247247 , notFound :: { body :: String }
248248 )
@@ -265,7 +265,7 @@ testComplexVariantOpenAPI = describe "OpenAPI Generation - Complex Variant" $ do
265265 let
266266 result = toOpenAPI
267267 @( Route POST Root
268− (Request { headers :: { authorization :: String } })
268+ { headers :: { authorization :: String } }
269269 ( created :: { headers :: { "Location" :: String }, body :: User }
270270 , badRequest :: { body :: ErrorMessage }
271271 , unauthorized :: { body :: ErrorMessage }
@@ -299,7 +299,7 @@ testVariantWithHeaders = describe "OpenAPI Generation - Variant with Response He
299299 let
300300 result = toOpenAPI
301301 @( Route POST Root
302− (Request {})
302+ {}
303303 ( created :: { headers :: { "Location" :: String, "X-Request-Id" :: String }, body :: User }
304304 )
305305 )