Skip to content

Commit

Permalink
feat: add support for actor fields
Browse files Browse the repository at this point in the history
  • Loading branch information
HoKim98 committed Nov 14, 2024
1 parent e06a8b3 commit 5e36887
Show file tree
Hide file tree
Showing 21 changed files with 611 additions and 106 deletions.
2 changes: 1 addition & 1 deletion Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ run *ARGS: ( _trunk "serve" ARGS )
run-examples *ARGS: ( _trunk "serve" "--features" "examples,full" ARGS )

run-gateway *ARGS:
cargo watch -s 'clear && cargo run --package cassette-gateway -- {{ ARGS }}'
cargo watch -s 'clear && cargo run --package cassette-gateway --features "unsafe-mock" -- {{ ARGS }}'

run-operator *ARGS:
cargo watch -s 'clear && cargo run --package cassette-operator -- {{ ARGS }}'
Expand Down
1 change: 1 addition & 0 deletions crates/cassette-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ schemars = { workspace = true, features = ["derive"] }
serde = { workspace = true, features = ["derive", "rc"] }
serde_json = { workspace = true }
strum = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
tracing = { workspace = true, optional = true }
uuid = { workspace = true }
wasm-streams = { workspace = true, optional = true }
Expand Down
30 changes: 27 additions & 3 deletions crates/cassette-core/src/cassette.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,10 @@ where

#[cfg(feature = "ui")]
impl<T> GenericCassetteTaskHandle<T> for UseStateHandle<T> {
type Ref<'a> = &'a T where T: 'a;
type Ref<'a>
= &'a T
where
T: 'a;

fn get<'a>(&'a self) -> <Self as GenericCassetteTaskHandle<T>>::Ref<'a>
where
Expand Down Expand Up @@ -427,7 +430,10 @@ where

#[cfg(feature = "ui")]
impl<T> GenericCassetteTaskHandle<T> for CassetteTaskHandle<T> {
type Ref<'a> = &'a T where T: 'a;
type Ref<'a>
= &'a T
where
T: 'a;

fn get<'a>(&'a self) -> <Self as GenericCassetteTaskHandle<T>>::Ref<'a>
where
Expand Down Expand Up @@ -485,6 +491,21 @@ impl<T> CassetteTaskHandle<Vec<T>> {
}
}

#[cfg(feature = "ui")]
impl CassetteTaskHandle<::serde_json::Value> {
pub fn get_item(&self, path: &crate::data::actor::SchemaPath) -> &::serde_json::Value {
path.get(self.get())
}

pub fn set_item(&self, path: &crate::data::actor::SchemaPath, value: ::serde_json::Value) {
if *self.get_item(path) != value {
let mut target = self.get().clone();
path.set(&mut target, value);
self.set(target)
}
}
}

#[cfg(feature = "ui")]
#[derive(Debug)]
pub struct CassetteLazyHandle<T>(CassetteTaskHandle<T>);
Expand All @@ -508,7 +529,10 @@ where

#[cfg(feature = "ui")]
impl<T> GenericCassetteTaskHandle<T> for CassetteLazyHandle<T> {
type Ref<'a> = &'a T where T: 'a;
type Ref<'a>
= &'a T
where
T: 'a;

fn get<'a>(&'a self) -> <Self as GenericCassetteTaskHandle<T>>::Ref<'a>
where
Expand Down
11 changes: 0 additions & 11 deletions crates/cassette-core/src/data/actor.rs

This file was deleted.

8 changes: 8 additions & 0 deletions crates/cassette-core/src/data/actor/boolean.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SchemaSpec {
#[serde(default)]
pub default: Option<bool>,
}
204 changes: 204 additions & 0 deletions crates/cassette-core/src/data/actor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
pub mod boolean;
pub mod number;
pub mod string;

use std::{fmt, str::FromStr};

use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::{Map, Value};
use thiserror::Error;

#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SchemaActor {
#[serde(default)]
pub create: Option<SchemaArray>,

#[serde(default)]
pub update: Option<SchemaArray>,
}

#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct SchemaArray(pub Vec<Schema>);

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Schema {
pub name: String,
pub path: SchemaPath,
#[serde(flatten)]
pub ty: SchemaType,
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum SchemaType {
Boolean(self::boolean::SchemaSpec),
Number(self::number::SchemaSpec),
String(self::string::SchemaSpec),
}

#[derive(Clone, Debug, Default, PartialEq)]
pub struct SchemaPath(pub Vec<SchemaPathItem>);

impl FromStr for SchemaPath {
type Err = SchemaPathParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if !s.starts_with("/") {
return Err(SchemaPathParseError::NoRootSlashPrefix);
}

Ok(Self(
s[1..]
.split('/')
// ('~1' to '/') => ('~0' to '~') => keep
// Please see: https://datatracker.ietf.org/doc/html/rfc6901#section-4
.map(|item| item.trim().replace("~1", "/").replace("~0", "~"))
.filter(|item| !item.is_empty())
.map(|item| {
item.parse()
.map(SchemaPathItem::List)
.unwrap_or_else(|_| SchemaPathItem::Object(item.into()))

Check failure on line 63 in crates/cassette-core/src/data/actor/mod.rs

View workflow job for this annotation

GitHub Actions / clippy-server

useless conversion to the same type: `std::string::String`

Check failure on line 63 in crates/cassette-core/src/data/actor/mod.rs

View workflow job for this annotation

GitHub Actions / clippy-app

useless conversion to the same type: `std::string::String`
})
.collect(),
))
}
}

