diff --git a/cargo-prosa/tests/cargo-prosa.rs b/cargo-prosa/tests/cargo-prosa.rs index 1e80c7e..73ced69 100644 --- a/cargo-prosa/tests/cargo-prosa.rs +++ b/cargo-prosa/tests/cargo-prosa.rs @@ -237,7 +237,7 @@ If you have an external git dependency, specify your ssh agent with: cmd.args(["completion", "bash"]); cmd.assert() .success() - .stdout(predicate::str::contains("cargo__prosa")); + .stdout(predicate::str::contains("cargo__subcmd__prosa")); // Get Zsh command completion let mut cmd = cargo_prosa_command(); @@ -245,7 +245,7 @@ If you have an external git dependency, specify your ssh agent with: cmd.args(["completion", "zsh"]); cmd.assert() .success() - .stdout(predicate::str::contains("cargo__prosa")); + .stdout(predicate::str::contains("cargo__subcmd__prosa")); Ok(()) } diff --git a/prosa_book/src/SUMMARY.md b/prosa_book/src/SUMMARY.md index f846bb9..801b2d4 100644 --- a/prosa_book/src/SUMMARY.md +++ b/prosa_book/src/SUMMARY.md @@ -3,6 +3,7 @@ [ProSA - Protocol Service Adaptor](ch00-00-prosa.md) [Foreword](ch00-01-foreword.md) [Introduction](ch00-02-introduction.md) +[Architecture](ch00-03-architecture.md) - [OPS](ch01-00-ops.md) - [Cargo-ProSA](ch01-01-cargo-prosa.md) @@ -16,6 +17,7 @@ - [Cloud](ch01-05-cloud.md) - [GCP - Cloud Run](ch01-05-01-gcp-cloud_run.md) - [Clever Cloud](ch01-05-02-clever_cloud.md) + - [Putting It All Together](ch01-06-sumup.md) - [Adaptor](ch02-00-adaptor.md) - [TVF](ch02-01-tvf.md) @@ -32,3 +34,4 @@ - [Events](ch03-06-events.md) - [I/O](ch03-07-io.md) - [Threads](ch03-08-threads.md) + - [Built-in Processors](ch03-09-builtin.md) diff --git a/prosa_book/src/ch00-03-architecture.md b/prosa_book/src/ch00-03-architecture.md new file mode 100644 index 0000000..35be93d --- /dev/null +++ b/prosa_book/src/ch00-03-architecture.md @@ -0,0 +1,142 @@ +# Architecture + +This chapter provides a high-level overview of the ProSA architecture, explaining how all the components interact to form a working microservice platform. + +## Overview + +ProSA follows a **service bus** pattern. At its core, a central **Main** processor routes messages between autonomous **Processors**, each of which communicates with external systems through an **Adaptor**. Messages flow through the system using an internal format called **TVF** (Tag-Value Field). + +```mermaid +graph TB + ext_a(External System A) + ext_b(External System B) + + subgraph prosa[ProSA Instance] + subgraph main[Main - Service Bus] + main_task[Main task] + table>Service table] + table <--> main_task + end + + subgraph proc_a[Processor A] + task_a[Task] + adaptor_a[Adaptor A] + task_a <--> adaptor_a + end + + subgraph proc_b[Processor B] + task_b[Task] + adaptor_b[Adaptor B] + task_b <--> adaptor_b + end + + table --> task_a + table --> task_b + end + + ext_a <--> adaptor_a + ext_b <--> adaptor_b +``` + +## Components + +### Main (Service Bus) + +The **Main** is the central hub of a ProSA instance. It is responsible for: + +- **Message routing**: forwarding requests and responses between processors +- **Service table management**: maintaining a registry of which processors provide which services +- **Processor lifecycle**: tracking processor registration, removal, crashes, and restarts +- **Observability**: collecting system-wide metrics (RAM usage, service counts, processor states) +- **Shutdown coordination**: propagating shutdown signals to all processors + +There is exactly **one Main** per ProSA instance. It runs on the Tokio async runtime and processes messages from its internal queue. + +### Processors + +A **Processor** is an autonomous unit that runs in its own thread(s). Processors: + +- **Register** with the Main to join the service bus +- **Declare services** they can provide (e.g., `"PAYMENT"`, `"AUTH"`) +- **Send and receive messages** through the Main's routing system +- **Run independently**, each in their own Tokio runtime (configurable: single-thread, multi-thread, or shared with Main) + +Processors communicate exclusively through the Main bus using **internal messages** (`InternalMsg`): + +| Message Type | Purpose | +|-------------|---------| +| `Request` | Send a transaction to a service for processing | +| `Response` | Return the result of a processed request | +| `Error` | Return an error for a request (timeout, unreachable, protocol error) | +| `Service` | Update the processor's copy of the service table | +| `Shutdown` | Ask the processor to stop gracefully | + +### Adaptors + +An **Adaptor** is the bridge between a processor and the outside world. It handles: + +- **Protocol translation**: converting external protocol messages (HTTP, TCP, custom binary, etc.) into internal TVF messages +- **Initialization**: setting up connections and resources when the processor starts +- **Termination**: cleaning up when the processor shuts down + +Each processor handle their adaptor as they wish. The adaptor is defined as a trait, so different protocol implementations can be swapped in without changing the processor logic. + +Adaptors can return values either synchronously or asynchronously using the [`MaybeAsync`](https://docs.rs/prosa/latest/prosa/core/adaptor/enum.MaybeAsync.html) enum. + +### TVF (Tag-Value Field) + +**TVF** is the internal message format. It is a trait, not a concrete type, which means different implementations can be used depending on performance or protocol requirements. + +The TVF trait provides a key-value interface where fields are identified by numeric IDs and can hold various types: strings, integers, floats, bytes, dates, and nested TVF buffers. + +`SimpleStringTvf` is the built-in reference implementation. See the [TVF chapter](ch02-01-tvf.md) for details. + +### Service Table + +The **Service Table** is maintained by Main and distributed to all processors. It maps service names (strings) to processor queues. When multiple processors provide the same service, requests are distributed using **round-robin** load balancing. + +When the service table changes, Main notifies all processors with an updated copy. + +## Message Flow + +Here is the typical flow of a request through ProSA: + +```mermaid +sequenceDiagram + participant ExtA as External System A + participant ProcA as Processor A (Consume service) + participant Main as Main (Service Bus) + participant ProcB as Processor B (Expose service) + participant ExtB as External System B + + ProcB-->>Main: Register service + Note over Main: Service table update + ExtA->>ProcA: Protocol message + Note over ProcA: Adaptor converts to TVF + ProcA->>Main: Lookup service + ProcA->>ProcB: RequestMsg (via service queue) + Note over ProcB: Adaptor processes request + ProcB->>ExtB: Protocol message (optional) + ExtB->>ProcB: Protocol response (optional) + ProcB->>ProcA: ResponseMsg + Note over ProcA: Adaptor converts from TVF + ProcA->>ExtA: Protocol response +``` + +Note that processors send requests **directly** to the target processor's queue (looked up from the service table). They do not go through Main for every transaction — Main is only involved in service registration and table distribution. + +## Processor Lifecycle + +1. **Creation**: `ProcConfig::create()` — allocates the processor with its settings and Main bus reference +2. **Run**: `Proc::run()` — spawns the processor in its own thread/runtime +3. **Registration**: `proc.add_proc()` — notifies Main that the processor is active +4. **Service declaration**: `proc.add_service_proc(names)` — registers the services this processor provides +5. **Processing loop**: `internal_run()` — the main event loop, receiving and processing messages +6. **Shutdown**: on `InternalMsg::Shutdown`, the processor calls `adaptor.terminate()` and `proc.remove_proc()` + +### Error Recovery + +If a processor's `internal_run()` returns an error: +- **Recoverable errors**: the processor restarts after a delay (exponential backoff up to a configured maximum) +- **Fatal errors**: the processor is marked as crashed and does not restart +- If ProSA is shutting down, no restart is attempted diff --git a/prosa_book/src/ch01-02-01-observability.md b/prosa_book/src/ch01-02-01-observability.md index 242d66e..c9a260d 100644 --- a/prosa_book/src/ch01-02-01-observability.md +++ b/prosa_book/src/ch01-02-01-observability.md @@ -135,7 +135,7 @@ To set it up, you have to: - Use a direct connection with a token - Decode the base64-encoded basic authorization token from the `Create an Instrumentation Instance`. You'll get an `id:password` to set OTLP credentials with. -> For the first configuration you may have to set the service name to `my-app` to let Grafana Cloud detect that it's working. +> If your token contains a trailing `=`, you need to replace it with `%3D` to ensure a correct URL format. With this information, set up your observability stack (look before if you want to set up traces): ```yaml diff --git a/prosa_book/src/ch01-02-02-run.md b/prosa_book/src/ch01-02-02-run.md deleted file mode 100644 index 6777bc1..0000000 --- a/prosa_book/src/ch01-02-02-run.md +++ /dev/null @@ -1 +0,0 @@ -# Run ProSA diff --git a/prosa_book/src/ch01-02-04-run.md b/prosa_book/src/ch01-02-04-run.md index 5f6b414..3f3fc1e 100644 --- a/prosa_book/src/ch01-02-04-run.md +++ b/prosa_book/src/ch01-02-04-run.md @@ -5,6 +5,8 @@ Once you have created a binary using `cargo-prosa`, the next step is to run this If you've installed a package or a container, you don't need to worry about the inner workings. However, if you want to execute the binary manually, this section explains the available parameters. +## Command-Line Options + When you run `prosa_binary -h`, you'll see output like the following: ``` Usage: prosa_binary [OPTIONS] @@ -18,16 +20,56 @@ Options: -V, --version Print version ``` -Based on this, you have several options: +## Dry Run + +Use `--dry_run` to validate your configuration without actually starting ProSA: + +```bash +prosa_binary -c config.yaml --dry_run +``` + +This will: +1. Parse and validate your configuration file +2. If the configuration file **does not exist**, write a default one with all your processors' default settings +3. Display how ProSA would run (which processors, which adaptors) +4. Exit without starting any processor + +This is particularly useful for: +- Generating a starter configuration file for a new ProSA instance +- Validating configuration changes before applying them in production + +## Configuration Path + +The `-c` / `--config` option accepts either a **single file** or a **directory**: + +```bash +# Single configuration file +prosa_binary -c /etc/prosa.yml + +# Directory containing multiple configuration files +prosa_binary -c /etc/myprosa/ +``` + +When pointing to a directory, both YAML/TOML files in that directory are merged together. This lets you split configuration across multiple files (e.g., one for observability, one per processor). See the [Configuration](ch01-02-config.md) chapter for details. + +## Environment Variables + +Configuration values can also be set through environment variables. For example: + +```bash +PROSA_NAME="my-prosa" prosa_binary -c config.yaml +``` + +This is useful in containerized environments where configuration is injected via environment variables. + +## Version Information + +The `-V` flag provides a short version, while `--version` shows detailed component information: -- dry_run: Use this option to test your configuration file or create it if it doesn't exist. -- config: Specify the path to your configuration folder/file. -- name: Override the name in your prosa settings. This sets the name of your ProSA instance. -- worker_threads: Specify the number of threads allocated for ProSA. Each processor can launch threads individually; thus, this option may have varying effects depending on your processor's capabilities. -- version: Provides information about the binary crate version as well as the versions of all components used. For example: ```bash $ prosa_binary -V prosa 0.1.0 + $ prosa_binary --version prosa 0.1.0 - core::main::MainProc = { crate = prosa, version = 0.2.0 } inj @@ -37,3 +79,28 @@ prosa 0.1.0 - core::main::MainProc = { crate = prosa, version = 0.2.0 } Processor: stub::proc::StubProc = { crate = prosa, version = 0.2.0 } Adaptor : stub::adaptor::StubParotAdaptor = { crate = prosa, version = 0.2.0 } ``` + +The verbose output shows: +- The **Main** processor type and its crate/version +- Each **Processor** instance with its name, type, crate, and version +- Each **Adaptor** associated with its processor + +This is valuable for debugging version mismatches or verifying that the correct components are compiled in. + +## Worker Threads + +The `-t` / `--worker_threads` option controls the number of threads for the **Main** task's Tokio runtime (default: 1). + +Note that each processor runs in its own thread(s) independently — see [Threads](ch03-08-threads.md) for details on per-processor thread configuration. + +## Graceful Shutdown + +ProSA handles `Ctrl+C` (SIGINT) for graceful shutdown: + +1. The Main processor receives the signal +2. A `Shutdown` message is sent to every registered processor +3. Each processor calls `adaptor.terminate()` to clean up resources +4. Each processor deregisters from the Main bus +5. The Main processor exits once all processors have stopped + +This ensures that in-flight transactions are not abruptly terminated and that resources (connections, files, etc.) are properly released. diff --git a/prosa_book/src/ch01-06-sumup.md b/prosa_book/src/ch01-06-sumup.md new file mode 100644 index 0000000..2270819 --- /dev/null +++ b/prosa_book/src/ch01-06-sumup.md @@ -0,0 +1,99 @@ +# Putting It All Together + +Now that you're familiar with [Cargo-ProSA](ch01-01-cargo-prosa.md), [configuration](ch01-02-config.md), [observability](ch01-02-01-observability.md), and [running ProSA](ch01-02-04-run.md), let's walk through a complete example from scratch using the built-in processors. + +## Prerequisites + +You need [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) installed, along with `cargo-prosa`: + +```bash +cargo install cargo-prosa +``` + +## Create and scaffold a project + +```bash +cargo prosa new my-first-prosa +cd my-first-prosa +``` + +This generates a Rust project with a `ProSA.toml` descriptor, a `build.rs`, and a `main.rs` that will be auto-generated from your ProSA configuration. + +You can inspect the available components with: + +```bash +cargo prosa list +``` + +This shows all discovered components: Main, TVF, Processors, and Adaptors. + +## Add processors + +Add a **stub** processor that will respond to requests, and an **injector** that will send requests: + +```bash +cargo prosa add -n stub-1 -a StubParotAdaptor stub +cargo prosa add -n inj-1 -a InjDummyAdaptor inj +``` + +- `-n` sets the processor instance name (used in configuration) +- `-a` selects which adaptor to use + +Your `ProSA.toml` file now contains the processor configuration. You can also edit this file manually. + +## Generate and edit the configuration + +Build the project and retrieve the generated configuration from _target/config.yml_ (or _target/config.toml_). + +You can also generate a default configuration using `--dry_run` (see [Run ProSA](ch01-02-04-run.md) for details on this flag): + +```bash +cargo run -- -c config.yaml --dry_run +``` + +Then edit `config.yaml` to wire the injector to the stub. The stub needs to declare a service name, and the injector needs to target that same service: + +```yaml +name: "my-first-prosa" +observability: + level: debug + metrics: + stdout: + level: info + traces: + stdout: + level: debug + +stub_1: + service_names: + - "TEST_SERVICE" + +inj_1: + service_name: "TEST_SERVICE" + max_speed: 1.0 +``` + +This configures: +- The stub to respond to requests on `"TEST_SERVICE"` +- The injector to send 2 transactions per second to `"TEST_SERVICE"` +- [Observability](ch01-02-01-observability.md) output to stdout so you can see what's happening + +## Run it + +```bash +cargo run -- -n "MyFirstProSA" -c config.yaml +``` + +You should see log output showing: +1. ProSA starting with the configured name +2. The stub processor registering `TEST_SERVICE` +3. The injector discovering the service and starting to send transactions +4. Responses flowing back from the stub to the injector + +Press `Ctrl+C` to stop (see [Graceful Shutdown](ch01-02-04-run.md#graceful-shutdown)). + +## What's next? + +You now know how to build and operate a ProSA instance. The next chapters cover how to develop your own components: +- **[Adaptor](ch02-00-adaptor.md)** — write custom protocol adaptors +- **[Processor](ch03-00-proc.md)** — write custom processors with their own business logic diff --git a/prosa_book/src/ch02-01-tvf.md b/prosa_book/src/ch02-01-tvf.md index 9d22391..2c0232f 100644 --- a/prosa_book/src/ch02-01-tvf.md +++ b/prosa_book/src/ch02-01-tvf.md @@ -42,6 +42,87 @@ where > Good to have are `Clone` and `Debug` for your TVF. When TVFs are used for messaging, `Send` and `Sync` are essential to safely move them across threads. +## Constructing TVF Messages with the `tvf!` Macro + +Instead of manually calling `put_*` methods one by one, you can use the `tvf!` procedural macro to construct TVF messages with a concise syntax. + +### Basic usage (map) + +Create a TVF with key-value pairs using `=>`: + +```rust,noplayground +use prosa_macros::tvf; +use prosa_utils::msg::simple_string_tvf::SimpleStringTvf; + +let message = tvf!(SimpleStringTvf { + 1 => "hello", + 2 => 42, + 3 => 3.14, +}); +``` + +Integer values are inferred as signed (`i64`) by default. Use type suffixes or `as` casts to specify other types: + +```rust,noplayground +let message = tvf!(SimpleStringTvf { + 1 => 2, // signed (i64) + 2 => 4usize, // also signed + 3 => 1 as Unsigned, // unsigned (u64) + 4 => 2.5 as Float, // float (f64) +}); +``` + +### Type annotations + +Use `as Type` to explicitly set the field type: + +| Annotation | Rust type | Example | +|---------------|-----------------|-----------------------------------------| +| `as Unsigned` | `u64` | `1 as Unsigned` | +| `as Signed` | `i64` | `-5 as Signed` | +| `as Float` | `f64` | `3.14 as Float` | +| `as Byte` | `u8` | `22 as Byte` | +| `as Bytes` | `Bytes` | `0x01020304 as Bytes` | +| `as Date` | `NaiveDate` | `"1995-01-10" as Date` | +| `as DateTime` | `NaiveDateTime` | `"2023-06-05 15:02:00.000" as DateTime` | + +### Lists + +Use `[ ]` to create sequential fields indexed starting from 1: + +```rust,noplayground +let list = tvf!(SimpleStringTvf [ + "first element", // index 1 + "second element", // index 2 + 42, // index 3 +]); +``` + +### Nested buffers + +Nest TVF structures inside other TVF messages: + +```rust,noplayground +let message = tvf!(SimpleStringTvf { + 1 => 2, + 5 => [ + 1 as Unsigned, + 2 as Float, + 3, + "four", + 5 as Byte, + { + 1 => "nested object", + 2 => 0x00010203 as Bytes, + } + ], + 6 => "1995-01-10" as Date, + 200 => "2023-06-05 15:02:00.000" as DateTime, +}); +``` + +Lists can contain nested maps (`{ }`), and maps can contain nested lists (`[ ]`), allowing arbitrary depth. + ## Implement your own TVF If you have your own internal format, you can implement the TVF trait on your own and expose your TVF struct: diff --git a/prosa_book/src/ch03-09-builtin.md b/prosa_book/src/ch03-09-builtin.md new file mode 100644 index 0000000..17ba230 --- /dev/null +++ b/prosa_book/src/ch03-09-builtin.md @@ -0,0 +1,207 @@ +# Built-in Processors + +ProSA ships with two built-in processors designed for testing and development: **InjProc** (Injector) and **StubProc** (Stub). Together, they let you validate your ProSA setup without writing any custom processor code. + +## InjProc — Injector + +The injector processor sends transactions to a target service at a regulated speed. It is useful for **load testing** and **functional validation**. + +### How it works + +1. Waits for the target service to appear in the service table +2. Calls the adaptor's `build_transaction()` to create a message +3. Sends the message to the target service at a controlled rate +4. Receives responses and calls the adaptor's `process_response()` +5. Handles errors: timeouts and unreachable services trigger a cooldown; other errors stop the processor + +### Settings + +Configure the injector through `InjSettings` in your YAML: + +```yaml +inj-1: + service_name: "MY_SERVICE" # Target service name to inject into + max_speed: 5.0 # Maximum transactions per second (default: 5.0) + timeout_threshold: + secs: 10 # Cooldown duration on timeout/unreachable (default: 10s) + nanos: 0 + max_concurrents_send: 1 # Max parallel transactions (default: 1) + speed_interval: 15 # Number of samples for speed calculation (default: 15) +``` + +### InjAdaptor Trait + +To customize what the injector sends and how it processes responses, implement the `InjAdaptor` trait: + +```rust,noplayground +pub trait InjAdaptor +where + M: Tvf + Clone + Debug + Default + Send + Sync + 'static, +{ + /// Called once when the processor starts + fn new(proc: &InjProc) -> Result> + where + Self: Sized; + + /// Build the next transaction to inject + fn build_transaction(&mut self) -> M; + + /// Process the response (optional — default ignores responses) + fn process_response( + &mut self, + response: M, + service_name: &str, + ) -> Result<(), Box> { + Ok(()) + } +} +``` + +### InjDummyAdaptor + +The built-in `InjDummyAdaptor` creates minimal messages with `"DUMMY"` in field 1. It is useful for quick smoke tests: + +```rust,noplayground +#[derive(Adaptor)] +pub struct InjDummyAdaptor {} + +impl InjAdaptor for InjDummyAdaptor +where + M: Tvf + Clone + Debug + Default + Send + Sync + 'static, +{ + fn new(_proc: &InjProc) -> Result> { + Ok(Self {}) + } + + fn build_transaction(&mut self) -> M { + let mut msg = M::default(); + msg.put_string(1, "DUMMY"); + msg + } +} +``` + +### Metrics + +The injector exposes a histogram metric `prosa_inj_request_duration` (in seconds) with attributes: +- `proc`: processor name +- `service`: target service name +- `err_code`: `"0"` for success, or the service error code + +--- + +## StubProc — Stub + +The stub processor responds to incoming requests. It is useful for **mocking services** during development or testing. + +### How it works + +1. Registers for the configured service names +2. Receives incoming `Request` messages +3. Calls the adaptor's `process_request()` to produce a response +4. Returns the response to the caller + +### Settings + +Configure the stub through `StubSettings` in your YAML: + +```yaml +stub-1: + service_names: + - "MY_SERVICE" + - "ANOTHER_SERVICE" +``` + +### StubAdaptor Trait + +To customize how the stub responds, implement the `StubAdaptor` trait: + +```rust,noplayground +pub trait StubAdaptor +where + M: Tvf + Clone + Debug + Default + Send + Sync + 'static, +{ + /// Called once when the processor starts + fn new(proc: &StubProc) -> Result> + where + Self: Sized; + + /// Process an incoming request and return a response + fn process_request( + &self, + service_name: &str, + request: M, + ) -> MaybeAsync>; +} +``` + +The return type `MaybeAsync` lets you choose between synchronous and asynchronous processing. + +### Synchronous response + +Return a value directly using `.into()`: + +```rust,noplayground +fn process_request(&self, service_name: &str, request: M) -> MaybeAsync> { + // Echo the request back + Ok(request).into() +} +``` + +### Asynchronous response + +Use the `maybe_async!` macro for async processing: + +```rust,noplayground +fn process_request(&self, service_name: &str, request: M) -> MaybeAsync> { + let service_name = service_name.to_string(); + maybe_async!(async move { + // Simulate async work (e.g., call an external API) + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + let mut msg = request.clone(); + msg.put_string(1, format!("response for {}", service_name)); + Ok(msg) + }) +} +``` + +### Built-in Adaptors + +**StubParotAdaptor** — echoes the request back as-is (synchronous): + +```rust,noplayground +fn process_request(&self, _service_name: &str, request: M) -> MaybeAsync> { + Ok(request.clone()).into() +} +``` + +**StubAsyncParotAdaptor** — echoes the request back after a 100ms delay (asynchronous): + +```rust,noplayground +fn process_request(&self, _service_name: &str, request: M) -> MaybeAsync> { + maybe_async!(async move { + tokio::time::sleep(std::time::Duration::from_millis(100)).await; + Ok(request) + }) +} +``` + +--- + +## Using Injector and Stub Together + +The injector and stub pair is ideal for validating your ProSA setup. Wire them together by pointing the injector's `service_name` at a service declared by the stub: + +```yaml +# Stub responds to "TEST_SERVICE" +stub-1: + service_names: + - "TEST_SERVICE" + +# Injector sends to "TEST_SERVICE" +inj-1: + service_name: "TEST_SERVICE" + max_speed: 10.0 +``` + +When you run ProSA with this configuration, the injector will continuously send messages to the stub, which echoes them back. You can observe the transaction flow through metrics and traces. diff --git a/prosa_macros/src/lib.rs b/prosa_macros/src/lib.rs index fa59865..1fe8a75 100644 --- a/prosa_macros/src/lib.rs +++ b/prosa_macros/src/lib.rs @@ -116,6 +116,7 @@ pub fn io(args: TokenStream, input: TokenStream) -> TokenStream { /// ], /// 6 => "2024-01-01" as Date, /// 7 => "2024-01-01 12:00:00.000" as DateTime, +/// 8 => 5 as Byte, /// 9 => 0x01020304 as Bytes, /// } ]; /// ``` diff --git a/prosa_macros/tests/tvf.rs b/prosa_macros/tests/tvf.rs index 5312023..4996cfe 100644 --- a/prosa_macros/tests/tvf.rs +++ b/prosa_macros/tests/tvf.rs @@ -16,6 +16,7 @@ mod macro_tests { 2 as Float, 3, "four", + 5 as Byte, { 1 => "object", 2 => 0x00010203_04050607_08090A0B_0C0D0E0F_10111213_14151617_18191A1B_1C1D1E1F as Bytes @@ -37,9 +38,10 @@ mod macro_tests { Ok("four"), subbuffer.get_string(4).map(|s| s.to_string()).as_deref() ); + assert_eq!(Ok(5u8), subbuffer.get_byte(5)); let sub = subbuffer - .get_buffer(5) + .get_buffer(6) .expect("TVF should have a sub buffer"); assert_eq!( Ok("object"),