Skip to content
Merged
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@
monorepo-style tag such as `myapp-1.2.3` (or `myapp-v1.2.3`). Defaults to unset, which trims a
leading `v` as before; when set, tags without the prefix are skipped from the listing rather than
mis-parsed. ([#76](https://github.com/jaemk/self_update/issues/76))
- `asset_key_pattern(..)` on the s3 `Update`/`ReleaseList` builders: a custom regex for deriving
`(name, version)` from object keys, replacing the built-in matcher whose version group only
captures a `major.minor.patch` triple. Lets a pre-release key such as
`mybin-0.1.2-beta-x86_64-unknown-linux-gnu` parse as `0.1.2-beta` instead of `0.1.2`. The
pattern must define `name` and `version` named capture groups and is validated at `build()`
(`Error::InvalidAssetKeyPattern`); a captured version that does not parse as semver skips the
key. Unset keeps the existing matcher unchanged.
([#61](https://github.com/jaemk/self_update/issues/61))

### Changed
- A recognized-but-unsupported compression extension now fails loudly instead of silently
Expand Down
5 changes: 4 additions & 1 deletion specs/ref-errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ code builds them via the public constructors (`http_status_error(404, ..)`,
| `InvalidAssetName { name: String }` | The server-supplied asset name is empty, `.`, `..`, contains a `/` or `\` path separator, or is an absolute path; the file is never created (`update.rs`). `#[non_exhaustive]`. | none | no (struct fields) |
| `SignatureNonUTF8` | Generated archive path contains non-UTF-8 characters so its signature cannot be verified. Unit variant. | `signatures` | no (unit) |
| `S3Auth(Box<dyn Error + Send + Sync>)` | S3 SigV4 request-signing failure, including the host-extraction case (a signed URL with no extractable host). Via `From<SystemTimeError>`, `From<hmac::digest::InvalidLength>`, `From<url::ParseError>`, `From<time::error::ComponentRange>`, and direct construction at the host-extraction sites (`s3.rs`). | `s3-auth` | yes (boxed) |
| `InvalidAssetKeyPattern { source: Box<dyn Error + Send + Sync> }` | A user-supplied `asset_key_pattern` on the s3 builders did not compile or lacks a required named capture group (`name` / `version`). Raised from `build()` via `compile_asset_key_pattern` (`s3.rs`); the source is the regex-compile error or a `MessageError` naming the missing group. `#[non_exhaustive]`. | `s3` | yes (boxed source) |

### Reclassification of construction sites

Expand Down Expand Up @@ -141,6 +142,7 @@ Each variant renders with a specific Display string:
- `Signature(e)` -> `"SignatureError: {e}"` (dereferences the box, `signatures`)
- `SignatureNonUTF8` -> `"SignatureError: cannot verify signature of a file with a non-UTF-8 name"` (`signatures`)
- `S3Auth(e)` -> `"S3AuthError: {e}"` (dereferences the box, `s3-auth`)
- `InvalidAssetKeyPattern { source }` -> `"ConfigError: invalid asset_key_pattern: {source}"` (`s3`)

Note: `ArchiveNotEnabled` was corrected from `"ArchiveNotEnabled: ..."` to `"ArchiveNotEnabledError: ..."`;
`SignatureNonUTF8` was corrected from the bare message to `"SignatureError: ..."`, consistent with
Expand All @@ -151,7 +153,8 @@ every other variant using a `<Name>Error:` prefix.
`source()` returns the inner error for the wrapping variants: `Io` (the concrete io error); the
boxed `Json`, `Transport`, `SemVer`, `Zip` (gated), `Signature` (gated), `S3Auth` (gated); the
boxed-source variants `InvalidResponse`, `InvalidHeader`, `InvalidAuthToken`,
`InvalidCertificate`, `InvalidProgressStyle` (gated); and `Internal` when its `source` is `Some`
`InvalidCertificate`, `InvalidProgressStyle` (gated), `InvalidAssetKeyPattern` (gated); and
`Internal` when its `source` is `Some`
-- each via deref of the box. The `Internal { source: None }` form and all field-only variants
(`VerificationRejected`, `ChecksumMismatch`, `Aborted`, `NotFound`, `Unauthorized`, `HttpStatus`,
`NoReleaseFound`, `MissingAssetField`, `MissingField`, `ArchiveNotEnabled`,
Expand Down
48 changes: 36 additions & 12 deletions specs/ref-s3-backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,17 @@ Two builders, each reached through a `configure()` entry point:
`async` feature, `ReleaseList::fetch_async` (`s3.rs:282`). The result is a bare listing
(`current_version()` is `None`); recover the `Vec<Release>` with `into_vec()`.
`ReleaseList::configure` (`s3.rs:205`) seeds the builder. Setters: `bucket_name`,
`asset_prefix`, `region`, `endpoint`, `filter_target`, `max_keys`, and (under `s3-auth`)
`access_key` and `signature_ttl`; plus the shared `request_config_setters!(request)`.
`asset_prefix`, `asset_key_pattern`, `region`, `endpoint`, `filter_target`, `max_keys`, and
(under `s3-auth`) `access_key` and `signature_ttl`; plus the shared
`request_config_setters!(request)`.
There is **no** `auth_token` setter on this builder (the deprecated no-op was removed);
the credential setter is `access_key`.
- `Update` / `UpdateBuilder` (`s3.rs:359`, `s3.rs:247`): the `ReleaseUpdate`
implementation. `Update::configure` returns an `UpdateBuilder`.
`build` (`s3.rs:433`) and `build_async` (under `async`, `s3.rs:442`) both return
the concrete `Update` (which is `Send` and exposes the update verbs as inherent
methods, so no trait import is needed). Backend setters mirror the list builder
(`endpoint`, `bucket_name`, `asset_prefix`, `region`, `access_key`); the common
(`endpoint`, `bucket_name`, `asset_prefix`, `asset_key_pattern`, `region`, `access_key`); the common
setters come from `impl_common_builder_setters!(no_auth_token)` (`s3.rs:314`).
As on the list builder, there is **no** `auth_token` setter (the deprecated shim was
removed); use `access_key`.
Expand Down Expand Up @@ -119,12 +120,26 @@ with their assets concatenated (`s3.rs:923`); otherwise it pushes a new release.

### Version derivation

A single case-insensitive regex parses object keys (`s3.rs:834`):
By default a single case-insensitive regex parses object keys (`ASSET_KEY_REGEX`):
`(?i)(?P<prefix>.*/)*(?P<name>.+)-[v]{0,1}(?P<version>\d+\.\d+\.\d+)-.+`.
The key must contain a `name-[v]<major>.<minor>.<patch>-<suffix>` shape: `name`
becomes the release name and the dotted triple becomes the version, with any
leading `v` stripped (`s3.rs:874`). Keys lacking this shape produce no release.
Regex construction failure surfaces as `Error::InvalidResponse` (`s3.rs:836`).
leading `v` stripped. Keys lacking this shape produce no release. The default
version group captures only the `major.minor.patch` triple, so a pre-release key
like `mybin-0.1.2-beta-x86_64-...` parses lossily as `0.1.2` (#61).

`asset_key_pattern(impl Into<String>)` on both builders replaces the default
matcher with a user-supplied regex tuned to the bucket's key layout (e.g. one
whose `version` group admits a pre-release segment, so `0.1.2-beta` /
`0.1.2-beta.1` round-trip). The pattern must define `name` and `version` named
capture groups; `compile_asset_key_pattern` compiles and validates it at
`build()`, surfacing a pattern that does not compile or lacks a required group as
`Error::InvalidAssetKeyPattern` (a `#[non_exhaustive]` variant gated on the `s3`
feature, `Display` prefix "ConfigError:", underlying error chained via
`source()`). At parse time a custom pattern's captured version (after the same
leading-`v` trim) must parse as semver or the key is skipped like a non-matching
key; the default pattern is exempt from that check since its version group only
matches a numeric triple. When unset, behavior is unchanged.

`ReleaseUpdate` selection helpers operate on the parsed list: `pick_latest`
(`s3.rs:498`) picks the highest version (ignoring unparseable ones, erroring
Expand Down Expand Up @@ -184,9 +199,9 @@ Missing region (for the region-requiring endpoints) and missing bucket are both
`S3DualStack`, `GCS`, `DigitalOceanSpaces`, `Generic(String)`; plus
`From<&str>` / `From<String>` -> `Generic`.
- `s3::ReleaseList`, `s3::ReleaseListBuilder` (setters: `bucket_name`,
`asset_prefix`, `region`, `endpoint`, `filter_target`, `max_keys`,
`access_key` / `signature_ttl` [s3-auth], request-config setters, `build`);
`ReleaseList::fetch` and `fetch_async` [async].
`asset_prefix`, `asset_key_pattern`, `region`, `endpoint`, `filter_target`,
`max_keys`, `access_key` / `signature_ttl` [s3-auth], request-config setters,
`build`); `ReleaseList::fetch` and `fetch_async` [async].
- `s3::UpdateBuilder`, `s3::Update` (`#[non_exhaustive]`); `Update::configure`,
`build` -> `Update`, `build_async` -> `Update` [async]. `Update` is `Send` with
the inherent verbs (`update`, `update_extended`, `get_latest_release`,
Expand All @@ -206,8 +221,13 @@ Missing region (for the region-requiring endpoints) and missing bucket are both
`s3-auth` each continuation URL is freshly signed; `signature_ttl` sets the `X-Amz-Expires`.
- `asset_prefix` appended as `&prefix=<value>`; absent when unset.
- Asset `name` is the key's filename component, not the full key path.
- Version regex requires a `\d+\.\d+\.\d+` triple; leading `v` stripped; keys not
matching produce no release.
- Default version regex requires a `\d+\.\d+\.\d+` triple; leading `v` stripped;
keys not matching produce no release. A pre-release key parses lossily as the
bare triple (`0.1.2-beta` -> `0.1.2`); that default is pinned.
- `asset_key_pattern` replaces the default matcher on both builders; it must
define `name` and `version` named groups, is validated at `build()`
(`Error::InvalidAssetKeyPattern`, source chained), and a custom capture that is
not semver (after the leading-`v` trim) skips the key.
- Releases with the same `name`+`version` merge their assets; empty name/version
dropped.
- Non-2xx listing response is an `Err`, never `Ok` from the error body.
Expand All @@ -222,7 +242,11 @@ Missing region (for the region-requiring endpoints) and missing bucket are both

In-module tests (`s3.rs:938`): `parse_s3_response` cases (single/multi asset,
v-prefix strip, multiple releases, non-matching-key skip, path-stripped filename,
malformed-XML error, empty body); `add_to_releases_list` empty-name/version drop;
malformed-XML error, empty body); custom `asset_key_pattern` cases (pre-release
kept and merged, non-semver capture skipped, default lossy pre-release pinned,
bad-pattern / missing-group `build()` errors on both builders, end-to-end
threading through `Update::get_latest_release` and `ReleaseList::fetch` over the
loopback stub); `add_to_releases_list` empty-name/version drop;
loopback-TCP stub tests for the sync and async `ReleaseUpdate` fetch methods
(`get_latest_release`, `get_newer_releases`, `get_release_version`,
`is_update_available`, multi-asset merge); the non-2xx error contract
Expand Down
Loading
Loading