impl fmt::Display for SchemaPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
"/".fmt(f)?;
for (index, item) in self.0.iter().enumerate() {
if index > 0 {
"/".fmt(f)?;
}
item.fmt(f)?;
}
Ok(())
}
}

impl Serialize for SchemaPath {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_string().serialize(serializer)
}
}

impl<'de> Deserialize<'de> for SchemaPath {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct SchemaPathVisitor;

impl<'de> de::Visitor<'de> for SchemaPathVisitor {
type Value = SchemaPath;

#[inline]
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
v.parse().map_err(E::custom)
}

fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "a json path")
}
}

deserializer.deserialize_string(SchemaPathVisitor)
}
}

impl SchemaPath {
pub fn get<'a>(&self, target: &'a Value) -> &'a Value {
let mut target = target;
for item in &self.0 {
target = match item {
&SchemaPathItem::List(i) => match target {
Value::Array(children) => match children.get(i) {
Some(child) => child,
None => return &Value::Null,
},
Value::Object(children) => match children.get(&i.to_string()) {
Some(child) => child,
None => return &Value::Null,
},
_ => return &Value::Null,
},
SchemaPathItem::Object(i) => match target {
Value::Object(children) => match children.get(i) {
Some(child) => child,
None => return &Value::Null,
},
_ => return &Value::Null,
},
};
}
target
}

fn get_mut<'a>(&self, target: &'a mut Value) -> &'a mut Value {
let mut target = target;
for item in &self.0 {
target = match item {
&SchemaPathItem::List(i) => match target {
Value::Array(children) => {
if i >= children.len() {
children.resize(i + 1, Value::Null);
}
&mut children[i]
}
Value::Object(children) => children.entry(i.to_string()).or_insert(Value::Null),
_ => {
*target = Value::Array(vec![Value::Null; i + 1]);
&mut target[i]
}
},
SchemaPathItem::Object(i) => match target {
Value::Object(children) => children.entry(i.clone()).or_insert(Value::Null),
_ => {
let mut map = Map::default();
map.insert(i.clone(), Value::Null);
*target = Value::Object(map);
target.get_mut(&i).unwrap()

Check failure on line 171 in crates/cassette-core/src/data/actor/mod.rs

View workflow job for this annotation

GitHub Actions / clippy-server

the borrowed expression implements the required traits

Check failure on line 171 in crates/cassette-core/src/data/actor/mod.rs

View workflow job for this annotation

GitHub Actions / clippy-app

the borrowed expression implements the required traits
}
},
};
}
target
}

pub fn set(&self, target: &mut Value, value: Value) {
*self.get_mut(target) = value
}
}

#[derive(Clone, Debug, PartialEq, Error)]
pub enum SchemaPathParseError {
#[error("No root slash(/) prefix")]
NoRootSlashPrefix,
}

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(untagged)]
pub enum SchemaPathItem {
List(usize),
Object(String),
}

impl fmt::Display for SchemaPathItem {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SchemaPathItem::List(value) => value.fmt(f),
SchemaPathItem::Object(value) => value.fmt(f),
}
}
}
9 changes: 9 additions & 0 deletions crates/cassette-core/src/data/actor/number.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
use serde_json::Number;

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SchemaSpec {
#[serde(default)]
pub default: Option<Number>,
}
7 changes: 7 additions & 0 deletions crates/cassette-core/src/data/actor/string.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct SchemaSpec {
#[serde(default)]
pub default: Option<String>,
}
3 changes: 2 additions & 1 deletion crates/cassette-gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ default = ["default-tls", "full", "openark"]
full = ["helm", "kubernetes"]
experimental = []
openark = ["vine"]
unsafe-mock = ["cassette-plugin-kubernetes-api?/unsafe-mock"]

# TLS
default-tls = ["rustls-tls"]
Expand Down Expand Up @@ -60,7 +61,7 @@ helm = ["dep:cassette-plugin-helm-api", "dep:reqwest"]
kubernetes = ["dep:cassette-plugin-kubernetes-api"]

[dependencies]
cassette-core = { path = "../cassette-core" }
cassette-core = { path = "../cassette-core", features = ["api"] }
cassette-loader-core = { path = "../cassette-loader-core" }
cassette-plugin-cdl-api = { path = "../cassette-plugin-cdl-api", optional = true }
cassette-plugin-helm-api = { path = "../cassette-plugin-helm-api", optional = true }
Expand Down
12 changes: 6 additions & 6 deletions crates/cassette-plugin-helm-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use cassette_core::data::actor::JsonSchemaActor;
use schemars::{schema_for, JsonSchema};
use cassette_core::data::actor::SchemaActor;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_json::Value;

pub fn actor() -> JsonSchemaActor {
JsonSchemaActor {
create: Some(schema_for!(HelmPut)),
update: Some(schema_for!(HelmPost)),
pub fn actor() -> SchemaActor {
SchemaActor {
create: None,
update: None,
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/cassette-plugin-kubernetes-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ workspace = true
[features]
default = []
vine = ["cassette-plugin-kubernetes-core/vine", "dep:vine-api", "dep:vine-rbac"]
unsafe-mock = []

[dependencies]
cassette-core = { path = "../cassette-core", features = ["api"] }
Expand Down
Loading

0 comments on commit 5e36887

Please sign in to comment.