pax_global_header00006660000000000000000000000064146705724360014530gustar00rootroot0000000000000052 comment=583a56fb736755e90a7a6d4c6a7a07cede09891d rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/000077500000000000000000000000001467057243600206515ustar00rootroot00000000000000rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/000077500000000000000000000000001467057243600222115ustar00rootroot00000000000000rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/ISSUE_TEMPLATE/000077500000000000000000000000001467057243600243745ustar00rootroot00000000000000rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000014121467057243600270640ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- ## Describe the bug A clear and concise description of what the bug is. A clear and concise description of what you expected to happen. ## Minimal Reproducible Examples (MRE) Please try to provide information which will help us to fix the issue faster. MREs with few dependencies are especially lovely <3. If applicable, add logs/screenshots to help explain your problem. **Environment (please complete the following information):** - Platform: [e.g.`uname -a`] - Rust [e.g.`rustic -vV`] - Cargo [e.g.`cargo -vV`] ## Additional context Add any other context about the problem here. For example, environment variables like `CARGO`, `RUSTUP_HOME` or `CARGO_HOME`. rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011351467057243600301210ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- ## Problem A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] Include Issue links if they exist. Minimal Reproducible Examples (MRE) with few dependencies are useful. ## Solution A clear and concise description of what you want to happen. ## Alternatives A clear and concise description of any alternative solutions or features you've considered. ## Additional context Add any other context or screenshots about the feature request here. rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/ISSUE_TEMPLATE/spec_bug_report.md000066400000000000000000000014401467057243600300770ustar00rootroot00000000000000--- name: Specification Non-conformance report about: Report an error in our implementation title: '' labels: specification assignees: '' --- ## Describe the bug A clear and concise description of what the bug is. Please include references to the relevant specification; for example: > RFC 2616, section 4.3.2: > > > The HEAD method is identical to GET except that the server MUST NOT > > send a message body in the response ## Minimal Reproducible Examples (MRE) Please try to provide information which will help us to fix the issue faster. MREs with few dependencies are especially lovely <3. ## Additional context Add any other context about the problem here. For example, any other specifications that provide additional information, or other implementations that show common behavior. rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/dependabot.yml000066400000000000000000000004301467057243600250360ustar00rootroot00000000000000version: 2 updates: - package-ecosystem: cargo directory: "/" schedule: interval: daily open-pull-requests-limit: 10 commit-message: prefix: "cargo prod" prefix-development: "cargo dev" include: "scope" reviewers: - johnstonskj rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/workflows/000077500000000000000000000000001467057243600242465ustar00rootroot00000000000000rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/workflows/release.yml000066400000000000000000000013271467057243600264140ustar00rootroot00000000000000name: Release to Crates.io on: push: tags: - 'v[0-9]+.[0-9]+.[0-9]+' - 'v[0-9]+.[0-9]+.[0-9]+-*' jobs: cargo-publish: name: Publishing to Cargo runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true - name: Cargo publish env: # This can help you tagging the github repository GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This can help you publish to crates.io CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} run: cargo publish rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/workflows/rust.yml000066400000000000000000000144471467057243600260000ustar00rootroot00000000000000name: Rust on: pull_request: paths: - '**' - '!/*.md' - '!/*.org' - "!/LICENSE" push: branches: - main paths: - '**' - '!/*.md' - '!/*.org' - "!/LICENSE" schedule: - cron: '12 12 12 * *' jobs: publish: name: Publish (dry-run) needs: [test, docs] strategy: matrix: package: - objio continue-on-error: true runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true - uses: Swatinem/rust-cache@v1 - name: Check publish uses: actions-rs/cargo@v1 with: command: publish args: --package ${{ matrix.package}} --dry-run check_tests: name: Check for test types runs-on: ubuntu-latest outputs: has_benchmarks: ${{ steps.check_benchmarks.outputs.has_benchmarks }} has_examples: ${{ steps.check_examples.outputs.has_examples }} steps: - name: Check for benchmarks id: check_benchmarks run: test -d benchmarks && echo "has_benchmarks=1" || echo "has_benchmarks=" >> $GITHUB_OUTPUT shell: bash - name: Check for examples id: check_examples run: test -d examples && echo "has_examples=1" || echo "has_examples=" >> $GITHUB_OUTPUT shell: bash test: name: Test needs: [rustfmt, clippy] strategy: matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] rust: ["stable", "beta", "nightly"] test-features: ["", "--all-features", "--no-default-features"] continue-on-error: ${{ matrix.rust != 'stable' }} runs-on: ${{ matrix.os }} steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} profile: minimal override: true - uses: Swatinem/rust-cache@v1 - name: Build uses: actions-rs/cargo@v1 with: command: build args: --workspace ${{ matrix.test-features }} - name: Test uses: actions-rs/cargo@v1 with: command: test args: --workspace ${{ matrix.test-features }} benchmarks: name: Benchmarks needs: [rustfmt, clippy, check_tests] if: needs.check_tests.outputs.has_benchmarks strategy: matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] rust: ["stable"] runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} profile: minimal override: true - uses: Swatinem/rust-cache@v1 - name: Run benchmarks with all features uses: actions-rs/cargo@v1 with: command: test args: --workspace --benches --all-features --no-fail-fast examples: name: Examples needs: [rustfmt, clippy, check_tests] if: needs.check_tests.outputs.has_examples runs-on: ubuntu-latest strategy: matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] rust: ["stable"] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} profile: minimal override: true - uses: Swatinem/rust-cache@v1 - name: Run examples with all features uses: actions-rs/cargo@v1 with: command: test args: --workspace --examples --all-features --no-fail-fast coverage: name: Code Coverage needs: test runs-on: ubuntu-latest strategy: matrix: os: ["ubuntu-latest"] rust: ["stable"] steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: Run cargo-tarpaulin uses: actions-rs/tarpaulin@v0.1 with: version: 0.22.0 args: --all-features -- --test-threads 1 - name: Upload to codecov.io uses: codecov/codecov-action@v1.0.2 with: token: ${{secrets.CODECOV_TOKEN}} - name: Archive code coverage results uses: actions/upload-artifact@v4 with: name: code-coverage-report path: cobertura.xml docs: name: Document generation needs: [rustfmt, clippy] runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true - uses: Swatinem/rust-cache@v1 - name: Generate documentation uses: actions-rs/cargo@v1 env: RUSTDOCFLAGS: -D warnings with: command: doc args: --workspace --all-features --no-deps rustfmt: name: rustfmt runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true components: rustfmt - uses: Swatinem/rust-cache@v1 - name: Check formatting uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check clippy: name: clippy runs-on: ubuntu-latest permissions: checks: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true components: clippy - uses: Swatinem/rust-cache@v1 - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: --workspace --no-deps --all-features --all-targets -- -D warnings rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/workflows/security-audit.yml000066400000000000000000000011731467057243600277460ustar00rootroot00000000000000name: Security audit on: push: paths: - '**/Cargo.toml' - '**/Cargo.lock' pull_request: paths: - '**/Cargo.toml' - '**/Cargo.lock' schedule: - cron: '12 12 12 * *' jobs: security_audit: runs-on: ubuntu-latest permissions: checks: write steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install Rust uses: actions-rs/toolchain@v1 with: toolchain: stable profile: minimal override: true - uses: actions-rs/audit-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.github/workflows/typos.yml000066400000000000000000000004101467057243600261420ustar00rootroot00000000000000name: Spelling on: [pull_request] jobs: spelling: name: Spell Check with Typos runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Spell check repository uses: crate-ci/typos@master rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/.gitignore000066400000000000000000000015301467057243600226400ustar00rootroot00000000000000# -*- mode: gitignore; -*- ### Emacs ### *~ \#*\# /.emacs.desktop /.emacs.desktop.lock *.elc auto-save-list tramp .\#* # Org-mode .org-id-locations *_archive # flymake-mode *_flymake.* # eshell files /eshell/history /eshell/lastdir # elpa packages /elpa/ # reftex files *.rel # AUCTeX auto folder /auto/ # cask packages .cask/ dist/ # Flycheck flycheck_*.el # server auth directory /server/ # projectiles files .projectile # directory configuration .dir-locals.el # network security /network-security.data ### Rust ### # Generated by Cargo # will have compiled files and executables /target/ # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html Cargo.lock ### IDEA ### /.idea /*.iml ### MacOS ### **/.DS_store rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/Cargo.toml000066400000000000000000000006411467057243600226020ustar00rootroot00000000000000[package] name = "objio" version = "0.1.2" authors = ["Simon Johnston "] description = "This crate provides simple traits for reading and writing objects." documentation = "https://docs.rs/objio/" repository = "https://github.com/johnstonskj/rust-objio.git" license = "MIT" readme = "README.md" edition = "2021" publish = true [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/LICENSE000066400000000000000000000020571467057243600216620ustar00rootroot00000000000000MIT License Copyright (c) 2019 Simon Johnston Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/README.md000066400000000000000000000014361467057243600221340ustar00rootroot00000000000000# Rust crate objio This crate provides simple traits for reading and writing objects. [![codecov](https://codecov.io/gh/johnstonskj/rust-objio/graph/badge.svg?token=yQLMeb2Io3)](https://codecov.io/gh/johnstonskj/rust-objio) The traits `ObjectReader` and `ObjectWriter` are **not** intended as a generalized serialization framework like serde, they are provided to simply read/write specific object types in specific formats. ## Example TBD ## Changes ### Version 0.1.2 * Documentation: added documentation to all traits and a detailed example at the module level. ### Version 0.1.1 * Refactor: updated error type processing. * Removed custom `Error` type. * Changed trait Error types to have a constraint requiring `From<>`. ### Version 0.1.0 * Initial release. rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/_typos.toml000066400000000000000000000000451467057243600230620ustar00rootroot00000000000000[files] extend-exclude = ["*/data/*"]rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/src/000077500000000000000000000000001467057243600214405ustar00rootroot00000000000000rust-objio-583a56fb736755e90a7a6d4c6a7a07cede09891d/src/lib.rs000066400000000000000000000306221467057243600225570ustar00rootroot00000000000000/*! This crate provides simple traits for reading and writing objects. The traits [`ObjectReader`] and [`ObjectWriter`] are **not** intended as a generalized serialization framework like [serde](https://serde.rs), they are provided to simply read/write specific object types in specific formats. These traits were refactored from the `rdftk_io` crate that provided a number of parsers and generators for different RDF representations. As a number of implementations require options to configure parsers and generators the trait [`HasOptions`] can be implemented to provide this in a common manner. # Example Writer 3. The type `TestObject` is the type we wich to be able to write, it has a single string field. 1. The type `TestError` is required as it can be created from an IO error instance. 2. The type `TestOptions` are the options that configure our generator, currently only defining the amount of indentation before writing `TestObject` instances. 4. The type TestWriter` is where the magic starts, ... 1. It contains a field for the options above. 2. It implements the trait `HasOptions` using the macro `impl_has_options!`. 3. It implements the trait `ObjectWriter`. 5. We the construct an example instance of the test object and an instance of the writer with options. 6. Finally, we write the example object and compare it to expected results. ```rust use objio::{impl_has_options, HasOptions, ObjectWriter}; use std::io::Write; #[derive(Debug, Default)] struct TestObject { // our writeable type value: String, } # impl From<&str> for TestObject { # fn from(s: &str) -> Self { # Self { value: s.to_string() } # } # } #[derive(Debug, Default)] struct TestError {} // implements From # impl From<::std::io::Error> for TestError { # fn from(_: ::std::io::Error) -> Self { # Self {} # } # } #[derive(Debug, Default)] struct TestOptions { indent: usize, } #[derive(Debug, Default)] struct TestWriter { options: TestOptions, } impl_has_options!(TestWriter, TestOptions); impl ObjectWriter for TestWriter { type Error = TestError; fn write(&self, w: &mut W, object: &TestObject) -> Result<(), Self::Error> where W: Write, { let indent = self.options.indent; let value = &object.value; w.write_all(format!("{:indent$}{value}", "").as_bytes())?; Ok(()) } } let example = TestObject::from("Hello"); let writer = TestWriter::default().with_options(TestOptions { indent: 2 }); assert_eq!( writer.write_to_string(&example).unwrap(), " Hello".to_string() ); ``` */ #![warn( unknown_lints, // ---------- Stylistic absolute_paths_not_starting_with_crate, elided_lifetimes_in_paths, explicit_outlives_requirements, macro_use_extern_crate, nonstandard_style, /* group */ noop_method_call, rust_2018_idioms, single_use_lifetimes, trivial_casts, trivial_numeric_casts, // ---------- Future future_incompatible, /* group */ rust_2021_compatibility, /* group */ // ---------- Public missing_debug_implementations, // missing_docs, unreachable_pub, // ---------- Unsafe unsafe_code, unsafe_op_in_unsafe_fn, // ---------- Unused unused, /* group */ )] #![deny( // ---------- Public exported_private_dependencies, // ---------- Deprecated anonymous_parameters, bare_trait_objects, ellipsis_inclusive_range_patterns, // ---------- Unsafe deref_nullptr, drop_bounds, dyn_drop, )] use std::fs::OpenOptions; use std::io::{Cursor, Read, Write}; use std::path::Path; // ------------------------------------------------------------------------------------------------ // Public Types // ------------------------------------------------------------------------------------------------ /// /// This trait is implemented by reader or writer types to attach option instances for /// configuration. /// /// Note that the option type `T` **must** implement `Default` so that it is not necessary to /// require an option during construction of the reader or writer. /// pub trait HasOptions { /// /// A builder-like function that can be called after the default constructor. /// /// # Example /// /// ```rust /// # use objio::{impl_has_options, HasOptions}; /// # fn get_options_from_config() -> TestOptions { Default::default() } /// #[derive(Debug, Default)] /// struct TestOptions { /// indent: usize, /// } /// /// #[derive(Debug, Default)] /// struct TestWriter { /// options: TestOptions, /// } /// /// impl_has_options!(TestWriter, TestOptions); /// /// let writer = TestWriter::default() /// .with_options(get_options_from_config()); /// ``` /// fn with_options(self, options: T) -> Self where Self: Sized, { let mut self_mut = self; self_mut.set_options(options); self_mut } /// /// Set the current options value to `options`. /// fn set_options(&mut self, options: T); /// /// Returns a reference to the current options. /// fn options(&self) -> &T; } // ------------------------------------------------------------------------------------------------ /// /// The trait implemented by types which read instances of `T`. /// pub trait ObjectReader { /// /// The type indicating errors, this **must** implement the conversion from `io::Error` as this /// error is intrinsic to the methods on `Read`. This constraint allows the error type to also /// signal parser errors related to the content itself. /// type Error: From<::std::io::Error>; /// /// Read an instance of `T` from the provided implementation of `Read`. /// fn read(&self, r: &mut R) -> Result where R: Read; /// /// Read an instance of `T` from the provided string. /// fn read_from_string(&self, string: S) -> Result where S: AsRef, { let mut data = string.as_ref().as_bytes(); self.read(&mut data) } /// /// Read an instance of `T` from the file identified by `path`. /// /// This method will return an IO error if the path is invalid, or file does not exist. /// fn read_from_file

