Skip to content

Commit

Permalink
update documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelnikonorov committed Nov 25, 2024
1 parent 57a3cba commit dc0a46b
Show file tree
Hide file tree
Showing 4 changed files with 427 additions and 19 deletions.
121 changes: 120 additions & 1 deletion lib/src/utils/configuration.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
//! This module defines the configuration structures and implementations for service extensions.
//!
//! It includes the following:
//!
//! - `ServiceExtensionConfiguration`: Represents the configuration for a single service extension.
//! - `ServiceExtensionsConfiguration`: A collection of service extension configurations.
//! - Methods to retrieve and manage these configurations.
use crate::clients::ServiceType;
use crate::utils::extension_manager::ExtensionManager;
use log::{debug, error};
Expand All @@ -8,18 +16,52 @@ use std::io::Read;
use std::fs::File;
use url::Url;

/// Represents the configuration for a single service extension.
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
pub struct ServiceExtensionConfiguration {
/// The name of the extension.
#[serde(rename = "name")]
pub extension_name: String,

/// Indicates whether the extension is required.
pub required: bool,

/// The configuration details for the extension.
pub configuration: Value,
}

/// A collection of service extension configurations.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ServiceExtensionsConfiguration(Vec<ServiceExtensionConfiguration>);

impl ServiceExtensionsConfiguration {
/// Retrieves the configuration for a specific extension by name.
///
/// # Arguments
///
/// * `name` - A reference to a string that holds the name of the extension.
///
/// # Returns
///
/// * `&Value` - A reference to the configuration value of the specified extension.
///
/// # Panics
///
/// This function will panic if the extension with the specified name is not found.
///
/// # Example
///
/// ```rust
/// let service_extensions_config = ServiceExtensionsConfiguration(vec![
/// ServiceExtensionConfig {
/// extension_name: "AttestedTLS-middleware".to_string(),
/// configuration: serde_json::json!({"trusted-repository": "trs://example-registry.org/"}),
/// },
/// ]);
/// // Considering that "AttestedTLS-middleware" exports "tls-verifier" extension method
/// let config = service_extensions_config.get_extension_config(&"tls-verifier".to_string());
/// println!("Extension configuration: {:?}", config);
/// ```
pub fn get_extension_config(&self, name: &String) -> &Value {
&self.0.iter().find(|service_extension_config| service_extension_config.extension_name == *name).unwrap().configuration
}
Expand Down Expand Up @@ -50,6 +92,31 @@ pub struct Configuration {
}

impl Serialize for Configuration {
/// Serializes the `Configuration` struct into a format suitable for storage or transmission.
///
/// # Arguments
///
/// * `serializer` - The serializer to use for converting the `Configuration` struct into a serializable format.
///
/// # Returns
///
/// * `Result<S::Ok, S::Error>` - The result of the serialization process.
///
/// # Example
///
/// ```rust
/// let config = Configuration {
/// base_path: Url::parse("https://example.com").unwrap(),
/// user_agent: Some("MyApp/1.0".to_string()),
/// basic_auth: None,
/// oauth_access_token: None,
/// bearer_access_token: None,
/// api_key: None,
/// extensions: None,
/// };
/// let serialized = serde_json::to_string(&config).unwrap();
/// println!("Serialized configuration: {}", serialized);
/// ```
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
Expand All @@ -67,6 +134,33 @@ impl Serialize for Configuration {
}

impl<'de> Deserialize<'de> for Configuration {
/// Deserializes the `Configuration` struct from a format suitable for storage or transmission.
///
/// # Arguments
///
/// * `deserializer` - The deserializer to use for converting the serialized data into a `Configuration` struct.
///
/// # Returns
///
/// * `Result<Self, D::Error>` - The result of the deserialization process.
///
/// # Example
///
/// ```rust
/// let serialized = r#"
/// {
/// "base_path": "https://example.com",
/// "user_agent": "MyApp/1.0",
/// "basic_auth": null,
/// "oauth_access_token": null,
/// "bearer_access_token": null,
/// "api_key": null,
/// "extensions": null
/// }
/// "#;
/// let config: Configuration = serde_json::from_str(serialized).unwrap();
/// println!("Deserialized configuration: {:?}", config);
/// ```
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
Expand Down Expand Up @@ -184,6 +278,31 @@ impl Configuration {
/// # Errors
///
/// This function will return an error if the configuration file is missing or malformed.
///
/// # Example
///
/// ```rust
/// // Example service configuration JSON
/// // {
/// // "TES": {
/// // "base_path": "https://some-host.org/ga4gh/tes/",
/// // "oauth_access_token": "...",
/// // "extensions": {
/// // "name": "extension-name",
/// // "required": true,
/// // "configuration": {
/// // "extension specific key": "value"
/// // }
/// // }
/// // }
/// // }
/// let config = Configuration::from_file(
/// Some(ServiceType::TES),
/// &"path/to/service-config.json".to_string(),
/// &"path/to/extensions-config.json".to_string()
/// )?;
/// println!("Loaded configuration: {:?}", config);
/// ```
pub fn from_file(service_type: Option<ServiceType>, service_config_path: &String, extensions_config_path: &String) -> Result<Self, Box<dyn std::error::Error>> {
// Example service configuration JSON
// {
Expand All @@ -194,7 +313,7 @@ impl Configuration {
// "name": "extension-name",
// "required": true,
// "configuration": {
// "extension specific-key": "value"
// "extension specific key": "value"
// }
// }
// }
Expand Down
135 changes: 135 additions & 0 deletions lib/src/utils/extension.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
//! Extension Module
//!
//! This module provides structures and functions to manage extensions, including loading, initializing, and invoking methods from extensions.
//!
//! # Example
//!
//! ```rust
//! use extension_manager::InstalledExtension;
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Load an extension from a file
//! let extension = InstalledExtension::from_file("/path/to/extension.json")?;
//! println!("Loaded extension: {:?}", extension);
//! Ok(())
//! }
//! ```
use log::{info, debug};
use libloading::{Library, Symbol};
use std::collections::HashMap;
Expand All @@ -10,42 +27,77 @@ use std::error::Error;
type ExtensionInitFunction = unsafe extern "Rust" fn(Value) -> Vec<&'static [&'static str]>;
type ExtensionMethodFunction = unsafe extern "Rust" fn(Value) -> Value;

/// Represents a method provided by an extension.
#[derive(Debug, Clone)]
pub struct ExtensionMethod {
/// The name of the extension providing this method.
pub extension_name: String,
/// The unified name of the method.
pub unified_name: String,
/// The internal name of the method.
pub internal_name: String,
/// The function pointer to the method implementation.
pub method: Symbol<'static, ExtensionMethodFunction>,
}

/// Represents an extension with its metadata.
#[derive(Debug, Serialize, Deserialize)]
pub struct Extension {
/// The name of the extension.
pub name: String,
/// The version of the extension.
pub version: String,
/// The file path to the extension.
pub path: String,
/// An optional description of the extension.
pub description: Option<String>,
}

/// Represents an installed extension with its runtime state.
#[derive(Debug, Serialize, Deserialize)]
pub struct InstalledExtension {
/// The name of the extension.
pub name: String,
/// The version of the extension.
pub version: String,
/// The path to the extension's definition file.
#[serde(rename = "definition-path")]
pub definition_path: String,
/// The path to the extension's library file.
#[serde(rename = "library-path")]
pub library_path: String,

/// Indicates whether the extension is enabled.
#[serde(skip_serializing, default)]
pub enabled: bool,
/// Indicates whether the extension is loaded.
#[serde(skip_serializing, default)]
pub loaded: bool,
/// The loaded library of the extension.
#[serde(skip)]
pub library: Option<Library>,
/// The methods provided by the extension.
#[serde(skip, default)]
pub methods: HashMap<String, Vec<ExtensionMethod>>,
}

impl InstalledExtension {
/// Loads an `InstalledExtension` from a JSON file.
///
/// # Arguments
///
/// * `file_path` - A string slice that holds the path to the JSON file.
///
/// # Returns
///
/// * `Result<Self, Box<dyn Error>>` - Returns an `InstalledExtension` on success, or an error on failure.
///
/// # Example
///
/// ```rust
/// let extension = InstalledExtension::from_file("/path/to/extension.json")?;
/// println!("Loaded extension: {:?}", extension);
/// ```
pub fn from_file(file_path: &str) -> Result<Self, Box<dyn Error>> {
let mut file = File::open(file_path)?;
let mut contents = String::new();
Expand All @@ -64,14 +116,57 @@ impl InstalledExtension {
Ok(extension)
}

/// Enables the extension.
///
/// This method sets the `enabled` flag to `true`.
///
/// # Example
///
/// ```rust
/// let mut extension = InstalledExtension::from_file("/path/to/extension.json")?;
/// extension.enable();
/// assert!(extension.enabled);
/// ```
pub fn enable(&mut self) {
self.enabled = true;
}

/// Disables the extension.
///
/// This method sets the `enabled` flag to `false`.
///
/// # Example
///
/// ```rust
/// let mut extension = InstalledExtension::from_file("/path/to/extension.json")?;
/// extension.disable();
/// assert!(!extension.enabled);
/// ```
pub fn disable(&mut self) {
self.enabled = false;
}

/// Loads the extension if it is enabled.
///
/// This method attempts to load the extension's shared library and initialize it with the provided service configuration.
/// It retrieves the extension's methods and stores them in the `methods` map.
///
/// # Arguments
///
/// * `service_config` - A `serde_json::Value` containing the service configuration to pass to the extension's initialization function.
///
/// # Safety
///
/// This function is unsafe because it involves loading a shared library and calling external functions.
///
/// # Example
///
/// ```rust
/// let mut extension = InstalledExtension::from_file("/path/to/extension.json")?;
/// let service_config = serde_json::json!({});
/// extension.load(service_config);
/// assert!(extension.loaded);
/// ```
pub fn load(&mut self, service_config: Value) {
if self.enabled {
debug!("Loading extension lib: {} v{}", self.name, self.version);
Expand Down Expand Up @@ -111,6 +206,23 @@ impl InstalledExtension {
}
}

/// Unloads the extension by calling its `deinit` function.
///
/// This method checks if the extension is currently loaded. If it is, it attempts to load the extension's shared library
/// and retrieve the `deinit` function symbol. It then calls the `deinit` function to properly unload the extension.
///
/// # Safety
///
/// This function is unsafe because it involves loading a shared library and calling an external function.
///
/// # Example
///
/// ```rust
/// let mut extension = InstalledExtension::from_file("/path/to/extension.json")?;
/// extension.load(serde_json::json!({}));
/// extension.unload();
/// assert!(!extension.loaded);
/// ```
pub fn unload(&mut self) {
if self.loaded {
debug!("Unloading extension: {} v{}", self.name, self.version);
Expand All @@ -130,6 +242,11 @@ impl InstalledExtension {
}

impl Clone for InstalledExtension {
/// Creates a clone of the `InstalledExtension`.
///
/// This method creates a new `InstalledExtension` instance with the same values for all fields except for the `library` field,
/// which is set to `None` in the cloned instance. This is because the `library` field represents a loaded shared library,
/// which cannot be safely cloned.
fn clone(&self) -> Self {
InstalledExtension {
name: self.name.clone(),
Expand All @@ -145,6 +262,24 @@ impl Clone for InstalledExtension {
}

impl Extension {
/// Loads an `Extension` from a JSON file.
///
/// This method reads the contents of the specified JSON file and deserializes it into an `Extension` instance.
///
/// # Arguments
///
/// * `file` - A string slice that holds the path to the JSON file.
///
/// # Returns
///
/// * `Result<Self, Box<dyn std::error::Error>>` - Returns an `Extension` on success, or an error on failure.
///
/// # Example
///
/// ```rust
/// let extension = Extension::from_file("/path/to/extension.json")?;
/// println!("Loaded extension: {:?}", extension);
/// ```
pub fn from_file(file: &str) -> Result<Self, Box<dyn std::error::Error>> {
let contents = fs::read_to_string(file)?;
let extension: Extension = serde_json::from_str(&contents)?;
Expand Down
Loading

0 comments on commit dc0a46b

Please sign in to comment.