devel/schemata: describe version 4
[notmuch] / devel / schemata
1 This file describes the schemata used for notmuch's structured output
2 format (currently JSON and S-Expressions).
3
4 []'s indicate lists.  List items can be marked with a '?', meaning
5 they are optional; or a '*', meaning there can be zero or more of that
6 item.  {}'s indicate an object that maps from field identifiers to
7 values.  An object field marked '?' is optional.  |'s indicate
8 alternates (e.g., int|string means something can be an int or a
9 string).
10
11 For S-Expression output, lists are printed delimited by () instead of
12 []. Objects are printed as p-lists, i.e. lists where the keys and values
13 are interleaved. Keys are printed as keywords (symbols preceded by a
14 colon), e.g. (:id "123" :time 54321 :from "foobar"). Null is printed as
15 nil, true as t and false as nil.
16
17 This is version 4 of the structured output format.
18
19 Version history
20 ---------------
21
22 v1
23 - First versioned schema release.
24 - Added part.content-length and part.content-transfer-encoding fields.
25
26 v2
27 - Added the thread_summary.query field.
28
29 v3
30 - Replaced message.filename string with a list of filenames.
31 - Added part.content-disposition field.
32
33 v4
34 - replace signature error integer bitmask with a set of flags for
35   individual errors.
36
37 Common non-terminals
38 --------------------
39
40 # Number of seconds since the Epoch
41 unix_time = int
42
43 # Thread ID, sans "thread:"
44 threadid = string
45
46 # Message ID, sans "id:"
47 messageid = string
48
49 notmuch show schema
50 -------------------
51
52 # A top-level set of threads (do_show)
53 # Returned by notmuch show without a --part argument
54 thread_set = [thread*]
55
56 # Top-level messages in a thread (show_messages)
57 thread = [thread_node*]
58
59 # A message and its replies (show_messages)
60 thread_node = [
61     message|null,             # null if not matched and not --entire-thread
62     [thread_node*]            # children of message
63 ]
64
65 # A message (format_part_sprinter)
66 message = {
67     # (format_message_sprinter)
68     id:             messageid,
69     match:          bool,
70     filename:       [string*],
71     timestamp:      unix_time, # date header as unix time
72     date_relative:  string,   # user-friendly timestamp
73     tags:           [string*],
74
75     headers:        headers,
76     body?:          [part]    # omitted if --body=false
77 }
78
79 # A MIME part (format_part_sprinter)
80 part = {
81     id:             int|string, # part id (currently DFS part number)
82
83     encstatus?:     encstatus,
84     sigstatus?:     sigstatus,
85
86     content-type:   string,
87     content-disposition?:       string,
88     content-id?:    string,
89     # if content-type starts with "multipart/":
90     content:        [part*],
91     # if content-type is "message/rfc822":
92     content:        [{headers: headers, body: [part]}],
93     # otherwise (leaf parts):
94     filename?:      string,
95     content-charset?: string,
96     # A leaf part's body content is optional, but may be included if
97     # it can be correctly encoded as a string.  Consumers should use
98     # this in preference to fetching the part content separately.
99     content?:       string,
100     # If a leaf part's body content is not included, the length of
101     # the encoded content (in bytes) may be given instead.
102     content-length?: int,
103     # If a leaf part's body content is not included, its transfer encoding
104     # may be given.  Using this and the encoded content length, it is
105     # possible for the consumer to estimate the decoded content length.
106     content-transfer-encoding?: string
107 }
108
109 # The headers of a message or part (format_headers_sprinter with reply = FALSE)
110 headers = {
111     Subject:        string,
112     From:           string,
113     To?:            string,
114     Cc?:            string,
115     Bcc?:           string,
116     Reply-To?:      string,
117     Date:           string
118 }
119
120 # Encryption status (format_part_sprinter)
121 encstatus = [{status: "good"|"bad"}]
122
123 # Signature status (format_part_sigstatus_sprinter)
124 sigstatus = [signature*]
125
126 signature = {
127     # (signature_status_to_string)
128     status:         "good"|"bad"|"error"|"unknown",
129     # if status is "good":
130     fingerprint?:   string,
131     created?:       unix_time,
132     expires?:       unix_time,
133     userid?:        string
134     # if status is not "good":
135     keyid?:         string
136     errors?:        sig_errors
137 }
138
139 sig_errors = {
140     key-revoked?: bool,
141     key-expired?: bool,
142     sig-expired?: bool,
143     key-missing?: bool,
144     alg-unsupported?: bool,
145     crl-missing?: bool,
146     crl-too-old?: bool,
147     bad-policy?: bool,
148     sys-error?: bool,
149     tofu-conflict?: bool
150 }
151
152 notmuch search schema
153 ---------------------
154
155 # --output=summary
156 search_summary = [thread_summary*]
157
158 # --output=threads
159 search_threads = [threadid*]
160
161 # --output=messages
162 search_messages = [messageid*]
163
164 # --output=files
165 search_files = [string*]
166
167 # --output=tags
168 search_tags = [string*]
169
170 thread_summary = {
171     thread:         threadid,
172     timestamp:      unix_time,
173     date_relative:  string,   # user-friendly timestamp
174     matched:        int,      # number of matched messages
175     total:          int,      # total messages in thread
176     authors:        string,   # comma-separated names with | between
177                               # matched and unmatched
178     subject:        string,
179     tags:           [string*],
180
181     # Two stable query strings identifying exactly the matched and
182     # unmatched messages currently in this thread.  The messages
183     # matched by these queries will not change even if more messages
184     # arrive in the thread.  If there are no matched or unmatched
185     # messages, the corresponding query will be null (there is no
186     # query that matches nothing).  (Added in schema version 2.)
187     query:          [string|null, string|null],
188 }
189
190 notmuch reply schema
191 --------------------
192
193 reply = {
194     # The headers of the constructed reply
195     reply-headers: reply_headers,
196
197     # As in the show format (format_part_sprinter)
198     original: message
199 }
200
201 # Reply headers (format_headers_sprinter with reply = TRUE)
202 reply_headers = {
203     Subject:        string,
204     From:           string,
205     To?:            string,
206     Cc?:            string,
207     Bcc?:           string,
208     In-reply-to:    string,
209     References:     string
210 }