Replace grpc-rs with tower-grpc.

This commit replaces the grpc-rs dependency with tower-grpc.

As a result, this also replaces the rust-protobuf dependency with prost.

Prost generates far less code that has the benefit of being simple structs and
enums.

Because tower-grpc is entirely Rust native, we no longer need to depend on a
CMake or C++ compiler to be installed to build Azure Functions for Rust.

Fixes #243.
Fixes #220.
This commit is contained in:
Peter Huene 2019-04-12 22:50:40 -07:00
parent 2ef12d9b97
commit f1e3f12af6
No known key found for this signature in database
GPG Key ID: E1D265D820213D6A
45 changed files with 2782 additions and 10610 deletions

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "azure-functions-shared/protobuf"]
path = azure-functions-shared/protobuf
url = https://github.com/Azure/azure-functions-language-worker-protobuf
url = https://github.com/peterhuene/azure-functions-language-worker-protobuf

809
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -44,62 +44,6 @@ Documentation for the [latest published version](https://docs.rs/azure-functions
# Getting Started
## Install CMake
The `azure-functions` crate has a dependency on the `grpcio` crate that uses [CMake](https://cmake.org) to build and link against the gRPC native library.
CMake must be installed and on the `PATH` to be able to use Azure Functions for Rust.
### Windows
Install CMake from the [Windows installer](https://cmake.org/download/).
### macOS
The easiest way to install CMake on macOS is with [Homebrew](https://brew.sh/):
```
$ brew install cmake
```
### Linux
Use your distro's package manager to install a `cmake` (or similar) package.
For example on Debian/Ubuntu:
```
$ apt-get install cmake
```
## Install a C++ Compiler
The `grpcio` crate builds a native library that implements the gRPC runtime.
Therefore, a C++ compiler must be on your PATH.
### Windows
Install [Visual Studio 2015 or later](https://visualstudio.microsoft.com/).
### macOS
Ensure the XCode command line utilities are installed, as this will install `clang`:
```
$ xcode-select --install
```
### Linux
Use your distro's package manager to install `g++` (or similar) package:
For example on Debian/Ubuntu:
```
$ apt-get install g++
```
## Install a .NET Core SDK
A .NET Core SDK is required to synchronize Azure Functions Host binding extensions.

View File

@ -42,11 +42,19 @@ impl<'a> Invoker<'a> {
if let Type::Path(tp) = Invoker::deref_arg_type(arg_type) {
if get_generic_argument_type(last_segment_in_path(&tp.path), "Vec").is_some() {
return Some(quote!(__param.take_data().into_vec()));
return Some(quote!(__param
.data
.take()
.expect("expected parameter binding data")
.into_vec()));
}
}
Some(quote!(__param.take_data().into()))
Some(quote!(__param
.data
.take()
.expect("expected parameter binding data")
.into()))
})
.collect()
}
@ -138,8 +146,8 @@ impl ToTokens for Invoker<'_> {
quote!(#[allow(dead_code)]
fn #invoker(
__name: &str,
__req: &mut ::azure_functions::rpc::protocol::InvocationRequest,
) -> ::azure_functions::rpc::protocol::InvocationResponse {
__req: &mut ::azure_functions::rpc::InvocationRequest,
) -> ::azure_functions::rpc::InvocationResponse {
use azure_functions::{IntoVec, FromVec};
let mut #trigger_arg: Option<#trigger_type> = None;
@ -147,7 +155,12 @@ impl ToTokens for Invoker<'_> {
for __param in __req.input_data.iter_mut() {
match __param.name.as_str() {
#trigger_name => #trigger_arg = Some(#trigger_type::new(__param.take_data(), &mut __req.trigger_metadata)),
#trigger_name => #trigger_arg = Some(
#trigger_type::new(
__param.data.take().expect("expected parameter binding data"),
&mut __req.trigger_metadata
)
),
#(#arg_names => #args_for_match = Some(#arg_assignments),)*
_ => panic!(format!("unexpected parameter binding '{}'", __param.name)),
};
@ -156,13 +169,19 @@ impl ToTokens for Invoker<'_> {
let __ctx = ::azure_functions::Context::new(&__req.invocation_id, &__req.function_id, __name);
let __ret = #target(#(#args_for_call,)*);
let mut __res = ::azure_functions::rpc::protocol::InvocationResponse::new();
__res.set_invocation_id(__req.invocation_id.clone());
let mut __res = ::azure_functions::rpc::InvocationResponse {
invocation_id: __req.invocation_id.clone(),
result: Some(::azure_functions::rpc::StatusResult {
status: ::azure_functions::rpc::status_result::Status::Success as i32,
..Default::default()
}),
..Default::default()
};
#output_bindings
__res.mut_result().status =
::azure_functions::rpc::protocol::StatusResult_Status::Success;
__res
}).to_tokens(tokens);
}
}

View File

