首页 \ 问答 \ 春天restdocs可以生成像swagger一样的实时文档吗?(Can spring restdocs generate live documentation like swagger?)

春天restdocs可以生成像swagger一样的实时文档吗?(Can spring restdocs generate live documentation like swagger?)

作为开发人员,我更喜欢春天的restdocs。 但作为文档的使用者,我发现swagger实时文档非常引人注目。 这是无处不在的例子: http//petstore.swagger.io/

有没有办法用spring restdocs记录我的休息apis,但生成像swagger petstore这样的实时文档? 如果是这样,你如何做到这一点?


As a developer, I prefer spring restdocs. But as a consumer of the documentation, I find the swagger live documentation to be very compelling. Here's the ubiquitous example: http://petstore.swagger.io/

Is there a way to document my rest apis with spring restdocs but generate live documentation like the swagger petstore with it? If so, how do you do this?


原文:https://stackoverflow.com/questions/36798940
更新时间:2023-05-12 19:05

最满意答案

TL; DR :使用“ 代码汇总 ”底部列出的模块为您生成代码,

你不忍心写所有的样板代码。 我非常同情。 还有其他方法,但我们可以使用Template Haskell来生成我们需要的代码。

如果你是模板Haskell的新手,你应该看看haskellwiki页面

首先,让我们打开模板Haskell扩展,并导入Control.Applicative将代码整理一下:

