Skip to content

Commit

Permalink
Make the sync server client an optional feature
Browse files Browse the repository at this point in the history
  • Loading branch information
djmitche committed Dec 23, 2023
1 parent 1c11f8b commit c46c22a
Show file tree
Hide file tree
Showing 11 changed files with 86 additions and 19 deletions.
38 changes: 36 additions & 2 deletions .github/workflows/rust-tests.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
## Run the TaskChampion tests, using both the minimum supported rust version
## and the latest stable Rust.

name: tests - rust

Expand All @@ -11,6 +9,42 @@ on:
types: [opened, reopened, synchronize]

jobs:
## Run the `taskchampion` crate's tests with various combinations of features.
features:
strategy:
matrix:
features:
- ""
- "server-sync"

name: "taskchampion ${{ matrix.features == '' && 'with no features' || format('with features {0}', matrix.features) }}"
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Cache cargo registry
uses: actions/cache@v3
with:
path: ~/.cargo/registry
key: ubuntu-latest-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }}

- name: Cache cargo build
uses: actions/cache@v3
with:
path: target
key: ubuntu-latest-stable-cargo-build-target-${{ hashFiles('**/Cargo.lock') }}

- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true

- name: test
run: cargo test -p taskchampion --no-default-features --features "${{ matrix.features }}"

## Run all TaskChampion crate tests, using both the minimum supported rust version
## and the latest stable Rust.
test:
strategy:
matrix:
Expand Down
2 changes: 1 addition & 1 deletion taskchampion/integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ publish = false
build = "build.rs"

[dependencies]
taskchampion = { path = "../taskchampion" }
taskchampion = { path = "../taskchampion", features = ["server-sync"] }
taskchampion-lib = { path = "../lib" }
taskchampion-sync-server = { path = "../sync-server" }

Expand Down
11 changes: 11 additions & 0 deletions taskchampion/taskchampion/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ readme = "../README.md"
license = "MIT"
edition = "2018"

[features]
default = ["server-sync" ]
server-sync = ["crypto", "dep:ureq"]
crypto = ["dep:ring"]

[package.metadata.docs.rs]
all-features = true

[dependencies]
uuid.workspace = true
serde.workspace = true
Expand All @@ -26,6 +34,9 @@ flate2.workspace = true
byteorder.workspace = true
ring.workspace = true

ureq.optional = true
ring.optional = true

