代码之家  ›  专栏  ›  技术社区  ›  Chris Stryczynski

如何使用Servant验证/报告无效输入的错误?

  •  0
  • Chris Stryczynski  · 技术社区  · 6 年前

    我在这里学习仆人教程: https://docs.servant.dev/en/stable/tutorial/Server.html#from-combinators-to-handler-arguments

    其代码大致如下:

    app1 :: Application
    app1 = serve (Proxy :: Proxy API) server3
    
    main' :: IO ()
    main' = run 8081 app1
    type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
          :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
          :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
    
    data Position = Position
      { xCoord :: Int
      , yCoord :: Int
      } deriving Generic
    
    instance ToJSON Position
    
    newtype HelloMessage = HelloMessage { msg :: String }
      deriving Generic
    
    instance ToJSON HelloMessage
    
    data ClientInfo = ClientInfo
      { clientName :: String
      , clientEmail :: String
      , clientAge :: Int
      , clientInterestedIn :: [String]
      } deriving Generic
    
    instance FromJSON ClientInfo
    instance ToJSON ClientInfo
    
    data Email = Email
      { from :: String
      , to :: String
      , subject :: String
      , body :: String
      } deriving Generic
    
    instance ToJSON Email
    
    emailForClient :: ClientInfo -> Email
    emailForClient c = Email from' to' subject' body'
    
      where from'    = "great@company.com"
            to'      = clientEmail c
            subject' = "Hey " ++ clientName c ++ ", we miss you!"
            body'    = "Hi " ++ clientName c ++ ",\n\n"
                    ++ "Since you've recently turned " ++ show (clientAge c)
                    ++ ", have you checked out our latest "
                    ++ intercalate ", " (clientInterestedIn c)
                    ++ " products? Give us a visit!"
    
    server3 :: Server API
    server3 = position
         :<|> hello
         :<|> marketing
    
      where position :: Int -> Int -> Handler Position
            position x y = return (Position x y)
    
            hello :: Maybe String -> Handler HelloMessage
            hello mname = return . HelloMessage $ case mname of
              Nothing -> "Hello, anonymous coward"
              Just n  -> "Hello, " ++ n
    
            marketing :: ClientInfo -> Handler Email
            marketing clientinfo = return (emailForClient clientinfo)
    

    简单的输入非常有用:

    curl http://localhost:8081/position/1/2        
    {"yCoord":2,"xCoord":1}
    

    给出一个简单的例子 无效的 输入效果不太好(替换) 2 用绳子 test :

    curl -v http://localhost:8081/position/1/test  
    *   Trying ::1:8081...
    * TCP_NODELAY set
    * connect to ::1 port 8081 failed: Connection refused
    *   Trying 127.0.0.1:8081...
    * TCP_NODELAY set
    * Connected to localhost (127.0.0.1) port 8081 (#0)
    > GET /position/1/test HTTP/1.1
    > Host: localhost:8081
    > User-Agent: curl/7.65.3
    > Accept: */*
    > 
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 400 Bad Request
    < Transfer-Encoding: chunked
    < Date: Mon, 16 Dec 2019 18:01:00 GMT
    < Server: Warp/3.2.28
    < 
    * Connection #0 to host localhost left intact
    

    在第二种情况下,如何向响应中添加错误处理/验证?因此,理想情况下,它不是一个空白的HTTP 400,而是以“error expecting int,get string”作为响应。这和 ExceptT 功能?有没有一个简单的例子?

    0 回复  |  直到 6 年前
        1
  •  2
  •   Fyodor Soikin    6 年前

    总的来说,我认为这不值得做,因为 一般来说 ,可能会有非常复杂的路由组合,产生非常不直观的错误消息。例如,考虑下面的API:

    type API = 
             "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
        :<|> "position" :> "foo" :> "test" :> Get '[JSON] Position
    

    这个API允许 /position/1/2 和 /position/foo/test ,但拒绝 /position/1/test ,并且对于最后一个案例,您无法生成任何正常的错误消息。它必须是类似于“ 期望在最末端有一个Int,或者在第二个from end位置有一个“foo”,但在末端有一个“test”,在第二个from end位置有一个“1” “。对消费者没有帮助。

    “如果您想要创建一条经过适当格式化的路径,则只需创建一条经过适当格式化的路径即可:

    type API = "position" :> Capture "x" Int :> Capture "y" Int :> Get '[JSON] Position
          :<|> "position" :> Capture "x" Text :> Capture "y" Text :> Get '[JSON] ()
          :<|> "hello" :> QueryParam "name" String :> Get '[JSON] HelloMessage
          :<|> "marketing" :> ReqBody '[JSON] ClientInfo :> Post '[JSON] Email
    
    ...
    
    server3 = position
         :<|> badPosition
         :<|> hello
         :<|> marketing
    
        where 
            ...
    
            badPosition x y =
                throwError $ err400 { errBody = "Expected ints, got '" <> x <> "' and '" <> y <> "'" }