{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Control.Applicative

我们应该生成什么模板haskell代码?

让我们让ghci为我们转换一个恰当的表达。 (为了方便,我伪装了一个getn函数,所以我可以在独立代码中使用它。)

*Main> :set -XTemplateHaskell
*Main> runQ [| Test <$> getn "field1" <*> getn "field2" <*> getn "field3" |]
InfixE (Just (InfixE (Just (InfixE (Just (ConE Main.Test)) (VarE Data.Functor.<$>) (Just (AppE (VarE Main.getn) (LitE (StringL "field1")))))) (VarE Control.Applicative.<*>) (Just (AppE (VarE Main.getn) (LitE (StringL "field2")))))) (VarE Control.Applicative.<*>) (Just (AppE (VarE Main.getn) (LitE (StringL "field3"))))

哇! 让我们整理一下,并使其有效的haskell代码。 首先请注意,诸如Data.Functor.<$>之类的表达式实际上是Name类型的。 为了得到它,我们可以mkName "<$>" ,但是字符串修改是最糟糕的源代码操作,所以让我们来执行'(<$>) ,它会从函数生成(完全限定)名称:

whatWeWant = InfixE 
    (Just (InfixE 
             (Just (InfixE 
                      (Just (ConE 'Test)) 
                      (VarE '(<$>)) 
                      (Just (AppE (VarE 'getn) (LitE (StringL "field1")))))) 
             (VarE '(<*>)) 
             (Just (AppE (VarE 'getn) (LitE (StringL "field2")))))) 
    (VarE '(<*>)) 
    (Just (AppE (VarE 'getn) (LitE (StringL "field3"))))

这个(隐藏的)美妙之处在于,它只是一个非常类似的表达,我们可以一起折叠。

生成我们需要的表达式

fieldExpressions :: Name -> [String] -> [Exp]
fieldExpressions getter = map $ \field -> AppE (VarE getter) (LitE (StringL field))

让我们使用<<*>>作为<*>的一种提升来将表达式与<*>绑定在一起:

(<<*>>) :: Exp -> Exp -> Exp
a <<*>> b = InfixE  (Just a)  (VarE '(<*>))  (Just b)

现在,当我们获取字段时,我们将首先通过<$>将构造函数应用于第一个字段,然后我们可以将其用作折叠其他字段的基础。

getFields :: Name -> [Exp] -> Exp
getFields _ [] = error "getFields: empty field list"
getFields constructor (f:fs) = foldl (<<*>>) 
                               ( InfixE  (Just $ ConE constructor)  (VarE '(<$>))  (Just f) )
                               fs

快速检查:

*Main> whatWeWant == (getFields 'Test $ fieldExpressions 'getn ["field1","field2","field3"])
True

舞台限制叮咬

我们可以用同一个源文件测试/使用它

domything = do
   optionsRecord <- $(return $ getFields 'Test $ fieldExpressions 'getn ["field1","field2","field3"])
   print optionsRecord

除非你会遇到相当不方便的阶段限制:

GHC stage restriction: `getFields'
  is used in a top-level splice or annotation,
  and must be imported, not defined locally

这意味着您必须在另一个模块中定义getFields等,然后将其导入到您的主文件中,并在其中拼接。

代码摘要

GetFields.hs:

{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Control.Applicative

module GetFields where

fieldExpressions :: Name -> [String] -> [Exp]
fieldExpressions getter = map $ \field -> AppE (VarE getter) (LitE (StringL field))

(<<*>>) :: Exp -> Exp -> Exp
a <<*>> b = InfixE  (Just a)  (VarE '(<*>))  (Just b)

getFields :: Name -> [Exp] -> Exp
getFields _ [] = error "getFields: empty field list"
getFields constructor (f:fs) = foldl (<<*>>) 
                               ( InfixE  (Just $ ConE constructor)  (VarE '(<$>))  (Just f) )
                               fs

Main.hs:

import GetFields
import Data.ConfigFile

...defs...

readConfigFile = do
  rv <- runErrorT $ do 
    cp <- join . liftIO $ readfile emptyCP "theconfig.cfg"
    let printn = liftIO . putStrLn
        getn = get x "DEFAULT"
        x = cp
    printn "Loading configuration file..."
    someoptions <- $(getFields 'Test $ fieldExpressions 'getn ["field" ++ show n| n<-[1..30]])

TL;DR: Generate your code for you using the module listed at the bottom under "Code Summary"

You can't bear to write all that boilerplate code. I can very much sympathise. There are other approaches, but we could use Template Haskell to generate the code we need.

If you're new to Template Haskell, you should have a look at the haskellwiki page.

First, let's turn on the Template Haskell extension, and import Control.Applicative to tidy the code up a bit:

{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Control.Applicative

What template haskell code should we generate?

And let's ask ghci to convert an appropriate expression for us. (I faked a getn function for convenience so I can use it in standalone code.)

*Main> :set -XTemplateHaskell
*Main> runQ [| Test <$> getn "field1" <*> getn "field2" <*> getn "field3" |]
InfixE (Just (InfixE (Just (InfixE (Just (ConE Main.Test)) (VarE Data.Functor.<$>) (Just (AppE (VarE Main.getn) (LitE (StringL "field1")))))) (VarE Control.Applicative.<*>) (Just (AppE (VarE Main.getn) (LitE (StringL "field2")))))) (VarE Control.Applicative.<*>) (Just (AppE (VarE Main.getn) (LitE (StringL "field3"))))

Woah! let's tidy that up a bit, and make it valid haskell code. Firstly note that an expression such as Data.Functor.<$> is actually of type Name. To get it we could do mkName "<$>", but string mangling is the ugliest sort of source code manipulation, so let's do '(<$>) instead, which generates the (fully qualified) name from the function:

whatWeWant = InfixE 
    (Just (InfixE 
             (Just (InfixE 
                      (Just (ConE 'Test)) 
                      (VarE '(<$>)) 
                      (Just (AppE (VarE 'getn) (LitE (StringL "field1")))))) 
             (VarE '(<*>)) 
             (Just (AppE (VarE 'getn) (LitE (StringL "field2")))))) 
    (VarE '(<*>)) 
    (Just (AppE (VarE 'getn) (LitE (StringL "field3"))))

The (hidden) beauty of this is that it's just a load of very similar expressions that we can fold together.

Generating the expressions we need

fieldExpressions :: Name -> [String] -> [Exp]
fieldExpressions getter = map $ \field -> AppE (VarE getter) (LitE (StringL field))

Let's use <<*>> as a sort of lift of <*> to glue expressions together with <*>:

(<<*>>) :: Exp -> Exp -> Exp
a <<*>> b = InfixE  (Just a)  (VarE '(<*>))  (Just b)

Now when we get the fields, we'll first apply the constructor via <$> to the first field, then we can use that as the base for a fold over the other fields.

getFields :: Name -> [Exp] -> Exp
getFields _ [] = error "getFields: empty field list"
getFields constructor (f:fs) = foldl (<<*>>) 
                               ( InfixE  (Just $ ConE constructor)  (VarE '(<$>))  (Just f) )
                               fs

A quick check:

*Main> whatWeWant == (getFields 'Test $ fieldExpressions 'getn ["field1","field2","field3"])
True

The stage restriction bites

We could test/use that in the same source file with

domything = do
   optionsRecord <- $(return $ getFields 'Test $ fieldExpressions 'getn ["field1","field2","field3"])
   print optionsRecord

except that you'll run into the rather inconvenient stage restriction:

GHC stage restriction: `getFields'
  is used in a top-level splice or annotation,
  and must be imported, not defined locally

That means that you'll have to define getFields etc in another module, and then import that into your main file where you can splice it in.

Code Summary

GetFields.hs:

{-# LANGUAGE TemplateHaskell #-}
import Language.Haskell.TH
import Control.Applicative

module GetFields where

fieldExpressions :: Name -> [String] -> [Exp]
fieldExpressions getter = map $ \field -> AppE (VarE getter) (LitE (StringL field))

(<<*>>) :: Exp -> Exp -> Exp
a <<*>> b = InfixE  (Just a)  (VarE '(<*>))  (Just b)

getFields :: Name -> [Exp] -> Exp
getFields _ [] = error "getFields: empty field list"
getFields constructor (f:fs) = foldl (<<*>>) 
                               ( InfixE  (Just $ ConE constructor)  (VarE '(<$>))  (Just f) )
                               fs

Main.hs:

import GetFields
import Data.ConfigFile

...defs...

readConfigFile = do
  rv <- runErrorT $ do 
    cp <- join . liftIO $ readfile emptyCP "theconfig.cfg"
    let printn = liftIO . putStrLn
        getn = get x "DEFAULT"
        x = cp
    printn "Loading configuration file..."
    someoptions <- $(getFields 'Test $ fieldExpressions 'getn ["field" ++ show n| n<-[1..30]])

相关问答

更多
  • 做你想做的事的直接方法是写你的特殊情况进行validate : validate env expr = concat $ gmapQ ([] `mkQ` (validate env)) expr 这使用了mkQ的Data.Generics.Aliases 。 mkQ是创建类型为forall d. Data d => d -> u查询forall d. Data d => d -> u forall d. Data d => d -> u ,可以在不同的Data实例上以不同的方式运行。 顺便说一句,这里没有 ...
  • 好吧,它不是Haskell 2010标准的一部分,所以默认情况下它不是开启的,而是作为语言扩展提供。 至于为什么它不在标准中,rank-n类型比Haskell普通的rank-1类型标准实现起来要困难得多; 他们也不是经常需要,所以委员会可能决定不因语言和实施简单的原因而烦恼他们。 当然,这并不意味着排名n型是没有用的; 它们是非常重要的,没有它们,我们就不会有像ST monad这样的有价值的工具(它提供了高效的局部可变状态 - 就像IO所有你可以做的就是使用IORef )。 但是它们的确增加了语言的复杂性, ...
  • 从Haskell '98报告 : 数据声明可以在多个构造函数中使用相同的字段标签,只要字段类型在类型同义词扩展之后的所有情况下都是相同的。 范围内的标签不能由多个类型共享。 字段名称与普通变量和类方法共享顶级名称空间,并且不能与范围内的其他顶级名称冲突。 我不认为有什么更深入的。 正如你所说的那样,结果字段访问器无论如何都有类型Pet -> String ,所以决定它的方便之处在于允许在不同的构造函数中重用相同的字段名称。 From the Haskell '98 Report: A data declar ...
  • ImpredicativeTypes 不能真正正常工作; 避免他们。 您可以将汇率转换表包装在保留其多态类型的data类型中,而不是使用IO (forall ab ...) 。 data ExchangeRates = ExchangeRates { getER :: forall a b. (KnownSymbol a, KnownSymbol b) => Currency a -> Currency b } 并返回IO ExchangeRates -- getConvert :: IO (fo ...
  • 你可能想要的不是返回一个值(forall a . a)因为它在几个方面是错误的。 首先,你没有任何价值,而只是两个中的一个。 对于二,这种类型不能存在于行为良好的程序中:它对应于无限循环和异常的类型,例如底部。 最后,这种类型允许拥有它的人做出选择以更具体地实例化它。 因为你将它交给你的函数调用者,这意味着他们可以选择你拥有的Int或Char中的哪一个。 显然这没有意义。 相反,你最想要的是向你的功能用户提出要求:“无论这种类型是什么,你都必须工作”。 foo :: (forall a . a -> r) ...
  • 查看foldl的类型 foldl :: (a -> b -> a) -> a -> [b] -> a 考虑foldl fz list 所以foldl基本上可以在列表上(或者任何可折叠的)递增地工作,从左边取1个元素并应用fz element来获取新元素,以便在下一步中使用新元素,同时折叠其余元素。 基本上foldl的简单定义可能有助于理解它。 foldl f z [] = z foldl f z (x:xs) = foldl f (f z x) xs 来自Haskell wiki的图表可能有助 ...
  • 通常情况下,Haskell中的所有类型变量都会在类型的最外层范围内隐式普遍量化。 RankNTypes允许通用量词出现嵌套,例如类型forall a b. (a -> a) -> b -> b forall a b. (a -> a) -> b -> b与forall a b. (a -> a) -> b -> b )非常不同forall b. (forall a. a -> a) -> b -> b forall b. (forall a. a -> a) -> b -> b 。 在函数箭头的左侧有一种类 ...
  • TL; DR :使用“ 代码汇总 ”底部列出的模块为您生成代码, 你不忍心写所有的样板代码。 我非常同情。 还有其他方法,但我们可以使用Template Haskell来生成我们需要的代码。 如果你是模板Haskell的新手,你应该看看haskellwiki页面 。 首先,让我们打开模板Haskell扩展,并导入Control.Applicative将代码整理一下: {-# LANGUAGE TemplateHaskell #-} import Language.Haskell.TH import Cont ...
  • 这些不等同于: *Demo> [ grad f | Fun f <- fList ] *Demo> map (\f -> grad (Fun f)) fList 第一个,粗略地,从fList提取一个值,比如x ,选择f使得Fun f = x ,然后调用grad f 。 第二个从fList提取一个fList f (!)的值,并计算Fun f ,并将其传递给grad 。 因此,第一个从列表元素中删除Fun包装器,第二个添加包装器。 比较它: *Demo> map (\ (Fun f) -> grad f) f ...
  • 将类约束视为推断的函数参数是有用的,因为它们对GHC Core中的函数和函数应用程序感到厌恶。 让我们去问一下这个类型: newtype ShowDict a = ShowDict (a -> String) test1 :: forall x. (ShowDict x -> x) -> String 或者更简单: test1 :: forall x. ((x -> String) -> x) -> String 这种类型并不真正有用。 通过参数化,它的实现必须是返回一些字符串的常量函数。 使用原始的非 ...

相关文章

更多

最新问答

更多
  • 获取MVC 4使用的DisplayMode后缀(Get the DisplayMode Suffix being used by MVC 4)
  • 如何通过引用返回对象?(How is returning an object by reference possible?)
  • 矩阵如何存储在内存中?(How are matrices stored in memory?)
  • 每个请求的Java新会话?(Java New Session For Each Request?)
  • css:浮动div中重叠的标题h1(css: overlapping headlines h1 in floated divs)
  • 无论图像如何,Caffe预测同一类(Caffe predicts same class regardless of image)
  • xcode语法颜色编码解释?(xcode syntax color coding explained?)
  • 在Access 2010 Runtime中使用Office 2000校对工具(Use Office 2000 proofing tools in Access 2010 Runtime)
  • 从单独的Web主机将图像传输到服务器上(Getting images onto server from separate web host)
  • 从旧版本复制文件并保留它们(旧/新版本)(Copy a file from old revision and keep both of them (old / new revision))
  • 西安哪有PLC可控制编程的培训
  • 在Entity Framework中选择基类(Select base class in Entity Framework)
  • 在Android中出现错误“数据集和渲染器应该不为null,并且应该具有相同数量的系列”(Error “Dataset and renderer should be not null and should have the same number of series” in Android)
  • 电脑二级VF有什么用
  • Datamapper Ruby如何添加Hook方法(Datamapper Ruby How to add Hook Method)
  • 金华英语角.
  • 手机软件如何制作
  • 用于Android webview中图像保存的上下文菜单(Context Menu for Image Saving in an Android webview)
  • 注意:未定义的偏移量:PHP(Notice: Undefined offset: PHP)
  • 如何读R中的大数据集[复制](How to read large dataset in R [duplicate])
  • Unity 5 Heighmap与地形宽度/地形长度的分辨率关系?(Unity 5 Heighmap Resolution relationship to terrain width / terrain length?)
  • 如何通知PipedOutputStream线程写入最后一个字节的PipedInputStream线程?(How to notify PipedInputStream thread that PipedOutputStream thread has written last byte?)
  • python的访问器方法有哪些
  • DeviceNetworkInformation:哪个是哪个?(DeviceNetworkInformation: Which is which?)
  • 在Ruby中对组合进行排序(Sorting a combination in Ruby)
  • 网站开发的流程?
  • 使用Zend Framework 2中的JOIN sql检索数据(Retrieve data using JOIN sql in Zend Framework 2)
  • 条带格式类型格式模式编号无法正常工作(Stripes format type format pattern number not working properly)
  • 透明度错误IE11(Transparency bug IE11)
  • linux的基本操作命令。。。