(&self, path: P) -> Result where P: AsRef, { let mut file = OpenOptions::new().read(true).open(path.as_ref())?; self.read(&mut file) } } // ------------------------------------------------------------------------------------------------ /// /// The trait implemented by types which write instances of `T`. /// pub trait ObjectWriter { /// /// The type indicating errors, this **must** implement the conversion from `io::Error` as this /// error is intrinsic to the methods on `Write`. This constraint allows the error type to also /// signal serialization errors related to the content itself. /// type Error: From<::std::io::Error>; /// /// Write an instance of `T` to the provided implementation of `Write`. /// fn write(&self, w: &mut W, object: &T) -> Result<(), Self::Error> where W: Write; /// /// Write an instance of `T` to, and return, a string. /// fn write_to_string(&self, object: &T) -> Result { let mut buffer = Cursor::new(Vec::new()); self.write(&mut buffer, object)?; Ok(String::from_utf8(buffer.into_inner()).unwrap()) } /// /// Write an instance of `T` into the file identified by `path`. /// /// This method will return an IO error if the path is invalid, or the file is not writeable. /// If the file exists it will be replaced. /// fn write_to_file

(&self, object: &T, path: P) -> Result<(), Self::Error> where P: AsRef, { let mut file = OpenOptions::new() .write(true) .create(true) .truncate(true) .open(path.as_ref())?; self.write(&mut file, object) } } // ------------------------------------------------------------------------------------------------ // Public Macros // ------------------------------------------------------------------------------------------------ /// /// Provides a boiler-place implementation of [`HasOptions`]. /// #[macro_export] macro_rules! impl_has_options { ($impl_type: ty, $option_type: ty) => { impl_has_options!($impl_type, $option_type, options); }; ($impl_type: ty, $option_type: ty, $field_name: ident) => { impl $crate::HasOptions<$option_type> for $impl_type { fn set_options(&mut self, options: $option_type) { self.$field_name = options; } fn options(&self) -> &$option_type { &self.$field_name } } }; } /// /// Provides a simple implementation of [`ObjectWriter`] where the existing implementation of /// `Display` provides the serialized form via the `ToString` trait. /// #[macro_export] macro_rules! impl_to_string_writer { ($impl_type: ty, $object_type: ty) => { impl $crate::ObjectWriter<$object_type> for $impl_type { fn write(&self, w: &mut W, object: &$object_type) -> Result<()> where W: Write, { let stringified: String = <$object_type as ::std::string::ToString>::to_string(object); w.write_all(stringified.as_bytes()).map_err(Error::Io)?; Ok(()) } } }; } /// /// Provides a simple implementation of [`ObjectWriter`] where an existing implementation of /// `Into` provides the serialized form. /// #[macro_export] macro_rules! impl_into_string_writer { ($impl_type: ty, $object_type: ty) => { impl $crate::ObjectWriter<$object_type> for $impl_type { fn write(&self, w: &mut W, object: &$object_type) -> Result<()> where W: Write, { let stringified: String = <$object_type as ::std::convert::Into<::std::string::String>>::into(object); w.write_all(stringified.as_bytes()).map_err(Error::Io)?; Ok(()) } } }; } // ------------------------------------------------------------------------------------------------ // Unit Tests // ------------------------------------------------------------------------------------------------ #[cfg(test)] mod tests { use super::*; #[derive(Debug, Default)] struct TestError {} impl From<::std::io::Error> for TestError { fn from(_: ::std::io::Error) -> Self { Self {} } } #[test] fn test_manual_options() { #[derive(Debug, Default)] struct TestOptions { count: u32, } #[derive(Debug, Default)] struct TestObject { options: TestOptions, } impl HasOptions for TestObject { fn set_options(&mut self, options: TestOptions) { self.options = options } fn options(&self) -> &TestOptions { &self.options } } let obj = TestObject::default().with_options(TestOptions { count: 2 }); assert_eq!(obj.options().count, 2); } #[test] fn test_macro_options() { #[derive(Debug, Default)] struct TestOptions { count: u32, } #[derive(Debug, Default)] struct TestObject { options: TestOptions, } impl_has_options!(TestObject, TestOptions); let obj = TestObject::default().with_options(TestOptions { count: 2 }); assert_eq!(obj.options().count, 2); } #[test] fn test_writer_to_string() { #[derive(Debug, Default)] struct TestObject {} #[derive(Debug, Default)] struct TestWriter {} impl ObjectWriter for TestWriter { type Error = TestError; fn write(&self, w: &mut W, _object: &TestObject) -> Result<(), Self::Error> where W: Write, { w.write_all(b"Hello")?; Ok(()) } } let writer = TestWriter::default(); assert_eq!( writer.write_to_string(&TestObject::default()).unwrap(), "Hello".to_string() ); } }