diff --git a/Cargo.lock b/Cargo.lock index a422ee3..12d872b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -483,6 +483,29 @@ dependencies = [ ] [[package]] +name = "dlopen2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bc2c7ed06fd72a8513ded8d0d2f6fd2655a85d6885c48cae8625d80faf28c03" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] name = "dotenvy" version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -549,6 +572,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] +name = "example-plugin" +version = "0.1.0" +dependencies = [ + "anyhow", + "dlopen2", + "giterated-models", + "giterated-plugin", + "giterated-plugin-sys", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -793,6 +830,20 @@ dependencies = [ ] [[package]] +name = "giterated-issues" +version = "0.1.0" +dependencies = [ + "anyhow", + "giterated-models", + "giterated-plugin", + "giterated-plugin-sys", + "serde", + "sqlx", + "thiserror", + "tokio", +] + +[[package]] name = "giterated-models" version = "0.1.0" dependencies = [ @@ -817,6 +868,28 @@ dependencies = [ ] [[package]] +name = "giterated-plugin" +version = "0.1.0" +dependencies = [ + "anyhow", + "dlopen2", + "giterated-models", + "semver", + "serde_json", + "thiserror", + "tracing", +] + +[[package]] +name = "giterated-plugin-sys" +version = "0.1.0" +dependencies = [ + "giterated-models", + "giterated-plugin", + "tracing", +] + +[[package]] name = "giterated-protocol" version = "0.1.0" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index 25943fc..b1a630d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,5 +4,9 @@ members = [ "giterated-models", "giterated-stack", "giterated-cache", - "giterated-protocol" + "giterated-protocol", + "giterated-plugins/giterated-plugin", + "giterated-plugins/giterated-plugin-sys", + "giterated-plugins/example-plugin", + "giterated-plugins/giterated-issues" ] \ No newline at end of file diff --git a/giterated-daemon/migrations/20231104175218_create_issues.sql b/giterated-daemon/migrations/20231104175218_create_issues.sql new file mode 100644 index 0000000..aba79fa --- /dev/null +++ b/giterated-daemon/migrations/20231104175218_create_issues.sql @@ -0,0 +1,25 @@ +CREATE TYPE comment_visibility AS ENUM +( + 'public', + 'maintainers', + 'private' +); + +CREATE TABLE IF NOT EXISTS issues +( + id SERIAL PRIMARY KEY, + repository TEXT NOT NULL, + author TEXT NOT NULL, + creation_date INTEGER NOT NULL, + issue_name TEXT NOT NULL, + contents TEXT +); + +CREATE TABLE IF NOT EXISTS issue_comments +( + id SERIAL PRIMARY KEY, + issue INTEGER NOT NULL REFERENCES issues(id), + author TEXT NOT NULL, + visibility comment_visibility NOT NULL, + contents TEXT +); \ No newline at end of file diff --git a/giterated-daemon/src/database_backend/mod.rs b/giterated-daemon/src/database_backend/mod.rs index 7c55f7a..26e2ee5 100644 --- a/giterated-daemon/src/database_backend/mod.rs +++ b/giterated-daemon/src/database_backend/mod.rs @@ -7,7 +7,9 @@ use std::sync::Arc; use anyhow::Context; use giterated_models::instance::Instance; -use giterated_models::repository::{DefaultBranch, Description, Repository, Visibility, CommitBodyType}; +use giterated_models::repository::{ + CommitBodyType, DefaultBranch, Description, Repository, Visibility, +}; use giterated_models::user::{Bio, DisplayName, User}; use giterated_stack::provider::MetadataProvider; use giterated_stack::{AnyObject, AnySetting, GiteratedStack, ObjectMeta, SubstackBuilder}; diff --git a/giterated-models/src/object/operations.rs b/giterated-models/src/object/operations.rs index 51d13b2..6de5011 100644 --- a/giterated-models/src/object/operations.rs +++ b/giterated-models/src/object/operations.rs @@ -16,6 +16,10 @@ impl GiteratedOperation for ObjectRequest { type Success = ObjectResponse; type Failure = ObjectRequestError; + + fn operation_name() -> &'static str { + "object_request" + } } #[derive(Debug, Clone, thiserror::Error, Serialize, Deserialize)] diff --git a/giterated-models/src/repository/mod.rs b/giterated-models/src/repository/mod.rs index cd5f87b..71bbc4c 100644 --- a/giterated-models/src/repository/mod.rs +++ b/giterated-models/src/repository/mod.rs @@ -69,11 +69,17 @@ impl GiteratedObject for Repository { } } -impl TryFrom for Repository { - type Error = RepositoryParseError; +// impl TryFrom for Repository { +// type Error = RepositoryParseError; - fn try_from(value: String) -> Result { - Self::from_str(&value) +// fn try_from(value: String) -> Result { +// Self::from_str(&value) +// } +// } + +impl From for Repository { + fn from(value: String) -> Self { + Repository::from_str(&value).unwrap() } } diff --git a/giterated-models/src/repository/settings.rs b/giterated-models/src/repository/settings.rs index 04a97ae..b827579 100644 --- a/giterated-models/src/repository/settings.rs +++ b/giterated-models/src/repository/settings.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use crate::{settings::Setting, user::User}; -use super::{DefaultBranch, CommitBodyType}; +use super::{CommitBodyType, DefaultBranch}; impl Setting for DefaultBranch { fn name() -> &'static str { diff --git a/giterated-models/src/repository/values.rs b/giterated-models/src/repository/values.rs index a8d73e3..9b10fd0 100644 --- a/giterated-models/src/repository/values.rs +++ b/giterated-models/src/repository/values.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use crate::{settings::Setting, value::GiteratedObjectValue}; -use super::{Commit, Repository, RepositoryVisibility, CommitBodyType}; +use super::{Commit, CommitBodyType, Repository, RepositoryVisibility}; // pub struct RepositorySetting(pub V); diff --git a/giterated-plugins/README.md b/giterated-plugins/README.md new file mode 100644 index 0000000..970f289 --- /dev/null +++ b/giterated-plugins/README.md @@ -0,0 +1,3 @@ +# Plugins + +Giterated uses a Plugin system to allow for easy extension, but also to separate concerns in the core. \ No newline at end of file diff --git a/giterated-plugins/example-plugin/Cargo.toml b/giterated-plugins/example-plugin/Cargo.toml new file mode 100644 index 0000000..1d1958a --- /dev/null +++ b/giterated-plugins/example-plugin/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "example-plugin" +version = "0.1.0" +edition = "2021" + +[lib] +name = "example_plugin_dylib" +path = "src/lib.rs" +crate-type = ["dylib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +giterated-plugin = { path = "../giterated-plugin" } +giterated-plugin-sys = { path = "../giterated-plugin-sys" } +dlopen2 = "0.6" +tracing-subscriber = "0.3" +giterated-models = { path = "../../giterated-models" } +tracing = "0.1" +serde_json = "1.0" +anyhow = "1" diff --git a/giterated-plugins/example-plugin/src/lib.rs b/giterated-plugins/example-plugin/src/lib.rs new file mode 100644 index 0000000..b305ea8 --- /dev/null +++ b/giterated-plugins/example-plugin/src/lib.rs @@ -0,0 +1,95 @@ +use std::sync::OnceLock; + +use giterated_models::{ + error::OperationError, + instance::Instance, + object::ObjectRequest, + user::{DisplayName, User}, +}; +use giterated_plugin::{ + handle::PluginInitializationState, + new_stack::{FFIPluginMeta, PluginState}, + HostVTable, InitializationVTable, +}; +use giterated_plugin_sys::PluginStackBuilder; +use tracing::{info, trace_span, Level}; + +static INIT_VTABLE: OnceLock = OnceLock::new(); + +#[no_mangle] +pub extern "C" fn plugin_meta() -> FFIPluginMeta { + const PLUGIN_NAME: &str = "Example Plugin"; + const PLUGIN_VERSION: &str = "1.0.0"; + + FFIPluginMeta { + name: PLUGIN_NAME.as_ptr(), + name_len: PLUGIN_NAME.len(), + version: PLUGIN_VERSION.as_ptr(), + version_len: PLUGIN_VERSION.len(), + } +} + +#[no_mangle] +pub extern "C" fn load_host_vtable(_vtable: &HostVTable) { + println!("Loading vtable"); +} + +#[no_mangle] +pub extern "C" fn load_initialization_vtable(init_vtable: &InitializationVTable) { + INIT_VTABLE.set(init_vtable.clone()).unwrap(); + println!("Loaded initialization vtable"); +} + +#[no_mangle] +pub extern "C" fn initialize() -> PluginState { + tracing_subscriber::fmt() + .pretty() + .with_thread_names(true) + .with_max_level(Level::TRACE) + .init(); + + PluginState { + inner: Box::into_raw(Box::new(())), + } +} + +#[no_mangle] +pub extern "C" fn initialize_registration( + state: *mut PluginInitializationState, +) -> *mut PluginInitializationState { + let _guard: tracing::span::EnteredSpan = trace_span!("initialize_registration").entered(); + let init_vtable = INIT_VTABLE.get().unwrap(); + let mut builder = PluginStackBuilder::new((), state, init_vtable); + + builder.object::().object::(); + builder.operation_handler(handler); + builder.value(value_getter); + // builder.setting_getter(setting_getter); + + state +} + +async fn handler( + state: (), + _object: Instance, + _operation: ObjectRequest, +) -> Result<(), OperationError<()>> { + info!("handling operation!"); + + todo!() +} + +async fn value_getter( + state: (), + object: User, +) -> Result> { + info!("OwO, value gotten!"); + + Ok(DisplayName(String::from("heya!"))) +} + +// fn setting_getter(state: (), object: User) -> Result { +// info!("OwO, setting gotten!"); + +// Ok(DisplayName(String::from("heya! (but from a setting)"))) +// } diff --git a/giterated-plugins/example-plugin/src/main.rs b/giterated-plugins/example-plugin/src/main.rs new file mode 100644 index 0000000..c2240d1 --- /dev/null +++ b/giterated-plugins/example-plugin/src/main.rs @@ -0,0 +1,63 @@ +use dlopen2::wrapper::Container; +use giterated_models::{ + instance::Instance, + object::{GiteratedObject, ObjectRequest}, + operation::GiteratedOperation, + settings::{GetSetting, Setting}, + user::{DisplayName, User}, + value::GetValue, +}; +use giterated_plugin::{handle::PluginHandle, new_stack::Runtime, GiteratedPluginApi}; +use tracing::{info, Level}; + +fn main() { + tracing_subscriber::fmt() + .pretty() + .with_thread_names(true) + .with_max_level(Level::TRACE) + .init(); + + let mut handle = PluginHandle::from_dylib("example_plugin_dylib.dll").unwrap(); + + let mut runtime = Runtime::default(); + + runtime.insert_plugin(handle); + + let object_request = ObjectRequest(String::from("foobar")); + + match runtime.handle( + Instance::object_name(), + ObjectRequest::operation_name(), + "meow", + &serde_json::to_vec(&object_request).unwrap(), + ) { + Ok(success) => info!("handler call success!"), + Err(_) => info!("handler call error"), + } + + match runtime.handle( + User::object_name(), + >::operation_name(), + "amber:giterated.dev", + &serde_json::to_vec(&GetValue { + value_name: String::from("display_name"), + }) + .unwrap(), + ) { + Ok(success) => info!("get_value handler call success!"), + Err(_) => info!("get_value handler call error"), + } + + match runtime.handle( + User::object_name(), + >::operation_name(), + "amber:giterated.dev", + &serde_json::to_vec(&GetSetting { + setting_name: DisplayName::name().to_string(), + }) + .unwrap(), + ) { + Ok(success) => info!("get_setting handler call success!"), + Err(_) => info!("get_setting handler call error"), + } +} diff --git a/giterated-plugins/giterated-issues/Cargo.toml b/giterated-plugins/giterated-issues/Cargo.toml new file mode 100644 index 0000000..ea726ba --- /dev/null +++ b/giterated-plugins/giterated-issues/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "giterated-issues" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +giterated-plugin = { path = "../giterated-plugin" } +giterated-models = { path = "../../giterated-models" } +serde = { version = "1.0", features = [ "derive" ]} +anyhow = "1" +thiserror = "1" +sqlx = { version = "0.7", features = [ "runtime-tokio", "tls-native-tls", "postgres", "macros", "migrate", "chrono" ] } +tokio = { version = "1.32", features = [ "full" ] } +giterated-plugin-sys = { path = "../giterated-plugin-sys" } diff --git a/giterated-plugins/giterated-issues/src/db.rs b/giterated-plugins/giterated-issues/src/db.rs new file mode 100644 index 0000000..3471fd9 --- /dev/null +++ b/giterated-plugins/giterated-issues/src/db.rs @@ -0,0 +1,24 @@ +use giterated_models::{repository::Repository, user::User}; + +use crate::operations::CommentVisibility; + +/// An [`Issue`]'s database representation. +#[derive(Debug, sqlx::FromRow)] +pub struct IssueRow { + pub id: i32, + #[sqlx(try_from = "String")] + pub repository: Repository, + #[sqlx(try_from = "String")] + pub author: User, + pub creation_date: i32, + pub issue_name: String, + pub contents: Option, +} + +#[derive(Debug, sqlx::FromRow)] +pub struct IssueCommentRow { + #[sqlx(try_from = "String")] + pub author: User, + pub contents: Option, + pub visibility: CommentVisibility, +} diff --git a/giterated-plugins/giterated-issues/src/handlers.rs b/giterated-plugins/giterated-issues/src/handlers.rs new file mode 100644 index 0000000..4463009 --- /dev/null +++ b/giterated-plugins/giterated-issues/src/handlers.rs @@ -0,0 +1,118 @@ +use crate::db::IssueRow; +use crate::setting::Contents; +use crate::value::{Author, CommentCount, CreationDate, Name}; +use crate::IssuesPluginState; +use crate::{ + operations::{ + CreateIssueRequest, IssueCreationError, IssueEditError, IssueEditRequest, + IssuePostCommentError, IssuePostCommentRequest, IssueQueryError, QueryIssuesRequest, + }, + Issue, +}; +use giterated_models::error::IntoInternalError; +use giterated_models::user::User; +use giterated_models::{error::OperationError, repository::Repository}; +use sqlx::PgPool; + +pub async fn create_issue_request( + state: IssuesPluginState, + repository: Repository, + request: CreateIssueRequest, +) -> Result> { + // TODO: AUTHN & AUTHZ + let issue = sqlx::query_as!( + IssueRow, + r#"INSERT INTO issues VALUES (null, $1, $2, $3, $4, $5) RETURNING *"#, + repository.to_string(), + request.author.to_string(), + 0, + request.name, + request.contents, + ) + .fetch_one(&state.pool) + .await + .as_internal_error_with_context("creating issue in db")?; + + Ok(Issue { + repository, + id: issue.id as u32, + }) +} + +pub async fn query_issues_request( + state: IssuesPluginState, + repository: Repository, + request: QueryIssuesRequest, +) -> Result, OperationError> { + // TODO: AUTHN & AUTHZ + todo!() +} + +pub async fn edit_issue_request( + state: IssuesPluginState, + issue: Issue, + request: IssueEditRequest, +) -> Result<(), OperationError> { + // TODO: AUTHN & AUTHZ + todo!() +} + +pub async fn issue_post_comment_request( + state: IssuesPluginState, + issue: Issue, + request: IssuePostCommentRequest, +) -> Result> { + // TODO: AUTHN & AUTHZ + todo!() +} + +pub async fn issue_value_author( + state: IssuesPluginState, + issue: Issue, +) -> Result> { + todo!() +} + +pub async fn issue_value_creation_date( + state: IssuesPluginState, + issue: Issue, +) -> Result> { + todo!() +} + +pub async fn issue_value_comment_count( + state: IssuesPluginState, + issue: Issue, +) -> Result> { + todo!() +} + +pub async fn issue_set_setting_name( + state: IssuesPluginState, + issue: Issue, + name: Name, +) -> Result<(), OperationError> { + todo!() +} + +pub async fn issue_get_setting_name( + state: IssuesPluginState, + issue: Issue, +) -> Result> { + todo!() +} + +pub async fn issue_set_setting_contents( + state: IssuesPluginState, + issue: Issue, + contents: Contents, +) -> Result<(), OperationError> { + todo!() +} + +pub async fn issue_get_setting_contents( + state: IssuesPluginState, + issue: Issue, +) -> Result> { + todo!() +} diff --git a/giterated-plugins/giterated-issues/src/lib.rs b/giterated-plugins/giterated-issues/src/lib.rs new file mode 100644 index 0000000..ed01965 --- /dev/null +++ b/giterated-plugins/giterated-issues/src/lib.rs @@ -0,0 +1,152 @@ +use std::{fmt::Display, str::FromStr, sync::OnceLock}; + +use anyhow::Error; +use giterated_models::{object::GiteratedObject, repository::Repository}; +use giterated_plugin::{ + handle::PluginInitializationState, + new_stack::{FFIPluginMeta, PluginState}, + HostVTable, InitializationVTable, +}; +use giterated_plugin_sys::PluginStackBuilder; +use handlers::{ + create_issue_request, edit_issue_request, issue_get_setting_contents, issue_get_setting_name, + issue_post_comment_request, issue_set_setting_contents, issue_set_setting_name, + issue_value_author, issue_value_comment_count, issue_value_creation_date, query_issues_request, +}; +use serde::{Deserialize, Serialize}; +use setting::{Contents, NotificationsOverride}; +use sqlx::PgPool; +use value::{Author, CommentCount, CreationDate, Name}; + +pub mod db; +pub mod handlers; +pub mod operations; +pub mod setting; +pub mod value; + +/// An issue, defined by the repository which owns it and its index. +/// +/// # Textual Format +/// An issue's textual format is defined as: +/// +/// `@{index: u32}:{repository: Repository}` +#[derive(Hash, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct Issue { + pub repository: Repository, + pub id: u32, +} + +impl Display for Issue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{}:{}", self.id, self.repository)) + } +} + +impl GiteratedObject for Issue { + fn object_name() -> &'static str { + "issue" + } + + fn home_uri(&self) -> String { + self.repository.home_uri() + } + + fn from_object_str(object_str: &str) -> Result { + Ok(Issue::from_str(object_str)?) + } +} + +impl FromStr for Issue { + type Err = IssueParseError; + + fn from_str(s: &str) -> Result { + let (index, repository) = s.split_once(':').ok_or_else(|| IssueParseError)?; + + let id: u32 = index.parse().map_err(|_| IssueParseError)?; + let repository = Repository::from_str(repository).map_err(|_| IssueParseError)?; + + Ok(Self { repository, id }) + } +} + +#[derive(Debug, thiserror::Error)] +#[error("error parsing issue")] +pub struct IssueParseError; + +static INIT_VTABLE: OnceLock = OnceLock::new(); + +#[no_mangle] +pub extern "C" fn plugin_meta() -> FFIPluginMeta { + const PLUGIN_NAME: &str = "Giterated [Issues]"; + const PLUGIN_VERSION: &str = "0.1.0"; + + FFIPluginMeta { + name: PLUGIN_NAME.as_ptr(), + name_len: PLUGIN_NAME.len(), + version: PLUGIN_VERSION.as_ptr(), + version_len: PLUGIN_VERSION.len(), + } +} + +#[no_mangle] +pub extern "C" fn load_host_vtable(_vtable: &HostVTable) { + println!("Loading vtable"); +} + +#[no_mangle] +pub extern "C" fn load_initialization_vtable(init_vtable: &InitializationVTable) { + INIT_VTABLE.set(init_vtable.clone()).unwrap(); + println!("Loaded initialization vtable"); +} + +#[no_mangle] +pub extern "C" fn initialize() -> PluginState { + // tracing_subscriber::fmt() + // .pretty() + // .with_thread_names(true) + // .with_max_level(Level::TRACE) + // .init(); + + PluginState { + inner: Box::into_raw(Box::new(())), + } +} + +#[no_mangle] +pub extern "C" fn initialize_registration( + state: *mut PluginInitializationState, +) -> *mut PluginInitializationState { + // let _guard: tracing::span::EnteredSpan = trace_span!("initialize_registration").entered(); + let init_vtable = INIT_VTABLE.get().unwrap(); + + let plugin_state = { todo!() }; + + let mut builder = PluginStackBuilder::new(plugin_state, state, init_vtable); + + builder.object::(); + + builder + .object_setting(issue_get_setting_name, issue_set_setting_name) + .object_setting(issue_get_setting_contents, issue_set_setting_contents); + + builder.object_user_setting::(); + + builder + .value(issue_value_creation_date) + .value(issue_value_comment_count) + .value(issue_get_setting_name) + .value(issue_value_author); + + builder + .operation_handler(create_issue_request) + .operation_handler(query_issues_request) + .operation_handler(edit_issue_request) + .operation_handler(issue_post_comment_request); + + state +} + +#[derive(Clone, Debug)] +pub struct IssuesPluginState { + pub pool: PgPool, +} diff --git a/giterated-plugins/giterated-issues/src/operations.rs b/giterated-plugins/giterated-issues/src/operations.rs new file mode 100644 index 0000000..1978b94 --- /dev/null +++ b/giterated-plugins/giterated-issues/src/operations.rs @@ -0,0 +1,75 @@ +use giterated_models::{operation::GiteratedOperation, repository::Repository, user::User}; +use serde::{Deserialize, Serialize}; + +use crate::Issue; + +/// Create an [`Issue`] on a [`Repository`]. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CreateIssueRequest { + pub name: String, + pub contents: String, + pub author: User, +} + +impl GiteratedOperation for CreateIssueRequest { + type Success = Issue; + + type Failure = IssueCreationError; +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +#[error("failed to create issue")] +pub struct IssueCreationError; + +/// Query for [`Issue`]s on a [`Repository`]. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct QueryIssuesRequest {} + +impl GiteratedOperation for QueryIssuesRequest { + type Success = Vec; + + type Failure = IssueQueryError; +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +#[error("failed to query issues")] +pub struct IssueQueryError; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IssueEditRequest { + // Might not be needed :) +} + +impl GiteratedOperation for IssueEditRequest { + type Success = (); + + type Failure = IssueEditError; +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +#[error("failed to edit issue")] +pub struct IssueEditError; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct IssuePostCommentRequest { + pub contents: String, + pub visibility: CommentVisibility, +} + +impl GiteratedOperation for IssuePostCommentRequest { + type Success = u32; + + type Failure = IssuePostCommentError; +} + +#[derive(Clone, Debug, Serialize, Deserialize, thiserror::Error)] +#[error("failed to post comment")] +pub struct IssuePostCommentError; + +#[derive(PartialEq, Eq, Debug, Hash, Serialize, Deserialize, Clone, sqlx::Type)] +#[sqlx(type_name = "comment_visibility", rename_all = "lowercase")] +pub enum CommentVisibility { + Public, + Maintainers, + Private, +} diff --git a/giterated-plugins/giterated-issues/src/setting.rs b/giterated-plugins/giterated-issues/src/setting.rs new file mode 100644 index 0000000..0f3d08d --- /dev/null +++ b/giterated-plugins/giterated-issues/src/setting.rs @@ -0,0 +1,28 @@ +use giterated_models::settings::Setting; +use serde::{Deserialize, Serialize}; + +use crate::value::Name; + +impl Setting for Name { + fn name() -> &'static str { + "name" + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Contents(String); + +impl Setting for Contents { + fn name() -> &'static str { + "contents" + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct NotificationsOverride(pub bool); + +impl Setting for NotificationsOverride { + fn name() -> &'static str { + "notifications_override" + } +} diff --git a/giterated-plugins/giterated-issues/src/value.rs b/giterated-plugins/giterated-issues/src/value.rs new file mode 100644 index 0000000..553227f --- /dev/null +++ b/giterated-plugins/giterated-issues/src/value.rs @@ -0,0 +1,48 @@ +use giterated_models::{user::User, value::GiteratedObjectValue}; +use serde::{Deserialize, Serialize}; + +use crate::Issue; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CreationDate(pub String); + +impl GiteratedObjectValue for CreationDate { + type Object = Issue; + + fn value_name() -> &'static str { + "creation_date" + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CommentCount(pub u32); + +impl GiteratedObjectValue for CommentCount { + type Object = Issue; + + fn value_name() -> &'static str { + "comment_count" + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Name(pub String); + +impl GiteratedObjectValue for Name { + type Object = Issue; + + fn value_name() -> &'static str { + "name" + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Author(pub User); + +impl GiteratedObjectValue for Author { + type Object = Issue; + + fn value_name() -> &'static str { + "owner" + } +} diff --git a/giterated-plugins/giterated-plugin-sys/Cargo.toml b/giterated-plugins/giterated-plugin-sys/Cargo.toml new file mode 100644 index 0000000..adc4334 --- /dev/null +++ b/giterated-plugins/giterated-plugin-sys/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "giterated-plugin-sys" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +giterated-plugin = { path = "../giterated-plugin" } +tracing = "0.1" +giterated-models = { path = "../../giterated-models" } \ No newline at end of file diff --git a/giterated-plugins/giterated-plugin-sys/src/lib.rs b/giterated-plugins/giterated-plugin-sys/src/lib.rs new file mode 100644 index 0000000..436daa0 --- /dev/null +++ b/giterated-plugins/giterated-plugin-sys/src/lib.rs @@ -0,0 +1,211 @@ +use std::sync::OnceLock; + +use giterated_models::{ + object::GiteratedObject, operation::GiteratedOperation, settings::Setting, + value::GiteratedObjectValue, +}; +use giterated_plugin::{ + callback::{ + IntoPluginOperationHandler, IntoPluginSettingGetter, IntoPluginSettingSetter, + IntoPluginValueGetter, OperationHandlerCallback, SettingGetterCallback, + ValueGetterCallback, + }, + handle::PluginInitializationState, + InitializationVTable, IntoObjectVTable, IntoOperationVTable, IntoSettingVTable, + IntoValueVTable, ObjectVtable, OperationVTable, SettingVtable, ValueVTable, +}; +use tracing::trace_span; + +pub struct PluginStackBuilder<'init, S> { + init_state: *mut PluginInitializationState, + vtable: &'init InitializationVTable, + state: S, +} + +impl<'init, S> PluginStackBuilder<'init, S> { + pub fn new( + plugin_state: S, + state: *mut PluginInitializationState, + vtable: &'init InitializationVTable, + ) -> Self { + Self { + init_state: state, + vtable, + state: plugin_state, + } + } + + pub fn object(&mut self) -> &mut Self { + let _guard = trace_span!("register object").entered(); + + let func = self.vtable.register_object; + + unsafe { func(self.init_state, O::object_name(), ObjectVtable::new::()) }; + + self + } + + pub fn operation(&mut self) -> &mut Self + where + D: IntoOperationVTable + GiteratedOperation, + O: GiteratedObject, + { + let _guard = trace_span!("register operation").entered(); + + unsafe { + (self.vtable.register_operation)( + self.init_state, + O::object_name(), + D::operation_name(), + OperationVTable::new::(), + ) + } + + self + } + + pub fn object_setting(&mut self, get: HG, set: HS) -> &mut Self + where + O: GiteratedObject, + OS: IntoSettingVTable + Setting, + HG: IntoPluginSettingGetter, + HS: IntoPluginSettingSetter, + { + let _guard = trace_span!("register setting").entered(); + + unsafe { + (self.vtable.register_setting)( + self.init_state, + O::object_name(), + OS::name(), + SettingVtable::new::(), + ) + } + + self + } + + pub fn object_user_setting(&mut self) -> &mut Self + where + O: GiteratedObject, + OS: IntoSettingVTable + Setting, + { + let _guard = trace_span!("register setting").entered(); + + unsafe { + (self.vtable.register_setting)( + self.init_state, + O::object_name(), + OS::name(), + SettingVtable::new::(), + ) + } + + self + } + + pub fn value(&mut self, handler: T) -> &mut Self + where + O: GiteratedObject, + V: IntoValueVTable + GiteratedObjectValue, + T: IntoPluginValueGetter, + { + let _guard = trace_span!("register value").entered(); + + unsafe { + (self.vtable.register_value)( + self.init_state, + O::object_name(), + V::value_name(), + ValueVTable::new::(), + ) + } + + unsafe { + (self.vtable.value_getter)( + self.init_state, + O::object_name(), + V::value_name(), + ValueGetterCallback::new::(handler), + ) + } + + self + } + + pub fn operation_handler< + DS, + DF, + O: GiteratedObject + IntoObjectVTable, + D: IntoOperationVTable + GiteratedOperation, + T: IntoPluginOperationHandler, + >( + &mut self, + handler: T, + ) -> &mut Self { + let _guard = trace_span!("register operation handler").entered(); + + unsafe { + (self.vtable.operation_handler)( + self.init_state, + O::object_name(), + D::operation_name(), + OperationHandlerCallback::new::(handler), + ) + } + + // TODO: Yikes? + self.object::(); + + self.operation::(); + + self + } + + // pub fn value_getter(&mut self, handler: T) -> &mut Self + // where + // O: GiteratedObject + IntoObjectVTable, + // V: GiteratedObjectValue + IntoValueVTable, + // T: IntoPluginValueGetter, + // { + // let _guard = trace_span!("register value_getter handler").entered(); + + // unsafe { + // (self.vtable.value_getter)( + // self.init_state, + // O::object_name(), + // V::value_name(), + // ValueGetterCallback::new::(handler), + // ) + // } + + // // TODO: Yikes? + // self.object::(); + // self.value::(); + + // self + // } + + // pub fn setting_getter(&mut self, handler: T) -> &mut Self + // where + // O: GiteratedObject + IntoObjectVTable, + // OS: Setting + IntoSettingVTable, + // T: IntoPluginSettingGetter, + // { + // let _guard = trace_span!("register setting_getter handler").entered(); + + // unsafe { + // (self.vtable.setting_getter)( + // self.init_state, + // O::object_name(), + // OS::name(), + // SettingGetterCallback::new::(handler), + // ) + // } + + // self.object::(); + // // self.setting::(); + + // self + // } +} diff --git a/giterated-plugins/giterated-plugin/Cargo.toml b/giterated-plugins/giterated-plugin/Cargo.toml new file mode 100644 index 0000000..6e6f1cb --- /dev/null +++ b/giterated-plugins/giterated-plugin/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "giterated-plugin" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dlopen2 = "0.6" +anyhow = "1" +thiserror = "1" +tracing = "0.1" +giterated-models = { path = "../../giterated-models" } +semver = "*" +serde_json = "1.0" \ No newline at end of file diff --git a/giterated-plugins/giterated-plugin/src/callback/mod.rs b/giterated-plugins/giterated-plugin/src/callback/mod.rs new file mode 100644 index 0000000..34afa9e --- /dev/null +++ b/giterated-plugins/giterated-plugin/src/callback/mod.rs @@ -0,0 +1,18 @@ +mod operation; +pub use operation::*; +mod value; +pub use value::*; +mod setting; +pub use setting::*; + +/// A container for a callback pointer, used to provide an internal callback function or +/// state to a plugin when performing a callback. +#[derive(Clone, Copy)] +#[repr(C)] +pub struct CallbackPtr(*const ()); + +impl CallbackPtr { + pub unsafe fn from_raw(callback: *const ()) -> Self { + Self(callback) + } +} diff --git a/giterated-plugins/giterated-plugin/src/callback/operation.rs b/giterated-plugins/giterated-plugin/src/callback/operation.rs new file mode 100644 index 0000000..81265f6 --- /dev/null +++ b/giterated-plugins/giterated-plugin/src/callback/operation.rs @@ -0,0 +1,75 @@ +use giterated_models::error::OperationError; + +use crate::{new_stack::PluginState, AnyObject, AnyOperation}; + +use std::{any::type_name, fmt::Debug, future::Future}; + +use super::CallbackPtr; + +#[derive(Clone, Copy)] +pub struct OperationHandlerCallback { + pub callback_ptr: CallbackPtr, + pub func: + unsafe extern "C" fn(CallbackPtr, &PluginState, object: AnyObject, operation: AnyOperation), +} + +impl OperationHandlerCallback { + pub fn new>( + handler: T, + ) -> Self { + OperationHandlerCallback { + func: T::handle, + callback_ptr: T::callback_ptr(&handler), + } + } +} + +pub trait IntoPluginOperationHandler { + unsafe extern "C" fn handle( + callback_ptr: CallbackPtr, + state: &PluginState, + object: AnyObject, + operation: AnyOperation, + ); + fn callback_ptr(&self) -> CallbackPtr; +} + +impl IntoPluginOperationHandler for F +where + Fut: Future>>, + F: Fn(S, O, D) -> Fut, + S: Clone + Debug, + O: Debug, + D: Debug, +{ + unsafe extern "C" fn handle( + callback: CallbackPtr, + state: &PluginState, + mut object: AnyObject, + mut operation: AnyOperation, + ) { + let _guard = trace_span!( + "operation handler", + object = type_name::(), + operation = type_name::() + ) + .entered(); + let state = unsafe { state.transmute_ref::() }; + + // Since this is Rust code, we know that the AnyObject and AnyOperation are just boxes + let object = unsafe { object.transmute_owned::() }; + let operation = unsafe { operation.transmute_owned::() }; + + // Cast the callback ptr to ourselves + let callback: *const F = std::mem::transmute(callback.0); + let callback = callback.as_ref().unwrap(); + + // callback(state.clone(), *object, *operation) + + todo!() + } + + fn callback_ptr(&self) -> CallbackPtr { + unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } + } +} diff --git a/giterated-plugins/giterated-plugin/src/callback/setting.rs b/giterated-plugins/giterated-plugin/src/callback/setting.rs new file mode 100644 index 0000000..21e368d --- /dev/null +++ b/giterated-plugins/giterated-plugin/src/callback/setting.rs @@ -0,0 +1,168 @@ +use std::future::Future; + +use giterated_models::{ + error::OperationError, + object::GiteratedObject, + settings::{AnySetting, Setting}, +}; + +use crate::{new_stack::PluginState, AnyObject, NewAnySetting}; + +use super::CallbackPtr; + +#[derive(Clone, Copy)] +pub struct SettingGetterCallback { + pub callback_ptr: CallbackPtr, + pub func: unsafe extern "C" fn( + CallbackPtr, + &PluginState, + object: AnyObject, + ) -> Result, +} + +impl SettingGetterCallback { + pub fn new>(callback: T) -> Self { + Self { + func: T::get_setting, + callback_ptr: callback.callback_ptr(), + } + } +} + +pub trait IntoPluginSettingGetter { + unsafe extern "C" fn get_setting( + callback_ptr: CallbackPtr, + state: &PluginState, + object: AnyObject, + ) -> Result; + + fn callback_ptr(&self) -> CallbackPtr { + unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } + } +} + +impl IntoPluginSettingGetter for F +where + Fut: Future>>, + S: Clone, + O: GiteratedObject, + OS: Setting, + F: Fn(S, O) -> Fut, +{ + unsafe extern "C" fn get_setting( + callback: CallbackPtr, + state: &PluginState, + mut object: AnyObject, + ) -> Result { + let _guard = trace_span!( + "get_setting handler", + object = O::object_name(), + setting = OS::name() + ) + .entered(); + let state = unsafe { state.transmute_ref::() }; + + let object = unsafe { object.transmute_owned::() }; + + // Cast the callback ptr to ourselves + let callback: *const F = std::mem::transmute(callback.0); + let callback = callback.as_ref().unwrap(); + + // let result = callback(state.clone(), *object); + + // match result { + // Ok(setting) => Ok(NewAnySetting::new(setting)), + // Err(_) => todo!(), + // } + + todo!() + } +} + +pub trait IntoPluginSettingSetter { + unsafe extern "C" fn set_setting( + callback_ptr: CallbackPtr, + state: &PluginState, + object: AnyObject, + setting: AnySetting, + ) -> Result<(), ()>; + + fn callback_ptr(&self) -> CallbackPtr { + unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } + } +} + +impl IntoPluginSettingSetter for F +where + Fut: Future>>, + S: Clone, + O: GiteratedObject, + OS: Setting, + F: Fn(S, O, OS) -> Fut, +{ + unsafe extern "C" fn set_setting( + callback: CallbackPtr, + state: &PluginState, + mut object: AnyObject, + setting: AnySetting, + ) -> Result<(), ()> { + let _guard = trace_span!( + "get_setting handler", + object = O::object_name(), + setting = OS::name() + ) + .entered(); + let state = unsafe { state.transmute_ref::() }; + + let object = unsafe { object.transmute_owned::() }; + + // Cast the callback ptr to ourselves + let callback: *const F = std::mem::transmute(callback.0); + let callback = callback.as_ref().unwrap(); + + // let result = callback(state.clone(), *object); + + // match result { + // Ok(setting) => Ok(NewAnySetting::new(setting)), + // Err(_) => todo!(), + // } + todo!() + } +} + +pub struct SettingChangeCallback { + func: unsafe extern "C" fn( + &PluginState, + object: AnyObject, + setting_name: &str, + new_setting: NewAnySetting, + ), +} + +pub trait IntoSettingChangeCallback { + unsafe extern "C" fn setting_changed( + state: &PluginState, + object: AnyObject, + setting_name: &str, + new_setting: NewAnySetting, + ); +} + +impl IntoSettingChangeCallback for F { + unsafe extern "C" fn setting_changed( + state: &PluginState, + object: AnyObject, + setting_name: &str, + new_setting: NewAnySetting, + ) { + todo!() + } +} + +impl SettingChangeCallback { + pub fn new>() -> Self { + Self { + func: T::setting_changed, + } + } +} diff --git a/giterated-plugins/giterated-plugin/src/callback/value.rs b/giterated-plugins/giterated-plugin/src/callback/value.rs new file mode 100644 index 0000000..d61217b --- /dev/null +++ b/giterated-plugins/giterated-plugin/src/callback/value.rs @@ -0,0 +1,117 @@ +use std::future::Future; + +use giterated_models::{ + error::OperationError, object::GiteratedObject, value::GiteratedObjectValue, +}; + +use crate::{new_stack::PluginState, AnyObject, NewAnyValue}; + +use super::CallbackPtr; + +#[derive(Copy, Clone)] +pub struct ValueGetterCallback { + pub callback_ptr: CallbackPtr, + pub func: unsafe extern "C" fn( + CallbackPtr, + &PluginState, + object: AnyObject, + ) -> Result, +} + +impl ValueGetterCallback { + pub fn new>(handler: T) -> Self { + Self { + func: T::get_value, + callback_ptr: handler.callback_ptr(), + } + } +} + +pub trait IntoPluginValueGetter { + unsafe extern "C" fn get_value( + callback: CallbackPtr, + state: &PluginState, + object: AnyObject, + ) -> Result; + + fn callback_ptr(&self) -> CallbackPtr; +} + +impl IntoPluginValueGetter for F +where + Fut: Future>>, + S: Clone, + O: GiteratedObject, + V: GiteratedObjectValue, + F: Fn(S, O) -> Fut, +{ + unsafe extern "C" fn get_value( + callback: CallbackPtr, + state: &PluginState, + mut object: AnyObject, + ) -> Result { + let _guard = trace_span!( + "get_value handler", + object = O::object_name(), + value = V::value_name() + ) + .entered(); + let state = unsafe { state.transmute_ref::() }; + + let object = unsafe { object.transmute_owned::() }; + + // Cast the callback ptr to ourselves + let callback: *const F = std::mem::transmute(callback.0); + let callback = callback.as_ref().unwrap(); + + let result = callback(state.clone(), *object); + + // match result { + // Ok(value) => Ok(NewAnyValue::new(value)), + // Err(_) => todo!(), + // } + + todo!() + } + + fn callback_ptr(&self) -> CallbackPtr { + unsafe { CallbackPtr::from_raw(self as *const _ as *const ()) } + } +} + +pub struct ValueChangeCallback { + func: unsafe extern "C" fn( + &PluginState, + object: AnyObject, + value_name: &str, + new_value: NewAnyValue, + ), +} + +pub trait IntoValueChangeCallback { + unsafe extern "C" fn value_changed( + state: &PluginState, + object: AnyObject, + value_name: &str, + new_value: NewAnyValue, + ); +} + +impl IntoValueChangeCallback for F { + unsafe extern "C" fn value_changed( + state: &PluginState, + object: AnyObject, + value_name: &str, + new_value: NewAnyValue, + ) { + todo!() + } +} + +impl ValueChangeCallback { + pub fn new>() -> Self { + Self { + func: T::value_changed, + } + } +} diff --git a/giterated-plugins/giterated-plugin/src/handle.rs b/giterated-plugins/giterated-plugin/src/handle.rs new file mode 100644 index 0000000..7b9c547 --- /dev/null +++ b/giterated-plugins/giterated-plugin/src/handle.rs @@ -0,0 +1,263 @@ +use std::{collections::HashMap, marker::PhantomData, path::Path, sync::Arc}; + +use anyhow::Error; +use dlopen2::wrapper::Container; +use semver::Version; +use tracing::{debug, trace}; + +use crate::{ + callback::{OperationHandlerCallback, SettingGetterCallback, ValueGetterCallback}, + new_stack::{ + ObjectOperationPair, ObjectSettingPair, ObjectValuePair, PluginMeta, PluginState, + RuntimeHandlers, TypeMetadata, + }, + FFISettingMeta, FFIValueChangeHandler, FFIValueMeta, GiteratedPluginApi, InitializationVTable, + ObjectVtable, OperationVTable, SettingVtable, ValueVTable, +}; + +#[derive(Clone)] +pub struct PluginHandle { + pub meta: PluginMeta, + pub raw: Arc>, + pub initialization: Arc, + pub state: PluginState, +} + +impl PluginHandle { + pub fn from_dylib(path: &str) -> Result { + let mut handle = unsafe { Container::load(path) }?; + + // Initialize the raw handle + let init_state = Self::initialize_raw_handle(&mut handle)?; + + let metadata = Self::get_meta(&mut handle)?; + + let initalization = Self::initialize_registration(&mut handle)?; + + trace!( + "Loaded plugin {} (Version: {})", + metadata.name, + metadata.version + ); + + Ok(Self { + raw: Arc::new(handle), + meta: metadata, + initialization: Arc::new(initalization), + state: init_state, + }) + } + + /// Builds the Plugin's Substack. + /// + /// Builds the Plugin into a substack, which can then be provided to the Giterated Runtime. + pub fn build_substack(&mut self) -> Result<(), Error> { + todo!() + } + + fn get_meta(handle: &mut Container) -> Result { + let meta = unsafe { handle.plugin_meta() }; + + let name = unsafe { std::slice::from_raw_parts(meta.name, meta.name_len) }; + let version = unsafe { std::slice::from_raw_parts(meta.version, meta.version_len) }; + + let name = std::str::from_utf8(name).unwrap(); + let version = std::str::from_utf8(version).unwrap(); + + Ok(PluginMeta { + name: String::from(name), + version: Version::parse(version).unwrap(), + }) + } + + pub fn initialize_registration( + handle: &mut Container, + ) -> Result { + debug!("Initializing plugin registration..."); + let mut builder = PluginInitializationTable::default(); + + // SAFETY: The lifetime of the returned type is only valid as long + // as the builder that returned it lives + let func_table = unsafe { builder.func_table() }; + + let state = Box::new(PluginInitializationState::new()); + + unsafe { handle.load_initialization_vtable(&func_table) }; + let state = unsafe { handle.initialize_registration(Box::into_raw(state)) }; + + debug!("Plugin handle initialized!"); + Ok(unsafe { *Box::from_raw(state) }) + } + + fn initialize_raw_handle( + handle: &mut Container, + ) -> Result { + debug!("Initializing plugin handle..."); + + let state = unsafe { handle.initialize() }; + + debug!("Plugin handle initialized!"); + + Ok(state) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CreationError { + #[error("an error occured opening the library {0}")] + LoadingLibrary(#[from] dlopen2::Error), +} + +pub struct PluginSubstackBuilder {} + +#[derive(Default)] +pub struct PluginInitializationState { + pub type_metadata: TypeMetadata, + pub operation_handlers: HashMap, OperationHandlerCallback>, + pub value_getters: HashMap, ValueGetterCallback>, + pub setting_getters: HashMap, SettingGetterCallback>, +} + +impl PluginInitializationState { + pub fn new() -> Self { + Self::default() + } +} + +#[derive(Default)] +pub struct PluginInitializationTable<'a> { + _marker: PhantomData<&'a ()>, +} + +impl<'a> PluginInitializationTable<'a> { + pub unsafe fn func_table(&mut self) -> InitializationVTable { + InitializationVTable { + register_value_change, + register_object, + register_operation, + register_setting, + register_value, + operation_handler, + value_getter, + setting_getter, + } + } +} + +unsafe extern "C" fn register_value_change( + state: *mut PluginInitializationState, + object_kind: &str, + value_name: &str, + vtable: FFIValueChangeHandler, +) { + todo!() +} + +unsafe extern "C" fn register_object( + state: *mut PluginInitializationState, + object_kind: &'static str, + vtable: ObjectVtable, +) { + let mut state = Box::from_raw(state); + + state.type_metadata.register_object(object_kind, vtable); + + Box::into_raw(state); +} + +unsafe extern "C" fn register_operation( + state: *mut PluginInitializationState, + object_kind: &'static str, + operation_name: &'static str, + vtable: OperationVTable, +) { + let mut state = Box::from_raw(state); + + state + .type_metadata + .register_operation(object_kind, operation_name, vtable); + + Box::into_raw(state); +} + +unsafe extern "C" fn register_setting( + state: *mut PluginInitializationState, + object_kind: &'static str, + setting_name: &'static str, + vtable: SettingVtable, +) { + let mut state = Box::from_raw(state); + + state + .type_metadata + .register_setting(object_kind, setting_name, vtable); + + Box::into_raw(state); +} + +unsafe extern "C" fn register_value( + state: *mut PluginInitializationState, + object_kind: &'static str, + value_name: &'static str, + vtable: ValueVTable, +) { + let mut state = Box::from_raw(state); + + state + .type_metadata + .register_value(object_kind, value_name, vtable); + + Box::into_raw(state); +} + +unsafe extern "C" fn operation_handler( + state: *mut PluginInitializationState, + object_kind: &'static str, + operation_name: &'static str, + handler: OperationHandlerCallback, +) { + let mut state = Box::from_raw(state); + + trace!("Operation handler for {}::{}", object_kind, operation_name); + + state.operation_handlers.insert( + ObjectOperationPair::new(object_kind, operation_name), + handler, + ); + + Box::into_raw(state); +} + +unsafe extern "C" fn value_getter( + state: *mut PluginInitializationState, + object_kind: &'static str, + value_name: &'static str, + handler: ValueGetterCallback, +) { + let mut state = Box::from_raw(state); + + trace!("Value getter for {}::{}", object_kind, value_name); + + state + .value_getters + .insert(ObjectValuePair::new(object_kind, value_name), handler); + + Box::into_raw(state); +} + +unsafe extern "C" fn setting_getter( + state: *mut PluginInitializationState, + object_kind: &'static str, + setting_name: &'static str, + handler: SettingGetterCallback, +) { + let mut state = Box::from_raw(state); + + trace!("Setting getter for {}::{}", object_kind, setting_name); + + state + .setting_getters + .insert(ObjectSettingPair::new(object_kind, setting_name), handler); + + Box::into_raw(state); +} diff --git a/giterated-plugins/giterated-plugin/src/lib.rs b/giterated-plugins/giterated-plugin/src/lib.rs new file mode 100644 index 0000000..393359d --- /dev/null +++ b/giterated-plugins/giterated-plugin/src/lib.rs @@ -0,0 +1,424 @@ +pub mod callback; +pub mod handle; +pub mod new_stack; +pub mod vtable; + +#[macro_use] +extern crate tracing; + +use std::{any::Any, fmt::Debug, mem::transmute, ptr::null_mut, sync::Arc}; + +use callback::{OperationHandlerCallback, SettingGetterCallback, ValueGetterCallback}; +use dlopen2::wrapper::WrapperApi; +use giterated_models::{ + object::GiteratedObject, operation::GiteratedOperation, settings::Setting, + value::GiteratedObjectValue, +}; +use handle::PluginInitializationState; +use new_stack::{FFIPluginMeta, PluginState}; + +#[derive(WrapperApi)] +pub struct GiteratedPluginApi { + plugin_meta: unsafe extern "C" fn() -> FFIPluginMeta, + load_host_vtable: unsafe extern "C" fn(vtable: &HostVTable), + load_initialization_vtable: unsafe extern "C" fn(vtable: &InitializationVTable), + initialize: unsafe extern "C" fn() -> PluginState, + initialize_registration: unsafe extern "C" fn( + init_state: *mut PluginInitializationState, + ) -> *mut PluginInitializationState, +} + +#[repr(C)] +pub struct FFIBox(*mut T); + +impl FFIBox { + pub fn from_box(src: Box) -> Self { + Self(Box::into_raw(src)) + } + + pub fn untyped(self) -> FFIBox<()> { + FFIBox(self.0 as *mut ()) + } +} + +#[repr(C)] +pub struct FFIObject(&'static str); + +#[repr(C)] +struct FFIOperationHandler( + for<'a> unsafe extern "C" fn(operation: FFIOperation<'a>) -> Result, FFIBox<[u8]>>, +); + +#[repr(C)] +struct FFIOperation<'a> { + operation_name: &'a str, + object_name: &'a str, + object: FFIObject, + operation: &'a [u8], +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct OperationVTable { + pub serialize: unsafe extern "C" fn(AnyObject) -> Result, ()>, + pub deserialize: unsafe extern "C" fn(&[u8]) -> Result, + pub is_same: unsafe extern "C" fn(AnyObject) -> bool, + pub serialize_success: unsafe extern "C" fn(()) -> Result, ()>, + pub serialize_failure: unsafe extern "C" fn(()) -> Result, ()>, + pub deserialize_success: unsafe extern "C" fn(&[u8]) -> Result<(), ()>, + pub deserialize_failure: unsafe extern "C" fn(&[u8]) -> Result<(), ()>, +} + +impl OperationVTable { + pub fn new>() -> Self { + Self { + serialize: T::serialize, + deserialize: T::deserialize, + is_same: T::is_same, + serialize_success: T::serialize_success, + serialize_failure: T::serialize_failure, + deserialize_success: T::deserialize_success, + deserialize_failure: T::deserialize_failure, + } + } +} + +pub struct AnyOperation { + /// A pointer to the plugin-local object type. We are not capable of + /// knowing what this type is, we use the provided vtable. + inner: FFIBox<()>, + vtable: OperationVTable, +} + +impl AnyOperation { + pub unsafe fn transmute_owned(&mut self) -> Box { + Box::from_raw(self.inner.0 as *mut T) + } + + pub unsafe fn transmute_ref(&self) -> &T { + let ptr: *const T = transmute(self.inner.0); + + ptr.as_ref().unwrap() + } +} + +#[repr(C)] +pub struct FFIValueChangeHandler(); + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct ObjectVtable { + pub to_str: unsafe extern "C" fn(AnyObject) -> FFIBox, + pub from_str: unsafe extern "C" fn(&str) -> Result>, + pub home_uri: unsafe extern "C" fn(AnyObject) -> FFIBox, + pub is_same: unsafe extern "C" fn(AnyObject) -> bool, +} + +impl ObjectVtable { + pub const fn new() -> Self { + Self { + to_str: T::to_str, + from_str: T::from_str, + home_uri: T::home_uri, + is_same: T::is_same, + } + } +} + +#[repr(C)] +pub struct NewAnySetting { + /// A pointer to the plugin-local object type. We are not capable of + /// knowing what this type is, we use the provided vtable. + inner: FFIBox<()>, + vtable: SettingVtable, +} + +impl NewAnySetting { + pub fn new(value: V) -> Self { + Self { + inner: FFIBox::from_box(Box::new(value)).untyped(), + vtable: SettingVtable::new::(), + } + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct SettingVtable { + pub deserialize: unsafe extern "C" fn(&[u8]) -> Result<(), ()>, + pub serialize: unsafe extern "C" fn(NewAnySetting) -> Result, ()>, +} + +impl SettingVtable { + pub fn new() -> Self { + Self { + deserialize: T::deserialize, + serialize: T::serialize, + } + } +} + +#[repr(C)] +pub struct NewAnyValue { + /// A pointer to the plugin-local object type. We are not capable of + /// knowing what this type is, we use the provided vtable. + inner: FFIBox<()>, + vtable: ValueVTable, +} + +impl NewAnyValue { + pub fn new>(value: V) -> Self { + NewAnyValue { + inner: FFIBox::from_box(Box::new(value)).untyped(), + vtable: ValueVTable::new::(), + } + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct ValueVTable { + pub deserialize: unsafe extern "C" fn(&[u8]) -> Result, + pub serialize: unsafe extern "C" fn(NewAnyValue) -> Result, ()>, +} + +impl ValueVTable { + pub fn new>() -> Self { + Self { + deserialize: T::deserialize, + serialize: T::serialize, + } + } +} + +pub trait IntoObjectVTable { + unsafe extern "C" fn to_str(this: AnyObject) -> FFIBox; + unsafe extern "C" fn from_str(src: &str) -> Result>; + unsafe extern "C" fn home_uri(this: AnyObject) -> FFIBox; + unsafe extern "C" fn is_same(other: AnyObject) -> bool; +} + +impl IntoObjectVTable for T { + unsafe extern "C" fn to_str(mut this: AnyObject) -> FFIBox { + let this: &Box = this.transmute_ref(); + + let result = this.to_string(); + + FFIBox::from_box(result.into_boxed_str()) + } + + unsafe extern "C" fn from_str(src: &str) -> Result> { + let result = T::from_object_str(src).unwrap(); + + let any_object = AnyObject::new(result); + + Ok(any_object) + } + + unsafe extern "C" fn home_uri(this: AnyObject) -> FFIBox { + todo!() + } + + unsafe extern "C" fn is_same(other: AnyObject) -> bool { + todo!() + } +} + +pub trait IntoOperationVTable { + unsafe extern "C" fn serialize(this: AnyObject) -> Result, ()>; + unsafe extern "C" fn deserialize(src: &[u8]) -> Result; + unsafe extern "C" fn is_same(this: AnyObject) -> bool; + unsafe extern "C" fn serialize_success(success: ()) -> Result, ()>; + unsafe extern "C" fn serialize_failure(failure: ()) -> Result, ()>; + unsafe extern "C" fn deserialize_success(src: &[u8]) -> Result<(), ()>; + unsafe extern "C" fn deserialize_failure(src: &[u8]) -> Result<(), ()>; +} + +impl IntoOperationVTable for D +where + D: GiteratedOperation, + O: GiteratedObject, +{ + unsafe extern "C" fn serialize(this: AnyObject) -> Result, ()> { + todo!() + } + + unsafe extern "C" fn deserialize(src: &[u8]) -> Result { + let deserialized: D = serde_json::from_slice(src).unwrap(); + + Ok(AnyOperation { + inner: FFIBox::from_box(Box::new(deserialized)).untyped(), + vtable: OperationVTable::new::(), + }) + } + + unsafe extern "C" fn is_same(this: AnyObject) -> bool { + todo!() + } + + unsafe extern "C" fn serialize_success(success: ()) -> Result, ()> { + todo!() + } + + unsafe extern "C" fn serialize_failure(failure: ()) -> Result, ()> { + todo!() + } + + unsafe extern "C" fn deserialize_success(src: &[u8]) -> Result<(), ()> { + todo!() + } + + unsafe extern "C" fn deserialize_failure(src: &[u8]) -> Result<(), ()> { + todo!() + } +} + +pub trait IntoSettingVTable { + unsafe extern "C" fn deserialize(src: &[u8]) -> Result<(), ()>; + unsafe extern "C" fn serialize(this: NewAnySetting) -> Result, ()>; +} + +impl IntoSettingVTable for S +where + S: Setting, +{ + unsafe extern "C" fn deserialize(src: &[u8]) -> Result<(), ()> { + todo!() + } + + unsafe extern "C" fn serialize(this: NewAnySetting) -> Result, ()> { + todo!() + } +} + +pub trait IntoValueVTable { + unsafe extern "C" fn deserialize(src: &[u8]) -> Result; + unsafe extern "C" fn serialize(this: NewAnyValue) -> Result, ()>; +} + +impl IntoValueVTable for V +where + O: GiteratedObject, + V: GiteratedObjectValue, +{ + unsafe extern "C" fn deserialize(src: &[u8]) -> Result { + let _guard = trace_span!( + "deserialize value", + object = O::object_name(), + value = V::value_name() + ); + + trace!("Deserializing"); + let deserialized: V = serde_json::from_slice(src).unwrap(); + + Ok(NewAnyValue { + inner: FFIBox::from_box(Box::new(deserialized)).untyped(), + vtable: ValueVTable::new::(), + }) + } + + unsafe extern "C" fn serialize(this: NewAnyValue) -> Result, ()> { + todo!() + } +} + +#[repr(C)] +pub struct AnyObject { + /// A pointer to the plugin-local object type. We are not capable of + /// knowing what this type is, we use the provided vtable. + inner: FFIBox<()>, + vtable: ObjectVtable, +} + +impl AnyObject { + pub fn new(inner: T) -> Self { + Self { + inner: FFIBox::from_box(Box::new(inner)).untyped(), + vtable: ObjectVtable::new::(), + } + } +} + +impl AnyObject { + pub unsafe fn transmute_owned(&mut self) -> Box { + Box::from_raw(self.inner.0 as *mut T) + } + + pub unsafe fn transmute_ref(&self) -> &T { + let ptr: *const T = transmute(self.inner.0); + + ptr.as_ref().unwrap() + } +} + +#[repr(C)] +pub struct FFISettingMeta(); + +#[repr(C)] +pub struct FFIValueMeta(); + +#[repr(C)] +pub struct HostVTable { + register_operation: unsafe extern "C" fn(&str, &str, FFIOperationHandler), + register_value_change: unsafe extern "C" fn(&str, &str, FFIValueChangeHandler), + register_object: unsafe extern "C" fn(&str, ObjectVtable), + register_setting: unsafe extern "C" fn(&str, &str, FFISettingMeta), + register_value: unsafe extern "C" fn(&str, &str, FFIValueMeta), +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct InitializationVTable { + pub register_value_change: + unsafe extern "C" fn(*mut PluginInitializationState, &str, &str, FFIValueChangeHandler), + pub register_object: + unsafe extern "C" fn(*mut PluginInitializationState, &'static str, ObjectVtable), + pub register_operation: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + OperationVTable, + ), + pub register_setting: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + SettingVtable, + ), + pub register_value: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + ValueVTable, + ), + + pub operation_handler: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + OperationHandlerCallback, + ), + + pub value_getter: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + ValueGetterCallback, + ), + + pub setting_getter: unsafe extern "C" fn( + *mut PluginInitializationState, + &'static str, + &'static str, + SettingGetterCallback, + ), +} + +impl Debug for InitializationVTable { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InitializationVTable").finish() + } +} + +unsafe impl Sync for InitializationVTable {} +unsafe impl Send for InitializationVTable {} diff --git a/giterated-plugins/giterated-plugin/src/new_stack/mod.rs b/giterated-plugins/giterated-plugin/src/new_stack/mod.rs new file mode 100644 index 0000000..622bc92 --- /dev/null +++ b/giterated-plugins/giterated-plugin/src/new_stack/mod.rs @@ -0,0 +1,452 @@ +pub mod operation_walker; + +use std::{ + any::type_name, collections::HashMap, fmt::Debug, mem::transmute, ptr::null_mut, sync::Arc, +}; + +use semver::Version; +use tracing::{debug, debug_span, field::DebugValue, span, trace, trace_span, warn, Level}; + +use crate::{ + callback::{ + OperationHandlerCallback, SettingChangeCallback, SettingGetterCallback, + ValueChangeCallback, ValueGetterCallback, + }, + handle::{PluginHandle, PluginInitializationState}, + AnyObject, AnyOperation, FFIBox, NewAnySetting, NewAnyValue, ObjectVtable, OperationVTable, + SettingVtable, ValueVTable, +}; + +use self::operation_walker::OperationHandlerRules; + +#[derive(Default)] +pub struct TypeMetadata { + pub objects: HashMap<&'static str, ObjectVtable>, + pub operations: HashMap, OperationVTable>, + pub settings: HashMap, SettingVtable>, + pub values: HashMap, ValueVTable>, +} + +impl TypeMetadata { + pub fn register_object(&mut self, object_kind: &'static str, vtable: ObjectVtable) { + trace!("Registering type metadata for {}", object_kind); + + self.objects.insert(object_kind, vtable); + } + + pub fn register_operation( + &mut self, + object_kind: &'static str, + operation_name: &'static str, + vtable: OperationVTable, + ) { + trace!( + "Registering operation metadata for {}::{}", + object_kind, + operation_name + ); + + self.operations.insert( + ObjectOperationPair { + object_kind, + operation_name, + }, + vtable, + ); + } + + pub fn register_setting( + &mut self, + object_kind: &'static str, + setting_name: &'static str, + vtable: SettingVtable, + ) { + trace!("Registering setting {}::{}", object_kind, setting_name); + + self.settings.insert( + ObjectSettingPair { + object_kind, + setting_name, + }, + vtable, + ); + } + + pub fn register_value( + &mut self, + object_kind: &'static str, + value_name: &'static str, + vtable: ValueVTable, + ) { + trace!("Registering value {}::{}", object_kind, value_name); + + self.values.insert( + ObjectValuePair { + object_kind, + value_name, + }, + vtable, + ); + } +} + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] +pub struct ObjectOperationPair<'s> { + pub object_kind: &'s str, + pub operation_name: &'s str, +} + +impl<'s> ObjectOperationPair<'s> { + pub fn new(object_kind: &'s str, operation_name: &'s str) -> Self { + Self { + object_kind, + operation_name, + } + } +} + +#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] +pub struct ObjectSettingPair<'s> { + pub object_kind: &'s str, + pub setting_name: &'s str, +} + +impl<'s> ObjectSettingPair<'s> { + pub fn new(object_kind: &'s str, setting_name: &'s str) -> Self { + Self { + object_kind, + setting_name, + } + } +} + +#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)] +pub struct ObjectValuePair<'s> { + pub object_kind: &'s str, + pub value_name: &'s str, +} + +impl<'s> ObjectValuePair<'s> { + pub fn new(object_kind: &'s str, value_name: &'s str) -> Self { + Self { + object_kind, + value_name, + } + } +} + +#[derive(Clone, Copy)] +#[repr(C)] +pub struct PluginState { + pub inner: *mut (), +} + +impl PluginState { + pub unsafe fn transmute_owned(&mut self) -> Box { + Box::from_raw(self.inner as *mut T) + } + + pub unsafe fn transmute_ref(&self) -> &T { + let ptr: *const T = transmute(self.inner); + + ptr.as_ref().unwrap() + } +} + +impl PluginState { + pub fn null() -> Self { + Self { inner: null_mut() } + } +} + +#[derive(Default)] +pub struct Runtime { + plugins: Vec<(PluginMeta, PluginHandle)>, + handlers: RuntimeHandlers, +} + +impl Runtime { + pub fn insert_plugin(&mut self, mut plugin: PluginHandle) { + let _guard = debug_span!("inserting plugin", meta = debug(&plugin.meta)).entered(); + + for (pair, callback) in &plugin.initialization.operation_handlers { + let _guard = + trace_span!("processing operation handler callbacks", pair = debug(pair)).entered(); + + if self + .handlers + .operation_handlers + .insert(*pair, (RuntimeDomain::from_plugin(&plugin), *callback)) + .is_some() + { + warn!("Warning! Insertion of handler for overwrote a previous handler.") + } + + trace!("Insertion of operation handler successful") + } + + for (pair, callback) in &plugin.initialization.value_getters { + let _guard = + trace_span!("processing value getter callbacks", pair = debug(pair)).entered(); + + if self + .handlers + .value_getters + .insert(*pair, (RuntimeDomain::from_plugin(&plugin), *callback)) + .is_some() + { + warn!("Warning! Insertion of handler for overwrote a previous handler.") + } + + trace!("Insertion of operation handler successful") + } + + for (pair, callback) in &plugin.initialization.setting_getters { + let _guard = + trace_span!("processing setting getter callbacks", pair = debug(pair)).entered(); + + if self + .handlers + .setting_getters + .insert(*pair, (RuntimeDomain::from_plugin(&plugin), *callback)) + .is_some() + { + warn!("Warning! Insertion of setting handler for overwrote a previous handler.") + } + + trace!("Insertion of setting handler successful") + } + } + + pub fn handle( + &self, + object_kind: &str, + operation_name: &str, + object: &str, + operation_payload: &[u8], + ) -> Result<(), HandlerError> { + let rules = self.handlers.handle_operation(object_kind, operation_name); + + rules.handle(object, operation_payload) + } +} + +#[derive(Default)] +pub struct RuntimeHandlers { + operation_handlers: + HashMap, (RuntimeDomain, OperationHandlerCallback)>, + value_getters: HashMap, (RuntimeDomain, ValueGetterCallback)>, + setting_getters: HashMap, (RuntimeDomain, SettingGetterCallback)>, + value_change: HashMap, (RuntimeDomain, ValueChangeCallback)>, + setting_change: HashMap, (RuntimeDomain, SettingChangeCallback)>, +} + +impl RuntimeHandlers { + pub fn operation_handler( + &mut self, + pair: ObjectOperationPair<'static>, + handler: OperationHandlerCallback, + domain: RuntimeDomain, + ) { + trace!( + "Inserting operation handler for {}::{}", + pair.object_kind, + pair.operation_name + ); + + // There can only be one handler per operation (at least for now?), send a warning if + // a newly registered handler overwrites the previous handler. + if self + .operation_handlers + .insert(pair, (domain, handler)) + .is_some() + { + debug!("Warning! A newly inserted operation handler for {}::{} overwrites a previous handler.", pair.object_kind, pair.operation_name); + } + } + + pub fn value_getter( + &mut self, + pair: ObjectValuePair<'static>, + handler: ValueGetterCallback, + domain: RuntimeDomain, + ) { + trace!( + "Inserting value getter for {}::{}", + pair.object_kind, + pair.value_name + ); + + if self.value_getters.insert(pair, (domain, handler)).is_some() { + debug!( + "Warning! A newly inserted value getter for {}::{} overwrites a previous handler.", + pair.object_kind, pair.value_name + ); + } + } + + pub fn setting_getter( + &mut self, + pair: ObjectSettingPair<'static>, + handler: SettingGetterCallback, + domain: RuntimeDomain, + ) { + trace!( + "Inserting setting getter for {}::{}", + pair.object_kind, + pair.setting_name + ); + + if self + .setting_getters + .insert(pair, (domain, handler)) + .is_some() + { + debug!("Warning! A newly inserted setting getter for {}::{} overwrites a previous handler.", pair.object_kind, pair.setting_name); + } + } + + pub fn value_change( + &mut self, + pair: ObjectValuePair<'static>, + handler: ValueChangeCallback, + domain: RuntimeDomain, + ) { + trace!( + "Inserting value change handler for {}::{}", + pair.object_kind, + pair.value_name + ); + + if self.value_change.insert(pair, (domain, handler)).is_some() { + debug!("Warning! A newly inserted value change handler for {}::{} overwrites a previous handler.", pair.object_kind, pair.value_name); + panic!("Not intended"); + } + } + + pub fn setting_change( + &mut self, + pair: ObjectSettingPair<'static>, + handler: SettingChangeCallback, + domain: RuntimeDomain, + ) { + trace!( + "Inserting setting change handler for {}::{}", + pair.object_kind, + pair.setting_name + ); + + if self + .setting_change + .insert(pair, (domain, handler)) + .is_some() + { + debug!("Warning! A newly inserted setting change handler for {}::{} overwrites a previous handler.", pair.object_kind, pair.setting_name); + panic!("Not intended"); + } + } +} + +impl RuntimeHandlers { + pub fn handle_operation<'o>( + &'o self, + object_kind: &'o str, + operation_name: &'o str, + ) -> OperationHandlerRules<'o> { + OperationHandlerRules::new(object_kind, operation_name, self) + } +} + +pub struct RuntimeDomain { + plugin: PluginHandle, +} + +impl RuntimeDomain { + pub fn from_plugin(plugin: &PluginHandle) -> Self { + Self { + plugin: plugin.clone(), + } + } + + pub fn object_vtable(&self, object_kind: &str) -> Option { + self.plugin + .initialization + .type_metadata + .objects + .get(object_kind) + .copied() + } + + pub fn operation_vtable( + &self, + object_kind: &str, + operation_name: &str, + ) -> Option { + self.plugin + .initialization + .type_metadata + .operations + .get(&ObjectOperationPair::new(object_kind, operation_name)) + .copied() + } + + pub fn setting_vtable(&self, object_kind: &str, setting_name: &str) -> Option { + self.plugin + .initialization + .type_metadata + .settings + .get(&ObjectSettingPair::new(object_kind, setting_name)) + .copied() + } + + pub fn value_vtable(&self, object_kind: &str, value_name: &str) -> Option { + self.plugin + .initialization + .type_metadata + .values + .get(&ObjectValuePair::new(object_kind, value_name)) + .copied() + } +} + +#[derive(Clone, Debug)] +pub struct PluginMeta { + pub name: String, + pub version: Version, +} + +#[repr(C)] +pub struct FFIPluginMeta { + pub name: *const u8, + pub name_len: usize, + pub version: *const u8, + pub version_len: usize, +} + +pub struct RuntimePlugin { + handle: PluginHandle, + type_metadata: Arc, +} + +impl RuntimePlugin { + pub fn plugin_meta(&self) -> PluginMeta { + let meta = unsafe { self.handle.raw.plugin_meta() }; + + let name = unsafe { std::slice::from_raw_parts(meta.name, meta.name_len) }; + let version = unsafe { std::slice::from_raw_parts(meta.version, meta.version_len) }; + + let name = std::str::from_utf8(name).unwrap(); + let version = std::str::from_utf8(version).unwrap(); + + PluginMeta { + name: String::from(name), + version: Version::parse(version).unwrap(), + } + } +} + +pub enum HandlerError { + Failure(()), + Internal(()), + Unhandled, +} diff --git a/giterated-plugins/giterated-plugin/src/new_stack/operation_walker.rs b/giterated-plugins/giterated-plugin/src/new_stack/operation_walker.rs new file mode 100644 index 0000000..28e694a --- /dev/null +++ b/giterated-plugins/giterated-plugin/src/new_stack/operation_walker.rs @@ -0,0 +1,225 @@ +use giterated_models::{operation::GiteratedOperation, settings::GetSetting, value::GetValue}; +use tracing::{debug_span, trace, trace_span}; + +use crate::new_stack::{ObjectOperationPair, PluginState}; + +use super::{HandlerError, ObjectSettingPair, ObjectValuePair, RuntimeHandlers}; + +/// A wrapper for operation handling that enforces handling rules. +/// +/// # Handler Resolution +/// In order, handler resolution will be attempted as follows: +/// +/// | Index | object_kind | operation_kind | Special Case? | +/// |-------|-------------|-----------------|---------------| +/// | 1 | `any` | `typed` | No | +/// | 2 | `typed` | `any` | No | +/// | 3 | `any` | `any` | No | +/// | 4 | `any` | `GetValue` | ⚠️ Yes ⚠️ | +/// | 5 | `any` | `GetSetting` | ⚠️ Yes ⚠️ | +/// | 6 | `any` | `SetSetting` | ⚠️ Yes ⚠️ | +/// | 7 | `any` | `ObjectRequest` | ⚠️ Yes ⚠️ | +/// | 8 | `typed` | `typed` | No | +pub struct OperationHandlerRules<'a> { + object_kind: &'a str, + operation_name: &'a str, + handlers: &'a RuntimeHandlers, +} + +impl<'o> OperationHandlerRules<'o> { + pub fn new( + object_kind: &'o str, + operation_name: &'o str, + handlers: &'o RuntimeHandlers, + ) -> Self { + Self { + object_kind, + operation_name, + handlers, + } + } + + pub fn handle(&self, object: &str, operation_payload: &[u8]) -> Result<(), HandlerError> { + // object_kind: `any` + // operation_kind: `typed` + if let Some(handler) = self + .handlers + .operation_handlers + .get(&ObjectOperationPair::new("any", self.operation_name)) + { + todo!() + } + + // object_kind: `typed` + // operation_kind: `any` + if let Some(handler) = self + .handlers + .operation_handlers + .get(&ObjectOperationPair::new(self.object_kind, "any")) + {} + + // object_kind: `any` + // operation_kind: `any` + if let Some(handler) = self + .handlers + .operation_handlers + .get(&ObjectOperationPair::new("any", "any")) + {} + + // ⚠️ Special Case ⚠️ + // object_kind: `any` + // operation_kind: `GetValue` + if self.operation_name == "get_value" { + let operation: GetValue = serde_json::from_slice(operation_payload).unwrap(); + let _guard = trace_span!( + "get_value handler resolving", + object = self.object_kind, + value = operation.value_name + ) + .entered(); + + if let Some((domain, callback)) = self.handlers.value_getters.get( + &ObjectValuePair::new(self.object_kind, &operation.value_name), + ) { + trace_span!( + "get_value handler.", + object = self.object_kind, + value_name = operation.value_name + ); + + let object_vtable = domain + .object_vtable(self.object_kind) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!("Resolved object vtable for {}", self.object_kind); + + let value_vtable = domain + .value_vtable(self.object_kind, &operation.value_name) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!( + "Resolved value vtable for {}::{}", + self.object_kind, + operation.value_name + ); + + let object = unsafe { (object_vtable.from_str)(object) } + .map_err(|_| HandlerError::Internal(()))?; + + let _guard = debug_span!("get_value handler"); + + let result = + unsafe { (callback.func)(callback.callback_ptr, &domain.plugin.state, object) }; + + // Todo deser + + return Ok(()); + } else { + trace!("Failed to resolve handler."); + } + } + + // ⚠️ Special Case ⚠️ + // object_kind: `any` + // operation_kind: `GetSetting` + if self.operation_name == "get_setting" { + let operation: GetSetting = serde_json::from_slice(operation_payload).unwrap(); + let _guard = trace_span!( + "get_setting handler resolving", + object = self.object_kind, + setting = operation.setting_name + ) + .entered(); + + if let Some((domain, callback)) = self.handlers.setting_getters.get( + &ObjectSettingPair::new(self.object_kind, &operation.setting_name), + ) { + trace_span!( + "get_setting handler.", + object = self.object_kind, + setting_name = operation.setting_name + ); + + let object_vtable = domain + .object_vtable(self.object_kind) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!("Resolved object vtable for {}", self.object_kind); + + let setting_vtable = domain + .setting_vtable(self.object_kind, &operation.setting_name) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!("Resolved setting vtable for {}", operation.setting_name); + + let object = unsafe { (object_vtable.from_str)(object) } + .map_err(|_| HandlerError::Internal(()))?; + + let _guard = debug_span!("get_value handler"); + + let result = + unsafe { (callback.func)(callback.callback_ptr, &domain.plugin.state, object) }; + + // Todo deser + + return Ok(()); + } else { + trace!("Failed to resolve handler."); + } + } + + // ⚠️ Special Case ⚠️ + // object_kind: `any` + // operation_kind: `SetSetting` + if self.operation_name == "set_setting" {} + + // ⚠️ Special Case ⚠️ + // object_kind: `any` + // operation_kind: `ObjectRequest` + if self.operation_name == "object_request" {} + + // object_kind: `typed` + // operation_kind: `typed` + if let Some((domain, handler)) = + self.handlers + .operation_handlers + .get(&ObjectOperationPair::new( + self.object_kind, + self.operation_name, + )) + { + let _guard = trace_span!("typed_typed handler resolved").entered(); + + let object_vtable = domain + .object_vtable(self.object_kind) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!("Resolved object vtable for {}", self.object_kind); + + let operation_vtable = domain + .operation_vtable(self.object_kind, self.operation_name) + .ok_or_else(|| HandlerError::Unhandled)?; + trace!( + "Resolved operation vtable for {}::{}", + self.object_kind, + self.operation_name + ); + + let object = unsafe { (object_vtable.from_str)(object) } + .map_err(|_| HandlerError::Internal(()))?; + let operation = unsafe { (operation_vtable.deserialize)(operation_payload) } + .map_err(|_| HandlerError::Internal(()))?; + trace!("Parsed operation data"); + + let _guard = debug_span!("calling handler").entered(); + let result = unsafe { + (handler.func)( + handler.callback_ptr, + &domain.plugin.state, + object, + operation, + ) + }; + + // todo + return Ok(()); + } + + Err(HandlerError::Unhandled) + } +} diff --git a/giterated-plugins/giterated-plugin/src/vtable/mod.rs b/giterated-plugins/giterated-plugin/src/vtable/mod.rs new file mode 100644 index 0000000..e64bf7b --- /dev/null +++ b/giterated-plugins/giterated-plugin/src/vtable/mod.rs @@ -0,0 +1,3 @@ +//! Giterated's VTable System +//! +//! Docs here? :)