Skip to content

Conversation

@sinkingsugar
Copy link
Member

@sinkingsugar sinkingsugar commented Nov 19, 2025

Introduces a new #[simple_shard] attribute macro that reduces boilerplate for simple transform shards by ~60-77%. Features:

  • ShardType trait for automatic Rust-to-Shards type mapping
  • Auto-generates struct, Default, and all Shard trait implementations
  • Parameters via #[param] (ClonedVar) and #[param_var] (ParamVar)
  • Automatically includes both base type and var type for params
  • Proper compose handling for context variable parameters
  • Memory management via ClonedVar for outputs

Converts yaml.rs and argon.rs as examples of the new syntax.


Note

Introduces the #[simple_shard] macro with ShardType support and refactors YAML, UUID/NanoID, and Argon2 shards to the new pattern, plus small runtime/type additions and a deps cleanup.

  • Macros/Runtime:
    • New #[simple_shard] macro in shards/rust_macro generating full shard boilerplate from functions (params via #[param]/#[param_var], automatic type/var handling, compose/warmup/cleanup, result handling).
    • Type system support: add shards::types::ShardType trait (types/shard_type.rs) and re-export; new output wrappers StringOut/BytesOut; extra ClonedVar From impls; export simple_shard in shards/rust/src/lib.rs.
  • Shards migrated:
    • Core/UUID (modules/core/src/uuid.rs): implement UUID, UUID.ToString, UUID.ToBytes, NanoID via simple_shard; keep UUID.Convert legacy; update registrations; minor type path fixes in Snowflake.
    • YAML (modules/core/src/yaml.rs): rewrite Yaml.ToJson and Yaml.FromJson using simple_shard; update registration.
    • Crypto/Argon2 (modules/crypto/src/argon.rs): rewrite Argon2id.Hash and Argon2id.Verify using simple_shard, with defaults and Hash as context var.
  • Build/Config:
    • Remove add_subdirectory(FTXUI) from deps/CMakeLists.txt.
    • Update .gitignore to ignore shards/rust_macro/Cargo.lock.

Written by Cursor Bugbot for commit 49d5cd1. This will update automatically on new commits. Configure here.

Introduces a new `#[simple_shard]` attribute macro that reduces boilerplate
for simple transform shards by ~60-77%. Features:

- ShardType trait for automatic Rust-to-Shards type mapping
- Auto-generates struct, Default, and all Shard trait implementations
- Parameters via #[param] (ClonedVar) and #[param_var] (ParamVar)
- Automatically includes both base type and var type for params
- Proper compose handling for context variable parameters
- Memory management via ClonedVar for outputs

Converts yaml.rs and argon.rs as examples of the new syntax.
Copilot AI review requested due to automatic review settings November 19, 2025 11:21
@claude
Copy link

claude bot commented Nov 19, 2025

Code Review: Add simple_shard proc macro for simplified shard definitions

This is a well-designed macro that significantly reduces boilerplate for simple transform shards. The implementation is clean and the examples demonstrate good reduction in code (~60-77% as claimed). Here's my detailed review:

✅ Strengths

  1. Excellent API Design: The #[simple_shard] attribute with #[param] and #[param_var] provides an intuitive, declarative interface
  2. Good Type Safety: The ShardType trait provides compile-time type mapping between Rust and Shards types
  3. Proper Memory Management: Uses ClonedVar for outputs and correctly handles ParamVar lifecycle
  4. Clean Examples: The yaml.rs and argon.rs rewrites demonstrate the value well

🔴 Issues to Address

1. Naming Inconsistency in yaml.rs (line 26)

#[simple_shard("Yaml.FromJson", "A shard that converts JSON to YAML.")]
fn json_to_yaml(...)

The function is named json_to_yaml but the shard is Yaml.FromJson. This generates YamlFromJsonShard but the semantics are confusing - it should be Json.ToYaml to match the operation. Consider either:

  • Rename to Yaml.ToYaml or Json.ToYaml
  • Or keep it as Yaml.FromJson but update the description to match

2. Missing Bounds Validation in argon.rs (lines 20-22)

let memory_cost = u32::try_from(memory_cost).map_err(|_| "Invalid memory cost")?;

No upper bound checking. Argon2 has limits (e.g., memory should be reasonable, not i64::MAX). Consider adding validation or documenting limits.

3. Potential Panic in macro (lib.rs:1180)

argon2::Params::new(memory_cost, time_cost, parallelism, None).unwrap()

Wait, I see this was actually fixed in the new code with .map_err(|_| "Invalid Argon2 parameters")?. Good!

4. Missing inputHelp and outputHelp in ShardGenerated implementation

The macro doesn't generate input_help() or output_help() methods, which could be useful for documentation. Not blocking, but worth considering for future enhancement.

5. Error type handling is narrow (macro)

The macro only supports Result<T, &'static str>. Consider supporting Result<T, String> or custom error types in the future.

6. No support for shards that don't return Result

The macro always expects Result<T, E> return type. Simple shards that never fail can't use a plain return type.

⚠️ Suggestions

1. Consider adding #[inline] to the inner function

The generated inner function could benefit from #[inline] hint since it's called from activate:

#[inline]
fn #func_name(...) -> ... { ... }

