Skip to content
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
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[Mail Action Protocol](https://mailschema.org) 0.3 for JavaScript. It parses and checks MAP descriptions, type contracts and Registry implementation records, and computes the RFC 8785 digests that identify contracts. The same code runs the MailSchema specification's own checks. It has no Node.js-only dependencies, so it also runs in browsers and workers.

```sh
npm install mailschema@0.3.0
npm install mailschema@0.3.1
```

## Check a description against its contract
Expand Down Expand Up @@ -34,11 +34,13 @@ The profile record, context and schemas are exported byte for byte: `mailschema/
## Command line

```sh
npx mailschema@0.3.0 contract campaign-send-approval-0.1.json
npx mailschema@0.3.0 description description.json --contract campaign-send-approval-0.1.json
npx mailschema@0.3.0 implementation record.json
npx mailschema@0.3.1 contract campaign-send-approval-0.1.json
npx mailschema@0.3.1 description description.json --contract campaign-send-approval-0.1.json
npx mailschema@0.3.1 implementation record.json
```

Each command first prints a line naming what it checked. `contract` prints `Valid type contract <id> <version>` and, on the second line, the contract's digest. `description` prints `Valid MAP 0.3 description <@id>` and, on the second line, the description's digest; with `--contract`, it first checks that the description uses that contract as it allows. `implementation` prints `Valid implementation record for <service>`. A digest is `sha-256:` and the SHA-256 of the document's RFC 8785 canonical JSON, the value `digest()` returns; the description's digest is not the contract digest it names. An invalid file prints every reason on standard error and exits with status 1. Nothing is uploaded or changed.

Checking does not authenticate a message, establish a service's authority or perform an action. Those belong to the implementation, under the [specification](https://mailschema.org/specification/core).

Source: [mailschema/javascript](https://github.com/mailschema/javascript). License: MIT.
2 changes: 1 addition & 1 deletion UPSTREAM.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Release provenance

Version `0.3.0` derives from [`mailschema/mailschema@2c26469`](https://github.com/mailschema/mailschema/commit/2c26469209070f04ff9f8ec29e1387b8699a46a0). Its sources, tests and bundled files are prepared from that commit by `npm run packages:prepare`.
Version `0.3.1` derives from [`mailschema/mailschema@67f9544`](https://github.com/mailschema/mailschema/commit/67f95447f6f2e640af8f515d6844f58d1b1d1fa2). Its sources, tests and bundled files are prepared from that commit by `npm run packages:prepare`.

The bundled MAP 0.3 artifacts are exact copies of that commit's files and do not independently define MAP:

Expand Down
10 changes: 6 additions & 4 deletions bin/mailschema.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { parseArgs } from 'node:util';
import {
Contract,
CONTRACT_MAX_BYTES,
digest,
InvalidDocument,
parseDescription,
parseImplementation,
Expand All @@ -16,9 +17,10 @@ const help = `Mail Action Protocol 0.3 checks
mailschema implementation <record.json>

contract checks a type contract and prints its digest. description checks a
description and, with --contract, that it uses that contract as it allows.
implementation checks a Registry implementation record. Nothing is uploaded
or changed.`;
description and, with --contract, that it uses that contract as it allows,
then prints the description's digest. implementation checks a Registry
implementation record. A digest is the SHA-256 of the document's RFC 8785
canonical JSON. Nothing is uploaded or changed.`;

async function read(path) {
const info = await stat(path);
Expand Down Expand Up @@ -48,7 +50,7 @@ try {
const description = parseDescription(await read(path));
if (values.contract)
report(Contract.parse(await read(values.contract)).descriptionErrors(description));
console.log(`Valid MAP 0.3 description ${description['@id']}`);
console.log(`Valid MAP 0.3 description ${description['@id']}\n${digest(description)}`);
} else if (command === 'implementation') {
const record = parseImplementation(await read(path));
console.log(`Valid implementation record for ${record.service}`);
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mailschema",
"version": "0.3.0",
"version": "0.3.1",
"description": "Mail Action Protocol 0.3: parse and check descriptions, type contracts and implementation records",
"type": "module",
"license": "MIT",
Expand Down
40 changes: 26 additions & 14 deletions test/package.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,33 @@ test('identifies each contract by the digest its example names', async () => {
}
});

test('checks files from the command line', () => {
test('checks files from the command line and prints their digests', async () => {
const cli = fileURLToPath(new URL('../bin/mailschema.js', import.meta.url));
const contract = fileURLToPath(fixture('contracts/publication-approval-0.1.json'));
const description = fileURLToPath(fixture('examples/publication-approval.json'));
assert.match(
execFileSync(process.execPath, [cli, 'contract', contract], { encoding: 'utf8' }),
/^Valid type contract .*\nsha-256:[0-9a-f]{64}\n$/,
);
assert.match(
execFileSync(process.execPath, [cli, 'description', description, '--contract', contract], {
encoding: 'utf8',
}),
/^Valid MAP 0\.3 description /,
);
const run = (...args) =>
execFileSync(process.execPath, [cli, ...args], { encoding: 'utf8', stdio: 'pipe' });
const path = (name) => fileURLToPath(fixture(name));
const descriptionDigests = {
'campaign-send-approval':
'sha-256:850cebcbd01af512c9a360adfdc5f672d08f5bc54ae68539508b5eb98cc44be1',
'publication-approval':
'sha-256:270b6c781f876aa56ecdc7b9ab45ed5dc0e8465bc8df56f1805e05a8f3a6db44',
};
for (const [slug, descriptionDigest] of Object.entries(descriptionDigests)) {
const { id, version, digest: contractDigest } = contracts.get(slug);
const contract = path(`contracts/${slug}-${version}.json`);
const description = path(`examples/${slug}.json`);
assert.equal(
run('contract', contract),
`Valid type contract ${id} ${version}\n${contractDigest}\n`,
);
const { '@id': descriptionId } = await load(`examples/${slug}.json`);
const output = `Valid MAP 0.3 description ${descriptionId}\n${descriptionDigest}\n`;
assert.equal(run('description', description), output, slug);
assert.equal(run('description', description, '--contract', contract), output, slug);
}
const campaign = path('examples/campaign-send-approval.json');
assert.throws(() => run('contract', campaign));
assert.throws(() =>
execFileSync(process.execPath, [cli, 'contract', description], { stdio: 'pipe' }),
run('description', campaign, '--contract', path('contracts/publication-approval-0.1.json')),
);
});
Loading