Skip to main content

onetaskgraph_core/
schema.rs

1//! The JSON Schema bundle both SDKs are generated from.
2//!
3//! It is emitted by `onetaskgraph schema` rather than committed, so the schema a
4//! consumer generates against can never drift from the types this binary actually
5//! serialises: they are the same types.
6
7use std::collections::BTreeMap;
8
9use onetaskgraph_plugin_api::{
10    Capabilities, DependencyEdge, DependencyEndpoint, DependencyKind, Direction, Document,
11    DocumentQuery, Health, ItemKind, Label, Location, Page, PageRequest, Project, ProjectQuery,
12    Repository, SourceError, Status, StatusCategory, Task, TaskQuery, TextFields,
13};
14use schemars::{Schema, schema_for};
15use serde_json::{Value, json};
16
17use crate::config::{EffectiveConfig, Origin, OutputFormat, Setting};
18use crate::registry::registry;
19use crate::secrets::{CredentialLayer, ResolvedCredential, SecretsReport};
20use crate::{
21    CopyAction, CopyOutcome, CopyReport, GlobalId, PageToken, Predicate, Qualified, QualifiedEdge,
22    QualifiedEndpoint, QueryPlan, QueryResponse, SearchHit, SearchKind, SourceFailure,
23    SourceListing, SourcePlan,
24};
25
26/// The bundle's own version, bumped whenever a root is added, removed or renamed.
27///
28/// Consumers generate code from this document, so the version is part of the
29/// contract rather than a convenience: an SDK can refuse a bundle it was not
30/// generated against instead of silently emitting the wrong models.
31///
32/// Which roots each version brought is what `git log` answers; what this number owes a
33/// reader is that it moves whenever [`schema_bundle`] below gains, loses or renames one.
34pub const SCHEMA_BUNDLE_VERSION: u32 = 9;
35
36/// Every contract root, keyed by name, plus each registered plugin's config schema.
37#[must_use]
38pub fn schema_bundle() -> Value {
39    let mut roots: BTreeMap<&'static str, Schema> = BTreeMap::new();
40
41    roots.insert("Task", schema_for!(Task));
42    roots.insert("Project", schema_for!(Project));
43    roots.insert("Document", schema_for!(Document));
44    // A root of its own although both `Task` and `Project` reach it inside their own
45    // definitions, for the reason `TextFields` is one: a consumer acts on a location by
46    // asking which of the two keys is present, so the shape it switches on has to be
47    // nameable rather than only reachable.
48    roots.insert("Location", schema_for!(Location));
49    roots.insert("Label", schema_for!(Label));
50    roots.insert("Status", schema_for!(Status));
51    roots.insert("StatusCategory", schema_for!(StatusCategory));
52    roots.insert("DependencyEdge", schema_for!(DependencyEdge));
53    roots.insert("DependencyEndpoint", schema_for!(DependencyEndpoint));
54    roots.insert("QualifiedEndpoint", schema_for!(QualifiedEndpoint));
55    roots.insert("ItemKind", schema_for!(ItemKind));
56    roots.insert("Repository", schema_for!(Repository));
57    roots.insert("DependencyKind", schema_for!(DependencyKind));
58    roots.insert("Direction", schema_for!(Direction));
59    // Roots of their own although both are reachable inside `TaskQuery`'s definitions,
60    // which is enough for a generator and not enough for a reconciliation: the command
61    // line spells both deliberately differently (`both` for `title-or-content`, `task`
62    // for `tasks`), so a variant added to either would leave the command line quietly
63    // unable to name it. A root apiece gives that gate one document to read.
64    roots.insert("TextFields", schema_for!(TextFields));
65    roots.insert("SearchKind", schema_for!(SearchKind));
66
67    roots.insert("TaskQuery", schema_for!(TaskQuery));
68    roots.insert("ProjectQuery", schema_for!(ProjectQuery));
69    roots.insert("DocumentQuery", schema_for!(DocumentQuery));
70    roots.insert("PageRequest", schema_for!(PageRequest));
71    roots.insert("PageOfTask", schema_for!(Page<Task>));
72    roots.insert("PageOfProject", schema_for!(Page<Project>));
73    roots.insert("PageOfDocument", schema_for!(Page<Document>));
74    roots.insert("PageOfLabel", schema_for!(Page<Label>));
75    roots.insert("PageOfDependencyEdge", schema_for!(Page<DependencyEdge>));
76
77    roots.insert("Capabilities", schema_for!(Capabilities));
78    roots.insert("Health", schema_for!(Health));
79    roots.insert("SourceError", schema_for!(SourceError));
80
81    roots.insert("GlobalId", schema_for!(GlobalId));
82    roots.insert("PageToken", schema_for!(PageToken));
83    roots.insert("QueryPlan", schema_for!(QueryPlan));
84    roots.insert("SourcePlan", schema_for!(SourcePlan));
85    roots.insert("Predicate", schema_for!(Predicate));
86    roots.insert("SourceFailure", schema_for!(SourceFailure));
87    roots.insert("QualifiedTask", schema_for!(Qualified<Task>));
88    roots.insert("QualifiedProject", schema_for!(Qualified<Project>));
89    roots.insert("QualifiedDocument", schema_for!(Qualified<Document>));
90    roots.insert("QualifiedLabel", schema_for!(Qualified<Label>));
91    roots.insert("QualifiedEdge", schema_for!(QualifiedEdge));
92    roots.insert("SearchHit", schema_for!(SearchHit));
93    roots.insert("SourceListing", schema_for!(SourceListing));
94    roots.insert("SourceListings", schema_for!(Vec<SourceListing>));
95    roots.insert(
96        "QueryResponseOfQualifiedTask",
97        schema_for!(QueryResponse<Qualified<Task>>),
98    );
99    roots.insert(
100        "QueryResponseOfQualifiedProject",
101        schema_for!(QueryResponse<Qualified<Project>>),
102    );
103    roots.insert(
104        "QueryResponseOfQualifiedDocument",
105        schema_for!(QueryResponse<Qualified<Document>>),
106    );
107    roots.insert(
108        "QueryResponseOfQualifiedLabel",
109        schema_for!(QueryResponse<Qualified<Label>>),
110    );
111    roots.insert(
112        "QueryResponseOfQualifiedEdge",
113        schema_for!(QueryResponse<QualifiedEdge>),
114    );
115    roots.insert(
116        "QueryResponseOfSearchHit",
117        schema_for!(QueryResponse<SearchHit>),
118    );
119
120    roots.insert("CopyReport", schema_for!(CopyReport));
121    roots.insert("CopyOutcome", schema_for!(CopyOutcome));
122    roots.insert("CopyAction", schema_for!(CopyAction));
123
124    roots.insert("EffectiveConfig", schema_for!(EffectiveConfig));
125    roots.insert("Setting", schema_for!(Setting));
126    roots.insert("Origin", schema_for!(Origin));
127    roots.insert("OutputFormat", schema_for!(OutputFormat));
128    roots.insert("SecretsReport", schema_for!(SecretsReport));
129    roots.insert("ResolvedCredential", schema_for!(ResolvedCredential));
130    roots.insert("CredentialLayer", schema_for!(CredentialLayer));
131
132    let plugins: BTreeMap<String, Schema> = registry()
133        .iter()
134        .map(|plugin| (plugin.kind().to_owned(), plugin.config_schema()))
135        .collect();
136
137    json!({
138        "version": SCHEMA_BUNDLE_VERSION,
139        "roots": roots,
140        "plugin_config": plugins,
141    })
142}