feat(S8): ingest through the bank — capture/import/drop into bank + assign to active instance
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
// assignment_request.cpp — see assignment_request.h. Pure: standard library only.
|
||||
|
||||
#include "assignment_request.h"
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kMagic = "rsassign1";
|
||||
|
||||
// Append one length-prefixed field: <decimal-len> ':' <bytes>. Mirror of
|
||||
// provenance's putField so the two seams share one wire idiom.
|
||||
void putField(std::string& out, const std::string& field) {
|
||||
out += std::to_string(field.size());
|
||||
out += ':';
|
||||
out += field;
|
||||
}
|
||||
|
||||
// Cursor over the encoded string. All reads are bounds-checked; a short read fails
|
||||
// the whole parse (ok_ latches false). Mirror of provenance's Cursor, trimmed to the
|
||||
// three field kinds this record needs.
|
||||
class Cursor {
|
||||
public:
|
||||
explicit Cursor(const std::string& s) : s_(s) {}
|
||||
|
||||
bool ok() const { return ok_; }
|
||||
bool atEnd() const { return pos_ >= s_.size(); }
|
||||
|
||||
// Reads one length-prefixed field into `out`. Fails on a missing ':', an empty or
|
||||
// non-numeric length, or a length that runs past the end.
|
||||
bool field(std::string& out) {
|
||||
if (!ok_) return false;
|
||||
const std::size_t colon = s_.find(':', pos_);
|
||||
if (colon == std::string::npos) return fail();
|
||||
if (colon == pos_) return fail(); // empty length token
|
||||
std::size_t len = 0;
|
||||
for (std::size_t i = pos_; i < colon; ++i) {
|
||||
const char c = s_[i];
|
||||
if (c < '0' || c > '9') return fail();
|
||||
len = len * 10 + static_cast<std::size_t>(c - '0');
|
||||
}
|
||||
const std::size_t start = colon + 1;
|
||||
if (start + len > s_.size()) return fail();
|
||||
out.assign(s_, start, len);
|
||||
pos_ = start + len;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Reads a length-prefixed field and parses it as a signed 64-bit decimal (an
|
||||
// optional leading '-'). Fails on empty, non-digit, or trailing bytes.
|
||||
bool fieldInt64(std::int64_t& out) {
|
||||
std::string f;
|
||||
if (!field(f)) return false;
|
||||
if (f.empty()) return fail();
|
||||
std::size_t i = 0;
|
||||
bool neg = false;
|
||||
if (f[0] == '-') {
|
||||
neg = true;
|
||||
i = 1;
|
||||
if (f.size() == 1) return fail(); // bare "-"
|
||||
}
|
||||
std::int64_t v = 0;
|
||||
for (; i < f.size(); ++i) {
|
||||
const char c = f[i];
|
||||
if (c < '0' || c > '9') return fail();
|
||||
v = v * 10 + static_cast<std::int64_t>(c - '0');
|
||||
}
|
||||
out = neg ? -v : v;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Consumes an exact literal at the cursor (the magic tag). Fails if absent.
|
||||
bool literal(const char* lit) {
|
||||
if (!ok_) return false;
|
||||
std::size_t i = 0;
|
||||
for (; lit[i] != '\0'; ++i) {
|
||||
if (pos_ + i >= s_.size() || s_[pos_ + i] != lit[i]) return fail();
|
||||
}
|
||||
pos_ += i;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
bool fail() {
|
||||
ok_ = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& s_;
|
||||
std::size_t pos_ = 0;
|
||||
bool ok_ = true;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string encodeAssignmentRequest(const AssignmentRequest& req) {
|
||||
std::string out = kMagic;
|
||||
putField(out, req.bankId);
|
||||
putField(out, req.sampleId);
|
||||
putField(out, std::to_string(req.generation));
|
||||
return out;
|
||||
}
|
||||
|
||||
std::optional<AssignmentRequest> decodeAssignmentRequest(const std::string& wire) {
|
||||
Cursor cur(wire);
|
||||
if (!cur.literal(kMagic)) return std::nullopt;
|
||||
|
||||
AssignmentRequest req;
|
||||
if (!cur.field(req.bankId)) return std::nullopt;
|
||||
if (!cur.field(req.sampleId)) return std::nullopt;
|
||||
if (!cur.fieldInt64(req.generation)) return std::nullopt;
|
||||
|
||||
// Reject trailing garbage: a well-formed value ends exactly at the last field.
|
||||
if (!cur.ok() || !cur.atEnd()) return std::nullopt;
|
||||
return req;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user