From b66380a1f009abdbca21c2d4c6fde834a0d9c53a Mon Sep 17 00:00:00 2001 From: Kam Date: Mon, 28 Sep 2026 14:52:15 +1000 Subject: [PATCH] feat: carry the MAP 0.2 core artifacts --- Cargo.lock | 2 +- Cargo.toml | 8 +- README.md | 31 +- UPSTREAM.md | 13 +- contexts/map-0.2.jsonld | 91 +++ contracts/content-review-0.1.json | 58 -- contracts/content-review-0.2.json | 91 --- schemas/content-review-0.1.schema.json | 54 -- schemas/content-review-0.2.schema.json | 54 -- schemas/forms-0.1.schema.json | 329 ++++++++ schemas/map-0.1.schema.json | 411 ---------- schemas/map-0.2.schema.json | 1033 ++++++++++++++++++++++++ schemas/type-contract-0.2.schema.json | 179 ++++ src/lib.rs | 88 +- 14 files changed, 1688 insertions(+), 754 deletions(-) create mode 100644 contexts/map-0.2.jsonld delete mode 100644 contracts/content-review-0.1.json delete mode 100644 contracts/content-review-0.2.json delete mode 100644 schemas/content-review-0.1.schema.json delete mode 100644 schemas/content-review-0.2.schema.json create mode 100644 schemas/forms-0.1.schema.json delete mode 100644 schemas/map-0.1.schema.json create mode 100644 schemas/map-0.2.schema.json create mode 100644 schemas/type-contract-0.2.schema.json diff --git a/Cargo.lock b/Cargo.lock index 0cd03a5..6b59aac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "mailschema" -version = "0.1.3" +version = "0.1.4" dependencies = [ "serde_json", ] diff --git a/Cargo.toml b/Cargo.toml index 5271fca..7c1cb92 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,17 @@ [package] name = "mailschema" -version = "0.1.3" +version = "0.1.4" edition = "2021" rust-version = "1.70" -description = "Embedded JSON Schemas for Mail Action Protocol and the MailSchema Registry" -homepage = "https://mailschema.org/tools/" +description = "Embedded Mail Action Protocol 0.2 core artifacts and MailSchema Registry schemas" +homepage = "https://mailschema.org/tools" repository = "https://github.com/mailschema/rust" documentation = "https://docs.rs/mailschema" license = "MIT" readme = "README.md" keywords = ["email", "schema", "json-schema", "agents"] categories = ["data-structures", "email"] -include = ["src/**", "schemas/**", "contracts/**", "Cargo.toml", "README.md", "LICENSE"] +include = ["src/**", "schemas/**", "contexts/**", "Cargo.toml", "README.md", "LICENSE"] [dev-dependencies] serde_json = "1" diff --git a/README.md b/README.md index be782b6..e4eb9e1 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,9 @@ [![crates.io](https://img.shields.io/crates/v/mailschema)](https://crates.io/crates/mailschema) [![CI](https://github.com/mailschema/rust/actions/workflows/test.yml/badge.svg)](https://github.com/mailschema/rust/actions/workflows/test.yml) -Embed the canonical Mail Action Protocol and MailSchema Registry schemas in a Rust application without runtime dependencies, filesystem access or network calls. +The Mail Action Protocol 0.2 core artifacts and the MailSchema Registry schemas, embedded without runtime dependencies, filesystem access or network calls. -[Specification](https://mailschema.org/specification/) · [Registry](https://mailschema.org/registry/) · [Tools](https://mailschema.org/tools/) · [Source](https://github.com/mailschema/rust) +[Specification](https://mailschema.org/specification) · [Registry](https://mailschema.org/registry) · [Tools](https://mailschema.org/tools) · [Source](https://github.com/mailschema/rust) ## Install @@ -16,32 +16,17 @@ mailschema = "0.1" Rust 1.70 or newer is required. -## Use a schema +## Use an artifact ```rust -use mailschema::{Schema, CONTENT_REVIEW_0_2_CONTRACT, MAP_0_1_SCHEMA}; +use mailschema::{Schema, MAP_CONTEXT, MAP_PROFILE, MAP_SCHEMA}; -assert_eq!(Schema::Map01.as_str(), MAP_0_1_SCHEMA); - -let content_review = Schema::ContentReview02.as_str(); -assert!(content_review.contains("Content Review")); -assert!(CONTENT_REVIEW_0_2_CONTRACT.contains("MapTypeContract")); +assert_eq!(Schema::Map.as_str(), MAP_SCHEMA); +assert!(MAP_CONTEXT.contains("MailAction")); ``` -The crate exposes five Draft 2020-12 documents: - -- `MAP_0_1_SCHEMA` -- `CONTENT_REVIEW_0_1_SCHEMA` -- `CONTENT_REVIEW_0_2_SCHEMA` -- `CONTRIBUTION_SCHEMA` -- `RECORD_SCHEMA` - -Parse the strings with your JSON library and pass them to a Draft 2020-12 validator with format checking enabled. The crate deliberately does not select a validation engine for its consumers. - -`CONTENT_REVIEW_0_1_CONTRACT` and `CONTENT_REVIEW_0_2_CONTRACT` expose the immutable canonical type contracts used by MAP wire messages. New integrations should use 0.2. +`MAP_SCHEMA`, `MAP_CONTEXT`, `CONTRACT_FORMAT_SCHEMA` and `FORMS_SCHEMA` are byte-identical to the files the [profile record](https://mailschema.org/profiles/map/0.2.json) binds by SHA-256. `CONTRIBUTION_SCHEMA` and `RECORD_SCHEMA` describe Registry files. Parse them with your JSON library and pass the schemas to a Draft 2020-12 validator; the crate does not choose a validation engine. Type contracts are not bundled: a client obtains them from the [Registry catalogue](https://mailschema.org/registry/catalog.json) by digest. ## Trust boundary -A valid document is structured input. Schema validation does not authenticate a service, grant authority, approve an action, check Registry references or establish product conformance. Implementations must apply their own endpoint trust, credentials, permissions and policy before executing a request. - -Package versions and MAP profile versions advance independently. MIT licensed. +A valid document is structured input. Schema validation does not authenticate a service, grant authority or establish product conformance. Package versions and MAP profile versions advance independently. MIT licensed. diff --git a/UPSTREAM.md b/UPSTREAM.md index bc9b205..63b90fa 100644 --- a/UPSTREAM.md +++ b/UPSTREAM.md @@ -1,14 +1,13 @@ # Release provenance -Version `0.1.3` derives from [`mailschema/mailschema@f630ec5`](https://github.com/mailschema/mailschema/commit/f630ec5569e7167114fb2a39a10548febfa6b979). +Version `0.1.4` derives from [`mailschema/mailschema@61a12c9`](https://github.com/mailschema/mailschema/commit/61a12c97663061b46707f8d996bd1f59cae9a544), in [pull request #22](https://github.com/mailschema/mailschema/pull/22). The bundled files are exact projections of that source commit and do not independently define MAP: -- MAP 0.1 schema: `c39befeecf8643e8beda1f53a8d1411482b68ddf14974f3806cfa95776103f85` -- Content Review 0.1 schema: `10e2263c820ba3a6ef0381c377078e2c1f324c3b142a6f8caf92b298cc66c692` -- Content Review 0.1 contract: `754ab7245ddb09ff4da000fccc60d746724379d4146ea227973e5d87f105f800` -- Content Review 0.2 schema: `d2286ffe410cceae56f9caa145634fd9613f5ce51e11ba089cad67b9f62259b7` -- Content Review 0.2 contract: `3bd1cd4a86e00d00590eeb150b7b29cebece589058d6094a40aa9d497dc8efe0` +- MAP 0.2 core schema: `732d60fcb758d945dc15c57d885c9d1c0c76423a7439096d9755e8ec9830836a` +- MAP 0.2 JSON-LD context: `dfed0189524d7d16d31ed3d8af97e9a0c458200c4c034b413f9d14fa35b60257` +- Type contract format: `e030007cc034662c9d9d448a8a035aff3106cfb96fb60e6aa9f92b99b25df0b9` +- Form fields block: `3a976b93a057380e3630ca05e77e221a43a98559fb7fda8066299ec4bf2a8f9e` - Contribution schema: `a8a241b4837971a6251bcc135c704283536f4fb825fbbaedaf1598c6e688d564` -The canonical contracts live in the main MailSchema repository. This repository owns the Rust package surface and its release history. +The canonical artifacts live in the main MailSchema repository. This repository owns the Rust crate surface and its release history. diff --git a/contexts/map-0.2.jsonld b/contexts/map-0.2.jsonld new file mode 100644 index 0000000..c97aca3 --- /dev/null +++ b/contexts/map-0.2.jsonld @@ -0,0 +1,91 @@ +{ + "@context": { + "@version": 1.1, + "map": "https://mailschema.org/ns/map#", + "schema": "https://schema.org/", + "MailAction": "map:MailAction", + "profile": { + "@id": "map:profile", + "@type": "@id" + }, + "type": { + "@id": "map:interactionType", + "@context": { + "id": { + "@id": "map:id", + "@type": "@id" + } + } + }, + "version": "schema:version", + "contractDigest": "map:contractDigest", + "describedAt": { + "@id": "schema:dateCreated", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "expiresAt": { + "@id": "schema:expires", + "@type": "http://www.w3.org/2001/XMLSchema#dateTime" + }, + "service": { + "@id": "schema:provider", + "@context": { + "id": { + "@id": "map:id", + "@type": "@id" + } + } + }, + "name": "schema:name", + "authority": "map:authority", + "onBehalfOf": { + "@id": "map:onBehalfOf", + "@context": { + "id": { + "@id": "map:id", + "@type": "@id" + } + } + }, + "resource": { + "@id": "map:resource", + "@type": "@id" + }, + "execution": "map:execution", + "url": { + "@id": "schema:url", + "@type": "@id" + }, + "resultUrlTemplate": "map:resultUrlTemplate", + "resultRetentionSeconds": "map:resultRetentionSeconds", + "humanUrl": { + "@id": "map:humanUrl", + "@type": "@id" + }, + "target": { + "@id": "schema:object", + "@context": { + "id": { + "@id": "map:id", + "@type": "@id" + } + } + }, + "recipient": "schema:recipient", + "details": { + "@id": "map:details", + "@type": "@json" + }, + "revision": "schema:version", + "title": "schema:name", + "digest": "map:digest", + "operations": { + "@id": "schema:potentialAction", + "@container": "@set", + "@context": { + "id": "map:operationId" + } + }, + "description": "schema:description" + } +} diff --git a/contracts/content-review-0.1.json b/contracts/content-review-0.1.json deleted file mode 100644 index 922baef..0000000 --- a/contracts/content-review-0.1.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "kind": "MapTypeContract", - "id": "https://mailschema.org/types/content-review", - "version": "0.1", - "profile": "https://mailschema.org/profiles/map/0.1", - "target": "Service-owned content at the exact revision and digest named in the request.", - "requestSchema": { - "url": "https://mailschema.org/schemas/content-review-0.1.schema.json", - "canonicalDigest": "sha-256:cdad1149ab193d1bf71cb9463e3c4b7331ed56b0123e225adf68f70a2022b29d" - }, - "operations": [ - { - "id": "request-changes", - "effect": "Record review feedback on the named revision.", - "results": [ - { - "state": "accepted", - "outputSchema": { - "type": "object", - "additionalProperties": false, - "properties": { - "feedbackRecorded": { "const": true } - }, - "required": ["feedbackRecorded"] - } - } - ] - }, - { - "id": "approve", - "effect": "Record approval of the named revision.", - "results": [ - { - "state": "approval-required", - "outputSchema": { - "type": "object", - "additionalProperties": false, - "properties": { - "approvalUrl": { "type": "string", "format": "uri", "pattern": "^https://" } - }, - "required": ["approvalUrl"] - } - }, - { - "state": "completed", - "outputSchema": { - "type": "object", - "additionalProperties": false, - "properties": { - "decision": { "const": "approved" } - }, - "required": ["decision"] - } - } - ] - } - ] -} diff --git a/contracts/content-review-0.2.json b/contracts/content-review-0.2.json deleted file mode 100644 index 7dec56a..0000000 --- a/contracts/content-review-0.2.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "kind": "MapTypeContract", - "id": "https://mailschema.org/types/content-review", - "version": "0.2", - "profile": "https://mailschema.org/profiles/map/0.1", - "target": "Service-owned content at the exact revision and digest named in the request.", - "requestSchema": { - "url": "https://mailschema.org/schemas/content-review-0.2.schema.json", - "canonicalDigest": "sha-256:43a01e620e0730e47755694bdf5d086f8d0a1d3d8328fb0c7d2a4b70c6e62e25" - }, - "operations": [ - { - "id": "request-changes", - "effect": "Record review feedback on the named revision.", - "results": [ - { - "state": "accepted", - "outputSchema": { - "type": "object", - "additionalProperties": false, - "properties": { - "feedbackRecorded": { - "const": true - } - }, - "required": [ - "feedbackRecorded" - ] - } - } - ] - }, - { - "id": "approve", - "effect": "Record approval of the named revision, or record why a proposed approval ended without approval.", - "results": [ - { - "state": "approval-required", - "outputSchema": { - "type": "object", - "additionalProperties": false, - "properties": { - "approvalUrl": { - "type": "string", - "format": "uri", - "pattern": "^https://" - } - }, - "required": [ - "approvalUrl" - ] - } - }, - { - "state": "completed", - "outputSchema": { - "type": "object", - "additionalProperties": false, - "properties": { - "decision": { - "const": "approved" - } - }, - "required": [ - "decision" - ] - } - }, - { - "state": "failed", - "outputSchema": { - "type": "object", - "additionalProperties": false, - "properties": { - "reason": { - "enum": [ - "declined", - "stale-target", - "expired" - ] - } - }, - "required": [ - "reason" - ] - } - } - ] - } - ] -} diff --git a/schemas/content-review-0.1.schema.json b/schemas/content-review-0.1.schema.json deleted file mode 100644 index 57fc90f..0000000 --- a/schemas/content-review-0.1.schema.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://mailschema.org/schemas/content-review-0.1.schema.json", - "title": "Content Review 0.1 MAP request binding", - "allOf": [ - { "$ref": "https://mailschema.org/schemas/map-0.1.schema.json#/$defs/request" }, - { - "type": "object", - "properties": { - "type": { - "type": "object", - "properties": { - "id": { "const": "https://mailschema.org/types/content-review" }, - "version": { "const": "0.1" } - } - } - } - }, - { - "oneOf": [ - { - "type": "object", - "properties": { - "operation": { "const": "request-changes" }, - "input": { - "type": "object", - "additionalProperties": false, - "properties": { - "feedback": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - } - }, - "required": ["feedback"] - } - } - }, - { - "type": "object", - "properties": { - "operation": { "const": "approve" }, - "input": { - "type": "object", - "additionalProperties": false, - "maxProperties": 0 - } - } - } - ] - } - ] -} diff --git a/schemas/content-review-0.2.schema.json b/schemas/content-review-0.2.schema.json deleted file mode 100644 index 88b5be7..0000000 --- a/schemas/content-review-0.2.schema.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://mailschema.org/schemas/content-review-0.2.schema.json", - "title": "Content Review 0.2 MAP request binding", - "allOf": [ - { "$ref": "https://mailschema.org/schemas/map-0.1.schema.json#/$defs/request" }, - { - "type": "object", - "properties": { - "type": { - "type": "object", - "properties": { - "id": { "const": "https://mailschema.org/types/content-review" }, - "version": { "const": "0.2" } - } - } - } - }, - { - "oneOf": [ - { - "type": "object", - "properties": { - "operation": { "const": "request-changes" }, - "input": { - "type": "object", - "additionalProperties": false, - "properties": { - "feedback": { - "type": "string", - "minLength": 1, - "maxLength": 12000, - "pattern": "\\S" - } - }, - "required": ["feedback"] - } - } - }, - { - "type": "object", - "properties": { - "operation": { "const": "approve" }, - "input": { - "type": "object", - "additionalProperties": false, - "maxProperties": 0 - } - } - } - ] - } - ] -} diff --git a/schemas/forms-0.1.schema.json b/schemas/forms-0.1.schema.json new file mode 100644 index 0000000..88f680b --- /dev/null +++ b/schemas/forms-0.1.schema.json @@ -0,0 +1,329 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mailschema.org/schemas/forms-0.1.schema.json", + "title": "MailSchema form fields 0.1", + "description": "Shared, immutable blocks for types that collect values: a closed subset of JSON Schema based on Model Context Protocol elicitation, with HTML autofill field names.", + "$defs": { + "fieldName": { + "type": "string", + "pattern": "^[a-z][A-Za-z0-9]{0,63}$" + }, + "text": { + "type": "string", + "minLength": 1, + "maxLength": 2000, + "pattern": "[^\\t\\n\\u000b\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]" + }, + "autocomplete": { + "type": "string", + "maxLength": 200, + "pattern": "^(?:section-[a-z0-9-]+ )?(?:(?:shipping|billing) )?(?:(?:home|work|mobile|fax|pager) )?[a-z][a-z0-9-]*$", + "not": { + "pattern": "(?:^| )(?:current-password|new-password|one-time-code|cc-[a-z-]+)$" + } + }, + "choice": { + "type": "object", + "additionalProperties": false, + "properties": { + "const": { + "type": "string", + "minLength": 1, + "maxLength": 200 + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 240, + "pattern": "[^\\t\\n\\u000b\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]" + } + }, + "required": ["const", "title"] + }, + "choices": { + "type": "array", + "items": { + "$ref": "#/$defs/choice" + }, + "minItems": 1, + "maxItems": 100 + }, + "textField": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "const": "string" + }, + "title": { + "$ref": "#/$defs/text" + }, + "description": { + "$ref": "#/$defs/text" + }, + "minLength": { + "type": "integer", + "minimum": 0, + "maximum": 12000 + }, + "maxLength": { + "type": "integer", + "minimum": 1, + "maximum": 12000 + }, + "format": { + "enum": ["email", "uri", "date", "date-time"] + }, + "default": { + "type": "string", + "maxLength": 12000 + }, + "autocomplete": { + "$ref": "#/$defs/autocomplete" + } + }, + "required": ["type", "title"] + }, + "numberField": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "enum": ["number", "integer"] + }, + "title": { + "$ref": "#/$defs/text" + }, + "description": { + "$ref": "#/$defs/text" + }, + "minimum": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "maximum": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "default": { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + "autocomplete": { + "$ref": "#/$defs/autocomplete" + } + }, + "required": ["type", "title"] + }, + "booleanField": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "const": "boolean" + }, + "title": { + "$ref": "#/$defs/text" + }, + "description": { + "$ref": "#/$defs/text" + }, + "default": { + "type": "boolean" + } + }, + "required": ["type", "title"] + }, + "choiceField": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "const": "string" + }, + "title": { + "$ref": "#/$defs/text" + }, + "description": { + "$ref": "#/$defs/text" + }, + "oneOf": { + "$ref": "#/$defs/choices" + }, + "default": { + "type": "string", + "maxLength": 200 + }, + "autocomplete": { + "$ref": "#/$defs/autocomplete" + } + }, + "required": ["type", "title", "oneOf"] + }, + "choicesField": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "const": "array" + }, + "title": { + "$ref": "#/$defs/text" + }, + "description": { + "$ref": "#/$defs/text" + }, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "anyOf": { + "$ref": "#/$defs/choices" + } + }, + "required": ["anyOf"] + }, + "minItems": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "maxItems": { + "type": "integer", + "minimum": 1, + "maximum": 100 + }, + "uniqueItems": { + "const": true + }, + "default": { + "type": "array", + "items": { + "type": "string", + "maxLength": 200 + }, + "maxItems": 100, + "uniqueItems": true + } + }, + "required": ["type", "title", "items", "uniqueItems"] + }, + "fields": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "minProperties": 1, + "maxProperties": 50, + "propertyNames": { + "$ref": "#/$defs/fieldName" + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/$defs/textField" + }, + { + "$ref": "#/$defs/numberField" + }, + { + "$ref": "#/$defs/booleanField" + }, + { + "$ref": "#/$defs/choiceField" + }, + { + "$ref": "#/$defs/choicesField" + } + ] + } + }, + "required": { + "type": "array", + "items": { + "$ref": "#/$defs/fieldName" + }, + "uniqueItems": true, + "maxItems": 50 + } + }, + "required": ["type", "properties"] + }, + "choiceFields": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "const": "object" + }, + "properties": { + "type": "object", + "minProperties": 1, + "maxProperties": 50, + "propertyNames": { + "$ref": "#/$defs/fieldName" + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/$defs/booleanField" + }, + { + "$ref": "#/$defs/choiceField" + }, + { + "$ref": "#/$defs/choicesField" + } + ] + } + }, + "required": { + "type": "array", + "items": { + "$ref": "#/$defs/fieldName" + }, + "uniqueItems": true, + "maxItems": 50 + } + }, + "required": ["type", "properties"] + }, + "fieldValues": { + "type": "object", + "maxProperties": 50, + "propertyNames": { + "$ref": "#/$defs/fieldName" + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string", + "maxLength": 12000 + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "type": "array", + "items": { + "type": "string", + "maxLength": 200 + }, + "maxItems": 100, + "uniqueItems": true + } + ] + } + } + } +} diff --git a/schemas/map-0.1.schema.json b/schemas/map-0.1.schema.json deleted file mode 100644 index 352cd5d..0000000 --- a/schemas/map-0.1.schema.json +++ /dev/null @@ -1,411 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://mailschema.org/schemas/map-0.1.schema.json", - "title": "Mail Action Protocol 0.1", - "description": "Description, request, result and problem documents for the MAP 0.1 authenticated HTTPS profile.", - "oneOf": [ - { "$ref": "#/$defs/description" }, - { "$ref": "#/$defs/request" }, - { "$ref": "#/$defs/result" }, - { "$ref": "#/$defs/problem" } - ], - "$defs": { - "profile": { - "const": "https://mailschema.org/profiles/map/0.1" - }, - "identifier": { - "type": "string", - "format": "uri", - "maxLength": 2048 - }, - "httpsIdentifier": { - "type": "string", - "format": "uri", - "pattern": "^https://", - "maxLength": 2048 - }, - "uuidUrn": { - "type": "string", - "pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" - }, - "digest": { - "type": "string", - "pattern": "^sha-256:[a-f0-9]{64}$" - }, - "contractDigest": { - "type": "string", - "pattern": "^sha-256:[a-f0-9]{64}$" - }, - "typeReference": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { "$ref": "#/$defs/identifier" }, - "version": { - "type": "string", - "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", - "maxLength": 80 - }, - "contractDigest": { "$ref": "#/$defs/contractDigest" } - }, - "required": ["id", "version", "contractDigest"] - }, - "target": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { "$ref": "#/$defs/identifier" }, - "revision": { "type": "string", "minLength": 1, "maxLength": 240 }, - "title": { "type": "string", "minLength": 1, "maxLength": 500 }, - "digest": { "$ref": "#/$defs/digest" } - }, - "required": ["id", "revision", "digest"] - }, - "operation": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "name": { "type": "string", "minLength": 1, "maxLength": 240 }, - "description": { "type": "string", "minLength": 1, "maxLength": 2000 }, - "inputSchema": { "$ref": "#/$defs/identifier" } - }, - "required": ["id", "name", "description", "inputSchema"] - }, - "description": { - "type": "object", - "additionalProperties": false, - "properties": { - "@context": { - "const": "https://mailschema.org/contexts/map-0.1.jsonld" - }, - "@type": { "const": "MailAction" }, - "@id": { "$ref": "#/$defs/uuidUrn" }, - "profile": { "$ref": "#/$defs/profile" }, - "type": { "$ref": "#/$defs/typeReference" }, - "describedAt": { "type": "string", "format": "date-time" }, - "expiresAt": { "type": "string", "format": "date-time" }, - "service": { - "type": "object", - "additionalProperties": false, - "properties": { - "id": { "$ref": "#/$defs/identifier" }, - "name": { "type": "string", "minLength": 1, "maxLength": 240 }, - "execution": { - "type": "object", - "additionalProperties": false, - "properties": { - "url": { "$ref": "#/$defs/httpsIdentifier" }, - "method": { "const": "POST" }, - "requestMediaType": { "const": "application/json" }, - "resultMediaType": { "const": "application/json" }, - "resultUrlTemplate": { - "type": "string", - "format": "uri-template", - "pattern": "^https://.*\\{requestId\\}.*$", - "maxLength": 2048 - }, - "resultRetentionSeconds": { - "type": "integer", - "minimum": 300, - "maximum": 31536000 - } - }, - "required": [ - "url", - "method", - "requestMediaType", - "resultMediaType", - "resultUrlTemplate", - "resultRetentionSeconds" - ] - }, - "humanUrl": { "$ref": "#/$defs/httpsIdentifier" }, - "authorization": { - "type": "object", - "additionalProperties": false, - "properties": { - "kind": { "const": "service-configured" }, - "schemes": { - "type": "array", - "items": { "enum": ["oauth2", "bearer", "session"] }, - "minItems": 1, - "uniqueItems": true - }, - "audience": { "type": "string", "minLength": 1, "maxLength": 500 } - }, - "required": ["kind", "schemes"] - } - }, - "required": ["id", "name", "execution", "humanUrl", "authorization"] - }, - "target": { "$ref": "#/$defs/target" }, - "operations": { - "type": "array", - "items": { "$ref": "#/$defs/operation" }, - "minItems": 1, - "maxItems": 100 - } - }, - "required": [ - "@context", - "@type", - "@id", - "profile", - "type", - "describedAt", - "expiresAt", - "service", - "target", - "operations" - ] - }, - "request": { - "type": "object", - "additionalProperties": false, - "properties": { - "kind": { "const": "MapRequest" }, - "profile": { "$ref": "#/$defs/profile" }, - "requestId": { "$ref": "#/$defs/uuidUrn" }, - "interactionId": { "$ref": "#/$defs/uuidUrn" }, - "type": { "$ref": "#/$defs/typeReference" }, - "operation": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "target": { "$ref": "#/$defs/target" }, - "input": { "type": "object", "maxProperties": 100 } - }, - "required": [ - "kind", - "profile", - "requestId", - "interactionId", - "type", - "operation", - "target", - "input" - ] - }, - "result": { - "type": "object", - "additionalProperties": false, - "properties": { - "kind": { "const": "MapResult" }, - "profile": { "$ref": "#/$defs/profile" }, - "requestId": { "$ref": "#/$defs/uuidUrn" }, - "interactionId": { "$ref": "#/$defs/uuidUrn" }, - "type": { "$ref": "#/$defs/typeReference" }, - "operation": { - "type": "string", - "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", - "maxLength": 100 - }, - "state": { - "enum": ["accepted", "completed", "failed", "pending", "approval-required"] - }, - "target": { "$ref": "#/$defs/target" }, - "recordedAt": { "type": "string", "format": "date-time" }, - "resultUrl": { "$ref": "#/$defs/httpsIdentifier" }, - "output": { "type": "object", "maxProperties": 100 } - }, - "required": [ - "kind", - "profile", - "requestId", - "interactionId", - "type", - "operation", - "state", - "target", - "recordedAt", - "resultUrl", - "output" - ] - }, - "problem": { - "type": "object", - "additionalProperties": false, - "properties": { - "type": { - "enum": [ - "https://mailschema.org/problems/refused", - "https://mailschema.org/problems/authentication-required", - "https://mailschema.org/problems/result-not-found", - "https://mailschema.org/problems/stale-target", - "https://mailschema.org/problems/expired-interaction", - "https://mailschema.org/problems/invalid-request", - "https://mailschema.org/problems/unsupported-profile", - "https://mailschema.org/problems/unsupported-type", - "https://mailschema.org/problems/unsupported-operation", - "https://mailschema.org/problems/idempotency-conflict", - "https://mailschema.org/problems/request-in-progress" - ] - }, - "title": { "type": "string", "minLength": 1, "maxLength": 240 }, - "status": { "type": "integer", "minimum": 400, "maximum": 599 }, - "detail": { "type": "string", "minLength": 1, "maxLength": 4000 }, - "instance": { "$ref": "#/$defs/identifier" }, - "profile": { "$ref": "#/$defs/profile" }, - "requestId": { "$ref": "#/$defs/uuidUrn" }, - "interactionId": { "$ref": "#/$defs/uuidUrn" }, - "code": { - "enum": [ - "refused", - "authentication-required", - "result-not-found", - "stale-target", - "expired-interaction", - "invalid-request", - "unsupported-profile", - "unsupported-type", - "unsupported-operation", - "idempotency-conflict", - "request-in-progress" - ] - }, - "target": { "$ref": "#/$defs/target" } - }, - "required": ["type", "title", "status", "detail"], - "dependentRequired": { - "instance": ["profile", "requestId", "code"], - "profile": ["instance", "requestId", "code"], - "requestId": ["instance", "profile", "code"], - "interactionId": ["instance", "profile", "requestId", "code"], - "code": ["instance", "profile", "requestId"] - }, - "allOf": [ - { - "if": { - "properties": { "code": { "not": { "const": "result-not-found" } } }, - "required": ["code"] - }, - "then": { "required": ["interactionId"] } - }, - { - "if": { "properties": { "code": { "const": "invalid-request" } }, "required": ["code"] }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/invalid-request" }, - "status": { "const": 400 } - } - } - }, - { - "if": { - "properties": { "code": { "const": "authentication-required" } }, - "required": ["code"] - }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/authentication-required" }, - "status": { "const": 401 } - } - } - }, - { - "if": { "properties": { "code": { "const": "refused" } }, "required": ["code"] }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/refused" }, - "status": { "const": 403 } - } - } - }, - { - "if": { "properties": { "code": { "const": "result-not-found" } }, "required": ["code"] }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/result-not-found" }, - "status": { "const": 404 } - } - } - }, - { - "if": { "properties": { "code": { "const": "stale-target" } }, "required": ["code"] }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/stale-target" }, - "status": { "const": 409 } - }, - "required": ["target"] - } - }, - { - "if": { - "properties": { "code": { "const": "idempotency-conflict" } }, - "required": ["code"] - }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/idempotency-conflict" }, - "status": { "const": 409 } - } - } - }, - { - "if": { - "properties": { "code": { "const": "request-in-progress" } }, - "required": ["code"] - }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/request-in-progress" }, - "status": { "const": 409 } - } - } - }, - { - "if": { - "properties": { "code": { "const": "expired-interaction" } }, - "required": ["code"] - }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/expired-interaction" }, - "status": { "const": 410 } - } - } - }, - { - "if": { - "properties": { "code": { "const": "unsupported-profile" } }, - "required": ["code"] - }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/unsupported-profile" }, - "status": { "const": 422 } - } - } - }, - { - "if": { "properties": { "code": { "const": "unsupported-type" } }, "required": ["code"] }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/unsupported-type" }, - "status": { "const": 422 } - } - } - }, - { - "if": { - "properties": { "code": { "const": "unsupported-operation" } }, - "required": ["code"] - }, - "then": { - "properties": { - "type": { "const": "https://mailschema.org/problems/unsupported-operation" }, - "status": { "const": 422 } - } - } - } - ] - } - } -} diff --git a/schemas/map-0.2.schema.json b/schemas/map-0.2.schema.json new file mode 100644 index 0000000..a87b566 --- /dev/null +++ b/schemas/map-0.2.schema.json @@ -0,0 +1,1033 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mailschema.org/schemas/map-0.2.schema.json", + "title": "Mail Action Protocol 0.2", + "description": "Description, request, result and problem documents for MAP 0.2: a type-agnostic core whose types are defined by digest-bound contracts, with credential or possession authority over HTTPS.", + "oneOf": [ + { + "$ref": "#/$defs/description" + }, + { + "$ref": "#/$defs/request" + }, + { + "$ref": "#/$defs/result" + }, + { + "$ref": "#/$defs/problem" + } + ], + "$defs": { + "profile": { + "const": "https://mailschema.org/profiles/map/0.2" + }, + "nonBlank": { + "type": "string", + "pattern": "[^\\t\\n\\u000b\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]" + }, + "dateTime": { + "type": "string", + "pattern": "^(?:[0-9]{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12][0-9]|3[01])|(?:0[469]|11)-(?:0[1-9]|[12][0-9]|30)|02-(?:0[1-9]|1[0-9]|2[0-8]))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:[02468][048]|[13579][26])00)-02-29)T(?:[01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](?:\\.[0-9]{1,3})?(?:Z|[+-](?:[01][0-9]|2[0-3]):[0-5][0-9])$" + }, + "date": { + "type": "string", + "pattern": "^(?:[0-9]{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12][0-9]|3[01])|(?:0[469]|11)-(?:0[1-9]|[12][0-9]|30)|02-(?:0[1-9]|1[0-9]|2[0-8]))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|(?:[02468][048]|[13579][26])00)-02-29)$" + }, + "identifier": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9+.-]*:(?:[A-Za-z0-9._~:/?#@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})+$", + "maxLength": 2048 + }, + "httpsIdentifier": { + "type": "string", + "pattern": "^https://(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)*[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?::(?:[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?(?:[/?#](?:[A-Za-z0-9._~:/?#@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})*)?$", + "maxLength": 2048 + }, + "httpsTarget": { + "type": "string", + "pattern": "^https://(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)*[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?::(?:[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?(?:[/?](?:[A-Za-z0-9._~:/?@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})*)?$", + "maxLength": 2048 + }, + "httpsOrigin": { + "type": "string", + "pattern": "^https://(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)*[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?::(?:[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?$", + "maxLength": 300 + }, + "urlTemplate": { + "type": "string", + "pattern": "^https://(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?\\.)*[A-Za-z](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?::(?:[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5]))?[/?](?:[A-Za-z0-9._~:/?@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})*\\{requestId\\}(?:[A-Za-z0-9._~:/?#@!$&'()*+,;=-]|%[0-9A-Fa-f]{2})*$", + "maxLength": 2048 + }, + "address": { + "type": "string", + "pattern": "^(?:[A-Za-z0-9.!#$%&'*+/=?^_`{|}~-]|[^\\u0000-\\u00a0\\u1680\\u2000-\\u200f\\u2028-\\u202f\\u205f-\\u206f\\u3000\\ufeff])+@[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?(?:\\.[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$", + "maxLength": 320 + }, + "uuidUrn": { + "type": "string", + "pattern": "^urn:uuid:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + }, + "digest": { + "type": "string", + "pattern": "^sha-256:[a-f0-9]{64}$" + }, + "contractDigest": { + "type": "string", + "pattern": "^sha-256:[a-f0-9]{64}$" + }, + "typeReference": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/$defs/identifier" + }, + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", + "maxLength": 80 + }, + "contractDigest": { + "$ref": "#/$defs/contractDigest" + } + }, + "required": ["id", "version", "contractDigest"] + }, + "token": { + "type": "string", + "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", + "maxLength": 100 + }, + "target": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/$defs/identifier" + }, + "revision": { + "type": "string", + "minLength": 1, + "maxLength": 240 + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 500 + }, + "digest": { + "$ref": "#/$defs/digest" + } + }, + "required": ["id", "revision", "digest"] + }, + "operation": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/$defs/token" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 240 + }, + "description": { + "type": "string", + "minLength": 1, + "maxLength": 2000 + } + }, + "required": ["id", "name", "description"] + }, + "description": { + "type": "object", + "additionalProperties": false, + "properties": { + "@context": { + "const": "https://mailschema.org/contexts/map-0.2.jsonld" + }, + "@type": { + "const": "MailAction" + }, + "@id": { + "$ref": "#/$defs/uuidUrn" + }, + "profile": { + "$ref": "#/$defs/profile" + }, + "type": { + "$ref": "#/$defs/typeReference" + }, + "describedAt": { + "$ref": "#/$defs/dateTime" + }, + "expiresAt": { + "$ref": "#/$defs/dateTime" + }, + "service": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/$defs/httpsIdentifier" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 240 + }, + "authority": { + "enum": ["credential", "possession"] + }, + "onBehalfOf": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/$defs/identifier" + }, + "name": { + "type": "string", + "minLength": 1, + "maxLength": 240 + } + }, + "required": ["id", "name"] + }, + "resource": { + "$ref": "#/$defs/httpsTarget" + }, + "execution": { + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "$ref": "#/$defs/httpsTarget" + }, + "resultUrlTemplate": { + "$ref": "#/$defs/urlTemplate" + }, + "resultRetentionSeconds": { + "type": "integer", + "minimum": 300, + "maximum": 31536000 + } + }, + "required": ["url", "resultUrlTemplate", "resultRetentionSeconds"] + }, + "humanUrl": { + "$ref": "#/$defs/httpsIdentifier" + } + }, + "required": ["id", "name", "authority", "execution", "humanUrl"] + }, + "recipient": { + "$ref": "#/$defs/address" + }, + "target": { + "$ref": "#/$defs/target" + }, + "details": { + "type": "object", + "maxProperties": 100, + "propertyNames": { + "$ref": "#/$defs/detailsKey" + }, + "additionalProperties": { + "$ref": "#/$defs/detailsValue" + } + }, + "operations": { + "type": "array", + "items": { + "$ref": "#/$defs/operation" + }, + "minItems": 1, + "maxItems": 100 + } + }, + "required": [ + "@context", + "@type", + "@id", + "profile", + "type", + "describedAt", + "expiresAt", + "service", + "target", + "operations" + ], + "allOf": [ + { + "if": { + "type": "object", + "properties": { + "service": { + "type": "object", + "properties": { + "authority": { + "const": "credential" + } + } + } + } + }, + "then": { + "type": "object", + "properties": { + "service": { + "type": "object", + "required": ["resource"] + } + }, + "not": { + "required": ["recipient"] + } + }, + "else": { + "type": "object", + "properties": { + "service": { + "type": "object", + "properties": { + "resource": false, + "onBehalfOf": false + } + } + }, + "required": ["recipient"] + } + } + ] + }, + "request": { + "type": "object", + "additionalProperties": false, + "properties": { + "kind": { + "const": "MapRequest" + }, + "profile": { + "$ref": "#/$defs/profile" + }, + "requestId": { + "$ref": "#/$defs/uuidUrn" + }, + "interactionId": { + "$ref": "#/$defs/uuidUrn" + }, + "descriptionDigest": { + "$ref": "#/$defs/digest" + }, + "type": { + "$ref": "#/$defs/typeReference" + }, + "operation": { + "$ref": "#/$defs/token" + }, + "input": { + "type": "object", + "maxProperties": 100 + } + }, + "required": [ + "kind", + "profile", + "requestId", + "interactionId", + "descriptionDigest", + "type", + "operation", + "input" + ] + }, + "result": { + "type": "object", + "additionalProperties": false, + "properties": { + "kind": { + "const": "MapResult" + }, + "profile": { + "$ref": "#/$defs/profile" + }, + "requestId": { + "$ref": "#/$defs/uuidUrn" + }, + "interactionId": { + "$ref": "#/$defs/uuidUrn" + }, + "descriptionDigest": { + "$ref": "#/$defs/digest" + }, + "type": { + "$ref": "#/$defs/typeReference" + }, + "operation": { + "$ref": "#/$defs/token" + }, + "state": { + "enum": ["accepted", "completed", "failed", "pending", "approval-required"] + }, + "target": { + "$ref": "#/$defs/target" + }, + "recordedAt": { + "$ref": "#/$defs/dateTime" + }, + "resultUrl": { + "$ref": "#/$defs/httpsIdentifier" + }, + "actor": { + "type": "string", + "minLength": 1, + "maxLength": 240 + }, + "approvalUrl": { + "$ref": "#/$defs/httpsIdentifier" + }, + "reason": { + "$ref": "#/$defs/token" + }, + "output": { + "type": "object", + "maxProperties": 100 + } + }, + "required": [ + "kind", + "profile", + "requestId", + "interactionId", + "descriptionDigest", + "type", + "operation", + "state", + "target", + "recordedAt", + "resultUrl", + "output" + ], + "allOf": [ + { + "if": { + "type": "object", + "properties": { + "state": { + "const": "approval-required" + } + } + }, + "then": { + "type": "object", + "required": ["approvalUrl"] + }, + "else": { + "type": "object", + "not": { + "required": ["approvalUrl"] + } + } + }, + { + "if": { + "type": "object", + "properties": { + "state": { + "const": "failed" + } + } + }, + "then": { + "type": "object", + "required": ["reason"] + }, + "else": { + "type": "object", + "not": { + "required": ["reason"] + } + } + } + ] + }, + "problem": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "enum": [ + "https://mailschema.org/problems/refused", + "https://mailschema.org/problems/authentication-required", + "https://mailschema.org/problems/result-not-found", + "https://mailschema.org/problems/stale-target", + "https://mailschema.org/problems/expired-interaction", + "https://mailschema.org/problems/invalid-request", + "https://mailschema.org/problems/unsupported-type", + "https://mailschema.org/problems/unsupported-operation", + "https://mailschema.org/problems/idempotency-conflict", + "https://mailschema.org/problems/request-in-progress", + "https://mailschema.org/problems/already-decided" + ] + }, + "title": { + "type": "string", + "minLength": 1, + "maxLength": 240 + }, + "status": { + "type": "integer", + "minimum": 400, + "maximum": 599 + }, + "detail": { + "type": "string", + "minLength": 1, + "maxLength": 4000 + }, + "instance": { + "$ref": "#/$defs/identifier" + }, + "profile": { + "$ref": "#/$defs/profile" + }, + "requestId": { + "$ref": "#/$defs/uuidUrn" + }, + "interactionId": { + "$ref": "#/$defs/uuidUrn" + }, + "code": { + "enum": [ + "refused", + "result-not-found", + "stale-target", + "expired-interaction", + "invalid-request", + "unsupported-type", + "unsupported-operation", + "idempotency-conflict", + "request-in-progress", + "already-decided" + ] + }, + "target": { + "$ref": "#/$defs/target" + }, + "errors": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "detail": { + "type": "string", + "minLength": 1, + "maxLength": 2000 + }, + "pointer": { + "type": "string", + "pattern": "^(?:/(?:[^~/]|~[01])*)*$", + "maxLength": 1000 + } + }, + "required": ["detail", "pointer"] + } + } + }, + "required": ["type", "title", "status", "detail"], + "dependentRequired": { + "instance": ["profile", "requestId", "code"], + "profile": ["instance", "requestId", "code"], + "requestId": ["instance", "profile", "code"], + "interactionId": ["instance", "profile", "requestId", "code"], + "code": ["instance", "profile", "requestId"] + }, + "allOf": [ + { + "if": { + "properties": { + "type": { + "enum": [ + "https://mailschema.org/problems/result-not-found", + "https://mailschema.org/problems/stale-target", + "https://mailschema.org/problems/expired-interaction", + "https://mailschema.org/problems/unsupported-type", + "https://mailschema.org/problems/unsupported-operation", + "https://mailschema.org/problems/idempotency-conflict", + "https://mailschema.org/problems/request-in-progress", + "https://mailschema.org/problems/already-decided" + ] + } + }, + "required": ["type"] + }, + "then": { + "required": ["code"] + } + }, + { + "if": { + "required": ["errors"] + }, + "then": { + "properties": { + "code": { + "const": "invalid-request" + } + }, + "required": ["code"] + } + }, + { + "if": { + "properties": { + "code": { + "not": { + "const": "result-not-found" + } + } + }, + "required": ["code"] + }, + "then": { + "required": ["interactionId"] + } + }, + { + "if": { + "properties": { + "code": { + "const": "invalid-request" + } + }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/invalid-request" + }, + "status": { + "const": 400 + } + } + } + }, + { + "if": { + "properties": { + "code": { + "const": "refused" + } + }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/refused" + }, + "status": { + "const": 403 + } + } + } + }, + { + "if": { + "properties": { + "code": { + "const": "result-not-found" + } + }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/result-not-found" + }, + "status": { + "const": 404 + } + } + } + }, + { + "if": { + "properties": { + "code": { + "const": "stale-target" + } + }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/stale-target" + }, + "status": { + "const": 409 + } + }, + "required": ["target"] + } + }, + { + "if": { + "properties": { + "code": { + "const": "idempotency-conflict" + } + }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/idempotency-conflict" + }, + "status": { + "const": 409 + } + } + } + }, + { + "if": { + "properties": { + "code": { + "const": "request-in-progress" + } + }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/request-in-progress" + }, + "status": { + "const": 409 + } + } + } + }, + { + "if": { + "properties": { + "code": { + "const": "already-decided" + } + }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/already-decided" + }, + "status": { + "const": 409 + } + } + } + }, + { + "if": { + "properties": { + "code": { + "const": "expired-interaction" + } + }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/expired-interaction" + }, + "status": { + "const": 410 + } + } + } + }, + { + "if": { + "properties": { + "code": { + "const": "unsupported-type" + } + }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/unsupported-type" + }, + "status": { + "const": 422 + } + } + } + }, + { + "if": { + "properties": { + "code": { + "const": "unsupported-operation" + } + }, + "required": ["code"] + }, + "then": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/unsupported-operation" + }, + "status": { + "const": 422 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/invalid-request" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 400 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/authentication-required" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 401 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/refused" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 403 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/result-not-found" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 404 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/stale-target" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 409 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/idempotency-conflict" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 409 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/request-in-progress" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 409 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/already-decided" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 409 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/expired-interaction" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 410 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/unsupported-type" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 422 + } + } + } + }, + { + "if": { + "properties": { + "type": { + "const": "https://mailschema.org/problems/unsupported-operation" + } + }, + "required": ["type"] + }, + "then": { + "properties": { + "status": { + "const": 422 + } + } + } + } + ] + }, + "detailsKey": { + "type": "string", + "pattern": "^[A-Za-z][A-Za-z0-9_]{0,63}$" + }, + "detailsValue": { + "anyOf": [ + { + "type": "string", + "maxLength": 12000 + }, + { + "type": "integer", + "minimum": -9007199254740991, + "maximum": 9007199254740991 + }, + { + "type": "boolean" + }, + { + "type": "null" + }, + { + "type": "array", + "maxItems": 100, + "items": { + "$ref": "#/$defs/detailsValue" + } + }, + { + "type": "object", + "maxProperties": 100, + "propertyNames": { + "$ref": "#/$defs/detailsKey" + }, + "additionalProperties": { + "$ref": "#/$defs/detailsValue" + } + } + ] + } + } +} diff --git a/schemas/type-contract-0.2.schema.json b/schemas/type-contract-0.2.schema.json new file mode 100644 index 0000000..1fe14b2 --- /dev/null +++ b/schemas/type-contract-0.2.schema.json @@ -0,0 +1,179 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://mailschema.org/schemas/type-contract-0.2.schema.json", + "title": "MailSchema type contract for MAP 0.2", + "description": "A versioned interaction contract: the details, inputs, results, authority, repeatability and consequences of each operation, with every schema it depends on pinned by digest.", + "type": "object", + "additionalProperties": false, + "properties": { + "kind": { + "const": "MapTypeContract" + }, + "id": { + "type": "string", + "pattern": "^https://mailschema\\.org/types/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", + "maxLength": 2048 + }, + "version": { + "type": "string", + "pattern": "^[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[a-zA-Z0-9.-]+)?$", + "maxLength": 80 + }, + "profile": { + "const": "https://mailschema.org/profiles/map/0.2" + }, + "target": { + "$ref": "#/$defs/prose" + }, + "dependencies": { + "type": "array", + "minItems": 1, + "maxItems": 20, + "items": { + "$ref": "#/$defs/schemaReference" + } + }, + "detailsSchema": { + "type": "object" + }, + "requestSchema": { + "$ref": "#/$defs/schemaReference" + }, + "operations": { + "type": "array", + "minItems": 1, + "maxItems": 100, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "id": { + "$ref": "#/$defs/token" + }, + "effect": { + "$ref": "#/$defs/prose" + }, + "authority": { + "type": "array", + "items": { + "enum": ["credential", "possession"] + }, + "minItems": 1, + "maxItems": 2, + "uniqueItems": true + }, + "consequences": { + "type": "array", + "items": { + "enum": [ + "refusal", + "protection", + "record", + "disclosure", + "commitment", + "authorization", + "assertion" + ] + }, + "minItems": 1, + "maxItems": 7, + "uniqueItems": true + }, + "repeatable": { + "type": "boolean" + }, + "fieldBindings": { + "type": "array", + "minItems": 1, + "maxItems": 10, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "input": { + "$ref": "#/$defs/pointer" + }, + "fields": { + "$ref": "#/$defs/pointer" + } + }, + "required": ["input", "fields"] + } + }, + "results": { + "type": "array", + "minItems": 1, + "maxItems": 5, + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "state": { + "enum": ["accepted", "completed", "failed", "pending", "approval-required"] + }, + "reasons": { + "type": "array", + "items": { + "$ref": "#/$defs/token" + }, + "minItems": 1, + "maxItems": 20, + "uniqueItems": true + }, + "outputSchema": { + "type": "object" + } + }, + "required": ["state", "outputSchema"] + } + } + }, + "required": ["id", "effect", "authority", "consequences", "results"] + } + } + }, + "required": [ + "kind", + "id", + "version", + "profile", + "target", + "dependencies", + "requestSchema", + "operations" + ], + "$defs": { + "prose": { + "type": "string", + "minLength": 1, + "maxLength": 12000, + "pattern": "[^\\t\\n\\u000b\\f\\r \\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]" + }, + "token": { + "type": "string", + "pattern": "^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$", + "maxLength": 100 + }, + "pointer": { + "type": "string", + "pattern": "^(?:/(?:[^~/]|~[01])*)+$", + "maxLength": 1000 + }, + "schemaReference": { + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "pattern": "^https://mailschema\\.org/schemas/[a-z0-9.-]+\\.json$", + "maxLength": 2048 + }, + "canonicalDigest": { + "type": "string", + "pattern": "^sha-256:[a-f0-9]{64}$" + } + }, + "required": ["url", "canonicalDigest"] + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 4ad9e7b..ce9707c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,52 +1,52 @@ -//! Bundled JSON Schemas for Mail Action Protocol and the MailSchema Registry. +//! The Mail Action Protocol 0.2 core artifacts and the MailSchema Registry schemas. //! -//! These Draft 2020-12 schemas describe contribution files and expanded type -//! records. Pass them to a JSON Schema validator with format checking enabled. -//! This crate does not implement email delivery, MAP authorization or Registry -//! reference checks. The embedded schemas need no filesystem or network access. +//! The MAP 0.2 core schema, JSON-LD context, type contract format and form fields +//! block are embedded exactly as the profile record binds them by SHA-256. Type +//! contracts are not bundled: clients obtain them from the Registry catalogue by +//! digest. The Registry schemas describe contribution files and expanded type +//! records; pass them to a JSON Schema validator with format checking enabled. +//! Nothing here needs filesystem or network access. -/// Schema for new types, amendments and implementation declarations. -pub const CONTRIBUTION_SCHEMA: &str = include_str!("../schemas/contribution.schema.json"); +/// The MAP profile whose core artifacts this crate carries. +pub const MAP_PROFILE: &str = "https://mailschema.org/profiles/map/0.2"; -/// Schema for expanded Registry records, including attribution and history. -pub const RECORD_SCHEMA: &str = include_str!("../schemas/record.schema.json"); +/// The MAP 0.2 core schema: descriptions, requests, results and problems. +pub const MAP_SCHEMA: &str = include_str!("../schemas/map-0.2.schema.json"); -/// Schema for MAP 0.1 descriptions, requests, results and problems. -pub const MAP_0_1_SCHEMA: &str = include_str!("../schemas/map-0.1.schema.json"); +/// The MAP 0.2 JSON-LD context. +pub const MAP_CONTEXT: &str = include_str!("../contexts/map-0.2.jsonld"); -/// Content Review 0.1 request binding. -pub const CONTENT_REVIEW_0_1_SCHEMA: &str = - include_str!("../schemas/content-review-0.1.schema.json"); +/// The type contract format every MAP 0.2 contract follows. +pub const CONTRACT_FORMAT_SCHEMA: &str = include_str!("../schemas/type-contract-0.2.schema.json"); -/// Canonical Content Review 0.1 type contract. -pub const CONTENT_REVIEW_0_1_CONTRACT: &str = include_str!("../contracts/content-review-0.1.json"); +/// The form fields block contracts pin. +pub const FORMS_SCHEMA: &str = include_str!("../schemas/forms-0.1.schema.json"); -/// Content Review 0.2 request binding. -pub const CONTENT_REVIEW_0_2_SCHEMA: &str = - include_str!("../schemas/content-review-0.2.schema.json"); +/// Schema for new types, amendments and implementation declarations. +pub const CONTRIBUTION_SCHEMA: &str = include_str!("../schemas/contribution.schema.json"); -/// Canonical Content Review 0.2 type contract. -pub const CONTENT_REVIEW_0_2_CONTRACT: &str = include_str!("../contracts/content-review-0.2.json"); +/// Schema for expanded Registry records, including attribution and history. +pub const RECORD_SCHEMA: &str = include_str!("../schemas/record.schema.json"); -/// Supported local schema documents. +/// The embedded JSON Schema documents. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum Schema { + Map, + ContractFormat, + Forms, Contribution, Record, - Map01, - ContentReview01, - ContentReview02, } impl Schema { /// Return the embedded JSON text for this schema. pub const fn as_str(self) -> &'static str { match self { + Self::Map => MAP_SCHEMA, + Self::ContractFormat => CONTRACT_FORMAT_SCHEMA, + Self::Forms => FORMS_SCHEMA, Self::Contribution => CONTRIBUTION_SCHEMA, Self::Record => RECORD_SCHEMA, - Self::Map01 => MAP_0_1_SCHEMA, - Self::ContentReview01 => CONTENT_REVIEW_0_1_SCHEMA, - Self::ContentReview02 => CONTENT_REVIEW_0_2_SCHEMA, } } } @@ -58,35 +58,21 @@ mod tests { #[test] fn embeds_standalone_draft_2020_12_documents() { for schema in [ + Schema::Map, + Schema::ContractFormat, + Schema::Forms, Schema::Contribution, Schema::Record, - Schema::Map01, - Schema::ContentReview01, - Schema::ContentReview02, ] { let value: serde_json::Value = serde_json::from_str(schema.as_str()).unwrap(); - assert_eq!( - value["$schema"], - "https://json-schema.org/draft/2020-12/schema" - ); + assert_eq!(value["$schema"], "https://json-schema.org/draft/2020-12/schema"); } - let contribution: serde_json::Value = serde_json::from_str(CONTRIBUTION_SCHEMA).unwrap(); - assert!(contribution["$defs"]["record"].is_object()); - let map: serde_json::Value = serde_json::from_str(MAP_0_1_SCHEMA).unwrap(); - assert_eq!( - map["$id"], - "https://mailschema.org/schemas/map-0.1.schema.json" - ); + let map: serde_json::Value = serde_json::from_str(MAP_SCHEMA).unwrap(); + assert_eq!(map["$id"], "https://mailschema.org/schemas/map-0.2.schema.json"); + let context: serde_json::Value = serde_json::from_str(MAP_CONTEXT).unwrap(); + assert_eq!(context["@context"]["MailAction"], "map:MailAction"); let record: serde_json::Value = serde_json::from_str(RECORD_SCHEMA).unwrap(); assert_eq!(record["$ref"], "#/$defs/record"); - let contract: serde_json::Value = - serde_json::from_str(CONTENT_REVIEW_0_1_CONTRACT).unwrap(); - assert_eq!( - contract["id"], - "https://mailschema.org/types/content-review" - ); - let current_contract: serde_json::Value = - serde_json::from_str(CONTENT_REVIEW_0_2_CONTRACT).unwrap(); - assert_eq!(current_contract["version"], "0.2"); + assert_eq!(MAP_PROFILE, "https://mailschema.org/profiles/map/0.2"); } }