+ update testdata
+ implement get_meta - refactor structs a bit
This commit is contained in:
parent
363cd11aa5
commit
a6de2133fd
7 changed files with 241 additions and 73 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
|
@ -1511,6 +1511,7 @@ dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"crossbeam-queue",
|
"crossbeam-queue",
|
||||||
"either",
|
"either",
|
||||||
|
|
@ -1574,6 +1575,7 @@ dependencies = [
|
||||||
"sha2",
|
"sha2",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"sqlx-mysql",
|
"sqlx-mysql",
|
||||||
|
"sqlx-postgres",
|
||||||
"sqlx-sqlite",
|
"sqlx-sqlite",
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
|
@ -1592,6 +1594,7 @@ dependencies = [
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"digest",
|
"digest",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
|
@ -1633,6 +1636,7 @@ dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bitflags 2.5.0",
|
"bitflags 2.5.0",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"chrono",
|
||||||
"crc",
|
"crc",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"etcetera",
|
"etcetera",
|
||||||
|
|
@ -1668,6 +1672,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
|
checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
|
"chrono",
|
||||||
"flume",
|
"flume",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ sqlx = { version = "0.7.4", features = [
|
||||||
"sqlite",
|
"sqlite",
|
||||||
# "postgres",
|
# "postgres",
|
||||||
# "any",
|
# "any",
|
||||||
|
"chrono",
|
||||||
"macros",
|
"macros",
|
||||||
"runtime-tokio",
|
"runtime-tokio",
|
||||||
"tls-rustls",
|
"tls-rustls",
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ RUN go mod download
|
||||||
COPY . .
|
COPY . .
|
||||||
# RUN go build -o /app
|
# RUN go build -o /app
|
||||||
RUN go build
|
RUN go build
|
||||||
|
# CMD export GOCACHE=off
|
||||||
CMD go test tests/*
|
CMD go test tests/*
|
||||||
|
|
||||||
# FROM docker.io/library/alpine:3.19
|
# FROM docker.io/library/alpine:3.19
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
-- Add migration script here
|
-- Add migration script here
|
||||||
|
|
||||||
INSERT INTO metadata VALUES ("bar", false, DateTime('now'), "123", 4, DateTime('now'), "customData");
|
INSERT INTO metadata VALUES ("bar", false, DateTime('now'), "30d", 4, DateTime('now'), '{"foo": "customData"}');
|
||||||
|
|
||||||
INSERT INTO secret_versions VALUES ("secret_data", DateTime('now'), DateTime('now'), 1, "bar");
|
INSERT INTO secret_versions VALUES ("secret_data", DateTime('now'), DateTime('now'), 1, "bar");
|
||||||
|
INSERT INTO secret_versions VALUES ("more_secret_data", DateTime('now'), datetime('now', '+30 day'), 2, "bar");
|
||||||
|
|
@ -2,24 +2,30 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
// pub mod logic; // TODO: Remove or correct errors
|
// pub mod logic; // TODO: Remove or correct errors
|
||||||
pub mod http_structs;
|
|
||||||
pub mod db_structs;
|
pub mod db_structs;
|
||||||
|
pub mod http_structs;
|
||||||
|
|
||||||
// #[cfg(test)]
|
// #[cfg(test)]
|
||||||
// mod tests;
|
// mod tests;
|
||||||
|
|
||||||
use std::{collections::HashMap, convert::Infallible};
|
|
||||||
use serde_json;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
engines::kv::http_structs::*,
|
engines::kv::{self, http_structs::*},
|
||||||
storage::DatabaseDriver,
|
storage::DatabaseDriver,
|
||||||
};
|
};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{self, Path, State}, http::StatusCode, response::IntoResponse, routing::*, Json, Router
|
extract::{self, Path, State},
|
||||||
|
http::StatusCode,
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::*,
|
||||||
|
Json, Router,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use log::{info, error};
|
use db_structs::*;
|
||||||
|
use log::{error, info};
|
||||||
|
use serde_json;
|
||||||
use sqlx::{error, Row};
|
use sqlx::{error, Row};
|
||||||
|
use std::{collections::HashMap, convert::Infallible};
|
||||||
|
|
||||||
pub fn kv_router(pool: DatabaseDriver) -> Router {
|
pub fn kv_router(pool: DatabaseDriver) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
|
|
@ -68,7 +74,13 @@ async fn get_data(
|
||||||
("secret_path".to_string(), v.get("secret_path")),
|
("secret_path".to_string(), v.get("secret_path")),
|
||||||
]);
|
]);
|
||||||
let return_secret = KvSecretRes::new(KvSecretResData {
|
let return_secret = KvSecretRes::new(KvSecretResData {
|
||||||
created_time: DateTime::parse_from_rfc3339(secret_content.get("created_time").unwrap_or(&"".to_string())).unwrap_or_default().to_utc(), // TODO
|
created_time: DateTime::parse_from_rfc3339(
|
||||||
|
secret_content
|
||||||
|
.get("created_time")
|
||||||
|
.unwrap_or(&"".to_string()),
|
||||||
|
)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_utc(), // TODO
|
||||||
custom_metadata: None,
|
custom_metadata: None,
|
||||||
deletion_time: None, // TODO
|
deletion_time: None, // TODO
|
||||||
destroyed: false,
|
destroyed: false,
|
||||||
|
|
@ -81,12 +93,12 @@ async fn get_data(
|
||||||
Err(e) => match e {
|
Err(e) => match e {
|
||||||
sqlx::Error::RowNotFound => {
|
sqlx::Error::RowNotFound => {
|
||||||
error!("{:?}", e);
|
error!("{:?}", e);
|
||||||
let error_struct : ErrorStruct = ErrorStruct{err: e.to_string() };
|
let error_struct: ErrorStruct = ErrorStruct { err: e.to_string() };
|
||||||
error!("{:?}", error_struct.err);
|
error!("{:?}", error_struct.err);
|
||||||
Ok(error_struct.into_response()) // TODO: API doesn't specify return value in case of error. Error struct correct? Else send empty secret back?
|
Ok(error_struct.into_response()) // TODO: API doesn't specify return value in case of error. Error struct correct? Else send empty secret back?
|
||||||
// let error_secret = KvSecretRes{data: None, options: None};
|
// let error_secret = KvSecretRes{data: None, options: None};
|
||||||
// Ok(Json())
|
// Ok(Json())
|
||||||
},
|
}
|
||||||
_ => panic!("{:?}", e),
|
_ => panic!("{:?}", e),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -99,7 +111,6 @@ async fn post_data(
|
||||||
) -> &'static str {
|
) -> &'static str {
|
||||||
// Insert Metadata first -> Else: Error because of foreign key constraint
|
// Insert Metadata first -> Else: Error because of foreign key constraint
|
||||||
|
|
||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Secret: {}, Content: {:?}, Version: {:?}, path: {}",
|
"Secret: {}, Content: {:?}, Version: {:?}, path: {}",
|
||||||
path,
|
path,
|
||||||
|
|
@ -108,18 +119,15 @@ async fn post_data(
|
||||||
path
|
path
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
let data = serde_json::to_string(&payload.data).unwrap();
|
let data = serde_json::to_string(&payload.data).unwrap();
|
||||||
log::debug!("Received data: {:?}", data);
|
log::debug!("Received data: {:?}", data);
|
||||||
let created_time = Utc::now().to_string();
|
let created_time = Utc::now().to_string();
|
||||||
let deletion_time = "12-12-2024 12:00:00"; // TODO:
|
let deletion_time = "12-12-2024 12:00:00"; // TODO:
|
||||||
let version = "1";
|
let version = "1";
|
||||||
match sqlx::query(
|
match sqlx::query("INSERT INTO secret_versions VALUES ($1, $2, $3, $4, $5)")
|
||||||
"INSERT INTO secret_versions VALUES ($1, $2, $3, $4, $5)",
|
|
||||||
)
|
|
||||||
.bind(data)
|
.bind(data)
|
||||||
.bind(created_time)
|
.bind(created_time)
|
||||||
.bind (deletion_time)
|
.bind(deletion_time)
|
||||||
.bind(version)
|
.bind(version)
|
||||||
.bind(path)
|
.bind(path)
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
|
|
@ -178,8 +186,96 @@ async fn destroy_path() -> &'static str {
|
||||||
todo!("not implemented")
|
todo!("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_meta() -> &'static str {
|
async fn get_meta(
|
||||||
todo!("not implemented")
|
State(pool): State<DatabaseDriver>,
|
||||||
|
Path(kv_path): Path<String>,
|
||||||
|
) -> Result<impl IntoResponse, Infallible> {
|
||||||
|
log::debug!("Path: {}", kv_path);
|
||||||
|
|
||||||
|
let mut metadata_res: KvMetaRes = KvMetaRes::default();
|
||||||
|
|
||||||
|
let dbmeta = sqlx::query_as::<_, DbSecretMeta>("SELECT * FROM metadata where secret_path = $1")
|
||||||
|
.bind(&kv_path)
|
||||||
|
.fetch_optional(&pool)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match dbmeta {
|
||||||
|
Ok(Some(dbmeta)) => {
|
||||||
|
metadata_res.data = KvMetaResData {
|
||||||
|
created_time: dbmeta.created_time,
|
||||||
|
|
||||||
|
// map the custom_data to a Hashmap
|
||||||
|
custom_metadata: dbmeta.custom_data.map(|data| {
|
||||||
|
serde_json::from_str::<HashMap<String, String>>(&data)
|
||||||
|
.unwrap_or_else(|_| HashMap::new())
|
||||||
|
}),
|
||||||
|
|
||||||
|
cas_required: dbmeta.cas_required,
|
||||||
|
max_versions: dbmeta.max_versions,
|
||||||
|
updated_time: dbmeta.updated_time,
|
||||||
|
delete_version_after: dbmeta.delete_version_after,
|
||||||
|
current_version: 0,
|
||||||
|
oldest_version: 0,
|
||||||
|
versions: HashMap::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let version_data = sqlx::query_as::<_, DbSecretVersionMeta>("SELECT version_number, created_time, deletion_time FROM secret_versions WHERE secret_path = $1")
|
||||||
|
.bind(&kv_path)
|
||||||
|
.fetch_all(&pool)
|
||||||
|
.await;
|
||||||
|
log::debug!("found version_data: {:?}", version_data);
|
||||||
|
|
||||||
|
if let Ok(version_data) = version_data {
|
||||||
|
// 1. iterate through all version data
|
||||||
|
// 2. put all version numbers as keys in the hashmap. the rest of the values values should be the value
|
||||||
|
let mut parsed_versions: HashMap<i64, KvMetaResVersionData> = HashMap::new();
|
||||||
|
let now = Utc::now();
|
||||||
|
|
||||||
|
for curr_ver in version_data {
|
||||||
|
let curr_num = curr_ver.version_number;
|
||||||
|
let data = KvMetaResVersionData {
|
||||||
|
created_time: curr_ver.created_time,
|
||||||
|
deletion_time: curr_ver.deletion_time,
|
||||||
|
destroyed: if curr_ver.deletion_time < now {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if metadata_res.data.current_version < curr_num {
|
||||||
|
// should be the max of the available version numbers
|
||||||
|
metadata_res.data.current_version = curr_num;
|
||||||
|
}
|
||||||
|
if metadata_res.data.oldest_version > curr_num {
|
||||||
|
// should be the min of the available version numbers
|
||||||
|
metadata_res.data.oldest_version = curr_num;
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed_versions.insert(curr_num, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata_res.data.versions = parsed_versions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None) => {
|
||||||
|
return Ok((StatusCode::BAD_REQUEST, Json("No metadata found")).into_response());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("Database error: {}", e);
|
||||||
|
return Ok((
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json("Internal server error"),
|
||||||
|
)
|
||||||
|
.into_response());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let json_string = serde_json::to_string(&metadata_res).unwrap();
|
||||||
|
log::debug!("Returning response: {}", json_string);
|
||||||
|
|
||||||
|
Ok((StatusCode::OK, Json(metadata_res)).into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn post_meta(
|
async fn post_meta(
|
||||||
|
|
|
||||||
|
|
@ -1,53 +1,63 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use log::*;
|
use serde::Serialize;
|
||||||
|
use sqlx::FromRow;
|
||||||
|
|
||||||
#[derive(Debug)]
|
// #[derive(Debug)]
|
||||||
#[deprecated(note = "Add Req or Res respecively if AND move to http file if intended; remove deprecation once used")]
|
// #[deprecated(note = "Add Req or Res respecively if AND move to http file if intended; remove deprecation once used")]
|
||||||
pub struct VersionMeta {
|
// pub struct SecretMeta {
|
||||||
pub created_time: DateTime<Utc>,
|
// pub cas_required: bool,
|
||||||
pub deletion_time: Option<DateTime<Utc>>, // optional deletion time
|
// pub created_time: DateTime<Utc>,
|
||||||
pub destroyed: bool,
|
// pub current_version: i64,
|
||||||
}
|
// /// In Hashicorp:
|
||||||
|
// /// If not set, the backend's configured delete_version_after is used.
|
||||||
|
// /// Cannot be greater than the backend's delete_version_after
|
||||||
|
// // TODO: implement duration type
|
||||||
|
// pub delete_version_after: String,
|
||||||
|
// // TODO https://developer.hashicorp.com/vault/docs/concepts/duration-format
|
||||||
|
// pub max_versions: i64,
|
||||||
|
// pub oldest_version: i64,
|
||||||
|
// pub updated_time: DateTime<Utc>,
|
||||||
|
// /// User-provided key-value pairs that are used to describe arbitrary and version-agnostic information about a secret.
|
||||||
|
// pub custom_metadata: Option<HashMap<String, String>>,
|
||||||
|
// pub versions: Vec<VersionMeta>,
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[derive(FromRow)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[deprecated(note = "Add Req or Res respecively if AND move to http file if intended; remove deprecation once used")]
|
pub struct DbSecretMeta {
|
||||||
pub struct SecretMeta {
|
pub secret_path: String,
|
||||||
pub cas_required: bool,
|
pub cas_required: bool,
|
||||||
pub created_time: DateTime<Utc>,
|
pub created_time: DateTime<Utc>,
|
||||||
pub current_version: i64,
|
// Consider: implement duration type
|
||||||
|
// https://developer.hashicorp.com/vault/docs/concepts/duration-format
|
||||||
|
|
||||||
/// In Hashicorp:
|
/// In Hashicorp:
|
||||||
/// If not set, the backend's configured delete_version_after is used.
|
/// If not set, the backend's configured delete_version_after is used.
|
||||||
/// Cannot be greater than the backend's delete_version_after
|
/// Cannot be greater than the backend's delete_version_after
|
||||||
// TODO: implement duration type
|
pub delete_version_after: Option<String>,
|
||||||
pub delete_version_after: String,
|
///In Hashicorp:
|
||||||
// TODO https://developer.hashicorp.com/vault/docs/concepts/duration-format
|
/// The number of versions to keep per key.
|
||||||
|
/// If not set, the backend’s configured max version is used.
|
||||||
|
/// Once a key has more than the configured allowed versions,
|
||||||
|
/// the oldest version will be permanently deleted.
|
||||||
pub max_versions: i64,
|
pub max_versions: i64,
|
||||||
pub oldest_version: i64,
|
|
||||||
pub updated_time: DateTime<Utc>,
|
pub updated_time: DateTime<Utc>,
|
||||||
/// User-provided key-value pairs that are used to describe arbitrary and version-agnostic information about a secret.
|
/// User-provided key-value pairs that are used to describe arbitrary and version-agnostic information about a secret.
|
||||||
pub custom_metadata: Option<HashMap<String, String>>,
|
|
||||||
pub versions: Vec<VersionMeta>,
|
pub custom_data: Option<String>,
|
||||||
|
|
||||||
|
// TODO: AS HASHMAP
|
||||||
|
// pub custom_data: Option<HashMap<String, String>>,
|
||||||
|
|
||||||
|
// pub current_version: i64,
|
||||||
|
// pub oldest_version: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SecretMeta {
|
#[derive(Serialize,Debug, FromRow)]
|
||||||
/// Use [Serde field defaults](https://serde.rs/attr-default.html) instead
|
/// Metadata concerning a specific secret version
|
||||||
/// TODO: remove this function
|
/// contained by [KvMetaRes]
|
||||||
fn default() -> Self {
|
pub struct DbSecretVersionMeta {
|
||||||
warn!("DEPRECATED: Default of SecretMeta was used, to be removed");
|
pub version_number: i64,
|
||||||
|
pub created_time: DateTime<Utc>,
|
||||||
let current = Utc::now();
|
pub deletion_time: DateTime<Utc>,
|
||||||
SecretMeta {
|
|
||||||
cas_required: false,
|
|
||||||
created_time: current,
|
|
||||||
current_version: 1,
|
|
||||||
delete_version_after: "24h00m00s".to_string(),
|
|
||||||
max_versions: 10,
|
|
||||||
oldest_version: 1,
|
|
||||||
updated_time: current,
|
|
||||||
custom_metadata: None,
|
|
||||||
versions: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
use axum::{body::Body, http::{Response, StatusCode}, response::IntoResponse, Error};
|
use axum::{
|
||||||
|
body::Body,
|
||||||
|
http::{Response, StatusCode},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::common::HttpError;
|
|
||||||
|
|
||||||
pub type KvSecretData = HashMap<String, String>;
|
pub type KvSecretData = HashMap<String, String>;
|
||||||
|
|
||||||
// This file contains structures for serializing HTTP Responses (Res) and deserializing Requests (Req) for the KV engine
|
// This file contains structures for serializing HTTP Responses (Res) and deserializing Requests (Req) for the KV engine
|
||||||
|
|
@ -48,7 +50,7 @@ impl KvSecretRes {
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct ErrorStruct {
|
pub struct ErrorStruct {
|
||||||
pub err: String
|
pub err: String,
|
||||||
}
|
}
|
||||||
impl ErrorStruct {
|
impl ErrorStruct {
|
||||||
pub fn into_response(self) -> Response<Body> {
|
pub fn into_response(self) -> Response<Body> {
|
||||||
|
|
@ -56,3 +58,55 @@ impl ErrorStruct {
|
||||||
(StatusCode::NOT_FOUND, body).into_response()
|
(StatusCode::NOT_FOUND, body).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
/// HTTP Response to Reading a Secret metadata
|
||||||
|
/// Container of [`KvMetaResData`]
|
||||||
|
pub struct KvMetaRes {
|
||||||
|
pub data: KvMetaResData,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for KvMetaRes {
|
||||||
|
fn default() -> Self {
|
||||||
|
let now = Utc::now();
|
||||||
|
Self {
|
||||||
|
data: KvMetaResData {
|
||||||
|
cas_required: false,
|
||||||
|
created_time: now,
|
||||||
|
delete_version_after: Some("".to_string()),
|
||||||
|
max_versions: 0,
|
||||||
|
updated_time: now,
|
||||||
|
custom_metadata: Some(HashMap::new()),
|
||||||
|
current_version: 0,
|
||||||
|
oldest_version: 0,
|
||||||
|
|
||||||
|
versions: HashMap::new(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
/// Metadata concerning a specific secret version
|
||||||
|
/// contained by [KvMetaRes]
|
||||||
|
pub struct KvMetaResVersionData {
|
||||||
|
pub created_time: DateTime<Utc>,
|
||||||
|
pub deletion_time: DateTime<Utc>,
|
||||||
|
pub destroyed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
/// contained by [KvMetaRes]
|
||||||
|
pub struct KvMetaResData {
|
||||||
|
pub cas_required: bool,
|
||||||
|
pub created_time: DateTime<Utc>,
|
||||||
|
pub current_version: i64,
|
||||||
|
pub delete_version_after: Option<String>,
|
||||||
|
pub max_versions: i64,
|
||||||
|
pub oldest_version: i64,
|
||||||
|
pub updated_time: DateTime<Utc>,
|
||||||
|
// pub custom_metadata: Option<String>, // TODO as hashmap
|
||||||
|
pub custom_metadata: Option<HashMap<String, String>>,
|
||||||
|
pub versions: HashMap<i64, KvMetaResVersionData>,
|
||||||
|
// the key to a version is the version number
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue