Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GOL-207 Initial support for exporting resources #124

Merged
merged 9 commits into from
Feb 23, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.0"
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
Loading