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:
parent
2ef12d9b97
commit
f1e3f12af6
|
@ -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
|
||||
|
|
File diff suppressed because it is too large
Load Diff
56
README.md
56
README.md
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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
|
@ -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()
|
||||
}
|
|
@ -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
|
|
@ -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>>,
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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])));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
mod run;
|
||||
|
||||
pub use self::run::*;
|
|
@ -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."),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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())));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
mod client;
|
||||
pub use self::client::*;
|
||||
|
||||
pub use azure_functions_shared::rpc::protocol;
|
|
@ -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
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue