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

iface: support for fixed choices in TS codegen #11630

Merged
1 commit merged into from
Nov 29, 2021
Merged
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
87 changes: 59 additions & 28 deletions language-support/ts/codegen/src/TsCodeGenMain.hs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,9 @@ genModule pkgMap (Scope scope) curPkgId mod
: map (internalImportDecl jsSyntax rootPath) internalImports
: body

(jsBody, tsDeclsBody) = unzip $ map (unzip . map renderTsDecl) (decls ++ ifaceDecls)
(jsBody, tsDeclsBody) = unzip $ map (unzip . map renderTsDecl) (ifaceDecls ++ decls)
-- ifaceDecls need to come before decls, otherwise (JavaScript) interface choice objects
-- will not be defined in template object.
depends = Set.map (Dependency . pkgRefStr pkgMap) externalImports
in Just ((makeMod ES5 jsBody, makeMod ES6 tsDeclsBody), depends)
where
Expand Down Expand Up @@ -382,35 +384,47 @@ data TemplateDef = TemplateDef
}

renderTemplateDef :: TemplateDef -> (T.Text, T.Text)
renderTemplateDef TemplateDef{..} =
let jsSource = T.unlines $ concat
[ [ "exports." <> tplName <> " = {"
, " templateId: '" <> templateId <> "',"
, " keyDecoder: " <> renderDecoder (DecoderLazy keyDec) <> ","
, " keyEncode: " <> renderEncode tplKeyEncode <> ","
, " decoder: " <> renderDecoder (DecoderLazy tplDecoder) <> ","
, " encode: " <> renderEncode tplEncode <> ","
]
, concat
[ [ " " <> chcName' <> ": {"
, " template: function () { return exports." <> tplName <> "; },"
, " choiceName: '" <> chcName' <> "',"
, " argumentDecoder: " <> renderDecoder (DecoderLazy (DecoderRef chcArgTy)) <> ","
, " argumentEncode: " <> renderEncode (EncodeRef chcArgTy) <> ","
, " resultDecoder: " <> renderDecoder (DecoderLazy (DecoderRef chcRetTy)) <> ","
, " resultEncode: " <> renderEncode (EncodeRef chcRetTy) <> ","
, " },"
]
| ChoiceDef{..} <- tplChoices'
renderTemplateDef TemplateDef {..} =
let jsSource =
T.unlines $
concat
[ ["exports." <> tplName <> " = Object.assign("]
, ["exports." <> impl <> "," | impl <- tplImplements']
-- we spread in the interface choices, the templateId field of the interface will be overwritten by the template object.
, [ T.unlines $
concat
[ ["{"]
, [ " templateId: '" <> templateId <> "',"
, " keyDecoder: " <> renderDecoder (DecoderLazy keyDec) <> ","
, " keyEncode: " <> renderEncode tplKeyEncode <> ","
, " decoder: " <> renderDecoder (DecoderLazy tplDecoder) <> ","
, " encode: " <> renderEncode tplEncode <> ","
]
, concat
[ [ " " <> chcName' <> ": {"
, " template: function () { return exports." <> tplName <> "; },"
, " choiceName: '" <> chcName' <> "',"
, " argumentDecoder: " <> renderDecoder (DecoderLazy (DecoderRef chcArgTy)) <>
","
, " argumentEncode: " <> renderEncode (EncodeRef chcArgTy) <> ","
, " resultDecoder: " <> renderDecoder (DecoderLazy (DecoderRef chcRetTy)) <>
","
, " resultEncode: " <> renderEncode (EncodeRef chcRetTy) <> ","
, " },"
]
| ChoiceDef {..} <- tplChoices'
]
, ["}"]
]
]
, [ "};" ]
, [");"]
]
tsDecl = T.unlines $ concat
[ ifaceDefTempl tplName (Just keyTy) tplImplements' tplChoices'
, [ "export declare const " <> tplName <> ":"
, " damlTypes.Template<" <> tplName <> ", " <> keyTy <> ", '" <> templateId <> "'> & " <> tplName <> "Interface;"
]
tsDecl = T.unlines $ concat
[ ifaceDefTempl tplName (Just keyTy) tplImplements' tplChoices'
, [ "export declare const " <> tplName <> ":"
, " damlTypes.Template<" <> tplName <> ", " <> keyTy <> ", '" <> templateId <> "'> & " <> tplName <> "Interface;"
]
]
in (jsSource, tsDecl)
where (keyTy, keyDec) = case tplKeyDecoder of
Nothing -> ("undefined", DecoderConstant ConstantUndefined)
Expand All @@ -430,7 +444,24 @@ data InterfaceDef = InterfaceDef
renderInterfaceDef :: InterfaceDef -> (T.Text, T.Text)
renderInterfaceDef InterfaceDef{ifName, ifChoices, ifModule, ifPkgId} = (jsSource, tsDecl)
where
jsSource = ""
jsSource = T.unlines $ concat
[ [ "exports." <> ifName <> " = {"
, " templateId: '" <> ifaceId <> "',"
]
, concat
[ [ " " <> chcName' <> ": {"
, " template: function () { return exports." <> ifName <> "; },"
, " choiceName: '" <> chcName' <> "',"
, " argumentDecoder: " <> renderDecoder (DecoderLazy (DecoderRef chcArgTy)) <> ","
, " argumentEncode: " <> renderEncode (EncodeRef chcArgTy) <> ","
, " resultDecoder: " <> renderDecoder (DecoderLazy (DecoderRef chcRetTy)) <> ","
, " resultEncode: " <> renderEncode (EncodeRef chcRetTy) <> ","
, " },"
]
| ChoiceDef{..} <- ifChoices
]
, [ "};" ]
]
tsDecl = T.unlines $ concat
[ifaceDefIface ifName Nothing ifChoices
, ["export declare const " <> ifName <> ": damlTypes.Template<object, undefined, '" <> ifaceId <> "'> & " <> ifName <> "Interface<object>;"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,37 +337,43 @@ test('create + fetch & exercise', async () => {

});


// TODO https://github.com/digital-asset/daml/issues/10810
// Reenable test
/*
// Reenable full test when JSON API can handle interface contract IDs.
test("interfaces", async () => {
const aliceLedger = new Ledger({token: ALICE_TOKEN, httpBaseUrl: httpBaseUrl()});
const bobLedger = new Ledger({token: BOB_TOKEN, httpBaseUrl: httpBaseUrl()});
// const bobLedger = new Ledger({token: BOB_TOKEN, httpBaseUrl: httpBaseUrl()});

const assetPayload = {
issuer: ALICE_PARTY,
owner: ALICE_PARTY
}
const ifaceContract = await aliceLedger.create(buildAndLint.Main.Asset, assetPayload);
expect(ifaceContract.payload).toEqual(assetPayload);
const [ifaceContract1, events1] = await aliceLedger.exercise(buildAndLint.Main.Asset.Transfer, ifaceContract.contractId, {newOwner: BOB_PARTY});
expect(events1).toMatchObject(
[ {archived: {templateId: buildAndLint.Main.Asset.templateId}},
{created: {templateId: buildAndLint.Main.Asset.templateId,
signatories: [ALICE_PARTY],
payload: {issuer: ALICE_PARTY, owner: BOB_PARTY}}}
]
)
const [,events2] = await bobLedger.exercise(buildAndLint.Main.Asset.Transfer, ifaceContract1 as unknown as ContractId<buildAndLint.Main.Asset>, {newOwner: ALICE_PARTY});
expect(events2).toMatchObject(
[ {archived: {templateId: buildAndLint.Main.Asset.templateId}},
{created: {templateId: buildAndLint.Main.Asset.templateId,
signatories: [ALICE_PARTY],
payload: {issuer: ALICE_PARTY, owner: ALICE_PARTY}}}
]
)
try {
const [, ] = await aliceLedger.exercise(buildAndLint.Main.Asset.Transfer, ifaceContract.contractId, {newOwner: BOB_PARTY});
} catch (ex) {
expect([400]).toContain(ex.status);
// currently the JSON API can't handle interface contract IDs in the response and returns a
// 400 error.
expect(ex.errors.length).toBe(1);
}
// expect(events1).toMatchObject(
// [ {archived: {templateId: buildAndLint.Main.Asset.templateId}},
// {created: {templateId: buildAndLint.Main.Asset.templateId,
// signatories: [ALICE_PARTY],
// payload: {issuer: ALICE_PARTY, owner: BOB_PARTY}}}
// ]
// )
// const [,events2] = await bobLedger.exercise(buildAndLint.Main.Token.Transfer, ifaceContract1, {newOwner: ALICE_PARTY});
// expect(events2).toMatchObject(
// [ {archived: {templateId: buildAndLint.Main.Asset.templateId}},
// {created: {templateId: buildAndLint.Main.Asset.templateId,
// signatories: [ALICE_PARTY],
// payload: {issuer: ALICE_PARTY, owner: ALICE_PARTY}}}
// ]
// )
});
*/

test("createAndExercise", async () => {
const ledger = new Ledger({token: ALICE_TOKEN, httpBaseUrl: httpBaseUrl()});
Expand Down