Skip to content

Commit

Permalink
feat: configure_interaction call to WASM plugin working
Browse files Browse the repository at this point in the history
  • Loading branch information
rholshausen committed Jul 24, 2024
1 parent 821ccd7 commit db7582c
Show file tree
Hide file tree
Showing 9 changed files with 2,114 additions and 124 deletions.
81 changes: 78 additions & 3 deletions drivers/rust/driver/src/wasm_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ use std::fmt::{Debug, Formatter};
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicUsize, Ordering};

use anyhow::anyhow;
use async_trait::async_trait;
use bytes::Bytes;
use itertools::Itertools;
use pact_models::bodies::OptionalBody;
use pact_models::content_types::ContentType;
Expand All @@ -17,8 +19,8 @@ use tracing::{debug, info, trace};
use wasmtime::{AsContextMut, Engine, Module, Store};
use wasmtime::component::{bindgen, Component, Instance, Linker, ResourceTable};
use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiView};

use crate::catalogue_manager::{CatalogueEntryProviderType, CatalogueEntryType, register_plugin_entries};
use crate::content::{InteractionContents, PluginConfiguration};
use crate::mock_server::{MockServerConfig, MockServerDetails, MockServerResults};
use crate::plugin_models::{CompareContentRequest, CompareContentResult, GenerateContentRequest, PactPlugin, PactPluginManifest};
use crate::verification::{InteractionVerificationData, InteractionVerificationResult};
Expand Down Expand Up @@ -59,6 +61,16 @@ impl From<&crate::catalogue_manager::CatalogueEntry> for CatalogueEntry {
}
}

impl Into<pact_models::content_types::ContentTypeHint> for ContentTypeHint {
fn into(self) -> pact_models::content_types::ContentTypeHint {
match self {
ContentTypeHint::Binary => pact_models::content_types::ContentTypeHint::BINARY,
ContentTypeHint::Text => pact_models::content_types::ContentTypeHint::TEXT,
ContentTypeHint::Default => pact_models::content_types::ContentTypeHint::DEFAULT
}
}
}

