mailschema 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -7
- data/README.md +29 -88
- data/artifacts/context.jsonld +50 -0
- data/artifacts/contract.schema.json +206 -0
- data/artifacts/core.schema.json +210 -0
- data/artifacts/implementation.schema.json +173 -0
- data/artifacts/profile.json +27 -0
- data/lib/mailschema/artifacts.rb +32 -75
- data/lib/mailschema/authentication.rb +77 -0
- data/lib/mailschema/contract.rb +184 -281
- data/lib/mailschema/description.rb +56 -0
- data/lib/mailschema/formats.rb +77 -0
- data/lib/mailschema/header.rb +75 -0
- data/lib/mailschema/implementation.rb +27 -0
- data/lib/mailschema/jcs.rb +11 -10
- data/lib/mailschema/json.rb +190 -0
- data/lib/mailschema/message.rb +127 -12
- data/lib/mailschema/origin.rb +18 -0
- data/lib/mailschema/patterns.rb +173 -0
- data/lib/mailschema/subschemas.rb +70 -0
- data/lib/mailschema/version.rb +1 -1
- data/lib/mailschema.rb +16 -26
- data/sig/mailschema.rbs +76 -68
- metadata +29 -54
- data/contexts/map-0.2.jsonld +0 -91
- data/lib/mailschema/capability.rb +0 -41
- data/lib/mailschema/document.rb +0 -79
- data/lib/mailschema/documents.rb +0 -127
- data/lib/mailschema/forms.rb +0 -41
- data/lib/mailschema/http.rb +0 -25
- data/lib/mailschema/limits.rb +0 -56
- data/lib/mailschema/pointer.rb +0 -46
- data/lib/mailschema/references.rb +0 -62
- data/lib/mailschema/schema_walk.rb +0 -29
- data/schemas/contribution.schema.json +0 -874
- data/schemas/forms-0.1.schema.json +0 -329
- data/schemas/map-0.2.schema.json +0 -1033
- data/schemas/type-contract-0.2.schema.json +0 -179
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mailschema
|
|
4
|
+
# The regular expressions a contract's schemas may use: the subset that ECMA-262 (with the u
|
|
5
|
+
# flag) and other engines read alike, so every implementation enforces the same contract.
|
|
6
|
+
class Patterns
|
|
7
|
+
SYNTAX_CHARACTERS = "^$\\.*+?()[]{}|/"
|
|
8
|
+
CONTROL_ESCAPES = { "t" => 0x09, "n" => 0x0a, "f" => 0x0c, "r" => 0x0d }.freeze
|
|
9
|
+
QUANTIFIERS = "*+?{"
|
|
10
|
+
BOUNDS = /\A\{([0-9]{1,4})(?:(,)([0-9]{1,4})?)?\}/
|
|
11
|
+
# A class member that is an unescaped hyphen.
|
|
12
|
+
HYPHEN = :hyphen
|
|
13
|
+
|
|
14
|
+
# Why a pattern is outside the portable subset, or nil. The subset is printable ASCII
|
|
15
|
+
# text, with any other code point written as \uXXXX. It has literal characters and escaped
|
|
16
|
+
# syntax characters; \t, \n, \f, \r and \d, the ASCII digits; non-empty classes of literal
|
|
17
|
+
# or escaped members and ranges, with a literal hyphen only first or last; (?: groups;
|
|
18
|
+
# alternation; ^ and $; and one *, +, ?, {n}, {n,} or {n,m} after an atom. Other class
|
|
19
|
+
# escapes and the dot match different characters in different engines, and brackets, && or
|
|
20
|
+
# a stray brace inside a class, or a quantifier on a quantifier, are read differently or
|
|
21
|
+
# refused by one of them. ^ and $ anchor the whole value, as in ECMA-262; an engine whose
|
|
22
|
+
# anchors also match at line breaks must read them so.
|
|
23
|
+
def self.unportable(pattern) = new(pattern).problem
|
|
24
|
+
|
|
25
|
+
def initialize(pattern)
|
|
26
|
+
@pattern = pattern
|
|
27
|
+
@at = 0
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def problem
|
|
31
|
+
return "a character outside printable ASCII" unless /\A[\x20-\x7e]*\z/.match?(@pattern)
|
|
32
|
+
|
|
33
|
+
disjunction(0)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def disjunction(depth)
|
|
39
|
+
quantifiable = false
|
|
40
|
+
while @at < @pattern.length
|
|
41
|
+
char = @pattern[@at]
|
|
42
|
+
return depth.zero? ? "an unmatched )" : nil if char == ")"
|
|
43
|
+
|
|
44
|
+
problem, quantifiable = QUANTIFIERS.include?(char) ? quantified(char, quantifiable) : term(char, depth)
|
|
45
|
+
return problem if problem
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The problem with the term at `char`, or nil and whether a quantifier may follow it.
|
|
50
|
+
def term(char, depth)
|
|
51
|
+
case char
|
|
52
|
+
when "|", "^", "$"
|
|
53
|
+
@at += 1
|
|
54
|
+
[nil, false]
|
|
55
|
+
when "(" then [group(depth), true]
|
|
56
|
+
when "[" then [character_class, true]
|
|
57
|
+
when "\\"
|
|
58
|
+
code = escape(false)
|
|
59
|
+
[(code if code.is_a?(String)), true]
|
|
60
|
+
when "." then ["an unescaped dot"]
|
|
61
|
+
when "]", "}" then ["an unescaped #{char}"]
|
|
62
|
+
else
|
|
63
|
+
@at += 1
|
|
64
|
+
[nil, true]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def group(depth)
|
|
69
|
+
return "a group other than (?:" unless @pattern[@at, 3] == "(?:"
|
|
70
|
+
|
|
71
|
+
@at += 3
|
|
72
|
+
problem = disjunction(depth + 1)
|
|
73
|
+
return problem if problem
|
|
74
|
+
return "an unclosed group" unless @pattern[@at] == ")"
|
|
75
|
+
|
|
76
|
+
@at += 1
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def quantified(char, quantifiable)
|
|
81
|
+
return [char == "{" ? "an unescaped {" : "a quantifier with nothing to repeat"] unless quantifiable
|
|
82
|
+
|
|
83
|
+
if char == "{"
|
|
84
|
+
bounds = BOUNDS.match(@pattern[@at..])
|
|
85
|
+
return ["a malformed {n,m} quantifier"] unless bounds
|
|
86
|
+
return ["a {n,m} quantifier with m below n"] if bounds[3] && bounds[3].to_i < bounds[1].to_i
|
|
87
|
+
|
|
88
|
+
@at += bounds[0].length
|
|
89
|
+
else
|
|
90
|
+
@at += 1
|
|
91
|
+
end
|
|
92
|
+
following = @pattern[@at]
|
|
93
|
+
[("a quantifier on a quantifier" if following && QUANTIFIERS.include?(following)), false]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# One escaped code point, -1 for the digit class outside a class, or a problem.
|
|
97
|
+
def escape(in_class)
|
|
98
|
+
following = @pattern[@at + 1]
|
|
99
|
+
return "a trailing backslash" if following.nil?
|
|
100
|
+
return unicode_escape if following == "u"
|
|
101
|
+
|
|
102
|
+
@at += 2
|
|
103
|
+
return -1 if following == "d" && !in_class
|
|
104
|
+
return CONTROL_ESCAPES[following] if CONTROL_ESCAPES.key?(following)
|
|
105
|
+
return following.ord if SYNTAX_CHARACTERS.include?(following) || (in_class && following == "-")
|
|
106
|
+
|
|
107
|
+
"the escape \\#{following}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def unicode_escape
|
|
111
|
+
digits = @pattern[@at + 2, 4].to_s
|
|
112
|
+
return "a malformed \\u escape" unless /\A\h{4}\z/.match?(digits)
|
|
113
|
+
|
|
114
|
+
code = digits.hex
|
|
115
|
+
return "a surrogate \\u escape" if code.between?(0xd800, 0xdfff)
|
|
116
|
+
|
|
117
|
+
@at += 6
|
|
118
|
+
code
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def character_class
|
|
122
|
+
@at += @pattern[@at + 1] == "^" ? 2 : 1
|
|
123
|
+
# Each member's code point, or HYPHEN.
|
|
124
|
+
members = []
|
|
125
|
+
until @pattern[@at] == "]"
|
|
126
|
+
problem = class_member(members)
|
|
127
|
+
return problem if problem
|
|
128
|
+
end
|
|
129
|
+
@at += 1
|
|
130
|
+
return "an empty character class" if members.empty?
|
|
131
|
+
|
|
132
|
+
range_problem(members)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def class_member(members)
|
|
136
|
+
char = @pattern[@at]
|
|
137
|
+
return "an unclosed character class" if char.nil?
|
|
138
|
+
return "a bracket inside a class" if char == "["
|
|
139
|
+
return "&& inside a class" if char == "&" && @pattern[@at + 1] == "&"
|
|
140
|
+
|
|
141
|
+
if char == "\\"
|
|
142
|
+
code = escape(true)
|
|
143
|
+
return code if code.is_a?(String)
|
|
144
|
+
|
|
145
|
+
members << code
|
|
146
|
+
else
|
|
147
|
+
members << (char == "-" ? HYPHEN : char.ord)
|
|
148
|
+
@at += 1
|
|
149
|
+
end
|
|
150
|
+
nil
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def range_problem(members)
|
|
154
|
+
last = members.size - 1
|
|
155
|
+
index = 0
|
|
156
|
+
while index <= last
|
|
157
|
+
low = members[index]
|
|
158
|
+
if low == HYPHEN
|
|
159
|
+
return "an ambiguous hyphen in a class" unless [0, last].include?(index)
|
|
160
|
+
elsif members[index + 1] == HYPHEN && index + 2 <= last
|
|
161
|
+
high = members[index + 2]
|
|
162
|
+
return "an ambiguous hyphen in a class" if high == HYPHEN
|
|
163
|
+
return "a class range out of order" if high < low
|
|
164
|
+
|
|
165
|
+
index += 2
|
|
166
|
+
return "an ambiguous hyphen in a class" if members[index + 1] == HYPHEN && index + 1 != last
|
|
167
|
+
end
|
|
168
|
+
index += 1
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
private_constant :Patterns
|
|
173
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mailschema
|
|
4
|
+
# The schemas within a JSON Schema 2020-12 schema, by the JSON Pointers that name them.
|
|
5
|
+
module Subschemas
|
|
6
|
+
SCHEMA = %w[additionalProperties propertyNames items contains not if then else unevaluatedItems
|
|
7
|
+
unevaluatedProperties].freeze
|
|
8
|
+
SCHEMA_LISTS = %w[allOf anyOf oneOf prefixItems].freeze
|
|
9
|
+
SCHEMA_MAPS = %w[properties patternProperties $defs dependentSchemas].freeze
|
|
10
|
+
# The keywords that apply their subschemas to the value itself, not to a member or item.
|
|
11
|
+
IN_PLACE = %w[not if then else allOf anyOf oneOf dependentSchemas].freeze
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
# Every schema within a schema, object or boolean, with its JSON Pointer, the schema
|
|
15
|
+
# itself first.
|
|
16
|
+
def all(schema, pointer)
|
|
17
|
+
return [[pointer, schema]] if [true, false].include?(schema)
|
|
18
|
+
return [] unless schema.is_a?(Hash)
|
|
19
|
+
|
|
20
|
+
[[pointer, schema], *children(schema, pointer).flat_map { |_keyword, at, item| all(item, at) }]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Whether applying the schema can never finish: from its root, a chain of $ref and
|
|
24
|
+
# in-place keywords returns to a schema without moving into a member or item.
|
|
25
|
+
def endless?(schema)
|
|
26
|
+
links = all(schema, "").to_h { |at, node| [at, node.is_a?(Hash) ? links(at, node) : []] }
|
|
27
|
+
reachable = [""]
|
|
28
|
+
seen = Set[""]
|
|
29
|
+
reachable.each { |at| links.fetch(at, []).map(&:first).each { |to| reachable << to if seen.add?(to) } }
|
|
30
|
+
state = {}
|
|
31
|
+
visit = lambda do |at|
|
|
32
|
+
return state[at] == :open if state.key?(at)
|
|
33
|
+
|
|
34
|
+
state[at] = :open
|
|
35
|
+
looped = links.fetch(at, []).any? { |to, in_place| in_place && visit.call(to) }
|
|
36
|
+
state[at] = :done
|
|
37
|
+
looped
|
|
38
|
+
end
|
|
39
|
+
reachable.any? { |at| visit.call(at) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
# The values a schema object holds where schemas belong, as [keyword, pointer, value].
|
|
45
|
+
def children(schema, pointer)
|
|
46
|
+
lists = SCHEMA_LISTS.select { |keyword| schema[keyword].is_a?(Array) }
|
|
47
|
+
maps = SCHEMA_MAPS.select { |keyword| schema[keyword].is_a?(Hash) }
|
|
48
|
+
[
|
|
49
|
+
*SCHEMA.filter_map { |keyword| [keyword, "#{pointer}/#{keyword}", schema[keyword]] if schema.key?(keyword) },
|
|
50
|
+
*lists.flat_map do |keyword|
|
|
51
|
+
schema[keyword].each_with_index.map { |item, index| [keyword, "#{pointer}/#{keyword}/#{index}", item] }
|
|
52
|
+
end,
|
|
53
|
+
*maps.flat_map do |keyword|
|
|
54
|
+
schema[keyword].map { |name, item| [keyword, "#{pointer}/#{keyword}/#{escape(name)}", item] }
|
|
55
|
+
end
|
|
56
|
+
]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Where a schema object sends its value: [pointer, whether it is the same value].
|
|
60
|
+
def links(at, node)
|
|
61
|
+
applied = children(node, at).reject { |keyword, _at, _item| keyword == "$defs" }
|
|
62
|
+
.map { |keyword, to, _item| [to, IN_PLACE.include?(keyword)] }
|
|
63
|
+
node.key?("$ref") ? [*applied, [node["$ref"][1..], true]] : applied
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def escape(name) = name.gsub("~", "~0").gsub("/", "~1")
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
private_constant :Subschemas
|
|
70
|
+
end
|
data/lib/mailschema/version.rb
CHANGED
data/lib/mailschema.rb
CHANGED
|
@@ -1,41 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "bigdecimal"
|
|
4
3
|
require "digest"
|
|
5
4
|
require "json"
|
|
6
5
|
require "json_schemer"
|
|
6
|
+
require "mail"
|
|
7
|
+
require "strscan"
|
|
7
8
|
require "time"
|
|
9
|
+
require "uri"
|
|
8
10
|
|
|
9
11
|
require_relative "mailschema/version"
|
|
10
|
-
require_relative "mailschema/limits"
|
|
11
12
|
require_relative "mailschema/jcs"
|
|
12
|
-
require_relative "mailschema/
|
|
13
|
+
require_relative "mailschema/json"
|
|
14
|
+
require_relative "mailschema/formats"
|
|
13
15
|
require_relative "mailschema/artifacts"
|
|
14
|
-
require_relative "mailschema/
|
|
15
|
-
require_relative "mailschema/
|
|
16
|
-
require_relative "mailschema/
|
|
17
|
-
require_relative "mailschema/
|
|
18
|
-
require_relative "mailschema/capability"
|
|
16
|
+
require_relative "mailschema/origin"
|
|
17
|
+
require_relative "mailschema/patterns"
|
|
18
|
+
require_relative "mailschema/subschemas"
|
|
19
|
+
require_relative "mailschema/description"
|
|
19
20
|
require_relative "mailschema/contract"
|
|
20
|
-
require_relative "mailschema/
|
|
21
|
-
require_relative "mailschema/
|
|
21
|
+
require_relative "mailschema/implementation"
|
|
22
|
+
require_relative "mailschema/header"
|
|
22
23
|
require_relative "mailschema/message"
|
|
24
|
+
require_relative "mailschema/authentication"
|
|
23
25
|
|
|
24
|
-
# Mail Action Protocol 0.
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
26
|
+
# Mail Action Protocol 0.3 processing: strict MAP JSON, RFC 8785 digests, descriptions, type
|
|
27
|
+
# contracts and implementation records, the MAP part of an email, and the qualifying DKIM
|
|
28
|
+
# signature rules. It does not verify DKIM, resolve DNS, fetch anything named in an email,
|
|
29
|
+
# grant authority or send email.
|
|
28
30
|
module Mailschema
|
|
29
|
-
# RFC 8785 canonical JSON of a value.
|
|
30
|
-
def self.canonicalize(value) = JCS.canonicalize(value)
|
|
31
|
-
|
|
32
|
-
# `sha-256:` and the SHA-256 of the RFC 8785 canonical form: the digest MAP uses
|
|
33
|
-
# for descriptions, contracts and pinned schemas.
|
|
34
|
-
def self.digest(value) = "sha-256:#{Digest::SHA256.hexdigest(JCS.canonicalize(value))}"
|
|
35
|
-
|
|
36
|
-
# Whether a deadline, a core date-time plus `after` seconds, has been reached.
|
|
37
|
-
# Anything but a core date-time counts as reached, so a mistake fails closed.
|
|
38
|
-
def self.reached?(now, deadline, after: 0)
|
|
39
|
-
!(DATE_TIME.valid?(deadline) && now < Time.iso8601(deadline) + after)
|
|
40
|
-
end
|
|
41
31
|
end
|
data/sig/mailschema.rbs
CHANGED
|
@@ -1,92 +1,100 @@
|
|
|
1
|
-
# Mail Action Protocol 0.
|
|
1
|
+
# Mail Action Protocol 0.3 processing.
|
|
2
2
|
module Mailschema
|
|
3
3
|
type json = Hash[String, untyped] | Array[untyped] | String | Integer | Float | bool | nil
|
|
4
4
|
type document = Hash[String, untyped]
|
|
5
|
-
type
|
|
5
|
+
type capability_kind = { effects: Array[String], max_lifetime_seconds: Integer }
|
|
6
6
|
|
|
7
7
|
VERSION: String
|
|
8
8
|
|
|
9
9
|
PROFILE: String
|
|
10
|
-
CORE_SCHEMA: String
|
|
11
|
-
FORMS_SCHEMA: String
|
|
12
|
-
CONTRACT_FORMAT: String
|
|
13
10
|
CONTEXT: String
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
PROBLEM_STATUS: Hash[String, Integer]
|
|
18
|
-
PROBLEM_TYPES: String
|
|
19
|
-
NON_TERMINAL_STATES: Array[String]
|
|
20
|
-
MAX_BYTES: Integer
|
|
11
|
+
DESCRIPTION_MEDIA_TYPE: String
|
|
12
|
+
DESCRIPTION_MAX_BYTES: Integer
|
|
13
|
+
CONTRACT_MAX_BYTES: Integer
|
|
21
14
|
MAX_DEPTH: Integer
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
end
|
|
15
|
+
EFFECTS: String
|
|
16
|
+
FORMATS: Array[String]
|
|
17
|
+
CAPABILITY_KINDS: Hash[String, capability_kind]
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
# A document that is not MAP JSON, or not the document it claims to be.
|
|
20
|
+
class InvalidDocument < StandardError
|
|
21
|
+
attr_reader errors: Array[String]
|
|
29
22
|
|
|
30
|
-
|
|
23
|
+
def initialize: (String message, ?Array[String] errors) -> void
|
|
31
24
|
end
|
|
32
25
|
|
|
33
|
-
def self.parse: (String
|
|
26
|
+
def self.parse: (String input, Integer max_bytes) -> json
|
|
34
27
|
def self.canonicalize: (json value) -> String
|
|
35
28
|
def self.digest: (json value) -> String
|
|
36
|
-
def self.reached?: (Time now, untyped deadline, ?after: Numeric) -> bool
|
|
37
|
-
|
|
38
|
-
def self.artifact: (String url) -> String
|
|
39
|
-
def self.document: (String url) -> document
|
|
40
|
-
|
|
41
|
-
def self.map_errors: (untyped document) -> Array[String]
|
|
42
|
-
def self.description_errors: (untyped description) -> Array[String]
|
|
43
|
-
def self.request_errors: (untyped request) -> Array[String]
|
|
44
|
-
def self.result_errors: (untyped result) -> Array[String]
|
|
45
|
-
def self.problem_errors: (untyped problem) -> Array[String]
|
|
46
|
-
def self.request_id?: (untyped value) -> bool
|
|
47
|
-
|
|
48
|
-
def self.capability: (document description) -> String
|
|
49
|
-
def self.written_path: (String? url) -> String
|
|
50
|
-
|
|
51
|
-
def self.result: (document request, state: String, target: document, result_url: String, recorded_at: Time,
|
|
52
|
-
?output: document, ?reason: String?, ?approval_url: String?, ?actor: String?) -> document
|
|
53
|
-
def self.transition: (document result, state: String, recorded_at: Time, ?reason: String?, ?output: document) -> document
|
|
54
|
-
def self.problem: (String code, title: String, detail: String, ?request_id: String?, ?interaction_id: String?,
|
|
55
|
-
?result_url: String?, ?target: document?, ?errors: Array[input_error]?) -> document
|
|
56
|
-
def self.result_status: (document result) -> Integer
|
|
57
|
-
def self.json_request?: (String? content_type) -> bool
|
|
58
|
-
DESCRIPTION_MEDIA_TYPE: String
|
|
59
|
-
def self.description_part?: (String? media_type, String? profile_parameter) -> bool
|
|
60
|
-
def self.result_url: (document description, String request_id) -> String
|
|
61
|
-
def self.retain_until: (document description, Time recorded_at) -> Time
|
|
62
|
-
def self.settle: (document result, document description, Time now) -> document?
|
|
63
29
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
# A MAP problem a request earns before the service's own state is consulted.
|
|
67
|
-
class Problem < Data
|
|
68
|
-
attr_reader code: String
|
|
69
|
-
attr_reader title: String
|
|
70
|
-
attr_reader detail: String
|
|
71
|
-
|
|
72
|
-
def self.new: (String code, String title, String detail) -> instance
|
|
73
|
-
| (code: String, title: String, detail: String) -> instance
|
|
74
|
-
end
|
|
30
|
+
def self.description_errors: (untyped value) -> Array[String]
|
|
31
|
+
def self.parse_description: (String input) -> document
|
|
75
32
|
|
|
33
|
+
def self.contract_errors: (untyped value) -> Array[String]
|
|
34
|
+
|
|
35
|
+
def self.implementation_errors: (untyped value) -> Array[String]
|
|
36
|
+
def self.parse_implementation: (String input) -> document
|
|
37
|
+
|
|
38
|
+
# A valid contract with its digest and compiled schemas.
|
|
39
|
+
class Contract
|
|
76
40
|
attr_reader document: document
|
|
77
41
|
attr_reader digest: String
|
|
78
|
-
attr_reader request_schema: document
|
|
79
42
|
|
|
80
|
-
def
|
|
81
|
-
|
|
43
|
+
def self.parse: (String input) -> Contract
|
|
44
|
+
|
|
45
|
+
def initialize: (untyped value) -> void
|
|
82
46
|
def id: () -> String
|
|
83
47
|
def version: () -> String
|
|
84
|
-
def
|
|
85
|
-
def
|
|
86
|
-
def
|
|
87
|
-
def description_errors: (
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
48
|
+
def operation: (String id) -> document?
|
|
49
|
+
def details_errors: (untyped details) -> Array[String]
|
|
50
|
+
def input_errors: (String operation_id, ?untyped input) -> Array[String]
|
|
51
|
+
def description_errors: (untyped description) -> Array[String]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# The MAP part of an email.
|
|
55
|
+
module Message
|
|
56
|
+
class Result < Data
|
|
57
|
+
attr_reader status: :none | :invalid | :valid
|
|
58
|
+
attr_reader reason: Symbol?
|
|
59
|
+
attr_reader description: document?
|
|
60
|
+
|
|
61
|
+
def self.new: (Symbol status, Symbol? reason, document? description) -> instance
|
|
62
|
+
| (status: Symbol, reason: Symbol?, description: document?) -> instance
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
NONE: Result
|
|
66
|
+
|
|
67
|
+
# `mail` is a Mail::Message read from the received bytes.
|
|
68
|
+
def self.extract: (untyped mail) -> Result
|
|
69
|
+
|
|
70
|
+
# Returns a Mail::Part.
|
|
71
|
+
def self.part: (document description) -> untyped
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Core "Qualifying message authentication" over a DKIM verifier's facts.
|
|
75
|
+
module Authentication
|
|
76
|
+
class Signature < Data
|
|
77
|
+
attr_reader pass: bool
|
|
78
|
+
attr_reader domain: String
|
|
79
|
+
attr_reader body_length_limited: bool
|
|
80
|
+
attr_reader algorithm: String
|
|
81
|
+
attr_reader key_type: String
|
|
82
|
+
attr_reader key_bits: Integer
|
|
83
|
+
attr_reader signed_headers: Array[String]
|
|
84
|
+
|
|
85
|
+
def self.new: (bool pass, String domain, bool body_length_limited, String algorithm, String key_type,
|
|
86
|
+
Integer key_bits, Array[String] signed_headers) -> instance
|
|
87
|
+
| (pass: bool, domain: String, body_length_limited: bool, algorithm: String, key_type: String,
|
|
88
|
+
key_bits: Integer, signed_headers: Array[String]) -> instance
|
|
89
|
+
|
|
90
|
+
def pass?: () -> bool
|
|
91
|
+
def body_length_limited?: () -> bool
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
REQUIRED: Array[String]
|
|
95
|
+
CONDITIONAL: Array[String]
|
|
96
|
+
|
|
97
|
+
def self.qualifying: (raw_message: String, signatures: Array[Signature],
|
|
98
|
+
?organizational_domain: ^(String) -> String?) -> Signature?
|
|
91
99
|
end
|
|
92
100
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mailschema
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- MailSchema contributors
|
|
@@ -10,61 +10,36 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '3.1'
|
|
19
|
-
- - "<"
|
|
20
|
-
- !ruby/object:Gem::Version
|
|
21
|
-
version: '5'
|
|
22
|
-
type: :runtime
|
|
23
|
-
prerelease: false
|
|
24
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
-
requirements:
|
|
26
|
-
- - ">="
|
|
27
|
-
- !ruby/object:Gem::Version
|
|
28
|
-
version: '3.1'
|
|
29
|
-
- - "<"
|
|
30
|
-
- !ruby/object:Gem::Version
|
|
31
|
-
version: '5'
|
|
32
|
-
- !ruby/object:Gem::Dependency
|
|
33
|
-
name: json
|
|
13
|
+
name: json_schemer
|
|
34
14
|
requirement: !ruby/object:Gem::Requirement
|
|
35
15
|
requirements:
|
|
36
|
-
- - "
|
|
37
|
-
- !ruby/object:Gem::Version
|
|
38
|
-
version: '2.21'
|
|
39
|
-
- - "<"
|
|
16
|
+
- - "~>"
|
|
40
17
|
- !ruby/object:Gem::Version
|
|
41
|
-
version: '
|
|
18
|
+
version: '2.5'
|
|
42
19
|
type: :runtime
|
|
43
20
|
prerelease: false
|
|
44
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
45
22
|
requirements:
|
|
46
|
-
- - "
|
|
47
|
-
- !ruby/object:Gem::Version
|
|
48
|
-
version: '2.21'
|
|
49
|
-
- - "<"
|
|
23
|
+
- - "~>"
|
|
50
24
|
- !ruby/object:Gem::Version
|
|
51
|
-
version: '
|
|
25
|
+
version: '2.5'
|
|
52
26
|
- !ruby/object:Gem::Dependency
|
|
53
|
-
name:
|
|
27
|
+
name: mail
|
|
54
28
|
requirement: !ruby/object:Gem::Requirement
|
|
55
29
|
requirements:
|
|
56
30
|
- - "~>"
|
|
57
31
|
- !ruby/object:Gem::Version
|
|
58
|
-
version: '2.
|
|
32
|
+
version: '2.8'
|
|
59
33
|
type: :runtime
|
|
60
34
|
prerelease: false
|
|
61
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
62
36
|
requirements:
|
|
63
37
|
- - "~>"
|
|
64
38
|
- !ruby/object:Gem::Version
|
|
65
|
-
version: '2.
|
|
66
|
-
description: Parse, canonicalize, digest and validate Mail Action Protocol 0.
|
|
67
|
-
|
|
39
|
+
version: '2.8'
|
|
40
|
+
description: Parse, canonicalize, digest and validate Mail Action Protocol 0.3 descriptions,
|
|
41
|
+
type contracts and implementation records, extract and build the MAP part of an
|
|
42
|
+
email, and decide whether a verified DKIM signature qualifies a message.
|
|
68
43
|
executables: []
|
|
69
44
|
extensions: []
|
|
70
45
|
extra_rdoc_files: []
|
|
@@ -72,33 +47,33 @@ files:
|
|
|
72
47
|
- CHANGELOG.md
|
|
73
48
|
- LICENSE
|
|
74
49
|
- README.md
|
|
75
|
-
-
|
|
50
|
+
- artifacts/context.jsonld
|
|
51
|
+
- artifacts/contract.schema.json
|
|
52
|
+
- artifacts/core.schema.json
|
|
53
|
+
- artifacts/implementation.schema.json
|
|
54
|
+
- artifacts/profile.json
|
|
76
55
|
- lib/mailschema.rb
|
|
77
56
|
- lib/mailschema/artifacts.rb
|
|
78
|
-
- lib/mailschema/
|
|
57
|
+
- lib/mailschema/authentication.rb
|
|
79
58
|
- lib/mailschema/contract.rb
|
|
80
|
-
- lib/mailschema/
|
|
81
|
-
- lib/mailschema/
|
|
82
|
-
- lib/mailschema/
|
|
83
|
-
- lib/mailschema/
|
|
59
|
+
- lib/mailschema/description.rb
|
|
60
|
+
- lib/mailschema/formats.rb
|
|
61
|
+
- lib/mailschema/header.rb
|
|
62
|
+
- lib/mailschema/implementation.rb
|
|
84
63
|
- lib/mailschema/jcs.rb
|
|
85
|
-
- lib/mailschema/
|
|
64
|
+
- lib/mailschema/json.rb
|
|
86
65
|
- lib/mailschema/message.rb
|
|
87
|
-
- lib/mailschema/
|
|
88
|
-
- lib/mailschema/
|
|
89
|
-
- lib/mailschema/
|
|
66
|
+
- lib/mailschema/origin.rb
|
|
67
|
+
- lib/mailschema/patterns.rb
|
|
68
|
+
- lib/mailschema/subschemas.rb
|
|
90
69
|
- lib/mailschema/version.rb
|
|
91
|
-
- schemas/contribution.schema.json
|
|
92
|
-
- schemas/forms-0.1.schema.json
|
|
93
|
-
- schemas/map-0.2.schema.json
|
|
94
|
-
- schemas/type-contract-0.2.schema.json
|
|
95
70
|
- sig/mailschema.rbs
|
|
96
|
-
homepage: https://mailschema.org
|
|
71
|
+
homepage: https://mailschema.org
|
|
97
72
|
licenses:
|
|
98
73
|
- MIT
|
|
99
74
|
metadata:
|
|
100
75
|
allowed_push_host: https://rubygems.org
|
|
101
|
-
homepage_uri: https://mailschema.org
|
|
76
|
+
homepage_uri: https://mailschema.org
|
|
102
77
|
source_code_uri: https://github.com/mailschema/ruby
|
|
103
78
|
changelog_uri: https://github.com/mailschema/ruby/blob/main/CHANGELOG.md
|
|
104
79
|
bug_tracker_uri: https://github.com/mailschema/ruby/issues
|
|
@@ -119,5 +94,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
119
94
|
requirements: []
|
|
120
95
|
rubygems_version: 4.0.20
|
|
121
96
|
specification_version: 4
|
|
122
|
-
summary: Mail Action Protocol 0.
|
|
97
|
+
summary: Mail Action Protocol 0.3 processing
|
|
123
98
|
test_files: []
|