Skip to content

Commit

Permalink
GOL-207 Initial support for exporting resources (#124)
Browse files Browse the repository at this point in the history
* Initial support for exporting resources

* Updated wasm-rpc

* ./scripts/generate-openapi-yaml.sh

* Submodule updates

* Reenabled test_validate_function_result_stdio

* Tests and fixes and resource drop support

* Fixes

* Update wasm-rpc

* Added new test-template
  • Loading branch information
vigoo committed Feb 23, 2024
1 parent 32d76da commit 1cba0fc
Show file tree
Hide file tree
Showing 68 changed files with 6,471 additions and 572 deletions.
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fred = { version = "8.0.0", features = ["metrics", "serde-json", "partial-tracin
futures = "0.3"
futures-core = "0.3.29"
futures-util = "0.3.29"
golem-wasm-ast = "0.1.5"
golem-wasm-ast = "0.2.1"
http = "1.0.0" # keep in sync with wasmtime
humantime-serde = "1.1.1"
hyper = { version = "1.0.1", features = ["full"] } # keep in sync with wasmtime
Expand Down
2 changes: 1 addition & 1 deletion golem-cli
156 changes: 155 additions & 1 deletion golem-common/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,15 +913,169 @@ pub enum WrappedFunctionType {
WriteRemote,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct ParsedFunctionName {
pub interface: Option<String>,
pub function: String,
}

impl ParsedFunctionName {
pub fn new(interface: Option<String>, function: String) -> Self {
Self {
interface,
function,
}
}

pub fn method_as_static(&self) -> Option<ParsedFunctionName> {
if self.function.starts_with("[method]") {
Some(ParsedFunctionName {
interface: self.interface.clone(),
function: self.function.replace("[method]", "[static]"),
})
} else {
None
}
}
}

pub fn parse_function_name(name: &str) -> ParsedFunctionName {
let parts = name.match_indices('/').collect::<Vec<_>>();
match parts.len() {
1 => ParsedFunctionName::new(
Some(name[0..parts[0].0].to_string()),
name[(parts[0].0 + 1)..name.len()].to_string(),
),
2 => ParsedFunctionName::new(
Some(name[0..parts[1].0].to_string()),
name[(parts[1].0 + 1)..name.len()].to_string(),
),
3 => {
let instance = &name[0..parts[1].0];
let resource_name = &name[(parts[1].0 + 1)..parts[2].0];
let function_name = &name[(parts[2].0 + 1)..name.len()];

match function_name {
"new" => ParsedFunctionName::new(
Some(instance.to_string()),
format!("[constructor]{}", resource_name),
),
"drop" => ParsedFunctionName::new(
Some(instance.to_string()),
format!("[drop]{}", resource_name),
),
_ => ParsedFunctionName::new(
Some(instance.to_string()),
format!("[method]{}.{}", resource_name, function_name),
),
}
}
_ => ParsedFunctionName::new(None, name.to_string()),
}
}

#[cfg(test)]
mod tests {
use crate::model::AccountId;
use crate::model::{parse_function_name, AccountId};
use crate::model::{CallingConvention, InvocationKey, Timestamp};
use crate::model::{OplogEntry, WrappedFunctionType};
use bincode::{Decode, Encode};
use golem_wasm_rpc::protobuf::{val, Val, ValResult};
use serde::{Deserialize, Serialize};

#[test]
fn parse_function_name_global() {
let parsed = parse_function_name("run-example");
assert_eq!(parsed.interface, None);
assert_eq!(parsed.function, "run-example");
}

#[test]
fn parse_function_name_in_exported_interface_no_package() {
let parsed = parse_function_name("interface/fn1");
assert_eq!(parsed.interface, Some("interface".to_string()));
assert_eq!(parsed.function, "fn1".to_string());
}

#[test]
fn parse_function_name_in_exported_interface() {
let parsed = parse_function_name("ns:name/interface/fn1");
assert_eq!(parsed.interface, Some("ns:name/interface".to_string()));
assert_eq!(parsed.function, "fn1".to_string());
}

#[test]
fn parse_function_name_constructor_syntax_sugar() {
let parsed = parse_function_name("ns:name/interface/resource1/new");
assert_eq!(parsed.interface, Some("ns:name/interface".to_string()));
assert_eq!(parsed.function, "[constructor]resource1".to_string());
}

#[test]
fn parse_function_name_constructor() {
let parsed = parse_function_name("ns:name/interface/[constructor]resource1");
assert_eq!(parsed.interface, Some("ns:name/interface".to_string()));
assert_eq!(parsed.function, "[constructor]resource1".to_string());
}

#[test]
fn parse_function_name_method_syntax_sugar() {
let parsed = parse_function_name("ns:name/interface/resource1/do-something");
assert_eq!(parsed.interface, Some("ns:name/interface".to_string()));
assert_eq!(
parsed.function,
"[method]resource1.do-something".to_string()
);
}

#[test]
fn parse_function_name_method() {
let parsed = parse_function_name("ns:name/interface/[method]resource1.do-something");
assert_eq!(parsed.interface, Some("ns:name/interface".to_string()));
assert_eq!(
parsed.function,
"[method]resource1.do-something".to_string()
);
}

#[test]
fn parse_function_name_static_method_syntax_sugar() {
// Note: the syntax sugared version cannot distinguish between method and static - so we need to check the actual existence of
// the function and fallback.
let parsed = parse_function_name("ns:name/interface/resource1/do-something-static")
.method_as_static()
.unwrap();
assert_eq!(parsed.interface, Some("ns:name/interface".to_string()));
assert_eq!(
parsed.function,
"[static]resource1.do-something-static".to_string()
);
}

#[test]
fn parse_function_name_static() {
let parsed = parse_function_name("ns:name/interface/[static]resource1.do-something-static");
assert_eq!(parsed.interface, Some("ns:name/interface".to_string()));
assert_eq!(
parsed.function,
"[static]resource1.do-something-static".to_string()
);
}

#[test]
fn parse_function_name_drop_syntax_sugar() {
let parsed = parse_function_name("ns:name/interface/resource1/drop");
assert_eq!(parsed.interface, Some("ns:name/interface".to_string()));
assert_eq!(parsed.function, "[drop]resource1".to_string());
}

#[test]
fn parse_function_name_drop() {
let parsed = parse_function_name("ns:name/interface/[drop]resource1");
assert_eq!(parsed.interface, Some("ns:name/interface".to_string()));
assert_eq!(parsed.function, "[drop]resource1".to_string());
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Encode, Decode)]
struct ExampleWithAccountId {
account_id: AccountId,
Expand Down
121 changes: 108 additions & 13 deletions golem-service-base/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@
use golem_api_grpc::proto::golem::shardmanager::{
Pod as GrpcPod, RoutingTable as GrpcRoutingTable, RoutingTableEntry as GrpcRoutingTableEntry,
};
use golem_common::model::{ShardId, TemplateId, WorkerStatus};
use golem_common::model::{parse_function_name, ShardId, TemplateId, WorkerStatus};
use golem_wasm_ast::analysis::{AnalysedResourceId, AnalysedResourceMode};
use http::Uri;
use poem_openapi::{NewType, Object, Union};
use poem_openapi::{Enum, NewType, Object, Union};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::HashMap;

Expand Down Expand Up @@ -816,6 +817,63 @@ impl Serialize for TypeBool {
}
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Enum)]
pub enum ResourceMode {
Borrowed,
Owned,
}

impl From<AnalysedResourceMode> for ResourceMode {
fn from(value: AnalysedResourceMode) -> Self {
match value {
AnalysedResourceMode::Borrowed => ResourceMode::Borrowed,
AnalysedResourceMode::Owned => ResourceMode::Owned,
}
}
}

impl From<ResourceMode> for AnalysedResourceMode {
fn from(value: ResourceMode) -> Self {
match value {
ResourceMode::Borrowed => AnalysedResourceMode::Borrowed,
ResourceMode::Owned => AnalysedResourceMode::Owned,
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Object)]
pub struct TypeHandle {
resource_id: u64,
mode: ResourceMode,
}

impl TryFrom<golem_wasm_rpc::protobuf::TypeHandle> for TypeHandle {
type Error = String;

fn try_from(value: golem_wasm_rpc::protobuf::TypeHandle) -> Result<Self, Self::Error> {
Ok(Self {
resource_id: value.resource_id,
mode: match golem_wasm_rpc::protobuf::ResourceMode::try_from(value.mode) {
Ok(golem_wasm_rpc::protobuf::ResourceMode::Borrowed) => ResourceMode::Borrowed,
Ok(golem_wasm_rpc::protobuf::ResourceMode::Owned) => ResourceMode::Owned,
Err(_) => Err("Invalid mode".to_string())?,
},
})
}
}

impl From<TypeHandle> for golem_wasm_rpc::protobuf::TypeHandle {
fn from(value: TypeHandle) -> Self {
Self {
resource_id: value.resource_id,
mode: match value.mode {
ResourceMode::Borrowed => golem_wasm_rpc::protobuf::ResourceMode::Borrowed as i32,
ResourceMode::Owned => golem_wasm_rpc::protobuf::ResourceMode::Owned as i32,
},
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Union)]
#[oai(discriminator_name = "type", one_of = true)]
pub enum Type {
Expand All @@ -840,6 +898,7 @@ pub enum Type {
U8(TypeU8),
S8(TypeS8),
Bool(TypeBool),
Handle(TypeHandle),
}

impl TryFrom<golem_wasm_rpc::protobuf::Type> for Type {
Expand Down Expand Up @@ -959,6 +1018,9 @@ impl TryFrom<golem_wasm_rpc::protobuf::Type> for Type {
Some(golem_wasm_rpc::protobuf::r#type::Type::Primitive(
golem_wasm_rpc::protobuf::TypePrimitive { primitive },
)) => Err(format!("Invalid primitive: {}", primitive)),
Some(golem_wasm_rpc::protobuf::r#type::Type::Handle(handle)) => {
Ok(Self::Handle(handle.try_into()?))
}
}
}
}
Expand Down Expand Up @@ -1100,6 +1162,11 @@ impl From<Type> for golem_wasm_rpc::protobuf::Type {
golem_wasm_rpc::protobuf::TypePrimitive { primitive: 0 },
)),
},
Type::Handle(handle) => Self {
r#type: Some(golem_wasm_rpc::protobuf::r#type::Type::Handle(
handle.into(),
)),
},
}
}
}
Expand Down Expand Up @@ -1579,6 +1646,12 @@ impl From<golem_wasm_ast::analysis::AnalysedType> for Type {
.collect(),
})
}
golem_wasm_ast::analysis::AnalysedType::Resource { id, resource_mode } => {
Type::Handle(TypeHandle {
resource_id: id.value,
mode: resource_mode.into(),
})
}
}
}
}
Expand Down Expand Up @@ -1629,6 +1702,12 @@ impl From<Type> for golem_wasm_ast::analysis::AnalysedType {
.map(|case| (case.name, case.typ.map(|t| (*t).into())))
.collect(),
),
Type::Handle(handle) => golem_wasm_ast::analysis::AnalysedType::Resource {
id: AnalysedResourceId {
value: handle.resource_id,
},
resource_mode: handle.mode.into(),
},
}
}
}
Expand Down Expand Up @@ -1660,25 +1739,41 @@ impl TemplateMetadata {
functions
}

pub fn function_by_name(&self, name: &String) -> Option<ExportFunction> {
let last_slash = name.rfind('/');
pub fn function_by_name(&self, name: &str) -> Option<ExportFunction> {
let parsed = parse_function_name(name);

match last_slash {
match &parsed.interface {
None => self.functions().iter().find(|f| f.name == *name).cloned(),
Some(last_slash_index) => {
let (instance_name, function_name) = name.split_at(last_slash_index);
let function_name = &function_name[1..];

self.instances()
Some(instance_name) => {
let exported_function = self
.instances()
.iter()
.find(|instance| instance.name == instance_name)
.find(|instance| instance.name == *instance_name)
.and_then(|instance| {
instance
.functions
.iter()
.find(|f| f.name == function_name)
.find(|f| f.name == parsed.function)
.cloned()
})
});
if exported_function.is_none() {
match parsed.method_as_static() {
Some(parsed_static) => self
.instances()
.iter()
.find(|instance| instance.name == *instance_name)
.and_then(|instance| {
instance
.functions
.iter()
.find(|f| f.name == parsed_static.function)
.cloned()
}),
None => None,
}
} else {
exported_function
}
}
}
}
Expand Down
Loading

0 comments on commit 1cba0fc

Please sign in to comment.