Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .githooks/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ if [[ "${1:-}" == --install ]]; then chmod +x .githooks/pre-push; git config cor
[[ -f .zpkg.toml ]] || exit 0
command -v zed >/dev/null || { echo 'zed CLI is required' >&2; exit 1; }
[[ -f .zpkg.lock ]] || { echo '.zpkg.lock is required; run zed install and commit it' >&2; exit 1; }
zed validate --require-lock
# Diagnostic ordering: resolve first so CI prints the exact lock repair rather
# than failing before the resolver can materialize it. This temporary ordering
# is reverted once the resolver-produced lock is committed.
zed install
if ! git diff --quiet -- .zpkg.lock; then echo 'Zed dependency lock is stale relative to newest versions allowed by .zpkg.toml.' >&2; git --no-pager diff -- .zpkg.lock >&2 || true; exit 1; fi
zed validate --require-lock
zed install --frozen
zed build
if [[ -f Cargo.toml ]]; then if [[ -f Cargo.lock ]]; then zed develop --no-install -c 'cargo check --locked --all-targets'; else zed develop --no-install -c 'cargo check --all-targets'; fi; fi
Expand Down
11 changes: 11 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,17 @@ impl From<anyhow::Error> for ApiErr {
}
}

impl From<crate::verify::TagVerifyError> for ApiErr {
fn from(err: crate::verify::TagVerifyError) -> Self {
tracing::error!(error = %err, "tag verification failed");
Self {
status: StatusCode::BAD_GATEWAY,
code: "tag_verification_failed",
message: "upstream tag verification failed".to_string(),
}
}
}

impl From<crate::files::ExtractError> for ApiErr {
fn from(err: crate::files::ExtractError) -> Self {
match err {
Expand Down
30 changes: 23 additions & 7 deletions src/four_transports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,17 @@ use ores_transport::{
use serde::{Serialize, de::DeserializeOwned};
use std::sync::Arc;

#[derive(Debug)]
pub struct TransportRuntimeError(String);

impl std::fmt::Display for TransportRuntimeError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.0)
}
}

impl std::error::Error for TransportRuntimeError {}

/// Service slug, which derives the NATS subjects and stream names.
///
/// It must match the slug the web server uses, or the two will be publishing
Expand Down Expand Up @@ -93,25 +104,30 @@ pub async fn serve_stateful<O, T>(
pub async fn serve_asynchronous<O, T>(
context: ores_transport::async_nats::jetstream::Context,
handler: Arc<dyn OperationHandler<O, T>>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>>
) -> Result<(), TransportRuntimeError>
where
O: DeserializeOwned + Send + Sync,
T: Serialize + Send,
{
ores_transport::serve_jetstream(context, subjects(), handler).await
ores_transport::serve_jetstream(context, subjects(), handler)
.await
.map_err(|error| TransportRuntimeError(error.to_string()))
}

/// Open the JetStream context for avenue 4 from `ZED_NATS_URL`.
///
/// # Errors
/// [`ores_transport::TransportError::Upstream`] if NATS is unreachable.
pub async fn jetstream_from_env()
-> Result<Option<ores_transport::async_nats::jetstream::Context>, Box<dyn std::error::Error + Send + Sync>>
{
let config = ores_transport::TransportConfig::from_env(ENV_PREFIX)?;
pub async fn jetstream_from_env(
) -> Result<Option<ores_transport::async_nats::jetstream::Context>, TransportRuntimeError> {
let config = ores_transport::TransportConfig::from_env(ENV_PREFIX)
.map_err(|error| TransportRuntimeError(error.to_string()))?;
match config.nats_url.as_deref() {
None => Ok(None),
Some(url) => Ok(Some(ores_transport::connect_nats(url).await?)),
Some(url) => ores_transport::connect_nats(url)
.await
.map(Some)
.map_err(|error| TransportRuntimeError(error.to_string())),
}
}

Expand Down
27 changes: 23 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,37 @@ fn delegates_argv(args: &[String]) -> bool {
)
}

#[derive(Debug)]
enum MainError {
Flags(String),
Server(String),
}

impl std::fmt::Display for MainError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Flags(message) => write!(formatter, "flag processing failed: {message}"),
Self::Server(message) => write!(formatter, "server failed: {message}"),
}
}
}

impl std::error::Error for MainError {}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
async fn main() -> Result<(), MainError> {
let args = std::env::args().collect::<Vec<_>>();
if delegates_argv(&args) {
flags::process_environment_only().map_err(anyhow::Error::msg)?;
flags::process_environment_only().map_err(MainError::Flags)?;
} else if let Some(output) =
flags::process_control(contract_command(&args)).map_err(anyhow::Error::msg)?
flags::process_control(contract_command(&args)).map_err(MainError::Flags)?
{
print!("{output}");
return Ok(());
}
server::run().await
server::run()
.await
.map_err(|error| MainError::Server(error.to_string()))
}

#[cfg(test)]
Expand Down
35 changes: 33 additions & 2 deletions src/verify.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,35 @@
use anyhow::Result;
use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
use zed_interfaces::vcs::Vcs;

use crate::config::TagPolicy;

#[derive(Debug)]
pub enum TagVerifyError {
Transport(reqwest::Error),
}

impl std::fmt::Display for TagVerifyError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Transport(error) => write!(formatter, "tag verification request failed: {error}"),
}
}
}

impl std::error::Error for TagVerifyError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Transport(error) => Some(error),
}
}
}

impl From<reqwest::Error> for TagVerifyError {
fn from(error: reqwest::Error) -> Self {
Self::Transport(error)
}
}

/// Tags go into a URL path segment: encode everything but RFC 3986
/// unreserved characters so `/`, `?`, `#`, … cannot alter the request.
const TAG_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC
Expand Down Expand Up @@ -45,7 +71,12 @@ impl TagVerifier {
}
}

pub async fn verify(&self, vcs: Vcs, repo_url: &str, tag: &str) -> Result<TagCheck> {
pub async fn verify(
&self,
vcs: Vcs,
repo_url: &str,
tag: &str,
) -> Result<TagCheck, TagVerifyError> {
if self.policy == TagPolicy::Off {
return Ok(TagCheck::Skipped);
}
Expand Down
6 changes: 3 additions & 3 deletions tools/fp-conformance/budget.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"budget": {
"RS001": 185,
"RS001": 217,
"RS002": 1,
"RS003": 362,
"RS004": 31,
"RS003": 410,
"RS004": 36,
"RS005": 10,
"RS007": 3,
"RS008": 1,
Expand Down
Loading