@@ -2,6 +2,7 @@ import { NodeHttpServer } from "@effect/platform-node"
22import { assert , describe , it } from "@effect/vitest"
33import {
44 Array ,
5+ Cause ,
56 DateTime ,
67 Effect ,
78 Equal ,
@@ -434,6 +435,158 @@ describe("HttpApi", () => {
434435 assert . strictEqual ( yield * authedClient . group . a ( ) , "token" )
435436 } ) . pipe ( Effect . provide ( ApiLive ) )
436437 } )
438+
439+ it . effect ( "addHttpApi + middleware works across merged groups" , ( ) => {
440+ class M1 extends HttpApiMiddleware . Service < M1 > ( ) ( "Http/M1" ) { }
441+ class M2 extends HttpApiMiddleware . Service < M2 > ( ) ( "Http/M2" ) { }
442+
443+ const calls : Array < string > = [ ]
444+
445+ const V0 = HttpApi . make ( "v0" ) . add (
446+ HttpApiGroup . make ( "users" ) . add (
447+ HttpApiEndpoint . get ( "list" , "/users" , {
448+ success : Schema . String
449+ } )
450+ )
451+ )
452+ const Api = HttpApi . make ( "api" )
453+ . add (
454+ HttpApiGroup . make ( "health" ) . add (
455+ HttpApiEndpoint . get ( "health" , "/health" , {
456+ success : Schema . String
457+ } )
458+ )
459+ )
460+ . addHttpApi ( V0 )
461+ . middleware ( M1 )
462+ . middleware ( M2 )
463+
464+ const HealthLive = HttpApiBuilder . group (
465+ Api ,
466+ "health" ,
467+ ( handlers ) => handlers . handle ( "health" , ( ) => Effect . succeed ( "ok" ) )
468+ )
469+ const UsersLive = HttpApiBuilder . group (
470+ Api ,
471+ "users" ,
472+ ( handlers ) => handlers . handle ( "list" , ( ) => Effect . succeed ( "ok" ) )
473+ )
474+ const M1Live = Layer . succeed (
475+ M1 ,
476+ ( effect , { endpoint, group } ) =>
477+ Effect . sync ( ( ) => calls . push ( `m1:${ group . identifier } .${ endpoint . name } ` ) ) . pipe (
478+ Effect . flatMap ( ( ) => effect )
479+ )
480+ )
481+ const M2Live = Layer . succeed (
482+ M2 ,
483+ ( effect , { endpoint, group } ) =>
484+ Effect . sync ( ( ) => calls . push ( `m2:${ group . identifier } .${ endpoint . name } ` ) ) . pipe (
485+ Effect . flatMap ( ( ) => effect )
486+ )
487+ )
488+
489+ const ApiLive = HttpRouter . serve (
490+ HttpApiBuilder . layer ( Api ) . pipe (
491+ Layer . provide ( HealthLive ) ,
492+ Layer . provide ( UsersLive ) ,
493+ Layer . provide ( M1Live ) ,
494+ Layer . provide ( M2Live )
495+ ) ,
496+ { disableListenLog : true , disableLogger : true }
497+ ) . pipe ( Layer . provideMerge ( NodeHttpServer . layerTest ) )
498+
499+ return Effect . gen ( function * ( ) {
500+ yield * assertServerJson ( yield * HttpClient . get ( "/health" ) , 200 , "ok" )
501+ yield * assertServerJson ( yield * HttpClient . get ( "/users" ) , 200 , "ok" )
502+ assert . deepStrictEqual ( calls , [
503+ "m2:health.health" ,
504+ "m1:health.health" ,
505+ "m2:users.list" ,
506+ "m1:users.list"
507+ ] )
508+ } ) . pipe ( Effect . provide ( ApiLive ) )
509+ } )
510+
511+ it . effect ( "missing middleware layer fails with service not found error" , ( ) => {
512+ class M extends HttpApiMiddleware . Service < M > ( ) ( "Server/MissingMiddleware" ) { }
513+
514+ const Api = HttpApi . make ( "api" ) . add (
515+ HttpApiGroup . make ( "group" )
516+ . add (
517+ HttpApiEndpoint . get ( "a" , "/a" , {
518+ success : Schema . String
519+ } )
520+ )
521+ . middleware ( M )
522+ )
523+ const GroupLive = HttpApiBuilder . group (
524+ Api ,
525+ "group" ,
526+ ( handlers ) => handlers . handle ( "a" , ( ) => Effect . succeed ( "ok" ) )
527+ )
528+ const ApiLive = HttpRouter . serve (
529+ HttpApiBuilder . layer ( Api ) . pipe ( Layer . provide ( GroupLive ) ) ,
530+ { disableListenLog : true , disableLogger : true }
531+ ) . pipe ( Layer . provideMerge ( NodeHttpServer . layerTest ) )
532+
533+ return HttpClient . get ( "/a" ) . pipe (
534+ Effect . provide ( ApiLive ) ,
535+ Effect . sandbox ,
536+ Effect . flip ,
537+ Effect . flatMap ( ( cause ) =>
538+ Effect . sync ( ( ) => {
539+ const defect = Cause . squash ( cause )
540+ assert . instanceOf ( defect , Error )
541+ assert . include ( defect . message , "Service not found: Server/MissingMiddleware" )
542+ assert . isFalse ( defect . message . includes ( "is not a function" ) )
543+ } )
544+ )
545+ ) as Effect . Effect < void , HttpClientResponse . HttpClientResponse >
546+ } )
547+ } )
548+
549+ it . effect ( "missing addHttpApi group layer has actionable error" , ( ) => {
550+ const HealthApi = HttpApiGroup . make ( "health" ) . add (
551+ HttpApiEndpoint . get ( "health" , "/health" , {
552+ success : Schema . String
553+ } )
554+ )
555+ const V0 = HttpApi . make ( "v0" ) . add (
556+ HttpApiGroup . make ( "users" ) . add (
557+ HttpApiEndpoint . get ( "list" , "/users" , {
558+ success : Schema . String
559+ } )
560+ )
561+ )
562+ const Api = HttpApi . make ( "api" )
563+ . add ( HealthApi )
564+ . addHttpApi ( V0 )
565+
566+ const UsersLive = HttpApiBuilder . group (
567+ Api ,
568+ "users" ,
569+ ( handlers ) => handlers . handle ( "list" , ( ) => Effect . succeed ( "ok" ) )
570+ )
571+ const ApiLive = HttpRouter . serve (
572+ HttpApiBuilder . layer ( Api ) . pipe ( Layer . provide ( UsersLive ) ) ,
573+ { disableListenLog : true , disableLogger : true }
574+ ) . pipe ( Layer . provideMerge ( NodeHttpServer . layerTest ) )
575+
576+ return HttpClient . get ( "/users" ) . pipe (
577+ Effect . provide ( ApiLive ) ,
578+ Effect . sandbox ,
579+ Effect . flip ,
580+ Effect . flatMap ( ( cause ) =>
581+ Effect . sync ( ( ) => {
582+ const defect = Cause . squash ( cause )
583+ assert . strictEqual ( typeof defect , "string" )
584+ assert . include ( defect , "HttpApiGroup \"health\" not found" )
585+ assert . include ( defect , "HttpApiBuilder.group(api, \"health\", ...)" )
586+ assert . include ( defect , "Available groups: effect/httpapi/HttpApiGroup/users" )
587+ } )
588+ )
589+ ) as Effect . Effect < void , HttpClientResponse . HttpClientResponse >
437590 } )
438591
439592 describe ( "payload option" , ( ) => {
0 commit comments