[dev-dependencies]
proptest.workspace = true
tempfile.workspace = true
Expand Down
1 change: 1 addition & 0 deletions taskchampion/taskchampion/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ macro_rules! other_error {
}
};
}
#[cfg(feature = "server-sync")]
other_error!(ureq::Error);
other_error!(io::Error);
other_error!(serde_json::Error);
Expand Down
8 changes: 8 additions & 0 deletions taskchampion/taskchampion/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ Create a server with [`ServerConfig`](crate::ServerConfig).
The [`server`](crate::server) module defines the interface a server must meet.
Users can define their own server impelementations.
# Feature Flags
Support for some optional functionality is controlled by feature flags.
Sync server client support:
* `server-sync` - sync to the taskchampion-sync-server
# See Also
See the [TaskChampion Book](http://taskchampion.github.com/taskchampion)
Expand Down
9 changes: 7 additions & 2 deletions taskchampion/taskchampion/src/server/config.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use super::types::Server;
use super::{LocalServer, RemoteServer};
use crate::errors::Result;
use crate::server::local::LocalServer;
#[cfg(feature = "server-sync")]
use crate::server::sync::SyncServer;
use std::path::PathBuf;
#[cfg(feature = "server-sync")]
use uuid::Uuid;

/// The configuration for a replica's access to a sync server.
Expand All @@ -12,6 +15,7 @@ pub enum ServerConfig {
server_dir: PathBuf,
},
/// A remote taskchampion-sync-server instance
#[cfg(feature = "server-sync")]
Remote {
/// Sync server "origin"; a URL with schema and hostname but no path or trailing `/`
origin: String,
Expand All @@ -30,11 +34,12 @@ impl ServerConfig {
pub fn into_server(self) -> Result<Box<dyn Server>> {
Ok(match self {
ServerConfig::Local { server_dir } => Box::new(LocalServer::new(server_dir)?),
#[cfg(feature = "server-sync")]
ServerConfig::Remote {
origin,
client_id,
encryption_secret,
} => Box::new(RemoteServer::new(origin, client_id, encryption_secret)?),
} => Box::new(SyncServer::new(origin, client_id, encryption_secret)?),
})
}
}
4 changes: 3 additions & 1 deletion taskchampion/taskchampion/src/server/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#![allow(dead_code)] // temporary
/// This module implements the encryption specified in the sync-protocol
/// document.
use crate::errors::{Error, Result};
use ring::{aead, digest, pbkdf2, rand, rand::SecureRandom};
use std::io::Read;
use uuid::Uuid;

const PBKDF2_ITERATIONS: u32 = 100000;
Expand Down Expand Up @@ -177,11 +177,13 @@ pub(super) struct Sealed {
}

impl Sealed {
#[cfg(feature = "server-sync")]
pub(super) fn from_resp(
resp: ureq::Response,
version_id: Uuid,
content_type: &str,
) -> Result<Sealed> {
use std::io::Read;
if resp.header("Content-Type") == Some(content_type) {
let mut reader = resp.into_reader();
let mut payload = vec![];
Expand Down
10 changes: 6 additions & 4 deletions taskchampion/taskchampion/src/server/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ However, users who wish to implement their own server interfaces can implement t
pub(crate) mod test;

mod config;
mod crypto;
mod local;
mod op;
mod remote;
mod types;

#[cfg(feature = "crypto")]
mod crypto;

#[cfg(feature = "server-sync")]
mod sync;

pub use config::ServerConfig;
pub use local::LocalServer;
pub use remote::RemoteServer;
pub use types::*;

pub(crate) use op::SyncOp;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use uuid::Uuid;

use super::crypto::{Cryptor, Sealed, Secret, Unsealed};

pub struct RemoteServer {
pub struct SyncServer {
origin: String,
client_id: Uuid,
cryptor: Cryptor,
Expand All @@ -21,19 +21,18 @@ const HISTORY_SEGMENT_CONTENT_TYPE: &str = "application/vnd.taskchampion.history
/// The content-type for snapshots (opaque blobs of bytes)
const SNAPSHOT_CONTENT_TYPE: &str = "application/vnd.taskchampion.snapshot";

/// A RemoeServer communicates with a remote server over HTTP (such as with
/// taskchampion-sync-server).
impl RemoteServer {
/// Construct a new RemoteServer. The `origin` is the sync server's protocol and hostname
/// A RemoeServer communicates with a taskchampion-sync-server over HTTP .
impl SyncServer {
/// Construct a new SyncServer. The `origin` is the sync server's protocol and hostname
/// without a trailing slash, such as `https://tcsync.example.com`. Pass a client_id to
/// identify this client to the server. Multiple replicas synchronizing the same task history
/// should use the same client_id.
pub fn new(
origin: String,
client_id: Uuid,
encryption_secret: Vec<u8>,
) -> Result<RemoteServer> {
Ok(RemoteServer {
) -> Result<SyncServer> {
Ok(SyncServer {
origin,
client_id,
cryptor: Cryptor::new(client_id, &Secret(encryption_secret.to_vec()))?,
Expand Down Expand Up @@ -67,7 +66,7 @@ fn get_snapshot_urgency(resp: &ureq::Response) -> SnapshotUrgency {
}
}

impl Server for RemoteServer {
impl Server for SyncServer {
fn add_version(
&mut self,
parent_version_id: VersionId,
Expand Down
7 changes: 6 additions & 1 deletion taskchampion/taskchampion/src/server/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::errors::Result;
use uuid::Uuid;

/// Versions are referred to with sha2 hashes.
/// Versions are referred to with UUIDs.
pub type VersionId = Uuid;

/// The distinguished value for "no version"
Expand Down Expand Up @@ -52,6 +52,11 @@ pub enum GetVersionResult {
/// A value implementing this trait can act as a server against which a replica can sync.
pub trait Server {
/// Add a new version.
///
/// This must ensure that the new version is the only version with the given
/// `parent_version_id`, and that all versions form a single parent-child chain. Inductively,
/// this means that if there are any versions on the server, then `parent_version_id` must be
/// the only version that does not already have a child.
fn add_version(
&mut self,
parent_version_id: VersionId,
Expand Down

0 comments on commit c46c22a

Please sign in to comment.