Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
resolver = "3"
members = [
"crates/types",
"crates/applications/show_hello_world_from_stdin",
"crates/applications/show_from_stdin",
]

[workspace.package]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
name = "show_hello_world_from_stdin"
name = "show_from_stdin"
version = "0.1.0"
edition.workspace = true
license-file.workspace = true
Expand Down
34 changes: 34 additions & 0 deletions crates/applications/show_from_stdin/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use types::capnp_projection_backend::CapnpPackedProjectionBackend;
use types::Message;

pub struct Application {
projection_backend: CapnpPackedProjectionBackend,
}

impl Application {
pub fn new() -> Self {
Self { projection_backend: CapnpPackedProjectionBackend }
}

pub fn run_and_exit_with_process_status_code(&self) -> i32 {
match self
.projection_backend
.from_stdin::<Message>()
{
Ok(message) => {
println!("{}", message.text().as_str());
0
}
Err(error) => {
eprintln!("{}", error.message());
2
}
}
}
}

fn main() {
let application = Application::new();
let process_status_code = application.run_and_exit_with_process_status_code();
std::process::exit(process_status_code);
}
35 changes: 0 additions & 35 deletions crates/applications/show_hello_world_from_stdin/src/main.rs

This file was deleted.

2 changes: 1 addition & 1 deletion crates/types/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
fn main() {
capnpc::CompilerCommand::new()
.file("schema/hello_world.capnp")
.file("schema/message.capnp")
.run()
.expect("Cap’n Proto schema compilation must succeed.");
}
5 changes: 0 additions & 5 deletions crates/types/schema/hello_world.capnp

This file was deleted.

5 changes: 5 additions & 0 deletions crates/types/schema/message.capnp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@0xb9d0dfb7a7c9a3a1;

struct Message {
text @0 :Text;
}
36 changes: 18 additions & 18 deletions crates/types/src/capnp_projection_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,56 +9,56 @@ use capnp::serialize_packed;
use crate::sajban_representation_codec::SajbanProjectionCodec;

/// A projection backend object that converts between packed Cap’n Proto bytes and domain objects.
pub struct CapnpPackedProjectionBackendObject;
pub struct CapnpPackedProjectionBackend;

