hoodunit / purescript-payload

An HTTP server and client library for PureScript
Apache License 2.0
100 stars 11 forks source link

Is it possible to merge Specs? #21

Closed glenstarchman closed 3 years ago

glenstarchman commented 3 years ago

I can merge handlers using Record.merge just fine but is there a way to merge Specs in a similar way?

I would like to be able to have multiple modules representing root endpoints (eg, User, Profile, etc...) that define their own spec and handlers and then merge them all together. For example:

import User
import Profile
import Record (merge)

handlers = merge User.handlers Profile.handlers
spec = ??? User.spec Profile.spec

...

Is there any way to accomplish this?

hoodunit commented 3 years ago

You can extract types from the spec using normal PureScript type aliases and put them together like this:

type AuthRoutes = Routes "/authentication" {
  token :: Routes "/token" {
    new :: GET "/new" {
      response :: RequestTokenResponse
    }
  },
  session :: Routes "/session" {
    create :: POST "/new" {
      body :: { requestToken :: String },
      response :: SessionIdResponse
    },
    delete :: DELETE "/" {
      body :: { sessionId :: String },
      response :: StatusResponse
    }
  }
}

type V1Routes = Routes "/v1" {
  guards :: Guards ("apiKey" : Nil),
  auth :: AuthRoutes,
  movies :: Routes "/movies" {
    latest :: GET "/latest" {
      response :: Movie
    },
    popular :: GET "/popular" {
      response :: { results :: Array Movie }
    },
    byId :: Routes "/<movieId>" {
      params :: { movieId :: Int },
      get :: GET "/" {
        response :: Movie
      },
      rating :: Routes "/rating" {
        guards :: Guards ("sessionId" : Nil),
        create :: POST "/rating" {
          body :: RatingValue,
          response :: StatusCodeResponse
        },
        delete :: DELETE "/rating" {
          response :: StatusCodeResponse
        }
      }
    }
  }
}

moviesApiSpec :: Spec {
  guards :: {
     apiKey :: ApiKey,
     sessionId :: SessionId
  },
  routes :: {
    v1 :: V1Routes
  }
}
moviesApiSpec = Spec

You can also group up different paths under a Routes wrapper to merge routes if they have the same path like this:

type AuthTokenRoutes = Routes "/authentication/token" {
  token :: Routes "/token" {
    new :: GET "/new" {
      response :: RequestTokenResponse
    }
  }
}

type AuthSessionRoutes = Routes "/authentication/session" {
  session :: Routes "/session" {
    create :: POST "/new" {
      body :: { requestToken :: String },
      response :: SessionIdResponse
    },
    delete :: DELETE "/" {
      body :: { sessionId :: String },
      response :: StatusResponse
    }
  }
}

type V1Routes = Routes "/v1" {
  guards :: Guards ("apiKey" : Nil),
  authToken :: AuthTokenRoutes,
  authSession :: AuthSessionRoutes,
  movies :: Routes "/movies" {
    latest :: GET "/latest" {
      response :: Movie
    },
    popular :: GET "/popular" {
      response :: { results :: Array Movie }
    },
    byId :: Routes "/<movieId>" {
      params :: { movieId :: Int },
      get :: GET "/" {
        response :: Movie
      },
      rating :: Routes "/rating" {
        guards :: Guards ("sessionId" : Nil),
        create :: POST "/rating" {
          body :: RatingValue,
          response :: StatusCodeResponse
        },
        delete :: DELETE "/rating" {
          response :: StatusCodeResponse
        }
      }
    }
  }
}

moviesApiSpec :: Spec {
  guards :: {
     apiKey :: ApiKey,
     sessionId :: SessionId
  },
  routes :: {
    v1 :: V1Routes
  }
}
moviesApiSpec = Spec

Does one of those work for you?

glenstarchman commented 3 years ago

Yes. Thank you!

On Thu, Mar 25, 2021, 23:35 Nicholas Kariniemi @.***> wrote:

You can extract types from the spec using normal PureScript type aliases and put them together like this:

type AuthRoutes = Routes "/authentication" { token :: Routes "/token" { new :: GET "/new" { response :: RequestTokenResponse } }, session :: Routes "/session" { create :: POST "/new" { body :: { requestToken :: String }, response :: SessionIdResponse }, delete :: DELETE "/" { body :: { sessionId :: String }, response :: StatusResponse } }} type V1Routes = Routes "/v1" { guards :: Guards ("apiKey" : Nil), auth :: AuthRoutes, movies :: Routes "/movies" { latest :: GET "/latest" { response :: Movie }, popular :: GET "/popular" { response :: { results :: Array Movie } }, byId :: Routes "/" { params :: { movieId :: Int }, get :: GET "/" { response :: Movie }, rating :: Routes "/rating" { guards :: Guards ("sessionId" : Nil), create :: POST "/rating" { body :: RatingValue, response :: StatusCodeResponse }, delete :: DELETE "/rating" { response :: StatusCodeResponse } } } }} moviesApiSpec :: Spec { guards :: { apiKey :: ApiKey, sessionId :: SessionId }, routes :: { v1 :: V1Routes } } moviesApiSpec = Spec

You can also group up different paths under a Routes wrapper to merge routes if they have the same path like this:

type AuthTokenRoutes = Routes "/authentication/token" { token :: Routes "/token" { new :: GET "/new" { response :: RequestTokenResponse } }} type AuthSessionRoutes = Routes "/authentication/session" { session :: Routes "/session" { create :: POST "/new" { body :: { requestToken :: String }, response :: SessionIdResponse }, delete :: DELETE "/" { body :: { sessionId :: String }, response :: StatusResponse } }} type V1Routes = Routes "/v1" { guards :: Guards ("apiKey" : Nil), authToken :: AuthTokenRoutes, authSession :: AuthSessionRoutes, movies :: Routes "/movies" { latest :: GET "/latest" { response :: Movie }, popular :: GET "/popular" { response :: { results :: Array Movie } }, byId :: Routes "/" { params :: { movieId :: Int }, get :: GET "/" { response :: Movie }, rating :: Routes "/rating" { guards :: Guards ("sessionId" : Nil), create :: POST "/rating" { body :: RatingValue, response :: StatusCodeResponse }, delete :: DELETE "/rating" { response :: StatusCodeResponse } } } }} moviesApiSpec :: Spec { guards :: { apiKey :: ApiKey, sessionId :: SessionId }, routes :: { v1 :: V1Routes } } moviesApiSpec = Spec

Does one of those work for you?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/hoodunit/purescript-payload/issues/21#issuecomment-807975637, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAITEEXH2JAECYFAT72IDSDTFQTJLANCNFSM4ZXRHXIA .