Skip to content

Commit

Permalink
Support autogenerated documentation for method results, in addition t…
Browse files Browse the repository at this point in the history
…o parameters (#161)

Currently, the `DescribedParams` class allows for declaring the documentation
for the _parameters_ of methods, but not the _results_. This makes it
insufficient for generating the sorts of documentation like what is written
[here](https://github.com/GaloisInc/saw-script/blob/7bb93c9b5c08422a49017e49772d8d2db3ff8fb0/saw-remote-api/docs/old-Saw.rst#running-proof-scripts)
in the handwritten SAW remote API docs.

This is an attempt at augmenting the `DescribedParams` class to allow
documenting result values in addition to parameters. Some of the highlights
are:

* The class has now been renamed to `DescribedMethod` to reflect its more
  general purpose.
* `DescribedMethod` now has two type parameters: `params` (which existed
  before) and `result`. There is a `params -> result` functional dependency
  since the `params` type is almost always a custom data type, whereas `result`
  can often be off-the-shelf data types like `Value` and `()`.
* In addition to the `parameterFieldDescription` method, there is now a
  `resultFieldDescription` method. It has a default implementation of `[]`
  in the common case where `result` is `()` (i.e., nothing is returned).
* There has been some reorganization of the autogenerated documentation so
  that there is a more clear separation of the parameters and results.
  • Loading branch information
RyanGlScott committed Apr 7, 2021
1 parent 594a040 commit b9b3edd
Show file tree
Hide file tree
Showing 5 changed files with 198 additions and 42 deletions.
41 changes: 31 additions & 10 deletions argo/src/Argo.hs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE UndecidableInstances #-}
Expand Down Expand Up @@ -75,6 +76,7 @@ module Argo
-- * AppMethod info
methodName,
methodParamDocs,
methodReturnFieldDocs,
methodDocs
) where

Expand Down Expand Up @@ -210,6 +212,7 @@ data AppCommand appState =
{ commandName :: !Text
, commandImplementation :: !(JSON.Value -> Command appState JSON.Value)
, commandParamDocs :: ![(Text, Doc.Block)]
, commandReturnFieldDocs :: ![(Text, Doc.Block)]
, commandDocs :: !Doc.Block
}

Expand All @@ -218,6 +221,7 @@ data AppQuery appState =
{ queryName :: !Text
, queryImplementation :: !(JSON.Value -> Query appState JSON.Value)
, queryParamDocs :: ![(Text, Doc.Block)]
, queryReturnFieldDocs :: ![(Text, Doc.Block)]
, queryDocs :: !Doc.Block
}

Expand Down Expand Up @@ -249,6 +253,11 @@ methodParamDocs (CommandMethod m) = commandParamDocs m
methodParamDocs (QueryMethod m) = queryParamDocs m
methodParamDocs (NotificationMethod m) = notificationParamDocs m

methodReturnFieldDocs :: AppMethod appState -> [(Text, Doc.Block)]
methodReturnFieldDocs (CommandMethod m) = commandReturnFieldDocs m
methodReturnFieldDocs (QueryMethod m) = queryReturnFieldDocs m
methodReturnFieldDocs NotificationMethod{} = []

methodDocs :: AppMethod appState -> Doc.Block
methodDocs (CommandMethod m) = commandDocs m
methodDocs (QueryMethod m) = queryDocs m
Expand All @@ -262,7 +271,7 @@ methodDocs (NotificationMethod m) = notificationDocs m
-- exception.
command ::
forall params result state.
(JSON.FromJSON params, Doc.DescribedParams params, JSON.ToJSON result) =>
(JSON.FromJSON params, Doc.DescribedMethod params result, JSON.ToJSON result) =>
Text ->
Doc.Block ->
(params -> Command state result) ->
Expand All @@ -277,6 +286,7 @@ command name doc f =
{ commandName = name
, commandImplementation = impl
, commandParamDocs = Doc.parameterFieldDescription @params
, commandReturnFieldDocs = Doc.resultFieldDescription @params @result
, commandDocs = doc
}

Expand All @@ -287,7 +297,7 @@ command name doc f =
-- exception.
query ::
forall params result state.
(JSON.FromJSON params, Doc.DescribedParams params, JSON.ToJSON result) =>
(JSON.FromJSON params, Doc.DescribedMethod params result, JSON.ToJSON result) =>
Text ->
Doc.Block ->
(params -> Query state result) ->
Expand All @@ -302,6 +312,7 @@ query name doc f =
{ queryName = name
, queryImplementation = impl
, queryParamDocs = Doc.parameterFieldDescription @params
, queryReturnFieldDocs = Doc.resultFieldDescription @params @result
, queryDocs = doc
}

Expand All @@ -312,7 +323,7 @@ query name doc f =
-- exception.
notification ::
forall params state.
(JSON.FromJSON params, Doc.DescribedParams params) =>
(JSON.FromJSON params, Doc.DescribedMethod params ()) =>
Text ->
Doc.Block ->
(params -> Notification ()) ->
Expand Down Expand Up @@ -439,13 +450,23 @@ mkApp name docs opts initAppState methods = do
docs ++
[Doc.Section "Methods"
[ Doc.Section (methodName m <> " (" <> methodKind m <> ")")
[ if null (methodParamDocs m)
then Doc.Paragraph [Doc.Text "No parameters"]
else Doc.DescriptionList
[ (Doc.Literal field :| [], fieldDocs)
| (field, fieldDocs) <- (methodParamDocs m)
]
, (methodDocs m)
[ methodDocs m
, Doc.Section "Parameter fields"
[ if null (methodParamDocs m)
then Doc.Paragraph [Doc.Text "No parameters"]
else Doc.DescriptionList
[ (Doc.Literal field :| [], fieldDocs)
| (field, fieldDocs) <- methodParamDocs m
]
]
, Doc.Section "Return fields"
[ if null (methodReturnFieldDocs m)
then Doc.Paragraph [Doc.Text "No return fields"]
else Doc.DescriptionList
[ (Doc.Literal field :| [], fieldDocs)
| (field, fieldDocs) <- methodReturnFieldDocs m
]
]
]
| m <- methods
]]
Expand Down
40 changes: 33 additions & 7 deletions argo/src/Argo/Doc.hs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

module Argo.Doc (LinkTarget(..), Block(..), Inline(..), Described(..), DescribedParams(..), datatype) where
module Argo.Doc (LinkTarget(..), Block(..), Inline(..), Described(..), DescribedMethod(..), datatype) where

import Data.List.NonEmpty
import Data.Text (Text)
Expand Down Expand Up @@ -35,14 +38,37 @@ class Described a where
description :: [Block]


-- | This class provides the canonical documentation for a datatype
-- that is deserialized as parameters to some method via a @FromJSON@
-- instance. The type variable does not occur in the method's
-- signature because it is intended to be used with the
-- @TypeApplications@ extension to GHC Haskell.
class DescribedParams a where
-- | This class provides the canonical documentation for a pair of datatypes,
-- where:
--
-- * The first datatype (@params@) is deserialized as parameters to some method
-- via a @FromJSON@ instance, and
--
-- * The second datatype (@result@) is serialized as the result returned by the
-- same method via a @ToJSON@ instance.
--
-- The @params@ type is almost always a custom data type defined for the
-- purpose of interfacing with RPC, which is why @result@ has a functional
-- dependency on @params@. On the other hand, it is common for @result@ to be
-- off-the-shelf data types such as @Value@ (for methods that return a JSON
-- object) or @()@ (for methods that do not return any values).
--
-- Neither @params@ nor @result@ occur in the signatures of the methods
-- because they are intended to be used with the @TypeApplications@ extension
-- to GHC Haskell.
class DescribedMethod params result | params -> result where
-- | Documentation for the parameters expected by the method.
parameterFieldDescription :: [(Text, Block)]

-- | Documentation for the result returned by the method.
--
-- If the method does not return anything—that is, if @result@ is
-- @()@—then this method does not need to be implemented, as it will be
-- defaulted appropriately.
resultFieldDescription :: [(Text, Block)]
default resultFieldDescription :: (result ~ ()) => [(Text, Block)]
resultFieldDescription = []

datatype :: forall a . (Typeable a, Described a) => Block
datatype =
Datatype (typeRep (Proxy @a)) (typeName @a) (description @a)
111 changes: 102 additions & 9 deletions file-echo-api/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -93,55 +93,110 @@ Methods
load (command)
~~~~~~~~~~~~~~

Load a file from disk into memory.

Parameter fields
++++++++++++++++


``file path``
The file to read into memory.


Load a file from disk into memory.

Return fields
+++++++++++++

No return fields



clear (command)
~~~~~~~~~~~~~~~

Forget the loaded file.

Parameter fields
++++++++++++++++

No parameters

Forget the loaded file.

Return fields
+++++++++++++

No return fields



prepend (command)
~~~~~~~~~~~~~~~~~

Append a string to the left of the current contents.

Parameter fields
++++++++++++++++


``content``
The string to append to the left of the current file content on the server.


Append a string to the left of the current contents.

Return fields
+++++++++++++

No return fields



drop (command)
~~~~~~~~~~~~~~

Drop from the left of the current contents.

Parameter fields
++++++++++++++++


``count``
The number of characters to drop from the left of the current file content on the server.


Drop from the left of the current contents.

Return fields
+++++++++++++

No return fields



implode (query)
~~~~~~~~~~~~~~~

Throw an error immediately.

Parameter fields
++++++++++++++++

No parameters

Throw an error immediately.

Return fields
+++++++++++++

No return fields



show (query)
~~~~~~~~~~~~

Show a substring of the file.

Parameter fields
++++++++++++++++


``start``
Start index (inclusive). If not provided, the substring is from the beginning of the file.
Expand All @@ -152,37 +207,75 @@ show (query)
End index (exclusive). If not provided, the remainder of the file is returned.


Show a substring of the file.

Return fields
+++++++++++++


``value``
The substring ranging from ``start`` to ``end``.




ignore (query)
~~~~~~~~~~~~~~

Ignore an :ref:`ignorable value <Ignorable>`.

Parameter fields
++++++++++++++++


``to be ignored``
The value to be ignored goes here.


Ignore an :ref:`ignorable value <Ignorable>`.

Return fields
+++++++++++++

No return fields



destroy state (notification)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Destroy a state in the server.

Parameter fields
++++++++++++++++


``state to destroy``
The state to destroy in the server (so it can be released from memory).


Destroy a state in the server.

Return fields
+++++++++++++

No return fields



destroy all states (notification)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Destroy all states in the server.

Parameter fields
++++++++++++++++

No parameters

Destroy all states in the server.

Return fields
+++++++++++++

No return fields




Expand Down
Loading

0 comments on commit b9b3edd

Please sign in to comment.