2016-02-11 6 views
4

Haskell beginner здесь, пытаясь обернуть API HTTP REST безопасным способом и с автоматическим декодированием Aeson возвращаемых значений. Я начал с функции Haskell для каждого вызова API. Это было немного условно, но хорошо.Моделирование POST API безопасным способом

Чтобы улучшить работу, я хотел бы превратить каждый вызов API в собственный тип данных. Например, для входа в систему я бы моделировал это как Login тип, который задает метод, тип Credentials для параметров метода и LoginReponse для результата вызова API. Параметры и типы ответов, конечно, имеют соответствующие экземпляры FromJSON и ToJSON.

Для два API называет это выглядит примерно так (с использованием GADTs):

data Void   = Void 
data Credentials = Credentials {...} 
data LoginResponse = LoginResponse {...} 
data LogoutResponse = LogoutResponse {...} 

data Command a where 
    Login :: Credentials -> Command LoginResponse 
    Logout :: Void  -> Command LogoutResponse 

execute :: FromJSON a => Command a -> IO a 
execute cmd = do 
    manager <- newManager tlsManagerSettings 
    let request = buildHttpRequest cmd 

    result <- httpLbs request manager 
    let body = responseBody result 
    let parsed = fromJust $ decode body 

    return parsed 

Это прекрасно работает для моего случая использования - я могу вникать команды перед их выполнением, я не могу построить недопустимые вызовов API и Aeson знает, как декодировать возвращаемые значения!

Только проблема с этим подходом заключается в том, что я должен хранить все мои Команды в одном файле под одной декларацией данных.

Я хотел бы переместить определение методов (в моем примере Login и Logout) для отдельных модулей, но, чтобы сохранить функцию execute подобными, и, конечно, сохранить безопасность типа и декодирование эсона.

Я пытался что-то сделать с помощью классов типов, но не получил нигде.

Любые советы, как это сделать, приветствуются!

+2

Я думаю, вас может заинтересовать [слуга] (https://hackage.haskell.org/package/servant). – Zeta

+1

Во-первых, использование существующей библиотеки, такой как слуга, определенно является для вас способом для практических целей. Если речь идет об обучении, вы можете решить эту проблему, используя [семейства данных] (https://wiki.haskell.org/GHC/Type_families#Detailed_definition_of_data_families), что позволит вам по существу иметь «открытый» тип данных. Тогда вы можете иметь класс с функцией запроса 'buildHttpRequest', семейством данных' Command' и всем, что вам нужно.В качестве побочного примечания тип, который вы назвали «Void», обычно называется «()» в Haskell (Void обычно зарезервирован для типа * empty *). – user2407038

ответ

1

Поскольку единственное, что вы сделали по-другому в execute для различных команд вызова buildHttpRequest, я предлагаю следующий альтернативный тип данных:

type Command a = Tagged a HttpRequest 

(я не знаю, тип возвращаемого buildHttpRequest, так что я сделал что-то вверх и предположил, что оно вернуло HttpRequest. Надеюсь, эта идея будет достаточно ясной, хотя я уверен, что эта часть была неправильной.) Тип Tagged исходит от tagged и позволяет прикрепить тип к значению; это тип урфантома. В нашем случае мы будем использовать прилагаемый тип, чтобы решить, как декодировать JSON на этапе execute.

Тип execute нуждается в незначительной модификации требовать, чтобы прилагаемое тип может быть расшифрован:

execute :: FromJSON a => Command a -> IO a 

Однако его реализация остается практически неизменным; просто замените buildHttpRequest на untag. Эти команды не очень удобны, но вы сможете разделить их по границам модулей; например

module FancyApp.Login (Credentials(..), LoginResponse(..), login) where 

import FancyApp.Types 

data Credentials = Credentials 
data LoginResponse = LoginResponse 

login :: Credentials -> Command LoginResponse 
login = {- the old implementation of buildHttpRequest for Login -} 

module FancyApp.Logout (LogoutResponse(..), logout) where 

import FancyApp.Types 

data LogoutResponse = LogoutResponse 

logout :: Command LogoutResponse 
logout = {- the old implementation of buildHttpRequest for Logout -} 
Смежные вопросы