You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Haskell项目中Servant路由通用页面加载校验实现咨询

Reusable Validation Logic for Servant Handlers

Great question! Repeating validation logic across handlers is definitely a pain, and Servant gives us a few clean ways to solve this. Let's walk through the most straightforward approaches tailored to your use case.

1. Higher-Order Function Wrapper (Simplest Approach)

The easiest way to add shared validation is to create a wrapper function that runs your check first, then executes the original handler if validation passes. This keeps your handler logic clean and avoids repetition.

Step 1: Define Your Validation Function

First, create a reusable check that takes a page identifier and either succeeds or throws an error (like 403 Forbidden):

import Servant
import qualified Text.Blaze.Html as H

-- Example validation: Check if the page is allowed to be loaded
checkPageAccess :: String -> Handler ()
checkPageAccess pageName = do
  -- Replace this with your actual validation logic:
  -- e.g., check user permissions, feature flags, database state, etc.
  let isPageAllowed = pageName `elem` ["page1.html", "page2.html", "page3.html"]
  if not isPageAllowed
    then throwError err403 { errBody = "Access denied to page: " ++ pageName }
    else return ()

Step 2: Create the Wrapper

Write a higher-order function that wraps your handler with the validation:

-- Wrap a handler to run validation before executing it
withPageValidation :: String -> Handler H.Html -> Handler H.Html
withPageValidation page handler = do
  checkPageAccess page
  handler -- Only run the original handler if validation passes

Step 3: Update Your Server

Now you can apply this wrapper to each of your handlers without repeating the validation code:

type MyAPI = "base" :> "page1.html" :> Get '[HTML] H.Html
        :<|> "base" :> "page2.html" :> Get '[HTML] H.Html
        :<|> "base" :> "page3.html" :> Get '[HTML] H.Html

serverAPI :: Server MyAPI
serverAPI = withPageValidation "page1.html" pageH1
       :<|> withPageValidation "page2.html" pageH2
       :<|> withPageValidation "page3.html" pageH3

2. Custom API Combinator (Cleaner API Definition)

If you want to enforce validation directly in your API type (making it explicit which endpoints require checks), you can create a custom Servant combinator. This way, your server implementation stays identical to the original.

Step 1: Define the Combinator

Create a type that carries the page name as a type-level symbol:

import GHC.TypeLits (KnownSymbol, symbolVal)

-- Custom combinator to mark endpoints that need page access validation
data CheckPage (pageName :: Symbol)

Step 2: Implement Servant's Server Instance

Tell Servant how to route endpoints using this combinator by implementing HasServer:

instance (KnownSymbol pageName, HasServer api context) 
  => HasServer (CheckPage pageName :> api) context where
  type ServerT (CheckPage pageName :> api) m = ServerT api m
  
  route _ ctx server = 
    route (Proxy :: Proxy api) ctx $ do
      let pageName = symbolVal (Proxy :: Proxy pageName)
      server >>= \handler -> return $ \req -> do
        checkPageAccess pageName -- Run validation before handling the request
        handler req
  
  hoistServerWithContext _ ctx nt server = 
    hoistServerWithContext (Proxy :: Proxy api) ctx nt server

Step 3: Update Your API Type

Add the CheckPage combinator to each endpoint that needs validation:

type MyAPI = "base" :> CheckPage "page1.html" :> "page1.html" :> Get '[HTML] H.Html
        :<|> "base" :> CheckPage "page2.html" :> "page2.html" :> Get '[HTML] H.Html
        :<|> "base" :> CheckPage "page3.html" :> "page3.html" :> Get '[HTML] H.Html

Step 4: Use Your Original Server

Now your server can use the original handlers directly—validation is handled automatically by the combinator:

serverAPI :: Server MyAPI
serverAPI = pageH1 :<|> pageH2 :<|> pageH3

Which Approach Should You Choose?

  • Use the higher-order function wrapper if you want minimal changes to your existing code and a quick solution.
  • Use the custom combinator if you want to make validation requirements explicit in your API type (great for larger teams or self-documenting code).

内容的提问来源于stack exchange,提问作者QSpider

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 03:54:30