@temporalio/core-bridge 1.11.8 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.lock +219 -193
- package/Cargo.toml +27 -8
- package/README.md +5 -0
- package/index.js +72 -12
- package/lib/errors.d.ts +25 -0
- package/lib/errors.js +76 -1
- package/lib/errors.js.map +1 -1
- package/lib/index.d.ts +11 -478
- package/lib/index.js +28 -5
- package/lib/index.js.map +1 -1
- package/lib/native.d.ts +330 -0
- package/lib/{worker-tuner.js → native.js} +1 -1
- package/lib/native.js.map +1 -0
- package/package.json +7 -3
- package/releases/aarch64-apple-darwin/index.node +0 -0
- package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
- package/releases/x86_64-apple-darwin/index.node +0 -0
- package/releases/x86_64-pc-windows-msvc/index.node +0 -0
- package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
- package/sdk-core/.cargo/config.toml +8 -2
- package/sdk-core/.cargo/multi-worker-manual-test +15 -0
- package/sdk-core/.github/workflows/per-pr.yml +40 -11
- package/sdk-core/AGENTS.md +73 -0
- package/sdk-core/ARCHITECTURE.md +71 -23
- package/sdk-core/Cargo.toml +1 -1
- package/sdk-core/README.md +4 -4
- package/sdk-core/arch_docs/workflow_task_chunking.md +51 -0
- package/sdk-core/client/Cargo.toml +1 -1
- package/sdk-core/client/src/lib.rs +49 -13
- package/sdk-core/client/src/metrics.rs +15 -16
- package/sdk-core/client/src/proxy.rs +2 -2
- package/sdk-core/client/src/raw.rs +54 -8
- package/sdk-core/client/src/retry.rs +109 -13
- package/sdk-core/client/src/worker_registry/mod.rs +4 -4
- package/sdk-core/client/src/workflow_handle/mod.rs +1 -1
- package/sdk-core/core/Cargo.toml +28 -8
- package/sdk-core/core/src/abstractions.rs +62 -10
- package/sdk-core/core/src/core_tests/activity_tasks.rs +180 -8
- package/sdk-core/core/src/core_tests/mod.rs +4 -4
- package/sdk-core/core/src/core_tests/queries.rs +18 -4
- package/sdk-core/core/src/core_tests/workers.rs +3 -3
- package/sdk-core/core/src/core_tests/workflow_tasks.rs +191 -25
- package/sdk-core/core/src/ephemeral_server/mod.rs +10 -3
- package/sdk-core/core/src/internal_flags.rs +14 -14
- package/sdk-core/core/src/lib.rs +5 -2
- package/sdk-core/core/src/pollers/mod.rs +1 -1
- package/sdk-core/core/src/pollers/poll_buffer.rs +495 -164
- package/sdk-core/core/src/protosext/mod.rs +3 -3
- package/sdk-core/core/src/replay/mod.rs +3 -3
- package/sdk-core/core/src/telemetry/metrics.rs +13 -4
- package/sdk-core/core/src/telemetry/mod.rs +72 -70
- package/sdk-core/core/src/telemetry/otel.rs +51 -54
- package/sdk-core/core/src/test_help/mod.rs +9 -3
- package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +31 -11
- package/sdk-core/core/src/worker/activities/local_activities.rs +35 -28
- package/sdk-core/core/src/worker/activities.rs +58 -30
- package/sdk-core/core/src/worker/client/mocks.rs +3 -3
- package/sdk-core/core/src/worker/client.rs +155 -53
- package/sdk-core/core/src/worker/mod.rs +103 -95
- package/sdk-core/core/src/worker/nexus.rs +100 -73
- package/sdk-core/core/src/worker/tuner/resource_based.rs +14 -6
- package/sdk-core/core/src/worker/tuner.rs +4 -13
- package/sdk-core/core/src/worker/workflow/history_update.rs +47 -51
- package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -4
- package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +127 -32
- package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +1 -2
- package/sdk-core/core/src/worker/workflow/machines/update_state_machine.rs +1 -1
- package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +55 -42
- package/sdk-core/core/src/worker/workflow/managed_run.rs +45 -35
- package/sdk-core/core/src/worker/workflow/mod.rs +200 -97
- package/sdk-core/core/src/worker/workflow/wft_poller.rs +175 -4
- package/sdk-core/core/src/worker/workflow/workflow_stream.rs +38 -36
- package/sdk-core/core-api/Cargo.toml +8 -0
- package/sdk-core/core-api/src/envconfig.rs +1544 -0
- package/sdk-core/core-api/src/lib.rs +2 -0
- package/sdk-core/core-api/src/telemetry/metrics.rs +8 -0
- package/sdk-core/core-api/src/telemetry.rs +36 -3
- package/sdk-core/core-api/src/worker.rs +301 -75
- package/sdk-core/docker/docker-compose-telem.yaml +1 -0
- package/sdk-core/etc/prometheus.yaml +6 -2
- package/sdk-core/histories/long_local_activity_with_update-0_history.bin +0 -0
- package/sdk-core/histories/long_local_activity_with_update-1_history.bin +0 -0
- package/sdk-core/histories/long_local_activity_with_update-2_history.bin +0 -0
- package/sdk-core/histories/long_local_activity_with_update-3_history.bin +0 -0
- package/sdk-core/sdk/src/activity_context.rs +5 -0
- package/sdk-core/sdk/src/interceptors.rs +73 -3
- package/sdk-core/sdk/src/lib.rs +15 -16
- package/sdk-core/sdk/src/workflow_context/options.rs +10 -0
- package/sdk-core/sdk/src/workflow_context.rs +48 -29
- package/sdk-core/sdk/src/workflow_future.rs +5 -6
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/.github/workflows/push-to-buf.yml +20 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/CODEOWNERS +6 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/README.md +17 -6
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/VERSION +1 -1
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.lock +7 -2
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.yaml +2 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/request_response.proto +78 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/service.proto +29 -0
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/identity/v1/message.proto +74 -32
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/namespace/v1/message.proto +45 -15
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/nexus/v1/message.proto +7 -1
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/operation/v1/message.proto +3 -3
- package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/region/v1/message.proto +3 -3
- package/sdk-core/sdk-core-protos/protos/api_upstream/LICENSE +1 -1
- package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +2 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +1103 -88
- package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +1233 -151
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/activity/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/batch/v1/message.proto +19 -24
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +12 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +45 -45
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/command_type.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/common.proto +15 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/deployment.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +4 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/namespace.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/nexus.proto +0 -20
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/query.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/schedule.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/workflow.proto +4 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/errordetails/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/export/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/filter/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +75 -49
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +0 -20
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/protocol/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/query/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/replication/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/rules/v1/message.proto +90 -0
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/enhanced_stack_trace.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/user_metadata.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +17 -38
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/update/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/version/v1/message.proto +0 -22
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +151 -44
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +172 -65
- package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +69 -28
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_task/activity_task.proto +18 -0
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/common/common.proto +5 -0
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/nexus/nexus.proto +16 -1
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +21 -15
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +3 -0
- package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +3 -0
- package/sdk-core/sdk-core-protos/src/lib.rs +60 -16
- package/sdk-core/test-utils/src/lib.rs +157 -39
- package/sdk-core/tests/cloud_tests.rs +86 -0
- package/sdk-core/tests/fuzzy_workflow.rs +23 -26
- package/sdk-core/tests/global_metric_tests.rs +116 -0
- package/sdk-core/tests/heavy_tests.rs +127 -7
- package/sdk-core/tests/integ_tests/client_tests.rs +2 -8
- package/sdk-core/tests/integ_tests/metrics_tests.rs +100 -106
- package/sdk-core/tests/integ_tests/polling_tests.rs +94 -8
- package/sdk-core/tests/integ_tests/update_tests.rs +75 -6
- package/sdk-core/tests/integ_tests/worker_tests.rs +54 -5
- package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +240 -0
- package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +41 -3
- package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +168 -8
- package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +285 -15
- package/sdk-core/tests/integ_tests/workflow_tests/priority.rs +12 -4
- package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +3 -2
- package/sdk-core/tests/integ_tests/workflow_tests.rs +124 -74
- package/sdk-core/tests/main.rs +3 -51
- package/sdk-core/tests/manual_tests.rs +430 -0
- package/sdk-core/tests/runner.rs +28 -2
- package/src/client.rs +565 -0
- package/src/helpers/abort_controller.rs +204 -0
- package/src/helpers/callbacks.rs +299 -0
- package/src/helpers/errors.rs +302 -0
- package/src/helpers/future.rs +44 -0
- package/src/helpers/handles.rs +191 -0
- package/src/helpers/inspect.rs +18 -0
- package/src/helpers/json_string.rs +58 -0
- package/src/helpers/mod.rs +20 -0
- package/src/helpers/properties.rs +71 -0
- package/src/helpers/try_from_js.rs +213 -0
- package/src/helpers/try_into_js.rs +129 -0
- package/src/lib.rs +28 -40
- package/src/logs.rs +111 -0
- package/src/metrics.rs +325 -0
- package/src/runtime.rs +409 -498
- package/src/testing.rs +315 -57
- package/src/worker.rs +907 -378
- package/ts/errors.ts +57 -0
- package/ts/index.ts +10 -596
- package/ts/native.ts +496 -0
- package/lib/worker-tuner.d.ts +0 -167
- package/lib/worker-tuner.js.map +0 -1
- package/src/conversions/slot_supplier_bridge.rs +0 -291
- package/src/conversions.rs +0 -618
- package/src/errors.rs +0 -38
- package/src/helpers.rs +0 -297
- package/ts/worker-tuner.ts +0 -193
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
use neon::prelude::*;
|
|
2
|
+
|
|
3
|
+
use super::{AppendFieldContext as _, BridgeError, BridgeResult, TryFromJs, TryIntoJs};
|
|
4
|
+
|
|
5
|
+
/// Extension trait for `JsObject`.
|
|
6
|
+
pub trait ObjectExt: Object {
|
|
7
|
+
/// Get a property from a JS Object, converting it to a Rust value using `TryFromJs`.
|
|
8
|
+
///
|
|
9
|
+
/// Type errors will be reported with `field` set to the name of the property being
|
|
10
|
+
/// accessed; it is expected that caller will prepend any additional path components
|
|
11
|
+
/// that led to this object, to help identify the object that failed.
|
|
12
|
+
fn get_property_into<'cx, C: Context<'cx>, T: TryFromJs>(
|
|
13
|
+
&self,
|
|
14
|
+
cx: &mut C,
|
|
15
|
+
key: &str,
|
|
16
|
+
) -> BridgeResult<T> {
|
|
17
|
+
let value = self.get_value(cx, key)?;
|
|
18
|
+
if value.is_a::<JsUndefined, _>(cx) {
|
|
19
|
+
return Err(BridgeError::TypeError {
|
|
20
|
+
message: format!("Missing property '{key}'"),
|
|
21
|
+
field: Some(key.to_string()),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
<T>::try_from_js(cx, value).field(key)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Set a property on a JS Object, converting it from a Rust value using `TryIntoJs`.
|
|
28
|
+
fn set_property_from<'cx, C: Context<'cx>, T: TryIntoJs>(
|
|
29
|
+
&self,
|
|
30
|
+
cx: &mut C,
|
|
31
|
+
key: &str,
|
|
32
|
+
value: T,
|
|
33
|
+
) -> NeonResult<()> {
|
|
34
|
+
let key = cx.string(key);
|
|
35
|
+
let value = value.try_into_js(cx)?;
|
|
36
|
+
self.set(cx, key, value)?;
|
|
37
|
+
Ok(())
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// Set a property on a JS Object, converting it from a Rust value using `TryIntoJs`.
|
|
41
|
+
fn set_property<'cx, C: Context<'cx>, T: Value>(
|
|
42
|
+
&self,
|
|
43
|
+
cx: &mut C,
|
|
44
|
+
key: &str,
|
|
45
|
+
value: Handle<'cx, T>,
|
|
46
|
+
) -> NeonResult<()> {
|
|
47
|
+
let key = cx.string(key);
|
|
48
|
+
self.set(cx, key, value)?;
|
|
49
|
+
Ok(())
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
impl ObjectExt for JsObject {}
|
|
54
|
+
impl ObjectExt for JsError {}
|
|
55
|
+
|
|
56
|
+
/// Extension trait for `FunctionContext` that adds a method to get an argument and
|
|
57
|
+
/// convert it to a value using `TryFromJs`.
|
|
58
|
+
///
|
|
59
|
+
/// Type errors will be reported with field name `args[index]`; it is expected that
|
|
60
|
+
/// caller will prepend the function name to the error message's `field` to help identify
|
|
61
|
+
/// the function that failed.
|
|
62
|
+
pub trait FunctionContextExt {
|
|
63
|
+
fn argument_into<T: TryFromJs>(&mut self, index: usize) -> BridgeResult<T>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
impl FunctionContextExt for FunctionContext<'_> {
|
|
67
|
+
fn argument_into<T: TryFromJs>(&mut self, index: usize) -> BridgeResult<T> {
|
|
68
|
+
let value = self.argument::<JsValue>(index)?;
|
|
69
|
+
<T>::try_from_js(self, value).field(format!("args[{index}]").as_str())
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
use std::{collections::HashMap, net::SocketAddr, time::Duration};
|
|
2
|
+
|
|
3
|
+
use neon::{
|
|
4
|
+
handle::Handle,
|
|
5
|
+
object::Object,
|
|
6
|
+
prelude::Context,
|
|
7
|
+
types::{
|
|
8
|
+
JsArray, JsBoolean, JsBuffer, JsNull, JsNumber, JsObject, JsString, JsUndefined, JsValue,
|
|
9
|
+
Value, buffer::TypedArray,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
use temporal_sdk_core::Url;
|
|
13
|
+
|
|
14
|
+
use super::{BridgeError, BridgeResult};
|
|
15
|
+
|
|
16
|
+
/// Trait for Rust types that can be created from JavaScript values, possibly throwing an error.
|
|
17
|
+
pub trait TryFromJs: Sized {
|
|
18
|
+
fn try_from_js<'cx, 'b>(
|
|
19
|
+
cx: &mut impl Context<'cx>,
|
|
20
|
+
js_value: Handle<'b, JsValue>,
|
|
21
|
+
) -> BridgeResult<Self>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// TryFromJs implementations for primitives and other basic types //////////////////////////////////
|
|
25
|
+
|
|
26
|
+
impl TryFromJs for () {
|
|
27
|
+
fn try_from_js<'cx, 'b>(
|
|
28
|
+
cx: &mut impl Context<'cx>,
|
|
29
|
+
js_value: Handle<'b, JsValue>,
|
|
30
|
+
) -> BridgeResult<Self> {
|
|
31
|
+
// Make sure we are not getting something where we expect nothing
|
|
32
|
+
let _ = js_value.downcast::<JsUndefined, _>(cx)?;
|
|
33
|
+
Ok(())
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl TryFromJs for String {
|
|
38
|
+
fn try_from_js<'cx, 'b>(
|
|
39
|
+
cx: &mut impl Context<'cx>,
|
|
40
|
+
js_value: Handle<'b, JsValue>,
|
|
41
|
+
) -> BridgeResult<Self> {
|
|
42
|
+
Ok(js_value.downcast::<JsString, _>(cx)?.value(cx))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl TryFromJs for bool {
|
|
47
|
+
fn try_from_js<'cx, 'b>(
|
|
48
|
+
cx: &mut impl Context<'cx>,
|
|
49
|
+
js_value: Handle<'b, JsValue>,
|
|
50
|
+
) -> BridgeResult<Self> {
|
|
51
|
+
Ok(js_value.downcast::<JsBoolean, _>(cx)?.value(cx))
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
impl TryFromJs for u16 {
|
|
56
|
+
fn try_from_js<'cx, 'b>(
|
|
57
|
+
cx: &mut impl Context<'cx>,
|
|
58
|
+
js_value: Handle<'b, JsValue>,
|
|
59
|
+
) -> BridgeResult<Self> {
|
|
60
|
+
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
|
61
|
+
Ok(js_value.downcast::<JsNumber, _>(cx)?.value(cx) as Self)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#[allow(clippy::cast_possible_truncation)]
|
|
66
|
+
impl TryFromJs for i32 {
|
|
67
|
+
fn try_from_js<'cx, 'b>(
|
|
68
|
+
cx: &mut impl Context<'cx>,
|
|
69
|
+
js_value: Handle<'b, JsValue>,
|
|
70
|
+
) -> BridgeResult<Self> {
|
|
71
|
+
Ok(js_value.downcast::<JsNumber, _>(cx)?.value(cx) as Self)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
#[allow(clippy::cast_possible_truncation)]
|
|
76
|
+
impl TryFromJs for f32 {
|
|
77
|
+
fn try_from_js<'cx, 'b>(
|
|
78
|
+
cx: &mut impl Context<'cx>,
|
|
79
|
+
js_value: Handle<'b, JsValue>,
|
|
80
|
+
) -> BridgeResult<Self> {
|
|
81
|
+
Ok(js_value.downcast::<JsNumber, _>(cx)?.value(cx) as Self)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
#[allow(clippy::cast_possible_truncation)]
|
|
86
|
+
impl TryFromJs for u64 {
|
|
87
|
+
fn try_from_js<'cx, 'b>(
|
|
88
|
+
cx: &mut impl Context<'cx>,
|
|
89
|
+
js_value: Handle<'b, JsValue>,
|
|
90
|
+
) -> BridgeResult<Self> {
|
|
91
|
+
#[allow(clippy::cast_sign_loss)]
|
|
92
|
+
Ok(js_value.downcast::<JsNumber, _>(cx)?.value(cx) as Self)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
impl TryFromJs for f64 {
|
|
97
|
+
fn try_from_js<'cx, 'b>(
|
|
98
|
+
cx: &mut impl Context<'cx>,
|
|
99
|
+
js_value: Handle<'b, JsValue>,
|
|
100
|
+
) -> BridgeResult<Self> {
|
|
101
|
+
Ok(js_value.downcast::<JsNumber, _>(cx)?.value(cx))
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
impl TryFromJs for usize {
|
|
106
|
+
fn try_from_js<'cx, 'b>(
|
|
107
|
+
cx: &mut impl Context<'cx>,
|
|
108
|
+
js_value: Handle<'b, JsValue>,
|
|
109
|
+
) -> BridgeResult<Self> {
|
|
110
|
+
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
|
111
|
+
Ok(js_value.downcast::<JsNumber, _>(cx)?.value(cx) as Self)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
impl TryFromJs for Duration {
|
|
116
|
+
fn try_from_js<'cx, 'b>(
|
|
117
|
+
cx: &mut impl Context<'cx>,
|
|
118
|
+
js_value: Handle<'b, JsValue>,
|
|
119
|
+
) -> BridgeResult<Self> {
|
|
120
|
+
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
|
|
121
|
+
Ok(Self::from_millis(
|
|
122
|
+
js_value.downcast::<JsNumber, _>(cx)?.value(cx) as u64,
|
|
123
|
+
))
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
impl<T: TryFromJs> TryFromJs for Option<T> {
|
|
128
|
+
fn try_from_js<'cx, 'b>(
|
|
129
|
+
cx: &mut impl Context<'cx>,
|
|
130
|
+
js_value: Handle<'b, JsValue>,
|
|
131
|
+
) -> BridgeResult<Self> {
|
|
132
|
+
if js_value.is_a::<JsNull, _>(cx) {
|
|
133
|
+
Ok(None)
|
|
134
|
+
} else {
|
|
135
|
+
Ok(Some(T::try_from_js(cx, js_value)?))
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
impl TryFromJs for Vec<u8> {
|
|
141
|
+
fn try_from_js<'cx, 'b>(
|
|
142
|
+
cx: &mut impl Context<'cx>,
|
|
143
|
+
js_value: Handle<'b, JsValue>,
|
|
144
|
+
) -> BridgeResult<Self> {
|
|
145
|
+
Ok(js_value.downcast::<JsBuffer, _>(cx)?.as_slice(cx).to_vec())
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
impl<T: TryFromJs> TryFromJs for Vec<T> {
|
|
150
|
+
fn try_from_js<'cx, 'b>(
|
|
151
|
+
cx: &mut impl Context<'cx>,
|
|
152
|
+
js_value: Handle<'b, JsValue>,
|
|
153
|
+
) -> BridgeResult<Self> {
|
|
154
|
+
let array = js_value.downcast::<JsArray, _>(cx)?;
|
|
155
|
+
let len = array.len(cx);
|
|
156
|
+
let mut result = Self::with_capacity(len as usize);
|
|
157
|
+
|
|
158
|
+
for i in 0..len {
|
|
159
|
+
let value = array.get_value(cx, i)?;
|
|
160
|
+
result.push(T::try_from_js(cx, value)?);
|
|
161
|
+
}
|
|
162
|
+
Ok(result)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#[allow(clippy::implicit_hasher)]
|
|
167
|
+
impl<T: TryFromJs> TryFromJs for HashMap<String, T> {
|
|
168
|
+
fn try_from_js<'cx, 'b>(
|
|
169
|
+
cx: &mut impl Context<'cx>,
|
|
170
|
+
js_value: Handle<'b, JsValue>,
|
|
171
|
+
) -> BridgeResult<Self> {
|
|
172
|
+
let obj = js_value.downcast::<JsObject, _>(cx)?;
|
|
173
|
+
let props = obj.get_own_property_names(cx)?.to_vec(cx)?;
|
|
174
|
+
|
|
175
|
+
let mut map = Self::new();
|
|
176
|
+
for key_handle in props {
|
|
177
|
+
let key = key_handle.to_string(cx)?.value(cx);
|
|
178
|
+
let value = obj.get_value(cx, key_handle)?;
|
|
179
|
+
map.insert(key, T::try_from_js(cx, value)?);
|
|
180
|
+
}
|
|
181
|
+
Ok(map)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
impl TryFromJs for SocketAddr {
|
|
186
|
+
fn try_from_js<'cx, 'b>(
|
|
187
|
+
cx: &mut impl Context<'cx>,
|
|
188
|
+
js_value: Handle<'b, JsValue>,
|
|
189
|
+
) -> BridgeResult<Self> {
|
|
190
|
+
let addr = js_value.downcast::<JsString, _>(cx)?;
|
|
191
|
+
addr.value(cx)
|
|
192
|
+
.parse::<Self>()
|
|
193
|
+
.map_err(|e| BridgeError::TypeError {
|
|
194
|
+
field: None,
|
|
195
|
+
message: e.to_string(),
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
impl TryFromJs for Url {
|
|
201
|
+
fn try_from_js<'cx, 'b>(
|
|
202
|
+
cx: &mut impl Context<'cx>,
|
|
203
|
+
js_value: Handle<'b, JsValue>,
|
|
204
|
+
) -> BridgeResult<Self> {
|
|
205
|
+
let url = js_value.downcast::<JsString, _>(cx)?;
|
|
206
|
+
url.value(cx)
|
|
207
|
+
.parse::<Self>()
|
|
208
|
+
.map_err(|e| BridgeError::TypeError {
|
|
209
|
+
field: None,
|
|
210
|
+
message: e.to_string(),
|
|
211
|
+
})
|
|
212
|
+
}
|
|
213
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
sync::Arc,
|
|
3
|
+
time::{Duration, SystemTime, UNIX_EPOCH},
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
use neon::{
|
|
7
|
+
object::Object,
|
|
8
|
+
prelude::Context,
|
|
9
|
+
result::JsResult,
|
|
10
|
+
types::{
|
|
11
|
+
JsArray, JsBigInt, JsBoolean, JsBuffer, JsNumber, JsString, JsUndefined, JsValue, Value,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
16
|
+
|
|
17
|
+
/// Trait for types that can be converted to JavaScript values, possibly throwing an error.
|
|
18
|
+
pub trait TryIntoJs {
|
|
19
|
+
type Output: Value;
|
|
20
|
+
fn try_into_js<'cx>(self, cx: &mut impl Context<'cx>) -> JsResult<'cx, Self::Output>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// TryIntoJs implementations for primitives and other basic types //////////////////////////////////
|
|
24
|
+
|
|
25
|
+
impl TryIntoJs for bool {
|
|
26
|
+
type Output = JsBoolean;
|
|
27
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsBoolean> {
|
|
28
|
+
Ok(cx.boolean(self))
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
impl TryIntoJs for u32 {
|
|
33
|
+
type Output = JsNumber;
|
|
34
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsNumber> {
|
|
35
|
+
Ok(cx.number(self))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
impl TryIntoJs for String {
|
|
40
|
+
type Output = JsString;
|
|
41
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsString> {
|
|
42
|
+
Ok(cx.string(self.as_str()))
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
impl TryIntoJs for &str {
|
|
47
|
+
type Output = JsString;
|
|
48
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsString> {
|
|
49
|
+
Ok(cx.string(self))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
impl<T: TryIntoJs> TryIntoJs for Vec<T> {
|
|
54
|
+
type Output = JsArray;
|
|
55
|
+
|
|
56
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsArray> {
|
|
57
|
+
let array = cx.empty_array();
|
|
58
|
+
for (i, item) in self.into_iter().enumerate() {
|
|
59
|
+
let item = item.try_into_js(cx)?;
|
|
60
|
+
#[allow(clippy::cast_possible_truncation)]
|
|
61
|
+
array.set(cx, i as u32, item)?;
|
|
62
|
+
}
|
|
63
|
+
Ok(array)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
impl TryIntoJs for Vec<u8> {
|
|
68
|
+
type Output = JsBuffer;
|
|
69
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsBuffer> {
|
|
70
|
+
JsBuffer::from_slice(cx, &self)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
impl TryIntoJs for &[u8] {
|
|
75
|
+
type Output = JsBuffer;
|
|
76
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsBuffer> {
|
|
77
|
+
JsBuffer::from_slice(cx, self)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
impl TryIntoJs for SystemTime {
|
|
82
|
+
type Output = JsBigInt;
|
|
83
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsBigInt> {
|
|
84
|
+
let nanos = self
|
|
85
|
+
.duration_since(UNIX_EPOCH)
|
|
86
|
+
.unwrap_or(Duration::ZERO)
|
|
87
|
+
.as_nanos();
|
|
88
|
+
Ok(JsBigInt::from_u128(cx, nanos))
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl<T: TryIntoJs> TryIntoJs for Option<T> {
|
|
93
|
+
// Output really is (T::Output | JsNull), hence JsValue
|
|
94
|
+
type Output = JsValue;
|
|
95
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsValue> {
|
|
96
|
+
if let Some(value) = self {
|
|
97
|
+
Ok(value.try_into_js(cx)?.upcast())
|
|
98
|
+
} else {
|
|
99
|
+
Ok(cx.null().upcast())
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
impl<T: TryIntoJs + Clone> TryIntoJs for Arc<T> {
|
|
105
|
+
type Output = T::Output;
|
|
106
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, T::Output> {
|
|
107
|
+
self.as_ref().clone().try_into_js(cx)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
impl TryIntoJs for () {
|
|
112
|
+
type Output = JsUndefined;
|
|
113
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsUndefined> {
|
|
114
|
+
Ok(cx.undefined())
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
impl<T0: TryIntoJs, T1: TryIntoJs> TryIntoJs for (T0, T1) {
|
|
119
|
+
type Output = JsArray;
|
|
120
|
+
fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsArray> {
|
|
121
|
+
let v0 = self.0.try_into_js(cx)?;
|
|
122
|
+
let v1 = self.1.try_into_js(cx)?;
|
|
123
|
+
|
|
124
|
+
let array = cx.empty_array();
|
|
125
|
+
array.set(cx, 0, v0)?;
|
|
126
|
+
array.set(cx, 1, v1)?;
|
|
127
|
+
Ok(array)
|
|
128
|
+
}
|
|
129
|
+
}
|
package/src/lib.rs
CHANGED
|
@@ -1,47 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
#![warn(
|
|
2
|
+
clippy::pedantic,
|
|
3
|
+
clippy::nursery,
|
|
4
|
+
clippy::cargo,
|
|
5
|
+
clippy::perf,
|
|
6
|
+
clippy::style
|
|
7
|
+
)]
|
|
8
|
+
#![allow(
|
|
9
|
+
clippy::missing_errors_doc,
|
|
10
|
+
clippy::too_long_first_doc_paragraph,
|
|
11
|
+
clippy::option_if_let_else,
|
|
12
|
+
clippy::multiple_crate_versions,
|
|
13
|
+
clippy::significant_drop_tightening
|
|
14
|
+
)]
|
|
15
|
+
|
|
16
|
+
pub mod helpers;
|
|
17
|
+
|
|
18
|
+
mod client;
|
|
19
|
+
mod logs;
|
|
20
|
+
mod metrics;
|
|
4
21
|
mod runtime;
|
|
5
22
|
mod testing;
|
|
6
23
|
mod worker;
|
|
7
24
|
|
|
8
|
-
use crate::runtime::*;
|
|
9
|
-
use crate::worker::*;
|
|
10
|
-
use neon::prelude::*;
|
|
11
|
-
use testing::*;
|
|
12
|
-
|
|
13
25
|
#[neon::main]
|
|
14
|
-
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
cx.export_function("pushHistory", push_history)?;
|
|
23
|
-
cx.export_function("closeHistoryStream", close_history_stream)?;
|
|
24
|
-
cx.export_function("workerInitiateShutdown", worker_initiate_shutdown)?;
|
|
25
|
-
cx.export_function("workerFinalizeShutdown", worker_finalize_shutdown)?;
|
|
26
|
-
cx.export_function("clientClose", client_close)?;
|
|
27
|
-
cx.export_function("runtimeShutdown", runtime_shutdown)?;
|
|
28
|
-
cx.export_function("pollLogs", poll_logs)?;
|
|
29
|
-
cx.export_function(
|
|
30
|
-
"workerPollWorkflowActivation",
|
|
31
|
-
worker_poll_workflow_activation,
|
|
32
|
-
)?;
|
|
33
|
-
cx.export_function("workerPollActivityTask", worker_poll_activity_task)?;
|
|
34
|
-
cx.export_function(
|
|
35
|
-
"workerCompleteWorkflowActivation",
|
|
36
|
-
worker_complete_workflow_activation,
|
|
37
|
-
)?;
|
|
38
|
-
cx.export_function("workerCompleteActivityTask", worker_complete_activity_task)?;
|
|
39
|
-
cx.export_function(
|
|
40
|
-
"workerRecordActivityHeartbeat",
|
|
41
|
-
worker_record_activity_heartbeat,
|
|
42
|
-
)?;
|
|
43
|
-
cx.export_function("startEphemeralServer", start_ephemeral_server)?;
|
|
44
|
-
cx.export_function("shutdownEphemeralServer", shutdown_ephemeral_server)?;
|
|
45
|
-
cx.export_function("getEphemeralServerTarget", get_ephemeral_server_target)?;
|
|
26
|
+
fn main(mut cx: neon::prelude::ModuleContext) -> neon::prelude::NeonResult<()> {
|
|
27
|
+
client::init(&mut cx)?;
|
|
28
|
+
logs::init(&mut cx)?;
|
|
29
|
+
metrics::init(&mut cx)?;
|
|
30
|
+
runtime::init(&mut cx)?;
|
|
31
|
+
testing::init(&mut cx)?;
|
|
32
|
+
worker::init(&mut cx)?;
|
|
33
|
+
|
|
46
34
|
Ok(())
|
|
47
35
|
}
|
package/src/logs.rs
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
collections::HashMap,
|
|
3
|
+
time::{Duration, SystemTime, UNIX_EPOCH},
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
use neon::prelude::*;
|
|
7
|
+
|
|
8
|
+
use serde::{Serialize, ser::SerializeMap as _};
|
|
9
|
+
use temporal_sdk_core::api::telemetry::CoreLog;
|
|
10
|
+
|
|
11
|
+
use bridge_macros::js_function;
|
|
12
|
+
|
|
13
|
+
use crate::helpers::{BridgeError, BridgeResult, IntoThrow, JsonString, TryIntoJs};
|
|
14
|
+
|
|
15
|
+
pub fn init(cx: &mut neon::prelude::ModuleContext) -> neon::prelude::NeonResult<()> {
|
|
16
|
+
cx.export_function("getTimeOfDay", get_time_of_day)?;
|
|
17
|
+
|
|
18
|
+
Ok(())
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Helper to get the current time in nanosecond resolution. Nano seconds timestamps are
|
|
22
|
+
/// used to precisely sort logs emitted from the Workflow Context, main thread, and Core.
|
|
23
|
+
#[js_function]
|
|
24
|
+
pub fn get_time_of_day() -> BridgeResult<SystemTime> {
|
|
25
|
+
Ok(SystemTime::now())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
#[derive(Debug, Clone, Serialize)]
|
|
29
|
+
pub struct LogEntry {
|
|
30
|
+
pub target: String,
|
|
31
|
+
pub message: String,
|
|
32
|
+
pub timestamp: String, // u128 as a string - JSON doesn't support u128 numbers
|
|
33
|
+
pub level: String,
|
|
34
|
+
|
|
35
|
+
#[serde(serialize_with = "serialize_map_as_camel_case")]
|
|
36
|
+
pub fields: HashMap<String, serde_json::Value>,
|
|
37
|
+
pub span_contexts: Vec<String>,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
impl TryFrom<CoreLog> for JsonString<LogEntry> {
|
|
41
|
+
type Error = BridgeError;
|
|
42
|
+
|
|
43
|
+
fn try_from(core_log: CoreLog) -> BridgeResult<Self> {
|
|
44
|
+
let timestamp = core_log
|
|
45
|
+
.timestamp
|
|
46
|
+
.duration_since(UNIX_EPOCH)
|
|
47
|
+
.unwrap_or(Duration::ZERO)
|
|
48
|
+
.as_nanos();
|
|
49
|
+
|
|
50
|
+
Self::try_from_value(LogEntry {
|
|
51
|
+
target: core_log.target,
|
|
52
|
+
message: core_log.message,
|
|
53
|
+
timestamp: timestamp.to_string(),
|
|
54
|
+
level: core_log.level.to_string(),
|
|
55
|
+
fields: core_log.fields,
|
|
56
|
+
span_contexts: core_log.span_contexts,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/// Serialize a map, converting keys to camel case.
|
|
62
|
+
fn serialize_map_as_camel_case<S>(
|
|
63
|
+
value: &HashMap<String, serde_json::Value>,
|
|
64
|
+
serializer: S,
|
|
65
|
+
) -> Result<S::Ok, S::Error>
|
|
66
|
+
where
|
|
67
|
+
S: serde::Serializer,
|
|
68
|
+
{
|
|
69
|
+
let mut map = serializer.serialize_map(Some(value.len()))?;
|
|
70
|
+
for (k, v) in value {
|
|
71
|
+
map.serialize_entry(&snake_to_camel(k), v)?;
|
|
72
|
+
}
|
|
73
|
+
map.end()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Convert a string from snake case to camel case.
|
|
77
|
+
fn snake_to_camel(input: &str) -> String {
|
|
78
|
+
match input.find('_') {
|
|
79
|
+
None => input.to_string(),
|
|
80
|
+
Some(first) => {
|
|
81
|
+
let mut result = String::with_capacity(input.len());
|
|
82
|
+
if first > 0 {
|
|
83
|
+
result.push_str(&input[..first]);
|
|
84
|
+
}
|
|
85
|
+
let mut capitalize = true;
|
|
86
|
+
for c in input[first + 1..].chars() {
|
|
87
|
+
if c == '_' {
|
|
88
|
+
capitalize = true;
|
|
89
|
+
} else if capitalize {
|
|
90
|
+
result.push(c.to_ascii_uppercase());
|
|
91
|
+
capitalize = false;
|
|
92
|
+
} else {
|
|
93
|
+
result.push(c.to_ascii_lowercase());
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
result
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#[cfg(test)]
|
|
102
|
+
mod tests {
|
|
103
|
+
use super::*;
|
|
104
|
+
|
|
105
|
+
#[test]
|
|
106
|
+
fn snake_to_camel_works() {
|
|
107
|
+
assert_eq!(snake_to_camel("this_is_a_test"), "thisIsATest");
|
|
108
|
+
assert_eq!(snake_to_camel("this___IS_a_TEST"), "thisIsATest");
|
|
109
|
+
assert_eq!(snake_to_camel("éàç_this_is_a_test"), "éàçThisIsATest");
|
|
110
|
+
}
|
|
111
|
+
}
|