Repository navigation
Expand file tree
/
Copy pathschema.rs
More file actions
199 lines (177 loc) · 7.74 KB
/
Copy pathschema.rs
File metadata and controls
199 lines (177 loc) · 7.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
//! `sendra schema`: materialize the JSON Schema files for editor tooling.
//!
//! `schema/*.schema.json` at the workspace root (request/collection/config/
//! environment — see `docs/reference/json-schema.md`) are generated from
//! `sendra-core`'s types by `xtask` and committed to the source repository.
//! That is enough for someone who has cloned the repo, but the actual
//! audience for editor tooling is anyone who has *installed* `sendra` —
//! most of whom never touch a checkout. `include_str!` bakes the four files
//! into this binary at compile time, so they are always available from the
//! one thing an installed-binary user actually has: the binary itself. No
//! network access, no docs site, no repository required.
//!
//! The `include_str!`s below read `./schema/*.schema.json` — a vendored
//! copy inside this crate's own directory, not the workspace-root
//! `schema/`. A `cargo publish` tarball can only ever contain files inside
//! the crate being published, so `sendra-cli` cannot reach `../../schema`
//! once packaged (this is exactly the bug this vendored copy fixes: the
//! packaged tarball failed cargo's own verify step). `xtask` writes this
//! vendored copy alongside the canonical one and its `check` command
//! guards both against drift from the Rust types **and** against drift
//! between the two copies — see `xtask/src/main.rs`. The regression test at
//! the bottom of this file guards the remaining way this could stop being
//! true (someone later replacing an `include_str!` with a literal or a
//! different path), by comparing the compiled-in bytes against the local
//! vendored file on disk independently, at test time.
use std::path::{Path, PathBuf};
use crate::exit::Exit;
use crate::output::print_error_line;
const SCHEMA_DIR_NAME: &str = "schema";
struct SchemaFile {
name: &'static str,
content: &'static str,
}
const SCHEMA_FILES: &[SchemaFile] = &[
SchemaFile {
name: "request.schema.json",
content: include_str!("../schema/request.schema.json"),
},
SchemaFile {
name: "collection.schema.json",
content: include_str!("../schema/collection.schema.json"),
},
SchemaFile {
name: "config.schema.json",
content: include_str!("../schema/config.schema.json"),
},
SchemaFile {
name: "environment.schema.json",
content: include_str!("../schema/environment.schema.json"),
},
];
/// Run `sendra schema`, writing into `output` (the current directory if not
/// given).
pub(crate) fn schema(output: Option<PathBuf>) -> Exit {
let root = match output {
Some(dir) => dir,
None => match std::env::current_dir() {
Ok(dir) => dir,
Err(err) => {
print_error_line(format!("could not read the current directory: {err}"));
return Exit::Failure;
}
},
};
match schema_in(&root) {
Ok(written) => {
println!("Wrote:");
for path in written {
println!(" {}", path.display());
}
Exit::Ok
}
Err(err) => {
print_error_line(format!("could not write schema files: {err}"));
Exit::Failure
}
}
}
/// The write itself, against an arbitrary root rather than the real current
/// directory, so it is testable against a temporary tree.
///
/// Unlike `sendra init`, this **overwrites** rather than refusing when the
/// target already exists: these are generated, derived files with nothing of
/// the author's in them, so there is nothing to protect by refusing — only a
/// reason to make `sendra schema` after a `sendra` upgrade a normal,
/// unremarkable way to pick up a newer schema. `schema/` mirrors the
/// repository's own layout so the same `yaml.schemas` paths in
/// `docs/reference/json-schema.md` work whether the four files came from a
/// checkout or from this command.
fn schema_in(root: &Path) -> Result<Vec<PathBuf>, std::io::Error> {
let schema_dir = root.join(SCHEMA_DIR_NAME);
std::fs::create_dir_all(&schema_dir)?;
let mut written = Vec::with_capacity(SCHEMA_FILES.len());
for file in SCHEMA_FILES {
std::fs::write(schema_dir.join(file.name), file.content)?;
written.push(PathBuf::from(SCHEMA_DIR_NAME).join(file.name));
}
Ok(written)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn writes_all_four_schema_files_into_an_empty_directory() {
let temp = tempfile::tempdir().unwrap();
let written = schema_in(temp.path()).expect("an empty directory has nothing in the way");
assert_eq!(
written,
vec![
PathBuf::from("schema/request.schema.json"),
PathBuf::from("schema/collection.schema.json"),
PathBuf::from("schema/config.schema.json"),
PathBuf::from("schema/environment.schema.json"),
]
);
for path in &written {
assert!(temp.path().join(path).is_file());
}
}
#[test]
fn every_embedded_file_is_valid_json() {
for file in SCHEMA_FILES {
serde_json::from_str::<serde_json::Value>(file.content)
.unwrap_or_else(|err| panic!("{} is not valid JSON: {err}", file.name));
}
}
/// Guards the thing that would otherwise let the embedded copy drift
/// silently from this crate's own vendored `schema/*.schema.json`: this
/// reads each file from disk independently, at test time, rather than
/// through the same `include_str!` the embedded constant already went
/// through — so a future change that hardcodes a literal, or points
/// `include_str!` at the wrong path, shows up here instead of shipping
/// unnoticed. Deliberately checked against this crate's own directory,
/// not the workspace-root `schema/`: that keeps this test meaningful
/// even when run against a standalone `cargo publish` tarball, which
/// has no workspace root to reach for. Drift between this vendored copy
/// and the workspace-root canonical one is `xtask check`'s job, not
/// this test's — see `xtask/src/main.rs`.
#[test]
fn every_embedded_schema_matches_the_committed_file_on_disk() {
let crate_root = Path::new(env!("CARGO_MANIFEST_DIR"));
for file in SCHEMA_FILES {
let path = crate_root.join("schema").join(file.name);
let on_disk = std::fs::read_to_string(&path)
.unwrap_or_else(|err| panic!("reading {}: {err}", path.display()));
assert_eq!(
file.content,
on_disk,
"the binary's embedded copy of {} has drifted from {}",
file.name,
path.display()
);
}
}
#[test]
fn running_it_twice_overwrites_rather_than_refusing() {
// The deliberate difference from `sendra init`: nothing here is
// author-owned, so a second run (after a `sendra` upgrade, say)
// should just pick up whatever is currently embedded.
let temp = tempfile::tempdir().unwrap();
let target = temp.path().join("schema/request.schema.json");
schema_in(temp.path()).unwrap();
std::fs::write(&target, "not a schema").unwrap();
schema_in(temp.path()).expect("a second run must not refuse");
assert_ne!(std::fs::read_to_string(&target).unwrap(), "not a schema");
}
#[test]
fn writes_under_a_given_output_directory_not_only_the_current_one() {
let temp = tempfile::tempdir().unwrap();
let target_dir = temp.path().join("some/nested/project");
std::fs::create_dir_all(&target_dir).unwrap();
let written = schema_in(&target_dir).unwrap();
for path in written {
assert!(target_dir.join(path).is_file());
}
}
}