Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: expose type of selection via openapi #1050

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions primer-service/exe-server/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,8 @@ instance ConvertLogMessage PrimerErr LogMsg where
convert (ApplyActionError as e) = LogMsg $ show (as, e)
convert (UndoError e) = LogMsg $ show e
convert (RedoError e) = LogMsg $ show e
convert (SetSelectionError sel e) = LogMsg $ show (sel, e)
convert (SetSelectionNothing sel) = LogMsg $ show sel

instance ConvertLogMessage APILog LogMsg where
convert = LogMsg . show
Expand Down
2 changes: 2 additions & 0 deletions primer-service/src/Primer/OpenAPI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import Primer.API (
Prog,
Selection (..),
Tree,
TypeOrKind,
)
import Primer.API qualified as API
import Primer.API.NodeFlavor (
Expand Down Expand Up @@ -149,6 +150,7 @@ deriving via PrimerJSON ApplyActionBody instance ToSchema ApplyActionBody
deriving via PrimerJSON Selection instance ToSchema Selection
deriving via PrimerJSON NodeSelection instance ToSchema NodeSelection
deriving via PrimerJSON NodeType instance ToSchema NodeType
deriving via PrimerJSON TypeOrKind instance ToSchema TypeOrKind
deriving via PrimerJSON Level instance ToSchema Level
deriving via PrimerJSON NewSessionReq instance ToSchema NewSessionReq

Expand Down
9 changes: 8 additions & 1 deletion primer-service/src/Primer/Servant/OpenAPI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ module Primer.Servant.OpenAPI (
import Foreword

import Data.OpenApi (OpenApi, ToSchema)
import Primer.API (ApplyActionBody, EvalFullResp, Prog, Selection)
import Primer.API (ApplyActionBody, EvalFullResp, Prog, Selection, TypeOrKind)
import Primer.Action.Available qualified as Available
import Primer.App (Level)
import Primer.Core (GVarName, ModuleName)
Expand Down Expand Up @@ -98,6 +98,13 @@ data SessionAPI mode = SessionAPI
:> Get '[JSON] Prog
, getSessionName :: GetSessionName mode
, setSessionName :: SetSessionName mode
, setSelection ::
mode
:- "selection"
:> Summary "Set the current selection, and obtain the type of the selected node"
:> OperationId "setSelection"
:> ReqBody '[JSON] Selection
:> Post '[JSON] TypeOrKind
, createDefinition ::
mode
:- "def"
Expand Down
3 changes: 3 additions & 0 deletions primer-service/src/Primer/Server.hs
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ openAPISessionServer sid =
, OpenAPI.getProgram = API.getProgram' sid
, OpenAPI.getSessionName = API.getSessionName sid
, OpenAPI.setSessionName = renameSession sid
, OpenAPI.setSelection = API.setSelection' sid
, OpenAPI.createDefinition = createDefinition sid
, OpenAPI.typeDef = openAPITypeDefServer sid
, OpenAPI.actions = openAPIActionServer sid
Expand Down Expand Up @@ -425,5 +426,7 @@ serve ss q v port origins logger = do
ToProgActionError a ae -> err400{errBody = "Error while converting action (" <> show a <> "): " <> show ae}
UndoError pe -> err500{errBody = "Undo failed: " <> show pe}
RedoError pe -> err500{errBody = "Redo failed: " <> show pe}
SetSelectionError sel pe -> err400{errBody = "Error while setting selection (" <> show sel <> "): " <> show pe}
SetSelectionNothing sel -> err400{errBody = "Error while setting selection (" <> show sel <> "): setSelection returned Nothing"}
where
encode = LT.encodeUtf8 . LT.fromStrict
3 changes: 3 additions & 0 deletions primer-service/test/Tests/OpenAPI.hs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import Primer.API (
Prog (Prog),
Selection (..),
Tree,
TypeOrKind (..),
viewTreeExpr,
viewTreeType,
)
Expand Down Expand Up @@ -328,3 +329,5 @@ instance Arbitrary CreateTypeDefBody where
arbitrary = CreateTypeDefBody <$> arbitrary <*> arbitrary <*> arbitrary
instance Arbitrary NewSessionReq where
arbitrary = NewSessionReq <$> arbitrary
instance Arbitrary TypeOrKind where
arbitrary = hedgehog $ G.choice [Type <$> genTree, Kind <$> genTree]
90 changes: 89 additions & 1 deletion primer-service/test/outputs/OpenAPI/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,10 @@
"TEmptyHole",
"THole",
"TFun",
"TApp"
"TApp",
"KType",
"KHole",
"KFun"
],
"type": "string"
},
Expand Down Expand Up @@ -716,6 +719,46 @@
],
"type": "object"
},
"TypeOrKind": {
"oneOf": [
{
"properties": {
"contents": {
"$ref": "#/components/schemas/Tree"
},
"tag": {
"enum": [
"Type"
],
"type": "string"
}
},
"required": [
"tag",
"contents"
],
"type": "object"
},
{
"properties": {
"contents": {
"$ref": "#/components/schemas/Tree"
},
"tag": {
"enum": [
"Kind"
],
"type": "string"
}
},
"required": [
"tag",
"contents"
],
"type": "object"
}
]
},
"UUID": {
"example": "00000000-0000-0000-0000-000000000000",
"format": "uuid",
Expand Down Expand Up @@ -1371,6 +1414,51 @@
"summary": "Redo the last undo"
}
},
"/openapi/sessions/{sessionId}/selection": {
"post": {
"operationId": "setSelection",
"parameters": [
{
"description": "The session ID",
"in": "path",
"name": "sessionId",
"required": true,
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"requestBody": {
"content": {
"application/json;charset=utf-8": {
"schema": {
"$ref": "#/components/schemas/Selection"
}
}
}
},
"responses": {
"200": {
"content": {
"application/json;charset=utf-8": {
"schema": {
"$ref": "#/components/schemas/TypeOrKind"
}
}
},
"description": ""
},
"400": {
"description": "Invalid `body`"
},
"404": {
"description": "`sessionId` not found"
}
},
"summary": "Set the current selection, and obtain the type of the selected node"
}
},
"/openapi/sessions/{sessionId}/typedef": {
"post": {
"operationId": "createTypeDef",
Expand Down
98 changes: 96 additions & 2 deletions primer/src/Primer/API.hs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ module Primer.API (
undoAvailable,
redoAvailable,
Name (..),
TypeOrKind (..),
setSelection,
setSelection',
) where

import Foreword
Expand All @@ -96,10 +99,10 @@ import Control.Monad.Writer (MonadWriter)
import Control.Monad.Zip (MonadZip)
import Data.Map qualified as Map
import Data.Tuple.Extra (curry3)
import Optics (ifoldr, over, traverseOf, view, (^.))
import Optics (ifoldr, over, preview, to, traverseOf, view, (%), (^.), _Just)
import Primer.API.NodeFlavor qualified as Flavor
import Primer.API.RecordPair (RecordPair (RecordPair))
import Primer.Action (ActionError, ProgAction, toProgActionInput, toProgActionNoInput)
import Primer.Action (Action (SetCursor), ActionError, ProgAction (BodyAction, MoveToDef, SigAction), toProgActionInput, toProgActionNoInput)
import Primer.Action.Available qualified as Available
import Primer.App (
App,
Expand Down Expand Up @@ -139,6 +142,7 @@ import Primer.Core (
CaseBranch' (..),
Expr,
Expr' (..),
ExprMeta,
GVarName,
GlobalName (..),
HasID (..),
Expand All @@ -152,16 +156,21 @@ import Primer.Core (
TyVarName,
Type,
Type' (..),
TypeMeta,
ValConName,
getID,
unLocalName,
unsafeMkLocalName,
_synthed,
_type,
_typeMeta,
_typeMetaLens,
)
import Primer.Core.DSL (create')
import Primer.Core.DSL qualified as DSL
import Primer.Core.Meta (LocalName)
import Primer.Core.Meta qualified as Core
import Primer.Core.Utils (generateTypeIDs)
import Primer.Database (
OffsetLimit,
OpStatus,
Expand Down Expand Up @@ -263,6 +272,8 @@ data PrimerErr
| ApplyActionError [ProgAction] ProgError
| UndoError ProgError
| RedoError ProgError
| SetSelectionError Selection ProgError
| SetSelectionNothing Selection
deriving stock (Show)

instance Exception PrimerErr
Expand Down Expand Up @@ -388,6 +399,7 @@ data APILog
| ApplyActionInput (ReqResp (SessionId, ApplyActionBody, Available.InputAction) Prog)
| Undo (ReqResp SessionId Prog)
| Redo (ReqResp SessionId Prog)
| SetSelection (ReqResp (SessionId, Selection) (Maybe TypeOrKind))
deriving stock (Show, Read)

type MonadAPILog l m = (MonadLog (WithSeverity l) m, ConvertLogMessage APILog l)
Expand Down Expand Up @@ -910,6 +922,42 @@ viewTreeType' t0 = case t0 of
where
nodeId = t0 ^. _typeMetaLens

-- | Like 'viewTreeType', but for kinds. This generates ids
viewTreeKind :: Kind -> Tree
viewTreeKind = flip evalState (0 :: Integer) . go
where
go k = do
id' <- get
let nodeId = "kind" <> show id'
modify succ
case k of
KType ->
pure $
Tree
{ nodeId
, body = NoBody Flavor.KType
, childTrees = []
, rightChild = Nothing
}
KHole ->
pure $
Tree
{ nodeId
, body = NoBody Flavor.KHole
, childTrees = []
, rightChild = Nothing
}
KFun k1 k2 -> do
k1tree <- go k1
k2tree <- go k2
pure $
Tree
{ nodeId
, body = NoBody Flavor.KFun
, childTrees = [k1tree, k2tree]
, rightChild = Nothing
}

globalName :: GlobalName k -> Name
globalName n = Name{qualifiedModule = Just $ Core.qualifiedModule n, baseName = Core.baseName n}

Expand Down Expand Up @@ -1171,3 +1219,49 @@ data NodeSelection = NodeSelection

viewNodeSelection :: App.NodeSelection -> NodeSelection
viewNodeSelection sel@App.NodeSelection{nodeType} = NodeSelection{nodeType, id = getID sel}

data TypeOrKind = Type Tree | Kind Tree
deriving stock (Eq, Show, Read, Generic)
deriving (FromJSON, ToJSON) via PrimerJSON TypeOrKind
deriving anyclass (NFData)

setSelection :: (MonadIO m, MonadThrow m, MonadAPILog l m) => SessionId -> Selection -> PrimerM m (Maybe TypeOrKind)
setSelection = curry $ logAPI (noError SetSelection) $ \(sid, sel) ->
edit sid (App.Edit $ MoveToDef sel.def : selNode sel)
>>= either (throwM . SetSelectionError sel) (pure . (viewTypeOrKind <=< progSelection))
where
selNode sel = case sel.node of
Nothing -> []
Just NodeSelection{id, nodeType = BodyNode} -> [BodyAction [SetCursor id]]
Just NodeSelection{id, nodeType = SigNode} -> [SigAction [SetCursor id]]
viewTypeOrKind :: App.Selection -> Maybe TypeOrKind
viewTypeOrKind sel = either viewExprType viewTypeKind . (.meta) <$> sel.selectedNode
trivialTree = Tree{nodeId = "seltype-0", childTrees = [], rightChild = Nothing, body = NoBody Flavor.EmptyHole}
viewExprType :: ExprMeta -> TypeOrKind
viewExprType = Type . fromMaybe trivialTree . viewExprType'
viewExprType' :: ExprMeta -> Maybe Tree
viewExprType' = preview $ _type % _Just % _synthed % to (viewTreeType' . mkIds)
-- We prefix ids to keep them unique from other ids in the emitted program
mkIds :: Type' () -> Type' Text
mkIds = over _typeMeta (("seltype-" <>) . show . getID) . create' . generateTypeIDs
viewTypeKind :: TypeMeta -> TypeOrKind
viewTypeKind = Kind . fromMaybe trivialTree . viewTypeKind'
viewTypeKind' :: TypeMeta -> Maybe Tree
viewTypeKind' = preview $ _type % _Just % to viewTreeKind

-- | Strip 'Maybe' off of 'setSelection''s return type.
--
-- For technical reasons owing to our typechecker's design,
-- 'setSelection' returns a 'Maybe TypeOrKind', but that curently
-- causes problems with our OpenAPI adapter due to the OpenAPI 3.0
-- specification not properly supporting nullable results. To work
-- around this, we strip 'setSelection''s return type down to
-- 'TypeOrKind' in normal operation, and throw an error otherwise. In
-- practice, this error should never occur (though 'setSelection' may
-- of course fail for other reasons; e.g., the client attempts to
-- select invalid ID).
--
-- When we can update to OpenAPI 3.1, or our adapter otherwise adds
-- support for nullable results, we may drop this method.
setSelection' :: (MonadIO m, MonadThrow m, MonadAPILog l m) => SessionId -> Selection -> PrimerM m TypeOrKind
setSelection' sid sel = setSelection sid sel >>= maybe (throwM $ SetSelectionNothing sel) pure
3 changes: 3 additions & 0 deletions primer/src/Primer/API/NodeFlavor.hs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ data NodeFlavorNoBody
| THole
| TFun
| TApp
| KType
| KHole
| KFun
deriving stock (Show, Read, Eq, Generic, Enum, Bounded)
deriving (ToJSON, FromJSON) via PrimerJSON NodeFlavorNoBody
deriving anyclass (NFData)