Allow testing Share API in transcripts (#3062)

* Support API calls in transcripts
This commit is contained in:
Chris Penner 2022-05-02 09:55:15 -06:00 committed by GitHub
parent ca2ea7039a
commit c073117871
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 947 additions and 29 deletions

View File

@ -45,6 +45,7 @@ dependencies:
- unliftio
- network-uri
- aeson
- aeson-pretty
- http-client >= 0.7.6
- http-client-tls
- http-types

View File

@ -21,6 +21,9 @@ where
import Control.Concurrent.STM (atomically)
import Control.Lens (view)
import qualified Crypto.Random as Random
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Encode.Pretty as Aeson
import qualified Data.ByteString.Lazy.Char8 as BL
import qualified Data.Char as Char
import qualified Data.Configurator as Configurator
import Data.Configurator.Types (Config)
@ -28,6 +31,7 @@ import Data.IORef
import Data.List (isSubsequenceOf)
import qualified Data.Map as Map
import qualified Data.Text as Text
import qualified Network.HTTP.Client as HTTP
import System.Directory (doesFileExist)
import System.Exit (die)
import qualified System.IO as IO
@ -54,14 +58,15 @@ import Unison.Parser.Ann (Ann)
import Unison.Prelude
import Unison.PrettyTerminal
import qualified Unison.Runtime.Interface as RTI
import qualified Unison.Server.CodebaseServer as Server
import Unison.Symbol (Symbol)
import qualified Unison.Util.Pretty as P
import qualified Unison.Util.Pretty as Pretty
import qualified Unison.Util.TQueue as Q
import qualified UnliftIO
import Prelude hiding (readFile, writeFile)
-- | Render transcript errors at a width of 65 chars.
terminalWidth :: P.Width
terminalWidth :: Pretty.Width
terminalWidth = 65
type ExpectingError = Bool
@ -77,9 +82,18 @@ data UcmLine
= UcmCommand Path.Absolute Text
| UcmComment Text -- Text does not include the '--' prefix.
data APIRequest
= GetRequest Text
| APIComment Text
instance Show APIRequest where
show (GetRequest txt) = "GET " <> Text.unpack txt
show (APIComment txt) = "-- " <> Text.unpack txt
data Stanza
= Ucm Hidden ExpectingError [UcmLine]
| Unison Hidden ExpectingError (Maybe ScratchFileName) Text
| API [APIRequest]
| UnprocessedFence FenceType Text
| Unfenced Text
@ -110,6 +124,13 @@ instance Show Stanza where
""
]
]
API apiRequests ->
"```api\n"
<> ( apiRequests
& fmap (\(GetRequest txt) -> Text.unpack txt)
& unlines
)
<> "```\n"
UnprocessedFence typ txt ->
unlines
[ "```" <> Text.unpack typ,
@ -150,10 +171,11 @@ withTranscriptRunner ::
withTranscriptRunner ucmVersion configFile action = do
withRuntime $ \runtime -> withConfig $ \config -> do
action $ \transcriptName transcriptSrc (codebaseDir, codebase) -> do
let parsed = parse transcriptName transcriptSrc
result <- for parsed $ \stanzas -> do
liftIO $ run codebaseDir stanzas codebase runtime config ucmVersion
pure $ join @(Either TranscriptError) result
Server.startServer Server.defaultCodebaseServerOpts runtime codebase $ \baseUrl -> do
let parsed = parse transcriptName transcriptSrc
result <- for parsed $ \stanzas -> do
liftIO $ run codebaseDir stanzas codebase runtime config ucmVersion (tShow baseUrl)
pure $ join @(Either TranscriptError) result
where
withRuntime :: ((Runtime.Runtime Symbol -> m a) -> m a)
withRuntime action =
@ -181,11 +203,13 @@ run ::
Runtime.Runtime Symbol ->
Maybe Config ->
UCMVersion ->
Text ->
IO (Either TranscriptError Text)
run dir stanzas codebase runtime config ucmVersion = UnliftIO.try $ do
run dir stanzas codebase runtime config ucmVersion baseURL = UnliftIO.try $ do
httpManager <- HTTP.newManager HTTP.defaultManagerSettings
let initialPath = Path.absoluteEmpty
putPrettyLn $
P.lines
Pretty.lines
[ asciiartUnison,
"",
"Running the provided transcript file...",
@ -224,6 +248,22 @@ run dir stanzas codebase runtime config ucmVersion = UnliftIO.try $ do
output = output' False
outputEcho = output' True
apiRequest :: APIRequest -> IO ()
apiRequest req = do
output (show req <> "\n")
case req of
(APIComment {}) -> pure ()
(GetRequest path) -> do
req <- case HTTP.parseRequest (Text.unpack $ baseURL <> path) of
Left err -> dieWithMsg (show err)
Right req -> pure req
respBytes <- HTTP.httpLbs req httpManager
case Aeson.eitherDecode (HTTP.responseBody respBytes) of
Right (v :: Aeson.Value) -> do
let prettyBytes = Aeson.encodePretty' (Aeson.defConfig {Aeson.confCompare = compare}) v
output . (<> "\n") . BL.unpack $ prettyBytes
Left err -> dieWithMsg ("Error decoding response from " <> Text.unpack path <> ": " <> err)
awaitInput :: IO (Either Event Input)
awaitInput = do
cmd <- atomically (Q.tryDequeue cmdQueue)
@ -257,7 +297,7 @@ run dir stanzas codebase runtime config ucmVersion = UnliftIO.try $ do
currentRoot <- Branch.head <$> readIORef rootBranchRef
case parseInput currentRoot curPath numberedArgs patternMap args of
-- invalid command is treated as a failure
Left msg -> dieWithMsg $ P.toPlain terminalWidth msg
Left msg -> dieWithMsg $ Pretty.toPlain terminalWidth msg
Right input -> pure $ Right input
Nothing -> do
dieUnexpectedSuccess
@ -290,6 +330,11 @@ run dir stanzas codebase runtime config ucmVersion = UnliftIO.try $ do
atomically . Q.enqueue cmdQueue $ Nothing
modifyIORef' unisonFiles (Map.insert (fromMaybe "scratch.u" filename) txt)
pure $ Left (UnisonFileChanged (fromMaybe "scratch.u" filename) txt)
API apiRequests -> do
output "```api\n"
for_ apiRequests apiRequest
output "```"
awaitInput
Ucm hide errOk cmds -> do
writeIORef hidden hide
writeIORef allowErrors errOk
@ -317,7 +362,7 @@ run dir stanzas codebase runtime config ucmVersion = UnliftIO.try $ do
print o = do
msg <- notifyUser dir o
errOk <- readIORef allowErrors
let rendered = P.toPlain terminalWidth (P.border 2 msg)
let rendered = Pretty.toPlain terminalWidth (Pretty.border 2 msg)
output rendered
when (Output.isFailure o) $
if errOk
@ -327,7 +372,7 @@ run dir stanzas codebase runtime config ucmVersion = UnliftIO.try $ do
printNumbered o = do
let (msg, numberedArgs) = notifyNumbered o
errOk <- readIORef allowErrors
let rendered = P.toPlain terminalWidth (P.border 2 msg)
let rendered = Pretty.toPlain terminalWidth (Pretty.border 2 msg)
output rendered
when (Output.isNumberedFailure o) $
if errOk
@ -441,31 +486,50 @@ ucmLine = ucmCommand <|> ucmComment
line <- P.takeWhileP Nothing (/= '\n') <* spaces
pure $ UcmComment line
apiRequest :: P APIRequest
apiRequest = do
apiComment <|> getRequest
where
getRequest = do
word "GET"
spaces
path <- P.takeWhile1P Nothing (/= '\n')
spaces
pure (GetRequest path)
apiComment = do
word "--"
comment <- P.takeWhileP Nothing (/= '\n')
spaces
pure (APIComment comment)
fenced :: P Stanza
fenced = do
fence
fenceType <- lineToken (word "ucm" <|> word "unison" <|> language)
fenceType <- lineToken (word "ucm" <|> word "unison" <|> word "api" <|> language)
stanza <-
if fenceType == "ucm"
then do
case fenceType of
"ucm" -> do
hide <- hidden
err <- expectingError
_ <- spaces
cmds <- many ucmLine
pure $ Ucm hide err cmds
else
if fenceType == "unison"
then do
-- todo: this has to be more interesting
-- ```unison:hide
-- ```unison
-- ```unison:hide:all scratch.u
hide <- lineToken hidden
err <- lineToken expectingError
fileName <- optional untilSpace1
blob <- spaces *> untilFence
pure $ Unison hide err fileName blob
else UnprocessedFence fenceType <$> untilFence
"unison" ->
do
-- todo: this has to be more interesting
-- ```unison:hide
-- ```unison
-- ```unison:hide:all scratch.u
hide <- lineToken hidden
err <- lineToken expectingError
fileName <- optional untilSpace1
blob <- spaces *> untilFence
pure $ Unison hide err fileName blob
"api" -> do
_ <- spaces
apiRequests <- many apiRequest
pure $ API apiRequests
_ -> UnprocessedFence fenceType <$> untilFence
fence
pure stanza

View File

@ -92,6 +92,7 @@ library
build-depends:
ListLike
, aeson
, aeson-pretty
, async
, base
, bytestring
@ -181,6 +182,7 @@ executable cli-integration-tests
build-depends:
ListLike
, aeson
, aeson-pretty
, async
, base
, bytestring
@ -268,6 +270,7 @@ executable transcripts
build-depends:
ListLike
, aeson
, aeson-pretty
, async
, base
, bytestring
@ -359,6 +362,7 @@ executable unison
build-depends:
ListLike
, aeson
, aeson-pretty
, async
, base
, bytestring
@ -455,6 +459,7 @@ test-suite cli-tests
build-depends:
ListLike
, aeson
, aeson-pretty
, async
, base
, bytestring

View File

@ -248,13 +248,22 @@ data CodebaseServerOpts = CodebaseServerOpts
}
deriving (Show, Eq)
defaultCodebaseServerOpts :: CodebaseServerOpts
defaultCodebaseServerOpts =
CodebaseServerOpts
{ token = Nothing,
host = Nothing,
port = Nothing,
codebaseUIPath = Nothing
}
-- The auth token required for accessing the server is passed to the function k
startServer ::
CodebaseServerOpts ->
Rt.Runtime Symbol ->
Codebase IO Symbol Ann ->
(BaseUrl -> IO ()) ->
IO ()
(BaseUrl -> IO a) ->
IO a
startServer opts rt codebase onStart = do
-- the `canonicalizePath` resolves symlinks
exePath <- canonicalizePath =<< getExecutablePath

View File

@ -0,0 +1,26 @@
# find api
```unison
rachel.filesystem.x = 42
ross.httpClient.y = 43
joey.httpServer.z = 44
joey.yaml.zz = 45
```
```ucm
.> add
```
```api
-- Namespace segment prefix search
GET /api/find?query=http
-- Namespace segment suffix search
GET /api/find?query=Server
-- Substring search
GET /api/find?query=lesys
-- Cross-segment search
GET /api/find?query=joey.http
```

View File

@ -0,0 +1,253 @@
# find api
```unison
rachel.filesystem.x = 42
ross.httpClient.y = 43
joey.httpServer.z = 44
joey.yaml.zz = 45
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
joey.httpServer.z : ##Nat
joey.yaml.zz : ##Nat
rachel.filesystem.x : ##Nat
ross.httpClient.y : ##Nat
```
```ucm
.> add
⍟ I've added these definitions:
joey.httpServer.z : ##Nat
joey.yaml.zz : ##Nat
rachel.filesystem.x : ##Nat
ross.httpClient.y : ##Nat
```
```api
-- Namespace segment prefix search
GET /api/find?query=http
[
[
{
"result": {
"segments": [
{
"contents": "ross.",
"tag": "Gap"
},
{
"contents": "http",
"tag": "Match"
},
{
"contents": "Client.y",
"tag": "Gap"
}
]
},
"score": 156
},
{
"contents": {
"bestFoundTermName": "y",
"namedTerm": {
"termHash": "#emomp74i93h6ps0b5sukke0tci0ooba3f9jk21qm919a7act9u7asani84c0mqbdk4lcjrdvr9olpedp23p6df78r4trqlg0cciadc8",
"termName": "ross.httpClient.y",
"termTag": null,
"termType": [
{
"annotation": {
"contents": "##Nat",
"tag": "HashQualifier"
},
"segment": "##Nat"
}
]
}
},
"tag": "FoundTermResult"
}
],
[
{
"result": {
"segments": [
{
"contents": "joey.",
"tag": "Gap"
},
{
"contents": "http",
"tag": "Match"
},
{
"contents": "Server.z",
"tag": "Gap"
}
]
},
"score": 156
},
{
"contents": {
"bestFoundTermName": "z",
"namedTerm": {
"termHash": "#a84tg4er4kfl9k2p250vp2o1dsp5kmn9a7q8g2bo723qbtbf9sagrl28fa4q0j5f2cv4alsjik6rf487ss646qt95gbm3dd13k7e1fo",
"termName": "joey.httpServer.z",
"termTag": null,
"termType": [
{
"annotation": {
"contents": "##Nat",
"tag": "HashQualifier"
},
"segment": "##Nat"
}
]
}
},
"tag": "FoundTermResult"
}
]
]
-- Namespace segment suffix search
GET /api/find?query=Server
[
[
{
"result": {
"segments": [
{
"contents": "joey.http",
"tag": "Gap"
},
{
"contents": "Server",
"tag": "Match"
},
{
"contents": ".z",
"tag": "Gap"
}
]
},
"score": 223
},
{
"contents": {
"bestFoundTermName": "z",
"namedTerm": {
"termHash": "#a84tg4er4kfl9k2p250vp2o1dsp5kmn9a7q8g2bo723qbtbf9sagrl28fa4q0j5f2cv4alsjik6rf487ss646qt95gbm3dd13k7e1fo",
"termName": "joey.httpServer.z",
"termTag": null,
"termType": [
{
"annotation": {
"contents": "##Nat",
"tag": "HashQualifier"
},
"segment": "##Nat"
}
]
}
},
"tag": "FoundTermResult"
}
]
]
-- Substring search
GET /api/find?query=lesys
[
[
{
"result": {
"segments": [
{
"contents": "rachel.fi",
"tag": "Gap"
},
{
"contents": "lesys",
"tag": "Match"
},
{
"contents": "tem.x",
"tag": "Gap"
}
]
},
"score": 175
},
{
"contents": {
"bestFoundTermName": "x",
"namedTerm": {
"termHash": "#qkhkl0n238s1eqibd1ecb8605sqj1m4hpoaag177cu572otqlaf1u28c8suuuqgljdtthsjtr07rv04np05o6oa27ml9105k7uas0t8",
"termName": "rachel.filesystem.x",
"termTag": null,
"termType": [
{
"annotation": {
"contents": "##Nat",
"tag": "HashQualifier"
},
"segment": "##Nat"
}
]
}
},
"tag": "FoundTermResult"
}
]
]
-- Cross-segment search
GET /api/find?query=joey.http
[
[
{
"result": {
"segments": [
{
"contents": "joey.http",
"tag": "Match"
},
{
"contents": "Server.z",
"tag": "Gap"
}
]
},
"score": 300
},
{
"contents": {
"bestFoundTermName": "z",
"namedTerm": {
"termHash": "#a84tg4er4kfl9k2p250vp2o1dsp5kmn9a7q8g2bo723qbtbf9sagrl28fa4q0j5f2cv4alsjik6rf487ss646qt95gbm3dd13k7e1fo",
"termName": "joey.httpServer.z",
"termTag": null,
"termType": [
{
"annotation": {
"contents": "##Nat",
"tag": "HashQualifier"
},
"segment": "##Nat"
}
]
}
},
"tag": "FoundTermResult"
}
]
]
```

View File

@ -0,0 +1,22 @@
# Get Definitions Test
```ucm:hide
.> builtins.mergeio
```
```unison
{{ Documentation }}
nested.names.x = 42
```
```ucm
.> add
```
```api
-- Should find names by suffix
GET /api/getDefinition?names=x
-- Term names should strip relativeTo prefix.
GET /api/getDefinition?names=x&relativeTo=nested
```

View File

@ -0,0 +1,220 @@
# Get Definitions Test
```unison
{{ Documentation }}
nested.names.x = 42
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
nested.names.x : Nat
nested.names.x.doc : Doc2
```
```ucm
.> add
⍟ I've added these definitions:
nested.names.x : Nat
nested.names.x.doc : Doc2
```
```api
-- Should find names by suffix
GET /api/getDefinition?names=x
{
"missingDefinitions": [],
"termDefinitions": {
"#qkhkl0n238s1eqibd1ecb8605sqj1m4hpoaag177cu572otqlaf1u28c8suuuqgljdtthsjtr07rv04np05o6oa27ml9105k7uas0t8": {
"bestTermName": "x",
"defnTermTag": null,
"signature": [
{
"annotation": {
"contents": "##Nat",
"tag": "TypeReference"
},
"segment": "Nat"
}
],
"termDefinition": {
"contents": [
{
"annotation": {
"contents": "x",
"tag": "HashQualifier"
},
"segment": "x"
},
{
"annotation": {
"tag": "TypeAscriptionColon"
},
"segment": " :"
},
{
"annotation": null,
"segment": " "
},
{
"annotation": {
"contents": "##Nat",
"tag": "TypeReference"
},
"segment": "Nat"
},
{
"annotation": null,
"segment": "\n"
},
{
"annotation": {
"contents": "x",
"tag": "HashQualifier"
},
"segment": "x"
},
{
"annotation": {
"tag": "BindingEquals"
},
"segment": " ="
},
{
"annotation": null,
"segment": " "
},
{
"annotation": {
"tag": "NumericLiteral"
},
"segment": "42"
}
],
"tag": "UserObject"
},
"termDocs": [
[
"doc",
"#ulr9f75rpcrv79d7sfo2ep2tvbntu3e360lfomird2bdpj4bnea230e8o5j0b9our8vggocpa7eck3pus14fcfajlttat1bg71t6rbg",
{
"contents": [
{
"contents": "Documentation",
"tag": "Word"
}
],
"tag": "Paragraph"
}
]
],
"termNames": [
"nested.names.x"
]
}
},
"typeDefinitions": {}
}
-- Term names should strip relativeTo prefix.
GET /api/getDefinition?names=x&relativeTo=nested
{
"missingDefinitions": [],
"termDefinitions": {
"#qkhkl0n238s1eqibd1ecb8605sqj1m4hpoaag177cu572otqlaf1u28c8suuuqgljdtthsjtr07rv04np05o6oa27ml9105k7uas0t8": {
"bestTermName": "x",
"defnTermTag": null,
"signature": [
{
"annotation": {
"contents": "##Nat",
"tag": "TypeReference"
},
"segment": "Nat"
}
],
"termDefinition": {
"contents": [
{
"annotation": {
"contents": "x",
"tag": "HashQualifier"
},
"segment": "x"
},
{
"annotation": {
"tag": "TypeAscriptionColon"
},
"segment": " :"
},
{
"annotation": null,
"segment": " "
},
{
"annotation": {
"contents": "##Nat",
"tag": "TypeReference"
},
"segment": "Nat"
},
{
"annotation": null,
"segment": "\n"
},
{
"annotation": {
"contents": "x",
"tag": "HashQualifier"
},
"segment": "x"
},
{
"annotation": {
"tag": "BindingEquals"
},
"segment": " ="
},
{
"annotation": null,
"segment": " "
},
{
"annotation": {
"tag": "NumericLiteral"
},
"segment": "42"
}
],
"tag": "UserObject"
},
"termDocs": [
[
"doc",
"#ulr9f75rpcrv79d7sfo2ep2tvbntu3e360lfomird2bdpj4bnea230e8o5j0b9our8vggocpa7eck3pus14fcfajlttat1bg71t6rbg",
{
"contents": [
{
"contents": "Documentation",
"tag": "Word"
}
],
"tag": "Paragraph"
}
]
],
"termNames": [
"names.x"
]
}
},
"typeDefinitions": {}
}
```

View File

@ -0,0 +1,20 @@
# Namespace details api
```ucm:hide
.> builtins.mergeio
```
```unison
{{ Documentation }}
nested.names.x = 42
nested.names.readme = {{ I'm a readme! }}
```
```ucm
.> add
```
```api
GET /api/namespaces/nested.names
```

View File

@ -0,0 +1,56 @@
# Namespace details api
```unison
{{ Documentation }}
nested.names.x = 42
nested.names.readme = {{ I'm a readme! }}
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
nested.names.readme : Doc2
nested.names.x : Nat
nested.names.x.doc : Doc2
```
```ucm
.> add
⍟ I've added these definitions:
nested.names.readme : Doc2
nested.names.x : Nat
nested.names.x.doc : Doc2
```
```api
GET /api/namespaces/nested.names
{
"fqn": "nested.names",
"hash": "#oms19b4f9s3c8tb5skeb8jii95ij35n3hdg038pu6rv5b0fikqe4gd7lnu6a1i6aq5tdh2opdo4s0sfrupvk6vfkr9lf0n752gbl8o0",
"readme": {
"contents": [
{
"contents": "I'm",
"tag": "Word"
},
{
"contents": "a",
"tag": "Word"
},
{
"contents": "readme!",
"tag": "Word"
}
],
"tag": "Paragraph"
}
}
```

View File

@ -0,0 +1,22 @@
# Namespace list api
```ucm:hide
.> builtins.mergeio
```
```unison
{{ Documentation }}
nested.names.x = 42
nested.names.readme = {{ I'm a readme! }}
```
```ucm
.> add
```
```api
GET /api/list?namespace=nested.names
GET /api/list?namespace=names&relativeTo=nested
```

View File

@ -0,0 +1,130 @@
# Namespace list api
```unison
{{ Documentation }}
nested.names.x = 42
nested.names.readme = {{ I'm a readme! }}
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
nested.names.readme : Doc2
nested.names.x : Nat
nested.names.x.doc : Doc2
```
```ucm
.> add
⍟ I've added these definitions:
nested.names.readme : Doc2
nested.names.x : Nat
nested.names.x.doc : Doc2
```
```api
GET /api/list?namespace=nested.names
{
"namespaceListingChildren": [
{
"contents": {
"termHash": "#ddmmatmmiqsts2ku0i02kntd0s7rvcui4nn1cusio8thp9oqhbtilvcnhen52ibv43kr5q83f5er5q9h56s807k17tnelnrac7cch8o",
"termName": "readme",
"termTag": "Doc",
"termType": [
{
"annotation": {
"contents": "#ej86si0ur1",
"tag": "HashQualifier"
},
"segment": "#ej86si0ur1"
}
]
},
"tag": "TermObject"
},
{
"contents": {
"termHash": "#qkhkl0n238s1eqibd1ecb8605sqj1m4hpoaag177cu572otqlaf1u28c8suuuqgljdtthsjtr07rv04np05o6oa27ml9105k7uas0t8",
"termName": "x",
"termTag": null,
"termType": [
{
"annotation": {
"contents": "##Nat",
"tag": "HashQualifier"
},
"segment": "##Nat"
}
]
},
"tag": "TermObject"
},
{
"contents": {
"namespaceHash": "#n1egracfeljprftoktbjcase2hs4f4p8idbhs5ujipl42agld1810hrq9t7p7ped16aagni2cm1fjcjhho770jh80ipthhmg0cnsur0",
"namespaceName": "x"
},
"tag": "Subnamespace"
}
],
"namespaceListingFQN": "nested.names",
"namespaceListingHash": "#oms19b4f9s3c8tb5skeb8jii95ij35n3hdg038pu6rv5b0fikqe4gd7lnu6a1i6aq5tdh2opdo4s0sfrupvk6vfkr9lf0n752gbl8o0"
}
GET /api/list?namespace=names&relativeTo=nested
{
"namespaceListingChildren": [
{
"contents": {
"termHash": "#ddmmatmmiqsts2ku0i02kntd0s7rvcui4nn1cusio8thp9oqhbtilvcnhen52ibv43kr5q83f5er5q9h56s807k17tnelnrac7cch8o",
"termName": "readme",
"termTag": "Doc",
"termType": [
{
"annotation": {
"contents": "#ej86si0ur1",
"tag": "HashQualifier"
},
"segment": "#ej86si0ur1"
}
]
},
"tag": "TermObject"
},
{
"contents": {
"termHash": "#qkhkl0n238s1eqibd1ecb8605sqj1m4hpoaag177cu572otqlaf1u28c8suuuqgljdtthsjtr07rv04np05o6oa27ml9105k7uas0t8",
"termName": "x",
"termTag": null,
"termType": [
{
"annotation": {
"contents": "##Nat",
"tag": "HashQualifier"
},
"segment": "##Nat"
}
]
},
"tag": "TermObject"
},
{
"contents": {
"namespaceHash": "#n1egracfeljprftoktbjcase2hs4f4p8idbhs5ujipl42agld1810hrq9t7p7ped16aagni2cm1fjcjhho770jh80ipthhmg0cnsur0",
"namespaceName": "x"
},
"tag": "Subnamespace"
}
],
"namespaceListingFQN": "nested.names",
"namespaceListingHash": "#oms19b4f9s3c8tb5skeb8jii95ij35n3hdg038pu6rv5b0fikqe4gd7lnu6a1i6aq5tdh2opdo4s0sfrupvk6vfkr9lf0n752gbl8o0"
}
```

View File

@ -0,0 +1,18 @@
# projects api
```unison
rachel.filesystem.x = 42
ross.http.y = 43
joey.json.z = 44
joey.yaml.zz = 45
```
```ucm
.> add
```
```api
GET /api/projects
GET /api/projects?owner=joey
```

View File

@ -0,0 +1,72 @@
# projects api
```unison
rachel.filesystem.x = 42
ross.http.y = 43
joey.json.z = 44
joey.yaml.zz = 45
```
```ucm
I found and typechecked these definitions in scratch.u. If you
do an `add` or `update`, here's how your codebase would
change:
⍟ These new definitions are ok to `add`:
joey.json.z : ##Nat
joey.yaml.zz : ##Nat
rachel.filesystem.x : ##Nat
ross.http.y : ##Nat
```
```ucm
.> add
⍟ I've added these definitions:
joey.json.z : ##Nat
joey.yaml.zz : ##Nat
rachel.filesystem.x : ##Nat
ross.http.y : ##Nat
```
```api
GET /api/projects
[
{
"hash": "#vjmnhfbas8pejgpgsh26255ebaolepuc56juiifft4b9bg8u43nmmhe2skfncrfvin3std4grbfa7io846nskq3j5b3819rvaddnbn0",
"name": "json",
"owner": "joey"
},
{
"hash": "#plgokdvco3iu26r56u20faojs7pv0r0114pkd5aumt7ucd567t307bcuv92ejtkcvvmp0tg4e2g5d3btqbggn54pifbvql2kd9hlg48",
"name": "yaml",
"owner": "joey"
},
{
"hash": "#sbh98idno2b9ide5ue7bcj01ftu7u9msm57g3jn7q9efsbo0bdtnaei5i8sq4p3gb6p8alkqrp8gttp4ptvq9f45c8stkf39l9pvb2g",
"name": "filesystem",
"owner": "rachel"
},
{
"hash": "#1l4rfnjpsut79lc0kcv7aa4m6elk1lj7nse69ptaipb4gvlfa7kcnqrte56opeeb5ahrr6tvms2052e9fjjjuh97glkll6hp3lam788",
"name": "http",
"owner": "ross"
}
]
GET /api/projects?owner=joey
[
{
"hash": "#vjmnhfbas8pejgpgsh26255ebaolepuc56juiifft4b9bg8u43nmmhe2skfncrfvin3std4grbfa7io846nskq3j5b3819rvaddnbn0",
"name": "json",
"owner": "joey"
},
{
"hash": "#plgokdvco3iu26r56u20faojs7pv0r0114pkd5aumt7ucd567t307bcuv92ejtkcvvmp0tg4e2g5d3btqbggn54pifbvql2kd9hlg48",
"name": "yaml",
"owner": "joey"
}
]
```