:: lib::std::containers::collections
:: lib::std::encoding::hex
:: lib::std::io::buffered
:: lib::std::io::errors as io_errors
:: lib::std::io::filesystem
:: lib::std::text::strings
const stringv HEADER = "FLECHA-WORKSPACE-SNAPSHOT\t1\n";
const usize WRITE_CAPACITY = 8192;
&const collections::Array<model::Entry> entries
present, existsError := filesystem::exists(destination);
return io_errors::IoError.AlreadyExists;
options := build file::OpenOptions {
output, openError := file::File::open(destination, options);
adapter := output.writer();
buffered::BufferedWriter::withCapacity(adapter, WRITE_CAPACITY);
result = write_contents(&writer, entries);
closeError := output.close();
error _ = filesystem::remove_file(destination);
&buffered::BufferedWriter writer,
&const collections::Array<model::Entry> entries
error writeError = writer->writeAll(HEADER.cast<[]const u8>);
values := entries->view();
loop usize index = 0; index < values.len(); index++ {
pathHex, pathError := hex::encode(values[index].path());
strings::int_to_string<u64>(values[index].byteSize());
writeError = writer->writeAll(
values[index].hash().cast<[]const u8>
writeError = writer->writeAll("\t".cast<[]const u8>);
writeError = writer->writeAll(sizeText.view().cast<[]const u8>);
writeError = writer->writeAll("\t".cast<[]const u8>);
writeError = writer->writeAll(pathHex.view().cast<[]const u8>);
writeError = writer->writeAll("\n".cast<[]const u8>);
export (collections::Array<model::Entry>, error) read(stringv sourcePath) {
contents, readError := filesystem::read_file(sourcePath);
return (collections::Array<model::Entry>::init(), readError);
return parse(contents.view());
(collections::Array<model::Entry>, error) parse(stringv contents) {
entries := collections::Array<model::Entry>::init();
if contents.len() < HEADER.len() or
contents.substr(0, HEADER.len()) != HEADER {
return (entries, model::GuardError.InvalidManifest);
u32 offset = HEADER.len();
while offset < contents.len() {
while end < contents.len() and contents[end] != '\n' {
if end == contents.len() or end == offset {
return (entries, model::GuardError.InvalidManifest);
entry, entryError := parse_line(contents.substr(offset, end - offset));
return (entries, entryError);
values := entries.view();
values[values.len() - 1].path(),
return (entries, model::GuardError.InvalidManifest);
pushError := entries.push(entry);
return (entries, pushError);
(model::Entry, error) parse_line(stringv line) {
loop u32 index = 0; index < line.len(); index++ {
} else if second == line.len() {
return (none, model::GuardError.InvalidManifest);
if first == 0 or second <= first + 1 or second + 1 >= line.len() {
return (none, model::GuardError.InvalidManifest);
digestText := line.substr(0, first);
sizeText := line.substr(first + 1, second - first - 1);
pathText := line.substr(second + 1, line.len() - second - 1);
digestBytes, digestDecodeError := hex::decode_to_bytes(digestText);
if digestDecodeError != none or digestBytes.len() != 32 {
return (none, model::GuardError.InvalidManifest);
canonicalDigest, digestEncodeError := hex::encode_bytes(digestBytes[:]);
if digestEncodeError != none {
return (none, digestEncodeError);
if canonicalDigest.view() != digestText {
return (none, model::GuardError.InvalidManifest);
size, sizeParseError := strings::stringv_to_int<u64>(sizeText);
if sizeParseError != none {
return (none, model::GuardError.InvalidManifest);
canonicalSize, sizeFormatError := strings::int_to_string<u64>(size);
if sizeFormatError != none {
return (none, sizeFormatError);
if canonicalSize.view() != sizeText {
return (none, model::GuardError.InvalidManifest);
pathValue, pathDecodeError := hex::decode(pathText);
if pathDecodeError != none or utf8::validate(pathValue.view()) != none or
!model::valid_relative_path(pathValue.view()) {
return (none, model::GuardError.InvalidManifest);
canonicalPath, pathEncodeError := hex::encode(pathValue.view());
if pathEncodeError != none {
return (none, pathEncodeError);
if canonicalPath.view() != pathText {
return (none, model::GuardError.InvalidManifest);
return (model::Entry::init(pathValue, canonicalDigest, size), none);