@@ -12,12 +12,13 @@ import {
1212 HttpClient ,
1313 HttpClientRequest ,
1414 HttpServerRequest ,
15+ HttpServerResponse ,
1516 Multipart ,
1617 OpenApi
1718} from "@effect/platform"
1819import { NodeHttpServer } from "@effect/platform-node"
1920import { assert , describe , it } from "@effect/vitest"
20- import { Context , DateTime , Effect , Layer , Redacted , Ref , Schema , Struct } from "effect"
21+ import { Chunk , Context , DateTime , Effect , Layer , Redacted , Ref , Schema , Stream , Struct } from "effect"
2122import OpenApiFixture from "./fixtures/openapi.json" with { type : "json" }
2223
2324describe ( "HttpApi" , ( ) => {
@@ -74,6 +75,18 @@ describe("HttpApi", () => {
7475 length : 5
7576 } )
7677 } ) . pipe ( Effect . provide ( HttpLive ) ) )
78+
79+ it . live ( "multipart stream" , ( ) =>
80+ Effect . gen ( function * ( ) {
81+ const client = yield * HttpApiClient . make ( Api )
82+ const data = new FormData ( )
83+ data . append ( "file" , new Blob ( [ "hello" ] , { type : "text/plain" } ) , "hello.txt" )
84+ const result = yield * client . users . uploadStream ( { payload : data } )
85+ assert . deepStrictEqual ( result , {
86+ contentType : "text/plain" ,
87+ length : 5
88+ } )
89+ } ) . pipe ( Effect . provide ( HttpLive ) ) )
7790 } )
7891
7992 describe ( "headers" , ( ) => {
@@ -237,6 +250,32 @@ describe("HttpApi", () => {
237250 assert . deepStrictEqual ( group , new Group ( { id : 1 , name : "Some group" } ) )
238251 } ) . pipe ( Effect . provide ( HttpLive ) ) )
239252
253+ it . effect ( ".handle can return HttpServerResponse" , ( ) =>
254+ Effect . gen ( function * ( ) {
255+ const client = yield * HttpApiClient . make ( Api )
256+ const response = yield * client . groups . handle ( {
257+ path : { id : 1 } ,
258+ payload : { name : "Some group" }
259+ } )
260+ assert . deepStrictEqual ( response , {
261+ id : 1 ,
262+ name : "Some group"
263+ } )
264+ } ) . pipe ( Effect . provide ( HttpLive ) ) )
265+
266+ it . effect ( ".handleRaw can manually process body" , ( ) =>
267+ Effect . gen ( function * ( ) {
268+ const client = yield * HttpApiClient . make ( Api )
269+ const response = yield * client . groups . handleRaw ( {
270+ path : { id : 1 } ,
271+ payload : { name : "Some group" }
272+ } )
273+ assert . deepStrictEqual ( response , {
274+ id : 1 ,
275+ name : "Some group"
276+ } )
277+ } ) . pipe ( Effect . provide ( HttpLive ) ) )
278+
240279 it ( "OpenAPI spec" , ( ) => {
241280 const spec = OpenApi . fromApi ( Api )
242281 assert . deepStrictEqual ( spec , OpenApiFixture as any )
@@ -300,6 +339,26 @@ class GroupsApi extends HttpApiGroup.make("groups")
300339 ) )
301340 . addSuccess ( Group )
302341 )
342+ . add (
343+ HttpApiEndpoint . post ( "handle" ) `/handle/${ HttpApiSchema . param ( "id" , Schema . NumberFromString ) } `
344+ . setPayload ( Schema . Struct ( {
345+ name : Schema . String
346+ } ) )
347+ . addSuccess ( Schema . Struct ( {
348+ id : Schema . Number ,
349+ name : Schema . String
350+ } ) )
351+ )
352+ . add (
353+ HttpApiEndpoint . post ( "handleRaw" ) `/handleraw/${ HttpApiSchema . param ( "id" , Schema . NumberFromString ) } `
354+ . setPayload ( Schema . Struct ( {
355+ name : Schema . String
356+ } ) )
357+ . addSuccess ( Schema . Struct ( {
358+ id : Schema . Number ,
359+ name : Schema . String
360+ } ) )
361+ )
303362 . addError ( GroupError . pipe (
304363 HttpApiSchema . asEmpty ( { status : 418 , decode : ( ) => new GroupError ( ) } )
305364 ) )
@@ -351,6 +410,16 @@ class UsersApi extends HttpApiGroup.make("users")
351410 length : Schema . Int
352411 } ) )
353412 )
413+ . add (
414+ HttpApiEndpoint . post ( "uploadStream" ) `/uploadstream`
415+ . setPayload ( HttpApiSchema . MultipartStream ( Schema . Struct ( {
416+ file : Multipart . SingleFileSchema
417+ } ) ) )
418+ . addSuccess ( Schema . Struct ( {
419+ contentType : Schema . String ,
420+ length : Schema . Int
421+ } ) )
422+ )
354423 . middleware ( Authorization )
355424 . annotateContext ( OpenApi . annotations ( { title : "Users API" } ) )
356425{ }
@@ -456,6 +525,24 @@ const HttpUsersLive = HttpApiBuilder.group(
456525 length : Number ( stat . size )
457526 }
458527 } ) )
528+ . handle ( "uploadStream" , ( _ ) =>
529+ Effect . gen ( function * ( ) {
530+ const { content, file } = yield * _ . payload . pipe (
531+ Stream . filter ( ( part ) => part . _tag === "File" ) ,
532+ Stream . mapEffect ( ( file ) =>
533+ file . contentEffect . pipe (
534+ Effect . map ( ( content ) => ( { file, content } ) )
535+ )
536+ ) ,
537+ Stream . runCollect ,
538+ Effect . flatMap ( Chunk . head ) ,
539+ Effect . orDie
540+ )
541+ return {
542+ contentType : file . contentType ,
543+ length : content . length
544+ }
545+ } ) )
459546 } )
460547) . pipe ( Layer . provide ( [
461548 DateTime . layerCurrentZoneOffset ( 0 ) ,
@@ -479,6 +566,25 @@ const HttpGroupsLive = HttpApiBuilder.group(
479566 name : "foo" in payload ? payload . foo : payload . name
480567 } )
481568 ) )
569+ . handle (
570+ "handle" ,
571+ Effect . fn ( function * ( { path, payload } ) {
572+ return HttpServerResponse . unsafeJson ( {
573+ id : path . id ,
574+ name : payload . name
575+ } )
576+ } )
577+ )
578+ . handleRaw (
579+ "handleRaw" ,
580+ Effect . fn ( function * ( { path, request } ) {
581+ const body = ( yield * Effect . orDie ( request . json ) ) as { name : string }
582+ return HttpServerResponse . unsafeJson ( {
583+ id : path . id ,
584+ name : body . name
585+ } )
586+ } )
587+ )
482588)
483589
484590const TopLevelLive = HttpApiBuilder . group (
0 commit comments