From 23aaff16f8d6b715b171fb0e1bf12372eec532ba Mon Sep 17 00:00:00 2001 From: reebayroo Date: Mon, 7 Oct 2024 10:27:53 -0400 Subject: [PATCH 1/4] Local Dependencies property support for Current Working Directory (#1196) * ignore all .DS_Store * Support to Current Working Directory * refactoring and clean up * Fetch cwd files before project dir files * attempt to fix vulnerability * attempt to fix vulnerability * putting package-lock back --- .gitignore | 1 + cli2/dependencies.test.ts | 8 +- cli2/dependencies.ts | 25 +++- package-lock.json | 138 ++++++++---------- package.json | 2 +- .../cli2-qa-test/cli2.dependencies.test.ts | 13 ++ 6 files changed, 93 insertions(+), 94 deletions(-) diff --git a/.gitignore b/.gitignore index 642ccdcf9..5c449bddc 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,4 @@ tests-integration/reference-model/Dockerfile *Debug.log* tests-integration/cli2-qa-test/test-data/.DS_Store tests-integration/cli2-qa-test/test-data/business-terms/.DS_Store +*.DS_Store* diff --git a/cli2/dependencies.test.ts b/cli2/dependencies.test.ts index 9350e96e1..5398876d2 100644 --- a/cli2/dependencies.test.ts +++ b/cli2/dependencies.test.ts @@ -18,7 +18,7 @@ describe("the dependencies module", () => { test("should fail if can't find the file.", () => { try { dep.LocalFile.parse({ - projectDir: __dirname, + baseDir: __dirname, sanitized: "./shouldFail.ir", }); } catch (error) { @@ -39,7 +39,7 @@ describe("the dependencies module", () => { let expectedUrl = new URL(`file://${expectedFile}`); let { success: urlSuccess, data: urlData } = dep.LocalFile.safeParse({ - projectDir: __dirname, + baseDir: __dirname, sanitized: `./${fileName}`, }); expect({ success: urlSuccess, data: urlData }).toStrictEqual({ @@ -53,7 +53,7 @@ describe("the dependencies module", () => { let expectedUrl = new URL(`file://${expectedFile}`); let { success: urlSuccess, data: urlData } = dep.LocalFile.safeParse({ - projectDir: __dirname, + baseDir: __dirname, sanitized: `../${fileName}`, }); expect({ success: urlSuccess, data: urlData }).toStrictEqual({ @@ -67,7 +67,7 @@ describe("the dependencies module", () => { let expectedUrl = new URL(`file://${expectedFile}`); let { success: urlSuccess, data: urlData } = dep.LocalFile.safeParse({ - projectDir: __dirname, + baseDir: __dirname, sanitized: `../cli/${fileName}`, }); expect({ success: urlSuccess, data: urlData }).toStrictEqual({ diff --git a/cli2/dependencies.ts b/cli2/dependencies.ts index df99f2e52..072931c0c 100644 --- a/cli2/dependencies.ts +++ b/cli2/dependencies.ts @@ -35,7 +35,7 @@ export const FileUrl = z.string().trim().url().transform((val, ctx) => { }); type LocalFileRef = { - projectDir: string, + baseDir: string, original: string, fullPath: string, url?: URL, @@ -43,9 +43,9 @@ type LocalFileRef = { } export const LocalFile = z.object({ - projectDir: z.string(), + baseDir: z.string(), sanitized: z.string(), -}).transform(val => {projectDir:val.projectDir, original: val.sanitized, fullPath: path.resolve(val.projectDir, val.sanitized ) }) +}).transform(val => { baseDir: val.baseDir, original: val.sanitized, fullPath: path.resolve(val.baseDir, val.sanitized) }) .transform(ref => ({ ...ref, url: new URL(`file://${ref.fullPath}`) })) .refine((ref: LocalFileRef) => fs.existsSync(ref.fullPath), (ref: LocalFileRef) => { @@ -87,7 +87,7 @@ const IncludeProvided = z.object({ const LocalDependencyProvided = z.object({ eventKind: z.literal('LocalDependencyProvided'), - payload: z.string() + payload: z.string() }) const DependencyProvided = z.object({ @@ -148,7 +148,7 @@ export async function loadAllDependencies(config: DependencyConfig) { }); } -const load = (config: DependencyConfig) => function(event: DependencyEvent) { +const load = (config: DependencyConfig) => function (event: DependencyEvent) { //TODO: Clear this up let source: "dependencies" | "localDependencies" | "includes"; @@ -173,7 +173,7 @@ const load = (config: DependencyConfig) => function(event: DependencyEvent) { } } } -const loadDependenciesFromString = (config: DependencyConfig) => function(input: string, source: string) { +const loadDependenciesFromString = (config: DependencyConfig) => function (input: string, source: string) { const doWork = async () => { let sanitized = input.trim(); let { success, data } = DataUrl.safeParse(sanitized); @@ -195,10 +195,19 @@ const loadDependenciesFromString = (config: DependencyConfig) => function(input: console.info("Loading url", urlData); return fetchUriToJson(urlData); } - let { success: localFileSuccess, data: localUrlData } = LocalFile.safeParse({projectDir : config.projectDir, sanitized}); + + let { success: localFileCWDSuccess, data: localUrlCWDData } = LocalFile.safeParse({ baseDir: process.cwd(), sanitized }); + if (localFileCWDSuccess && localUrlCWDData !== undefined) { + + console.info("Loading local file url from current working directory ", localUrlCWDData); + return fetchUriToJson(localUrlCWDData); + + } + + let { success: localFileSuccess, data: localUrlData } = LocalFile.safeParse({ baseDir: config.projectDir, sanitized }); if (localFileSuccess && localUrlData !== undefined) { - console.info("Loading local file url", localUrlData); + console.info("Loading local file url from morphir.json directory", localUrlData); return fetchUriToJson(localUrlData); } diff --git a/package-lock.json b/package-lock.json index f9578a73f..2122265c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,6 @@ "": { "name": "morphir-elm", "version": "2.90.1", - "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { "ajv": "^8.10.0", @@ -16,7 +15,7 @@ "commander": "^9.0.0", "cookie-parser": "^1.4.6", "data-urls": "^5.0.0", - "express": "^4.19.2", + "express": "^4.21.0", "fs-extra": "^9.1.0", "get-stdin": "^8.0.0", "get-uri": "^6.0.3", @@ -2436,10 +2435,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "license": "MIT", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -2449,7 +2447,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -2594,7 +2592,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -3060,7 +3057,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3217,7 +3213,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -3470,7 +3465,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -3479,7 +3473,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", "engines": { "node": ">= 0.8", "npm": "1.2.8000 || >= 1.4.16" @@ -3595,8 +3588,7 @@ "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { "version": "3.1.10", @@ -3683,10 +3675,9 @@ "license": "MIT" }, "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { "node": ">= 0.8" } @@ -3745,8 +3736,7 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/escape-string-regexp": { "version": "1.0.5", @@ -3775,7 +3765,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -3931,37 +3920,36 @@ "license": "MIT" }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "license": "MIT", + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -4173,13 +4161,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "license": "MIT", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -4371,7 +4358,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5825,7 +5811,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -7913,7 +7898,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -7959,10 +7943,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", - "license": "MIT" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -8008,7 +7994,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", "bin": { "mime": "cli.js" }, @@ -8407,8 +8392,7 @@ "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/mustache": { "version": "4.2.0", @@ -8663,7 +8647,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -8733,7 +8716,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -8989,7 +8971,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -9079,10 +9060,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", - "license": "MIT" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "4.0.0", @@ -9297,12 +9277,11 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "license": "BSD-3-Clause", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -9366,7 +9345,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -9375,7 +9353,6 @@ "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -9934,10 +9911,9 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "license": "MIT", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -9957,11 +9933,18 @@ "node": ">= 0.8.0" } }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serialize-javascript": { "version": "6.0.0", @@ -9974,15 +9957,14 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "license": "MIT", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -10008,8 +9990,7 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/sha.js": { "version": "2.4.11", @@ -10070,7 +10051,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -10271,7 +10251,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -10689,7 +10668,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", "engines": { "node": ">=0.6" } @@ -10890,7 +10868,6 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -10993,7 +10970,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } diff --git a/package.json b/package.json index 87177a3f8..3918c5fa4 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,7 @@ "commander": "^9.0.0", "cookie-parser": "^1.4.6", "data-urls": "^5.0.0", - "express": "^4.19.2", + "express": "^4.21.0", "fs-extra": "^9.1.0", "get-stdin": "^8.0.0", "get-uri": "^6.0.3", diff --git a/tests-integration/cli2-qa-test/cli2.dependencies.test.ts b/tests-integration/cli2-qa-test/cli2.dependencies.test.ts index 8f561fd8a..2edb30133 100644 --- a/tests-integration/cli2-qa-test/cli2.dependencies.test.ts +++ b/tests-integration/cli2-qa-test/cli2.dependencies.test.ts @@ -99,6 +99,19 @@ describe('morphir dependencies', () => { assertMorphirHashesExists(); }); + test("should support local dependency from current working directory ", async () => { + let localInclude = 'tests-integration/cli2-qa-test/temp-local-dependencies/dependency-project/morphir-ir.json'; + let newMorphir = { ...morphirJSON, localDependencies: [localInclude] }; + await makeMorphirJson(newMorphir); + + + + assertMorphirHashesIsMisssing(); + const resultIR = await cli2.make(PATH_TO_PROJECT, CLI_OPTIONS) + expect(resultIR).not.toBeNull(); + + assertMorphirHashesExists(); + }); }) describe('Should support data URL dependencies ', () => { From e6f73f0dfe3aca6ecbf51a68c6ddf3ea57cf6bf5 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Mon, 7 Oct 2024 11:55:58 -0500 Subject: [PATCH 2/4] Bump Elm package version --- elm.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elm.json b/elm.json index dc692bfb1..9415781af 100644 --- a/elm.json +++ b/elm.json @@ -3,7 +3,7 @@ "name": "finos/morphir-elm", "summary": "Morphir Elm bindings", "license": "Apache-2.0", - "version": "22.0.0", + "version": "22.0.2", "exposed-modules": [ "Morphir.SDK.Decimal", "Morphir.SDK.Int", From f71850abecde7af8924eb0f82beac537312d1b05 Mon Sep 17 00:00:00 2001 From: Damian Reeves <957246+DamianReeves@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:09:00 -0500 Subject: [PATCH 3/4] 2.91.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2122265c5..e3902cf61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "morphir-elm", - "version": "2.90.1", + "version": "2.91.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "morphir-elm", - "version": "2.90.1", + "version": "2.91.0", "license": "Apache-2.0", "dependencies": { "ajv": "^8.10.0", diff --git a/package.json b/package.json index 3918c5fa4..75b705f64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "morphir-elm", - "version": "2.90.1", + "version": "2.91.0", "description": "Elm bindings for Morphir", "type": "commonjs", "types": "lib/dist/main.d.ts", From 8e77b36eb5f7b2a9c13b9a132413fddc78c668a6 Mon Sep 17 00:00:00 2001 From: Michelle <6752211+michelchan@users.noreply.github.com> Date: Tue, 5 Nov 2024 20:30:53 -0500 Subject: [PATCH 4/4] Adding encoders and decoders for json (#1198) * Adding encoders and decoders for json * Adding tests for encoder decoders * Adding tests for decoders and removing date * Fix vulnerabilities --------- Co-authored-by: Damian Reeves <957246+DamianReeves@users.noreply.github.com> --- elm.json | 5 +- package-lock.json | 29 +- src/Morphir/IR/SDK/Json/Decode.elm | 143 ++++++ src/Morphir/IR/SDK/Json/Encode.elm | 69 +++ src/Morphir/SDK/Json/Decode.elm | 685 +++++++++++++++++++++++++ src/Morphir/SDK/Json/Encode.elm | 273 ++++++++++ src/Morphir/SDK/LocalDate.elm | 49 +- tests/Morphir/SDK/Json/DecodeTests.elm | 610 ++++++++++++++++++++++ tests/Morphir/SDK/Json/EncodeTests.elm | 169 ++++++ 9 files changed, 2015 insertions(+), 17 deletions(-) create mode 100644 src/Morphir/IR/SDK/Json/Decode.elm create mode 100644 src/Morphir/IR/SDK/Json/Encode.elm create mode 100644 src/Morphir/SDK/Json/Decode.elm create mode 100644 src/Morphir/SDK/Json/Encode.elm create mode 100644 tests/Morphir/SDK/Json/DecodeTests.elm create mode 100644 tests/Morphir/SDK/Json/EncodeTests.elm diff --git a/elm.json b/elm.json index 9415781af..4ca8e1e35 100644 --- a/elm.json +++ b/elm.json @@ -19,6 +19,8 @@ "Morphir.SDK.ResultList", "Morphir.SDK.LocalTime", "Morphir.SDK.UUID", + "Morphir.SDK.Json.Encode", + "Morphir.SDK.Json.Decode", "Morphir.IR.Name", "Morphir.IR.NodeId", "Morphir.IR.Decoration", @@ -86,7 +88,8 @@ "pzp1997/assoc-list": "1.0.0 <= v < 2.0.0", "rtfeldman/elm-iso8601-date-strings": "1.1.4 <= v < 2.0.0", "rundis/elm-bootstrap": "5.2.0 <= v < 6.0.0", - "stil4m/elm-syntax": "7.2.1 <= v < 8.0.0" + "stil4m/elm-syntax": "7.2.1 <= v < 8.0.0", + "waratuman/json-extra": "1.0.0 <= v < 1.0.3" }, "test-dependencies": {} } diff --git a/package-lock.json b/package-lock.json index e3902cf61..0d2d367e7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3069,21 +3069,21 @@ "license": "MIT" }, "node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", "license": "MIT", "dependencies": { - "cookie": "0.4.1", + "cookie": "0.7.2", "cookie-signature": "1.0.6" }, "engines": { @@ -3920,16 +3920,17 @@ "license": "MIT" }, "node_modules/express": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", - "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -3961,9 +3962,9 @@ } }, "node_modules/express/node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", "engines": { "node": ">= 0.6" diff --git a/src/Morphir/IR/SDK/Json/Decode.elm b/src/Morphir/IR/SDK/Json/Decode.elm new file mode 100644 index 000000000..4b6b60a7e --- /dev/null +++ b/src/Morphir/IR/SDK/Json/Decode.elm @@ -0,0 +1,143 @@ +module Morphir.IR.SDK.Json.Decode exposing (..) + +import Dict +import Morphir.IR.Documented exposing (Documented) +import Morphir.IR.Literal exposing (Literal(..)) +import Morphir.IR.Module as Module exposing (ModuleName) +import Morphir.IR.Name as Name +import Morphir.IR.Path as Path +import Morphir.IR.SDK.Basics exposing (floatType, intType) +import Morphir.IR.SDK.Common exposing (tFun, tVar, toFQName, vSpec) +import Morphir.IR.SDK.Dict exposing (dictType) +import Morphir.IR.SDK.Json.Encode exposing (valueType) +import Morphir.IR.SDK.List exposing (listType) +import Morphir.IR.SDK.Maybe exposing (maybeType) +import Morphir.IR.SDK.Result exposing (resultType) +import Morphir.IR.SDK.String exposing (stringType) +import Morphir.IR.Type exposing (Specification(..), Type(..)) + + +moduleName : ModuleName +moduleName = + Path.fromString "Decode" + + +moduleSpec : Module.Specification () +moduleSpec = + { types = + Dict.fromList + [ ( Name.fromString "Decode", OpaqueTypeSpecification [] |> Documented "Type that represents a JSON Decoder" ) + , ( Name.fromString "Error", OpaqueTypeSpecification [] |> Documented "Type that represents a JSON Decoding Error" ) + ] + , values = + Dict.fromList + [ vSpec "string" [ ( "d", decoderType () (tVar "a") ) ] (stringType ()) + , vSpec "bool" [ ( "d", decoderType () (tVar "a") ) ] (intType ()) + , vSpec "int" [ ( "d", decoderType () (tVar "a") ) ] (intType ()) + , vSpec "float" [ ( "d", decoderType () (tVar "a") ) ] (floatType ()) + , vSpec "nullable" [ ( "d", decoderType () (tVar "a") ) ] (maybeType () (tVar "a")) + , vSpec "list" + [ ( "d", decoderType () (tVar "a") ) ] + (decoderType () (listType () (tVar "a"))) + , vSpec "dict" + [ ( "d", decoderType () (tVar "a") ) ] + (decoderType () (dictType () (stringType ()) (tVar "a"))) + , vSpec "keyValuePairs" + [ ( "d", decoderType () (tVar "a") ) ] + (decoderType () (listType () (Tuple () [ tVar "a", tVar "b" ]))) + , vSpec "oneOrMore" + [ ( "f", tFun [ tVar "a", listType () (tVar "a") ] (tVar "value") ) + , ( "d", decoderType () (tVar "a") ) + ] + (decoderType () (tVar "value")) + , vSpec "field" [ ( "s", stringType () ), ( "d", decoderType () (tVar "a") ) ] (decoderType () (tVar "a")) + , vSpec "at" [ ( "l", listType () (stringType ()) ), ( "d", decoderType () (tVar "a") ) ] (decoderType () (tVar "a")) + , vSpec "index" [ ( "i", intType () ), ( "d", decoderType () (tVar "a") ) ] (decoderType () (tVar "a")) + , vSpec "maybe" [ ( "d", decoderType () (tVar "a") ) ] (decoderType () (maybeType () (tVar "a"))) + , vSpec "oneOf" [ ( "l", listType () (decoderType () (tVar "a")) ) ] (decoderType () (tVar "a")) + , vSpec "map" + [ ( "f", tFun [ tVar "a" ] (tVar "value") ) + , ( "d", decoderType () (tVar "a") ) + ] + (decoderType () (tVar "value")) + , vSpec "map2" + [ ( "f", tFun [ tVar "a", tVar "b" ] (tVar "value") ) + , ( "d1", decoderType () (tVar "a") ) + , ( "d2", decoderType () (tVar "b") ) + ] + (decoderType () (tVar "value")) + , vSpec "map3" + [ ( "f", tFun [ tVar "a", tVar "b", tVar "c" ] (tVar "value") ) + , ( "d1", decoderType () (tVar "a") ) + , ( "d2", decoderType () (tVar "b") ) + , ( "d3", decoderType () (tVar "c") ) + ] + (decoderType () (tVar "value")) + , vSpec "map4" + [ ( "f", tFun [ tVar "a", tVar "b", tVar "c", tVar "d" ] (tVar "value") ) + , ( "d1", decoderType () (tVar "a") ) + , ( "d2", decoderType () (tVar "b") ) + , ( "d3", decoderType () (tVar "c") ) + , ( "d4", decoderType () (tVar "d") ) + ] + (decoderType () (tVar "value")) + , vSpec "map5" + [ ( "f", tFun [ tVar "a", tVar "b", tVar "c", tVar "d", tVar "e" ] (tVar "value") ) + , ( "d1", decoderType () (tVar "a") ) + , ( "d2", decoderType () (tVar "b") ) + , ( "d3", decoderType () (tVar "c") ) + , ( "d4", decoderType () (tVar "d") ) + , ( "d5", decoderType () (tVar "e") ) + ] + (decoderType () (tVar "value")) + , vSpec "map6" + [ ( "f", tFun [ tVar "a", tVar "b", tVar "c", tVar "d", tVar "e", tVar "f" ] (tVar "value") ) + , ( "d1", decoderType () (tVar "a") ) + , ( "d2", decoderType () (tVar "b") ) + , ( "d3", decoderType () (tVar "c") ) + , ( "d4", decoderType () (tVar "d") ) + , ( "d5", decoderType () (tVar "e") ) + , ( "d6", decoderType () (tVar "f") ) + ] + (decoderType () (tVar "value")) + , vSpec "map7" + [ ( "f", tFun [ tVar "a", tVar "b", tVar "c", tVar "d", tVar "e", tVar "f", tVar "g" ] (tVar "value") ) + , ( "d1", decoderType () (tVar "a") ) + , ( "d2", decoderType () (tVar "b") ) + , ( "d3", decoderType () (tVar "c") ) + , ( "d4", decoderType () (tVar "d") ) + , ( "d5", decoderType () (tVar "e") ) + , ( "d6", decoderType () (tVar "f") ) + , ( "d7", decoderType () (tVar "g") ) + ] + (decoderType () (tVar "value")) + , vSpec "map8" + [ ( "f", tFun [ tVar "a", tVar "b", tVar "c", tVar "d", tVar "e", tVar "f", tVar "g", tVar "h" ] (tVar "value") ) + , ( "d1", decoderType () (tVar "a") ) + , ( "d2", decoderType () (tVar "b") ) + , ( "d3", decoderType () (tVar "c") ) + , ( "d4", decoderType () (tVar "d") ) + , ( "d5", decoderType () (tVar "e") ) + , ( "d6", decoderType () (tVar "f") ) + , ( "d7", decoderType () (tVar "g") ) + , ( "d8", decoderType () (tVar "h") ) + ] + (decoderType () (tVar "value")) + , vSpec "decodeString" [ ( "d", decoderType () (tVar "a") ), ( "s", stringType () ) ] (resultType () (errorType ()) (tVar "a")) + , vSpec "decodeValue" [ ( "d", decoderType () (tVar "a") ), ( "v", valueType () ) ] (resultType () (errorType ()) (tVar "a")) + , vSpec "errorToString" [ ( "e", errorType () ) ] (stringType ()) + , vSpec "succeed" [ ( "a", tVar "a" ) ] (decoderType () (tVar "a")) + , vSpec "fail" [ ( "s", stringType () ) ] (decoderType () (tVar "a")) + ] + , doc = Just "The Decode type and associated functions" + } + + +decoderType : a -> Type a -> Type a +decoderType attributes itemType = + Reference attributes (toFQName moduleName "Decoder") [ itemType ] + + +errorType : a -> Type a +errorType attributes = + Reference attributes (toFQName moduleName "Error") [] diff --git a/src/Morphir/IR/SDK/Json/Encode.elm b/src/Morphir/IR/SDK/Json/Encode.elm new file mode 100644 index 000000000..f6a1ce3a5 --- /dev/null +++ b/src/Morphir/IR/SDK/Json/Encode.elm @@ -0,0 +1,69 @@ +module Morphir.IR.SDK.Json.Encode exposing (..) + +import Dict +import Morphir.IR.Documented exposing (Documented) +import Morphir.IR.Literal exposing (Literal(..)) +import Morphir.IR.Module as Module exposing (ModuleName) +import Morphir.IR.Name as Name +import Morphir.IR.Path as Path +import Morphir.IR.SDK.Basics exposing (boolType, floatType, intType) +import Morphir.IR.SDK.Common exposing (tFun, tVar, toFQName, vSpec) +import Morphir.IR.SDK.Dict exposing (dictType) +import Morphir.IR.SDK.List exposing (listType) +import Morphir.IR.SDK.LocalTime exposing (localTimeType) +import Morphir.IR.SDK.Maybe exposing (maybeType) +import Morphir.IR.SDK.Set exposing (setType) +import Morphir.IR.SDK.String exposing (stringType) +import Morphir.IR.Type exposing (Specification(..), Type(..)) + + +moduleName : ModuleName +moduleName = + Path.fromString "Encode" + + +moduleSpec : Module.Specification () +moduleSpec = + { types = + Dict.fromList + [ ( Name.fromString "Encode", OpaqueTypeSpecification [] |> Documented "Type that represents a JSON Encoder" ) + ] + , values = + Dict.fromList + [ vSpec "encode" [ ( "i", intType () ), ( "v", valueType () ) ] (stringType ()) + , vSpec "string" [ ( "s", stringType () ) ] (valueType ()) + , vSpec "int" [ ( "i", intType () ) ] (valueType ()) + , vSpec "float" [ ( "f", floatType () ) ] (valueType ()) + , vSpec "bool" [ ( "f", boolType () ) ] (valueType ()) + , vSpec "null" [] (valueType ()) + , vSpec "list" + [ ( "f", tFun [ tVar "a" ] (tVar "Value") ) + , ( "list", listType () (tVar "a") ) + ] + (valueType ()) + , vSpec "set" + [ ( "f", tFun [ tVar "a" ] (tVar "Value") ) + , ( "set", setType () (tVar "a") ) + ] + (valueType ()) + , vSpec "object" [ ( "list", listType () (Tuple () [ tVar "a", tVar "b" ]) ) ] (valueType ()) + , vSpec "dict" + [ ( "f", tFun [ tVar "k" ] (stringType ()) ) + , ( "f", tFun [ tVar "v" ] (valueType ()) ) + , ( "dict", dictType () (tVar "k") (tVar "v") ) + ] + (dictType () (tVar "comparable") (tVar "v")) + , vSpec "localTime" [ ( "t", localTimeType () ) ] (valueType ()) + , vSpec "maybe" + [ ( "f", tFun [ tVar "a" ] (tVar "Value") ) + , ( "maybe", maybeType () (tVar "a") ) + ] + (valueType ()) + ] + , doc = Just "The Encode type and associated functions" + } + + +valueType : a -> Type a +valueType attributes = + Reference attributes (toFQName moduleName "Encode") [] diff --git a/src/Morphir/SDK/Json/Decode.elm b/src/Morphir/SDK/Json/Decode.elm new file mode 100644 index 000000000..f578ebae0 --- /dev/null +++ b/src/Morphir/SDK/Json/Decode.elm @@ -0,0 +1,685 @@ +module Morphir.SDK.Json.Decode exposing + ( Decoder, string, bool, int, float + , nullable, list, dict, keyValuePairs, oneOrMore + , field, at, index + , maybe, oneOf + , decodeString, decodeValue, Value, Error, errorToString + , map, map2, map3, map4, map5, map6, map7, map8 + , lazy, value, null, succeed, fail, andThen + , localTime, nothing + ) + +{-| Turn JSON values into Elm values. Definitely check out this [intro to +JSON decoders][guide] to get a feel for how this library works! + +[guide]: https://guide.elm-lang.org/effects/json.html + + +# Primitives + +@docs Decoder, string, bool, int, float + + +# Data Structures + +@docs nullable, list, dict, keyValuePairs, oneOrMore + + +# Object Primitives + +@docs field, at, index + + +# Inconsistent Structure + +@docs maybe, oneOf + + +# Run Decoders + +@docs decodeString, decodeValue, Value, Error, errorToString + + +# Mapping + +**Note:** If you run out of map functions, take a look at [elm-json-decode-pipeline][pipe] +which makes it easier to handle large objects, but produces lower quality type +errors. + +[pipe]: /packages/NoRedInk/elm-json-decode-pipeline/latest + +@docs map, map2, map3, map4, map5, map6, map7, map8 + + +# Fancy Decoding + +@docs lazy, value, null, succeed, fail, andThen + + +# Extra + +@docs localTime, nothing + +-} + +import Dict exposing (Dict) +import Json.Decode as DE +import Json.Decode.Extra as DEE +import Json.Encode exposing (Value) +import Morphir.SDK.LocalDate exposing (Month, intToMonth) +import Morphir.SDK.LocalTime exposing (LocalTime) + + + +-- PRIMITIVES + + +{-| A value that knows how to decode JSON values. + +There is a whole section in `guide.elm-lang.org` about decoders, so [check it +out](https://guide.elm-lang.org/interop/json.html) for a more comprehensive +introduction! + +-} +type alias Decoder a = + DE.Decoder a + + +{-| A structured error describing exactly how the decoder failed. You can use +this to create more elaborate visualizations of a decoder problem. For example, +you could show the entire JSON object and show the part causing the failure in +red. +-} +type alias Error = + DE.Error + + +{-| Decode a JSON string into an Elm `String`. + + decodeString string "true" == Err ... + decodeString string "42" == Err ... + decodeString string "3.14" == Err ... + decodeString string "\"hello\"" == Ok "hello" + decodeString string "{ \"hello\": 42 }" == Err ... + +-} +string : Decoder String +string = + DE.string + + +{-| Decode a JSON boolean into an Elm `Bool`. + + decodeString bool "true" == Ok True + decodeString bool "42" == Err ... + decodeString bool "3.14" == Err ... + decodeString bool "\"hello\"" == Err ... + decodeString bool "{ \"hello\": 42 }" == Err ... + +-} +bool : Decoder Bool +bool = + DE.bool + + +{-| Decode a JSON number into an Elm `Int`. + + decodeString int "true" == Err ... + decodeString int "42" == Ok 42 + decodeString int "3.14" == Err ... + decodeString int "\"hello\"" == Err ... + decodeString int "{ \"hello\": 42 }" == Err ... + +-} +int : Decoder Int +int = + DE.int + + +{-| Decode a JSON number into an Elm `Float`. + + decodeString float "true" == Err .. + decodeString float "42" == Ok 42 + decodeString float "3.14" == Ok 3.14 + decodeString float "\"hello\"" == Err ... + decodeString float "{ \"hello\": 42 }" == Err ... + +-} +float : Decoder Float +float = + DE.float + + + +-- DATA STRUCTURES + + +{-| Decode a nullable JSON value into an Elm value. + + decodeString (nullable int) "13" == Ok (Just 13) + decodeString (nullable int) "42" == Ok (Just 42) + decodeString (nullable int) "null" == Ok Nothing + decodeString (nullable int) "true" == Err .. + +-} +nullable : Decoder a -> Decoder (Maybe a) +nullable = + DE.nullable + + +{-| Decode a JSON array into an Elm `List`. + + decodeString (list int) "[1,2,3]" == Ok [ 1, 2, 3 ] + + decodeString (list bool) "[true,false]" == Ok [ True, False ] + +-} +list : Decoder a -> Decoder (List a) +list = + DE.list + + +{-| Decode a JSON object into an Elm `Dict`. + + decodeString (dict int) "{ \"alice\": 42, \"bob\": 99 }" + == Ok (Dict.fromList [ ( "alice", 42 ), ( "bob", 99 ) ]) + +If you need the keys (like `"alice"` and `"bob"`) available in the `Dict` +values as well, I recommend using a (private) intermediate data structure like +`Info` in this example: + + module User exposing (User, decoder) + + import Dict + import Json.Decode exposing (..) + + type alias User = + { name : String + , height : Float + , age : Int + } + + decoder : Decoder (Dict.Dict String User) + decoder = + map (Dict.map infoToUser) (dict infoDecoder) + + type alias Info = + { height : Float + , age : Int + } + + infoDecoder : Decoder Info + infoDecoder = + map2 Info + (field "height" float) + (field "age" int) + + infoToUser : String -> Info -> User + infoToUser name { height, age } = + User name height age + +So now JSON like `{ "alice": { height: 1.6, age: 33 }}` are turned into +dictionary values like `Dict.singleton "alice" (User "alice" 1.6 33)` if +you need that. + +-} +dict : Decoder a -> Decoder (Dict String a) +dict = + DE.dict + + +{-| Decode a JSON object into an Elm `List` of pairs. + + decodeString (keyValuePairs int) "{ \"alice\": 42, \"bob\": 99 }" + == Ok [ ( "alice", 42 ), ( "bob", 99 ) ] + +-} +keyValuePairs : Decoder a -> Decoder (List ( String, a )) +keyValuePairs = + DE.keyValuePairs + + +{-| Decode a JSON array that has one or more elements. This comes up if you +want to enable drag-and-drop of files into your application. You would pair +this function with [`elm/file`]() to write a `dropDecoder` like this: + + import File exposing (File) + import Json.Decoder as D + + type Msg + = GotFiles File (List Files) + + inputDecoder : D.Decoder Msg + inputDecoder = + D.at [ "dataTransfer", "files" ] (D.oneOrMore GotFiles File.decoder) + +This captures the fact that you can never drag-and-drop zero files. + +-} +oneOrMore : (a -> List a -> value) -> Decoder a -> Decoder value +oneOrMore = + DE.oneOrMore + + + +-- OBJECT PRIMITIVES + + +{-| Decode a JSON object, requiring a particular field. + + decodeString (field "x" int) "{ \"x\": 3 }" == Ok 3 + + decodeString (field "x" int) "{ \"x\": 3, \"y\": 4 }" == Ok 3 + + decodeString (field "x" int) "{ \"x\": true }" + == Err + ... decodeString (field "x" int) "{ \"y\": 4 }" + == Err + ... decodeString (field "name" string) "{ \"name\": \"tom\" }" + == Ok "tom" + +The object _can_ have other fields. Lots of them! The only thing this decoder +cares about is if `x` is present and that the value there is an `Int`. + +Check out [`map2`](#map2) to see how to decode multiple fields! + +-} +field : String -> Decoder a -> Decoder a +field = + DE.field + + +{-| Decode a nested JSON object, requiring certain fields. + + json = """{ "person": { "name": "tom", "age": 42 } }""" + + decodeString (at ["person", "name"] string) json == Ok "tom" + decodeString (at ["person", "age" ] int ) json == Ok "42 + +This is really just a shorthand for saying things like: + + field "person" (field "name" string) == at [ "person", "name" ] string + +-} +at : List String -> Decoder a -> Decoder a +at = + DE.at + + +{-| Decode a JSON array, requiring a particular index. + + json = """[ "alice", "bob", "chuck" ]""" + + decodeString (index 0 string) json == Ok "alice" + decodeString (index 1 string) json == Ok "bob" + decodeString (index 2 string) json == Ok "chuck" + decodeString (index 3 string) json == Err ... + +-} +index : Int -> Decoder a -> Decoder a +index = + DE.index + + + +-- WEIRD STRUCTURE + + +{-| Helpful for dealing with optional fields. Here are a few slightly different +examples: + + json = """{ "name": "tom", "age": 42 }""" + + decodeString (maybe (field "age" int )) json == Ok (Just 42) + decodeString (maybe (field "name" int )) json == Ok Nothing + decodeString (maybe (field "height" float)) json == Ok Nothing + + decodeString (field "age" (maybe int )) json == Ok (Just 42) + decodeString (field "name" (maybe int )) json == Ok Nothing + decodeString (field "height" (maybe float)) json == Err ... + +Notice the last example! It is saying we _must_ have a field named `height` and +the content _may_ be a float. There is no `height` field, so the decoder fails. + +Point is, `maybe` will make exactly what it contains conditional. For optional +fields, this means you probably want it _outside_ a use of `field` or `at`. + +-} +maybe : Decoder a -> Decoder (Maybe a) +maybe = + DE.maybe + + +{-| Try a bunch of different decoders. This can be useful if the JSON may come +in a couple different formats. For example, say you want to read an array of +numbers, but some of them are `null`. + + import String + + badInt : Decoder Int + badInt = + oneOf [ int, null 0 ] + + -- decodeString (list badInt) "[1,2,null,4]" == Ok [1,2,0,4] + +Why would someone generate JSON like this? Questions like this are not good +for your health. The point is that you can use `oneOf` to handle situations +like this! + +You could also use `oneOf` to help version your data. Try the latest format, +then a few older ones that you still support. You could use `andThen` to be +even more particular if you wanted. + +-} +oneOf : List (Decoder a) -> Decoder a +oneOf = + DE.oneOf + + + +-- MAPPING + + +{-| Transform a decoder. Maybe you just want to know the length of a string: + + import String + + stringLength : Decoder Int + stringLength = + map String.length string + +It is often helpful to use `map` with `oneOf`, like when defining `nullable`: + + nullable : Decoder a -> Decoder (Maybe a) + nullable decoder = + oneOf + [ null Nothing + , map Just decoder + ] + +-} +map : (a -> value) -> Decoder a -> Decoder value +map = + DE.map + + +{-| Try two decoders and then combine the result. We can use this to decode +objects with many fields: + + + type alias Point = + { x : Float, y : Float } + + point : Decoder Point + point = + map2 Point + (field "x" float) + (field "y" float) + + -- decodeString point """{ "x": 3, "y": 4 }""" == Ok { x = 3, y = 4 } + +It tries each individual decoder and puts the result together with the `Point` +constructor. + +-} +map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value +map2 = + DE.map2 + + +{-| Try three decoders and then combine the result. We can use this to decode +objects with many fields: + + + type alias Person = + { name : String, age : Int, height : Float } + + person : Decoder Person + person = + map3 Person + (at [ "name" ] string) + (at [ "info", "age" ] int) + (at [ "info", "height" ] float) + + -- json = """{ "name": "tom", "info": { "age": 42, "height": 1.8 } }""" + -- decodeString person json == Ok { name = "tom", age = 42, height = 1.8 } + +Like `map2` it tries each decoder in order and then give the results to the +`Person` constructor. That can be any function though! + +-} +map3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value +map3 = + DE.map3 + + +{-| -} +map4 : (a -> b -> c -> d -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder value +map4 = + DE.map4 + + +{-| -} +map5 : (a -> b -> c -> d -> e -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder value +map5 = + DE.map5 + + +{-| -} +map6 : (a -> b -> c -> d -> e -> f -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder value +map6 = + DE.map6 + + +{-| -} +map7 : (a -> b -> c -> d -> e -> f -> g -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder value +map7 = + DE.map7 + + +{-| -} +map8 : (a -> b -> c -> d -> e -> f -> g -> h -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder f -> Decoder g -> Decoder h -> Decoder value +map8 = + DE.map8 + + + +-- RUN DECODERS + + +{-| Parse the given string into a JSON value and then run the `Decoder` on it. +This will fail if the string is not well-formed JSON or if the `Decoder` +fails for some reason. + + decodeString int "4" == Ok 4 + decodeString int "1 + 2" == Err ... + +-} +decodeString : Decoder a -> String -> Result Error a +decodeString = + DE.decodeString + + +{-| Run a `Decoder` on some JSON `Value`. You can send these JSON values +through ports, so that is probably the main time you would use this function. +-} +decodeValue : Decoder a -> Value -> Result Error a +decodeValue = + DE.decodeValue + + +{-| Represents a JavaScript value. +-} +type alias Value = + Json.Encode.Value + + +{-| Convert a decoding error into a `String` that is nice for debugging. + +It produces multiple lines of output, so you may want to peek at it with +something like this: + + import Html + import Json.Decode as Decode + + errorToHtml : Decode.Error -> Html.Html msg + errorToHtml error = + Html.pre [] [ Html.text (Decode.errorToString error) ] + +**Note:** It would be cool to do nicer coloring and fancier HTML, but I wanted +to avoid having an `elm/html` dependency for now. It is totally possible to +crawl the `Error` structure and create this separately though! + +-} +errorToString : Error -> String +errorToString = + DE.errorToString + + + +-- FANCY PRIMITIVES + + +{-| Ignore the JSON and produce a certain Elm value. + + decodeString (succeed 42) "true" == Ok 42 + decodeString (succeed 42) "[1,2,3]" == Ok 42 + decodeString (succeed 42) "hello" == Err ... -- this is not a valid JSON string + +This is handy when used with `oneOf` or `andThen`. + +-} +succeed : a -> Decoder a +succeed = + DE.succeed + + +{-| Ignore the JSON and make the decoder fail. This is handy when used with +`oneOf` or `andThen` where you want to give a custom error message in some +case. + +See the [`andThen`](#andThen) docs for an example. + +-} +fail : String -> Decoder a +fail = + DE.fail + + +{-| Create decoders that depend on previous results. If you are creating +versioned data, you might do something like this: + + + info : Decoder Info + info = + field "version" int + |> andThen infoHelp + + infoHelp : Int -> Decoder Info + infoHelp version = + case version of + 4 -> + infoDecoder4 + + 3 -> + infoDecoder3 + + _ -> + fail <| + "Trying to decode info, but version " + ++ toString version + ++ " is not supported." + + -- infoDecoder4 : Decoder Info + -- infoDecoder3 : Decoder Info + +-} +andThen : (a -> Decoder b) -> Decoder a -> Decoder b +andThen = + DE.andThen + + +{-| Sometimes you have JSON with recursive structure, like nested comments. +You can use `lazy` to make sure your decoder unrolls lazily. + + type alias Comment = + { message : String + , responses : Responses + } + + type Responses + = Responses (List Comment) + + comment : Decoder Comment + comment = + map2 Comment + (field "message" string) + (field "responses" (map Responses (list (lazy (\_ -> comment))))) + +If we had said `list comment` instead, we would start expanding the value +infinitely. What is a `comment`? It is a decoder for objects where the +`responses` field contains comments. What is a `comment` though? Etc. + +By using `list (lazy (\_ -> comment))` we make sure the decoder only expands +to be as deep as the JSON we are given. You can read more about recursive data +structures [here]. + +[here]: https://github.com/elm/compiler/blob/master/hints/recursive-alias.md + +-} +lazy : (() -> Decoder a) -> Decoder a +lazy = + DE.lazy + + +{-| Do not do anything with a JSON value, just bring it into Elm as a `Value`. +This can be useful if you have particularly complex data that you would like to +deal with later. Or if you are going to send it out a port and do not care +about its structure. +-} +value : Decoder Value +value = + DE.value + + +{-| Decode a `null` value into some Elm value. + + decodeString (null False) "null" == Ok False + decodeString (null 42) "null" == Ok 42 + decodeString (null 42) "42" == Err .. + decodeString (null 42) "false" == Err .. + +So if you ever see a `null`, this will return whatever value you specified. + +-} +null : a -> Decoder a +null = + DE.null + + +{-| Decode a JSON value and do nothing with it. + + import Json.Decode exposing (..) + + decodeString nothing "{}" --> Ok () + +-} +nothing : Decoder () +nothing = + DEE.nothing + + +{-| Decode a JSON float value representing the number of seconds since epoch +into a `LocalTime`. + + import Json.Decode exposing (..) + import LocalTime + + decodeString (field "created_at" localTime) + "{ \"created_at\": 1574447205.394}" + --> Ok (Time.millisToPosix 1574447205000) + +-} +localTime : Decoder LocalTime +localTime = + DEE.posix diff --git a/src/Morphir/SDK/Json/Encode.elm b/src/Morphir/SDK/Json/Encode.elm new file mode 100644 index 000000000..548ef8a40 --- /dev/null +++ b/src/Morphir/SDK/Json/Encode.elm @@ -0,0 +1,273 @@ +module Morphir.SDK.Json.Encode exposing + ( encode, Value + , string, int, float, bool, null + , list, set + , object, dict + , localTime, maybe + ) + +{-| Library for turning values into Json values. + + +# Encoding + +@docs encode, Value + + +# Primitives + +@docs string, int, float, bool, null + + +# Arrays + +@docs list, set + + +# Objects + +@docs object, dict + + +# Extra + +@docs localTime, maybe + +-} + +import Dict exposing (Dict) +import Json.Encode as JE +import Json.Encode.Extra as JEE +import Morphir.SDK.LocalTime exposing (LocalTime) +import Set exposing (Set) + + + +-- ENCODE + + +{-| Represents a JavaScript value. +-} +type alias Value = + JE.Value + + +{-| Convert a `Value` into a prettified string. The first argument specifies +the amount of indentation in the resulting string. + + import JE as Encode + + tom : Encode.Value + tom = + Encode.object + [ ( "name", Encode.string "Tom" ) + , ( "age", Encode.int 42 ) + ] + + compact = + Encode.encode 0 tom + + -- {"name":"Tom","age":42} + readable = + Encode.encode 4 tom + + -- { + -- "name": "Tom", + -- "age": 42 + -- } + +-} +encode : Int -> Value -> String +encode = + JE.encode + + + +-- PRIMITIVES + + +{-| Turn a `String` into a JSON string. + + import JE exposing (encode, string) + + + -- encode 0 (string "") == "\"\"" + -- encode 0 (string "abc") == "\"abc\"" + -- encode 0 (string "hello") == "\"hello\"" + +-} +string : String -> Value +string = + JE.string + + +{-| Turn an `Int` into a JSON number. + + import JE exposing (encode, int) + + + -- encode 0 (int 42) == "42" + -- encode 0 (int -7) == "-7" + -- encode 0 (int 0) == "0" + +-} +int : Int -> Value +int = + JE.int + + +{-| Turn a `Float` into a JSON number. + + import JE exposing (encode, float) + + + -- encode 0 (float 3.14) == "3.14" + -- encode 0 (float 1.618) == "1.618" + -- encode 0 (float -42) == "-42" + -- encode 0 (float NaN) == "null" + -- encode 0 (float Infinity) == "null" + +**Note:** Floating point numbers are defined in the [IEEE 754 standard][ieee] +which is hardcoded into almost all CPUs. This standard allows `Infinity` and +`NaN`. [The JSON spec][json] does not include these values, so we encode them +both as `null`. + +[ieee]: https://en.wikipedia.org/wiki/IEEE_754 +[json]: https://www.json.org/ + +-} +float : Float -> Value +float = + JE.float + + +{-| Turn a `Bool` into a JSON boolean. + + import JE exposing (bool, encode) + + + -- encode 0 (bool True) == "true" + -- encode 0 (bool False) == "false" + +-} +bool : Bool -> Value +bool = + JE.bool + + + +-- NULLS + + +{-| Create a JSON `null` value. + + import JE exposing (encode, null) + + + -- encode 0 null == "null" + +-} +null : Value +null = + JE.null + + + +-- ARRAYS + + +{-| Turn a `List` into a JSON array. + + import JE as Encode exposing (bool, encode, int, list, string) + + + -- encode 0 (list int [1,3,4]) == "[1,3,4]" + -- encode 0 (list bool [True,False]) == "[true,false]" + -- encode 0 (list string ["a","b"]) == """["a","b"]""" + +-} +list : (a -> Value) -> List a -> Value +list = + JE.list + + +{-| Turn an `Set` into a JSON array. +-} +set : (a -> Value) -> Set a -> Value +set = + JE.set + + + +-- OBJECTS + + +{-| Create a JSON object. + + import JE as Encode + + tom : Encode.Value + tom = + Encode.object + [ ( "name", Encode.string "Tom" ) + , ( "age", Encode.int 42 ) + ] + + -- Encode.encode 0 tom == """{"name":"Tom","age":42}""" + +-} +object : List ( String, Value ) -> Value +object = + JE.object + + +{-| Turn a `Dict` into a JSON object. + + import Dict exposing (Dict) + import JE as Encode + + people : Dict String Int + people = + Dict.fromList [ ( "Tom", 42 ), ( "Sue", 38 ) ] + + -- Encode.encode 0 (Encode.dict identity Encode.int people) + -- == """{"Tom":42,"Sue":38}""" + +-} +dict : (k -> String) -> (v -> Value) -> Dict k v -> Value +dict = + JE.dict + + +{-| Encode a `LocalTime` value into a JSON float representing the number of seconds +since epoch. + + import Json.Encode exposing (..) + import Time + + encode 0 + ( object + [ ( "created_at", localTime (Time.millisToPosix 1574447205 ) ) ] + ) + --> "{\"created_at\":1574447.205}" + +-} +localTime : LocalTime -> Value +localTime = + JEE.posix + + +{-| Encode a `Maybe` value with the given encoder. + + import Json.Encode exposing (..) + + encode 0 (maybe int Nothing) + --> "null" + + encode 0 (maybe int (Just 1)) + --> "1" + +-} +maybe : (a -> Value) -> Maybe a -> Value +maybe = + JEE.maybe diff --git a/src/Morphir/SDK/LocalDate.elm b/src/Morphir/SDK/LocalDate.elm index 32baf1ef4..32a49eef8 100644 --- a/src/Morphir/SDK/LocalDate.elm +++ b/src/Morphir/SDK/LocalDate.elm @@ -20,7 +20,7 @@ module Morphir.SDK.LocalDate exposing , diffInDays, diffInWeeks, diffInMonths, diffInYears , addDays, addWeeks, addMonths, addYears , fromCalendarDate, fromISO, fromOrdinalDate, fromParts - , toISOString, monthToInt + , toISOString, monthToInt, intToMonth , DayOfWeek(..), dayOfWeek, isWeekend, isWeekday , Month(..) , year, month, monthNumber, day @@ -47,7 +47,7 @@ module Morphir.SDK.LocalDate exposing # Convert -@docs toISOString, monthToInt +@docs toISOString, monthToInt, intToMonth # Query @@ -334,6 +334,51 @@ monthToInt m = 12 +{-| Convert an `Int` to a `Month`, with January being 1. +-} +intToMonth : Int -> Maybe Month +intToMonth i = + case i of + 1 -> + Just January + + 2 -> + Just February + + 3 -> + Just March + + 4 -> + Just April + + 5 -> + Just May + + 6 -> + Just June + + 7 -> + Just July + + 8 -> + Just August + + 9 -> + Just September + + 10 -> + Just October + + 11 -> + Just November + + 12 -> + Just December + + _ -> + Nothing + + monthToMonth : Month -> Time.Month monthToMonth m = case m of diff --git a/tests/Morphir/SDK/Json/DecodeTests.elm b/tests/Morphir/SDK/Json/DecodeTests.elm new file mode 100644 index 000000000..20d2c8d81 --- /dev/null +++ b/tests/Morphir/SDK/Json/DecodeTests.elm @@ -0,0 +1,610 @@ +module Morphir.SDK.Json.DecodeTests exposing (..) + +import Dict exposing (Dict) +import Expect +import Json.Decode as DE exposing (..) +import Json.Decode.Extra as DEE +import Morphir.SDK.Json.Decode as D +import Morphir.SDK.Json.Encode as E +import Morphir.SDK.LocalTime exposing (LocalTime, fromMilliseconds) +import Set exposing (Set) +import Test exposing (..) +import Time exposing (millisToPosix) + + +stringTests : Test +stringTests = + describe "json to string" + [ test "bool json to string" <| + \_ -> + Expect.equal (D.decodeString D.string "true") (D.decodeString DE.string "true") + , test "int json to string" <| + \_ -> + Expect.equal (D.decodeString D.string "42") (D.decodeString DE.string "42") + , test "float json to string" <| + \_ -> + Expect.equal (D.decodeString D.string "3.14") (D.decodeString DE.string "3.14") + , test "string json to string" <| + \_ -> + Expect.equal (D.decodeString D.string "\"hello\"") (D.decodeString DE.string "\"hello\"") + , test "object json to string" <| + \_ -> + Expect.equal (D.decodeString D.string "{ \"hello\": 42 }") (D.decodeString DE.string "{ \"hello\": 42 }") + ] + + +boolTests : Test +boolTests = + describe "json to bool" + [ test "bool json to bool" <| + \_ -> + Expect.equal (D.decodeString D.bool "true") (D.decodeString DE.bool "true") + , test "int json to bool" <| + \_ -> + Expect.equal (D.decodeString D.bool "42") (D.decodeString DE.bool "42") + , test "float json to bool" <| + \_ -> + Expect.equal (D.decodeString D.bool "3.14") (D.decodeString DE.bool "3.14") + , test "string json to bool" <| + \_ -> + Expect.equal (D.decodeString D.bool "\"hello\"") (D.decodeString DE.bool "\"hello\"") + , test "object json to bool" <| + \_ -> + Expect.equal (D.decodeString D.bool "{ \"hello\": 42 }") (D.decodeString DE.bool "{ \"hello\": 42 }") + ] + + +intTests : Test +intTests = + describe "json to int" + [ test "bool json to int" <| + \_ -> + Expect.equal (D.decodeString D.int "true") (D.decodeString DE.int "true") + , test "int json to int" <| + \_ -> + Expect.equal (D.decodeString D.int "42") (D.decodeString DE.int "42") + , test "float json to int" <| + \_ -> + Expect.equal (D.decodeString D.int "3.14") (D.decodeString DE.int "3.14") + , test "string json to int" <| + \_ -> + Expect.equal (D.decodeString D.int "\"hello\"") (D.decodeString DE.int "\"hello\"") + , test "object json to int" <| + \_ -> + Expect.equal (D.decodeString D.int "{ \"hello\": 42 }") (D.decodeString DE.int "{ \"hello\": 42 }") + ] + + +floatTests : Test +floatTests = + describe "json to float" + [ test "bool json to float" <| + \_ -> + Expect.equal (D.decodeString D.float "true") (D.decodeString DE.float "true") + , test "int json to float" <| + \_ -> + Expect.equal (D.decodeString D.float "42") (D.decodeString DE.float "42") + , test "float json to float" <| + \_ -> + Expect.equal (D.decodeString D.float "3.14") (D.decodeString DE.float "3.14") + , test "string json to float" <| + \_ -> + Expect.equal (D.decodeString D.float "\"hello\"") (D.decodeString DE.float "\"hello\"") + , test "object json to float" <| + \_ -> + Expect.equal (D.decodeString D.float "{ \"hello\": 42 }") (D.decodeString DE.float "{ \"hello\": 42 }") + ] + + +nullableTests : Test +nullableTests = + describe "json to nullable" + [ test "bool json to nullable" <| + \_ -> + Expect.equal (D.decodeString (D.nullable D.int) "true") (D.decodeString (DE.nullable D.int) "true") + , test "int json to nullable" <| + \_ -> + Expect.equal (D.decodeString (D.nullable D.int) "42") (D.decodeString (DE.nullable D.int) "42") + , test "float json to nullable" <| + \_ -> + Expect.equal (D.decodeString (D.nullable D.int) "3.14") (D.decodeString (DE.nullable D.int) "3.14") + , test "string json to nullable" <| + \_ -> + Expect.equal (D.decodeString (D.nullable D.int) "\"hello\"") (D.decodeString (DE.nullable D.int) "\"hello\"") + , test "object json to nullable" <| + \_ -> + Expect.equal (D.decodeString (D.nullable D.int) "{ \"hello\": 42 }") (D.decodeString (DE.nullable D.int) "{ \"hello\": 42 }") + ] + + +listTests : Test +listTests = + describe "json to list" + [ test "int list json to list" <| + \_ -> + Expect.equal (D.decodeString (D.list D.int) "[1,2,3]") (D.decodeString (DE.list D.int) "[1,2,3]") + , test "bool list json to list" <| + \_ -> + Expect.equal (D.decodeString (D.list D.bool) "[true,false]") (D.decodeString (DE.list D.bool) "[true,false]") + ] + + +dictTests : Test +dictTests = + let + people = + Dict.fromList [ ( "Tom", 42 ), ( "Sue", 38 ) ] + in + describe "json to dict" + [ test "test json to dict" <| + \_ -> + Expect.equal (D.decodeString (D.dict D.int) "{ \"alice\": 42, \"bob\": 99 }") (D.decodeString (DE.dict D.int) "{ \"alice\": 42, \"bob\": 99 }") + , test "empty json to dict" <| + \_ -> + Expect.equal (D.decodeString (D.dict D.int) "{}") (D.decodeString (DE.dict D.int) "{}") + ] + + +keyValuePairsTests : Test +keyValuePairsTests = + let + people = + Dict.fromList [ ( "Tom", 42 ), ( "Sue", 38 ) ] + in + describe "json to keyValuePairs" + [ test "test json to keyValuePairs" <| + \_ -> + Expect.equal (D.decodeString (D.keyValuePairs D.int) "{ \"alice\": 42, \"bob\": 99 }") (D.decodeString (DE.keyValuePairs D.int) "{ \"alice\": 42, \"bob\": 99 }") + , test "empty json to keyValuePairs" <| + \_ -> + Expect.equal (D.decodeString (D.keyValuePairs D.int) "{}") (D.decodeString (DE.keyValuePairs D.int) "{}") + ] + + +fieldTests : Test +fieldTests = + describe "field in json" + [ test "one field json" <| + \_ -> + Expect.equal (D.decodeString (D.field "x" int) "{ \"x\": 3 }") (D.decodeString (DE.field "x" int) "{ \"x\": 3 }") + , test "two field json" <| + \_ -> + Expect.equal (D.decodeString (D.field "x" int) "{ \"x\": 3, \"y\": 4 }") (D.decodeString (DE.field "x" int) "{ \"x\": 3, \"y\": 4 }") + , test "bad field json" <| + \_ -> + Expect.equal (D.decodeString (D.keyValuePairs D.int) "{ \"x\": true }") (D.decodeString (DE.keyValuePairs D.int) "{ \"x\": true }") + , test "empty field json" <| + \_ -> + Expect.equal (D.decodeString (D.keyValuePairs D.int) "{}") (D.decodeString (DE.keyValuePairs D.int) "{}") + ] + + +atTests : Test +atTests = + let + json = + """{ "person": { "name": "tom", "age": 42 } }""" + in + describe "at" + [ test "string field exists" <| + \_ -> + Expect.equal (D.decodeString (D.at [ "person", "name" ] D.string) json) (D.decodeString (DE.at [ "person", "name" ] D.string) json) + , test "int field exists" <| + \_ -> + Expect.equal (D.decodeString (D.at [ "person", "age" ] D.int) json) (D.decodeString (DE.at [ "person", "age" ] D.int) json) + , test "int field isn't string" <| + \_ -> + Expect.equal (D.decodeString (D.at [ "person", "age" ] D.string) json) (D.decodeString (DE.at [ "person", "age" ] D.string) json) + , test "empty json" <| + \_ -> + Expect.equal (D.decodeString (D.at [ "person", "age" ] D.int) "{}") (D.decodeString (DE.at [ "person", "age" ] D.int) "{}") + ] + + +indexTests : Test +indexTests = + let + json = + """[ "alice", "bob", "chuck" ]""" + in + describe "index" + [ test "at 0" <| + \_ -> + Expect.equal (D.decodeString (D.index 0 D.string) json) (D.decodeString (DE.index 0 D.string) json) + , test "at 1" <| + \_ -> + Expect.equal (D.decodeString (D.index 1 D.string) json) (D.decodeString (DE.index 1 D.string) json) + , test "at 2" <| + \_ -> + Expect.equal (D.decodeString (D.index 2 D.string) json) (D.decodeString (DE.index 2 D.string) json) + , test "at 3" <| + \_ -> + Expect.equal (D.decodeString (D.index 3 D.string) json) (D.decodeString (DE.index 3 D.string) json) + ] + + +maybeTests : Test +maybeTests = + let + json = + """{ "name": "tom", "age": 42 }""" + in + describe "maybe" + [ test "age int" <| + \_ -> + Expect.equal (D.decodeString (D.maybe (D.field "age" D.int)) json) (D.decodeString (DE.maybe (D.field "age" D.int)) json) + , test "name int" <| + \_ -> + Expect.equal (D.decodeString (D.maybe (D.field "name" D.int)) json) (D.decodeString (DE.maybe (D.field "name" D.int)) json) + , test "height float" <| + \_ -> + Expect.equal (D.decodeString (D.maybe (D.field "height" D.float)) json) (D.decodeString (DE.maybe (D.field "height" D.float)) json) + , test "maybe age" <| + \_ -> + Expect.equal (D.decodeString (D.field "age" (D.maybe D.int)) json) (D.decodeString (D.field "age" (DE.maybe D.int)) json) + , test "maybe name" <| + \_ -> + Expect.equal (D.decodeString (D.field "name" (D.maybe D.int)) json) (D.decodeString (D.field "name" (D.maybe D.int)) json) + , test "maybe height" <| + \_ -> + Expect.equal (D.decodeString (D.field "height" (D.maybe D.float)) json) (D.decodeString (D.field "height" (D.maybe D.float)) json) + ] + + +oneOfTests : Test +oneOfTests = + let + badIntResult = + D.oneOf [ D.int, D.null 0 ] + + badIntExpected = + DE.oneOf [ D.int, D.null 0 ] + in + describe "oneOf" + [ test "check int array" <| + \_ -> + Expect.equal (D.decodeString (D.list badIntResult) "[1,2,null,4]") (D.decodeString (D.list badIntExpected) "[1,2,null,4]") + ] + + +mapTests : Test +mapTests = + describe "map" + [ test "map json" <| + \_ -> + Expect.equal (D.decodeString (D.map (\x -> "Hello" ++ x) D.string) "\"World\"") (Ok "HelloWorld") + ] + + +type alias Point = + { x : Float, y : Float } + + +map2Tests : Test +map2Tests = + let + result = + D.map2 Point + (field "x" float) + (field "y" float) + in + describe "map2" + [ test "map2 json" <| + \_ -> + Expect.equal (D.decodeString result """{ "x": 3, "y": 4 }""") (Ok { x = 3, y = 4 }) + ] + + +type alias Person3 = + { name : String, age : Int, height : Float } + + +map3Tests : Test +map3Tests = + let + result = + D.map3 Person3 + (at [ "name" ] string) + (at [ "info", "age" ] int) + (at [ "info", "height" ] float) + + json = + """{ "name": "tom", "info": { "age": 42, "height": 1.8 } }""" + in + describe "map3" + [ test "map3 json" <| + \_ -> + Expect.equal (D.decodeString result json) (Ok { name = "tom", age = 42, height = 1.8 }) + ] + + +type alias Person4 = + { name : String, age : Int, height : Float, weight : Int } + + +map4Tests : Test +map4Tests = + let + result = + D.map4 Person4 + (at [ "name" ] string) + (at [ "info", "age" ] int) + (at [ "info", "height" ] float) + (at [ "info", "weight" ] int) + + json = + """{ "name": "tom", "info": { "age": 42, "height": 1.8 , "weight" : 150} }""" + in + describe "map4" + [ test "map4 json" <| + \_ -> + Expect.equal (D.decodeString result json) (Ok { name = "tom", age = 42, height = 1.8, weight = 150 }) + ] + + +type alias Person5 = + { name : String, age : Int, height : Float, weight : Int, married : Bool } + + +map5Tests : Test +map5Tests = + let + result = + D.map5 Person5 + (at [ "name" ] string) + (at [ "info", "age" ] int) + (at [ "info", "height" ] float) + (at [ "info", "weight" ] int) + (at [ "married" ] bool) + + json = + """{ "name": "tom", "married": true, "info": { "age": 42, "height": 1.8 , "weight" : 150} }""" + in + describe "map5" + [ test "map5 json" <| + \_ -> + Expect.equal (D.decodeString result json) (Ok { name = "tom", age = 42, height = 1.8, weight = 150, married = True }) + ] + + +type alias Person6 = + { name : String, age : Int, height : Float, weight : Int, married : Bool, city : String } + + +map6Tests : Test +map6Tests = + let + result = + D.map6 Person6 + (at [ "name" ] string) + (at [ "info", "age" ] int) + (at [ "info", "height" ] float) + (at [ "info", "weight" ] int) + (at [ "married" ] bool) + (at [ "address", "city" ] string) + + json = + """{ "name": "tom", "married": true, "info": { "age": 42, "height": 1.8 , "weight" : 160}, "address": { "city": "New York" } }""" + in + describe "map6" + [ test "map6 json" <| + \_ -> + Expect.equal (D.decodeString result json) (Ok { name = "tom", age = 42, height = 1.8, weight = 160, married = True, city = "New York" }) + ] + + +type alias Person7 = + { name : String, age : Int, height : Float, weight : Int, married : Bool, city : String, zipCode : String } + + +map7Tests : Test +map7Tests = + let + result = + D.map7 Person7 + (at [ "name" ] string) + (at [ "info", "age" ] int) + (at [ "info", "height" ] float) + (at [ "info", "weight" ] int) + (at [ "married" ] bool) + (at [ "address", "city" ] string) + (at [ "address", "zipCode" ] string) + + json = + """{ "name": "tom", "married": true, "info": { "age": 42, "height": 1.8 , "weight" : 170}, "address": { "city": "New York", "zipCode": "10001" } }""" + in + describe "map7" + [ test "map7 json" <| + \_ -> + Expect.equal (D.decodeString result json) (Ok { name = "tom", age = 42, height = 1.8, weight = 170, married = True, city = "New York", zipCode = "10001" }) + ] + + +type alias Person8 = + { name : String, age : Int, height : Float, weight : Int, married : Bool, city : String, zipCode : String, bankrupted : Bool } + + +map8Tests : Test +map8Tests = + let + result = + D.map8 Person8 + (at [ "name" ] string) + (at [ "info", "age" ] int) + (at [ "info", "height" ] float) + (at [ "info", "weight" ] int) + (at [ "married" ] bool) + (at [ "address", "city" ] string) + (at [ "address", "zipCode" ] string) + (at [ "bankrupted" ] bool) + + json = + """{ "name": "tom", "married": true, "info": { "age": 42, "height": 1.8 , "weight" : 180}, "address": { "city": "New York", "zipCode": "10001" }, "bankrupted": false }""" + in + describe "map8" + [ test "map8 json" <| + \_ -> + Expect.equal (D.decodeString result json) (Ok { name = "tom", age = 42, height = 1.8, weight = 180, married = True, city = "New York", zipCode = "10001", bankrupted = False }) + ] + + +decodeValueTests : Test +decodeValueTests = + describe "decodeValue" + [ test "bool json to string" <| + \_ -> + Expect.equal (D.decodeValue D.string (E.bool True)) (DE.decodeValue D.string (E.bool True)) + , test "int json to string" <| + \_ -> + Expect.equal (D.decodeValue D.string (E.int 42)) (D.decodeValue DE.string (E.int 42)) + , test "float json to string" <| + \_ -> + Expect.equal (D.decodeValue D.string (E.float 3.14)) (D.decodeValue DE.string (E.float 3.14)) + , test "string json to string" <| + \_ -> + Expect.equal (D.decodeValue D.string (E.string "Hello")) (Ok "Hello") + , test "object json to string" <| + \_ -> + Expect.equal + (D.decodeValue D.string + (E.object + [ ( "name", E.string "Tom" ) + , ( "age", E.int 42 ) + ] + ) + ) + (DE.decodeValue D.string + (E.object + [ ( "name", E.string "Tom" ) + , ( "age", E.int 42 ) + ] + ) + ) + ] + + +errorToStringTests : Test +errorToStringTests = + describe "errorToString" + [ test "Field" <| + \_ -> + Expect.equal (D.errorToString (DE.Failure "name" (E.string "32"))) "Problem with the given value:\n\n\"32\"\n\nname" + ] + + +succeedTests : Test +succeedTests = + describe "succeed" + [ test "bool" <| + \_ -> + Expect.equal (D.decodeString (D.succeed 42) "true")(Ok 42) + , test "array" <| + \_ -> + Expect.equal (D.decodeString (D.succeed 42) "[1,2,3]")(Ok 42) + , test "string" <| + \_ -> + Expect.equal (D.decodeString (D.succeed 42) "hello") (D.decodeString (DE.succeed 42) "hello") + ] + + +failTests : Test +failTests = + describe "fail" + [ test "bool" <| + \_ -> + Expect.equal (D.decodeString (D.fail "Bad input") "true")(D.decodeString (DE.fail "Bad input") "true") + ] + +person : Decoder String +person = + D.at [ "info", "age"] D.int + |> D.andThen personHelp + +personHelp : Int -> Decoder String +personHelp age = + case age of + 18 -> + D.at ["name"] D.string + + 65 -> + D.at ["address", "city"] D.string + + _ -> + D.fail <| + "Trying to decode person, but age is not supported." + + +andThenTests : Test +andThenTests = + let + json = """{ "name": "tom", "married": true, "info": { "age": 65, "height": 1.8 , "weight" : 170}, "address": { "city": "New York", "zipCode": "10001" } }""" + in + describe "andThen" + [ test "person" <| + \_ -> + Expect.equal (D.decodeString person json) (Ok "New York") + ] + +type alias Comment = + { message : String + , responses : Responses + } + +type Responses + = Responses (List Comment) + +comment : Decoder Comment +comment = + map2 Comment + (field "message" string) + (field "responses" (map Responses (list (lazy (\_ -> comment))))) + +lazyTests : Test +lazyTests = + let + json = """{ "message": "Hello", "responses": [ { "message": "World", "responses": [] } ] }""" + in + describe "lazy" + [ test "person" <| + \_ -> + Expect.equal (D.decodeString comment json) (Ok { message = "Hello", responses = Responses [ { message = "World", responses = Responses [] } ] }) + ] + + + +nullTests : Test +nullTests = + describe "null" + [ test "null bool json to False" <| + \_ -> + Expect.equal (D.decodeString (D.null False) "null") (Ok False) + , test "null int json to string" <| + \_ -> + Expect.equal (D.decodeString (D.null 42) "null") (Ok 42) + , test "null int json to int" <| + \_ -> + Expect.equal (D.decodeString (D.null 42) "42") (D.decodeString (DE.null 42) "42") + , test "null int json to false" <| + \_ -> + Expect.equal (D.decodeString (D.null 42) "false") (D.decodeString (DE.null 42) "false") + ] + + +nothingTests : Test +nothingTests = + describe "nothing" + [ test "decode nothing" <| + \_ -> + Expect.equal (D.decodeString D.nothing "{}") (Ok ()) + ] + + + +localTimeTests : Test +localTimeTests = + describe "LocalTime" + [ test "decode localTime" <| + \_ -> + Expect.equal (D.decodeString (D.field "created_at" D.localTime) + "{ \"created_at\": 1574447205.394}") (Ok (fromMilliseconds 1574447205000)) + ] diff --git a/tests/Morphir/SDK/Json/EncodeTests.elm b/tests/Morphir/SDK/Json/EncodeTests.elm new file mode 100644 index 000000000..ad5429855 --- /dev/null +++ b/tests/Morphir/SDK/Json/EncodeTests.elm @@ -0,0 +1,169 @@ +module Morphir.SDK.Json.EncodeTests exposing (..) + +import Dict exposing (Dict) +import Expect +import Json.Encode as JE exposing (..) +import Json.Encode.Extra as JEE +import Morphir.SDK.Json.Encode as J +import Morphir.SDK.LocalTime exposing (LocalTime, fromMilliseconds) +import Set exposing (Set) +import Test exposing (..) +import Time exposing (millisToPosix) + + +stringTests : Test +stringTests = + describe "string to json" + [ test "empty string to json" <| + \_ -> + Expect.equal (J.string "") (JE.string "") + , test "test regular string" <| + \_ -> + Expect.equal (J.string "Hello world") (JE.string "Hello world") + ] + + +intTests : Test +intTests = + describe "int to json" + [ test "negative int to json" <| + \_ -> + Expect.equal (J.int -9999) (JE.int -9999) + , test "zero to json" <| + \_ -> + Expect.equal (J.int 0) (JE.int 0) + , test "positive int to json" <| + \_ -> + Expect.equal (J.int 1234) (JE.int 1234) + ] + + +floatTests : Test +floatTests = + describe "float to json" + [ test "negative float to json" <| + \_ -> + Expect.equal (J.float -0.124) (JE.float -0.124) + , test "zero to json" <| + \_ -> + Expect.equal (J.float 0) (JE.float 0) + , test "positive float to json" <| + \_ -> + Expect.equal (J.float 99.9) (JE.float 99.9) + ] + + +boolTests : Test +boolTests = + describe "bool to json" + [ test "true to json" <| + \_ -> + Expect.equal (J.bool True) (JE.bool True) + , test "false to json" <| + \_ -> + Expect.equal (J.bool False) (JE.bool False) + ] + + +nullTests : Test +nullTests = + describe "null to json" + [ test "true to json" <| + \_ -> + Expect.equal J.null JE.null + ] + + +listTests : Test +listTests = + describe "list to json" + [ test "int list to json" <| + \_ -> + Expect.equal (J.list J.int [ 1, 3, 4 ]) (JE.list J.int [ 1, 3, 4 ]) + , test "bool list to json" <| + \_ -> + Expect.equal (J.list J.bool [ True, False ]) (JE.list J.bool [ True, False ]) + , test "string list to json" <| + \_ -> + Expect.equal (J.list J.string [ "a", "b" ]) (JE.list J.string [ "a", "b" ]) + , test "empty list to json" <| + \_ -> + Expect.equal (J.list J.string []) (JE.list J.string []) + ] + + +setTests : Test +setTests = + describe "set to json" + [ test "int set to json" <| + \_ -> + Expect.equal (J.set J.int (Set.singleton 1)) (JE.set J.int (Set.singleton 1)) + , test "string set to json" <| + \_ -> + Expect.equal (J.set J.string (Set.singleton "a")) (JE.set J.string (Set.singleton "a")) + , test "multiple string set to json" <| + \_ -> + Expect.equal (J.set J.string (Set.fromList [ "a", "b" ])) (JE.set J.string (Set.fromList [ "a", "b" ])) + , test "empty set to json" <| + \_ -> + Expect.equal (J.set J.string Set.empty) (JE.set J.string Set.empty) + ] + + +objectTests : Test +objectTests = + describe "object to json" + [ test "test object to json" <| + \_ -> + Expect.equal + (J.object + [ ( "name", J.string "Tom" ) + , ( "age", J.int 42 ) + ] + ) + (JE.object + [ ( "name", J.string "Tom" ) + , ( "age", J.int 42 ) + ] + ) + , test "empty object to json" <| + \_ -> + Expect.equal (J.object []) (JE.object []) + ] + + +dictTests : Test +dictTests = + let + people = + Dict.fromList [ ( "Tom", 42 ), ( "Sue", 38 ) ] + in + describe "dict to json" + [ test "test dict to json" <| + \_ -> + Expect.equal (J.dict identity J.int people) (JE.dict identity J.int people) + , test "empty dict to json" <| + \_ -> + Expect.equal (J.dict identity J.int Dict.empty) (JE.dict identity J.int Dict.empty) + ] + + +localTimeTests : Test +localTimeTests = + describe "localtime to json" + [ test "test localtime to json" <| + \_ -> + Expect.equal (J.localTime (millisToPosix 1643374590000)) (JEE.posix (millisToPosix 1643374590000)) + ] + + +maybeTests : Test +maybeTests = + describe "maybe to json" + [ test "Nothing to json" <| + \_ -> + Expect.equal (J.maybe J.int Nothing) (JEE.maybe J.int Nothing) + , test "Just to json" <| + \_ -> + Expect.equal (J.maybe J.int (Just 1)) (JEE.maybe J.int (Just 1)) + ]