git-ents.gitmain
⌘K
foforge
lib.rs124 lines · 5.1 KB · rusthistorycomment on this file
1//! `ents-web`: the web UI (`docs/development-plan.adoc`, phase 7) --
2//! a second leaf, sibling to `git-ents`, in the layering
3//! `docs/abstractions.adoc` states (`substrate -> kernel -> {forge, kiln}
4//! -> {git-ents, ents-web}`).
5//!
6//! This crate's one responsibility is rendering the kernel's and every
7//! installed package's own state as HTML, and accepting signed,
8//! CSRF-checked mutations back -- never a second copy of forge or kiln
9//! business logic. Every page is a thin caller into `ents-model`,
10//! `ents-anchor`, `ents-query`, `ents-receive`, `ents-forge`, or
11//! `ents-kiln`, exactly as `git-ents`'s own `commands` modules are thin
12//! callers into the same crates ([`crate::pages`]'s own module doc draws
13//! the line between the generic, reflection-driven pages and the
14//! legitimate custom ones).
15//!
16//! # Deployment-agnostic by construction (`roots.web-agnostic`)
17//!
18//! Nothing in this crate binds a socket except [`serve_on`], and nothing
19//! upstream of it assumes one exists: `router()` alone builds a complete,
20//! in-process `tower::Service` a caller can drive via
21//! `tower::ServiceExt::oneshot` with no network transport at all -- the
22//! same shape an in-process webview embedding would drive a request
23//! through. See [`identity::SigningIdentity`]'s own doc for the other half
24//! of this requirement: the signing identity a mutation is signed with is
25//! always injected by the composition root, never resolved by this crate
26//! itself.
27//!
28//! # What this crate does not expose (`roots.local`)
29//!
30//! There is no `/info/refs`, no `git-upload-pack`/`git-receive-pack`
31//! route, and no code path that shells to `git` as a smart-HTTP backend
32//! anywhere in `router()`'s route table. `git ents serve`'s own doc
33//! (`git-ents`'s `commands::serve`) states why: the local root's existing
34//! wiring already serves git's own transport for the test-harness case
35//! (`roots.worktree-update`); this crate adds only the web UI on top of
36//! it, on loopback, never a second git-serving surface.
37//!
38//! # Spec coverage
39//!
40//! From `docs/spec/roots.adoc`:
41//!
42//! - `roots.local` -- this crate's route table carries no git
43//! smart-HTTP surface; `git-ents`'s own `serve` command reuses
44//! `LocalRoot`'s existing seams and binds loopback only (see that
45//! crate's `commands::serve` module).
46//! - `roots.web-signing`, `roots.web-agnostic` -- [`identity::SigningIdentity`].
47//! - `roots.web-session` -- [`session::SessionStore`], and
48//! `pages::require_csrf` on every state-changing route.
49//!
50//! `roots.path-validation` and `roots.fetch-auth` are out of scope for
51//! this crate: both describe `git-ents-server`'s multi-repository hosted
52//! root (phase 8) -- "reject a path that would escape the data
53//! directory, nest inside an existing repository, or collide with a
54//! non-repository namespace directory" and "private-repository access...
55//! out of scope for v1" both presuppose a data directory holding more
56//! than one repository, which does not exist until that phase. This
57//! crate's composition root always already has exactly one, already-open
58//! repository.
59//!
60//! # Examples
61//!
62//! Driving a full request through this crate with no socket bound at all
63//! (`roots.web-agnostic`'s in-process case) -- see `tests/router.rs` for
64//! the full-fixture version of this same shape, wired against a real
65//! signed member.
66//!
67//! ```
68//! use std::sync::Arc;
69//!
70//! use ents_web::identity::SigningIdentity;
71//! use ents_web::state::AppState;
72//! use ents_receive::{Mode, NullEventSink};
73//! use ents_testutil::ObjectStore;
74//! use gix_ref_store::LooseRefStore;
75//! use http_body_util::BodyExt as _;
76//! use tower::ServiceExt as _;
77//!
78//! struct Fixture;
79//! impl SigningIdentity for Fixture {
80//! fn actor(&self) -> gix::actor::Signature {
81//! gix::actor::Signature {
82//! name: "fixture".into(), email: "fixture@ents.test".into(),
83//! time: gix::date::Time { seconds: 0, offset: 0 },
84//! }
85//! }
86//! fn sign(&self, _payload: &[u8]) -> String { String::new() }
87//! fn public_openssh(&self) -> String { "ssh-ed25519 AAAA... fixture".to_owned() }
88//! }
89//!
90//! # let runtime = tokio::runtime::Runtime::new().expect("runtime");
91//! # runtime.block_on(async {
92//! let dir = tempfile::tempdir().expect("tempdir");
93//! gix::init(dir.path()).expect("init");
94//! let refs = LooseRefStore::open(dir.path()).expect("opens");
95//! let objects = ObjectStore::default();
96//! let state = Arc::new(AppState::new(
97//! Box::new(refs), objects, Box::new(NullEventSink), Mode::Advisory,
98//! Box::new(Fixture), dir.path().to_owned(),
99//! ));
100//! let router = ents_web::router(state);
101//! let response = router
102//! .oneshot(axum::http::Request::get("/").body(axum::body::Body::empty()).expect("request"))
103//! .await
104//! .expect("in-process call");
105//! assert_eq!(response.status(), axum::http::StatusCode::OK);
106//! # });
107//! ```
108
109pub(crate) mod asciidoc;
110pub(crate) mod assets;
111pub mod auth;
112pub(crate) mod editor;
113pub mod error;
114pub mod form;
115pub mod identity;
116pub(crate) mod markdown;
117pub mod pages;
118pub mod render;
119pub mod router;
120pub mod session;
121pub mod state;
122
123pub use error::{Error, Result};
124pub use router::{bind, router, serve_on};