impl CapnpPackedProjectionBackendObject {
pub fn read_object_from_stdin_as_packed_capnp<T: SajbanProjectionCodec>(
impl CapnpPackedProjectionBackend {
pub fn from_stdin<T: SajbanProjectionCodec>(
&self,
) -> Result<T, CapnpPackedProjectionBackendErrorObject> {
) -> Result<T, CapnpPackedProjectionBackendError> {
let stdin = std::io::stdin();
let mut locked_stdin = stdin.lock();

let reader = serialize_packed::read_message(&mut locked_stdin, ReaderOptions::new())
.map_err(CapnpPackedProjectionBackendErrorObject::new_from_capnp_error)?;
.map_err(CapnpPackedProjectionBackendError::new)?;

let root = reader
.get_root::<T::ProjectionReader<'_>>()
.map_err(CapnpPackedProjectionBackendErrorObject::new_from_capnp_error)?;
.map_err(CapnpPackedProjectionBackendError::new)?;

Ok(T::read_from_projection(root))
}

pub fn write_object_to_packed_capnp_bytes<T: SajbanProjectionCodec>(
pub fn to_bytes<T: SajbanProjectionCodec>(
&self,
object: &T,
value: &T,
init_root: fn(&mut Builder<capnp::message::HeapAllocator>) -> T::ProjectionBuilder<'_>,
) -> Result<Vec<u8>, CapnpPackedProjectionBackendErrorObject> {
) -> Result<Vec<u8>, CapnpPackedProjectionBackendError> {
let mut message_builder: Builder<capnp::message::HeapAllocator> = Builder::new_default();
{
let builder = init_root(&mut message_builder);
object.write_to_projection(builder);
value.write_to_projection(builder);
}

let mut output_bytes: Vec<u8> = Vec::new();
serialize_packed::write_message(&mut output_bytes, &message_builder)
.map_err(CapnpPackedProjectionBackendErrorObject::new_from_capnp_error)?;
.map_err(CapnpPackedProjectionBackendError::new)?;

Ok(output_bytes)
}
}

/// An explicit error object that avoids returning naked standard-library error types at the boundary.
#[derive(Debug)]
pub struct CapnpPackedProjectionBackendErrorObject {
capnp_error_string: String,
pub struct CapnpPackedProjectionBackendError {
message: String,
}

impl CapnpPackedProjectionBackendErrorObject {
pub fn new_from_capnp_error(error: capnp::Error) -> Self {
Self { capnp_error_string: error.to_string() }
impl CapnpPackedProjectionBackendError {
pub fn new(error: capnp::Error) -> Self {
Self { message: error.to_string() }
}

pub fn capnp_error_string(&self) -> &str {
&self.capnp_error_string
pub fn message(&self) -> &str {
&self.message
}
}
56 changes: 24 additions & 32 deletions crates/types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,70 +8,62 @@ pub mod sajban_representation_codec;

#[allow(dead_code)]
pub(crate) mod generated_capnp {
include!(concat!(env!("OUT_DIR"), "/hello_world_capnp.rs"));
include!(concat!(env!("OUT_DIR"), "/message_capnp.rs"));
}

use crate::sajban_representation_codec::{SajbanProjectionCodec, SajbanProjectionSchemaIdentity};

/// A domain object whose meaning is carried by explicit fields rather than positional primitives.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HelloWorldObject {
hello_world_utf8_text_object: HelloWorldUtf8TextObject,
pub struct Message {
text: Text,
}

impl HelloWorldObject {
/// Constructs a HelloWorldObject from a text object, preserving semantic meaning as an object boundary.
pub fn new_with_hello_world_utf8_text_object(
hello_world_utf8_text_object: HelloWorldUtf8TextObject,
) -> Self {
Self { hello_world_utf8_text_object }
impl Message {
/// Constructs a message from a text object, preserving semantic meaning as an object boundary.
pub fn new(text: Text) -> Self {
Self { text }
}

/// Provides access to the contained text object.
pub fn hello_world_utf8_text_object(&self) -> &HelloWorldUtf8TextObject {
&self.hello_world_utf8_text_object
pub fn text(&self) -> &Text {
&self.text
}
}

/// A text wrapper object that prevents meaning from collapsing into a naked standard-library primitive.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HelloWorldUtf8TextObject {
hello_world_utf8_text: String,
pub struct Text {
value: String,
}

impl HelloWorldUtf8TextObject {
pub fn new_with_hello_world_utf8_text(hello_world_utf8_text: String) -> Self {
Self { hello_world_utf8_text }
impl Text {
pub fn new(value: String) -> Self {
Self { value }
}

pub fn hello_world_utf8_text(&self) -> &str {
&self.hello_world_utf8_text
pub fn as_str(&self) -> &str {
&self.value
}
}

impl SajbanProjectionSchemaIdentity for HelloWorldObject {
impl SajbanProjectionSchemaIdentity for Message {
fn sajban_projection_type_name() -> &'static str {
"HelloWorld"
"Message"
}
}

impl SajbanProjectionCodec for HelloWorldObject {
type ProjectionReader<'a> = generated_capnp::hello_world::Reader<'a>;
type ProjectionBuilder<'a> = generated_capnp::hello_world::Builder<'a>;
impl SajbanProjectionCodec for Message {
type ProjectionReader<'a> = generated_capnp::message::Reader<'a>;
type ProjectionBuilder<'a> = generated_capnp::message::Builder<'a>;

fn read_from_projection(reader: Self::ProjectionReader<'_>) -> Self {
let hello_world_utf8_text_object =
HelloWorldUtf8TextObject::new_with_hello_world_utf8_text(
reader
.get_hello_world_utf8_text()
.unwrap_or("")
.to_string(),
);
let text = Text::new(reader.get_text().unwrap_or("").to_string());

Self::new_with_hello_world_utf8_text_object(hello_world_utf8_text_object)
Self::new(text)
}

fn write_to_projection(&self, mut builder: Self::ProjectionBuilder<'_>) {
builder.set_hello_world_utf8_text(self.hello_world_utf8_text_object.hello_world_utf8_text());
builder.set_text(self.text.as_str());
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,30 @@
use std::io::Write;
use std::process::{Command, Stdio};

use types::capnp_projection_backend::CapnpPackedProjectionBackendObject;
use types::sajban_representation_codec::SajbanProjectionCodec;
use types::HelloWorldObject;
pub struct TextProjectionRoundTripTest;

pub struct HelloWorldTextProjectionRoundTripTestObject;
impl TextProjectionRoundTripTest {
pub fn execute(&self) {
let capnp_message = self.capnp_message();

impl HelloWorldTextProjectionRoundTripTestObject {
pub fn execute_round_trip_test(&self) {
let hello_world_text_message = self.hello_world_capnp_text_message();

let packed_capnp_bytes = self.encode_capnp_text_message_into_packed_capnp_bytes(
hello_world_text_message,
);
let packed_capnp_bytes = self.encode_capnp_text_message_into_packed_capnp_bytes(capnp_message);

let displayed_text = self.run_application_and_capture_stdout(packed_capnp_bytes);

assert_eq!(displayed_text.trim_end(), "hello, world");
}

fn hello_world_capnp_text_message(&self) -> &'static str {
"(helloWorldUtf8Text = \"hello, world\")"
fn capnp_message(&self) -> &'static str {
"(text = \"hello, world\")"
}

fn encode_capnp_text_message_into_packed_capnp_bytes(&self, capnp_text_message: &str) -> Vec<u8> {
let mut capnp_encode_command = Command::new("capnp");
capnp_encode_command
.arg("encode")
.arg("--packed")
.arg("--schema").arg("schema/hello_world.capnp")
.arg("HelloWorld")
.arg("--schema").arg("schema/message.capnp")
.arg("Message")
.current_dir(self.types_crate_root_path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
Expand Down Expand Up @@ -58,7 +52,7 @@ impl HelloWorldTextProjectionRoundTripTestObject {
.arg("run")
.arg("-q")
.arg("-p")
.arg("show_hello_world_from_stdin")
.arg("show_from_stdin")
.current_dir(self.workspace_root_path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
Expand Down Expand Up @@ -93,7 +87,7 @@ impl HelloWorldTextProjectionRoundTripTestObject {
}

#[test]
fn hello_world_text_projection_round_trip() {
let test_object = HelloWorldTextProjectionRoundTripTestObject;
test_object.execute_round_trip_test();
fn text_projection_round_trip() {
let test_object = TextProjectionRoundTripTest;
test_object.execute();
}