2. Typo in struct name (lib.rs:998)

struct SimplShardAttrArgs {  // Should be SimpleShardAttrArgs

Minor but worth fixing for consistency.

3. Add unit type support for inputs

The macro requires at least one input. Consider supporting () for shards that only use parameters:

#[simple_shard("Random.Int", "Generates random integer")]
fn random_int(
    _: (),
    #[param("Max", "Maximum value", default = 100i64)]
    max: i64,
) -> Result<i64, &'static str> { ... }

4. ShardType could use more vector types

Missing common types in shard_type.rs:

  • (i64, i64, i64) for int3 with i64 components
  • Consider implementing for Var directly for passthrough shards

🔒 Security Considerations

The Argon2 defaults (8MB memory, 4 iterations) are reasonable for password hashing, but:

  • Consider adding minimum bounds checking (e.g., time_cost >= 1, parallelism >= 1)
  • Memory cost should have a reasonable upper limit to prevent DoS through parameter abuse

📋 Test Coverage

No new tests are visible in this PR. Consider adding:

  1. Unit tests for the ShardType implementations
  2. Integration tests using the simple_shard macro with various type combinations
  3. Tests for edge cases (missing required params, invalid defaults)

Summary

This is a solid addition that will significantly improve developer experience when writing simple shards. The core implementation is sound. Address the naming inconsistency in yaml.rs and consider adding the bounds checking for argon2 parameters before merging.

Recommendation: Approve with minor changes required.


🤖 Generated with Claude Code

@claude
Copy link

claude bot commented Nov 19, 2025

PR Review: Add simple_shard proc macro for simplified shard definitions

This is an excellent addition that significantly reduces boilerplate for simple transform shards. The macro design is clean and the automatic type inference via ShardType trait is well thought out.

Summary

👍 Overall: Good to merge with fixes

The macro implementation is solid and the converted shards demonstrate the value proposition well (77% reduction in yaml.rs, 69% in argon.rs).


Issues to Address

1. Naming Inconsistency in yaml.rs (Bug)

register_shard::<YamlToJsonShard>();
register_shard::<YamlFromJsonShard>();  // Should this be JsonToYamlShard?

The function is named json_to_yaml but the generated struct is YamlFromJsonShard. This is inconsistent - the macro generates the struct name from the shard name "Yaml.FromJson" → "YamlFromJsonShard", but the function name implies the opposite direction.

The shard name "Yaml.FromJson" is semantically confusing - it reads as "Yaml from JSON" but could be misread as "Yaml.FromJson outputs Yaml". Consider renaming to "Json.ToYaml" for clarity, matching the function name pattern.

2. Missing Required Parameter Validation (Bug)

In argon2id_verify, the Hash parameter uses #[param_var] without a default value:

#[param_var("Hash", "The Argon2id hash to verify against.")]
hash: &str,

The original code had explicit validation in compose:

if self.hash.is_none() {
    return Err("Hash parameter is required");
}

The new macro doesn't generate this validation. When Hash is not provided, it will use ParamVar::default() which is Var::default() (None), and the conversion to &str in activate will fail with a generic error instead of a helpful "Hash parameter is required" message at compose time.

Fix: Add compose-time validation for required param_var parameters. Consider adding a required attribute or checking for None defaults.

3. Minor: Cleanup calls signature mismatch

In the macro-generated code:

fn cleanup(&mut self, context: std::option::Option<&shards::types::Context>) -> std::result::Result<(), &str> {
    if let Some(context) = context {
        #(#param_cleanups)*
    }
    // ...
}

But ParamVar::cleanup takes Option<&Context>, so the unwrapping is correct. However, verify that all cleanup paths are handled properly when context is None.


Suggestions for Improvement

1. Error Messages Lose Context

The original argon.rs had error handling that could be improved:

argon2::Params::new(memory_cost, time_cost, parallelism, None).unwrap()

The new version properly handles this:

argon2::Params::new(memory_cost, time_cost, parallelism, None)
    .map_err(|_| "Invalid Argon2 parameters")?

👍 Good improvement!

2. Consider Adding #[param_required] Attribute

For parameters that must be provided (like Hash in Argon2id.Verify), a dedicated attribute would be clearer:

#[param_var_required("Hash", "The Argon2id hash to verify against.")]
hash: &str,

This would generate compose-time validation.

3. Type Safety for Numeric Conversions

The integer parameters use i64 but need conversion to u32. The error handling is good, but consider whether the ShardType trait should support u32 directly to avoid runtime conversion failures.


Code Quality

Positives:

  • Clean macro implementation with good separation of concerns
  • Proper handling of ParamVar warmup/cleanup lifecycle
  • Automatic generation of both base and var types for parameters
  • Good use of lazy_static for parameter definitions
  • CRC-based hash for shard identification

Concerns:

  • The macro is quite complex (~500 lines) - ensure it's well-tested with edge cases
  • No unit tests for the macro itself visible in this PR

FTXUI Removal

The removal of FTXUI from deps/CMakeLists.txt appears unrelated to the main feature. Is this intentional?


Testing

The existing tests in hello.shs and crypto.shs should verify the converted shards still work correctly. Ensure these tests pass with the new implementation.


Recommended Actions Before Merge