@ -12,10 +12,10 @@ impl<'a> OutputBindings<'a> {
.map(|(name, _)| {
let name_str = to_camel_case(&name.to_string());
quote!(
let mut __output_binding = ::azure_functions::rpc::protocol::ParameterBinding::new();
__output_binding.set_name(#name_str.to_string());
__output_binding.set_data(#name.unwrap().into());
__output_data.push(__output_binding);
__output_data.push(::azure_functions::rpc::ParameterBinding{
name: #name_str.to_string(),
data: Some(#name.unwrap().into()),
});
)
})
.collect()
@ -33,20 +33,20 @@ impl<'a> OutputBindings<'a> {
let conversion = OutputBindings::get_binding_conversion(inner, None);
Some(quote!(
if let Some(__ret) = __ret.#index {
let mut __output_binding = ::azure_functions::rpc::protocol::ParameterBinding::new();
__output_binding.set_name(#name.to_string());
__output_binding.set_data(#conversion);
__output_data.push(__output_binding);
__res.output_data.push(::azure_functions::rpc::ParameterBinding{
name: #name.to_string(),
data: Some(#conversion)
});
}
))
}
None => {
let conversion = OutputBindings::get_binding_conversion(ty, Some(index));
Some(quote!(
let mut __output_binding = ::azure_functions::rpc::protocol::ParameterBinding::new();
__output_binding.set_name(#name.to_string());
__output_binding.set_data(#conversion);
__output_data.push(__output_binding);
__res.output_data.push(::azure_functions::rpc::ParameterBinding{
name: #name.to_string(),
data: Some(#conversion)
});
))
}
}
@ -55,10 +55,8 @@ impl<'a> OutputBindings<'a> {
fn get_binding_conversion(ty: &Type, index: Option<Index>) -> TokenStream {
match OutputBindings::get_generic_argument_type(ty, "Vec") {
Some(_) => match index {
Some(index) => {
quote!(::azure_functions::rpc::protocol::TypedData::from_vec(__ret.#index))
}
None => quote!(::azure_functions::rpc::protocol::TypedData::from_vec(__ret)),
Some(index) => quote!(::azure_functions::rpc::TypedData::from_vec(__ret.#index)),
None => quote!(::azure_functions::rpc::TypedData::from_vec(__ret)),
},
None => match index {
Some(index) => quote!(__ret.#index.into()),
@ -132,13 +130,13 @@ impl<'a> OutputBindings<'a> {
let conversion = OutputBindings::get_binding_conversion(inner, None);
Some(quote!(
if let Some(__ret) = __ret.0 {
__res.set_return_value(#conversion);
__res.return_value = Some(#conversion);
}
))
}
None => {
let conversion = OutputBindings::get_binding_conversion(ty, Some(0.into()));
Some(quote!(__res.set_return_value(#conversion);))
Some(quote!(__res.return_value = Some(#conversion);))
}
}
} else {
@ -154,13 +152,13 @@ impl<'a> OutputBindings<'a> {
let conversion = OutputBindings::get_binding_conversion(inner, None);
Some(quote!(
if let Some(__ret) = __ret {
__res.set_return_value(#conversion);
__res.return_value = Some(#conversion);
}
))
}
None => {
let conversion = OutputBindings::get_binding_conversion(ty, None);
Some(quote!(__res.set_return_value(#conversion);))
Some(quote!(__res.return_value = Some(#conversion);))
}
}
}
@ -169,17 +167,12 @@ impl<'a> OutputBindings<'a> {
impl ToTokens for OutputBindings<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let mut output_bindings = self.get_output_argument_bindings();
output_bindings.append(&mut self.iter_output_return_bindings());
for binding in self.get_output_argument_bindings() {
binding.to_tokens(tokens);
}
if !output_bindings.is_empty() {
quote!(
{
let mut __output_data = __res.mut_output_data();
#(#output_bindings;)*
}
)
.to_tokens(tokens);
for binding in self.iter_output_return_bindings() {
binding.to_tokens(tokens);
}
match &self.0.decl.output {

View File

@ -10,9 +10,10 @@ edition = "2018"
[dependencies]
azure-functions-shared-codegen = { version = "0.7.0", path = "../azure-functions-shared-codegen" }
protobuf = "2.3.0"
grpcio = { version = "0.4.4", default-features = false, features = ["protobuf-codec"] }
futures = "0.1.26"
tower-grpc = { git = "https://github.com/tower-rs/tower-grpc", rev = "55d004e49f6c6097a9f556752bceb8b32c8c700f"}
prost = "0.5"
prost-types = "0.5"
bytes = "0.4"
serde = "1.0.90"
serde_json = "1.0.39"
serde_derive = "1.0.90"
@ -22,7 +23,7 @@ proc-macro2 = { version = "0.4.27" }
lazy_static = "1.3.0"
[build-dependencies]
protoc-grpcio = "1.0.2"
tower-grpc-build = { git = "https://github.com/tower-rs/tower-grpc", rev = "55d004e49f6c6097a9f556752bceb8b32c8c700f"}
[features]
default = []

View File

@ -4,21 +4,14 @@ use std::path::PathBuf;
const OUT_DIR_VAR: &str = "OUT_DIR";
const CACHE_DIR_NAME: &str = "cache";
const PROTOBUF_INPUT_FILES: &[&str] = &["FunctionRpc.proto", "identity/ClaimsIdentityRpc.proto"];
const OUTPUT_FILES: &[&str] = &[
"FunctionRpc.rs",
"FunctionRpc_grpc.rs",
"ClaimsIdentityRpc.rs",
];
const PROTOBUF_INPUT_FILES: &[&str] = &["FunctionRpc.proto"];
const OUTPUT_FILES: &[&str] = &["azure_functions_rpc_messages.rs"];
fn compile_protobufs(out_dir: &PathBuf, cache_dir: &PathBuf) {
protoc_grpcio::compile_grpc_protos(
PROTOBUF_INPUT_FILES,
&["protobuf/src/proto"],
&out_dir,
None,
)
.expect("Failed to compile gRPC definitions.");
tower_grpc_build::Config::new()
.enable_client(true)
.build(PROTOBUF_INPUT_FILES, &["protobuf/src/proto"])
.unwrap_or_else(|e| panic!("protobuf compilation failed: {}", e));
for file in OUTPUT_FILES.iter() {
fs::copy(out_dir.join(file), cache_dir.join(file))
@ -36,7 +29,9 @@ fn use_cached_files(out_dir: &PathBuf, cache_dir: &PathBuf) {
}
fn main() {
println!("cargo:rerun-if-changed=protobuf/src/proto/FunctionRpc.proto");
for file in PROTOBUF_INPUT_FILES {
println!("cargo:rerun-if-changed=protobuf/src/proto/{}", file);
}
let out_dir = PathBuf::from(env::var(OUT_DIR_VAR).unwrap());

View File

@ -1,550 +0,0 @@
// This file is generated by rust-protobuf 2.3.0. Do not edit
// @generated
// https://github.com/Manishearth/rust-clippy/issues/702
#![allow(unknown_lints)]
#![allow(clippy)]
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(box_pointers)]
#![allow(dead_code)]
#![allow(missing_docs)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(trivial_casts)]
#![allow(unsafe_code)]
#![allow(unused_imports)]
#![allow(unused_results)]
use protobuf::Message as Message_imported_for_functions;
use protobuf::ProtobufEnum as ProtobufEnum_imported_for_functions;
#[derive(PartialEq,Clone,Default)]
pub struct RpcClaimsIdentity {
// message fields
pub authentication_type: ::std::string::String,
pub name_claim_type: ::std::string::String,
pub role_claim_type: ::std::string::String,
pub claims: ::protobuf::RepeatedField<RpcClaim>,
// special fields
pub unknown_fields: ::protobuf::UnknownFields,
pub cached_size: ::protobuf::CachedSize,
}
impl RpcClaimsIdentity {
pub fn new() -> RpcClaimsIdentity {
::std::default::Default::default()
}
// string authentication_type = 1;
pub fn clear_authentication_type(&mut self) {
self.authentication_type.clear();
}
// Param is passed by value, moved
pub fn set_authentication_type(&mut self, v: ::std::string::String) {
self.authentication_type = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_authentication_type(&mut self) -> &mut ::std::string::String {
&mut self.authentication_type
}
// Take field
pub fn take_authentication_type(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.authentication_type, ::std::string::String::new())
}
pub fn get_authentication_type(&self) -> &str {
&self.authentication_type
}
// string name_claim_type = 2;
pub fn clear_name_claim_type(&mut self) {
self.name_claim_type.clear();
}
// Param is passed by value, moved
pub fn set_name_claim_type(&mut self, v: ::std::string::String) {
self.name_claim_type = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_name_claim_type(&mut self) -> &mut ::std::string::String {
&mut self.name_claim_type
}
// Take field
pub fn take_name_claim_type(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.name_claim_type, ::std::string::String::new())
}
pub fn get_name_claim_type(&self) -> &str {
&self.name_claim_type
}
// string role_claim_type = 3;
pub fn clear_role_claim_type(&mut self) {
self.role_claim_type.clear();
}
// Param is passed by value, moved
pub fn set_role_claim_type(&mut self, v: ::std::string::String) {
self.role_claim_type = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_role_claim_type(&mut self) -> &mut ::std::string::String {
&mut self.role_claim_type
}
// Take field
pub fn take_role_claim_type(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.role_claim_type, ::std::string::String::new())
}
pub fn get_role_claim_type(&self) -> &str {
&self.role_claim_type
}
// repeated .RpcClaim claims = 4;
pub fn clear_claims(&mut self) {
self.claims.clear();
}
// Param is passed by value, moved
pub fn set_claims(&mut self, v: ::protobuf::RepeatedField<RpcClaim>) {
self.claims = v;
}
// Mutable pointer to the field.
pub fn mut_claims(&mut self) -> &mut ::protobuf::RepeatedField<RpcClaim> {
&mut self.claims
}
// Take field
pub fn take_claims(&mut self) -> ::protobuf::RepeatedField<RpcClaim> {
::std::mem::replace(&mut self.claims, ::protobuf::RepeatedField::new())
}
pub fn get_claims(&self) -> &[RpcClaim] {
&self.claims
}
}
impl ::protobuf::Message for RpcClaimsIdentity {
fn is_initialized(&self) -> bool {
for v in &self.claims {
if !v.is_initialized() {
return false;
}
};
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream) -> ::protobuf::ProtobufResult<()> {
while !is.eof()? {
let (field_number, wire_type) = is.read_tag_unpack()?;
match field_number {
1 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.authentication_type)?;
},
2 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.name_claim_type)?;
},
3 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.role_claim_type)?;
},
4 => {
::protobuf::rt::read_repeated_message_into(wire_type, is, &mut self.claims)?;
},
_ => {
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
},
};
}
::std::result::Result::Ok(())
}
// Compute sizes of nested messages
#[allow(unused_variables)]
fn compute_size(&self) -> u32 {
let mut my_size = 0;
if !self.authentication_type.is_empty() {
my_size += ::protobuf::rt::string_size(1, &self.authentication_type);
}
if !self.name_claim_type.is_empty() {
my_size += ::protobuf::rt::string_size(2, &self.name_claim_type);
}
if !self.role_claim_type.is_empty() {
my_size += ::protobuf::rt::string_size(3, &self.role_claim_type);
}
for value in &self.claims {
let len = value.compute_size();
my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
};
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
self.cached_size.set(my_size);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream) -> ::protobuf::ProtobufResult<()> {
if !self.authentication_type.is_empty() {
os.write_string(1, &self.authentication_type)?;
}
if !self.name_claim_type.is_empty() {
os.write_string(2, &self.name_claim_type)?;
}
if !self.role_claim_type.is_empty() {
os.write_string(3, &self.role_claim_type)?;
}
for v in &self.claims {
os.write_tag(4, ::protobuf::wire_format::WireTypeLengthDelimited)?;
os.write_raw_varint32(v.get_cached_size())?;
v.write_to_with_cached_sizes(os)?;
};
os.write_unknown_fields(self.get_unknown_fields())?;
::std::result::Result::Ok(())
}
fn get_cached_size(&self) -> u32 {
self.cached_size.get()
}
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
&self.unknown_fields
}
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
&mut self.unknown_fields
}
fn as_any(&self) -> &::std::any::Any {
self as &::std::any::Any
}
fn as_any_mut(&mut self) -> &mut ::std::any::Any {
self as &mut ::std::any::Any
}
fn into_any(self: Box<Self>) -> ::std::boxed::Box<::std::any::Any> {
self
}
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
Self::descriptor_static()
}
fn new() -> RpcClaimsIdentity {
RpcClaimsIdentity::new()
}
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
static mut descriptor: ::protobuf::lazy::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::lazy::Lazy {
lock: ::protobuf::lazy::ONCE_INIT,
ptr: 0 as *const ::protobuf::reflect::MessageDescriptor,
};
unsafe {
descriptor.get(|| {
let mut fields = ::std::vec::Vec::new();
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"authentication_type",
|m: &RpcClaimsIdentity| { &m.authentication_type },
|m: &mut RpcClaimsIdentity| { &mut m.authentication_type },
));
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"name_claim_type",
|m: &RpcClaimsIdentity| { &m.name_claim_type },
|m: &mut RpcClaimsIdentity| { &mut m.name_claim_type },
));
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"role_claim_type",
|m: &RpcClaimsIdentity| { &m.role_claim_type },
|m: &mut RpcClaimsIdentity| { &mut m.role_claim_type },
));
fields.push(::protobuf::reflect::accessor::make_repeated_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<RpcClaim>>(
"claims",
|m: &RpcClaimsIdentity| { &m.claims },
|m: &mut RpcClaimsIdentity| { &mut m.claims },
));
::protobuf::reflect::MessageDescriptor::new::<RpcClaimsIdentity>(
"RpcClaimsIdentity",
fields,
file_descriptor_proto()
)
})
}
}
fn default_instance() -> &'static RpcClaimsIdentity {
static mut instance: ::protobuf::lazy::Lazy<RpcClaimsIdentity> = ::protobuf::lazy::Lazy {
lock: ::protobuf::lazy::ONCE_INIT,
ptr: 0 as *const RpcClaimsIdentity,
};
unsafe {
instance.get(RpcClaimsIdentity::new)
}
}
}
impl ::protobuf::Clear for RpcClaimsIdentity {
fn clear(&mut self) {
self.clear_authentication_type();
self.clear_name_claim_type();
self.clear_role_claim_type();
self.clear_claims();
self.unknown_fields.clear();
}
}
impl ::std::fmt::Debug for RpcClaimsIdentity {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::protobuf::text_format::fmt(self, f)
}
}
impl ::protobuf::reflect::ProtobufValue for RpcClaimsIdentity {
fn as_ref(&self) -> ::protobuf::reflect::ProtobufValueRef {
::protobuf::reflect::ProtobufValueRef::Message(self)
}
}
#[derive(PartialEq,Clone,Default)]
pub struct RpcClaim {
// message fields
pub value: ::std::string::String,
pub field_type: ::std::string::String,
// special fields
pub unknown_fields: ::protobuf::UnknownFields,
pub cached_size: ::protobuf::CachedSize,
}
impl RpcClaim {
pub fn new() -> RpcClaim {
::std::default::Default::default()
}
// string value = 1;
pub fn clear_value(&mut self) {
self.value.clear();
}
// Param is passed by value, moved
pub fn set_value(&mut self, v: ::std::string::String) {
self.value = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_value(&mut self) -> &mut ::std::string::String {
&mut self.value
}
// Take field
pub fn take_value(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.value, ::std::string::String::new())
}
pub fn get_value(&self) -> &str {
&self.value
}
// string type = 2;
pub fn clear_field_type(&mut self) {
self.field_type.clear();
}
// Param is passed by value, moved
pub fn set_field_type(&mut self, v: ::std::string::String) {
self.field_type = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_field_type(&mut self) -> &mut ::std::string::String {
&mut self.field_type
}
// Take field
pub fn take_field_type(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.field_type, ::std::string::String::new())
}
pub fn get_field_type(&self) -> &str {
&self.field_type
}
}
impl ::protobuf::Message for RpcClaim {
fn is_initialized(&self) -> bool {
true
}
fn merge_from(&mut self, is: &mut ::protobuf::CodedInputStream) -> ::protobuf::ProtobufResult<()> {
while !is.eof()? {
let (field_number, wire_type) = is.read_tag_unpack()?;
match field_number {
1 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.value)?;
},
2 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.field_type)?;
},
_ => {
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
},
};
}
::std::result::Result::Ok(())
}
// Compute sizes of nested messages
#[allow(unused_variables)]
fn compute_size(&self) -> u32 {
let mut my_size = 0;
if !self.value.is_empty() {
my_size += ::protobuf::rt::string_size(1, &self.value);
}
if !self.field_type.is_empty() {
my_size += ::protobuf::rt::string_size(2, &self.field_type);
}
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
self.cached_size.set(my_size);
my_size
}
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream) -> ::protobuf::ProtobufResult<()> {
if !self.value.is_empty() {
os.write_string(1, &self.value)?;
}
if !self.field_type.is_empty() {
os.write_string(2, &self.field_type)?;
}
os.write_unknown_fields(self.get_unknown_fields())?;
::std::result::Result::Ok(())
}
fn get_cached_size(&self) -> u32 {
self.cached_size.get()
}
fn get_unknown_fields(&self) -> &::protobuf::UnknownFields {
&self.unknown_fields
}
fn mut_unknown_fields(&mut self) -> &mut ::protobuf::UnknownFields {
&mut self.unknown_fields
}
fn as_any(&self) -> &::std::any::Any {
self as &::std::any::Any
}
fn as_any_mut(&mut self) -> &mut ::std::any::Any {
self as &mut ::std::any::Any
}
fn into_any(self: Box<Self>) -> ::std::boxed::Box<::std::any::Any> {
self
}
fn descriptor(&self) -> &'static ::protobuf::reflect::MessageDescriptor {
Self::descriptor_static()
}
fn new() -> RpcClaim {
RpcClaim::new()
}
fn descriptor_static() -> &'static ::protobuf::reflect::MessageDescriptor {
static mut descriptor: ::protobuf::lazy::Lazy<::protobuf::reflect::MessageDescriptor> = ::protobuf::lazy::Lazy {
lock: ::protobuf::lazy::ONCE_INIT,
ptr: 0 as *const ::protobuf::reflect::MessageDescriptor,
};
unsafe {
descriptor.get(|| {
let mut fields = ::std::vec::Vec::new();
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"value",
|m: &RpcClaim| { &m.value },
|m: &mut RpcClaim| { &mut m.value },
));
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"type",
|m: &RpcClaim| { &m.field_type },
|m: &mut RpcClaim| { &mut m.field_type },
));
::protobuf::reflect::MessageDescriptor::new::<RpcClaim>(
"RpcClaim",
fields,
file_descriptor_proto()
)
})
}
}
fn default_instance() -> &'static RpcClaim {
static mut instance: ::protobuf::lazy::Lazy<RpcClaim> = ::protobuf::lazy::Lazy {
lock: ::protobuf::lazy::ONCE_INIT,
ptr: 0 as *const RpcClaim,
};
unsafe {
instance.get(RpcClaim::new)
}
}
}
impl ::protobuf::Clear for RpcClaim {
fn clear(&mut self) {
self.clear_value();
self.clear_field_type();
self.unknown_fields.clear();
}
}
impl ::std::fmt::Debug for RpcClaim {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
::protobuf::text_format::fmt(self, f)
}
}
impl ::protobuf::reflect::ProtobufValue for RpcClaim {
fn as_ref(&self) -> ::protobuf::reflect::ProtobufValueRef {
::protobuf::reflect::ProtobufValueRef::Message(self)
}
}
static file_descriptor_proto_data: &'static [u8] = b"\
\n\x20identity/ClaimsIdentityRpc.proto\"\xb7\x01\n\x11RpcClaimsIdentity\
\x12/\n\x13authentication_type\x18\x01\x20\x01(\tR\x12authenticationType\
\x12&\n\x0fname_claim_type\x18\x02\x20\x01(\tR\rnameClaimType\x12&\n\x0f\
role_claim_type\x18\x03\x20\x01(\tR\rroleClaimType\x12!\n\x06claims\x18\
\x04\x20\x03(\x0b2\t.RpcClaimR\x06claims\"4\n\x08RpcClaim\x12\x14\n\x05v\
alue\x18\x01\x20\x01(\tR\x05value\x12\x12\n\x04type\x18\x02\x20\x01(\tR\
\x04typeb\x06proto3\
";
static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy {
lock: ::protobuf::lazy::ONCE_INIT,
ptr: 0 as *const ::protobuf::descriptor::FileDescriptorProto,
};
fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
::protobuf::parse_from_bytes(file_descriptor_proto_data).unwrap()
}
pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
unsafe {
file_descriptor_proto_lazy.get(|| {
parse_descriptor_proto()
})
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,63 +0,0 @@
// This file is generated. Do not edit
// @generated
// https://github.com/Manishearth/rust-clippy/issues/702
#![allow(unknown_lints)]
#![allow(clippy)]
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(box_pointers)]
#![allow(dead_code)]
#![allow(missing_docs)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(trivial_casts)]
#![allow(unsafe_code)]
#![allow(unused_imports)]
#![allow(unused_results)]
const METHOD_FUNCTION_RPC_EVENT_STREAM: ::grpcio::Method<super::FunctionRpc::StreamingMessage, super::FunctionRpc::StreamingMessage> = ::grpcio::Method {
ty: ::grpcio::MethodType::Duplex,
name: "/AzureFunctionsRpcMessages.FunctionRpc/EventStream",
req_mar: ::grpcio::Marshaller { ser: ::grpcio::pb_ser, de: ::grpcio::pb_de },
resp_mar: ::grpcio::Marshaller { ser: ::grpcio::pb_ser, de: ::grpcio::pb_de },
};
#[derive(Clone)]
pub struct FunctionRpcClient {
client: ::grpcio::Client,
}
impl FunctionRpcClient {
pub fn new(channel: ::grpcio::Channel) -> Self {
FunctionRpcClient {
client: ::grpcio::Client::new(channel),
}
}
pub fn event_stream_opt(&self, opt: ::grpcio::CallOption) -> ::grpcio::Result<(::grpcio::ClientDuplexSender<super::FunctionRpc::StreamingMessage>, ::grpcio::ClientDuplexReceiver<super::FunctionRpc::StreamingMessage>)> {
self.client.duplex_streaming(&METHOD_FUNCTION_RPC_EVENT_STREAM, opt)
}
pub fn event_stream(&self) -> ::grpcio::Result<(::grpcio::ClientDuplexSender<super::FunctionRpc::StreamingMessage>, ::grpcio::ClientDuplexReceiver<super::FunctionRpc::StreamingMessage>)> {
self.event_stream_opt(::grpcio::CallOption::default())
}
pub fn spawn<F>(&self, f: F) where F: ::futures::Future<Item = (), Error = ()> + Send + 'static {
self.client.spawn(f)
}
}
pub trait FunctionRpc {
fn event_stream(&mut self, ctx: ::grpcio::RpcContext, stream: ::grpcio::RequestStream<super::FunctionRpc::StreamingMessage>, sink: ::grpcio::DuplexSink<super::FunctionRpc::StreamingMessage>);
}
pub fn create_function_rpc<S: FunctionRpc + Send + Clone + 'static>(s: S) -> ::grpcio::Service {
let mut builder = ::grpcio::ServiceBuilder::new();
let mut instance = s.clone();
builder = builder.add_duplex_streaming_handler(&METHOD_FUNCTION_RPC_EVENT_STREAM, move |ctx, req, resp| {
instance.event_stream(ctx, req, resp)
});
builder.build()
}

View File

@ -0,0 +1,515 @@
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct NullableString {
#[prost(oneof="nullable_string::String", tags="1")]
pub string: ::std::option::Option<nullable_string::String>,
}
pub mod nullable_string {
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum String {
#[prost(string, tag="1")]
Value(std::string::String),
}
}
/// Light-weight representation of a .NET System.Security.Claims.ClaimsIdentity object.
/// This is the same serialization as found in EasyAuth, and needs to be kept in sync with
/// its ClaimsIdentitySlim definition, as seen in the WebJobs extension:
/// https://github.com/Azure/azure-webjobs-sdk-extensions/blob/dev/src/WebJobs.Extensions.Http/ClaimsIdentitySlim.cs
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RpcClaimsIdentity {
#[prost(message, optional, tag="1")]
pub authentication_type: ::std::option::Option<NullableString>,
#[prost(message, optional, tag="2")]
pub name_claim_type: ::std::option::Option<NullableString>,
#[prost(message, optional, tag="3")]
pub role_claim_type: ::std::option::Option<NullableString>,
#[prost(message, repeated, tag="4")]
pub claims: ::std::vec::Vec<RpcClaim>,
}
/// Light-weight representation of a .NET System.Security.Claims.Claim object.
/// This is the same serialization as found in EasyAuth, and needs to be kept in sync with
/// its ClaimSlim definition, as seen in the WebJobs extension:
/// https://github.com/Azure/azure-webjobs-sdk-extensions/blob/dev/src/WebJobs.Extensions.Http/ClaimSlim.cs
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RpcClaim {
#[prost(string, tag="1")]
pub value: std::string::String,
#[prost(string, tag="2")]
pub r#type: std::string::String,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StreamingMessage {
/// Used to identify message between host and worker
#[prost(string, tag="1")]
pub request_id: std::string::String,
/// Payload of the message
#[prost(oneof="streaming_message::Content", tags="20, 17, 16, 15, 14, 12, 13, 6, 7, 8, 9, 4, 5, 21, 2, 25, 26")]
pub content: ::std::option::Option<streaming_message::Content>,
}
pub mod streaming_message {
/// Payload of the message
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Content {
/// Worker initiates stream
#[prost(message, tag="20")]
StartStream(super::StartStream),
/// Host sends capabilities/init data to worker
#[prost(message, tag="17")]
WorkerInitRequest(super::WorkerInitRequest),
/// Worker responds after initializing with its capabilities & status
#[prost(message, tag="16")]
WorkerInitResponse(super::WorkerInitResponse),
/// Worker periodically sends empty heartbeat message to host
#[prost(message, tag="15")]
WorkerHeartbeat(super::WorkerHeartbeat),
/// Host sends terminate message to worker.
/// Worker terminates if it can, otherwise host terminates after a grace period
#[prost(message, tag="14")]
WorkerTerminate(super::WorkerTerminate),
/// Add any worker relevant status to response
#[prost(message, tag="12")]
WorkerStatusRequest(super::WorkerStatusRequest),
#[prost(message, tag="13")]
WorkerStatusResponse(super::WorkerStatusResponse),
/// On file change event, host sends notification to worker
#[prost(message, tag="6")]
FileChangeEventRequest(super::FileChangeEventRequest),
/// Worker requests a desired action (restart worker, reload function)
#[prost(message, tag="7")]
WorkerActionResponse(super::WorkerActionResponse),
/// Host sends required metadata to worker to load function
#[prost(message, tag="8")]
FunctionLoadRequest(super::FunctionLoadRequest),
/// Worker responds after loading with the load result
#[prost(message, tag="9")]
FunctionLoadResponse(super::FunctionLoadResponse),
/// Host requests a given invocation
#[prost(message, tag="4")]
InvocationRequest(super::InvocationRequest),
/// Worker responds to a given invocation
#[prost(message, tag="5")]
InvocationResponse(super::InvocationResponse),
/// Host sends cancel message to attempt to cancel an invocation.
/// If an invocation is cancelled, host will receive an invocation response with status cancelled.
#[prost(message, tag="21")]
InvocationCancel(super::InvocationCancel),
/// Worker logs a message back to the host
#[prost(message, tag="2")]
RpcLog(super::RpcLog),
#[prost(message, tag="25")]
FunctionEnvironmentReloadRequest(super::FunctionEnvironmentReloadRequest),
#[prost(message, tag="26")]
FunctionEnvironmentReloadResponse(super::FunctionEnvironmentReloadResponse),
}
}
// Process.Start required info
// connection details
// protocol type
// protocol version
/// Worker sends the host information identifying itself
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StartStream {
/// id of the worker
#[prost(string, tag="2")]
pub worker_id: std::string::String,
}
/// Host requests the worker to initialize itself
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WorkerInitRequest {
/// version of the host sending init request
#[prost(string, tag="1")]
pub host_version: std::string::String,
/// A map of host supported features/capabilities
#[prost(map="string, string", tag="2")]
pub capabilities: ::std::collections::HashMap<std::string::String, std::string::String>,
/// inform worker of supported categories and their levels
/// i.e. Worker = Verbose, Function.MyFunc = None
#[prost(map="string, enumeration(rpc_log::Level)", tag="3")]
pub log_categories: ::std::collections::HashMap<std::string::String, i32>,
}
/// Worker responds with the result of initializing itself
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WorkerInitResponse {
/// Version of worker
#[prost(string, tag="1")]
pub worker_version: std::string::String,
/// A map of worker supported features/capabilities
#[prost(map="string, string", tag="2")]
pub capabilities: ::std::collections::HashMap<std::string::String, std::string::String>,
/// Status of the response
#[prost(message, optional, tag="3")]
pub result: ::std::option::Option<StatusResult>,
}
/// Used by the host to determine success/failure/cancellation
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct StatusResult {
/// Status for the given result
#[prost(enumeration="status_result::Status", tag="4")]
pub status: i32,
/// Specific message about the result
#[prost(string, tag="1")]
pub result: std::string::String,
/// Exception message (if exists) for the status
#[prost(message, optional, tag="2")]
pub exception: ::std::option::Option<RpcException>,
/// Captured logs or relevant details can use the logs property
#[prost(message, repeated, tag="3")]
pub logs: ::std::vec::Vec<RpcLog>,
}
pub mod status_result {
/// Indicates Failure/Success/Cancelled
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum Status {
Failure = 0,
Success = 1,
Cancelled = 2,
}
}
// TODO: investigate grpc heartbeat - don't limit to grpc implemention
/// Message is empty by design - Will add more fields in future if needed
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WorkerHeartbeat {
}
/// Warning before killing the process after grace_period
/// Worker self terminates ..no response on this
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WorkerTerminate {
#[prost(message, optional, tag="1")]
pub grace_period: ::std::option::Option<::prost_types::Duration>,
}
/// Host notifies worker of file content change
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FileChangeEventRequest {
/// type for this event
#[prost(enumeration="file_change_event_request::Type", tag="1")]
pub r#type: i32,
/// full file path for the file change notification
#[prost(string, tag="2")]
pub full_path: std::string::String,
/// Name of the function affected
#[prost(string, tag="3")]
pub name: std::string::String,
}
pub mod file_change_event_request {
/// Types of File change operations (See link for more info: https://msdn.microsoft.com/en-us/library/t6xf43e0(v=vs.110).aspx)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum Type {
Unknown = 0,
Created = 1,
Deleted = 2,
Changed = 4,
Renamed = 8,
All = 15,
}
}
/// Indicates whether worker reloaded successfully or needs a restart
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WorkerActionResponse {
/// action for this response
#[prost(enumeration="worker_action_response::Action", tag="1")]
pub action: i32,
/// text reason for the response
#[prost(string, tag="2")]
pub reason: std::string::String,
}
pub mod worker_action_response {
/// indicates whether a restart is needed, or reload succesfully
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum Action {
Restart = 0,
Reload = 1,
}
}
/// NOT USED
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WorkerStatusRequest {
}
/// NOT USED
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct WorkerStatusResponse {
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FunctionEnvironmentReloadRequest {
/// Environment variables from the current process
#[prost(map="string, string", tag="1")]
pub environment_variables: ::std::collections::HashMap<std::string::String, std::string::String>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FunctionEnvironmentReloadResponse {
/// Status of the response
#[prost(message, optional, tag="3")]
pub result: ::std::option::Option<StatusResult>,
}
/// Host tells the worker to load a Function
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FunctionLoadRequest {
/// unique function identifier (avoid name collisions, facilitate reload case)
#[prost(string, tag="1")]
pub function_id: std::string::String,
/// Metadata for the request
#[prost(message, optional, tag="2")]
pub metadata: ::std::option::Option<RpcFunctionMetadata>,
/// A flag indicating if managed dependency is enabled or not
#[prost(bool, tag="3")]
pub managed_dependency_enabled: bool,
}
/// Worker tells host result of reload
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct FunctionLoadResponse {
/// unique function identifier
#[prost(string, tag="1")]
pub function_id: std::string::String,
/// Result of load operation
///
/// TODO: return type expected?
#[prost(message, optional, tag="2")]
pub result: ::std::option::Option<StatusResult>,
/// Result of load operation
#[prost(bool, tag="3")]
pub is_dependency_downloaded: bool,
}
/// Information on how a Function should be loaded and its bindings
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RpcFunctionMetadata {
/// TODO: do we want the host's name - the language worker might do a better job of assignment than the host
#[prost(string, tag="4")]
pub name: std::string::String,
/// base directory for the Function
#[prost(string, tag="1")]
pub directory: std::string::String,
/// Script file specified
#[prost(string, tag="2")]
pub script_file: std::string::String,
/// Entry point specified
#[prost(string, tag="3")]
pub entry_point: std::string::String,
/// Bindings info
#[prost(map="string, message", tag="6")]
pub bindings: ::std::collections::HashMap<std::string::String, BindingInfo>,
}
/// Host requests worker to invoke a Function
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct InvocationRequest {
/// Unique id for each invocation
#[prost(string, tag="1")]
pub invocation_id: std::string::String,
/// Unique id for each Function
#[prost(string, tag="2")]
pub function_id: std::string::String,
/// Input bindings (include trigger)
#[prost(message, repeated, tag="3")]
pub input_data: ::std::vec::Vec<ParameterBinding>,
/// binding metadata from trigger
#[prost(map="string, message", tag="4")]
pub trigger_metadata: ::std::collections::HashMap<std::string::String, TypedData>,
}
/// Host requests worker to cancel invocation
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct InvocationCancel {
/// Unique id for invocation
#[prost(string, tag="2")]
pub invocation_id: std::string::String,
/// Time period before force shutdown
///
/// could also use absolute time
#[prost(message, optional, tag="1")]
pub grace_period: ::std::option::Option<::prost_types::Duration>,
}
/// Worker responds with status of Invocation
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct InvocationResponse {
/// Unique id for invocation
#[prost(string, tag="1")]
pub invocation_id: std::string::String,
/// Output binding data
#[prost(message, repeated, tag="2")]
pub output_data: ::std::vec::Vec<ParameterBinding>,
/// data returned from Function (for $return and triggers with return support)
#[prost(message, optional, tag="4")]
pub return_value: ::std::option::Option<TypedData>,
/// Status of the invocation (success/failure/canceled)
#[prost(message, optional, tag="3")]
pub result: ::std::option::Option<StatusResult>,
}
/// Used to encapsulate data which could be a variety of types
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TypedData {
#[prost(oneof="typed_data::Data", tags="1, 2, 3, 4, 5, 6, 7")]
pub data: ::std::option::Option<typed_data::Data>,
}
pub mod typed_data {
#[derive(Clone, PartialEq, ::prost::Oneof)]
pub enum Data {
#[prost(string, tag="1")]
String(std::string::String),
#[prost(string, tag="2")]
Json(std::string::String),
#[prost(bytes, tag="3")]
Bytes(std::vec::Vec<u8>),
#[prost(bytes, tag="4")]
Stream(std::vec::Vec<u8>),
#[prost(message, tag="5")]
Http(Box<super::RpcHttp>),
#[prost(sint64, tag="6")]
Int(i64),
#[prost(double, tag="7")]
Double(f64),
}
}
/// Used to describe a given binding on invocation
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ParameterBinding {
/// Name for the binding
#[prost(string, tag="1")]
pub name: std::string::String,
/// Data for the binding
#[prost(message, optional, tag="2")]
pub data: ::std::option::Option<TypedData>,
}
/// Used to describe a given binding on load
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BindingInfo {
/// Type of binding (e.g. HttpTrigger)
#[prost(string, tag="2")]
pub r#type: std::string::String,
/// Direction of the given binding
#[prost(enumeration="binding_info::Direction", tag="3")]
pub direction: i32,
#[prost(enumeration="binding_info::DataType", tag="4")]
pub data_type: i32,
}
pub mod binding_info {
/// Indicates whether it is an input or output binding (or a fancy inout binding)
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum Direction {
In = 0,
Out = 1,
Inout = 2,
}
/// Indicates the type of the data for the binding
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum DataType {
Undefined = 0,
String = 1,
Binary = 2,
Stream = 3,
}
}
/// Used to send logs back to the Host
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RpcLog {
/// Unique id for invocation (if exists)
#[prost(string, tag="1")]
pub invocation_id: std::string::String,
/// TOD: This should be an enum
/// Category for the log (startup, load, invocation, etc.)
#[prost(string, tag="2")]
pub category: std::string::String,
/// Level for the given log message
#[prost(enumeration="rpc_log::Level", tag="3")]
pub level: i32,
/// Message for the given log
#[prost(string, tag="4")]
pub message: std::string::String,
/// Id for the even associated with this log (if exists)
#[prost(string, tag="5")]
pub event_id: std::string::String,
/// Exception (if exists)
#[prost(message, optional, tag="6")]
pub exception: ::std::option::Option<RpcException>,
/// json serialized property bag, or could use a type scheme like map<string, TypedData>
#[prost(string, tag="7")]
pub properties: std::string::String,
}
pub mod rpc_log {
/// Matching ILogger semantics
/// https://github.com/aspnet/Logging/blob/9506ccc3f3491488fe88010ef8b9eb64594abf95/src/Microsoft.Extensions.Logging/Logger.cs
/// Level for the Log
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum Level {
Trace = 0,
Debug = 1,
Information = 2,
Warning = 3,
Error = 4,
Critical = 5,
None = 6,
}
}
/// Encapsulates an Exception
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RpcException {
/// Source of the exception
#[prost(string, tag="3")]
pub source: std::string::String,
/// Stack trace for the exception
#[prost(string, tag="1")]
pub stack_trace: std::string::String,
/// Textual message describing hte exception
#[prost(string, tag="2")]
pub message: std::string::String,
}
/// TODO - solidify this or remove it
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct RpcHttp {
#[prost(string, tag="1")]
pub method: std::string::String,
#[prost(string, tag="2")]
pub url: std::string::String,
#[prost(map="string, string", tag="3")]
pub headers: ::std::collections::HashMap<std::string::String, std::string::String>,
#[prost(message, optional, boxed, tag="4")]
pub body: ::std::option::Option<::std::boxed::Box<TypedData>>,
#[prost(map="string, string", tag="10")]
pub params: ::std::collections::HashMap<std::string::String, std::string::String>,
#[prost(string, tag="12")]
pub status_code: std::string::String,
#[prost(map="string, string", tag="15")]
pub query: ::std::collections::HashMap<std::string::String, std::string::String>,
#[prost(bool, tag="16")]
pub enable_content_negotiation: bool,
#[prost(message, optional, boxed, tag="17")]
pub raw_body: ::std::option::Option<::std::boxed::Box<TypedData>>,
#[prost(message, repeated, tag="18")]
pub identities: ::std::vec::Vec<RpcClaimsIdentity>,
}
pub mod client {
use ::tower_grpc::codegen::client::*;
use super::StreamingMessage;
/// Interface exported by the server.
#[derive(Debug, Clone)]
pub struct FunctionRpc<T> {
inner: grpc::Grpc<T>,
}
impl<T> FunctionRpc<T> {
pub fn new(inner: T) -> Self {
let inner = grpc::Grpc::new(inner);
Self { inner }
}
pub fn poll_ready<R>(&mut self) -> futures::Poll<(), grpc::Status>
where T: grpc::GrpcService<R>,
{
self.inner.poll_ready()
}
/// Interface exported by the server.
pub fn event_stream<R, B>(&mut self, request: grpc::Request<B>) -> grpc::streaming::ResponseFuture<StreamingMessage, T::Future>
where T: grpc::GrpcService<R>,
B: futures::Stream<Item = StreamingMessage>,
B: grpc::Encodable<R>,
{
let path = http::PathAndQuery::from_static("/AzureFunctionsRpcMessages.FunctionRpc/EventStream");
self.inner.streaming(request, path)
}
}
}

@ -1 +1 @@
Subproject commit 8450073938d10bae8e73901cfcc52fac4a3745fa
Subproject commit 2601636fcb9f36a9d0baf8c90602f108c61b6834

View File

@ -3,7 +3,7 @@ use crate::codegen::{
get_boolean_value, get_string_value, iter_attribute_args, macro_panic,
quotable::{QuotableBorrowedStr, QuotableOption},
};
use crate::rpc::protocol;
use crate::rpc;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use serde::{ser::SerializeMap, Serialize, Serializer};
@ -17,7 +17,7 @@ pub struct Function {
pub disabled: bool,
pub bindings: Cow<'static, [Binding]>,
pub invoker_name: Option<Cow<'static, str>>,
pub invoker: Option<fn(&str, &mut protocol::InvocationRequest) -> protocol::InvocationResponse>,
pub invoker: Option<fn(&str, &mut rpc::InvocationRequest) -> rpc::InvocationResponse>,
pub manifest_dir: Option<Cow<'static, str>>,
pub file: Option<Cow<'static, str>>,
}

View File

@ -5,6 +5,7 @@
#![cfg_attr(feature = "unstable", feature(proc_macro_diagnostic))]
#![deny(missing_docs)]
#![deny(unused_extern_crates)]
#![allow(clippy::large_enum_variant)]
#[doc(hidden)]
pub mod codegen;
@ -15,17 +16,11 @@ pub mod util;
#[doc(hidden)]
#[allow(renamed_and_removed_lints)]
pub mod rpc {
pub mod protocol {
use azure_functions_shared_codegen::generated_mod;
use azure_functions_shared_codegen::generated_mod;
generated_mod!(FunctionRpc);
generated_mod!(FunctionRpc_grpc);
generated_mod!(ClaimsIdentityRpc);
generated_mod!(azure_functions_rpc_messages);
pub use self::ClaimsIdentityRpc::*;
pub use self::FunctionRpc::*;
pub use self::FunctionRpc_grpc::*;
}
pub use self::azure_functions_rpc_messages::*;
}
pub use self::context::*;

View File

@ -11,11 +11,15 @@ edition = "2018"
[dependencies]
azure-functions-shared = { version = "0.7.0", path = "../azure-functions-shared" }
azure-functions-codegen = { version = "0.7.0", path = "../azure-functions-codegen" }
tower-h2 = { git = "https://github.com/tower-rs/tower-h2", rev = "a3c958a243ef3d837933b31bf186ba0e6b0e60c9"}
tower-request-modifier = { git = "https://github.com/tower-rs/tower-http", rev = "e7ef6ef623411342ee89c0a83ef924db6206143a" }
tower-grpc = { git = "https://github.com/tower-rs/tower-grpc", rev = "55d004e49f6c6097a9f556752bceb8b32c8c700f"}
tower-service = "0.2.0"
tower-util = { git = "https://github.com/tower-rs/tower", rev = "979c1399127bdb269c751b99c12b3adfad55463c" }
log = { version = "0.4.6", features = ["std"] }
grpcio = { version = "0.4.4", default-features = false, features = ["protobuf-codec"] }
futures = "0.1.26"
clap = "2.33.0"
tokio-threadpool = "0.1.13"
tokio = "0.1.18"
serde = "1.0.90"
serde_json = "1.0.39"
serde_derive = "1.0.90"
@ -25,7 +29,6 @@ lazy_static = "1.3.0"
tempfile = "3.0.7"
ctrlc = "3.1.1"
backtrace = "0.3.15"
crossbeam-channel = "0.3.8"
fs_extra = "1.1.0"
[features]

View File

@ -1,5 +1,7 @@
use crate::http::Body;
use crate::rpc::protocol;
use crate::{
http::Body,
rpc::{typed_data::Data, TypedData},
};
use serde::de::Error;
use serde::Deserialize;
use serde_json::{from_str, Result, Value};
@ -59,44 +61,31 @@ use std::str::from_utf8;
/// }
/// ```
#[derive(Debug, Clone)]
pub struct Blob(protocol::TypedData);
pub struct Blob(TypedData);
impl Blob {
/// Gets the content of the blob as a string.
///
/// Returns None if there is no valid string representation of the blob.
pub fn as_str(&self) -> Option<&str> {
if self.0.has_string() {
return Some(self.0.get_string());
match &self.0.data {
Some(Data::String(s)) => Some(s),
Some(Data::Json(s)) => Some(s),
Some(Data::Bytes(b)) => from_utf8(b).ok(),
Some(Data::Stream(s)) => from_utf8(s).ok(),
_ => None,
}
if self.0.has_json() {
return Some(self.0.get_json());
}
if self.0.has_bytes() {
return from_utf8(self.0.get_bytes()).map(|s| s).ok();
}
if self.0.has_stream() {
return from_utf8(self.0.get_stream()).map(|s| s).ok();
}
None
}
/// Gets the content of the blob as a slice of bytes.
pub fn as_bytes(&self) -> &[u8] {
if self.0.has_string() {
return self.0.get_string().as_bytes();
match &self.0.data {
Some(Data::String(s)) => s.as_bytes(),
Some(Data::Json(s)) => s.as_bytes(),
Some(Data::Bytes(b)) => b,
Some(Data::Stream(s)) => s,
_ => panic!("unexpected data for blob content"),
}
if self.0.has_json() {
return self.0.get_json().as_bytes();
}
if self.0.has_bytes() {
return self.0.get_bytes();
}
if self.0.has_stream() {
return self.0.get_stream();
}
panic!("unexpected data for blob content");
}
/// Deserializes the blob as JSON to the requested type.
@ -119,76 +108,72 @@ impl fmt::Display for Blob {
impl<'a> From<&'a str> for Blob {
fn from(content: &'a str) -> Self {
let mut data = protocol::TypedData::new();
data.set_string(content.to_owned());
Blob(data)
Blob(TypedData {
data: Some(Data::String(content.to_owned())),
})
}
}
impl From<String> for Blob {
fn from(content: String) -> Self {
let mut data = protocol::TypedData::new();
data.set_string(content);
Blob(data)
Blob(TypedData {
data: Some(Data::String(content)),
})
}
}
impl From<&Value> for Blob {
fn from(content: &Value) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(content.to_string());
Blob(data)
Blob(TypedData {
data: Some(Data::Json(content.to_string())),
})
}
}
impl From<Value> for Blob {
fn from(content: Value) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(content.to_string());
Blob(data)
Blob(TypedData {
data: Some(Data::Json(content.to_string())),
})
}
}
impl<'a> From<&'a [u8]> for Blob {
fn from(content: &'a [u8]) -> Self {
let mut data = protocol::TypedData::new();
data.set_bytes(content.to_owned());
Blob(data)
Blob(TypedData {
data: Some(Data::Bytes(content.to_owned())),
})
}
}
impl From<Vec<u8>> for Blob {
fn from(content: Vec<u8>) -> Self {
let mut data = protocol::TypedData::new();
data.set_bytes(content);
Blob(data)
Blob(TypedData {
data: Some(Data::Bytes(content)),
})
}
}
#[doc(hidden)]
impl From<protocol::TypedData> for Blob {
fn from(data: protocol::TypedData) -> Self {
impl From<TypedData> for Blob {
fn from(data: TypedData) -> Self {
Blob(data)
}
}
impl Into<String> for Blob {
fn into(mut self) -> String {
if self.0.has_string() {
return self.0.take_string();
fn into(self) -> String {
match self.0.data {
Some(Data::String(s)) => s,
Some(Data::Json(s)) => s,
Some(Data::Bytes(b)) => {
String::from_utf8(b).expect("blob does not contain valid UTF-8 bytes")
}
Some(Data::Stream(s)) => {
String::from_utf8(s).expect("blob does not contain valid UTF-8 bytes")
}
_ => panic!("unexpected data for blob content"),
}
if self.0.has_json() {
return self.0.take_json();
}
if self.0.has_bytes() {
return String::from_utf8(self.0.take_bytes())
.expect("blob does not contain valid UTF-8 bytes");
}
if self.0.has_stream() {
return String::from_utf8(self.0.take_stream())
.expect("blob does not contain valid UTF-8 bytes");
}
panic!("unexpected data for blob content");
}
}
@ -203,46 +188,32 @@ impl Into<Value> for Blob {
}
impl Into<Vec<u8>> for Blob {
fn into(mut self) -> Vec<u8> {
if self.0.has_string() {
return self.0.take_string().into_bytes();
fn into(self) -> Vec<u8> {
match self.0.data {
Some(Data::String(s)) => s.into_bytes(),
Some(Data::Json(s)) => s.into_bytes(),
Some(Data::Bytes(b)) => b,
Some(Data::Stream(s)) => s,
_ => panic!("unexpected data for blob content"),
}
if self.0.has_json() {
return self.0.take_json().into_bytes();
}
if self.0.has_bytes() {
return self.0.take_bytes();
}
if self.0.has_stream() {
return self.0.take_stream();
}
panic!("unexpected data for blob content");
}
}
impl<'a> Into<Body<'a>> for Blob {
fn into(mut self) -> Body<'a> {
if self.0.has_string() {
return self.0.take_string().into();
fn into(self) -> Body<'a> {
match self.0.data {
Some(Data::String(s)) => s.into(),
Some(Data::Json(s)) => Body::Json(Cow::from(s)),
Some(Data::Bytes(b)) => b.into(),
Some(Data::Stream(s)) => s.into(),
_ => panic!("unexpected data for blob content"),
}
if self.0.has_json() {
return Body::Json(Cow::from(self.0.take_json()));
}
if self.0.has_bytes() {
return self.0.take_bytes().into();
}
if self.0.has_stream() {
return self.0.take_stream().into();
}
panic!("unexpected data for blob content");
}
}
#[doc(hidden)]
impl Into<protocol::TypedData> for Blob {
fn into(self) -> protocol::TypedData {
impl Into<TypedData> for Blob {
fn into(self) -> TypedData {
self.0
}
}
@ -262,28 +233,31 @@ mod tests {
let blob: Blob = BLOB.into();
assert_eq!(blob.as_str().unwrap(), BLOB);
let data: protocol::TypedData = blob.into();
assert_eq!(data.get_string(), BLOB);
let data: TypedData = blob.into();
assert_eq!(data.data, Some(Data::String(BLOB.to_string())));
}
#[test]
fn it_has_json_content() {
#[derive(Serialize, Deserialize)]
struct Data {
struct SerializedData {
message: String,
};
const MESSAGE: &'static str = "test";
let data = Data {
let data = SerializedData {
message: MESSAGE.to_string(),
};
let blob: Blob = ::serde_json::to_value(data).unwrap().into();
assert_eq!(blob.as_json::<Data>().unwrap().message, MESSAGE);
assert_eq!(blob.as_json::<SerializedData>().unwrap().message, MESSAGE);
let data: protocol::TypedData = blob.into();
assert_eq!(data.get_json(), r#"{"message":"test"}"#);
let data: TypedData = blob.into();
assert_eq!(
data.data,
Some(Data::Json(r#"{"message":"test"}"#.to_string()))
);
}
#[test]
@ -293,8 +267,8 @@ mod tests {
let blob: Blob = BLOB.into();
assert_eq!(blob.as_bytes(), BLOB);
let data: protocol::TypedData = blob.into();
assert_eq!(data.get_bytes(), BLOB);
let data: TypedData = blob.into();
assert_eq!(data.data, Some(Data::Bytes(BLOB.to_owned())));
}
#[test]
@ -343,8 +317,9 @@ mod tests {
fn it_converts_from_typed_data() {
const BLOB: &'static str = "hello world!";
let mut data = protocol::TypedData::new();
data.set_string(BLOB.to_string());
let data = TypedData {
data: Some(Data::String(BLOB.to_string())),
};
let blob: Blob = data.into();
assert_eq!(blob.as_str().unwrap(), BLOB);
@ -389,18 +364,15 @@ mod tests {
#[test]
fn it_converts_to_typed_data() {
let blob: Blob = "test".into();
let data: protocol::TypedData = blob.into();
assert!(data.has_string());
assert_eq!(data.get_string(), "test");
let data: TypedData = blob.into();
assert_eq!(data.data, Some(Data::String("test".to_string())));
let blob: Blob = to_value("test").unwrap().into();
let data: protocol::TypedData = blob.into();
assert!(data.has_json());
assert_eq!(data.get_json(), r#""test""#);
let data: TypedData = blob.into();
assert_eq!(data.data, Some(Data::Json(r#""test""#.to_string())));
let blob: Blob = vec![1, 2, 3].into();
let data: protocol::TypedData = blob.into();
assert!(data.has_bytes());
assert_eq!(data.get_bytes(), [1, 2, 3]);
let data: TypedData = blob.into();
assert_eq!(data.data, Some(Data::Bytes(vec![1, 2, 3])));
}
}

View File

@ -1,7 +1,9 @@
use crate::bindings::Blob;
use crate::blob::Properties;
use crate::rpc::protocol;
use crate::util::convert_from;
use crate::{
bindings::Blob,
blob::Properties,
rpc::{typed_data::Data, TypedData},
util::convert_from,
};
use serde_json::from_str;
use std::collections::HashMap;
@ -51,27 +53,35 @@ pub struct BlobTrigger {
impl BlobTrigger {
#[doc(hidden)]
pub fn new(
data: protocol::TypedData,
metadata: &mut HashMap<String, protocol::TypedData>,
) -> Self {
pub fn new(data: TypedData, metadata: &mut HashMap<String, TypedData>) -> Self {
BlobTrigger {
blob: data.into(),
path: metadata
.get_mut(PATH_KEY)
.map_or(String::new(), protocol::TypedData::take_string),
uri: metadata.get(URI_KEY).map_or(String::new(), |x| {
convert_from(x)
.unwrap_or_else(|| panic!("failed to read '{}' from metadata", URI_KEY))
.remove(PATH_KEY)
.map(|data| match data.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for 'path' metadata key"),
})
.expect("expected a blob path"),
uri: metadata.get(URI_KEY).map_or(String::new(), |data| {
convert_from(data).unwrap_or_else(|| panic!("failed to convert uri"))
}),
properties: metadata
.get(PROPERTIES_KEY)
.map_or(Default::default(), |x| {
from_str(x.get_json()).expect("failed to deserialize blob properties")
.remove(PROPERTIES_KEY)
.map_or(Properties::default(), |data| match data.data {
Some(Data::Json(s)) => {
from_str(&s).expect("failed to deserialize blob properties")
}
_ => panic!("expected a string for properties"),
}),
metadata: metadata
.remove(METADATA_KEY)
.map_or(HashMap::new(), |data| match data.data {
Some(Data::Json(s)) => {
from_str(&s).expect("failed to deserialize blob metadata")
}
_ => panic!("expected a string for metadata"),
}),
metadata: metadata.get(METADATA_KEY).map_or(Default::default(), |x| {
from_str(x.get_json()).expect("failed to deserialize blob metadata")
}),
}
}
}
@ -130,31 +140,41 @@ mod tests {
"BlobTierLastModifiedTime": null
});
let mut data = protocol::TypedData::new();
data.set_string(BLOB.to_string());
let data = TypedData {
data: Some(Data::String(BLOB.to_string())),
};
let mut metadata = HashMap::new();
let mut value = protocol::TypedData::new();
value.set_string(PATH.to_string());
metadata.insert(PATH_KEY.to_string(), value);
let mut value = protocol::TypedData::new();
value.set_json("\"".to_string() + URI + "\"");
metadata.insert(URI_KEY.to_string(), value);
let mut value = protocol::TypedData::new();
value.set_json(properties.to_string());
metadata.insert(PROPERTIES_KEY.to_string(), value);
let mut value = protocol::TypedData::new();
let mut user_metadata = HashMap::new();
user_metadata.insert(
USER_METADAT_KEY.to_string(),
USER_METADATA_VALUE.to_string(),
);
value.set_json(to_string(&user_metadata).unwrap());
metadata.insert(METADATA_KEY.to_string(), value);
let mut metadata = HashMap::new();
metadata.insert(
PATH_KEY.to_string(),
TypedData {
data: Some(Data::String(PATH.to_string())),
},
);
metadata.insert(
URI_KEY.to_string(),
TypedData {
data: Some(Data::Json("\"".to_string() + URI + "\"")),
},
);
metadata.insert(
PROPERTIES_KEY.to_string(),
TypedData {
data: Some(Data::Json(properties.to_string())),
},
);
metadata.insert(
METADATA_KEY.to_string(),
TypedData {
data: Some(Data::Json(to_string(&user_metadata).unwrap())),
},
);
let trigger = BlobTrigger::new(data, &mut metadata);
assert_eq!(trigger.path, PATH);

View File

@ -1,4 +1,9 @@
use crate::{http::Body, rpc::protocol, util::convert_from, FromVec, IntoVec};
use crate::{
http::Body,
rpc::{typed_data::Data, TypedData},
util::convert_from,
FromVec, IntoVec,
};
use serde_json::{from_str, Map, Value};
use std::borrow::Cow;
use std::fmt;
@ -152,7 +157,7 @@ impl From<Value> for CosmosDbDocument {
}
#[doc(hidden)]
impl IntoVec<CosmosDbDocument> for protocol::TypedData {
impl IntoVec<CosmosDbDocument> for TypedData {
fn into_vec(self) -> Vec<CosmosDbDocument> {
if self.data.is_none() {
return vec![];
@ -168,17 +173,19 @@ impl IntoVec<CosmosDbDocument> for protocol::TypedData {
}
#[doc(hidden)]
impl FromVec<CosmosDbDocument> for protocol::TypedData {
impl FromVec<CosmosDbDocument> for TypedData {
fn from_vec(vec: Vec<CosmosDbDocument>) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(Value::Array(vec.into_iter().map(|d| d.0).collect()).to_string());
data
TypedData {
data: Some(Data::Json(
Value::Array(vec.into_iter().map(|d| d.0).collect()).to_string(),
)),
}
}
}
#[doc(hidden)]
impl From<protocol::TypedData> for CosmosDbDocument {
fn from(data: protocol::TypedData) -> Self {
impl From<TypedData> for CosmosDbDocument {
fn from(data: TypedData) -> Self {
if data.data.is_none() {
return CosmosDbDocument(Value::Null);
}
@ -227,11 +234,11 @@ impl<'a> Into<Body<'a>> for Vec<CosmosDbDocument> {
}
#[doc(hidden)]
impl Into<protocol::TypedData> for CosmosDbDocument {
fn into(self) -> protocol::TypedData {
let mut data = protocol::TypedData::new();
data.set_json(self.0.to_string());
data
impl Into<TypedData> for CosmosDbDocument {
fn into(self) -> TypedData {
TypedData {
data: Some(Data::Json(self.0.to_string())),
}
}
}
@ -312,10 +319,11 @@ mod tests {
#[test]
fn it_converts_from_typed_data() {
let mut data = protocol::TypedData::new();
data.set_json(r#"{ "foo": "bar" }"#.to_string());
let document: CosmosDbDocument = TypedData {
data: Some(Data::Json(r#"{ "foo": "bar" }"#.to_string())),
}
.into();
let document: CosmosDbDocument = data.into();
let data = document.as_object().unwrap();
assert_eq!(data["foo"].as_str().unwrap(), "bar");
}
@ -326,8 +334,7 @@ mod tests {
let data = document.as_object().unwrap();
assert_eq!(data["foo"].as_str().unwrap(), "bar");
let data: protocol::TypedData = document.into();
assert!(data.has_json());
assert_eq!(data.get_json(), r#"{"foo":"bar"}"#);
let data: TypedData = document.into();
assert_eq!(data.data, Some(Data::Json(r#"{"foo":"bar"}"#.to_string())));
}
}

View File

@ -1,5 +1,4 @@
use crate::rpc::protocol;
use crate::util::convert_from;
use crate::{rpc::TypedData, util::convert_from};
use serde_json::Value;
use std::collections::HashMap;
@ -58,10 +57,7 @@ pub struct CosmosDbTrigger {
impl CosmosDbTrigger {
#[doc(hidden)]
pub fn new(
data: protocol::TypedData,
_metadata: &mut HashMap<String, protocol::TypedData>,
) -> Self {
pub fn new(data: TypedData, _metadata: &mut HashMap<String, TypedData>) -> Self {
let value = convert_from(&data).expect("expected JSON document data");
match value {
Value::Array(array) => CosmosDbTrigger { documents: array },
@ -73,6 +69,7 @@ impl CosmosDbTrigger {
#[cfg(test)]
mod tests {
use super::*;
use crate::rpc::typed_data::Data;
#[test]
fn it_constructs() {
@ -103,8 +100,9 @@ mod tests {
}
]"#;
let mut data = protocol::TypedData::new();
data.set_json(DOCUMENTS.to_string());
let data = TypedData {
data: Some(Data::Json(DOCUMENTS.to_string())),
};
let mut metadata = HashMap::new();
let trigger = CosmosDbTrigger::new(data, &mut metadata);

View File

@ -1,5 +1,7 @@
use crate::rpc::protocol;
use crate::util::deserialize_datetime;
use crate::{
rpc::{typed_data::Data, TypedData},
util::deserialize_datetime,
};
use chrono::{DateTime, Utc};
use serde_derive::Deserialize;
use serde_json::from_str;
@ -51,12 +53,11 @@ pub struct EventGridEvent {
impl EventGridEvent {
#[doc(hidden)]
pub fn new(data: protocol::TypedData, _: &mut HashMap<String, protocol::TypedData>) -> Self {
if !data.has_json() {
panic!("expected JSON data for Event Grid trigger binding");
pub fn new(data: TypedData, _: &mut HashMap<String, TypedData>) -> Self {
match data.data {
Some(Data::Json(s)) => from_str(&s).expect("failed to parse Event Grid JSON"),
_ => panic!("expected JSON data for Event Grid trigger binding"),
}
from_str(data.get_json()).expect("failed to parse Event Grid JSON")
}
}
@ -68,8 +69,9 @@ mod tests {
fn it_constructs() {
const EVENT: &'static str = r#"{"topic":"/subscriptions/{subscription-id}/resourceGroups/Storage/providers/Microsoft.Storage/storageAccounts/xstoretestaccount","subject":"/blobServices/default/containers/oc2d2817345i200097container/blobs/oc2d2817345i20002296blob","eventType":"Microsoft.Storage.BlobCreated","eventTime":"2017-06-26T18:41:00.9584103Z","id":"831e1650-001e-001b-66ab-eeb76e069631","data":{"api":"PutBlockList","clientRequestId":"6d79dbfb-0e37-4fc4-981f-442c9ca65760","requestId":"831e1650-001e-001b-66ab-eeb76e000000","eTag":"0x8D4BCC2E4835CD0","contentType":"application/octet-stream","contentLength":524288,"blobType":"BlockBlob","url":"https://oc2d2817345i60006.blob.core.windows.net/oc2d2817345i200097container/oc2d2817345i20002296blob","sequencer":"00000000000004420000000000028963","storageDiagnostics":{"batchId":"b68529f3-68cd-4744-baa4-3c0498ec19f0"}},"dataVersion":"1","metadataVersion":"1"}"#;
let mut data = protocol::TypedData::new();
data.set_json(EVENT.to_string());
let data = TypedData {
data: Some(Data::Json(EVENT.to_string())),
};
let mut metadata = HashMap::new();

View File

@ -1,4 +1,8 @@
use crate::{http::Body, rpc::protocol, FromVec};
use crate::{
http::Body,
rpc::{typed_data::Data, TypedData},
FromVec,
};
use serde::de::Error;
use serde::Deserialize;
use serde_json::{from_str, Result, Value};
@ -58,44 +62,31 @@ use std::str::from_utf8;
/// }
/// ```
#[derive(Debug, Clone)]
pub struct EventHubMessage(protocol::TypedData);
pub struct EventHubMessage(TypedData);
impl EventHubMessage {
/// Gets the content of the message as a string.
///
/// Returns None if there is no valid string representation of the message.
pub fn as_str(&self) -> Option<&str> {
if self.0.has_string() {
return Some(self.0.get_string());
match &self.0.data {
Some(Data::String(s)) => Some(s),
Some(Data::Json(s)) => Some(s),
Some(Data::Bytes(b)) => from_utf8(b).ok(),
Some(Data::Stream(s)) => from_utf8(s).ok(),
_ => None,
}
if self.0.has_json() {
return Some(self.0.get_json());
}
if self.0.has_bytes() {
return from_utf8(self.0.get_bytes()).map(|s| s).ok();
}
if self.0.has_stream() {
return from_utf8(self.0.get_stream()).map(|s| s).ok();
}
None
}
/// Gets the content of the message as a slice of bytes.
pub fn as_bytes(&self) -> &[u8] {
if self.0.has_string() {
return self.0.get_string().as_bytes();
match &self.0.data {
Some(Data::String(s)) => s.as_bytes(),
Some(Data::Json(s)) => s.as_bytes(),
Some(Data::Bytes(b)) => b,
Some(Data::Stream(s)) => s,
_ => panic!("unexpected data for Event Hub message contents"),
}
if self.0.has_json() {
return self.0.get_json().as_bytes();
}
if self.0.has_bytes() {
return self.0.get_bytes();
}
if self.0.has_stream() {
return self.0.get_stream();
}
panic!("unexpected data for Event Hub message contents");
}
/// Deserializes the message as JSON to the requested type.
@ -119,162 +110,136 @@ impl fmt::Display for EventHubMessage {
impl<'a> From<&'a str> for EventHubMessage {
fn from(content: &'a str) -> Self {
let mut data = protocol::TypedData::new();
data.set_string(content.to_owned());
EventHubMessage(data)
EventHubMessage(TypedData {
data: Some(Data::String(content.to_owned())),
})
}
}
impl From<String> for EventHubMessage {
fn from(content: String) -> Self {
let mut data = protocol::TypedData::new();
data.set_string(content);
EventHubMessage(data)
EventHubMessage(TypedData {
data: Some(Data::String(content)),
})
}
}
impl From<&Value> for EventHubMessage {
fn from(content: &Value) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(content.to_string());
EventHubMessage(data)
EventHubMessage(TypedData {
data: Some(Data::Json(content.to_string())),
})
}
}
impl From<Value> for EventHubMessage {
fn from(content: Value) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(content.to_string());
EventHubMessage(data)
EventHubMessage(TypedData {
data: Some(Data::Json(content.to_string())),
})
}
}
impl<'a> From<&'a [u8]> for EventHubMessage {
fn from(content: &'a [u8]) -> Self {
let mut data = protocol::TypedData::new();
data.set_bytes(content.to_owned());
EventHubMessage(data)
EventHubMessage(TypedData {
data: Some(Data::Bytes(content.to_owned())),
})
}
}
impl From<Vec<u8>> for EventHubMessage {
fn from(content: Vec<u8>) -> Self {
let mut data = protocol::TypedData::new();
data.set_bytes(content);
EventHubMessage(TypedData {
data: Some(Data::Bytes(content)),
})
}
}
#[doc(hidden)]
impl From<TypedData> for EventHubMessage {
fn from(data: TypedData) -> Self {
EventHubMessage(data)
}
}
#[doc(hidden)]
impl From<protocol::TypedData> for EventHubMessage {
fn from(data: protocol::TypedData) -> Self {
EventHubMessage(data)
}
}
#[doc(hidden)]
impl FromVec<EventHubMessage> for protocol::TypedData {
impl FromVec<EventHubMessage> for TypedData {
fn from_vec(vec: Vec<EventHubMessage>) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(Value::Array(vec.into_iter().map(Into::into).collect()).to_string());
data
TypedData {
data: Some(Data::Json(
Value::Array(vec.into_iter().map(Into::into).collect()).to_string(),
)),
}
}
}
impl Into<String> for EventHubMessage {
fn into(mut self) -> String {
if self.0.has_string() {
return self.0.take_string();
fn into(self) -> String {
match self.0.data {
Some(Data::String(s)) => s,
Some(Data::Json(s)) => s,
Some(Data::Bytes(b)) => {
String::from_utf8(b).expect("Event Hub message does not contain valid UTF-8 bytes")
}
Some(Data::Stream(s)) => {
String::from_utf8(s).expect("Event Hub message does not contain valid UTF-8 bytes")
}
_ => panic!("unexpected data for Event Hub message content"),
}
if self.0.has_json() {
return self.0.take_json();
}
if self.0.has_bytes() {
return String::from_utf8(self.0.take_bytes())
.expect("Event Hub message does not contain valid UTF-8 bytes");
}
if self.0.has_stream() {
return String::from_utf8(self.0.take_stream())
.expect("Event Hub message does not contain valid UTF-8 bytes");
}
panic!("unexpected data for Event Hub message content");
}
}
impl Into<Value> for EventHubMessage {
fn into(mut self) -> Value {
if self.0.has_string() {
return Value::String(self.0.take_string());
}
if self.0.has_json() {
return from_str(self.0.get_json())
.expect("queue message does not contain valid JSON data");
}
// TODO: this is not an efficient encoding
if self.0.has_bytes() {
return Value::Array(
self.0
.get_bytes()
.iter()
fn into(self) -> Value {
// TODO: this is not an efficient encoding for bytes/stream
match self.0.data {
Some(Data::String(s)) => Value::String(s),
Some(Data::Json(s)) => {
from_str(&s).expect("Event Hub message does not contain valid JSON data")
}
Some(Data::Bytes(b)) => Value::Array(
b.iter()
.map(|n| Value::Number(u64::from(*n).into()))
.collect(),
);
}
// TODO: this is not an efficient encoding
if self.0.has_stream() {
return Value::Array(
self.0
.get_stream()
.iter()
),
Some(Data::Stream(s)) => Value::Array(
s.iter()
.map(|n| Value::Number(u64::from(*n).into()))
.collect(),
);
),
_ => panic!("unexpected data for Event Hub message content"),
}
panic!("unexpected data for Event Hub message content");
}
}
impl Into<Vec<u8>> for EventHubMessage {
fn into(mut self) -> Vec<u8> {
if self.0.has_string() {
return self.0.take_string().into_bytes();
fn into(self) -> Vec<u8> {
match self.0.data {
Some(Data::String(s)) => s.into_bytes(),
Some(Data::Json(s)) => s.into_bytes(),
Some(Data::Bytes(b)) => b,
Some(Data::Stream(s)) => s,
_ => panic!("unexpected data for Event Hub message content"),
}
if self.0.has_json() {
return self.0.take_json().into_bytes();
}
if self.0.has_bytes() {
return self.0.take_bytes();
}
if self.0.has_stream() {
return self.0.take_stream();
}
panic!("unexpected data for Event Hub message content");
}
}
impl<'a> Into<Body<'a>> for EventHubMessage {
fn into(mut self) -> Body<'a> {
if self.0.has_string() {
return self.0.take_string().into();
fn into(self) -> Body<'a> {
match self.0.data {
Some(Data::String(s)) => s.into(),
Some(Data::Json(s)) => Body::Json(Cow::from(s)),
Some(Data::Bytes(b)) => b.into(),
Some(Data::Stream(s)) => s.into(),
_ => panic!("unexpected data for Event Hub message content"),
}
if self.0.has_json() {
return Body::Json(Cow::from(self.0.take_json()));
}
if self.0.has_bytes() {
return self.0.take_bytes().into();
}
if self.0.has_stream() {
return self.0.take_stream().into();
}
panic!("unexpected data for blob content");
}
}
#[doc(hidden)]
impl Into<protocol::TypedData> for EventHubMessage {
fn into(self) -> protocol::TypedData {
impl Into<TypedData> for EventHubMessage {
fn into(self) -> TypedData {
self.0
}
}
@ -293,28 +258,34 @@ mod tests {
let message: EventHubMessage = MESSAGE.into();
assert_eq!(message.as_str().unwrap(), MESSAGE);
let data: protocol::TypedData = message.into();
assert_eq!(data.get_string(), MESSAGE);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::String(MESSAGE.to_string())));
}
#[test]
fn it_has_json_content() {
#[derive(Serialize, Deserialize)]
struct Data {
struct SerializedData {
message: String,
};
const MESSAGE: &'static str = "test";
let data = Data {
let data = SerializedData {
message: MESSAGE.to_string(),
};
let message: EventHubMessage = ::serde_json::to_value(data).unwrap().into();
assert_eq!(message.as_json::<Data>().unwrap().message, MESSAGE);
assert_eq!(
message.as_json::<SerializedData>().unwrap().message,
MESSAGE
);
let data: protocol::TypedData = message.into();
assert_eq!(data.get_json(), r#"{"message":"test"}"#);
let data: TypedData = message.into();
assert_eq!(
data.data,
Some(Data::Json(r#"{"message":"test"}"#.to_string()))
);
}
#[test]
@ -324,8 +295,8 @@ mod tests {
let message: EventHubMessage = MESSAGE.into();
assert_eq!(message.as_bytes(), MESSAGE);
let data: protocol::TypedData = message.into();
assert_eq!(data.get_bytes(), MESSAGE);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::Bytes(MESSAGE.to_owned())));
}
#[test]
@ -409,18 +380,15 @@ mod tests {
#[test]
fn it_converts_to_typed_data() {
let message: EventHubMessage = "test".into();
let data: protocol::TypedData = message.into();
assert!(data.has_string());
assert_eq!(data.get_string(), "test");
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::String("test".to_string())));
let message: EventHubMessage = to_value("test").unwrap().into();
let data: protocol::TypedData = message.into();
assert!(data.has_json());
assert_eq!(data.get_json(), r#""test""#);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::Json(r#""test""#.to_string())));
let message: EventHubMessage = vec![1, 2, 3].into();
let data: protocol::TypedData = message.into();
assert!(data.has_bytes());
assert_eq!(data.get_bytes(), [1, 2, 3]);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::Bytes([1, 2, 3].to_vec())));
}
}

View File

@ -1,7 +1,9 @@
use crate::bindings::EventHubMessage;
use crate::event_hub::{PartitionContext, SystemProperties};
use crate::rpc::protocol;
use crate::util::convert_from;
use crate::{
bindings::EventHubMessage,
event_hub::{PartitionContext, SystemProperties},
rpc::{typed_data::Data, TypedData},
util::convert_from,
};
use chrono::{DateTime, Utc};
use serde_json::{from_str, Value};
use std::collections::HashMap;
@ -59,17 +61,18 @@ pub struct EventHubTrigger {
impl EventHubTrigger {
#[doc(hidden)]
pub fn new(
data: protocol::TypedData,
metadata: &mut HashMap<String, protocol::TypedData>,
) -> Self {
pub fn new(data: TypedData, metadata: &mut HashMap<String, TypedData>) -> Self {
EventHubTrigger {
message: data.into(),
partition_context: from_str(
metadata
match &metadata
.get(PARTITION_CONTEXT_KEY)
.expect("expected partition context")
.get_json(),
.data
{
Some(Data::Json(s)) => s,
_ => panic!("expected JSON data for partition context"),
},
)
.expect("failed to deserialize partition context"),
enqueued_time: convert_from(
@ -79,14 +82,21 @@ impl EventHubTrigger {
)
.expect("failed to convert enqueued time"),
offset: metadata
.get_mut(OFFSET_KEY)
.expect("expected offset")
.take_string(),
.remove(OFFSET_KEY)
.map(|offset| match offset.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for offset"),
})
.expect("expected offset"),
properties: from_str(
metadata
match &metadata
.get(PROPERTIES_KEY)
.expect("expected properties")
.get_json(),
.data
{
Some(Data::Json(s)) => s,
_ => panic!("expected JSON data for properties"),
},
)
.expect("failed to deserialize properties"),
sequence_number: convert_from(
@ -96,10 +106,14 @@ impl EventHubTrigger {
)
.expect("failed to convert sequence number"),
system_properties: from_str(
metadata
match &metadata
.get(SYSTEM_PROPERTIES_KEY)
.expect("expected system properties")
.get_json(),
.data
{
Some(Data::Json(s)) => s,
_ => panic!("expected JSON data for system properties"),
},
)
.expect("failed to deserialize system properties"),
}
@ -128,8 +142,9 @@ mod tests {
const USER_PROPERTY_VALUE: &str = "property value";
const PARTITION_KEY: &str = "partition key";
let mut data = protocol::TypedData::new();
data.set_string(MESSAGE.to_string());
let data = TypedData {
data: Some(Data::String(MESSAGE.to_string())),
};
let mut metadata = HashMap::new();
@ -156,29 +171,44 @@ mod tests {
enqueued_time: DateTime::<Utc>::from_str(ENQUEUED_TIME).unwrap(),
};
let mut value = protocol::TypedData::new();
value.set_json(serde_json::to_string(&context).unwrap());
metadata.insert(PARTITION_CONTEXT_KEY.to_string(), value);
let mut value = protocol::TypedData::new();
value.set_string(ENQUEUED_TIME.to_string());
metadata.insert(ENQUEUED_TIME_KEY.to_string(), value);
let mut value = protocol::TypedData::new();
value.set_string(OFFSET.to_string());
metadata.insert(OFFSET_KEY.to_string(), value);
let mut value = protocol::TypedData::new();
value.set_json(properties.to_string());
metadata.insert(PROPERTIES_KEY.to_string(), value);
let mut value = protocol::TypedData::new();
value.set_int(SEQUENCE_NUMBER);
metadata.insert(SEQUENCE_NUMBER_KEY.to_string(), value);
let mut value = protocol::TypedData::new();
value.set_json(serde_json::to_string(&system_properties).unwrap());
metadata.insert(SYSTEM_PROPERTIES_KEY.to_string(), value);
metadata.insert(
PARTITION_CONTEXT_KEY.to_string(),
TypedData {
data: Some(Data::Json(serde_json::to_string(&context).unwrap())),
},
);
metadata.insert(
ENQUEUED_TIME_KEY.to_string(),
TypedData {
data: Some(Data::String(ENQUEUED_TIME.to_string())),
},
);
metadata.insert(
OFFSET_KEY.to_string(),
TypedData {
data: Some(Data::String(OFFSET.to_string())),
},
);
metadata.insert(
PROPERTIES_KEY.to_string(),
TypedData {
data: Some(Data::Json(properties.to_string())),
},
);
metadata.insert(
SEQUENCE_NUMBER_KEY.to_string(),
TypedData {
data: Some(Data::Int(SEQUENCE_NUMBER)),
},
);
metadata.insert(
SYSTEM_PROPERTIES_KEY.to_string(),
TypedData {
data: Some(Data::Json(
serde_json::to_string(&system_properties).unwrap(),
)),
},
);
let trigger = EventHubTrigger::new(data, &mut metadata);

View File

@ -1,5 +1,7 @@
use crate::http::Body;
use crate::rpc::protocol;
use crate::{
http::Body,
rpc::{typed_data::Data, RpcHttp, TypedData},
};
use std::collections::HashMap;
/// Represents a HTTP trigger binding.
@ -32,7 +34,7 @@ use std::collections::HashMap;
/// }
/// ```
#[derive(Debug)]
pub struct HttpRequest(protocol::RpcHttp);
pub struct HttpRequest(RpcHttp);
impl HttpRequest {
/// Gets the HTTP method (e.g. "GET") for the request.
@ -104,24 +106,21 @@ impl HttpRequest {
/// Gets the body of the request.
pub fn body(&self) -> Body {
if self.0.has_body() {
Body::from(self.0.get_body())
} else {
Body::Empty
}
self.0
.body
.as_ref()
.map(|b| Body::from(&**b))
.unwrap_or(Body::Empty)
}
}
impl HttpRequest {
#[doc(hidden)]
pub fn new(
mut data: protocol::TypedData,
_: &mut HashMap<String, protocol::TypedData>,
) -> Self {
if !data.has_http() {
panic!("unexpected type data for HTTP request.");
pub fn new(data: TypedData, _: &mut HashMap<String, TypedData>) -> Self {
match data.data {
Some(Data::Http(http)) => HttpRequest(*http),
_ => panic!("unexpected type data for HTTP request."),
}
HttpRequest(data.take_http())
}
}
@ -135,10 +134,12 @@ mod tests {
fn it_has_the_method() {
const METHOD: &'static str = "GET";
let mut data = protocol::TypedData::new();
let mut http = protocol::RpcHttp::new();
http.set_method(METHOD.to_string());
data.set_http(http);
let mut http = RpcHttp::default();
http.method = METHOD.to_string();
let data = TypedData {
data: Some(Data::Http(Box::new(http))),
};
let mut metadata = HashMap::new();
@ -150,10 +151,12 @@ mod tests {
fn it_has_the_url() {
const URL: &'static str = "http://example.com";
let mut data = protocol::TypedData::new();
let mut http = protocol::RpcHttp::new();
http.set_url(URL.to_string());
data.set_http(http);
let mut http = RpcHttp::default();
http.url = URL.to_string();
let data = TypedData {
data: Some(Data::Http(Box::new(http))),
};
let mut metadata = HashMap::new();
@ -166,12 +169,12 @@ mod tests {
const KEY: &'static str = "Accept";
const VALUE: &'static str = "application/json";
let mut data = protocol::TypedData::new();
let mut http = protocol::RpcHttp::new();
let mut headers = HashMap::new();
headers.insert(KEY.to_string(), VALUE.to_string());
http.set_headers(headers);
data.set_http(http);
let mut http = RpcHttp::default();
http.headers.insert(KEY.to_string(), VALUE.to_string());
let data = TypedData {
data: Some(Data::Http(Box::new(http))),
};
let mut metadata = HashMap::new();
@ -184,12 +187,12 @@ mod tests {
const KEY: &'static str = "id";
const VALUE: &'static str = "12345";
let mut data = protocol::TypedData::new();
let mut http = protocol::RpcHttp::new();
let mut params = HashMap::new();
params.insert(KEY.to_string(), VALUE.to_string());
http.set_params(params);
data.set_http(http);
let mut http = RpcHttp::default();
http.params.insert(KEY.to_string(), VALUE.to_string());
let data = TypedData {
data: Some(Data::Http(Box::new(http))),
};
let mut metadata = HashMap::new();
@ -202,12 +205,12 @@ mod tests {
const KEY: &'static str = "name";
const VALUE: &'static str = "Peter";
let mut data = protocol::TypedData::new();
let mut http = protocol::RpcHttp::new();
let mut params = HashMap::new();
params.insert(KEY.to_string(), VALUE.to_string());
http.set_query(params);
data.set_http(http);
let mut http = RpcHttp::default();
http.query.insert(KEY.to_string(), VALUE.to_string());
let data = TypedData {
data: Some(Data::Http(Box::new(http))),
};
let mut metadata = HashMap::new();
@ -217,10 +220,9 @@ mod tests {
#[test]
fn it_has_an_empty_body() {
let mut data = protocol::TypedData::new();
let http = protocol::RpcHttp::new();
data.set_http(http);
let data = TypedData {
data: Some(Data::Http(Box::new(RpcHttp::default()))),
};
let mut metadata = HashMap::new();
@ -232,13 +234,14 @@ mod tests {
fn it_has_a_string_body() {
const BODY: &'static str = "TEXT BODY";
let mut data = protocol::TypedData::new();
let mut http = protocol::RpcHttp::new();
let mut body = protocol::TypedData::new();
let mut http = RpcHttp::default();
http.body = Some(Box::new(TypedData {
data: Some(Data::String(BODY.to_string())),
}));
body.set_string(BODY.to_string());
http.set_body(body);
data.set_http(http);
let data = TypedData {
data: Some(Data::Http(Box::new(http))),
};
let mut metadata = HashMap::new();
@ -250,13 +253,14 @@ mod tests {
fn it_has_a_json_body() {
const BODY: &'static str = r#"{ "json": "body" }"#;
let mut data = protocol::TypedData::new();
let mut http = protocol::RpcHttp::new();
let mut body = protocol::TypedData::new();
let mut http = RpcHttp::default();
http.body = Some(Box::new(TypedData {
data: Some(Data::Json(BODY.to_string())),
}));
body.set_json(BODY.to_string());
http.set_body(body);
data.set_http(http);
let data = TypedData {
data: Some(Data::Http(Box::new(http))),
};
let mut metadata = HashMap::new();
@ -268,13 +272,14 @@ mod tests {
fn it_has_a_bytes_body() {
const BODY: &'static [u8] = &[0, 1, 2];
let mut data = protocol::TypedData::new();
let mut http = protocol::RpcHttp::new();
let mut body = protocol::TypedData::new();
let mut http = RpcHttp::default();
http.body = Some(Box::new(TypedData {
data: Some(Data::Bytes(BODY.to_vec())),
}));
body.set_bytes(BODY.to_owned());
http.set_body(body);
data.set_http(http);
let data = TypedData {
data: Some(Data::Http(Box::new(http))),
};
let mut metadata = HashMap::new();
@ -286,13 +291,14 @@ mod tests {
fn it_has_a_stream_body() {
const BODY: &'static [u8] = &[0, 1, 2];
let mut data = protocol::TypedData::new();
let mut http = protocol::RpcHttp::new();
let mut body = protocol::TypedData::new();
let mut http = RpcHttp::default();
http.body = Some(Box::new(TypedData {
data: Some(Data::Stream(BODY.to_vec())),
}));
body.set_stream(BODY.to_owned());
http.set_body(body);
data.set_http(http);
let data = TypedData {
data: Some(Data::Http(Box::new(http))),
};
let mut metadata = HashMap::new();

View File

@ -1,5 +1,7 @@
use crate::http::{Body, ResponseBuilder, Status};
use crate::rpc::protocol;
use crate::{
http::{Body, ResponseBuilder, Status},
rpc::{typed_data::Data, RpcHttp, TypedData},
};
use std::collections::HashMap;
/// Represents a HTTP output binding.
@ -69,14 +71,14 @@ use std::collections::HashMap;
/// ```
#[derive(Default, Debug)]
pub struct HttpResponse {
pub(crate) data: protocol::RpcHttp,
pub(crate) data: RpcHttp,
pub(crate) status: Status,
}
impl HttpResponse {
pub(crate) fn new() -> Self {
HttpResponse {
data: protocol::RpcHttp::new(),
data: RpcHttp::default(),
status: Status::Ok,
}
}
@ -122,11 +124,11 @@ impl HttpResponse {
/// assert_eq!(response.body().as_str().unwrap(), "example");
/// ```
pub fn body(&self) -> Body {
if self.data.has_body() {
Body::from(self.data.get_body())
} else {
Body::Empty
}
self.data
.body
.as_ref()
.map(|b| Body::from(&**b))
.unwrap_or(Body::Empty)
}
/// Gets the headers of the response.
@ -160,13 +162,13 @@ impl From<ResponseBuilder> for HttpResponse {
}
#[doc(hidden)]
impl Into<protocol::TypedData> for HttpResponse {
fn into(mut self) -> protocol::TypedData {
self.data.set_status_code(self.status.to_string());
impl Into<TypedData> for HttpResponse {
fn into(mut self) -> TypedData {
self.data.status_code = self.status.to_string();
let mut data = protocol::TypedData::new();
data.set_http(self.data);
data
TypedData {
data: Some(Data::Http(Box::new(self.data))),
}
}
}
@ -270,13 +272,19 @@ mod tests {
.body("body")
.into();
let data: protocol::TypedData = response.into();
assert!(data.has_http());
let http = data.get_http();
assert_eq!(http.get_status_code(), "400");
assert_eq!(http.get_headers().get("header").unwrap(), "value");
assert!(http.get_body().has_string());
assert_eq!(http.get_body().get_string(), "body");
let data: TypedData = response.into();
match data.data {
Some(Data::Http(http)) => {
assert_eq!(http.status_code, "400");
assert_eq!(http.headers.get("header").unwrap(), "value");
assert_eq!(
http.body,
Some(Box::new(TypedData {
data: Some(Data::String("body".to_string()))
}))
);
}
_ => assert!(false),
}
}
}

View File

@ -1,4 +1,8 @@
use crate::{http::Body, rpc::protocol, FromVec};
use crate::{
http::Body,
rpc::{typed_data::Data, TypedData},
FromVec,
};
use serde::de::Error;
use serde::Deserialize;
use serde_json::{from_str, Result, Value};
@ -59,44 +63,31 @@ use std::str::from_utf8;
/// }
/// ```
#[derive(Debug, Clone)]
pub struct QueueMessage(protocol::TypedData);
pub struct QueueMessage(TypedData);
impl QueueMessage {
/// Gets the content of the message as a string.
///
/// Returns None if there is no valid string representation of the message.
pub fn as_str(&self) -> Option<&str> {
if self.0.has_string() {
return Some(self.0.get_string());
match &self.0.data {
Some(Data::String(s)) => Some(s),
Some(Data::Json(s)) => Some(s),
Some(Data::Bytes(b)) => from_utf8(b).ok(),
Some(Data::Stream(s)) => from_utf8(s).ok(),
_ => None,
}
if self.0.has_json() {
return Some(self.0.get_json());
}
if self.0.has_bytes() {
return from_utf8(self.0.get_bytes()).map(|s| s).ok();
}
if self.0.has_stream() {
return from_utf8(self.0.get_stream()).map(|s| s).ok();
}
None
}
/// Gets the content of the message as a slice of bytes.
pub fn as_bytes(&self) -> &[u8] {
if self.0.has_string() {
return self.0.get_string().as_bytes();
match &self.0.data {
Some(Data::String(s)) => s.as_bytes(),
Some(Data::Json(s)) => s.as_bytes(),
Some(Data::Bytes(b)) => b,
Some(Data::Stream(s)) => s,
_ => panic!("unexpected data for queue message content"),
}
if self.0.has_json() {
return self.0.get_json().as_bytes();
}
if self.0.has_bytes() {
return self.0.get_bytes();
}
if self.0.has_stream() {
return self.0.get_stream();
}
panic!("unexpected data for queue message content");
}
/// Deserializes the message as JSON to the requested type.
@ -119,162 +110,136 @@ impl fmt::Display for QueueMessage {
impl<'a> From<&'a str> for QueueMessage {
fn from(content: &'a str) -> Self {
let mut data = protocol::TypedData::new();
data.set_string(content.to_owned());
QueueMessage(data)
QueueMessage(TypedData {
data: Some(Data::String(content.to_owned())),
})
}
}
impl From<String> for QueueMessage {
fn from(content: String) -> Self {
let mut data = protocol::TypedData::new();
data.set_string(content);
QueueMessage(data)
QueueMessage(TypedData {
data: Some(Data::String(content)),
})
}
}
impl From<&Value> for QueueMessage {
fn from(content: &Value) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(content.to_string());
QueueMessage(data)
QueueMessage(TypedData {
data: Some(Data::Json(content.to_string())),
})
}
}
impl From<Value> for QueueMessage {
fn from(content: Value) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(content.to_string());
QueueMessage(data)
QueueMessage(TypedData {
data: Some(Data::Json(content.to_string())),
})
}
}
impl<'a> From<&'a [u8]> for QueueMessage {
fn from(content: &'a [u8]) -> Self {
let mut data = protocol::TypedData::new();
data.set_bytes(content.to_owned());
QueueMessage(data)
QueueMessage(TypedData {
data: Some(Data::Bytes(content.to_owned())),
})
}
}
impl From<Vec<u8>> for QueueMessage {
fn from(content: Vec<u8>) -> Self {
let mut data = protocol::TypedData::new();
data.set_bytes(content);
QueueMessage(TypedData {
data: Some(Data::Bytes(content)),
})
}
}
#[doc(hidden)]
impl From<TypedData> for QueueMessage {
fn from(data: TypedData) -> Self {
QueueMessage(data)
}
}
#[doc(hidden)]
impl From<protocol::TypedData> for QueueMessage {
fn from(data: protocol::TypedData) -> Self {
QueueMessage(data)
}
}
#[doc(hidden)]
impl FromVec<QueueMessage> for protocol::TypedData {
impl FromVec<QueueMessage> for TypedData {
fn from_vec(vec: Vec<QueueMessage>) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(Value::Array(vec.into_iter().map(Into::into).collect()).to_string());
data
TypedData {
data: Some(Data::Json(
Value::Array(vec.into_iter().map(Into::into).collect()).to_string(),
)),
}
}
}
impl Into<String> for QueueMessage {
fn into(mut self) -> String {
if self.0.has_string() {
return self.0.take_string();
fn into(self) -> String {
match self.0.data {
Some(Data::String(s)) => s,
Some(Data::Json(s)) => s,
Some(Data::Bytes(b)) => {
String::from_utf8(b).expect("queue message does not contain valid UTF-8 bytes")
}
Some(Data::Stream(s)) => {
String::from_utf8(s).expect("queue message does not contain valid UTF-8 bytes")
}
_ => panic!("unexpected data for queue message content"),
}
if self.0.has_json() {
return self.0.take_json();
}
if self.0.has_bytes() {
return String::from_utf8(self.0.take_bytes())
.expect("queue message does not contain valid UTF-8 bytes");
}
if self.0.has_stream() {
return String::from_utf8(self.0.take_stream())
.expect("queue message does not contain valid UTF-8 bytes");
}
panic!("unexpected data for queue message content");
}
}
impl Into<Value> for QueueMessage {
fn into(mut self) -> Value {
if self.0.has_string() {
return Value::String(self.0.take_string());
}
if self.0.has_json() {
return from_str(self.0.get_json())
.expect("queue message does not contain valid JSON data");
}
// TODO: this is not an efficient encoding
if self.0.has_bytes() {
return Value::Array(
self.0
.get_bytes()
.iter()
fn into(self) -> Value {
// TODO: this is not an efficient encoding for bytes/stream
match self.0.data {
Some(Data::String(s)) => Value::String(s),
Some(Data::Json(s)) => {
from_str(&s).expect("queue message does not contain valid JSON data")
}
Some(Data::Bytes(b)) => Value::Array(
b.iter()
.map(|n| Value::Number(u64::from(*n).into()))
.collect(),
);
}
// TODO: this is not an efficient encoding
if self.0.has_stream() {
return Value::Array(
self.0
.get_stream()
.iter()
),
Some(Data::Stream(s)) => Value::Array(
s.iter()
.map(|n| Value::Number(u64::from(*n).into()))
.collect(),
);
),
_ => panic!("unexpected data for queue message content"),
}
panic!("unexpected data for queue message content");
}
}
impl Into<Vec<u8>> for QueueMessage {
fn into(mut self) -> Vec<u8> {
if self.0.has_string() {
return self.0.take_string().into_bytes();
fn into(self) -> Vec<u8> {
match self.0.data {
Some(Data::String(s)) => s.into_bytes(),
Some(Data::Json(s)) => s.into_bytes(),
Some(Data::Bytes(b)) => b,
Some(Data::Stream(s)) => s,
_ => panic!("unexpected data for queue message content"),
}
if self.0.has_json() {
return self.0.take_json().into_bytes();
}
if self.0.has_bytes() {
return self.0.take_bytes();
}
if self.0.has_stream() {
return self.0.take_stream();
}
panic!("unexpected data for queue message content");
}
}
impl<'a> Into<Body<'a>> for QueueMessage {
fn into(mut self) -> Body<'a> {
if self.0.has_string() {
return self.0.take_string().into();
fn into(self) -> Body<'a> {
match self.0.data {
Some(Data::String(s)) => s.into(),
Some(Data::Json(s)) => Body::Json(Cow::from(s)),
Some(Data::Bytes(b)) => b.into(),
Some(Data::Stream(s)) => s.into(),
_ => panic!("unexpected data for queue message content"),
}
if self.0.has_json() {
return Body::Json(Cow::from(self.0.take_json()));
}
if self.0.has_bytes() {
return self.0.take_bytes().into();
}
if self.0.has_stream() {
return self.0.take_stream().into();
}
panic!("unexpected data for queue message content");
}
}
#[doc(hidden)]
impl Into<protocol::TypedData> for QueueMessage {
fn into(self) -> protocol::TypedData {
impl Into<TypedData> for QueueMessage {
fn into(self) -> TypedData {
self.0
}
}
@ -293,28 +258,34 @@ mod tests {
let message: QueueMessage = MESSAGE.into();
assert_eq!(message.as_str().unwrap(), MESSAGE);
let data: protocol::TypedData = message.into();
assert_eq!(data.get_string(), MESSAGE);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::String(MESSAGE.to_string())));
}
#[test]
fn it_has_json_content() {
#[derive(Serialize, Deserialize)]
struct Data {
struct SerializedData {
message: String,
};
const MESSAGE: &'static str = "test";
let data = Data {
let data = SerializedData {
message: MESSAGE.to_string(),
};
let message: QueueMessage = ::serde_json::to_value(data).unwrap().into();
assert_eq!(message.as_json::<Data>().unwrap().message, MESSAGE);
assert_eq!(
message.as_json::<SerializedData>().unwrap().message,
MESSAGE
);
let data: protocol::TypedData = message.into();
assert_eq!(data.get_json(), r#"{"message":"test"}"#);
let data: TypedData = message.into();
assert_eq!(
data.data,
Some(Data::Json(r#"{"message":"test"}"#.to_string()))
);
}
#[test]
@ -324,8 +295,8 @@ mod tests {
let message: QueueMessage = MESSAGE.into();
assert_eq!(message.as_bytes(), MESSAGE);
let data: protocol::TypedData = message.into();
assert_eq!(data.get_bytes(), MESSAGE);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::Bytes([1, 2, 3].to_vec())));
}
#[test]
@ -409,18 +380,15 @@ mod tests {
#[test]
fn it_converts_to_typed_data() {
let message: QueueMessage = "test".into();
let data: protocol::TypedData = message.into();
assert!(data.has_string());
assert_eq!(data.get_string(), "test");
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::String("test".to_string())));
let message: QueueMessage = to_value("test").unwrap().into();
let data: protocol::TypedData = message.into();
assert!(data.has_json());
assert_eq!(data.get_json(), r#""test""#);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::Json(r#""test""#.to_string())));
let message: QueueMessage = vec![1, 2, 3].into();
let data: protocol::TypedData = message.into();
assert!(data.has_bytes());
assert_eq!(data.get_bytes(), [1, 2, 3]);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::Bytes([1, 2, 3].to_vec())));
}
}

View File

@ -1,6 +1,8 @@
use crate::bindings::QueueMessage;
use crate::rpc::protocol;
use crate::util::convert_from;
use crate::{
bindings::QueueMessage,
rpc::{typed_data::Data, TypedData},
util::convert_from,
};
use chrono::{DateTime, Utc};
use std::collections::HashMap;
@ -56,16 +58,16 @@ pub struct QueueTrigger {
impl QueueTrigger {
#[doc(hidden)]
pub fn new(
data: protocol::TypedData,
metadata: &mut HashMap<String, protocol::TypedData>,
) -> Self {
pub fn new(data: TypedData, metadata: &mut HashMap<String, TypedData>) -> Self {
QueueTrigger {
message: data.into(),
id: metadata
.get_mut(ID_KEY)
.expect("expected a message id")
.take_string(),
.remove(ID_KEY)
.map(|data| match data.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for message id"),
})
.expect("expected a message id"),
dequeue_count: convert_from(
metadata
.get(DEQUEUE_COUNT_KEY)
@ -91,9 +93,12 @@ impl QueueTrigger {
)
.expect("failed to convert next visible time"),
pop_receipt: metadata
.get_mut(POP_RECEIPT_KEY)
.expect("expected a pop receipt")
.take_string(),
.remove(POP_RECEIPT_KEY)
.map(|data| match data.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for pop receipt"),
})
.expect("expected a pop receipt"),
}
}
}
@ -110,34 +115,53 @@ mod tests {
const MESSAGE: &'static str = "\"hello world\"";
let now = Utc::now();
let mut data = protocol::TypedData::new();
data.set_json(MESSAGE.to_string());
let data = TypedData {
data: Some(Data::Json(MESSAGE.to_string())),
};
let mut metadata = HashMap::new();
let mut value = protocol::TypedData::new();
value.set_string(ID.to_string());
metadata.insert(ID_KEY.to_string(), value);
metadata.insert(
ID_KEY.to_string(),
TypedData {
data: Some(Data::String(ID.to_string())),
},
);
let mut value = protocol::TypedData::new();
value.set_json(DEQUEUE_COUNT.to_string());
metadata.insert(DEQUEUE_COUNT_KEY.to_string(), value);
metadata.insert(
DEQUEUE_COUNT_KEY.to_string(),
TypedData {
data: Some(Data::Json(DEQUEUE_COUNT.to_string())),
},
);
let mut value = protocol::TypedData::new();
value.set_string(now.to_rfc3339());
metadata.insert(EXPIRATION_TIME_KEY.to_string(), value);
metadata.insert(
EXPIRATION_TIME_KEY.to_string(),
TypedData {
data: Some(Data::String(now.to_rfc3339())),
},
);
let mut value = protocol::TypedData::new();
value.set_string(now.to_rfc3339());
metadata.insert(INSERTION_TIME_KEY.to_string(), value);
metadata.insert(
INSERTION_TIME_KEY.to_string(),
TypedData {
data: Some(Data::String(now.to_rfc3339())),
},
);
let mut value = protocol::TypedData::new();
value.set_json("\"".to_string() + &now.to_rfc3339() + "\"");
metadata.insert(NEXT_VISIBLE_TIME_KEY.to_string(), value);
metadata.insert(
NEXT_VISIBLE_TIME_KEY.to_string(),
TypedData {
data: Some(Data::Json("\"".to_string() + &now.to_rfc3339() + "\"")),
},
);
let mut value = protocol::TypedData::new();
value.set_string(POP_RECEIPT.to_string());
metadata.insert(POP_RECEIPT_KEY.to_string(), value);
metadata.insert(
POP_RECEIPT_KEY.to_string(),
TypedData {
data: Some(Data::String(POP_RECEIPT.to_string())),
},
);
let trigger = QueueTrigger::new(data, &mut metadata);
assert_eq!(trigger.id, ID);

View File

@ -1,4 +1,8 @@
use crate::{http::Body, rpc::protocol, FromVec};
use crate::{
http::Body,
rpc::{typed_data::Data, TypedData},
FromVec,
};
use serde::de::Error;
use serde::Deserialize;
use serde_json::{from_str, Result, Value};
@ -63,44 +67,31 @@ use std::str::from_utf8;
/// }
/// ```
#[derive(Debug, Clone)]
pub struct ServiceBusMessage(protocol::TypedData);
pub struct ServiceBusMessage(TypedData);
impl ServiceBusMessage {
/// Gets the content of the message as a string.
///
/// Returns None if there is no valid string representation of the message.
pub fn as_str(&self) -> Option<&str> {
if self.0.has_string() {
return Some(self.0.get_string());
match &self.0.data {
Some(Data::String(s)) => Some(s),
Some(Data::Json(s)) => Some(s),
Some(Data::Bytes(b)) => from_utf8(b).ok(),
Some(Data::Stream(s)) => from_utf8(s).ok(),
_ => None,
}
if self.0.has_json() {
return Some(self.0.get_json());
}
if self.0.has_bytes() {
return from_utf8(self.0.get_bytes()).map(|s| s).ok();
}
if self.0.has_stream() {
return from_utf8(self.0.get_stream()).map(|s| s).ok();
}
None
}
/// Gets the content of the message as a slice of bytes.
pub fn as_bytes(&self) -> &[u8] {
if self.0.has_string() {
return self.0.get_string().as_bytes();
match &self.0.data {
Some(Data::String(s)) => s.as_bytes(),
Some(Data::Json(s)) => s.as_bytes(),
Some(Data::Bytes(b)) => b,
Some(Data::Stream(s)) => s,
_ => panic!("unexpected data for service bus message content"),
}
if self.0.has_json() {
return self.0.get_json().as_bytes();
}
if self.0.has_bytes() {
return self.0.get_bytes();
}
if self.0.has_stream() {
return self.0.get_stream();
}
panic!("unexpected data for service bus message content");
}
/// Deserializes the message as JSON to the requested type.
@ -124,162 +115,134 @@ impl fmt::Display for ServiceBusMessage {
impl<'a> From<&'a str> for ServiceBusMessage {
fn from(content: &'a str) -> Self {
let mut data = protocol::TypedData::new();
data.set_string(content.to_owned());
ServiceBusMessage(data)
ServiceBusMessage(TypedData {
data: Some(Data::String(content.to_owned())),
})
}
}
impl From<String> for ServiceBusMessage {
fn from(content: String) -> Self {
let mut data = protocol::TypedData::new();
data.set_string(content);
ServiceBusMessage(data)
ServiceBusMessage(TypedData {
data: Some(Data::String(content)),
})
}
}
impl From<&Value> for ServiceBusMessage {
fn from(content: &Value) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(content.to_string());
ServiceBusMessage(data)
ServiceBusMessage(TypedData {
data: Some(Data::Json(content.to_string())),
})
}
}
impl From<Value> for ServiceBusMessage {
fn from(content: Value) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(content.to_string());
ServiceBusMessage(data)
ServiceBusMessage(TypedData {
data: Some(Data::Json(content.to_string())),
})
}
}
impl<'a> From<&'a [u8]> for ServiceBusMessage {
fn from(content: &'a [u8]) -> Self {
let mut data = protocol::TypedData::new();
data.set_bytes(content.to_owned());
ServiceBusMessage(data)
ServiceBusMessage(TypedData {
data: Some(Data::Bytes(content.to_owned())),
})
}
}
impl From<Vec<u8>> for ServiceBusMessage {
fn from(content: Vec<u8>) -> Self {
let mut data = protocol::TypedData::new();
data.set_bytes(content);
ServiceBusMessage(TypedData {
data: Some(Data::Bytes(content)),
})
}
}
#[doc(hidden)]
impl From<TypedData> for ServiceBusMessage {
fn from(data: TypedData) -> Self {
ServiceBusMessage(data)
}
}
#[doc(hidden)]
impl From<protocol::TypedData> for ServiceBusMessage {
fn from(data: protocol::TypedData) -> Self {
ServiceBusMessage(data)
}
}
#[doc(hidden)]
impl FromVec<ServiceBusMessage> for protocol::TypedData {
impl FromVec<ServiceBusMessage> for TypedData {
fn from_vec(vec: Vec<ServiceBusMessage>) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(Value::Array(vec.into_iter().map(Into::into).collect()).to_string());
data
TypedData {
data: Some(Data::Json(
Value::Array(vec.into_iter().map(Into::into).collect()).to_string(),
)),
}
}
}
impl Into<String> for ServiceBusMessage {
fn into(mut self) -> String {
if self.0.has_string() {
return self.0.take_string();
fn into(self) -> String {
match self.0.data {
Some(Data::String(s)) => s,
Some(Data::Json(s)) => s,
Some(Data::Bytes(b)) => String::from_utf8(b)
.expect("service bus message does not contain valid UTF-8 bytes"),
Some(Data::Stream(s)) => String::from_utf8(s)
.expect("service bus message does not contain valid UTF-8 bytes"),
_ => panic!("unexpected data for service bus message content"),
}
if self.0.has_json() {
return self.0.take_json();
}
if self.0.has_bytes() {
return String::from_utf8(self.0.take_bytes())
.expect("service bus message does not contain valid UTF-8 bytes");
}
if self.0.has_stream() {
return String::from_utf8(self.0.take_stream())
.expect("service bus message does not contain valid UTF-8 bytes");
}
panic!("unexpected data for service bus message content");
}
}
impl Into<Value> for ServiceBusMessage {
fn into(mut self) -> Value {
if self.0.has_string() {
return Value::String(self.0.take_string());
}
if self.0.has_json() {
return from_str(self.0.get_json())
.expect("service bus message does not contain valid JSON data");
}
// TODO: this is not an efficient encoding
if self.0.has_bytes() {
return Value::Array(
self.0
.get_bytes()
.iter()
fn into(self) -> Value {
// TODO: this is not an efficient encoding for bytes/stream
match self.0.data {
Some(Data::String(s)) => Value::String(s),
Some(Data::Json(s)) => {
from_str(&s).expect("service bus message does not contain valid JSON data")
}
Some(Data::Bytes(b)) => Value::Array(
b.iter()
.map(|n| Value::Number(u64::from(*n).into()))
.collect(),
);
}
// TODO: this is not an efficient encoding
if self.0.has_stream() {
return Value::Array(
self.0
.get_stream()
.iter()
),
Some(Data::Stream(s)) => Value::Array(
s.iter()
.map(|n| Value::Number(u64::from(*n).into()))
.collect(),
);
),
_ => panic!("unexpected data for service bus message content"),
}
panic!("unexpected data for service bus message content");
}
}
impl Into<Vec<u8>> for ServiceBusMessage {
fn into(mut self) -> Vec<u8> {
if self.0.has_string() {
return self.0.take_string().into_bytes();
fn into(self) -> Vec<u8> {
match self.0.data {
Some(Data::String(s)) => s.into_bytes(),
Some(Data::Json(s)) => s.into_bytes(),
Some(Data::Bytes(b)) => b,
Some(Data::Stream(s)) => s,
_ => panic!("unexpected data for service bus message content"),
}
if self.0.has_json() {
return self.0.take_json().into_bytes();
}
if self.0.has_bytes() {
return self.0.take_bytes();
}
if self.0.has_stream() {
return self.0.take_stream();
}
panic!("unexpected data for service bus message content");
}
}
impl<'a> Into<Body<'a>> for ServiceBusMessage {
fn into(mut self) -> Body<'a> {
if self.0.has_string() {
return self.0.take_string().into();
fn into(self) -> Body<'a> {
match self.0.data {
Some(Data::String(s)) => s.into(),
Some(Data::Json(s)) => Body::Json(Cow::from(s)),
Some(Data::Bytes(b)) => b.into(),
Some(Data::Stream(s)) => s.into(),
_ => panic!("unexpected data for service bus message content"),
}
if self.0.has_json() {
return Body::Json(Cow::from(self.0.take_json()));
}
if self.0.has_bytes() {
return self.0.take_bytes().into();
}
if self.0.has_stream() {
return self.0.take_stream().into();
}
panic!("unexpected data for service bus message content");
}
}
#[doc(hidden)]
impl Into<protocol::TypedData> for ServiceBusMessage {
fn into(self) -> protocol::TypedData {
impl Into<TypedData> for ServiceBusMessage {
fn into(self) -> TypedData {
self.0
}
}
@ -298,28 +261,34 @@ mod tests {
let message: ServiceBusMessage = MESSAGE.into();
assert_eq!(message.as_str().unwrap(), MESSAGE);
let data: protocol::TypedData = message.into();
assert_eq!(data.get_string(), MESSAGE);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::String(MESSAGE.to_string())));
}
#[test]
fn it_has_json_content() {
#[derive(Serialize, Deserialize)]
struct Data {
struct SerializedData {
message: String,
};
const MESSAGE: &'static str = "test";
let data = Data {
let data = SerializedData {
message: MESSAGE.to_string(),
};
let message: ServiceBusMessage = ::serde_json::to_value(data).unwrap().into();
assert_eq!(message.as_json::<Data>().unwrap().message, MESSAGE);
assert_eq!(
message.as_json::<SerializedData>().unwrap().message,
MESSAGE
);
let data: protocol::TypedData = message.into();
assert_eq!(data.get_json(), r#"{"message":"test"}"#);
let data: TypedData = message.into();
assert_eq!(
data.data,
Some(Data::Json(r#"{"message":"test"}"#.to_string()))
);
}
#[test]
@ -329,8 +298,8 @@ mod tests {
let message: ServiceBusMessage = MESSAGE.into();
assert_eq!(message.as_bytes(), MESSAGE);
let data: protocol::TypedData = message.into();
assert_eq!(data.get_bytes(), MESSAGE);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::Bytes(MESSAGE.to_vec())));
}
#[test]
@ -414,18 +383,15 @@ mod tests {
#[test]
fn it_converts_to_typed_data() {
let message: ServiceBusMessage = "test".into();
let data: protocol::TypedData = message.into();
assert!(data.has_string());
assert_eq!(data.get_string(), "test");
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::String("test".to_string())));
let message: ServiceBusMessage = to_value("test").unwrap().into();
let data: protocol::TypedData = message.into();
assert!(data.has_json());
assert_eq!(data.get_json(), r#""test""#);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::Json(r#""test""#.to_string())));
let message: ServiceBusMessage = vec![1, 2, 3].into();
let data: protocol::TypedData = message.into();
assert!(data.has_bytes());
assert_eq!(data.get_bytes(), [1, 2, 3]);
let data: TypedData = message.into();
assert_eq!(data.data, Some(Data::Bytes([1, 2, 3].to_vec())));
}
}

View File

@ -1,6 +1,8 @@
use crate::bindings::ServiceBusMessage;
use crate::rpc::protocol;
use crate::util::convert_from;
use crate::{
bindings::ServiceBusMessage,
rpc::{typed_data::Data, TypedData},
util::convert_from,
};
use chrono::{DateTime, Utc};
use serde_json::{from_str, Map, Value};
use std::collections::HashMap;
@ -95,10 +97,7 @@ pub struct ServiceBusTrigger {
impl ServiceBusTrigger {
#[doc(hidden)]
pub fn new(
data: protocol::TypedData,
metadata: &mut HashMap<String, protocol::TypedData>,
) -> Self {
pub fn new(data: TypedData, metadata: &mut HashMap<String, TypedData>) -> Self {
ServiceBusTrigger {
message: data.into(),
delivery_count: convert_from(
@ -107,9 +106,12 @@ impl ServiceBusTrigger {
.expect("expected a delivery count"),
)
.expect("failed to convert delivery count"),
dead_letter_source: metadata
.get_mut(DEAD_LETTER_SOURCE_KEY)
.map(protocol::TypedData::take_string),
dead_letter_source: metadata.remove(DEAD_LETTER_SOURCE_KEY).map(|data| {
match data.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for dead letter source"),
}
}),
expiration_time: convert_from(
metadata
.get(EXPIRATION_TIME_KEY)
@ -123,34 +125,49 @@ impl ServiceBusTrigger {
)
.expect("failed to convert enqueued time"),
message_id: metadata
.get_mut(MESSAGE_ID_KEY)
.expect("expected a message id")
.take_string(),
.remove(MESSAGE_ID_KEY)
.map(|data| match data.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for message id"),
})
.expect("expected a message id"),
content_type: metadata
.get_mut(CONTENT_TYPE_KEY)
.map(protocol::TypedData::take_string),
reply_to: metadata
.get_mut(REPLY_TO_KEY)
.map(protocol::TypedData::take_string),
.remove(CONTENT_TYPE_KEY)
.map(|data| match data.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for content type"),
}),
reply_to: metadata.remove(REPLY_TO_KEY).map(|data| match data.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for reply to"),
}),
sequence_number: convert_from(
metadata
.get(SEQUENCE_NUMBER_KEY)
.expect("expected a sequence number"),
)
.expect("failed to convert sequence number"),
to: metadata
.get_mut(TO_KEY)
.map(protocol::TypedData::take_string),
label: metadata
.get_mut(LABEL_KEY)
.map(protocol::TypedData::take_string),
to: metadata.remove(TO_KEY).map(|data| match data.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for to"),
}),
label: metadata.remove(LABEL_KEY).map(|data| match data.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for label"),
}),
correlation_id: metadata
.get_mut(CORRELATION_ID_KEY)
.map(protocol::TypedData::take_string),
.remove(CORRELATION_ID_KEY)
.map(|data| match data.data {
Some(Data::String(s)) => s,
_ => panic!("expected a string for correlation id"),
}),
user_properties: from_str(
metadata
.get(USER_PROPERTIES_KEY)
.map(protocol::TypedData::get_json)
.map(|data| match &data.data {
Some(Data::Json(s)) => s.as_str(),
_ => panic!("expected JSON data for user properties"),
})
.unwrap_or("{}"),
)
.expect("failed to convert user properties"),
@ -177,58 +194,95 @@ mod tests {
const MESSAGE: &'static str = "\"hello world\"";
let now = Utc::now();
let mut data = protocol::TypedData::new();
data.set_json(MESSAGE.to_string());
let data = TypedData {
data: Some(Data::Json(MESSAGE.to_string())),
};
let mut metadata = HashMap::new();
let mut value = protocol::TypedData::new();
value.set_int(DELIVERY_COUNT.into());
metadata.insert(DELIVERY_COUNT_KEY.to_string(), value);
metadata.insert(
DELIVERY_COUNT_KEY.to_string(),
TypedData {
data: Some(Data::Int(DELIVERY_COUNT as i64)),
},
);
let mut value = protocol::TypedData::new();
value.set_string(DEAD_LETTER_SOURCE.to_string());
metadata.insert(DEAD_LETTER_SOURCE_KEY.to_string(), value);
metadata.insert(
DEAD_LETTER_SOURCE_KEY.to_string(),
TypedData {
data: Some(Data::String(DEAD_LETTER_SOURCE.to_string())),
},
);
let mut value = protocol::TypedData::new();
value.set_string(now.to_rfc3339());
metadata.insert(EXPIRATION_TIME_KEY.to_string(), value);
metadata.insert(
EXPIRATION_TIME_KEY.to_string(),
TypedData {
data: Some(Data::String(now.to_rfc3339())),
},
);
let mut value = protocol::TypedData::new();
value.set_string(now.to_rfc3339());
metadata.insert(ENQUEUED_TIME_KEY.to_string(), value);
metadata.insert(
ENQUEUED_TIME_KEY.to_string(),
TypedData {
data: Some(Data::String(now.to_rfc3339())),
},
);
let mut value = protocol::TypedData::new();
value.set_string(MESSAGE_ID.to_string());
metadata.insert(MESSAGE_ID_KEY.to_string(), value);
metadata.insert(
MESSAGE_ID_KEY.to_string(),
TypedData {
data: Some(Data::String(MESSAGE_ID.to_string())),
},
);
let mut value = protocol::TypedData::new();
value.set_string(CONTENT_TYPE.to_string());
metadata.insert(CONTENT_TYPE_KEY.to_string(), value);
metadata.insert(
CONTENT_TYPE_KEY.to_string(),
TypedData {
data: Some(Data::String(CONTENT_TYPE.to_string())),
},
);
let mut value = protocol::TypedData::new();
value.set_string(REPLY_TO.to_string());
metadata.insert(REPLY_TO_KEY.to_string(), value);
metadata.insert(
REPLY_TO_KEY.to_string(),
TypedData {
data: Some(Data::String(REPLY_TO.to_string())),
},
);
let mut value = protocol::TypedData::new();
value.set_int(SEQUENCE_NUMBER);
metadata.insert(SEQUENCE_NUMBER_KEY.to_string(), value);
metadata.insert(
SEQUENCE_NUMBER_KEY.to_string(),
TypedData {
data: Some(Data::Int(SEQUENCE_NUMBER)),
},
);
let mut value = protocol::TypedData::new();
value.set_string(TO.to_string());
metadata.insert(TO_KEY.to_string(), value);
metadata.insert(
TO_KEY.to_string(),
TypedData {
data: Some(Data::String(TO.to_string())),
},
);
let mut value = protocol::TypedData::new();
value.set_string(LABEL.to_string());
metadata.insert(LABEL_KEY.to_string(), value);
metadata.insert(
LABEL_KEY.to_string(),
TypedData {
data: Some(Data::String(LABEL.to_string())),
},
);
let mut value = protocol::TypedData::new();
value.set_string(CORRELATION_ID.to_string());
metadata.insert(CORRELATION_ID_KEY.to_string(), value);
metadata.insert(
CORRELATION_ID_KEY.to_string(),
TypedData {
data: Some(Data::String(CORRELATION_ID.to_string())),
},
);
let mut value = protocol::TypedData::new();
value.set_json(USER_PROPERTIES.to_string());
metadata.insert(USER_PROPERTIES_KEY.to_string(), value);
metadata.insert(
USER_PROPERTIES_KEY.to_string(),
TypedData {
data: Some(Data::Json(USER_PROPERTIES.to_string())),
},
);
let trigger = ServiceBusTrigger::new(data, &mut metadata);

View File

@ -1,5 +1,7 @@
use crate::http::Body;
use crate::rpc::protocol;
use crate::{
http::Body,
rpc::{typed_data::Data, TypedData},
};
use serde_derive::{Deserialize, Serialize};
use serde_json::{from_str, to_string};
use std::borrow::Cow;
@ -47,9 +49,12 @@ pub struct SignalRConnectionInfo {
}
#[doc(hidden)]
impl From<protocol::TypedData> for SignalRConnectionInfo {
fn from(data: protocol::TypedData) -> Self {
from_str(data.get_json()).expect("failed to parse SignalR connection info")
impl From<TypedData> for SignalRConnectionInfo {
fn from(data: TypedData) -> Self {
match &data.data {
Some(Data::Json(s)) => from_str(s).expect("failed to parse SignalR connection info"),
_ => panic!("expected JSON data for SignalR connection info"),
}
}
}
@ -78,8 +83,11 @@ mod tests {
#[test]
fn it_converts_from_typed_data() {
let mut data = protocol::TypedData::new();
data.set_json(r#"{ "url": "foo", "accessToken": "bar"}"#.to_owned());
let data = TypedData {
data: Some(Data::Json(
r#"{ "url": "foo", "accessToken": "bar"}"#.to_owned(),
)),
};
let info: SignalRConnectionInfo = data.into();
assert_eq!(info.url, "foo");

View File

@ -1,4 +1,8 @@
use crate::{rpc::protocol, signalr::GroupAction, FromVec};
use crate::{
rpc::{typed_data::Data, TypedData},
signalr::GroupAction,
FromVec,
};
use serde_derive::{Deserialize, Serialize};
use serde_json::{to_string, to_value, Value};
@ -46,25 +50,24 @@ pub struct SignalRGroupAction {
}
#[doc(hidden)]
impl Into<protocol::TypedData> for SignalRGroupAction {
fn into(self) -> protocol::TypedData {
let mut data = protocol::TypedData::new();
data.set_json(
to_string(&self).expect("failed to convert SignalR group action to JSON string"),
);
data
impl Into<TypedData> for SignalRGroupAction {
fn into(self) -> TypedData {
TypedData {
data: Some(Data::Json(
to_string(&self).expect("failed to convert SignalR group action to JSON string"),
)),
}
}
}
#[doc(hidden)]
impl FromVec<SignalRGroupAction> for protocol::TypedData {
impl FromVec<SignalRGroupAction> for TypedData {
fn from_vec(vec: Vec<SignalRGroupAction>) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(
Value::Array(vec.into_iter().map(|a| to_value(a).unwrap()).collect()).to_string(),
);
data
TypedData {
data: Some(Data::Json(
Value::Array(vec.into_iter().map(|a| to_value(a).unwrap()).collect()).to_string(),
)),
}
}
}
@ -92,10 +95,12 @@ mod tests {
action: GroupAction::Remove,
};
let data: protocol::TypedData = action.into();
let data: TypedData = action.into();
assert_eq!(
data.get_json(),
r#"{"groupName":"foo","userId":"bar","action":"remove"}"#
data.data,
Some(Data::Json(
r#"{"groupName":"foo","userId":"bar","action":"remove"}"#.to_string()
))
);
}
}

View File

@ -1,4 +1,7 @@
use crate::{rpc::protocol, FromVec};
use crate::{
rpc::{typed_data::Data, TypedData},
FromVec,
};
use serde_derive::{Deserialize, Serialize};
use serde_json::{to_string, to_value, Value};
@ -49,22 +52,24 @@ pub struct SignalRMessage {
}
#[doc(hidden)]
impl Into<protocol::TypedData> for SignalRMessage {
fn into(self) -> protocol::TypedData {
let mut data = protocol::TypedData::new();
data.set_json(to_string(&self).expect("failed to convert SignalR message to JSON string"));
data
impl Into<TypedData> for SignalRMessage {
fn into(self) -> TypedData {
TypedData {
data: Some(Data::Json(
to_string(&self).expect("failed to convert SignalR message to JSON string"),
)),
}
}
}
#[doc(hidden)]
impl FromVec<SignalRMessage> for protocol::TypedData {
impl FromVec<SignalRMessage> for TypedData {
fn from_vec(vec: Vec<SignalRMessage>) -> Self {
let mut data = protocol::TypedData::new();
data.set_json(
Value::Array(vec.into_iter().map(|m| to_value(m).unwrap()).collect()).to_string(),
);
data
TypedData {
data: Some(Data::Json(
Value::Array(vec.into_iter().map(|m| to_value(m).unwrap()).collect()).to_string(),
)),
}
}
}
@ -106,10 +111,13 @@ mod tests {
],
};
let data: protocol::TypedData = message.into();
let data: TypedData = message.into();
assert_eq!(
data.get_json(),
r#"{"userId":"foo","groupName":"bar","target":"baz","arguments":[1,"foo",false]}"#
data.data,
Some(Data::Json(
r#"{"userId":"foo","groupName":"bar","target":"baz","arguments":[1,"foo",false]}"#
.to_string()
))
);
}
}

View File

@ -1,5 +1,7 @@
use crate::http::Body;
use crate::rpc::protocol;
use crate::{
http::Body,
rpc::{typed_data::Data, TypedData},
};
use serde_json::{from_str, json, Map, Value};
use std::fmt;
@ -117,23 +119,24 @@ impl fmt::Display for Table {
}
#[doc(hidden)]
impl From<protocol::TypedData> for Table {
fn from(data: protocol::TypedData) -> Self {
if data.has_json() {
let mut rows: Value =
from_str(data.get_json()).expect("expected valid JSON data for table binding");
impl From<TypedData> for Table {
fn from(data: TypedData) -> Self {
match &data.data {
Some(Data::Json(s)) => {
let mut rows: Value =
from_str(s).expect("expected valid JSON data for table binding");
if rows.is_object() {
rows = Value::Array(vec![rows]);
if rows.is_object() {
rows = Value::Array(vec![rows]);
}
if !rows.is_array() {
panic!("expected an object or array for table binding data");
}
Table(rows)
}
if !rows.is_array() {
panic!("expected an object or array for table binding data");
}
Table(rows)
} else {
Table::new()
_ => Table::new(),
}
}
}
@ -151,11 +154,11 @@ impl<'a> Into<Body<'a>> for Table {
}
#[doc(hidden)]
impl Into<protocol::TypedData> for Table {
fn into(self) -> protocol::TypedData {
let mut data = protocol::TypedData::new();
data.set_json(self.0.to_string());
data
impl Into<TypedData> for Table {
fn into(self) -> TypedData {
TypedData {
data: Some(Data::Json(self.0.to_string())),
}
}
}
@ -258,15 +261,17 @@ mod tests {
const TABLE: &'static str =
r#"[{"PartitionKey":"partition1","RowKey":"row1","data":"value"}]"#;
let mut data = protocol::TypedData::new();
data.set_json(TABLE.to_string());
let data = TypedData {
data: Some(Data::Json(TABLE.to_string())),
};
let table: Table = data.into();
assert_eq!(table.len(), 1);
assert_eq!(table.to_string(), TABLE);
let mut data = protocol::TypedData::new();
data.set_string("".to_string());
let data = TypedData {
data: Some(Data::String("".to_string())),
};
let table: Table = data.into();
assert_eq!(table.len(), 0);
@ -291,8 +296,9 @@ mod tests {
const TABLE: &'static str =
r#"[{"PartitionKey":"partition1","RowKey":"row1","data":"value"}]"#;
let mut data = protocol::TypedData::new();
data.set_json(TABLE.to_string());
let data = TypedData {
data: Some(Data::Json(TABLE.to_string())),
};
let table: Table = data.into();
let body: Body = table.into();
@ -309,11 +315,12 @@ mod tests {
let row = table.add_row("partition1", "row1");
row.insert("data".to_string(), Value::String("value".to_string()));
}
let data: protocol::TypedData = table.into();
assert!(data.has_json());
let data: TypedData = table.into();
assert_eq!(
data.get_json(),
r#"[{"PartitionKey":"partition1","RowKey":"row1","data":"value"}]"#
data.data,
Some(Data::Json(
r#"[{"PartitionKey":"partition1","RowKey":"row1","data":"value"}]"#.to_string()
))
);
}
}

View File

@ -1,5 +1,7 @@
use crate::rpc::protocol;
use crate::timer::ScheduleStatus;
use crate::{
rpc::{typed_data::Data, TypedData},
timer::ScheduleStatus,
};
use serde_derive::Deserialize;
use serde_json::from_str;
use std::collections::HashMap;
@ -43,12 +45,11 @@ pub struct TimerInfo {
impl TimerInfo {
#[doc(hidden)]
pub fn new(data: protocol::TypedData, _: &mut HashMap<String, protocol::TypedData>) -> Self {
if !data.has_json() {
panic!("expected JSON data for timer trigger binding");
pub fn new(data: TypedData, _: &mut HashMap<String, TypedData>) -> Self {
match &data.data {
Some(Data::Json(s)) => from_str(s).expect("failed to parse timer JSON data"),
_ => panic!("expected JSON data for timer trigger binding"),
}
from_str(data.get_json()).expect("failed to parse timer JSON data")
}
}
@ -60,8 +61,9 @@ mod tests {
fn it_has_json_data() {
const JSON: &'static str = r#"{"ScheduleStatus":{"Last":"0001-01-01T00:00:00","Next":"2018-07-24T23:24:00-07:00","LastUpdated":"0001-01-01T00:00:00"},"IsPastDue":true}"#;
let mut data = protocol::TypedData::new();
data.set_json(JSON.to_string());
let data = TypedData {
data: Some(Data::Json(JSON.to_string())),
};
let mut metadata = HashMap::new();

View File

@ -1,3 +1,4 @@
use crate::commands::Run;
use clap::{App, AppSettings, Arg, SubCommand};
use std::path::Path;
@ -65,43 +66,5 @@ pub fn create_app<'a, 'b>() -> App<'a, 'b> {
.help("Use verbose output.")
)
)
.subcommand(
SubCommand::with_name("run")
.about("Runs the Rust language worker.")
.arg(
Arg::with_name("host")
.long("host")
.value_name("HOST")
.help("The hostname of the Azure Functions Host.")
.required(true),
)
.arg(
Arg::with_name("port")
.long("port")
.value_name("PORT")
.help("The port of the Azure Functions Host.")
.required(true),
)
.arg(
Arg::with_name("worker_id")
.long("workerId")
.value_name("WORKER_ID")
.help("The worker ID to use when registering with the Azure Functions Host.")
.required(true),
)
.arg(
Arg::with_name("request_id")
.long("requestId")
.value_name("REQUEST_ID")
.help("The request ID to use when communicating with the Azure Functions Host.")
.hidden(true)
.required(true),
)
.arg(
Arg::with_name("max_message_length")
.long("grpcMaxMessageLength")
.value_name("MAXIMUM")
.help("The maximum message length to use for gRPC messages.")
)
)
.subcommand(Run::create_subcommand())
}

View File

@ -0,0 +1,3 @@
mod run;
pub use self::run::*;

View File

@ -0,0 +1,392 @@
use crate::{
backtrace::Backtrace,
codegen::Function,
logger,
registry::Registry,
rpc::{
client::FunctionRpc, status_result::Status, streaming_message::Content,
FunctionLoadRequest, FunctionLoadResponse, InvocationRequest, InvocationResponse,
StartStream, StatusResult, StreamingMessage, WorkerInitResponse, WorkerStatusRequest,
WorkerStatusResponse,
},
};
use clap::{App, Arg, ArgMatches, SubCommand};
use futures::{future::lazy, sync::mpsc::unbounded, Future, Poll, Stream};
use log::error;
use std::cell::RefCell;
use std::net::{IpAddr, SocketAddr};
use std::panic::{catch_unwind, set_hook, AssertUnwindSafe, PanicInfo};
use tokio::{
executor::DefaultExecutor,
net::tcp::{ConnectFuture, TcpStream},
};
use tower_grpc::Request;
use tower_h2::client::Connect;
use tower_service::Service;
use tower_util::MakeService;
const UNKNOWN: &str = "<unknown>";
thread_local!(static FUNCTION_NAME: RefCell<&'static str> = RefCell::new(UNKNOWN));
type Sender = futures::sync::mpsc::UnboundedSender<StreamingMessage>;
struct HttpService(IpAddr, u16);
impl Service<()> for HttpService {
type Response = TcpStream;
type Error = ::std::io::Error;
type Future = ConnectFuture;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
Ok(().into())
}
fn call(&mut self, _: ()) -> Self::Future {
TcpStream::connect(&SocketAddr::new(self.0, self.1))
}
}
pub struct Run<'a> {
host: &'a str,
port: u16,
worker_id: &'a str,
}
impl<'a> Run<'a> {
pub fn create_subcommand<'b>() -> App<'a, 'b> {
SubCommand::with_name("run")
.about("Runs the Rust language worker.")
.arg(
Arg::with_name("host")
.long("host")
.value_name("HOST")
.help("The hostname of the Azure Functions Host.")
.required(true),
)
.arg(
Arg::with_name("port")
.long("port")
.value_name("PORT")
.help("The port of the Azure Functions Host.")
.required(true),
)
.arg(
Arg::with_name("worker_id")
.long("workerId")
.value_name("WORKER_ID")
.help("The worker ID to use when registering with the Azure Functions Host.")
.required(true),
)
.arg(
Arg::with_name("request_id")
.long("requestId")
.value_name("REQUEST_ID")
.help("The request ID to use when communicating with the Azure Functions Host.")
.hidden(true)
.required(true),
)
.arg(
Arg::with_name("max_message_length")
.long("grpcMaxMessageLength")
.value_name("MAXIMUM")
.help("The maximum message length to use for gRPC messages."),
)
}
pub fn execute(&self, mut registry: Registry<'static>) -> Result<(), String> {
ctrlc::set_handler(|| {}).expect("failed setting SIGINT handler");
let uri = format!("http://{0}:{1}", self.host, self.port);
let (sender, receiver) = unbounded::<StreamingMessage>();
// Start by sending a start stream message to the channel
// This will be sent to the host upon connection
sender
.unbounded_send(StreamingMessage {
content: Some(Content::StartStream(StartStream {
worker_id: self.worker_id.to_owned(),
})),
..Default::default()
})
.unwrap();
println!("Connecting to Azure Functions host at {}", uri);
let run = Connect::new(
HttpService(self.host.parse().unwrap(), self.port),
Default::default(),
DefaultExecutor::current(),
)
.make_service(())
.map(move |conn| {
FunctionRpc::new(
tower_request_modifier::Builder::new()
.set_origin(uri)
.build(conn)
.unwrap(),
)
})
.map_err(|e| panic!("failed to connect to host: {}", e))
.and_then(|mut client| {
client
.event_stream(Request::new(
receiver.map_err(|_| panic!("failed to receive from channel")),
))
.map_err(|e| panic!("failed to start event stream: {}", e))
})
.and_then(move |stream| {
stream
.into_inner()
.into_future()
.map_err(|(e, _)| panic!("failed to read worker init request: {}", e))
.and_then(move |(init_req, stream)| {
Run::handle_worker_init_request(
sender.clone(),
init_req.expect("expected a worker init request"),
);
stream
.for_each(move |req| {
Run::handle_request(&mut registry, sender.clone(), req);
Ok(())
})
.map_err(|e| panic!("fail to read request: {}", e))
})
});
tokio::run(run);
Ok(())
}
fn handle_worker_init_request(sender: Sender, req: StreamingMessage) {
match req.content {
Some(Content::WorkerInitRequest(req)) => {
println!(
"Connected to Azure Functions host version {}.",
req.host_version
);
// TODO: use the level requested by the Azure functions host
log::set_boxed_logger(Box::new(logger::Logger::new(
log::Level::Info,
sender.clone(),
)))
.expect("failed to set the global logger instance");
set_hook(Box::new(Run::handle_panic));
log::set_max_level(log::LevelFilter::Trace);
sender
.unbounded_send(StreamingMessage {
content: Some(Content::WorkerInitResponse(WorkerInitResponse {
worker_version: env!("CARGO_PKG_VERSION").to_owned(),
result: Some(StatusResult {
status: Status::Success as i32,
..Default::default()
}),
..Default::default()
})),
..Default::default()
})
.unwrap();
}
_ => panic!("expected a worker init request message from the host"),
};
}
fn handle_request(registry: &mut Registry<'static>, sender: Sender, req: StreamingMessage) {
match req.content {
Some(Content::FunctionLoadRequest(req)) => {
Run::handle_function_load_request(registry, sender, req)
}
Some(Content::InvocationRequest(req)) => {
Run::handle_invocation_request(registry, sender, req)
}
Some(Content::WorkerStatusRequest(req)) => {
Run::handle_worker_status_request(sender, req)
}
Some(Content::FileChangeEventRequest(_)) => {}
Some(Content::InvocationCancel(_)) => {}
Some(Content::FunctionEnvironmentReloadRequest(_)) => {}
_ => panic!("unexpected message from host: {:?}.", req),
};
}
fn handle_function_load_request(
registry: &mut Registry<'static>,
sender: Sender,
req: FunctionLoadRequest,
) {
let mut result = StatusResult::default();
match req.metadata.as_ref() {
Some(metadata) => {
if registry.register(&req.function_id, &metadata.name) {
result.status = Status::Success as i32;
} else {
result.status = Status::Failure as i32;
result.result = format!("Function '{}' does not exist.", metadata.name);
}
}
None => {
result.status = Status::Failure as i32;
result.result = "Function load request metadata is missing.".to_string();
}
};
sender
.unbounded_send(StreamingMessage {
content: Some(Content::FunctionLoadResponse(FunctionLoadResponse {
function_id: req.function_id,
result: Some(result),
..Default::default()
})),
..Default::default()
})
.expect("failed to send function load response");
}
fn handle_invocation_request(
registry: &Registry<'static>,
sender: Sender,
req: InvocationRequest,
) {
if let Some(func) = registry.get(&req.function_id) {
tokio::spawn(lazy(move || {
Run::invoke_function(func, sender, req);
Ok(())
}));
return;
}
let error = format!("Function with id '{}' does not exist.", req.function_id);
sender
.unbounded_send(StreamingMessage {
content: Some(Content::InvocationResponse(InvocationResponse {
invocation_id: req.invocation_id,
result: Some(StatusResult {
status: Status::Failure as i32,
result: error,
..Default::default()
}),
..Default::default()
})),
..Default::default()
})
.expect("failed to send invocation response");
}
fn handle_worker_status_request(sender: Sender, _: WorkerStatusRequest) {
sender
.unbounded_send(StreamingMessage {
content: Some(Content::WorkerStatusResponse(WorkerStatusResponse {})),
..Default::default()
})
.expect("failed to send worker status response");
}
fn invoke_function(func: &'static Function, sender: Sender, mut req: InvocationRequest) {
// Set the function name in TLS
FUNCTION_NAME.with(|n| {
*n.borrow_mut() = &func.name;
});
// Set the invocation ID in TLS
logger::INVOCATION_ID.with(|id| {
id.borrow_mut().replace_range(.., &req.invocation_id);
});
let response = match catch_unwind(AssertUnwindSafe(|| {
(func
.invoker
.as_ref()
.expect("function must have an invoker"))(&func.name, &mut req)
})) {
Ok(res) => res,
Err(_) => InvocationResponse {
invocation_id: req.invocation_id,
result: Some(StatusResult {
status: Status::Failure as i32,
result: "Azure Function panicked: see log for more information.".to_string(),
..Default::default()
}),
..Default::default()
},
};
// Clear the function name from TLS
FUNCTION_NAME.with(|n| {
*n.borrow_mut() = UNKNOWN;
});
// Clear the invocation ID from TLS
logger::INVOCATION_ID.with(|id| {
id.borrow_mut().clear();
});
sender
.unbounded_send(StreamingMessage {
content: Some(Content::InvocationResponse(response)),
..Default::default()
})
.expect("failed to send invocation response");
}
fn handle_panic(info: &PanicInfo) {
let backtrace = Backtrace::new();
match info.location() {
Some(location) => {
error!(
"Azure Function '{}' panicked with '{}', {}:{}:{}{}",
FUNCTION_NAME.with(|f| *f.borrow()),
info.payload()
.downcast_ref::<&str>()
.cloned()
.unwrap_or_else(|| info
.payload()
.downcast_ref::<String>()
.map(String::as_str)
.unwrap_or(UNKNOWN)),
location.file(),
location.line(),
location.column(),
backtrace
);
}
None => {
error!(
"Azure Function '{}' panicked with '{}'{}",
FUNCTION_NAME.with(|f| *f.borrow()),
info.payload()
.downcast_ref::<&str>()
.cloned()
.unwrap_or_else(|| info
.payload()
.downcast_ref::<String>()
.map(String::as_str)
.unwrap_or(UNKNOWN)),
backtrace
);
}
};
}
}
impl<'a> From<&'a ArgMatches<'a>> for Run<'a> {
fn from(args: &'a ArgMatches<'a>) -> Self {
Run {
host: args.value_of("host").expect("A host is required."),
port: args
.value_of("port")
.map(|port| port.parse::<u16>().expect("Invalid port number"))
.expect("A port number is required."),
worker_id: args
.value_of("worker_id")
.expect("A worker id is required."),
}
}
}

View File

@ -1,6 +1,5 @@
use crate::rpc::protocol;
use serde::de::Error;
use serde::Deserialize;
use crate::rpc::{typed_data::Data, TypedData};
use serde::{de::Error, Deserialize};
use serde_json::{from_str, Result, Value};
use std::borrow::Cow;
use std::fmt;
@ -127,22 +126,15 @@ impl fmt::Display for Body<'_> {
}
#[doc(hidden)]
impl<'a> From<&'a protocol::TypedData> for Body<'a> {
fn from(data: &'a protocol::TypedData) -> Self {
if data.has_string() {
return Body::String(Cow::Borrowed(data.get_string()));
impl<'a> From<&'a TypedData> for Body<'a> {
fn from(data: &'a TypedData) -> Self {
match &data.data {
Some(Data::String(s)) => Body::String(Cow::from(s)),
Some(Data::Json(s)) => Body::Json(Cow::from(s)),
Some(Data::Bytes(b)) => Body::Bytes(Cow::from(b)),
Some(Data::Stream(s)) => Body::Bytes(Cow::from(s)),
_ => Body::Empty,
}
if data.has_json() {
return Body::Json(Cow::Borrowed(data.get_json()));
}
if data.has_bytes() {
return Body::Bytes(Cow::Borrowed(data.get_bytes()));
}
if data.has_stream() {
return Body::Bytes(Cow::Borrowed(data.get_stream()));
}
Body::Empty
}
}
@ -183,18 +175,16 @@ impl From<Vec<u8>> for Body<'_> {
}
#[doc(hidden)]
impl Into<protocol::TypedData> for Body<'_> {
fn into(self) -> protocol::TypedData {
let mut data = protocol::TypedData::new();
match self {
Body::Empty => {}
Body::String(s) => data.set_string(s.into_owned()),
Body::Json(s) => data.set_json(s.into_owned()),
Body::Bytes(b) => data.set_bytes(b.into_owned()),
};
data
impl Into<TypedData> for Body<'_> {
fn into(self) -> TypedData {
TypedData {
data: match self {
Body::Empty => None,
Body::String(s) => Some(Data::String(s.into_owned())),
Body::Json(s) => Some(Data::Json(s.into_owned())),
Body::Bytes(b) => Some(Data::Bytes(b.into_owned())),
},
}
}
}
@ -228,28 +218,31 @@ mod tests {
let body: Body = BODY.into();
assert_eq!(body.as_str().unwrap(), BODY);
let data: protocol::TypedData = body.into();
assert_eq!(data.get_string(), BODY);
let data: TypedData = body.into();
assert_eq!(data.data, Some(Data::String(BODY.to_string())));
}
#[test]
fn it_has_a_json_body() {
#[derive(Serialize, Deserialize)]
struct Data {
struct SerializedData {
message: String,
};
const MESSAGE: &'static str = "test";
let data = Data {
let data = SerializedData {
message: MESSAGE.to_string(),
};
let body: Body = ::serde_json::to_value(data).unwrap().into();
assert_eq!(body.as_json::<Data>().unwrap().message, MESSAGE);
assert_eq!(body.as_json::<SerializedData>().unwrap().message, MESSAGE);
let data: protocol::TypedData = body.into();
assert_eq!(data.get_json(), r#"{"message":"test"}"#);
let data: TypedData = body.into();
assert_eq!(
data.data,
Some(Data::Json(r#"{"message":"test"}"#.to_string()))
);
}
#[test]
@ -259,8 +252,8 @@ mod tests {
let body: Body = BODY.into();
assert_eq!(body.as_bytes(), BODY);
let data: protocol::TypedData = body.into();
assert_eq!(data.get_bytes(), BODY);
let data: TypedData = body.into();
assert_eq!(data.data, Some(Data::Bytes(BODY.to_vec())));
}
#[test]
@ -277,26 +270,31 @@ mod tests {
#[test]
fn it_converts_from_typed_data() {
let mut data = protocol::TypedData::new();
data.set_string("test".to_string());
let data = TypedData {
data: Some(Data::String("test".to_string())),
};
let body: Body = (&data).into();
assert!(matches!(body, Body::String(_)));
assert_eq!(body.as_str().unwrap(), "test");
let mut data = protocol::TypedData::new();
data.set_json("test".to_string());
let data = TypedData {
data: Some(Data::Json("test".to_string())),
};
let body: Body = (&data).into();
assert!(matches!(body, Body::Json(_)));
assert_eq!(body.as_str().unwrap(), "test");
let mut data = protocol::TypedData::new();
data.set_bytes(vec![0, 1, 2]);
let data = TypedData {
data: Some(Data::Bytes([0, 1, 2].to_vec())),
};
let body: Body = (&data).into();
assert!(matches!(body, Body::Bytes(_)));
assert_eq!(body.as_bytes(), [0, 1, 2]);
let mut data = protocol::TypedData::new();
data.set_stream(vec![0, 1, 2]);
let data = TypedData {
data: Some(Data::Stream([0, 1, 2].to_vec())),
};
let body: Body = (&data).into();
assert!(matches!(body, Body::Bytes(_)));
assert_eq!(body.as_bytes(), [0, 1, 2]);
@ -340,22 +338,19 @@ mod tests {
#[test]
fn it_converts_to_typed_data() {
let body = Body::Empty;
let data: protocol::TypedData = body.into();
let data: TypedData = body.into();
assert!(data.data.is_none());
let body: Body = "test".into();
let data: protocol::TypedData = body.into();
assert!(data.has_string());
assert_eq!(data.get_string(), "test");
let data: TypedData = body.into();
assert_eq!(data.data, Some(Data::String("test".to_string())));
let body: Body = to_value("test").unwrap().into();
let data: protocol::TypedData = body.into();
assert!(data.has_json());
assert_eq!(data.get_json(), r#""test""#);
let data: TypedData = body.into();
assert_eq!(data.data, Some(Data::Json(r#""test""#.to_string())));
let body: Body = vec![1, 2, 3].into();
let data: protocol::TypedData = body.into();
assert!(data.has_bytes());
assert_eq!(data.get_bytes(), [1, 2, 3]);
let data: TypedData = body.into();
assert_eq!(data.data, Some(Data::Bytes([1, 2, 3].to_vec())));
}
}

View File

@ -52,7 +52,7 @@ impl ResponseBuilder {
/// );
/// ```
pub fn header<T: Into<String>, U: Into<String>>(mut self, name: T, value: U) -> Self {
self.0.data.mut_headers().insert(name.into(), value.into());
self.0.data.headers.insert(name.into(), value.into());
self
}
@ -85,17 +85,17 @@ impl ResponseBuilder {
{
let body = body.into();
if let Body::Empty = &body {
self.0.data.clear_body();
self.0.data.body = None;
return self;
}
if !self.0.headers().contains_key("Content-Type") {
self.0.data.mut_headers().insert(
self.0.data.headers.insert(
"Content-Type".to_string(),
body.default_content_type().to_string(),
);
}
self.0.data.set_body(body.into());
self.0.data.body = Some(Box::new(body.into()));
self
}
}

View File

@ -91,6 +91,7 @@ pub use azure_functions_shared::codegen;
mod backtrace;
mod cli;
mod commands;
mod logger;
mod registry;
mod util;
@ -99,17 +100,14 @@ pub mod bindings;
pub mod blob;
pub mod event_hub;
pub mod http;
#[doc(hidden)]
pub mod rpc;
pub mod signalr;
pub mod timer;
#[doc(no_inline)]
pub use azure_functions_codegen::export;
pub use azure_functions_shared::Context;
pub use azure_functions_shared::{rpc, Context};
use crate::registry::Registry;
use clap::ArgMatches;
use futures::Future;
use serde::Serialize;
use serde_json::{json, to_string_pretty, Serializer};
use std::env::{current_dir, current_exe, var};
@ -707,33 +705,6 @@ fn sync_extensions(script_root: &str, verbose: bool, registry: Registry<'static>
}
}
fn run_worker(
worker_id: &str,
host: &str,
port: u32,
max_message_length: Option<i32>,
registry: Registry<'static>,
) {
ctrlc::set_handler(|| {}).expect("failed setting SIGINT handler");
let client = rpc::Client::new(worker_id.to_string(), max_message_length);
println!("Connecting to Azure Functions host at {}:{}.", host, port);
client
.connect(host, port)
.and_then(move |client| {
println!(
"Connected to Azure Functions host version {}.",
client.host_version().unwrap()
);
client.process_all_messages(registry)
})
.wait()
.unwrap();
}
fn get_local_settings_path(matches: &ArgMatches) -> Option<PathBuf> {
if let Some(local_settings) = matches.value_of("local_settings") {
return Some(local_settings.into());
@ -793,23 +764,14 @@ pub fn worker_main(args: impl Iterator<Item = String>, functions: &[&'static cod
return;
}
if let Some(matches) = matches.subcommand_matches("run") {
run_worker(
matches
.value_of("worker_id")
.expect("A worker id is required."),
matches.value_of("host").expect("A host is required."),
matches
.value_of("port")
.map(|port| port.parse::<u32>().expect("Invalid port number"))
.expect("A port number is required."),
matches
.value_of("max_message_length")
.map(|len| len.parse::<i32>().expect("Invalid maximum message length")),
registry,
);
return;
if let Err(e) = match matches
//.get_or_insert_with(|| create_app().get_matches_from(env::args().skip(1)))
.subcommand()
{
("run", Some(args)) => commands::Run::from(args).execute(registry),
_ => panic!("expected a subcommand."),
} {
eprintln!("error: {}", e);
std::process::exit(1);
}
panic!("expected a subcommand.");
}

View File

@ -1,9 +1,11 @@
use crate::rpc::{protocol, Sender};
use crate::rpc::{rpc_log, streaming_message::Content, RpcLog, StreamingMessage};
use log::{Level, Log, Metadata, Record};
use std::cell::RefCell;
thread_local!(pub static INVOCATION_ID: RefCell<String> = RefCell::new(String::new()));
type Sender = futures::sync::mpsc::UnboundedSender<StreamingMessage>;
pub struct Logger {
level: Level,
sender: Sender,
@ -25,26 +27,31 @@ impl Log for Logger {
return;
}
let mut event = protocol::RpcLog::new();
event.set_level(match record.level() {
Level::Trace => protocol::RpcLog_Level::Trace,
Level::Debug => protocol::RpcLog_Level::Debug,
Level::Info => protocol::RpcLog_Level::Information,
Level::Warn => protocol::RpcLog_Level::Warning,
Level::Error => protocol::RpcLog_Level::Error,
});
event.set_message(record.args().to_string());
let mut event = RpcLog {
level: match record.level() {
Level::Trace => rpc_log::Level::Trace,
Level::Debug => rpc_log::Level::Debug,
Level::Info => rpc_log::Level::Information,
Level::Warn => rpc_log::Level::Warning,
Level::Error => rpc_log::Level::Error,
} as i32,
message: record.args().to_string(),
..Default::default()
};
INVOCATION_ID.with(|id| {
let id = id.borrow();
if !id.is_empty() {
event.set_invocation_id(id.clone());
event.invocation_id = id.clone();
}
});
let mut message = protocol::StreamingMessage::new();
message.set_rpc_log(event);
self.sender.try_send(message).unwrap();
self.sender
.unbounded_send(StreamingMessage {
content: Some(Content::RpcLog(event)),
..Default::default()
})
.unwrap_or(());
}
fn flush(&self) {}

View File

@ -1,4 +0,0 @@
mod client;
pub use self::client::*;
pub use azure_functions_shared::rpc::protocol;

View File

@ -1,383 +0,0 @@
use crate::backtrace::Backtrace;
use crate::codegen::Function;
use crate::logger;
use crate::registry::Registry;
use azure_functions_shared::rpc::protocol;
use crossbeam_channel::unbounded;
use futures::{
future::{lazy, ok},
Future, Sink, Stream,
};
use grpcio::{ChannelBuilder, ClientDuplexReceiver, EnvBuilder, WriteFlags};
use log::{self, error};
use std::cell::RefCell;
use std::panic::{self, AssertUnwindSafe, PanicInfo};
use std::sync::Arc;
use std::thread;
use tokio_threadpool::ThreadPool;
pub type Sender = crossbeam_channel::Sender<protocol::StreamingMessage>;
type Receiver = ClientDuplexReceiver<protocol::StreamingMessage>;
const UNKNOWN: &str = "<unknown>";
thread_local!(static FUNCTION_NAME: RefCell<&'static str> = RefCell::new(UNKNOWN));
pub struct Client {
worker_id: String,
max_message_len: Option<i32>,
client: Option<protocol::FunctionRpcClient>, // We must store the client to ensure the underlying channel isn't dropped
sender: Option<Sender>,
receiver: Option<Receiver>,
host_version: Option<String>,
}
impl Client {
pub fn new(worker_id: String, max_message_len: Option<i32>) -> Client {
Client {
worker_id,
max_message_len,
client: None,
sender: None,
receiver: None,
host_version: None,
}
}
pub fn host_version(&self) -> Option<&str> {
self.host_version.as_ref().map(String::as_str)
}
pub fn sender(&self) -> Option<Sender> {
self.sender.clone()
}
pub fn connect(mut self, host: &str, port: u32) -> impl Future<Item = Client, Error = ()> {
let mut channel = ChannelBuilder::new(Arc::new(EnvBuilder::new().build()));
if let Some(len) = self.max_message_len {
if len > 0 {
channel = channel
.max_receive_message_len(len)
.max_send_message_len(len);
}
}
let (mut rpc_sender, rpc_receiver) = self
.client
.get_or_insert(protocol::FunctionRpcClient::new(
channel.connect(&format!("{}:{}", host, port)),
))
.event_stream()
.unwrap();
let (sender, receiver) = unbounded();
self.sender = Some(sender);
self.receiver = Some(rpc_receiver);
thread::spawn(move || {
while let Ok(msg) = receiver.recv() {
rpc_sender = rpc_sender
.send((msg, WriteFlags::default()))
.wait()
.expect("failed to send message to host");
}
});
let mut message = protocol::StreamingMessage::new();
message.mut_start_stream().worker_id = self.worker_id.to_owned();
self.sender
.as_ref()
.unwrap()
.send(message)
.expect("failed to send start stream message");
self.read().and_then(|(mut c, msg)| {
let msg = msg.expect("host disconnected during worker initialization");
if !msg.has_worker_init_request() {
panic!("expected a worker init request, but received: {:?}.", msg);
}
c.host_version = Some(msg.get_worker_init_request().host_version.clone());
let mut msg = protocol::StreamingMessage::new();
{
let worker_init_res = msg.mut_worker_init_response();
worker_init_res.worker_version = env!("CARGO_PKG_VERSION").to_owned();
let result = worker_init_res.mut_result();
result.status = protocol::StatusResult_Status::Success;
}
c.sender
.as_ref()
.unwrap()
.send(msg)
.expect("failed to send init response message");
ok(c)
})
}
pub fn process_all_messages(
mut self,
mut registry: Registry<'static>,
) -> impl Future<Item = Client, Error = ()> {
let pool = tokio_threadpool::ThreadPool::new();
// TODO: use the level requested by the Azure functions host
log::set_boxed_logger(Box::new(logger::Logger::new(
log::Level::Trace,
self.sender.clone().unwrap(),
)))
.expect("Failed to set the global logger instance");
panic::set_hook(Box::new(Client::handle_panic));
log::set_max_level(log::LevelFilter::Trace);
loop {
let (c, msg) = self.read().wait().expect("Failed to read message");
self = c;
if msg.is_none() {
break;
}
let msg = msg.unwrap();
if msg.has_worker_terminate() {
break;
}
Client::handle_request(&mut registry, self.sender().unwrap(), msg, &pool);
}
pool.shutdown_on_idle().and_then(|_| ok(self))
}
fn read(
mut self,
) -> impl Future<Item = (Client, Option<protocol::StreamingMessage>), Error = ()> {
self.receiver
.take()
.unwrap()
.into_future()
.map_err(|(err, _)| panic!("failed to receive message: {:?}.", err))
.and_then(move |(msg, r)| {
self.receiver = Some(r);
ok((self, msg))
})
}
fn handle_function_load_request(
registry: &mut Registry<'static>,
sender: Sender,
req: protocol::FunctionLoadRequest,
) {
let mut message = protocol::StreamingMessage::new();
{
let response = message.mut_function_load_response();
response.function_id = req.function_id.clone();
response.set_result(match req.metadata.as_ref() {
Some(metadata) => {
let mut result = protocol::StatusResult::new();
if registry.register(&req.function_id, &metadata.name) {
result.status = protocol::StatusResult_Status::Success;
} else {
result.status = protocol::StatusResult_Status::Failure;
result.result = format!("Function '{}' does not exist.", metadata.name);
}
result
}
None => {
let mut result = protocol::StatusResult::new();
result.status = protocol::StatusResult_Status::Failure;
result.result = "Function load request metadata is missing.".to_string();
result
}
});
}
sender
.send(message)
.expect("Failed to send message to response thread");
}
fn invoke_function(
func: &'static Function,
sender: Sender,
mut req: protocol::InvocationRequest,
) {
// Set the function name in TLS
FUNCTION_NAME.with(|n| {
*n.borrow_mut() = &func.name;
});
// Set the invocation ID in TLS
logger::INVOCATION_ID.with(|id| {
id.borrow_mut().replace_range(.., &req.invocation_id);
});
let res = match panic::catch_unwind(AssertUnwindSafe(|| {
(func
.invoker
.as_ref()
.expect("function must have an invoker"))(&func.name, &mut req)
})) {
Ok(res) => res,
Err(_) => {
let mut res = protocol::InvocationResponse::new();
res.set_invocation_id(req.invocation_id.clone());
let mut result = protocol::StatusResult::new();
result.status = protocol::StatusResult_Status::Failure;
result.result =
"Azure Function panicked: see log for more information.".to_string();
res.set_result(result);
res
}
};
// Clear the function name from TLS
FUNCTION_NAME.with(|n| {
*n.borrow_mut() = UNKNOWN;
});
// Clear the invocation ID from TLS
logger::INVOCATION_ID.with(|id| {
id.borrow_mut().clear();
});
let mut message = protocol::StreamingMessage::new();
message.set_invocation_response(res);
sender
.try_send(message)
.expect("Failed to send message to response thread");
}
fn handle_invocation_request(
registry: &Registry<'static>,
sender: Sender,
req: protocol::InvocationRequest,
pool: &ThreadPool,
) {
if let Some(func) = registry.get(&req.function_id) {
pool.spawn(lazy(move || {
Client::invoke_function(func, sender, req);
Ok(())
}));
return;
}
let mut res = protocol::InvocationResponse::new();
res.set_invocation_id(req.invocation_id.clone());
let mut result = protocol::StatusResult::new();
result.status = protocol::StatusResult_Status::Failure;
result.result = format!("Function with id '{}' does not exist.", req.function_id);
res.set_result(result);
let mut message = protocol::StreamingMessage::new();
message.set_invocation_response(res);
sender
.send(message)
.expect("Failed to send message to response thread");
}
fn handle_worker_status_request(sender: Sender, _req: protocol::WorkerStatusRequest) {
let mut message = protocol::StreamingMessage::new();
{
message.mut_worker_status_response();
// TODO: in the future, this message might have fields to set
}
sender
.send(message)
.expect("Failed to send message to response thread");
}
fn handle_request(
registry: &mut Registry<'static>,
sender: Sender,
mut msg: protocol::StreamingMessage,
pool: &ThreadPool,
) {
if msg.has_function_load_request() {
Client::handle_function_load_request(
registry,
sender,
msg.take_function_load_request(),
);
return;
}
if msg.has_invocation_request() {
Client::handle_invocation_request(
registry,
sender,
msg.take_invocation_request(),
pool,
);
return;
}
if msg.has_worker_status_request() {
Client::handle_worker_status_request(sender, msg.take_worker_status_request());
return;
}
if msg.has_file_change_event_request() {
// Not supported (no-op)
return;
}
if msg.has_invocation_cancel() {
// Not supported (no-op)
return;
}
if msg.has_function_environment_reload_request() {
// Not supported (no-op)
return;
}
panic!("Unexpected message from host: {:?}.", msg);
}
fn handle_panic(info: &PanicInfo) {
let backtrace = Backtrace::new();
match info.location() {
Some(location) => {
error!(
"Azure Function '{}' panicked with '{}', {}:{}:{}{}",
FUNCTION_NAME.with(|f| *f.borrow()),
info.payload()
.downcast_ref::<&str>()
.cloned()
.unwrap_or_else(|| info
.payload()
.downcast_ref::<String>()
.map(String::as_str)
.unwrap_or(UNKNOWN)),
location.file(),
location.line(),
location.column(),
backtrace
);
}
None => {
error!(
"Azure Function '{}' panicked with '{}'{}",
FUNCTION_NAME.with(|f| *f.borrow()),
info.payload()
.downcast_ref::<&str>()
.cloned()
.unwrap_or_else(|| info
.payload()
.downcast_ref::<String>()
.map(String::as_str)
.unwrap_or(UNKNOWN)),
backtrace
);
}
};
}
}

View File

@ -1,48 +1,40 @@
use crate::rpc::protocol;
use crate::rpc::{typed_data::Data, TypedData};
use chrono::{DateTime, FixedOffset, Utc};
use serde::{de::Error, de::IntoDeserializer, Deserialize, Deserializer};
use serde_json::from_str;
use std::str::{from_utf8, FromStr};
pub fn convert_from<'a, T>(data: &'a protocol::TypedData) -> Option<T>
pub fn convert_from<'a, T>(data: &'a TypedData) -> Option<T>
where
T: FromStr + Deserialize<'a>,
{
if data.has_string() {
return data.get_string().parse::<T>().ok();
}
if data.has_json() {
return from_str(data.get_json()).ok();
}
if data.has_bytes() {
if let Ok(s) = from_utf8(data.get_bytes()) {
return s.parse::<T>().ok();
match &data.data {
Some(Data::String(s)) => s.parse::<T>().ok(),
Some(Data::Json(s)) => from_str(s).ok(),
Some(Data::Bytes(b)) => {
if let Ok(s) = from_utf8(b) {
return s.parse::<T>().ok();
}
None
}
return None;
}
if data.has_stream() {
if let Ok(s) = from_utf8(data.get_stream()) {
return s.parse::<T>().ok();
Some(Data::Stream(s)) => {
if let Ok(s) = from_utf8(s) {
return s.parse::<T>().ok();
}
None
}
return None;
Some(Data::Int(i)) => {
let deserializer: ::serde::de::value::I64Deserializer<::serde_json::error::Error> =
i.into_deserializer();
T::deserialize(deserializer).ok()
}
Some(Data::Double(d)) => {
let deserializer: ::serde::de::value::F64Deserializer<::serde_json::error::Error> =
d.into_deserializer();
T::deserialize(deserializer).ok()
}
_ => None,
}
if data.has_int() {
let deserializer: ::serde::de::value::I64Deserializer<::serde_json::error::Error> =
data.get_int().into_deserializer();
return T::deserialize(deserializer).ok();
}
if data.has_double() {
let deserializer: ::serde::de::value::F64Deserializer<::serde_json::error::Error> =
data.get_double().into_deserializer();
return T::deserialize(deserializer).ok();
}
None
}
pub fn deserialize_datetime<'a, D>(deserializer: D) -> Result<DateTime<Utc>, D::Error>
@ -70,8 +62,9 @@ mod tests {
fn it_converts_from_string_data() {
const DATA: &'static str = "test";
let mut data = protocol::TypedData::new();
data.set_string(DATA.to_string());
let data = TypedData {
data: Some(Data::String(DATA.to_string())),
};
let s: String = convert_from(&data).unwrap();
assert_eq!(s, DATA);
@ -79,8 +72,9 @@ mod tests {
#[test]
fn it_converts_from_json_data() {
let mut data = protocol::TypedData::new();
data.set_json(r#""hello world""#.to_string());
let data = TypedData {
data: Some(Data::Json(r#""hello world""#.to_string())),
};
let s: String = convert_from(&data).unwrap();
assert_eq!(s, "hello world");
@ -88,10 +82,11 @@ mod tests {
#[test]
fn it_converts_from_bytes_data() {
let mut data = protocol::TypedData::new();
data.set_bytes(vec![
0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64,
]);
let data = TypedData {
data: Some(Data::Bytes(vec![
0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64,
])),
};
let s: String = convert_from(&data).unwrap();
assert_eq!(s, "hello world");
@ -99,10 +94,11 @@ mod tests {
#[test]
fn it_converts_from_stream_data() {
let mut data = protocol::TypedData::new();
data.set_stream(vec![
0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64,
]);
let data = TypedData {
data: Some(Data::Stream(vec![
0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x77, 0x6F, 0x72, 0x6C, 0x64,
])),
};
let s: String = convert_from(&data).unwrap();
assert_eq!(s, "hello world");
@ -112,8 +108,9 @@ mod tests {
fn it_converts_from_int_data() {
const DATA: i64 = 42;
let mut data = protocol::TypedData::new();
data.set_int(DATA);
let data = TypedData {
data: Some(Data::Int(DATA)),
};
let d: i64 = convert_from(&data).unwrap();
assert_eq!(d, DATA);
@ -123,8 +120,9 @@ mod tests {
fn it_converts_from_double_data() {
const DATA: f64 = 42.24;
let mut data = protocol::TypedData::new();
data.set_double(DATA);
let data = TypedData {
data: Some(Data::Double(DATA)),
};
let d: f64 = convert_from(&data).unwrap();
assert_eq!(d, DATA);

View File

@ -16,7 +16,7 @@ RUN apt-get update \
&& wget https://github.com/google/protobuf/releases/download/v3.6.1/protoc-3.6.1-linux-x86_64.zip \
&& unzip protoc-3.6.1-linux-x86_64.zip -d /usr \
&& rm protoc-3.6.1-linux-x86_64.zip \
&& apt-get install -y cmake g++ dotnet-sdk-2.2 \
&& apt-get install -y dotnet-sdk-2.2 \
&& apt-get remove -y --purge wget unzip apt-transport-https gnupg \
&& apt-get autoremove -y \
&& apt-get clean \

View File

@ -1,6 +1,6 @@
# syntax=docker/dockerfile-upstream:experimental
FROM peterhuene/azure-functions-rs-build:0.5.2 AS build-image
FROM peterhuene/azure-functions-rs-build:0.7.0 AS build-image
WORKDIR /src
COPY . /src