Skip to content

Add non-empty string and collection types for API boundary validation#40

Draft
Copilot wants to merge 6 commits intomainfrom
copilot/add-non-empty-string-collection-types
Draft

Add non-empty string and collection types for API boundary validation#40
Copilot wants to merge 6 commits intomainfrom
copilot/add-non-empty-string-collection-types

Conversation

Copy link
Copy Markdown

Copilot AI commented Jan 13, 2026

API payloads need to reject empty strings and collections at deserialization boundaries, not in business logic. Current types allow deserializing empty values, pushing validation downstream.

Implementation

New types under non_empty feature:

  • NonEmptyString - Rejects empty strings during serde deserialization
  • TrimmedNonEmptyString - Trims whitespace, rejects blank strings
  • NonEmptyVec<T> - Type alias for nonempty::NonEmpty<T> with serde support
  • NonEmptyError - Validation error enum

Re-exported from nonempty crate:

  • NonEmpty<T> - Full type with complete API surface
  • nonempty! macro - Ergonomic construction of non-empty collections

Behavior:

use famedly_rust_utils::{NonEmptyString, TrimmedNonEmptyString, NonEmptyVec, nonempty};

#[derive(Deserialize)]
struct Request {
    username: NonEmptyString,           // Rejects ""
    comment: TrimmedNonEmptyString,     // Rejects "   ", trims "  text  " → "text"
    ids: NonEmptyVec<u32>,              // Rejects []
}

// Construct non-empty collections with the macro
let numbers = nonempty![1, 2, 3, 4, 5];

// Fails at deserialization with clear error messages:
// - "string must be non-empty"
// - "string must contain non-whitespace characters"
// - (empty array error from nonempty crate)

Dependencies:

  • Added nonempty v0.10.0 with serialize feature (no vulnerabilities)

API surface:

  • All types implement FromStr, Display, Deref, Serialize, Deserialize
  • Transparent serde representation
  • Optional schemars support when feature enabled
  • Full nonempty crate API re-exported for advanced usage

Additional fix

Removed doc_auto_cfg feature (removed in Rust 1.92.0) that prevented doc builds.

Original prompt

This section details on the original issue you should resolve

<issue_title>Add non-empty String and collection types</issue_title>
<issue_description>## Issue Description
Most inbound API payloads must reject empty strings and empty collections at the boundary. Current types allow deserialising empty strings/vectors, pushing validation deeper into business logic. We need a consistent, centralised approach so inputs fail fast (e.g. Url-like deserialisation error) and lists are guaranteed non-empty (e.g. via nonempty crate).

Options Comparison

Scope Approach Pros Cons
Deserialisation layer Custom serde validators / newtypes (NonEmptyString, TrimmedNonEmptyString) and #[serde(deserialize_with = ...)] Fails fast at boundary; keeps invariants in types; minimal call-site changes Requires newtype coverage of all string fields; migration effort
Collection enforcement nonempty::NonEmpty<T> for lists in request types Compile-time non-empty guarantee; good serde support; clear invariant in type Type churn on existing structs; iterator ergonomics differ slightly
Mixed validation Smart constructors post-deserialisation Centralised logic; fewer type changes Errors shift deeper; risks partial validation

User Story

As a client or service integrating with our APIs, I want invalid empty strings or empty lists to be rejected immediately at request parsing, so I get clear errors and the system maintains strong invariants.

Acceptance Criteria

  • Provide well-documented newtypes for non-empty strings (e.g. NonEmptyString, optionally TrimmedNonEmptyString) with serde deserialisation that rejects empty/blank input at parse time.
  • Provide a non-empty list type (or re-export/alias of nonempty::NonEmpty) with serde support and ergonomic constructors/iterators for library consumers.
  • Ensure error messages surfaced by deserialisation are explicit and consistent (e.g. “field X must be non-empty”).
  • Add unit tests for the newtypes covering empty/blank strings and empty collections, demonstrating deserialisation failures and success paths.
  • Document usage in Rustdoc: how to annotate fields in consumer DTOs with these types and any helper macros/constructors.

Documentation and References

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 13, 2026

Codecov Report

❌ Patch coverage is 93.18182% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 75.89%. Comparing base (c111be9) to head (fc5e368).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/non_empty.rs 93.18% 12 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #40      +/-   ##
==========================================
+ Coverage   67.89%   75.89%   +8.00%     
==========================================
  Files           6        7       +1     
  Lines         380      556     +176     
==========================================
+ Hits          258      422     +164     
- Misses        122      134      +12     
Files with missing lines Coverage Δ
src/lib.rs 64.55% <ø> (ø)
src/non_empty.rs 93.18% <93.18%> (ø)

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c111be9...fc5e368. Read the comment docs.

Copilot AI and others added 4 commits January 13, 2026 12:02
- Add nonempty dependency (v0.10.0) with serialize feature
- Create non_empty module with NonEmptyString, TrimmedNonEmptyString
- Re-export NonEmptyVec type alias for nonempty::NonEmpty
- Add comprehensive unit tests for all types
- Add rustdoc examples and usage documentation
- Export types from lib.rs under non_empty feature flag

Co-authored-by: emgrav <614975+emgrav@users.noreply.github.com>
Remove unstable doc_auto_cfg feature that was removed in Rust 1.92.0

Co-authored-by: emgrav <614975+emgrav@users.noreply.github.com>
The EmptyCollection variant was never used since NonEmptyVec uses the nonempty crate's own error handling

Co-authored-by: emgrav <614975+emgrav@users.noreply.github.com>
Capitalize first word in error variant docs to follow Rust conventions

Co-authored-by: emgrav <614975+emgrav@users.noreply.github.com>
Copilot AI changed the title [WIP] Add non-empty string and collection type validations Add non-empty string and collection types for API boundary validation Jan 13, 2026
Copilot AI requested a review from emgrav January 13, 2026 12:13
@emgrav
Copy link
Copy Markdown
Member

emgrav commented Jan 13, 2026

@copilot We should ensure that we provide the full API surface of the nonempty crate and not just the newtype. F.x. also the Macro. Preferrably by simply reexporting it rather than reimplementing it.

- Export NonEmpty type for full API surface access
- Export nonempty! macro for ergonomic construction
- Add module-level documentation example for macro usage
- Add rustdoc example for macro usage
- Keep NonEmptyVec type alias for convenient Vec-like usage

Co-authored-by: emgrav <614975+emgrav@users.noreply.github.com>
Copy link
Copy Markdown
Author

Copilot AI commented Jan 13, 2026

@copilot We should ensure that we provide the full API surface of the nonempty crate and not just the newtype. F.x. also the Macro. Preferrably by simply reexporting it rather than reimplementing it.

Done. Re-exported the full nonempty API surface:

  • NonEmpty type for complete API access
  • nonempty! macro for ergonomic construction
  • Added documentation examples showing macro usage

See commit c4fa4b2.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add non-empty String and collection types

3 participants