  1. Fix or document the naming inconsistency in yaml.rs
  2. Add compose-time validation for required param_var parameters in the macro
  3. Verify FTXUI removal is intentional
  4. Run the full test suite (just tests)

Review by Claude Code

Copilot finished reviewing on behalf of sinkingsugar November 19, 2025 11:25
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces a #[simple_shard] procedural macro that dramatically reduces boilerplate for defining simple transform shards in Rust. The macro automatically generates struct definitions, trait implementations, and parameter handling code from annotated functions. It achieves this through a new ShardType trait that maps Rust types to Shards type system types, enabling automatic type inference.

Key changes:

  • New simple_shard macro in rust_macro/src/lib.rs (~512 lines) that generates complete shard implementations from annotated functions
  • ShardType trait in types/shard_type.rs providing bidirectional Rust⟷Shards type mappings for primitives, strings, tuples, and bytes
  • Conversion of yaml.rs and argon.rs shards to the new syntax, demonstrating 60-77% reduction in boilerplate code

Reviewed Changes

Copilot reviewed 7 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
shards/rust_macro/src/lib.rs Implements the simple_shard macro with parameter parsing, type extraction, and code generation for complete shard implementations
shards/rust/src/types/shard_type.rs Adds ShardType trait with implementations for common Rust types, enabling automatic type inference in the macro
shards/rust/src/types/mod.rs Re-exports ShardType trait for use with simple_shard macro
shards/rust/src/lib.rs Re-exports simple_shard macro at the library level
shards/modules/crypto/src/argon.rs Converts Argon2id shards to use simple_shard macro, demonstrating cleaner syntax and better error handling
shards/modules/core/src/yaml.rs Converts YAML conversion shards to use simple_shard macro, showing significant reduction in boilerplate

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 998 to 1003
struct SimplShardAttrArgs {
name: LitStr,
help: LitStr,
}

impl syn::parse::Parse for SimplShardAttrArgs {
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in struct name: SimplShardAttrArgs should be SimpleShardAttrArgs for consistency with the naming pattern used elsewhere (e.g., SimpleParamInfo, SimpleParamAttr).

Suggested change
struct SimplShardAttrArgs {
name: LitStr,
help: LitStr,
}
impl syn::parse::Parse for SimplShardAttrArgs {
struct SimpleShardAttrArgs {
name: LitStr,
help: LitStr,
}
impl syn::parse::Parse for SimpleShardAttrArgs {

Copilot uses AI. Check for mistakes.
}
}

fn generate_simple_shard(args: SimplShardAttrArgs, func: syn::ItemFn) -> Result<TokenStream, Error> {
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in struct name: SimplShardAttrArgs should be SimpleShardAttrArgs for consistency with the naming pattern used elsewhere (e.g., SimpleParamInfo, SimpleParamAttr).

Copilot uses AI. Check for mistakes.
/// trait implementations.
#[proc_macro_attribute]
pub fn simple_shard(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = syn::parse_macro_input!(attr as SimplShardAttrArgs);
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in struct name: SimplShardAttrArgs should be SimpleShardAttrArgs for consistency with the naming pattern used elsewhere (e.g., SimpleParamInfo, SimpleParamAttr).

Suggested change
let args = syn::parse_macro_input!(attr as SimplShardAttrArgs);
let args = syn::parse_macro_input!(attr as SimpleShardAttrArgs);

Copilot uses AI. Check for mistakes.
Comment on lines +1090 to +1119
// Rest are parameters - parse #[param(...)] or #[param_var(...)] attribute
let mut param_name = arg_name.to_string();
let mut param_desc = String::new();
let mut param_default: Option<syn::Expr> = None;
let mut is_var = false;

for attr in &pat_type.attrs {
if attr.path().is_ident("param") {
let parsed: SimpleParamAttr = attr.parse_args()?;
param_name = parsed.name;
param_desc = parsed.description;
param_default = parsed.default;
} else if attr.path().is_ident("param_var") {
// For context variable parameters
let parsed: SimpleParamAttr = attr.parse_args()?;
param_name = parsed.name;
param_desc = parsed.description;
param_default = parsed.default;
is_var = true;
}
}

params.push(SimpleParamInfo {
name: param_name,
rust_name: arg_name,
rust_type: arg_type,
description: param_desc,
default: param_default,
is_var,
});
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Parameters without #[param] or #[param_var] attributes are silently processed with default metadata (empty description, arg name as parameter name). This could lead to confusing behavior. Consider requiring one of these attributes for all parameters after the input argument, or at minimum documenting this behavior.

Copilot uses AI. Check for mistakes.
Comment on lines 998 to 1003
struct SimplShardAttrArgs {
name: LitStr,
help: LitStr,
}

impl syn::parse::Parse for SimplShardAttrArgs {
Copy link

Copilot AI Nov 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo in struct name: SimplShardAttrArgs should be SimpleShardAttrArgs for consistency with the naming pattern used elsewhere (e.g., SimpleParamInfo, SimpleParamAttr).

Suggested change
struct SimplShardAttrArgs {
name: LitStr,
help: LitStr,
}
impl syn::parse::Parse for SimplShardAttrArgs {
struct SimpleShardAttrArgs {
name: LitStr,
help: LitStr,
}
impl syn::parse::Parse for SimpleShardAttrArgs {

Copilot uses AI. Check for mistakes.
- Add unit type () input support for generator shards
- Support plain return types (non-Result) for infallible shards
- Add ShardType for i64/f64 vector types and [u8; 16]
- Add From<Vec<u8>>, From<[u8; 16]>, From<String> for ClonedVar
- Fix typo SimplShardAttrArgs -> SimpleShardAttrArgs
- Add #[inline] to generated inner functions
- Convert uuid.rs shards to simple_shard (82% reduction)
@claude
Copy link

claude bot commented Nov 19, 2025

PR Review: Add simple_shard proc macro for simplified shard definitions

This PR introduces a well-designed #[simple_shard] attribute macro that significantly reduces boilerplate for simple transform shards. The implementation demonstrates a solid understanding of proc macros and the existing Shards infrastructure.

Strengths

  1. Significant Boilerplate Reduction: The examples show ~60-77% reduction in code. Compare the original NanoIDCreate (70+ lines) to the new version (8 lines).

  2. Good Type System Integration: The ShardType trait provides clean Rust-to-Shards type mapping with proper support for primitives, vectors, and special types like [u8; 16] for UUID.

  3. Proper Parameter Handling: Both #[param] (ClonedVar) and #[param_var] (ParamVar/context variables) are supported with automatic type array generation including var types.

  4. Good Memory Management: Uses ClonedVar for output storage, ensuring proper cleanup.

  5. Test Coverage: Existing tests in crypto.shs exercise all the converted shards (UUID, UUID.ToString, UUID.ToBytes, NanoID, Argon2id.Hash, Argon2id.Verify).

Issues to Address

Critical

  1. Argon2id.Hash Panics on Invalid Parameters (argon.rs:21-22):

    argon2::Params::new(memory_cost, time_cost, parallelism, None)
      .map_err(|_| "Invalid Argon2 parameters")?,

    The original code used .unwrap() which would panic. While this PR adds map_err, this is good, but consider validating the parameters earlier with more specific error messages (e.g., "Memory cost must be at least 8 KiB", "Parallelism must be at least 1").

  2. Yaml.FromJson Registration Name Mismatch (yaml.rs:27):

    register_shard::<YamlFromJsonShard>();

    This should likely be JsonToYamlShard or the generated struct name should match the function name json_to_yaml. Currently the macro generates YamlFromJsonShard from the shard name "Yaml.FromJson", which is correct, but the function is named json_to_yaml which could be confusing during debugging.

Moderate

  1. Missing required_variables Validation for param_var (rust_macro/src/lib.rs:1386):
    The Argon2id.Verify shard uses #[param_var("Hash", ...)] but there's no validation in compose that the parameter is actually provided (non-None). The original implementation had:

    if self.hash.is_none() {
      return Err("Hash parameter is required");
    }

    The new macro should support marking param_var as required vs optional.

  2. Hardcoded Hash Version String (rust_macro/src/lib.rs:1271):

    let crc = crc32(format!("{}-rust-0x20250822", shard_name));

    This date string should be documented or made configurable. When the macro implementation changes, this should be updated to invalidate cached shards.

Minor

  1. Extra Blank Lines (metadata.rs:1626, 2020):
    The PR adds extra blank lines at lines 1626 and 2020 which should be cleaned up.

  2. Inconsistent Error Messages: Some shards return &'static str errors while others could benefit from more context. Consider a consistent error message format.

Suggestions for Future Enhancement

  1. Support for Multiple Input Types: The original UUID.Convert shard accepts BYTES_OR_STRING_TYPES. The simple_shard macro could support this via a union type or custom attribute.

  2. Optional Parameters Without Defaults: Currently #[param_var] without a default creates a required parameter, but there's no way to express "optional parameter that defaults to None".

  3. Documentation Attribute: Support #[doc = "..."] to generate input/output help text beyond the main help string.

  4. Inline Output Type: For simple cases like [u8; 16], the ShardType implementation works well. Consider documenting the pattern for adding new type mappings.

Questions

  1. Is the has_warmup() -> true always correct? The macro generates empty warmup for shards without param_var, which is technically unnecessary overhead.

  2. The has_compose() -> bool returns #has_var_params - should shards with only #[param] (ClonedVar) still run compose for type validation?

Verdict

This is a well-implemented feature that will significantly improve developer experience when writing simple shards. The converted examples (UUID, YAML, Argon2) demonstrate the macro works correctly for real-world use cases.

Recommend merge after addressing:

The other items are improvements that could be addressed in follow-up PRs.


🤖 Generated with Claude Code

@sinkingsugar sinkingsugar force-pushed the feature/simple-shard-macro branch from 2461801 to 2d12923 Compare November 19, 2025 12:32
@claude
Copy link

claude bot commented Nov 19, 2025

PR Review: Add simple_shard proc macro for simplified shard definitions

This is a well-designed ergonomic improvement that significantly reduces boilerplate for simple transform shards. The implementation demonstrates good Rust macro design patterns.

Strengths

1. Excellent Developer Experience

  • ~60-77% boilerplate reduction as claimed
  • Clean, intuitive syntax with #[param] and #[param_var] attributes
  • Type inference via ShardType trait is elegant

2. Good Code Quality

  • Clear separation between #[param] (ClonedVar) and #[param_var] (ParamVar/context variables)
  • Proper compose handling for context variable parameters
  • Memory management properly handled with ClonedVar wrappers

3. Solid Examples

  • UUID, YAML, and Argon2 migrations demonstrate the macro well
  • Existing tests in crypto.shs and hello.shs exercise the converted shards

Issues to Address

1. Missing Required Parameter Validation (Important)

In Argon2id.Verify, the Hash parameter is marked with #[param_var] but has no default attribute and no required validation in compose:

#[simple_shard("Argon2id.Verify", "Verifies a password against an Argon2id hash.")]
fn argon2id_verify(
  password: &str,
  #[param_var("Hash", "The Argon2id hash to verify against.")]
  hash: &str,
) -> Result<bool, &'static str> {

The old implementation had:

fn compose(&mut self, data: &InstanceData) -> Result<Type, &str> {
    if self.hash.is_none() {
      return Err("Hash parameter is required");
    }
    // ...
}

The simple_shard macro doesn't generate this validation. Users will get a confusing runtime error when hash is not set.

Recommendation: Add support for a required marker or automatically validate that param_var parameters without defaults are set in compose.


2. Inconsistent Shard Name in Registration (Bug)

In yaml.rs:

pub fn register_shards() {
  register_shard::<YamlToJsonShard>();
  register_shard::<YamlFromJsonShard>();  // Function is json_to_yaml, shard name is "Yaml.FromJson"
}

The generated struct for json_to_yaml function with #[simple_shard("Yaml.FromJson", ...)] will be YamlFromJsonShard, which is correct. This looks fine - just confirming the naming works out.


3. has_warmup Always True (Minor)

The macro generates:

fn has_warmup() -> bool { true }

But the warmup implementation only does something if there are param_var parameters:

fn warmup(&mut self, context: &shards::types::Context) -> std::result::Result<(), &str> {
    #(#param_warmups)*  // Only for param_var
    Ok(())
}

For shards with no param_var parameters, has_warmup could be false for minor optimization.


4. Type Safety for Fixed Arrays (Question)

impl ShardType for [u8; 16] {
    fn shards_type() -> Type { common_type::int16 }

This maps [u8; 16] to int16 (which is 16 bytes). This is correct for UUID, but the naming int16 might be confusing as it looks like a 16-bit integer. This appears to be existing convention, but worth noting that documentation might help users understand int16 means "16 integers/bytes" not "16-bit int".


5. Cleanup Missing Context Check (Bug)

In the cleanup implementation:

fn cleanup(&mut self, context: std::option::Option<&shards::types::Context>) -> std::result::Result<(), &str> {
    if let Some(context) = context {
      #(#param_cleanups)*
    }
    self.output = shards::types::ClonedVar::default();
    Ok(())
}

But param_cleanups generates:

self.#name.cleanup(Some(context));

This is passing Some(context) but the variable is already unwrapped. Should be:

self.#name.cleanup(context);  // context is already Option<&Context>

Wait, actually the variable context in scope is &Context (unwrapped), so Some(context) is correct. Disregard this point.


6. Unused Import Cleanup (Minor)

In uuid.rs, several imports are removed but some like INT_TYPES are still used. Verify all imports are correctly managed. For example:

use shards::types::INT_TYPES;  // Still needed for SnowflakeShard

Looking at the diff, it seems INT_TYPES is still used via the SnowflakeShard. However, the fully qualified shards::types::NONE_TYPES and shards::types::INT16_TYPES are used inline rather than imported. This is inconsistent styling.


Suggestions for Future Enhancement

  1. Add #[required] attribute for param_var parameters to generate compose validation

  2. Consider optional parameters - A way to mark parameters as truly optional (Option) rather than always having defaults

  3. Input type validation - The macro could validate that the first parameter type implements TryFrom<&Var>

  4. Documentation generation - Consider generating Rust doc comments from the help string


Testing Verification

The existing test coverage appears adequate:

  • shards/tests/crypto.shs - Tests UUID, UUID.ToString, UUID.ToBytes, NanoID, Argon2id.Hash, Argon2id.Verify
  • shards/tests/hello.shs - Tests Yaml.ToJson, Yaml.FromJson
  • shards/tests/crdts.shs - Uses UUID

The shard API appears to be preserved based on how these tests use the shards.


Summary

Approve with minor revisions needed:

  1. Must fix: Add required parameter validation for param_var without defaults (the Argon2id.Verify issue)
  2. Should fix: Consider optimizing has_warmup() to return false when no param_var exists

The PR significantly improves developer ergonomics for writing simple shards. The implementation is solid and the converted examples demonstrate the value well.

@sinkingsugar sinkingsugar force-pushed the feature/simple-shard-macro branch from 2d12923 to 1023525 Compare November 19, 2025 12:33
@claude
Copy link

claude bot commented Nov 19, 2025

PR Review: Add simple_shard proc macro for simplified shard definitions

This PR introduces a #[simple_shard] attribute macro to reduce boilerplate for simple transform shards. The macro implementation is well-designed and the converted examples demonstrate significant code reduction.


✅ Strengths

  1. Excellent boilerplate reduction - The YAML and UUID shards show ~60-77% reduction in code while maintaining functionality
  2. Good type inference - The ShardType trait provides clean mapping from Rust types to Shards types
  3. Proper Result handling - The macro correctly handles both plain return types and Result<T, &'static str>
  4. Context variable support - #[param_var] attribute properly handles ParamVar with compose/warmup/cleanup

🔴 Critical Issues

1. Bug in Argon2id.Verify - Missing required parameter validation

shards/modules/crypto/src/argon.rs:55-56

The old implementation validated that Hash parameter was provided in compose:

if self.hash.is_none() {
    return Err("Hash parameter is required");
}

The new #[param_var] version has no default value, which could cause runtime failures if the parameter isn't set. The macro should either:

  • Enforce required parameters at compose time
  • Or require the caller to specify a default

2. Cleanup inconsistency - context may be None

shards/rust_macro/src/lib.rs:1516-1519

fn cleanup(&mut self, context: std::option::Option<&shards::types::Context>) -> std::result::Result<(), &str> {
    if let Some(context) = context {
        #(#param_cleanups)*
    }

But the cleanup calls generated use Some(context):

quote! { self.#name.cleanup(Some(context)); }

This would pass context (the unwrapped value) wrapped in Some() again. Should be:

quote! { self.#name.cleanup(context); }

Where context is from the outer match, not rewrapped.

3. Regression: UUID.ToString help text changed

The old help text was "Reads an UUID" (grammatically correct for spelled-out "U-U-I-D") but the new one says "Reads a UUID" - minor but this is inconsistent with the original.


🟡 Potential Issues

1. Error handling is less informative

shards/modules/core/src/yaml.rs:6

Original: serde_yml::from_str(yaml).map_err(|e| /* potentially include error details */)
New: serde_yml::from_str(yaml).map_err(|_| "Failed to parse YAML")?

Consider preserving error details. The macro could support Result<T, String> to allow formatted errors.

2. Unnecessary type wrapping for BytesOut/StringOut

shards/rust/src/types/common.rs:215-258

These wrappers duplicate what ClonedVar already does. While they provide type safety for the ShardType trait, they add complexity. Consider whether these could be type aliases instead.

3. Memory: output not cleared on warmup

The output ClonedVar is cleared in cleanup but not reset on warmup. If the shard is reused, it could hold stale data between activations. The activate function does overwrite it, but initializing to default in warmup would be safer.


🟠 Code Quality

1. Missing parameter validation in macro

shards/rust_macro/src/lib.rs:1118-1130

When a param has no #[param] attribute, it gets empty description:

let mut param_desc = String::new();

This should probably be an error - all parameters should have documentation.

2. Inconsistent naming: YamlFromJsonShard vs JsonToYamlShard

shards/modules/core/src/yaml.rs:29

The shard name is "Yaml.FromJson" but the generated struct name becomes YamlFromJsonShard. The old code had JsonToYamlShard for the same functionality. This is just an observation - the generated name is correct.

3. Unused import cleanup needed

shards/modules/core/src/uuid.rs

Several imports were removed but the file still has some unused ones after conversion:

  • ExposedTypes is only used by UUIDConvert and SnowflakeShard (kept shards)
  • InstanceData may not be needed anymore

🔵 Suggestions for Improvement

  1. Add validation for required param_var parameters
    The macro should generate compose-time validation for #[param_var] without defaults

  2. Support for input validation
    Many shards need to validate input types in compose. Consider adding a #[input_types(...)] attribute for specifying multiple input types

  3. Add tests for the macro
    The converted shards serve as integration tests, but unit tests for the macro itself (edge cases, error conditions) would be valuable

  4. Consider deprecation path
    Since this is pre-release software, it's fine, but consider documenting which approach (manual vs simple_shard) is preferred going forward


📝 Unrelated Change

The removal of add_subdirectory(FTXUI) from deps/CMakeLists.txt should probably be in a separate PR or at least mentioned in the PR description.


Summary

The macro is well-implemented and significantly reduces boilerplate. The critical issues around parameter validation and cleanup context handling should be addressed before merge. The converted shards work correctly but lose some validation that the original implementations had.

Recommendation: Address the cleanup context bug and add required parameter validation for #[param_var], then this is ready to merge.


🤖 Generated with Claude Code

Avoid extra allocations by returning typed ClonedVar wrappers directly.
The macro detects these types and extracts the inner ClonedVar without
calling .into() on intermediate allocations.
@sinkingsugar sinkingsugar force-pushed the feature/simple-shard-macro branch from 1023525 to 49d5cd1 Compare November 19, 2025 12:37
@claude
Copy link

claude bot commented Nov 19, 2025

PR Review: Add simple_shard proc macro for simplified shard definitions

Summary

This PR introduces a #[simple_shard] attribute macro that significantly reduces boilerplate for simple transform shards, with excellent examples in yaml.rs, uuid.rs, and argon.rs.


✅ Strengths

  1. Significant boilerplate reduction - yaml.rs goes from 130 lines to 29 lines (~77% reduction). This is a major developer experience improvement.

  2. Clean API design - The function-based approach with #[param] and #[param_var] attributes is intuitive and ergonomic.

  3. Type safety via ShardType trait - Good use of the trait system for automatic type mapping from Rust types to Shards types.

  4. Proper memory management - The StringOut/BytesOut wrappers handle output allocation correctly through ClonedVar.

  5. Good handling of edge cases - Support for Result returns, unit inputs, and context variables via #[param_var].


🔴 Critical Issues

1. Memory allocation in cleanup for output reset

Location: shards/rust_macro/src/lib.rs:1520

fn cleanup(&mut self, context: std::option::Option<&shards::types::Context>) -> std::result::Result<(), &str> {
    if let Some(context) = context {
        #(#param_cleanups)*
    }
    self.output = shards::types::ClonedVar::default();  // Potential issue
    Ok(())
}

This unconditionally allocates a new default ClonedVar on every cleanup, even when context is None. The old implementation patterns don't do this. Consider only resetting when needed or ensuring this matches expected behavior for the runtime.

2. Missing required parameter validation in compose for #[param_var]

Location: shards/rust_macro/src/lib.rs:1524-1527

For #[param_var] parameters without defaults (like Hash in argon2id_verify), there's no validation that the parameter is actually set. The old Argon2idVerifyShard had:

if self.hash.is_none() {
    return Err("Hash parameter is required");
}

This validation is missing in the generated code. Users will get a confusing runtime error instead of a clear compile-time error message.


🟡 Issues to Address

3. Naming inconsistency in yaml.rs

Location: shards/modules/core/src/yaml.rs:25-26

The shard is named "Yaml.FromJson" but the function is named json_to_yaml. The generated struct would be YamlFromJsonShard, which is correctly registered, but the registration at line 27 references this:

register_shard::<YamlToJsonShard>();
register_shard::<YamlFromJsonShard>();  // Note: This is the second shard's struct name

The function name json_to_yaml describes what it does (JSON → YAML), but the shard name Yaml.FromJson is a bit confusing (is it "from JSON" or "to JSON"?). Consider renaming to Yaml.ToYaml or ensuring naming consistency.

4. Potential unwrap panic in Argon2 params

Location: shards/modules/crypto/src/argon.rs:35-36

Old code:

argon2::Params::new(memory_cost, time_cost, parallelism, None).unwrap()

New code:

argon2::Params::new(memory_cost, time_cost, parallelism, None)
    .map_err(|_| "Invalid Argon2 parameters")?

The new code is actually better - it properly handles the error. This is an improvement.

5. NanoID size parameter edge case

Location: shards/modules/core/src/uuid.rs:40-42

fn nanoid_create(
  _: (),
  #[param("Size", "The output string length of the created NanoID.", default = 21i64)]
  size: i64,
) -> StringOut {
  let size = size as usize;  // Unchecked cast
  nanoid::nanoid!(size).as_str().into()
}

If a negative value is passed, size as usize will wrap to a very large number. Consider adding validation:

let size = usize::try_from(size).map_err(|_| "Size must be non-negative")?;

🟠 Suggestions for Improvement

6. Add has_compose optimization

Location: shards/rust_macro/src/lib.rs:1493

fn has_compose() -> bool { #has_var_params }

This correctly optimizes by only enabling compose when there are var params. Good!

However, there's no optimization for has_warmup - it's always true even when there are no ParamVar parameters. Consider:

fn has_warmup() -> bool { #has_var_params }

This would skip unnecessary warmup calls for simple shards with only ClonedVar parameters.

7. Consider adding input property support

The macro currently doesn't support specifying input properties (like InputInfo with properties). For many shards this is fine, but it might be useful to have optional support like:

#[simple_shard("Foo", "desc", input_property = "X")]

8. Documentation example has incorrect Result usage

Location: shards/rust_macro/src/lib.rs:1549-1557

The docstring example should use &'static str for the error type to match the actual generated code pattern.


🔵 Code Quality Notes

9. Extra blank lines in metadata.rs

Location: shards/rust/src/types/metadata.rs:1627, 2024

There are extra trailing blank lines added. Minor formatting issue.

10. FTXUI removal

Location: deps/CMakeLists.txt

The FTXUI dependency removal appears unrelated to this PR. If it's intentional, consider documenting why in the PR description or doing it in a separate PR.

11. Good use of const generics candidate

The [u8; 16] implementation for ShardType is great. Consider also adding [u8; 32] for SHA-256 hashes or similar if commonly used.


Testing Considerations

  1. The converted shards (UUID, NanoID, YAML, Argon2) should have existing test coverage. Verify these tests still pass.

  2. Consider adding specific test cases for:

    • Negative values for integer parameters
    • Empty string inputs for YAML/JSON conversion
    • Invalid hash format for Argon2.Verify

Overall Assessment

This is a well-designed macro that will significantly improve developer productivity when creating simple shards. The main issues to address are:

  1. Critical: Add required parameter validation for #[param_var] without defaults
  2. Medium: Consider the output cleanup allocation behavior
  3. Low: Fix NanoID size validation

Once these are addressed, this should be good to merge. The 60-77% boilerplate reduction is a significant improvement to the codebase.

🤖 Generated with Claude Code

@claude
Copy link

claude bot commented Nov 19, 2025

PR Review: Add simple_shard proc macro for simplified shard definitions

Overview

This PR introduces a new #[simple_shard] attribute macro to reduce boilerplate when defining simple transform shards. The implementation is well-designed and significantly reduces code complexity for common use cases.

Positives ✅

  • Substantial boilerplate reduction: ~60-77% less code for simple shards
  • Clean DSL design: Intuitive attribute syntax with #[param] and #[param_var]
  • Proper trait abstractions: ShardType trait provides clean Rust-to-Shards type mapping
  • Memory safety: Correct use of ClonedVar for output ownership
  • Good migration examples: Converting yaml.rs, uuid.rs, and argon.rs demonstrates the macro's versatility

Issues to Address

1. Critical: Missing required parameter validation in Argon2id.Verify (shards/modules/crypto/src/argon.rs:43-48)

The old implementation had validation in compose:

if self.hash.is_none() {
    return Err("Hash parameter is required");
}

The new #[param_var("Hash", "...")] has no default value, but the macro doesn't generate validation to ensure required param_var parameters are set. This could cause runtime errors instead of compose-time errors.

Suggestion: Either:

  • Add a required flag to #[param_var] that generates compose-time validation
  • Or set a sensible default and handle empty strings in the function

2. Potential Issue: Type inference mismatch for NanoID size parameter (shards/modules/core/src/uuid.rs:52-53)

let size = size as usize;
nanoid::nanoid!(size)

The nanoid! macro may not accept a runtime variable - it typically expects a compile-time constant. This needs verification that it compiles and works correctly at runtime.

3. Inconsistent error handling in Argon2id.Hash (shards/modules/crypto/src/argon.rs:23)

The old code used .unwrap() on Params::new():

argon2::Params::new(memory_cost, time_cost, parallelism, None).unwrap()

The new code correctly returns an error:

argon2::Params::new(...).map_err(|_| "Invalid Argon2 parameters")?

This is actually an improvement - good catch! 👍

4. has_warmup() always returns true (shards/rust_macro/src/lib.rs:1494)

The generated code sets has_warmup() -> bool { true } even when there are no param_var parameters to warm up. While this is not incorrect (empty warmup is valid), it's slightly inefficient for shards with no context variable parameters.

Suggestion: Only return true when there are actual warmup operations:

fn has_warmup() -> bool { #has_var_params }

5. Missing test coverage

The PR doesn't appear to add tests for the new macro functionality. Consider adding:

  • Unit tests for edge cases (no params, all param_var, mixed params)
  • Integration tests verifying generated shards work correctly
  • Tests for error cases (invalid defaults, missing required params)

6. Minor: Unused parameter warning potential (shards/rust_macro/src/lib.rs:1528)

When is_unit_input is true, the generated code has _context and input parameters where input is unused. The _input naming convention handles this, but worth verifying no warnings are generated.

Code Quality Observations

Well-designed abstractions

The ShardType trait (shard_type.rs) is well-implemented with comprehensive type mappings. Good coverage of:

  • Primitive types (i64, f64, bool)
  • String types (String, &str)
  • Vector types (int2, int3, float4, etc.)
  • Special types (BytesOut, StringOut)

Clean macro structure

The generate_simple_shard function is well-organized with clear separation of:

  • Input parsing
  • Field generation
  • Parameter handling
  • Output generation

Minor Suggestions

  1. Consider adding #[must_use] to BytesOut/StringOut to catch accidental drops

  2. Add more detailed error context in the macro - currently some errors are generic strings without source location hints

  3. Consider supporting &mut for stateful transformations as a future enhancement

  4. The FTXUI removal in deps/CMakeLists.txt appears unrelated to this PR - should this be in a separate commit?

Testing Recommendations

Before merging, please verify:

  1. just build completes without warnings
  2. just tests passes all tests
  3. just cargo-check succeeds for Rust code
  4. The converted shards (UUID, NanoID, YAML, Argon2) work correctly in Shards scripts

Summary

This is a well-designed addition that will significantly improve developer productivity when creating simple shards. The macro implementation is solid and the conversions demonstrate good real-world applicability.

Recommendation: Address the missing required parameter validation for Argon2id.Verify.Hash and verify the nanoid! macro works with runtime values before merging. The other issues are lower priority but worth tracking.


🤖 Generated with Claude Code

@codecov
Copy link

codecov bot commented Nov 19, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 77.30%. Comparing base (2e295ea) to head (49d5cd1).
⚠️ Report is 6 commits behind head on devel.

Additional details and impacted files
@@            Coverage Diff             @@
##            devel    #1214      +/-   ##
==========================================
- Coverage   77.34%   77.30%   -0.05%     
==========================================
  Files         386      385       -1     
  Lines       60709    60215     -494     
==========================================
- Hits        46958    46548     -410     
+ Misses      13751    13667      -84     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sinkingsugar sinkingsugar merged commit beeefdd into devel Nov 19, 2025
49 of 61 checks passed
@sinkingsugar sinkingsugar deleted the feature/simple-shard-macro branch November 19, 2025 23:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants