mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
rename frontend to web and update contributing guide (#2509)
* rename frontend to web and update contributing guide * rename this time * fix build * restructure rust code * update documentation * update descriptions * Update CONTRIBUTING.md Co-authored-by: J H <2364004+Blu-J@users.noreply.github.com> --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com> Co-authored-by: J H <2364004+Blu-J@users.noreply.github.com>
This commit is contained in:
38
core/models/Cargo.toml
Normal file
38
core/models/Cargo.toml
Normal file
@@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "models"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.21.4"
|
||||
color-eyre = "0.6.2"
|
||||
ed25519-dalek = { version = "2.0.0", features = ["serde"] }
|
||||
lazy_static = "1.4"
|
||||
mbrman = "0.5.2"
|
||||
emver = { version = "0.1", git = "https://github.com/Start9Labs/emver-rs.git", features = [
|
||||
"serde",
|
||||
] }
|
||||
ipnet = "2.8.0"
|
||||
openssl = { version = "0.10.57", features = ["vendored"] }
|
||||
patch-db = { version = "*", path = "../../patch-db/patch-db", features = [
|
||||
"trace",
|
||||
] }
|
||||
rand = "0.8.5"
|
||||
regex = "1.10.2"
|
||||
reqwest = "0.11.22"
|
||||
rpc-toolkit = "0.2.2"
|
||||
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||
serde_json = "1.0"
|
||||
sqlx = { version = "0.7.2", features = [
|
||||
"chrono",
|
||||
"runtime-tokio-rustls",
|
||||
"postgres",
|
||||
] }
|
||||
ssh-key = "0.6.2"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
torut = "0.2.1"
|
||||
tracing = "0.1.39"
|
||||
yasi = "0.1.5"
|
||||
171
core/models/src/data_url.rs
Normal file
171
core/models/src/data_url.rs
Normal file
@@ -0,0 +1,171 @@
|
||||
use std::borrow::Cow;
|
||||
use std::path::Path;
|
||||
|
||||
use base64::Engine;
|
||||
use color_eyre::eyre::eyre;
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt};
|
||||
use yasi::InternedString;
|
||||
|
||||
use crate::{mime, Error, ErrorKind, ResultExt};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DataUrl<'a> {
|
||||
mime: InternedString,
|
||||
data: Cow<'a, [u8]>,
|
||||
}
|
||||
impl<'a> DataUrl<'a> {
|
||||
pub const DEFAULT_MIME: &'static str = "application/octet-stream";
|
||||
pub const MAX_SIZE: u64 = 100 * 1024;
|
||||
|
||||
// data:{mime};base64,{data}
|
||||
pub fn to_string(&self) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut res = String::with_capacity(self.data_url_len_without_mime() + self.mime.len());
|
||||
let _ = write!(res, "data:{};base64,", self.mime);
|
||||
base64::engine::general_purpose::STANDARD.encode_string(&self.data, &mut res);
|
||||
res
|
||||
}
|
||||
|
||||
fn data_url_len_without_mime(&self) -> usize {
|
||||
5 + 8 + (4 * self.data.len() / 3) + 3
|
||||
}
|
||||
|
||||
pub fn data_url_len(&self) -> usize {
|
||||
self.data_url_len_without_mime() + self.mime.len()
|
||||
}
|
||||
|
||||
pub fn from_slice(mime: &str, data: &'a [u8]) -> Self {
|
||||
Self {
|
||||
mime: InternedString::intern(mime),
|
||||
data: Cow::Borrowed(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl DataUrl<'static> {
|
||||
pub async fn from_reader(
|
||||
mime: &str,
|
||||
rdr: impl AsyncRead + Unpin,
|
||||
size_est: Option<u64>,
|
||||
) -> Result<Self, Error> {
|
||||
let check_size = |s| {
|
||||
if s > Self::MAX_SIZE {
|
||||
Err(Error::new(
|
||||
eyre!("Data URLs must be smaller than 100KiB"),
|
||||
ErrorKind::Filesystem,
|
||||
))
|
||||
} else {
|
||||
Ok(s)
|
||||
}
|
||||
};
|
||||
let mut buf = size_est
|
||||
.map(check_size)
|
||||
.transpose()?
|
||||
.map(|s| Vec::with_capacity(s as usize))
|
||||
.unwrap_or_default();
|
||||
rdr.take(Self::MAX_SIZE + 1).read_to_end(&mut buf).await?;
|
||||
check_size(buf.len() as u64)?;
|
||||
|
||||
Ok(Self {
|
||||
mime: InternedString::intern(mime),
|
||||
data: Cow::Owned(buf),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn from_path(path: impl AsRef<Path>) -> Result<Self, Error> {
|
||||
let path = path.as_ref();
|
||||
let f = tokio::fs::File::open(path).await?;
|
||||
let m = f.metadata().await?;
|
||||
let mime = path
|
||||
.extension()
|
||||
.and_then(|s| s.to_str())
|
||||
.and_then(mime)
|
||||
.unwrap_or(Self::DEFAULT_MIME);
|
||||
Self::from_reader(mime, f, Some(m.len())).await
|
||||
}
|
||||
|
||||
pub async fn from_response(res: reqwest::Response) -> Result<Self, Error> {
|
||||
let mime = InternedString::intern(
|
||||
res.headers()
|
||||
.get(CONTENT_TYPE)
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.unwrap_or(Self::DEFAULT_MIME),
|
||||
);
|
||||
let data = res.bytes().await.with_kind(ErrorKind::Network)?.to_vec();
|
||||
Ok(Self {
|
||||
mime,
|
||||
data: Cow::Owned(data),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_vec(mime: &str, data: Vec<u8>) -> Self {
|
||||
Self {
|
||||
mime: InternedString::intern(mime),
|
||||
data: Cow::Owned(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Debug for DataUrl<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for DataUrl<'static> {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
struct Visitor;
|
||||
impl<'de> serde::de::Visitor<'de> for Visitor {
|
||||
type Value = DataUrl<'static>;
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
write!(formatter, "a valid base64 data url")
|
||||
}
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
v.strip_prefix("data:")
|
||||
.and_then(|v| v.split_once(";base64,"))
|
||||
.and_then(|(mime, data)| {
|
||||
Some(DataUrl {
|
||||
mime: InternedString::intern(mime),
|
||||
data: Cow::Owned(
|
||||
base64::engine::general_purpose::STANDARD
|
||||
.decode(data)
|
||||
.ok()?,
|
||||
),
|
||||
})
|
||||
})
|
||||
.ok_or_else(|| {
|
||||
E::invalid_value(serde::de::Unexpected::Str(v), &"a valid base64 data url")
|
||||
})
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_any(Visitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Serialize for DataUrl<'a> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_reallocate() {
|
||||
let random: [u8; 10] = rand::random();
|
||||
for i in 0..10 {
|
||||
let icon = DataUrl {
|
||||
mime: InternedString::intern("png"),
|
||||
data: Cow::Borrowed(&random[..i]),
|
||||
};
|
||||
assert_eq!(dbg!(icon.to_string()).capacity(), icon.data_url_len());
|
||||
}
|
||||
}
|
||||
434
core/models/src/errors.rs
Normal file
434
core/models/src/errors.rs
Normal file
@@ -0,0 +1,434 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use color_eyre::eyre::eyre;
|
||||
use patch_db::Revision;
|
||||
use rpc_toolkit::hyper::http::uri::InvalidUri;
|
||||
use rpc_toolkit::reqwest;
|
||||
use rpc_toolkit::yajrc::RpcError;
|
||||
|
||||
use crate::InvalidId;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ErrorKind {
|
||||
Unknown = 1,
|
||||
Filesystem = 2,
|
||||
Docker = 3,
|
||||
ConfigSpecViolation = 4,
|
||||
ConfigRulesViolation = 5,
|
||||
NotFound = 6,
|
||||
IncorrectPassword = 7,
|
||||
VersionIncompatible = 8,
|
||||
Network = 9,
|
||||
Registry = 10,
|
||||
Serialization = 11,
|
||||
Deserialization = 12,
|
||||
Utf8 = 13,
|
||||
ParseVersion = 14,
|
||||
IncorrectDisk = 15,
|
||||
// Nginx = 16,
|
||||
Dependency = 17,
|
||||
ParseS9pk = 18,
|
||||
ParseUrl = 19,
|
||||
DiskNotAvailable = 20,
|
||||
BlockDevice = 21,
|
||||
InvalidOnionAddress = 22,
|
||||
Pack = 23,
|
||||
ValidateS9pk = 24,
|
||||
DiskCorrupted = 25, // Remove
|
||||
Tor = 26,
|
||||
ConfigGen = 27,
|
||||
ParseNumber = 28,
|
||||
Database = 29,
|
||||
InvalidPackageId = 30,
|
||||
InvalidSignature = 31,
|
||||
Backup = 32,
|
||||
Restore = 33,
|
||||
Authorization = 34,
|
||||
AutoConfigure = 35,
|
||||
Action = 36,
|
||||
RateLimited = 37,
|
||||
InvalidRequest = 38,
|
||||
MigrationFailed = 39,
|
||||
Uninitialized = 40,
|
||||
ParseNetAddress = 41,
|
||||
ParseSshKey = 42,
|
||||
SoundError = 43,
|
||||
ParseTimestamp = 44,
|
||||
ParseSysInfo = 45,
|
||||
Wifi = 46,
|
||||
Journald = 47,
|
||||
DiskManagement = 48,
|
||||
OpenSsl = 49,
|
||||
PasswordHashGeneration = 50,
|
||||
DiagnosticMode = 51,
|
||||
ParseDbField = 52,
|
||||
Duplicate = 53,
|
||||
MultipleErrors = 54,
|
||||
Incoherent = 55,
|
||||
InvalidBackupTargetId = 56,
|
||||
ProductKeyMismatch = 57,
|
||||
LanPortConflict = 58,
|
||||
Javascript = 59,
|
||||
Pem = 60,
|
||||
TLSInit = 61,
|
||||
Ascii = 62,
|
||||
MissingHeader = 63,
|
||||
Grub = 64,
|
||||
Systemd = 65,
|
||||
OpenSsh = 66,
|
||||
Zram = 67,
|
||||
Lshw = 68,
|
||||
CpuSettings = 69,
|
||||
Firmware = 70,
|
||||
Timeout = 71,
|
||||
}
|
||||
impl ErrorKind {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
use ErrorKind::*;
|
||||
match self {
|
||||
Unknown => "Unknown Error",
|
||||
Filesystem => "Filesystem I/O Error",
|
||||
Docker => "Docker Error",
|
||||
ConfigSpecViolation => "Config Spec Violation",
|
||||
ConfigRulesViolation => "Config Rules Violation",
|
||||
NotFound => "Not Found",
|
||||
IncorrectPassword => "Incorrect Password",
|
||||
VersionIncompatible => "Version Incompatible",
|
||||
Network => "Network Error",
|
||||
Registry => "Registry Error",
|
||||
Serialization => "Serialization Error",
|
||||
Deserialization => "Deserialization Error",
|
||||
Utf8 => "UTF-8 Parse Error",
|
||||
ParseVersion => "Version Parsing Error",
|
||||
IncorrectDisk => "Incorrect Disk",
|
||||
// Nginx => "Nginx Error",
|
||||
Dependency => "Dependency Error",
|
||||
ParseS9pk => "S9PK Parsing Error",
|
||||
ParseUrl => "URL Parsing Error",
|
||||
DiskNotAvailable => "Disk Not Available",
|
||||
BlockDevice => "Block Device Error",
|
||||
InvalidOnionAddress => "Invalid Onion Address",
|
||||
Pack => "Pack Error",
|
||||
ValidateS9pk => "S9PK Validation Error",
|
||||
DiskCorrupted => "Disk Corrupted", // Remove
|
||||
Tor => "Tor Daemon Error",
|
||||
ConfigGen => "Config Generation Error",
|
||||
ParseNumber => "Number Parsing Error",
|
||||
Database => "Database Error",
|
||||
InvalidPackageId => "Invalid Package ID",
|
||||
InvalidSignature => "Invalid Signature",
|
||||
Backup => "Backup Error",
|
||||
Restore => "Restore Error",
|
||||
Authorization => "Unauthorized",
|
||||
AutoConfigure => "Auto-Configure Error",
|
||||
Action => "Action Failed",
|
||||
RateLimited => "Rate Limited",
|
||||
InvalidRequest => "Invalid Request",
|
||||
MigrationFailed => "Migration Failed",
|
||||
Uninitialized => "Uninitialized",
|
||||
ParseNetAddress => "Net Address Parsing Error",
|
||||
ParseSshKey => "SSH Key Parsing Error",
|
||||
SoundError => "Sound Interface Error",
|
||||
ParseTimestamp => "Timestamp Parsing Error",
|
||||
ParseSysInfo => "System Info Parsing Error",
|
||||
Wifi => "WiFi Internal Error",
|
||||
Journald => "Journald Error",
|
||||
DiskManagement => "Disk Management Error",
|
||||
OpenSsl => "OpenSSL Internal Error",
|
||||
PasswordHashGeneration => "Password Hash Generation Error",
|
||||
DiagnosticMode => "Server is in Diagnostic Mode",
|
||||
ParseDbField => "Database Field Parse Error",
|
||||
Duplicate => "Duplication Error",
|
||||
MultipleErrors => "Multiple Errors",
|
||||
Incoherent => "Incoherent",
|
||||
InvalidBackupTargetId => "Invalid Backup Target ID",
|
||||
ProductKeyMismatch => "Incompatible Product Keys",
|
||||
LanPortConflict => "Incompatible LAN Port Configuration",
|
||||
Javascript => "Javascript Engine Error",
|
||||
Pem => "PEM Encoding Error",
|
||||
TLSInit => "TLS Backend Initialization Error",
|
||||
Ascii => "ASCII Parse Error",
|
||||
MissingHeader => "Missing Header",
|
||||
Grub => "Grub Error",
|
||||
Systemd => "Systemd Error",
|
||||
OpenSsh => "OpenSSH Error",
|
||||
Zram => "Zram Error",
|
||||
Lshw => "LSHW Error",
|
||||
CpuSettings => "CPU Settings Error",
|
||||
Firmware => "Firmware Error",
|
||||
Timeout => "Timeout Error",
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub source: color_eyre::eyre::Error,
|
||||
pub kind: ErrorKind,
|
||||
pub revision: Option<Revision>,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}: {}", self.kind.as_str(), self.source)
|
||||
}
|
||||
}
|
||||
impl Error {
|
||||
pub fn new<E: Into<color_eyre::eyre::Error>>(source: E, kind: ErrorKind) -> Self {
|
||||
Error {
|
||||
source: source.into(),
|
||||
kind,
|
||||
revision: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<InvalidId> for Error {
|
||||
fn from(err: InvalidId) -> Self {
|
||||
Error::new(err, ErrorKind::InvalidPackageId)
|
||||
}
|
||||
}
|
||||
impl From<std::io::Error> for Error {
|
||||
fn from(e: std::io::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Filesystem)
|
||||
}
|
||||
}
|
||||
impl From<std::str::Utf8Error> for Error {
|
||||
fn from(e: std::str::Utf8Error) -> Self {
|
||||
Error::new(e, ErrorKind::Utf8)
|
||||
}
|
||||
}
|
||||
impl From<std::string::FromUtf8Error> for Error {
|
||||
fn from(e: std::string::FromUtf8Error) -> Self {
|
||||
Error::new(e, ErrorKind::Utf8)
|
||||
}
|
||||
}
|
||||
impl From<emver::ParseError> for Error {
|
||||
fn from(e: emver::ParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseVersion)
|
||||
}
|
||||
}
|
||||
impl From<rpc_toolkit::url::ParseError> for Error {
|
||||
fn from(e: rpc_toolkit::url::ParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseUrl)
|
||||
}
|
||||
}
|
||||
impl From<std::num::ParseIntError> for Error {
|
||||
fn from(e: std::num::ParseIntError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNumber)
|
||||
}
|
||||
}
|
||||
impl From<std::num::ParseFloatError> for Error {
|
||||
fn from(e: std::num::ParseFloatError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNumber)
|
||||
}
|
||||
}
|
||||
impl From<patch_db::Error> for Error {
|
||||
fn from(e: patch_db::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Database)
|
||||
}
|
||||
}
|
||||
impl From<sqlx::Error> for Error {
|
||||
fn from(e: sqlx::Error) -> Self {
|
||||
Error::new(e, ErrorKind::Database)
|
||||
}
|
||||
}
|
||||
impl From<ed25519_dalek::SignatureError> for Error {
|
||||
fn from(e: ed25519_dalek::SignatureError) -> Self {
|
||||
Error::new(e, ErrorKind::InvalidSignature)
|
||||
}
|
||||
}
|
||||
impl From<std::net::AddrParseError> for Error {
|
||||
fn from(e: std::net::AddrParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNetAddress)
|
||||
}
|
||||
}
|
||||
impl From<torut::control::ConnError> for Error {
|
||||
fn from(e: torut::control::ConnError) -> Self {
|
||||
Error::new(e, ErrorKind::Tor)
|
||||
}
|
||||
}
|
||||
impl From<ipnet::AddrParseError> for Error {
|
||||
fn from(e: ipnet::AddrParseError) -> Self {
|
||||
Error::new(e, ErrorKind::ParseNetAddress)
|
||||
}
|
||||
}
|
||||
impl From<openssl::error::ErrorStack> for Error {
|
||||
fn from(e: openssl::error::ErrorStack) -> Self {
|
||||
Error::new(eyre!("{}", e), ErrorKind::OpenSsl)
|
||||
}
|
||||
}
|
||||
impl From<mbrman::Error> for Error {
|
||||
fn from(e: mbrman::Error) -> Self {
|
||||
Error::new(e, ErrorKind::DiskManagement)
|
||||
}
|
||||
}
|
||||
impl From<InvalidUri> for Error {
|
||||
fn from(e: InvalidUri) -> Self {
|
||||
Error::new(eyre!("{}", e), ErrorKind::ParseUrl)
|
||||
}
|
||||
}
|
||||
impl From<ssh_key::Error> for Error {
|
||||
fn from(e: ssh_key::Error) -> Self {
|
||||
Error::new(e, ErrorKind::OpenSsh)
|
||||
}
|
||||
}
|
||||
impl From<reqwest::Error> for Error {
|
||||
fn from(e: reqwest::Error) -> Self {
|
||||
let kind = match e {
|
||||
_ if e.is_builder() => ErrorKind::ParseUrl,
|
||||
_ if e.is_decode() => ErrorKind::Deserialization,
|
||||
_ => ErrorKind::Network,
|
||||
};
|
||||
Error::new(e, kind)
|
||||
}
|
||||
}
|
||||
impl From<patch_db::value::Error> for Error {
|
||||
fn from(value: patch_db::value::Error) -> Self {
|
||||
match value.kind {
|
||||
patch_db::value::ErrorKind::Serialization => {
|
||||
Error::new(value.source, ErrorKind::Serialization)
|
||||
}
|
||||
patch_db::value::ErrorKind::Deserialization => {
|
||||
Error::new(value.source, ErrorKind::Deserialization)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for RpcError {
|
||||
fn from(e: Error) -> Self {
|
||||
let mut data_object = serde_json::Map::with_capacity(3);
|
||||
data_object.insert("details".to_owned(), format!("{}", e.source).into());
|
||||
data_object.insert("debug".to_owned(), format!("{:?}", e.source).into());
|
||||
data_object.insert(
|
||||
"revision".to_owned(),
|
||||
match serde_json::to_value(&e.revision) {
|
||||
Ok(a) => a,
|
||||
Err(e) => {
|
||||
tracing::warn!("Error serializing revision for Error object: {}", e);
|
||||
serde_json::Value::Null
|
||||
}
|
||||
},
|
||||
);
|
||||
RpcError {
|
||||
code: e.kind as i32,
|
||||
message: e.kind.as_str().into(),
|
||||
data: Some(data_object.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ErrorCollection(Vec<Error>);
|
||||
impl ErrorCollection {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn handle<T, E: Into<Error>>(&mut self, result: Result<T, E>) -> Option<T> {
|
||||
match result {
|
||||
Ok(a) => Some(a),
|
||||
Err(e) => {
|
||||
self.0.push(e.into());
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_result(self) -> Result<(), Error> {
|
||||
if self.0.is_empty() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(Error::new(eyre!("{}", self), ErrorKind::MultipleErrors))
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<ErrorCollection> for Result<(), Error> {
|
||||
fn from(e: ErrorCollection) -> Self {
|
||||
e.into_result()
|
||||
}
|
||||
}
|
||||
impl<T, E: Into<Error>> Extend<Result<T, E>> for ErrorCollection {
|
||||
fn extend<I: IntoIterator<Item = Result<T, E>>>(&mut self, iter: I) {
|
||||
for item in iter {
|
||||
self.handle(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for ErrorCollection {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for (idx, e) in self.0.iter().enumerate() {
|
||||
if idx > 0 {
|
||||
write!(f, "; ")?;
|
||||
}
|
||||
write!(f, "{}", e)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ResultExt<T, E>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error>;
|
||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display + Send + Sync + 'static>(
|
||||
self,
|
||||
f: F,
|
||||
) -> Result<T, Error>;
|
||||
}
|
||||
impl<T, E> ResultExt<T, E> for Result<T, E>
|
||||
where
|
||||
color_eyre::eyre::Error: From<E>,
|
||||
{
|
||||
fn with_kind(self, kind: ErrorKind) -> Result<T, Error> {
|
||||
self.map_err(|e| Error {
|
||||
source: e.into(),
|
||||
kind,
|
||||
revision: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn with_ctx<F: FnOnce(&E) -> (ErrorKind, D), D: Display + Send + Sync + 'static>(
|
||||
self,
|
||||
f: F,
|
||||
) -> Result<T, Error> {
|
||||
self.map_err(|e| {
|
||||
let (kind, ctx) = f(&e);
|
||||
let source = color_eyre::eyre::Error::from(e);
|
||||
let ctx = format!("{}: {}", ctx, source);
|
||||
let source = source.wrap_err(ctx);
|
||||
Error {
|
||||
kind,
|
||||
source,
|
||||
revision: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub trait OptionExt<T>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
fn or_not_found(self, message: impl std::fmt::Display) -> Result<T, Error>;
|
||||
}
|
||||
impl<T> OptionExt<T> for Option<T> {
|
||||
fn or_not_found(self, message: impl std::fmt::Display) -> Result<T, Error> {
|
||||
self.ok_or_else(|| Error::new(eyre!("{}", message), ErrorKind::NotFound))
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! ensure_code {
|
||||
($x:expr, $c:expr, $fmt:expr $(, $arg:expr)*) => {
|
||||
if !($x) {
|
||||
return Err(Error::new(color_eyre::eyre::eyre!($fmt, $($arg, )*), $c));
|
||||
}
|
||||
};
|
||||
}
|
||||
43
core/models/src/id/action.rs
Normal file
43
core/models/src/id/action.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{Id, InvalidId};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
pub struct ActionId(Id);
|
||||
impl FromStr for ActionId {
|
||||
type Err = InvalidId;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(ActionId(Id::try_from(s.to_owned())?))
|
||||
}
|
||||
}
|
||||
impl AsRef<ActionId> for ActionId {
|
||||
fn as_ref(&self) -> &ActionId {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for ActionId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for ActionId {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl AsRef<Path> for ActionId {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.0.as_ref().as_ref()
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for ActionId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
Ok(ActionId(serde::Deserialize::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
||||
59
core/models/src/id/address.rs
Normal file
59
core/models/src/id/address.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use std::path::Path;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::Id;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
pub struct AddressId(Id);
|
||||
impl From<Id> for AddressId {
|
||||
fn from(id: Id) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for AddressId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Deref for AddressId {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for AddressId {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for AddressId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(AddressId(Deserialize::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
||||
impl AsRef<Path> for AddressId {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.0.as_ref().as_ref()
|
||||
}
|
||||
}
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for AddressId {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
buf: &mut <sqlx::Postgres as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
|
||||
) -> sqlx::encode::IsNull {
|
||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
||||
}
|
||||
}
|
||||
impl sqlx::Type<sqlx::Postgres> for AddressId {
|
||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
||||
}
|
||||
|
||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
||||
}
|
||||
}
|
||||
31
core/models/src/id/health_check.rs
Normal file
31
core/models/src/id/health_check.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use std::path::Path;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::Id;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
pub struct HealthCheckId(Id);
|
||||
impl std::fmt::Display for HealthCheckId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for HealthCheckId {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for HealthCheckId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(HealthCheckId(Deserialize::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
||||
impl AsRef<Path> for HealthCheckId {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.0.as_ref().as_ref()
|
||||
}
|
||||
}
|
||||
38
core/models/src/id/image.rs
Normal file
38
core/models/src/id/image.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
use std::fmt::Debug;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::{Id, InvalidId, PackageId, Version};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
pub struct ImageId(Id);
|
||||
impl std::fmt::Display for ImageId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
impl ImageId {
|
||||
pub fn for_package(&self, pkg_id: &PackageId, pkg_version: Option<&Version>) -> String {
|
||||
format!(
|
||||
"start9/{}/{}:{}",
|
||||
pkg_id,
|
||||
self.0,
|
||||
pkg_version.map(|v| { v.as_str() }).unwrap_or("latest")
|
||||
)
|
||||
}
|
||||
}
|
||||
impl FromStr for ImageId {
|
||||
type Err = InvalidId;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(ImageId(Id::try_from(s.to_owned())?))
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for ImageId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(ImageId(Deserialize::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
||||
59
core/models/src/id/interface.rs
Normal file
59
core/models/src/id/interface.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use std::path::Path;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::Id;
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
|
||||
pub struct InterfaceId(Id);
|
||||
impl From<Id> for InterfaceId {
|
||||
fn from(id: Id) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for InterfaceId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
impl std::ops::Deref for InterfaceId {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for InterfaceId {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for InterfaceId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
Ok(InterfaceId(Deserialize::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
||||
impl AsRef<Path> for InterfaceId {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.0.as_ref().as_ref()
|
||||
}
|
||||
}
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for InterfaceId {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
buf: &mut <sqlx::Postgres as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
|
||||
) -> sqlx::encode::IsNull {
|
||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
||||
}
|
||||
}
|
||||
impl sqlx::Type<sqlx::Postgres> for InterfaceId {
|
||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
||||
}
|
||||
|
||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
||||
}
|
||||
}
|
||||
3
core/models/src/id/invalid_id.rs
Normal file
3
core/models/src/id/invalid_id.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("Invalid ID")]
|
||||
pub struct InvalidId;
|
||||
116
core/models/src/id/mod.rs
Normal file
116
core/models/src/id/mod.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use std::borrow::Borrow;
|
||||
|
||||
use regex::Regex;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use yasi::InternedString;
|
||||
|
||||
mod action;
|
||||
mod address;
|
||||
mod health_check;
|
||||
mod image;
|
||||
mod interface;
|
||||
mod invalid_id;
|
||||
mod package;
|
||||
mod volume;
|
||||
|
||||
pub use action::ActionId;
|
||||
pub use address::AddressId;
|
||||
pub use health_check::HealthCheckId;
|
||||
pub use image::ImageId;
|
||||
pub use interface::InterfaceId;
|
||||
pub use invalid_id::InvalidId;
|
||||
pub use package::{PackageId, SYSTEM_PACKAGE_ID};
|
||||
pub use volume::VolumeId;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref ID_REGEX: Regex = Regex::new("^[a-z]+(-[a-z]+)*$").unwrap();
|
||||
pub static ref SYSTEM_ID: Id = Id(InternedString::intern("x_system"));
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct Id(InternedString);
|
||||
impl TryFrom<InternedString> for Id {
|
||||
type Error = InvalidId;
|
||||
fn try_from(value: InternedString) -> Result<Self, Self::Error> {
|
||||
if ID_REGEX.is_match(&*value) {
|
||||
Ok(Id(value))
|
||||
} else {
|
||||
Err(InvalidId)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<String> for Id {
|
||||
type Error = InvalidId;
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
if ID_REGEX.is_match(&value) {
|
||||
Ok(Id(InternedString::intern(value)))
|
||||
} else {
|
||||
Err(InvalidId)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<&str> for Id {
|
||||
type Error = InvalidId;
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
if ID_REGEX.is_match(&value) {
|
||||
Ok(Id(InternedString::intern(value)))
|
||||
} else {
|
||||
Err(InvalidId)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::ops::Deref for Id {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Id {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &*self.0)
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for Id {
|
||||
fn as_ref(&self) -> &str {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
impl Borrow<str> for Id {
|
||||
fn borrow(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for Id {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let unchecked: InternedString = Deserialize::deserialize(deserializer)?;
|
||||
Id::try_from(unchecked).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
impl Serialize for Id {
|
||||
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
|
||||
where
|
||||
Ser: Serializer,
|
||||
{
|
||||
serializer.serialize_str(&*self)
|
||||
}
|
||||
}
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for Id {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
buf: &mut <sqlx::Postgres as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
|
||||
) -> sqlx::encode::IsNull {
|
||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
||||
}
|
||||
}
|
||||
impl sqlx::Type<sqlx::Postgres> for Id {
|
||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
||||
}
|
||||
|
||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
||||
}
|
||||
}
|
||||
88
core/models/src/id/package.rs
Normal file
88
core/models/src/id/package.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::{Id, InvalidId, SYSTEM_ID};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref SYSTEM_PACKAGE_ID: PackageId = PackageId(SYSTEM_ID.clone());
|
||||
}
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct PackageId(Id);
|
||||
impl FromStr for PackageId {
|
||||
type Err = InvalidId;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(PackageId(Id::try_from(s.to_owned())?))
|
||||
}
|
||||
}
|
||||
impl From<Id> for PackageId {
|
||||
fn from(id: Id) -> Self {
|
||||
PackageId(id)
|
||||
}
|
||||
}
|
||||
impl std::ops::Deref for PackageId {
|
||||
type Target = str;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&*self.0
|
||||
}
|
||||
}
|
||||
impl AsRef<PackageId> for PackageId {
|
||||
fn as_ref(&self) -> &PackageId {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for PackageId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", &self.0)
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for PackageId {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl Borrow<str> for PackageId {
|
||||
fn borrow(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
impl AsRef<Path> for PackageId {
|
||||
fn as_ref(&self) -> &Path {
|
||||
self.0.as_ref().as_ref()
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for PackageId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::de::Deserializer<'de>,
|
||||
{
|
||||
Ok(PackageId(Deserialize::deserialize(deserializer)?))
|
||||
}
|
||||
}
|
||||
impl Serialize for PackageId {
|
||||
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
|
||||
where
|
||||
Ser: Serializer,
|
||||
{
|
||||
Serialize::serialize(&self.0, serializer)
|
||||
}
|
||||
}
|
||||
impl<'q> sqlx::Encode<'q, sqlx::Postgres> for PackageId {
|
||||
fn encode_by_ref(
|
||||
&self,
|
||||
buf: &mut <sqlx::Postgres as sqlx::database::HasArguments<'q>>::ArgumentBuffer,
|
||||
) -> sqlx::encode::IsNull {
|
||||
<&str as sqlx::Encode<'q, sqlx::Postgres>>::encode_by_ref(&&**self, buf)
|
||||
}
|
||||
}
|
||||
impl sqlx::Type<sqlx::Postgres> for PackageId {
|
||||
fn type_info() -> sqlx::postgres::PgTypeInfo {
|
||||
<&str as sqlx::Type<sqlx::Postgres>>::type_info()
|
||||
}
|
||||
|
||||
fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
|
||||
<&str as sqlx::Type<sqlx::Postgres>>::compatible(ty)
|
||||
}
|
||||
}
|
||||
58
core/models/src/id/volume.rs
Normal file
58
core/models/src/id/volume.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::path::Path;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
|
||||
use crate::Id;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub enum VolumeId {
|
||||
Backup,
|
||||
Custom(Id),
|
||||
}
|
||||
impl std::fmt::Display for VolumeId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
VolumeId::Backup => write!(f, "BACKUP"),
|
||||
VolumeId::Custom(id) => write!(f, "{}", id),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for VolumeId {
|
||||
fn as_ref(&self) -> &str {
|
||||
match self {
|
||||
VolumeId::Backup => "BACKUP",
|
||||
VolumeId::Custom(id) => id.as_ref(),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Borrow<str> for VolumeId {
|
||||
fn borrow(&self) -> &str {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
impl AsRef<Path> for VolumeId {
|
||||
fn as_ref(&self) -> &Path {
|
||||
AsRef::<str>::as_ref(self).as_ref()
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for VolumeId {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let unchecked: String = Deserialize::deserialize(deserializer)?;
|
||||
Ok(match unchecked.as_ref() {
|
||||
"BACKUP" => VolumeId::Backup,
|
||||
_ => VolumeId::Custom(Id::try_from(unchecked).map_err(serde::de::Error::custom)?),
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Serialize for VolumeId {
|
||||
fn serialize<Ser>(&self, serializer: Ser) -> Result<Ser::Ok, Ser::Error>
|
||||
where
|
||||
Ser: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(self.as_ref())
|
||||
}
|
||||
}
|
||||
164
core/models/src/js_engine_types.rs
Normal file
164
core/models/src/js_engine_types.rs
Normal file
@@ -0,0 +1,164 @@
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use color_eyre::eyre::bail;
|
||||
use container_init::{Input, Output, ProcessId, RpcId};
|
||||
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// Used by the js-executor, it is the ability to just create a command in an already running exec
|
||||
pub type ExecCommand = Arc<
|
||||
dyn Fn(
|
||||
String,
|
||||
Vec<String>,
|
||||
UnboundedSender<container_init::Output>,
|
||||
Option<Duration>,
|
||||
) -> Pin<Box<dyn Future<Output = Result<RpcId, String>> + 'static>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>;
|
||||
|
||||
/// Used by the js-executor, it is the ability to just create a command in an already running exec
|
||||
pub type SendKillSignal = Arc<
|
||||
dyn Fn(RpcId, u32) -> Pin<Box<dyn Future<Output = Result<(), String>> + 'static>>
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
>;
|
||||
|
||||
pub trait CommandInserter {
|
||||
fn insert_command(
|
||||
&self,
|
||||
command: String,
|
||||
args: Vec<String>,
|
||||
sender: UnboundedSender<container_init::Output>,
|
||||
timeout: Option<Duration>,
|
||||
) -> Pin<Box<dyn Future<Output = Option<RpcId>>>>;
|
||||
|
||||
fn send_signal(&self, id: RpcId, command: u32) -> Pin<Box<dyn Future<Output = ()>>>;
|
||||
}
|
||||
|
||||
pub type ArcCommandInserter = Arc<Mutex<Option<Box<dyn CommandInserter>>>>;
|
||||
|
||||
pub struct ExecutingCommand {
|
||||
rpc_id: RpcId,
|
||||
/// Will exist until killed
|
||||
command_inserter: Arc<Mutex<Option<ArcCommandInserter>>>,
|
||||
owned_futures: Arc<Mutex<Vec<Pin<Box<dyn Future<Output = ()>>>>>>,
|
||||
}
|
||||
|
||||
impl ExecutingCommand {
|
||||
pub async fn new(
|
||||
command_inserter: ArcCommandInserter,
|
||||
command: String,
|
||||
args: Vec<String>,
|
||||
timeout: Option<Duration>,
|
||||
) -> Result<ExecutingCommand, color_eyre::Report> {
|
||||
let (sender, receiver) = tokio::sync::mpsc::unbounded_channel::<Output>();
|
||||
let rpc_id = {
|
||||
let locked_command_inserter = command_inserter.lock().await;
|
||||
let locked_command_inserter = match &*locked_command_inserter {
|
||||
Some(a) => a,
|
||||
None => bail!("Expecting containers.main in the package manifest".to_string()),
|
||||
};
|
||||
match locked_command_inserter
|
||||
.insert_command(command, args, sender, timeout)
|
||||
.await
|
||||
{
|
||||
Some(a) => a,
|
||||
None => bail!("Couldn't get command started ".to_string()),
|
||||
}
|
||||
};
|
||||
let executing_commands = ExecutingCommand {
|
||||
rpc_id,
|
||||
command_inserter: Arc::new(Mutex::new(Some(command_inserter.clone()))),
|
||||
owned_futures: Default::default(),
|
||||
};
|
||||
// let waiting = self.wait()
|
||||
Ok(executing_commands)
|
||||
}
|
||||
|
||||
async fn wait(
|
||||
rpc_id: RpcId,
|
||||
mut outputs: UnboundedReceiver<Output>,
|
||||
) -> Result<String, (Option<i32>, String)> {
|
||||
let (process_id_send, process_id_recv) = tokio::sync::oneshot::channel::<ProcessId>();
|
||||
let mut answer = String::new();
|
||||
let mut command_error = String::new();
|
||||
let mut status: Option<i32> = None;
|
||||
let mut process_id_send = Some(process_id_send);
|
||||
while let Some(output) = outputs.recv().await {
|
||||
match output {
|
||||
Output::ProcessId(process_id) => {
|
||||
if let Some(process_id_send) = process_id_send.take() {
|
||||
if let Err(err) = process_id_send.send(process_id) {
|
||||
tracing::error!(
|
||||
"Could not get a process id {process_id:?} sent for {rpc_id:?}"
|
||||
);
|
||||
tracing::debug!("{err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Output::Line(value) => {
|
||||
answer.push_str(&value);
|
||||
answer.push('\n');
|
||||
}
|
||||
Output::Error(error) => {
|
||||
command_error.push_str(&error);
|
||||
command_error.push('\n');
|
||||
}
|
||||
Output::Done(error_code) => {
|
||||
status = error_code;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if !command_error.is_empty() {
|
||||
return Err((status, command_error));
|
||||
}
|
||||
|
||||
Ok(answer)
|
||||
}
|
||||
|
||||
async fn send_signal(&self, signal: u32) {
|
||||
let locked = self.command_inserter.lock().await;
|
||||
let inner = match &*locked {
|
||||
Some(a) => a,
|
||||
None => return,
|
||||
};
|
||||
let locked = inner.lock().await;
|
||||
let command_inserter = match &*locked {
|
||||
Some(a) => a,
|
||||
None => return,
|
||||
};
|
||||
command_inserter.send_signal(self.rpc_id, signal);
|
||||
}
|
||||
/// Should only be called when output::done
|
||||
async fn killed(&self) {
|
||||
*self.owned_futures.lock().await = Default::default();
|
||||
*self.command_inserter.lock().await = Default::default();
|
||||
}
|
||||
pub fn rpc_id(&self) -> RpcId {
|
||||
self.rpc_id
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ExecutingCommand {
|
||||
fn drop(&mut self) {
|
||||
let command_inserter = self.command_inserter.clone();
|
||||
let rpc_id = self.rpc_id.clone();
|
||||
tokio::spawn(async move {
|
||||
let command_inserter_lock = command_inserter.lock().await;
|
||||
let command_inserter = match &*command_inserter_lock {
|
||||
Some(a) => a,
|
||||
None => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
command_inserter.send_kill_command(rpc_id, 9).await;
|
||||
});
|
||||
}
|
||||
}
|
||||
13
core/models/src/lib.rs
Normal file
13
core/models/src/lib.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
mod data_url;
|
||||
mod errors;
|
||||
mod id;
|
||||
mod mime;
|
||||
mod procedure_name;
|
||||
mod version;
|
||||
|
||||
pub use data_url::*;
|
||||
pub use errors::*;
|
||||
pub use id::*;
|
||||
pub use mime::*;
|
||||
pub use procedure_name::*;
|
||||
pub use version::*;
|
||||
47
core/models/src/mime.rs
Normal file
47
core/models/src/mime.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
pub fn mime(extension: &str) -> Option<&'static str> {
|
||||
match extension {
|
||||
"apng" => Some("image/apng"),
|
||||
"avif" => Some("image/avif"),
|
||||
"flif" => Some("image/flif"),
|
||||
"gif" => Some("image/gif"),
|
||||
"jpg" | "jpeg" | "jfif" | "pjpeg" | "pjp" => Some("image/jpeg"),
|
||||
"jxl" => Some("image/jxl"),
|
||||
"png" => Some("image/png"),
|
||||
"svg" => Some("image/svg+xml"),
|
||||
"webp" => Some("image/webp"),
|
||||
"mng" | "x-mng" => Some("image/x-mng"),
|
||||
"css" => Some("text/css"),
|
||||
"csv" => Some("text/csv"),
|
||||
"html" => Some("text/html"),
|
||||
"php" => Some("text/php"),
|
||||
"plain" | "md" | "txt" => Some("text/plain"),
|
||||
"xml" => Some("text/xml"),
|
||||
"js" => Some("text/javascript"),
|
||||
"wasm" => Some("application/wasm"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unmime(mime: &str) -> Option<&'static str> {
|
||||
match mime {
|
||||
"image/apng" => Some("apng"),
|
||||
"image/avif" => Some("avif"),
|
||||
"image/flif" => Some("flif"),
|
||||
"image/gif" => Some("gif"),
|
||||
"jpg" | "jpeg" | "jfif" | "pjpeg" | "image/jpeg" => Some("pjp"),
|
||||
"image/jxl" => Some("jxl"),
|
||||
"image/png" => Some("png"),
|
||||
"image/svg+xml" => Some("svg"),
|
||||
"image/webp" => Some("webp"),
|
||||
"mng" | "image/x-mng" => Some("x-mng"),
|
||||
"text/css" => Some("css"),
|
||||
"text/csv" => Some("csv"),
|
||||
"text/html" => Some("html"),
|
||||
"text/php" => Some("php"),
|
||||
"plain" | "md" | "text/plain" => Some("txt"),
|
||||
"text/xml" => Some("xml"),
|
||||
"text/javascript" => Some("js"),
|
||||
"application/wasm" => Some("wasm"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
57
core/models/src/procedure_name.rs
Normal file
57
core/models/src/procedure_name.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{ActionId, HealthCheckId, PackageId};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ProcedureName {
|
||||
Main, // Usually just run container
|
||||
CreateBackup,
|
||||
RestoreBackup,
|
||||
GetConfig,
|
||||
SetConfig,
|
||||
Migration,
|
||||
Properties,
|
||||
LongRunning,
|
||||
Check(PackageId),
|
||||
AutoConfig(PackageId),
|
||||
Health(HealthCheckId),
|
||||
Action(ActionId),
|
||||
Signal,
|
||||
}
|
||||
|
||||
impl ProcedureName {
|
||||
pub fn docker_name(&self) -> Option<String> {
|
||||
match self {
|
||||
ProcedureName::Main => None,
|
||||
ProcedureName::LongRunning => None,
|
||||
ProcedureName::CreateBackup => Some("CreateBackup".to_string()),
|
||||
ProcedureName::RestoreBackup => Some("RestoreBackup".to_string()),
|
||||
ProcedureName::GetConfig => Some("GetConfig".to_string()),
|
||||
ProcedureName::SetConfig => Some("SetConfig".to_string()),
|
||||
ProcedureName::Migration => Some("Migration".to_string()),
|
||||
ProcedureName::Properties => Some(format!("Properties-{}", rand::random::<u64>())),
|
||||
ProcedureName::Health(id) => Some(format!("{}Health", id)),
|
||||
ProcedureName::Action(id) => Some(format!("{}Action", id)),
|
||||
ProcedureName::Check(_) => None,
|
||||
ProcedureName::AutoConfig(_) => None,
|
||||
ProcedureName::Signal => None,
|
||||
}
|
||||
}
|
||||
pub fn js_function_name(&self) -> Option<String> {
|
||||
match self {
|
||||
ProcedureName::Main => Some("/main".to_string()),
|
||||
ProcedureName::LongRunning => None,
|
||||
ProcedureName::CreateBackup => Some("/createBackup".to_string()),
|
||||
ProcedureName::RestoreBackup => Some("/restoreBackup".to_string()),
|
||||
ProcedureName::GetConfig => Some("/getConfig".to_string()),
|
||||
ProcedureName::SetConfig => Some("/setConfig".to_string()),
|
||||
ProcedureName::Migration => Some("/migration".to_string()),
|
||||
ProcedureName::Properties => Some("/properties".to_string()),
|
||||
ProcedureName::Health(id) => Some(format!("/health/{}", id)),
|
||||
ProcedureName::Action(id) => Some(format!("/action/{}", id)),
|
||||
ProcedureName::Check(id) => Some(format!("/dependencies/{}/check", id)),
|
||||
ProcedureName::AutoConfig(id) => Some(format!("/dependencies/{}/autoConfigure", id)),
|
||||
ProcedureName::Signal => Some("/handleSignal".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
106
core/models/src/version.rs
Normal file
106
core/models/src/version.rs
Normal file
@@ -0,0 +1,106 @@
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Version {
|
||||
version: emver::Version,
|
||||
string: String,
|
||||
}
|
||||
impl Version {
|
||||
pub fn as_str(&self) -> &str {
|
||||
self.string.as_str()
|
||||
}
|
||||
pub fn into_version(self) -> emver::Version {
|
||||
self.version
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.string)
|
||||
}
|
||||
}
|
||||
impl std::str::FromStr for Version {
|
||||
type Err = <emver::Version as FromStr>::Err;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Version {
|
||||
string: s.to_owned(),
|
||||
version: s.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl From<emver::Version> for Version {
|
||||
fn from(v: emver::Version) -> Self {
|
||||
Version {
|
||||
string: v.to_string(),
|
||||
version: v,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<Version> for emver::Version {
|
||||
fn from(v: Version) -> Self {
|
||||
v.version
|
||||
}
|
||||
}
|
||||
impl Default for Version {
|
||||
fn default() -> Self {
|
||||
Self::from(emver::Version::default())
|
||||
}
|
||||
}
|
||||
impl Deref for Version {
|
||||
type Target = emver::Version;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.version
|
||||
}
|
||||
}
|
||||
impl AsRef<emver::Version> for Version {
|
||||
fn as_ref(&self) -> &emver::Version {
|
||||
&self.version
|
||||
}
|
||||
}
|
||||
impl AsRef<str> for Version {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
impl PartialEq for Version {
|
||||
fn eq(&self, other: &Version) -> bool {
|
||||
self.version.eq(&other.version)
|
||||
}
|
||||
}
|
||||
impl Eq for Version {}
|
||||
impl PartialOrd for Version {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.version.partial_cmp(&other.version)
|
||||
}
|
||||
}
|
||||
impl Ord for Version {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.version.cmp(&other.version)
|
||||
}
|
||||
}
|
||||
impl Hash for Version {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.version.hash(state)
|
||||
}
|
||||
}
|
||||
impl<'de> Deserialize<'de> for Version {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let string = String::deserialize(deserializer)?;
|
||||
let version = emver::Version::from_str(&string).map_err(::serde::de::Error::custom)?;
|
||||
Ok(Self { string, version })
|
||||
}
|
||||
}
|
||||
impl Serialize for Version {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.string.serialize(serializer)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user