/// Plugin that executes in a WASM VM
#[derive(Clone)]
pub struct WasmPlugin {
Expand Down Expand Up @@ -129,8 +141,71 @@ impl PactPlugin for WasmPlugin {
todo!()
}

async fn configure_interaction(&self, content_type: &ContentType, definition: &HashMap<String, Value>) -> anyhow::Result<(Vec<InteractionContents>, Option<PluginConfiguration>)> {
todo!()
async fn configure_interaction(
&self,
content_type: &ContentType,
definition: &HashMap<String, Value>
) -> anyhow::Result<(Vec<crate::content::InteractionContents>, Option<crate::content::PluginConfiguration>)> {
let mut store = self.store.lock().unwrap();

// Unfortunately, WIT does not allow recursive data structures, so we need to feed JSON strings
// in and out here.
let json = Value::Object(definition.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect());
let result = self.instance.call_configure_interaction(store.as_context_mut(),
content_type.sub_type.to_string().as_str(), json.to_string().as_str())?
.map_err(|err| anyhow!(err))?;
debug!("Result from call to configure_interaction: {:?}", result);

let mut interaction_details = vec![];
for config in &result.interaction {
interaction_details.push(crate::content::InteractionContents {
part_name: config.part_name.clone(),
body: OptionalBody::Present(
Bytes::copy_from_slice(config.contents.content.as_slice()),
ContentType::parse(config.contents.content_type.as_str()).ok(),
config.contents.content_type_hint.map(|v| v.into())
),
rules: None,
generators: None,
metadata: None,
metadata_rules: None,
plugin_config: Default::default(),
interaction_markup: "".to_string(),
interaction_markup_type: "".to_string(),
});
}
debug!("interaction_details = {:?}", interaction_details);

let plugin_config = match result.plugin_config {
Some(config) => {
let interaction_configuration = if config.interaction_configuration.is_empty() {
Default::default()
} else {
serde_json::from_str::<Value>(config.interaction_configuration.as_str())?
.as_object()
.cloned()
.unwrap_or_default()
};
let pact_configuration = if config.pact_configuration.is_empty() {
Default::default()
} else {
serde_json::from_str::<Value>(config.pact_configuration.as_str())?
.as_object()
.cloned()
.unwrap_or_default()
};
Some(crate::content::PluginConfiguration {
interaction_configuration: interaction_configuration.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
pact_configuration: pact_configuration.iter().map(|(k, v)| (k.clone(), v.clone())).collect()
})
}
None => None
};
debug!("plugin_config = {:?}", plugin_config);

Ok((interaction_details, plugin_config))
}

async fn verify_interaction(&self, pact: &V4Pact, interaction: &(dyn V4Interaction + Send + Sync), verification_data: &InteractionVerificationData, config: &HashMap<String, Value>) -> anyhow::Result<InteractionVerificationResult> {
Expand Down
95 changes: 95 additions & 0 deletions drivers/rust/driver/wit/plugin.wit
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,105 @@ world plugin {
values: list<tuple<string, string>>
}

// Enum of content type override. This is a hint on how the content type should be treated.
enum content-type-hint {
// Determine the form of the content using the default rules of the Pact implementation
DEFAULT,
// Contents must always be treated as a text form
TEXT,
// Contents must always be treated as a binary form
BINARY
}

// a request, response or message body
record body {
// The content type of the body in MIME format (i.e. application/json)
content-type: string,
// Bytes of the actual content
content: list<u8>,
// Content type override to apply (if required). If omitted, the default rules of the Pact implementation
// will be used
content-type-hint: option<content-type-hint>
}

/*
// Represents a matching rule
message MatchingRule {
// Type of the matching rule
string type = 1;
// Associated data for the matching rule
google.protobuf.Struct values = 2;
}
// List of matching rules
message MatchingRules {
repeated MatchingRule rule = 1;
}
// Example generator
message Generator {
// Type of generator
string type = 1;
// Associated data for the generator
google.protobuf.Struct values = 2;
}*/

// Plugin configuration added to the pact file by the configure-interaction step
record plugin-configuration {
// Data to be persisted against the interaction (as JSON)
interaction-configuration: string,
// Data to be persisted in the Pact file metadata (Global data, as JSON)
pact-configuration: string
}

/*
// All matching rules to apply
map<string, MatchingRules> rules = 2;
// Generators to apply
map<string, Generator> generators = 3;
// For message interactions, any metadata to be applied
google.protobuf.Struct messageMetadata = 4;
// Plugin specific data to be persisted in the pact file
PluginConfiguration pluginConfiguration = 5;
// Markdown/HTML formatted text representation of the interaction
string interactionMarkup = 6;
// Type of markup used
enum MarkupType {
// CommonMark format
COMMON_MARK = 0;
// HTML format
HTML = 1;
}
MarkupType interactionMarkupType = 7;
// All matching rules to apply to any message metadata
map<string, MatchingRules> metadata_rules = 9;
// Generators to apply to any message metadata
map<string, Generator> metadata_generators = 10;
}
*/
record interaction-contents {
// Description of what part this interaction belongs to (in the case of there being more than one, for instance,
// request/response messages)
part-name: string,
// Contents for the interaction
contents: body,
// Plugin specific data to be persisted in the pact file
plugin-configuration: plugin-configuration
}

// Details for the interaction to configure
record interaction-details {
interaction: list<interaction-contents>,
plugin-config: option<plugin-configuration>
}

// Init function is called after the plugin is loaded. It needs to return the plugin catalog
// entries to be added to the global catalog
export init: func(implementation: string, version: string) -> list<catalogue-entry>;

// Updated catalogue. This will be sent when the core catalogue has been updated (probably by a plugin loading).
export update-catalogue: func(catalogue: list<catalogue-entry>);

// Request to configure/setup the interaction for later verification. Data returned will be persisted in the pact file.
export configure-interaction: func(content-type: string, config-json: string) -> result<interaction-details, string>;
}
2 changes: 1 addition & 1 deletion plugins/jwt/lua-plugin/jwt.lua
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ end
function jwt.sign_token(config, header, private_key, base_token)
if header["alg"] ~= "RS512" then
logger("Signature algorithm is set to " .. header["alg"])
error("Only the RS512 alogirthim is supported at the moment")
error("Only the RS512 algorithm is supported at the moment")
end

local signature = rsa_sign(base_token, private_key)
Expand Down
Loading

0 comments on commit db7582c

Please sign in to comment.