Write type-safe metadata to the archive.
Write type-safe metadata to the archive.User-facing API that abstracts JSON serialization format. Components use SerializationMetadata to build metadata without knowledge of underlying format.
SerializationMetadata meta; meta.set("type", "Linear") .set("version", 1) .set("input_features", 128); archive.writeMetadata("meta.json", meta);
module;
#include <memory>
#include <string>
#include <vector>
#include <stdexcept>
#include <utility>
#include <cstdint>
#include <exception>
#include <format>
namespace Mila::Dnn::Serialization
{
using json = nlohmann::json;
export class ModelArchive
{
public:
explicit ModelArchive(
const std::string& filepath, std::unique_ptr<ArchiveSerializer> serializer,
OpenMode mode )
: filepath_( filepath ), serializer_( std::move( serializer ) ), mode_( mode ), closed_( false )
{
if ( !serializer_ )
{
throw std::invalid_argument( "ModelArchive requires a non-null serializer" );
}
if ( !serializer_->open( filepath, mode ) )
{
throw std::runtime_error(
std::format(
"ModelArchive: failed to open '{}' for {}", filepath,
archiveModeToString( mode ) ) );
}
}
{
if ( !closed_ && serializer_ )
{
try
{
close();
}
catch ( ... )
{
}
}
}
: serializer_( std::move( other.serializer_ ) )
, filepath_( std::move( other.filepath_ ) )
, mode_( other.mode_ )
, closed_( other.closed_ )
{
other.closed_ = true;
}
{
if ( this != &other )
{
if ( !closed_ )
{
try
{
close();
}
catch ( ... )
{
}
}
serializer_ = std::move( other.serializer_ );
filepath_ = std::move( other.filepath_ );
mode_ = other.mode_;
closed_ = other.closed_;
other.closed_ = true;
}
return *this;
}
OpenMode getMode() const noexcept
{
return mode_;
}
const std::string& getFilepath() const noexcept
{
return filepath_;
}
bool isClosed() const noexcept
{
return closed_;
}
void close()
{
if ( closed_ ) return;
if ( !serializer_ )
{
closed_ = true;
return;
}
if ( !serializer_->close() )
{
throw std::runtime_error(
std::format( "ModelArchive::close failed for '{}'", filepath_ ) );
}
closed_ = true;
}
void pushScope( const std::string& scope )
{
std::string s = scope;
while ( !s.empty() && s.front() == '/' ) s.erase( s.begin() );
while ( !s.empty() && s.back() == '/' ) s.pop_back();
if ( !s.empty() )
{
scope_stack_.push_back( s );
}
}
void popScope()
{
if ( scope_stack_.empty() )
{
throw std::runtime_error( "ModelArchive::popScope: no scope to pop" );
}
scope_stack_.pop_back();
}
class ScopedScope
{
public:
ScopedScope(
ModelArchive& archive,
const std::string& scope )
: archive_( archive ), active_( true )
{
archive_.
pushScope( scope );
}
~ScopedScope()
{
if ( active_ )
{
try
{
}
catch ( ... )
{
}
}
}
ScopedScope( const ScopedScope& ) = delete;
ScopedScope& operator=( const ScopedScope& ) = delete;
ScopedScope( ScopedScope&& other ) noexcept
: archive_( other.archive_ ), active_( other.active_ )
{
other.active_ = false;
}
private:
bool active_;
};
bool hasFile( const std::string& path ) const
{
requireOpen( "hasFile" );
return serializer_->hasFile( scopedPath( path ) );
}
size_t getFileSize( const std::string& path ) const
{
requireOpen( "getFileSize" );
return serializer_->getFileSize( scopedPath( path ) );
}
std::vector<std::string> listFiles() const
{
requireOpen( "listFiles" );
return serializer_->listFiles();
}
void writeMetadata( const std::string& path, const SerializationMetadata& metadata )
{
requireOpen( "writeMetadata" );
requireMode( OpenMode::Write, "writeMetadata" );
json j = metadata.toJson();
const std::string full = scopedPath( path );
std::string s = j.dump( 2 );
if ( !serializer_->addData( full, s.data(), s.size() ) )
{
throw std::runtime_error(
std::format( "ModelArchive::writeMetadata failed for path: {}", full ) );
}
}
SerializationMetadata
readMetadata(
const std::string& path )
const
{
requireMode( OpenMode::Read, "readMetadata" );
const std::string full = scopedPath( path );
size_t sz = serializer_->getFileSize( full );
if ( sz == 0 )
{
throw std::runtime_error( "ModelArchive::readMetadata missing file: " + full );
}
std::string s( sz, '\0' );
size_t read = serializer_->extractData( full, s.data(), sz );
if ( read != sz )
{
throw std::runtime_error( "ModelArchive::readMetadata failed to read entire file: " + full );
}
try
{
json j = json::parse( s );
}
catch ( const std::exception& e )
{
throw std::runtime_error( std::string( "ModelArchive::readMetadata parse error: " ) + e.what() );
}
}
void writeBlob(
const std::string& path,
const void* data,
size_t size )
{
requireOpen( "writeBlob" );
requireMode( OpenMode::Write, "writeBlob" );
if ( !data && size > 0 )
{
throw std::invalid_argument( "ModelArchive::writeBlob: null data with non-zero size" );
}
const std::string full = scopedPath( path );
if ( !serializer_->addData( full, data, size ) )
{
throw std::runtime_error(
std::format( "ModelArchive::writeBlob failed for path: {}", full ) );
}
}
std::vector<uint8_t>
readBlob(
const std::string& path )
const
{
requireOpen( "readBlob" );
requireMode( OpenMode::Read, "readBlob" );
const std::string full = scopedPath( path );
if ( !serializer_->hasFile( full ) )
{
throw std::runtime_error(
std::format( "ModelArchive::readBlob: file not found: {}", full ) );
}
size_t sz = serializer_->getFileSize( full );
if ( sz == 0 )
{
return {};
}
std::vector<uint8_t> buf( sz );
size_t read = serializer_->extractData( full, buf.data(), sz );
if ( read != sz )
{
throw std::runtime_error(
std::format( "ModelArchive::readBlob: incomplete read from {}", full ) );
}
return buf;
}
size_t readBlobInto(
const std::string& path,
void* buffer,
size_t buffer_size )
const
{
requireOpen( "readBlobInto" );
requireMode( OpenMode::Read, "readBlobInto" );
const std::string full = scopedPath( path );
if ( !serializer_->hasFile( full ) )
{
throw std::runtime_error(
std::format( "ModelArchive::readBlobInto: file not found: {}", full ) );
}
size_t file_size = serializer_->getFileSize( full );
if ( file_size > buffer_size )
{
throw std::runtime_error(
std::format( "ModelArchive::readBlobInto: buffer too small ({} < {})",
buffer_size, file_size ) );
}
size_t read = serializer_->extractData( full, buffer, file_size );
if ( read != file_size )
{
throw std::runtime_error(
std::format( "ModelArchive::readBlobInto: incomplete read from {}", full ) );
}
return read;
}
void addMetadata(
const std::string& key,
const std::string& value )
{
if ( !serializer_->addMetadata( key, value ) )
{
throw std::runtime_error(
std::format( "ModelArchive::addMetadata failed for key '{}' in '{}'", key, filepath_ ) );
}
}
std::string
getMetadata(
const std::string& key )
const
{
return serializer_->getMetadata( key );
}
private:
std::unique_ptr<ArchiveSerializer> serializer_;
std::string filepath_;
OpenMode mode_;
bool closed_{ false };
std::vector<std::string> scope_stack_;
static inline const std::vector<std::string> kAbsoluteRoots = { "network/", "components/", "modules/" };
std::string currentPrefix() const
{
if ( scope_stack_.empty() ) return std::string();
std::string p;
for ( size_t i = 0; i < scope_stack_.size(); ++i )
{
if ( i ) p.push_back( '/' );
p += scope_stack_[ i ];
}
if ( !p.empty() && p.back() != '/' ) p.push_back( '/' );
return p;
}
bool isAbsolutePath( const std::string& path ) const noexcept
{
if ( path.empty() ) return false;
if ( path.front() == '/' ) return true;
for ( const auto& root : kAbsoluteRoots )
{
if ( path.rfind( root, 0 ) == 0 ) return true;
}
return false;
}
std::string scopedPath( const std::string& path ) const
{
if ( isAbsolutePath( path ) || scope_stack_.empty() )
{
return path;
}
return currentPrefix() + path;
}
void requireMode( OpenMode expected, const char* op ) const
{
if ( mode_ != expected )
{
throw std::runtime_error(
std::format( "ModelArchive::{}: operation requires {} mode, but archive is in {} mode",
op, archiveModeToString( expected ), archiveModeToString( mode_ ) ) );
}
}
void requireOpen( const char* op ) const
{
if ( closed_ )
{
throw std::runtime_error(
std::format( "ModelArchive::{}: archive is closed", op ) );
}
}
};
}
void addMetadata(const std::string &key, const std::string &value)
Add simple metadata key-value pair.
Definition ModelArchive.ixx:460
SerializationMetadata readMetadata(const std::string &path) const
Read type-safe metadata from the archive.
Definition ModelArchive.ixx:311
ModelArchive(const std::string &filepath, std::unique_ptr< ArchiveSerializer > serializer, OpenMode mode)
Construct archive with serializer and automatically open for specified mode.
Definition ModelArchive.ixx:62
size_t readBlobInto(const std::string &path, void *buffer, size_t buffer_size) const
Read binary blob directly into provided buffer.
Definition ModelArchive.ixx:423
std::vector< uint8_t > readBlob(const std::string &path) const
Read raw binary blob from archive into returned vector.
Definition ModelArchive.ixx:383
~ModelArchive()
Destructor automatically closes the serializer.
Definition ModelArchive.ixx:80
std::string getMetadata(const std::string &key) const
Retrieve simple metadata value.
Definition ModelArchive.ixx:475
void writeBlob(const std::string &path, const void *data, size_t size)
Write raw binary blob to archive.
Definition ModelArchive.ixx:355