0.4.0-pre.14

This is a special version (since the repository hasn't been updated for a while). It includes partial updates from 0.3 to 0.4, along with several fixes for 0.4.0-pre.13.

这是一个特殊版本(因为一段时间没有更新存储库),它包含0.3至0.4的部分更新以及对0.4.0-pre.13的几处修复。
This commit is contained in:
wisdgod
2025-12-23 11:18:28 +08:00
parent 280c2d71a8
commit 65a390d4f2
428 changed files with 66005 additions and 15324 deletions

View File

@@ -6,8 +6,8 @@ HOST=
# 服务器监听端口
PORT=3000
# 路由前缀,必须以 / 开头(如果不为空)
ROUTE_PREFIX=
# 路由前缀,必须以 / 开头(如果不为空)(已弃用,使用 route_registry.json 定义)
# ROUTE_PREFIX=
# 最高权限的认证令牌,必填
AUTH_TOKEN=
@@ -181,3 +181,79 @@ ALLOWED_PROVIDERS=auth0,google-oauth2,github
# 绕过模型验证,允许所有模型(会带有一定的性能损失)
BYPASS_MODEL_VALIDATION=false
# 请求模型唯一标识符来源
# - 可选值
# - id
# - client_id
# - server_id
MODEL_ID_SOURCE=client_id
# 上下文填充位
# - 可选值
# - context: 1 # 仅使用当前上下文
# - repo_context: 2 # 仅使用仓库上下文
# - context + repo_context: 3 # 当前上下文 + 仓库上下文
# - mode_specific_context: 4 # 模式特定上下文
# - context + mode_specific_context: 5 # 当前上下文 + 模式特定上下文
# - repo_context + mode_specific_context: 6 # 仓库上下文 + 模式特定上下文
# - all_contexts: 7 # 所有上下文组合
CONTEXT_FILL_MODE=1
# 前端资源路径
# 事实上定义了 route_registry.json
FRONTEND_PATH=frontend.zip
# NTP 服务器列表(逗号分隔)
# 留空则完全禁用 NTP 时间同步功能
# 示例pool.ntp.org,time.cloudflare.com,time.windows.com
NTP_SERVERS=
# NTP 周期性同步间隔(秒)
# 仅在配置了服务器时生效
# 0 或不设置表示仅在启动时同步一次(不启动后台任务)
NTP_SYNC_INTERVAL_SECS=3600
# 每次同步的采样次数
# 多次采样可提高精度,但会增加同步耗时
# 可用最小值为 3
NTP_SAMPLE_COUNT=8
# 采样间隔(毫秒)
# 两次采样之间的等待时间
# 过小可能导致网络拥塞,过大会延长同步时间
NTP_SAMPLE_INTERVAL_MS=50
# 预计的峰值速率
# RPS: 每秒请求数
LOG_PEAK_RPS=25
# 期望的缓冲时长
# 不进行日志丢弃时期望的堵塞延迟时间
LOG_BUFFER_SECONDS=2
# 过载时日志丢弃(未实现)
# LOG_DROP_ON_OVERLOAD=false
# 运行时间的显示格式
# 可选值:
# - auto : 自动选择格式
# - compact : 紧凑格式 (如: 1h30m)
# - standard : 标准格式 (如: 1 hour 30 minutes)
# - detailed : 详细格式 (如: 1 hour, 30 minutes, 5 seconds)
# - iso8601 : ISO 8601 格式 (如: PT1H30M5S)
# - fuzzy : 模糊格式 (如: about an hour)
# - numeric : 纯数字格式 (如: 5405)
# - verbose : 冗长格式 (如: 1 hour and 30 minutes)
# - random : 随机格式 (仅用于测试)
DURATION_FORMAT=random
# 运行时间的显示语言
# 可选值:
# - english : 英语
# - chinese : 中文
# - japanese : 日语
# - spanish : 西班牙语
# - german : 德语
# - random : 随机语言 (仅用于测试)
DURATION_LANGUAGE=random

6
.gitignore vendored
View File

@@ -32,3 +32,9 @@ Cargo.lock
/*.tar.gz
/src/core/model/a.rs
.cargo/config.toml
/front
/pmacro
/*.json
/tests/data/stream_data.txt
/frontend.zip
/config.toml

2
.rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

View File

@@ -1,80 +1,16 @@
max_width = 100
hard_tabs = false
tab_spaces = 4
newline_style = "Unix"
indent_style = "Block"
use_small_heuristics = "Max"
fn_call_width = 60
attr_fn_like_width = 70
struct_lit_width = 18
struct_variant_width = 35
array_width = 60
chain_width = 60
single_line_if_else_max_width = 50
single_line_let_else_max_width = 50
wrap_comments = false
format_code_in_doc_comments = false
doc_comment_code_block_width = 100
comment_width = 80
normalize_comments = false
normalize_doc_attributes = false
format_strings = false
format_macro_matchers = true
format_macro_bodies = true
skip_macro_invocations = []
hex_literal_case = "Preserve"
empty_item_single_line = true
struct_lit_single_line = true
fn_single_line = true
where_single_line = false
imports_indent = "Block"
imports_layout = "Mixed"
imports_granularity = "Crate"
group_imports = "Preserve"
reorder_imports = true
reorder_modules = true
reorder_impl_items = false
type_punctuation_density = "Wide"
space_before_colon = false
space_after_colon = true
spaces_around_ranges = false
binop_separator = "Front"
remove_nested_parens = true
combine_control_expr = true
short_array_element_width_threshold = 10
overflow_delimited_expr = true
struct_field_align_threshold = 0
enum_discrim_align_threshold = 0
match_arm_blocks = false
match_arm_leading_pipes = "Never"
force_multiline_blocks = false
fn_params_layout = "Tall"
brace_style = "SameLineWhere"
control_brace_style = "AlwaysSameLine"
trailing_semicolon = true
trailing_comma = "Vertical"
match_block_trailing_comma = false
blank_lines_upper_bound = 1
blank_lines_lower_bound = 0
edition = "2024"
# reorder_imports = false
# reorder_modules = false
style_edition = "2024"
# version = "One"
inline_attribute_width = 0
format_generated_files = true
generated_marker_line_search_limit = 5
merge_derives = true
use_try_shorthand = true
use_small_heuristics = "Max"
# merge_derives = false
# group_imports = "Preserve"
imports_granularity = "Crate"
group_imports = "One"
# imports_granularity = "One"
use_field_init_shorthand = true
force_explicit_abi = true
condense_wildcard_suffixes = false
color = "Auto"
required_version = "1.8.0"
unstable_features = true
disable_all_formatting = false
skip_children = false
show_parse_errors = true
error_on_line_overflow = false
error_on_unformatted = false
ignore = []
emit_mode = "Files"
make_backup = false
#unstable_features = true
fn_single_line = true
where_single_line = true

View File

@@ -1,12 +1,93 @@
[package]
name = "cursor-api"
version = "0.3.6-2"
[workspace]
resolver = "2"
members = [
".",
"crates/manually_init",
"crates/rep_move",
"crates/grpc-stream",
"crates/interned",
]
default-members = ["."]
[workspace.package]
version = "0.4.0-pre.14"
edition = "2024"
authors = ["wisdgod <nav@wisdgod.com>"]
description = "OpenAI format compatibility layer for the Cursor API"
description = "A format compatibility layer for the Cursor API"
license = "MIT OR Apache-2.0"
repository = "https://github.com/wisdgod/cursor-api"
[workspace.dependencies]
manually_init = { path = "crates/manually_init", features = ["sync"] }
rep_move = { path = "crates/rep_move" }
grpc-stream = { path = "crates/grpc-stream" }
interned = { path = "crates/interned" }
# ===== 开发配置(编译速度优先)=====
[profile.dev]
opt-level = 0
debug = "line-tables-only"
split-debuginfo = "unpacked"
incremental = true
codegen-units = 256
rustflags = ["-Clink-arg=-fuse-ld=mold"]
# ===== 快速测试构建(平衡配置)=====
[profile.fast]
inherits = "dev"
opt-level = 1
# ===== 性能测试配置(接近 release 但编译更快)=====
[profile.bench]
inherits = "release"
lto = "thin"
codegen-units = 16
# rustflags = [
# "-Ctarget-cpu=native",
# ]
# ===== 发布配置(性能最大化)=====
[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1
panic = "abort"
strip = true
debug = false
overflow-checks = false
incremental = false
trim-paths = "all"
# rustflags = [
# "-Clink-arg=-s",
# # "-Clink-arg=-fuse-ld=lld",
# # "-Ctarget-cpu=native",
# ]
[patch.crates-io]
h2 = { path = "patch/h2-0.4.10" }
reqwest = { path = "patch/reqwest-0.12.18" }
rustls = { path = "patch/rustls-0.23.28" }
chrono = { path = "patch/chrono-0.4.42" }
ulid = { path = "patch/ulid-1.2.1" }
dotenvy = { path = "patch/dotenvy-0.15.7" }
# bs58 = { path = "patch/bs58-0.5.1" }
# base62 = { path = "patch/base62-2.2.1" }
prost = { path = "patch/prost-0.14.1" }
prost-derive = { path = "patch/prost-derive" }
prost-types = { path = "patch/prost-types" }
rkyv = { path = "patch/rkyv-0.8.12" }
# ===========================================
[package]
name = "cursor-api"
version.workspace = true
edition.workspace = true
authors.workspace = true
description.workspace = true
license.workspace = true
repository.workspace = true
[[bin]]
name = "cursor-api"
path = "src/main.rs"
@@ -16,92 +97,147 @@ path = "src/main.rs"
# path = "tools/rkyv_adapter/src/main.rs"
[build-dependencies]
chrono = { version = "0.4", default-features = false, features = ["alloc"]}
chrono = { version = "0.4", default-features = false, features = ["alloc"] }
prost-build = { version = "0.14", optional = true }
sha2 = { version = "0.10", default-features = false }
sha2 = { version = "0", default-features = false }
serde_json = "1"
[dependencies]
ahash = { version = "0.8", default-features = false, features = ["std", "compile-time-rng", "serde"] }
# owned
manually_init.workspace = true
rep_move.workspace = true
grpc-stream.workspace = true
interned.workspace = true
ahash = { version = "0.8", default-features = false, features = [
"compile-time-rng",
"serde",
] }
arc-swap = "1"
axum = { version = "0.8", default-features = false, features = ["http1", "http2", "json", "tokio", "query", "macros"] }
axum = { version = "0.8", default-features = false, features = [
"http1",
"http2",
"json",
"tokio",
"query",
"macros",
] }
# base62 = "2.2.1"
base64 = { version = "0.22", default-features = false, features = ["std"] }
# bs58 = { version = "0.5.1", default-features = false, features = ["std"] }
# brotli = { version = "7.0", default-features = false, features = ["std"] }
bytes = "1.10"
chrono = { version = "0.4", default-features = false, features = ["alloc", "serde", "rkyv-64"] }
bytes = "1"
chrono = { version = "0.4", default-features = false, features = [
"alloc",
"serde",
"rkyv-64",
] }
chrono-tz = { version = "0.10", features = ["serde"] }
dotenvy = "0.15"
flate2 = { version = "1", default-features = false, features = ["rust_backend"] }
flate2 = { version = "1", default-features = false, features = [
"rust_backend",
] }
futures = { version = "0.3", default-features = false, features = ["std"] }
gif = { version = "0.13", default-features = false, features = ["std"] }
hashbrown = { version = "0.15", default-features = false }
gif = { version = "0.14", default-features = false, features = ["std"] }
hashbrown = { version = "0.16", default-features = false, features = [
"serde",
"raw-entry",
"inline-more",
] }
hex = { version = "0.4", default-features = false, features = ["std"] }
http = "1"
http-body-util = "0.1"
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "gif", "webp"] }
image = { version = "0.25", default-features = false, features = [
"jpeg",
"png",
"gif",
"webp",
] }
# lasso = { version = "0.7", features = ["multi-threaded", "ahasher"] }
memmap2 = "0.9"
minicbor = { version = "2", features = ["derive", "alloc"] }
# openssl = { version = "0.10", features = ["vendored"] }
parking_lot = "0.12"
paste = "1.0"
phf = { version = "0.12", features = ["macros"] }
parking_lot = { version = "0.12", features = [
"arc_lock",
"hardware-lock-elision",
] }
paste = "1"
phf = { version = "0.13", features = ["macros"] }
# pin-project-lite = "0.2"
# pin-project = "1"
prost = "0.14"
prost-types = "0.14"
prost = { version = "0.14", features = ["indexmap"] }
# prost-types = "0.14"
rand = { version = "0.9", default-features = false, features = ["thread_rng"] }
reqwest = { version = "0.12", default-features = false, features = ["gzip", "brotli", "json", "stream", "socks", "charset", "http2", "macos-system-configuration"] }
rkyv = { version = "0.8", default-features = false, features = ["std", "pointer_width_64", "uuid-1"] }
reqwest = { version = "0.12", default-features = false, features = [
"gzip",
"brotli",
"json",
"stream",
"socks",
"charset",
"http2",
"system-proxy",
] }
rkyv = { version = "0.8", default-features = false, features = [
"std",
"pointer_width_64",
"hashbrown-0_16",
"uuid-1",
] }
# rustls = { version = "0.23.26", default-features = false, features = ["std", "tls12"] }
serde = { version = "1", default-features = false, features = ["std", "derive", "rc"] }
serde = { version = "1", default-features = false, features = [
"std",
"derive",
"rc",
] }
# serde_json = { package = "sonic-rs", version = "0" }
serde_json = "1"
serde_json = { version = "1", features = ["preserve_order"] }
sha2 = { version = "0", default-features = false }
sysinfo = { version = "0.37", default-features = false, features = ["system"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "sync", "time", "fs", "signal"] }
tokio = { version = "1", features = [
"rt-multi-thread",
"macros",
"net",
"sync",
"time",
"fs",
"signal",
] }
tokio-util = { version = "0.7", features = ["io"] }
# tokio-tungstenite = { version = "0.26.2", features = ["rustls-tls-webpki-roots"] }
# tokio-stream = { version = "0.1", features = ["time"] }
tower-http = { version = "0.6", features = ["cors", "limit"] }
tracing = { version = "*", default-features = false, features = ["max_level_off", "release_max_level_off"] }
tracing = { version = "*", default-features = false, features = [
"max_level_off",
"release_max_level_off",
] }
ulid = { version = "1.2", default-features = false, features = ["std", "rkyv"] }
# tracing-subscriber = "0.3"
url = { version = "2.5", default-features = false, features = ["serde"] }
uuid = { version = "1.14", default-features = false, features = ["v4", "fast-rng", "serde"] }
[profile.dev]
debug = "line-tables-only"
[profile.release]
lto = true
codegen-units = 1
panic = 'abort'
strip = true
# debug = true
# split-debuginfo = 'packed'
# strip = "none"
# panic = 'unwind'
opt-level = 3
trim-paths = "all"
rustflags = ["-Cdebuginfo=0", "-Zthreads=8"]
uuid = { version = "1.14", default-features = false, features = [
"v4",
"fast-rng",
"serde",
] }
zip = { version = "7", default-features = false, features = [
"deflate",
"bzip2",
"zstd",
"deflate64",
"lzma",
"xz",
] }
indexmap = { version = "2", default-features = false, features = ["serde"] }
itoa = "1.0"
[features]
default = ["webpki-roots"]
default = ["webpki-roots", "horizon"]
webpki-roots = ["reqwest/rustls-tls-webpki-roots"]
native-roots = ["reqwest/rustls-tls-native-roots"]
use-minified = []
__preview = []
__preview_locked = ["__preview"]
__protoc = ["prost-build"]
__compat = []
[patch.crates-io]
h2 = { path = "patch/h2-0.4.10" }
reqwest = { path = "patch/reqwest-0.12.18" }
rustls = { path = "patch/rustls-0.23.28" }
chrono = { path = "patch/chrono-0.4.41" }
ulid = { path = "patch/ulid-1.2.1" }
dotenvy = { path = "patch/dotenvy-0.15.7" }
# bs58 = { path = "patch/bs58-0.5.1" }
# base62 = { path = "patch/base62-2.2.1" }
horizon = ["nightly"]
nightly = ["hashbrown/nightly", "parking_lot/nightly"]

View File

@@ -7,7 +7,7 @@ WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends gcc nodejs npm musl-tools && rm -rf /var/lib/apt/lists/* && case "$TARGETARCH" in amd64) rustup target add x86_64-unknown-linux-musl ;; arm64) rustup target add aarch64-unknown-linux-musl ;; *) echo "Unsupported architecture for rustup: $TARGETARCH" && exit 1 ;; esac
COPY . .
RUN case "$TARGETARCH" in amd64) TARGET_TRIPLE="x86_64-unknown-linux-musl"; TARGET_CPU="x86-64-v3" ;; arm64) TARGET_TRIPLE="aarch64-unknown-linux-musl"; TARGET_CPU="neoverse-n1" ;; *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; esac && RUSTFLAGS="-C link-arg=-s -C target-feature=+crt-static -C target-cpu=$TARGET_CPU -A unused" cargo build --bin cursor-api --release --target=$TARGET_TRIPLE && mkdir /app && cp target/$TARGET_TRIPLE/release/cursor-api /app/
RUN case "$TARGETARCH" in amd64) TARGET_TRIPLE="x86_64-unknown-linux-musl"; TARGET_CPU="x86-64-v2" ;; arm64) TARGET_TRIPLE="aarch64-unknown-linux-musl"; TARGET_CPU="generic" ;; *) echo "Unsupported architecture: $TARGETARCH" && exit 1 ;; esac && RUSTFLAGS="-C link-arg=-s -C target-feature=+crt-static -C target-cpu=$TARGET_CPU -A unused" cargo build --bin cursor-api --release --target=$TARGET_TRIPLE && mkdir /app && cp target/$TARGET_TRIPLE/release/cursor-api /app/
# 运行阶段
FROM scratch

View File

@@ -1,6 +0,0 @@
This project is licensed under either of
* Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.

View File

@@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -1,7 +0,0 @@
Copyright 2025 wisdgod <nav@wisdgod.com>
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.

20
LICENSE.md Normal file
View File

@@ -0,0 +1,20 @@
This project is licensed under either of
* Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
* MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
### Additional Terms and Restrictions
The following additional terms apply to all forks and derivative works:
1. **Attribution Restrictions**: You may NOT use the name(s) of the original author(s), maintainer(s), or contributor(s) of this project for any promotional, marketing, or advertising purposes in connection with your fork or derivative work.
2. **No Implied Endorsement**: Forks and derivative works must not imply or suggest that they are endorsed by, affiliated with, or approved by the original author(s) or maintainer(s) of this project.
3. **Low-Profile Usage Preferred**: While not legally binding, users are encouraged to use this project and its derivatives in a low-profile manner, respecting the original author's preference for discretion.
4. **Clear Distinction Required**: Any fork or derivative work must clearly indicate that it is a modified version and not the original project.
These additional terms supplement and do not replace the chosen license (Apache 2.0 or MIT). By using, modifying, or distributing this project, you agree to comply with both the chosen license and these additional terms.

1687
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
89
18

178
build.rs
View File

@@ -1,8 +1,8 @@
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
use sha2::{Digest, Sha256};
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
use std::collections::HashMap;
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
use std::fs;
#[cfg(not(debug_assertions))]
#[cfg(feature = "__preview")]
@@ -11,28 +11,29 @@ use std::io::Result;
#[cfg(not(debug_assertions))]
#[cfg(feature = "__preview")]
use std::io::{Read, Write};
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
use std::path::Path;
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
use std::path::PathBuf;
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
use std::process::Command;
// 支持的文件类型
#[cfg(not(any(feature = "use-minified")))]
const SUPPORTED_EXTENSIONS: [&str; 4] = ["html", "js", "css", "md"];
// #[cfg(not(feature = "use-minified"))]
// const SUPPORTED_EXTENSIONS: [&str; 4] = ["html", "js", "css", "md"];
#[cfg(not(any(feature = "use-minified")))]
// 需要处理的 Markdown 文件列表
#[cfg(not(feature = "use-minified"))]
const MARKDOWN_FILES: [&str; 2] = ["README.md", "LICENSE.md"];
#[cfg(not(feature = "use-minified"))]
fn check_and_install_deps() -> Result<()> {
let scripts_dir = Path::new("scripts");
let node_modules = scripts_dir.join("node_modules");
if !node_modules.exists() {
println!("cargo:warning=Installing minifier dependencies...");
let status = Command::new("npm")
.current_dir(scripts_dir)
.arg("install")
.status()?;
let status = Command::new("npm").current_dir(scripts_dir).arg("install").status()?;
if !status.success() {
panic!("Failed to install npm dependencies");
@@ -42,67 +43,100 @@ fn check_and_install_deps() -> Result<()> {
Ok(())
}
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
fn get_files_hash() -> Result<HashMap<PathBuf, String>> {
let mut file_hashes = HashMap::new();
let static_dir = Path::new("static");
// let static_dir = Path::new("static");
// 首先处理 README.md
let readme_path = Path::new("README.md");
if readme_path.exists() {
let content = fs::read(readme_path)?;
let hash = format!("{:x}", Sha256::new().chain_update(&content).finalize());
file_hashes.insert(readme_path.to_path_buf(), hash);
pub const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
#[inline]
pub fn to_str<'buf>(bytes: &[u8], buf: &'buf mut [u8]) -> &'buf mut str {
for (i, &byte) in bytes.iter().enumerate() {
buf[i * 2] = HEX_CHARS[(byte >> 4) as usize];
buf[i * 2 + 1] = HEX_CHARS[(byte & 0x0f) as usize];
}
// SAFETY: 输出都是有效的 ASCII 字符
unsafe { core::str::from_utf8_unchecked_mut(buf) }
}
if static_dir.exists() {
for entry in fs::read_dir(static_dir)? {
let entry = entry?;
let path = entry.path();
// 检查是否是支持的文件类型,且不是已经压缩的文件
if let Some(ext) = path.extension().and_then(|e| e.to_str())
&& SUPPORTED_EXTENSIONS.contains(&ext)
&& !path.to_string_lossy().contains(".min.")
{
let content = fs::read(&path)?;
let hash = format!("{:x}", Sha256::new().chain_update(&content).finalize());
file_hashes.insert(path, hash);
}
// 处理根目录的 Markdown 文件
for md_file in MARKDOWN_FILES {
let md_path = Path::new(md_file);
if md_path.exists() {
let content = fs::read(md_path)?;
#[allow(deprecated)]
let hash =
to_str(Sha256::new().chain_update(&content).finalize().as_slice(), &mut [0; 64])
.to_string();
file_hashes.insert(md_path.to_path_buf(), hash);
}
}
// 处理 static 目录中的文件
// if static_dir.exists() {
// for entry in fs::read_dir(static_dir)? {
// let entry = entry?;
// let path = entry.path();
// // 检查是否是支持的文件类型,且不是已经压缩的文件
// if let Some(ext) = path.extension().and_then(|e| e.to_str())
// && SUPPORTED_EXTENSIONS.contains(&ext)
// && !path.to_string_lossy().contains(".min.")
// {
// let content = fs::read(&path)?;
// #[allow(deprecated)]
// let hash =
// to_str(Sha256::new().chain_update(&content).finalize().as_slice(), &mut [0; 64])
// .to_string();
// file_hashes.insert(path, hash);
// }
// }
// }
Ok(file_hashes)
}
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
fn load_saved_hashes() -> Result<HashMap<PathBuf, String>> {
let hash_file = Path::new("scripts/.asset-hashes.json");
if hash_file.exists() {
let content = fs::read_to_string(hash_file)?;
let hash_map: HashMap<String, String> = serde_json::from_str(&content)?;
Ok(hash_map
.into_iter()
.map(|(k, v)| (PathBuf::from(k), v))
.collect())
Ok(hash_map.into_iter().map(|(k, v)| (PathBuf::from(k), v)).collect())
} else {
Ok(HashMap::new())
}
}
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
fn save_hashes(hashes: &HashMap<PathBuf, String>) -> Result<()> {
let hash_file = Path::new("scripts/.asset-hashes.json");
let string_map: HashMap<String, String> = hashes
.iter()
.map(|(k, v)| (k.to_string_lossy().into_owned(), v.clone()))
.collect();
let string_map: HashMap<String, String> =
hashes.iter().map(|(k, v)| (k.to_string_lossy().into_owned(), v.clone())).collect();
let content = serde_json::to_string_pretty(&string_map)?;
fs::write(hash_file, content)?;
Ok(())
}
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
fn get_minified_output_path(path: &Path) -> PathBuf {
let file_name = path.file_name().and_then(|f| f.to_str()).unwrap_or("");
// 检查是否是根目录的 Markdown 文件
if MARKDOWN_FILES.contains(&file_name) {
// 将文件名转换为小写并生成对应的 .min.html 文件
let base_name = path.file_stem().unwrap().to_string_lossy().to_lowercase();
PathBuf::from(format!("static/{}.min.html", base_name))
} else {
// 其他文件保持原有逻辑
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
path.with_file_name(format!("{}.min.{}", path.file_stem().unwrap().to_string_lossy(), ext))
}
}
#[cfg(not(feature = "use-minified"))]
fn minify_assets() -> Result<()> {
// 获取现有文件的哈希
let current_hashes = get_files_hash()?;
@@ -119,19 +153,8 @@ fn minify_assets() -> Result<()> {
let files_to_update: Vec<_> = current_hashes
.iter()
.filter(|(path, current_hash)| {
let is_readme = path.file_name().is_some_and(|f| f == "README.md");
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or("");
// 为 README.md 和其他文件使用不同的输出路径检查
let min_path = if is_readme {
PathBuf::from("static/readme.min.html")
} else {
path.with_file_name(format!(
"{}.min.{}",
path.file_stem().unwrap().to_string_lossy(),
ext
))
};
// 获取压缩后的输出路径
let min_path = get_minified_output_path(path);
// 检查压缩/转换后的文件是否存在
if !min_path.exists() {
@@ -150,13 +173,10 @@ fn minify_assets() -> Result<()> {
}
println!("cargo:warning=Minifying {} files...", files_to_update.len());
println!("cargo:warning={}", files_to_update.join(" "));
println!("cargo:warning=Files: {}", files_to_update.join(" "));
// 运行压缩脚本
let status = Command::new("node")
.arg("scripts/minify.js")
.args(&files_to_update)
.status()?;
let status = Command::new("node").arg("scripts/minify.js").args(&files_to_update).status()?;
if !status.success() {
panic!("Asset minification failed");
@@ -291,35 +311,31 @@ fn main() -> Result<()> {
}
config
.compile_protos(
&["src/core/aiserver/v1/lite.proto"],
&["src/core/aiserver/v1/"],
)
.unwrap();
config
.compile_protos(&["src/core/config/key.proto"], &["src/core/config/"])
.compile_protos(&["src/core/aiserver/v1/lite.proto"], &["src/core/aiserver/v1/"])
.unwrap();
// config.compile_protos(&["src/core/config/key.proto"], &["src/core/config/"]).unwrap();
}
// 静态资源文件处理
println!("cargo:rerun-if-changed=scripts/minify.js");
println!("cargo:rerun-if-changed=scripts/package.json");
println!("cargo:rerun-if-changed=static/api.html");
println!("cargo:rerun-if-changed=static/build_key.html");
println!("cargo:rerun-if-changed=static/config.html");
println!("cargo:rerun-if-changed=static/logs.html");
println!("cargo:rerun-if-changed=static/proxies.html");
println!("cargo:rerun-if-changed=static/shared-styles.css");
println!("cargo:rerun-if-changed=static/shared.js");
println!("cargo:rerun-if-changed=static/tokens.html");
// println!("cargo:rerun-if-changed=scripts/package.json");
// println!("cargo:rerun-if-changed=static/api.html");
// println!("cargo:rerun-if-changed=static/build_key.html");
// println!("cargo:rerun-if-changed=static/config.html");
// println!("cargo:rerun-if-changed=static/logs.html");
// println!("cargo:rerun-if-changed=static/proxies.html");
// println!("cargo:rerun-if-changed=static/shared-styles.css");
// println!("cargo:rerun-if-changed=static/shared.js");
// println!("cargo:rerun-if-changed=static/tokens.html");
println!("cargo:rerun-if-changed=README.md");
println!("cargo:rerun-if-changed=LICENSE.md");
// 只在release模式下监控VERSION文件变化
#[cfg(not(debug_assertions))]
#[cfg(feature = "__preview")]
println!("cargo:rerun-if-changed=VERSION");
#[cfg(not(any(feature = "use-minified")))]
#[cfg(not(feature = "use-minified"))]
{
// 检查并安装依赖
check_and_install_deps()?;

View File

@@ -1,3 +1,109 @@
include!("src/app/model/version.rs");
/// 版本字符串解析错误
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParseError {
/// 整体格式错误(如缺少必需部分)
InvalidFormat,
/// 数字解析失败
InvalidNumber,
/// pre 部分格式错误
InvalidPreRelease,
/// build 部分格式错误
InvalidBuild,
// /// 正式版不能带 build 标识
// BuildWithoutPreview,
}
impl core::fmt::Display for ParseError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
ParseError::InvalidFormat => write!(f, "invalid version format"),
ParseError::InvalidNumber => write!(f, "invalid number in version"),
ParseError::InvalidPreRelease => write!(f, "invalid pre-release format"),
ParseError::InvalidBuild => write!(f, "invalid build format"),
// ParseError::BuildWithoutPreview => {
// write!(f, "build metadata cannot exist without pre-release version")
// }
}
}
}
impl std::error::Error for ParseError {}
impl core::str::FromStr for Version {
type Err = ParseError;
fn from_str(s: &str) -> core::result::Result<Self, Self::Err> {
// 按 '-' 分割基础版本号和扩展部分
let (base, extension) = match s.split_once('-') {
Some((base, ext)) => (base, Some(ext)),
None => (s, None),
};
// 解析基础版本号 major.minor.patch
let mut parts: [u16; 3] = [0, 0, 0];
let mut parsed_count = 0;
for (i, s) in base.split('.').enumerate() {
if i >= parts.len() {
return Err(ParseError::InvalidFormat);
}
parts[i] = s.parse().map_err(|_| ParseError::InvalidNumber)?;
parsed_count += 1;
}
if parsed_count != 3 {
return Err(ParseError::InvalidFormat);
}
let major = parts[0];
let minor = parts[1];
let patch = parts[2];
// 解析扩展部分(如果存在)
let stage =
if let Some(ext) = extension { parse_extension(ext)? } else { ReleaseStage::Release };
Ok(Version { major, minor, patch, stage })
}
}
/// 解析扩展部分pre.X 或 pre.X+build.Y
fn parse_extension(s: &str) -> core::result::Result<ReleaseStage, ParseError> {
// 检查是否以 "pre." 开头
if !s.starts_with("pre.") {
return Err(ParseError::InvalidPreRelease);
}
// 移除 "pre." 前缀
let after_pre = &s[4..];
// 按 '+' 分割 version 和 build 部分
let (version_str, build_str) = match after_pre.split_once('+') {
Some((ver, build_part)) => (ver, Some(build_part)),
None => (after_pre, None),
};
// 解析 pre 版本号
let version = version_str.parse().map_err(|_| ParseError::InvalidPreRelease)?;
// 解析 build 号(如果存在)
let build = if let Some(build_part) = build_str {
// 检查格式是否为 "build.X"
if !build_part.starts_with("build.") {
return Err(ParseError::InvalidBuild);
}
let build_num_str = &build_part[6..];
let build_num = build_num_str.parse().map_err(|_| ParseError::InvalidBuild)?;
Some(build_num)
} else {
None
};
Ok(ReleaseStage::Preview { version, build })
}
/**
* 更新版本号函数
* 此函数会读取 VERSION 文件中的数字将其加1然后保存回文件
@@ -26,6 +132,7 @@ fn update_version() -> Result<()> {
file.read_to_string(&mut version)?;
// 确保版本号是有效数字
#[allow(unused_variables)]
let version_num = match version.trim().parse::<u64>() {
Ok(num) => num,
Err(_) => {
@@ -36,20 +143,23 @@ fn update_version() -> Result<()> {
}
};
// 版本号加1
let new_version = version_num + 1;
println!(
"cargo:warning=Release build - bumping version from {version_num} to {new_version}",
);
#[cfg(not(feature = "__preview_locked"))]
{
// 版本号加1
let new_version = version_num + 1;
println!(
"cargo:warning=Release build - bumping version from {version_num} to {new_version}",
);
// 写回文件
let mut file = File::create(version_path)?;
file.write_all(new_version.to_string().as_bytes())?;
// 写回文件
let mut file = File::create(version_path)?;
write!(file, "{new_version}")?;
}
Ok(())
}
#[cfg(feature = "__preview")]
#[allow(unused)]
fn read_version_number() -> Result<u64> {
let mut version = String::with_capacity(4);
match std::fs::File::open("VERSION") {
@@ -63,21 +173,19 @@ fn read_version_number() -> Result<u64> {
}
fn generate_build_info() -> Result<()> {
// let out_dir = std::env::var("OUT_DIR").unwrap();
// let dest_path = Path::new(out_dir).join("build_info.rs");
#[cfg(debug_assertions)]
let out_dir = "target/debug/build/build_info.rs";
#[cfg(not(debug_assertions))]
let out_dir = "target/release/build/build_info.rs";
let dest_path = Path::new(out_dir);
let out_dir = std::env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("build_info.rs");
// #[cfg(debug_assertions)]
// let out_dir = "../target/debug/build/build_info.rs";
// #[cfg(not(debug_assertions))]
// let out_dir = "../target/release/build/build_info.rs";
// let dest_path = Path::new(out_dir);
// if dest_path.is_file() {
// return Ok(());
// }
let build_timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let build_timestamp =
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
let build_timestamp_str = chrono::DateTime::from_timestamp(build_timestamp as i64, 0)
.unwrap()
@@ -85,56 +193,43 @@ fn generate_build_info() -> Result<()> {
let pkg_version = env!("CARGO_PKG_VERSION");
#[cfg(feature = "__preview")]
let (version_str, build_version_str) = {
let build_num = read_version_number()?;
(
format!("{pkg_version}+build.{build_num}"),
format!("pub const BUILD_VERSION: u32 = {build_num};\n"),
)
};
let (version_str, build_version_str) =
if cfg!(feature = "__preview") && pkg_version.contains("-pre") {
let build_num = read_version_number()?;
(
format!("{pkg_version}+build.{build_num}"),
format!("pub const BUILD_VERSION: u32 = {build_num};\n"),
)
} else {
(pkg_version.to_string(), String::new())
};
#[cfg(not(feature = "__preview"))]
let (version_str, build_version_str) = (pkg_version, "");
let version: Version = version_str.parse().unwrap();
let build_info_content = format!(
r#"// 此文件由 build.rs 自动生成,请勿手动修改
use crate::app::model::version::{{Version, ReleaseStage::Preview}};
{build_version_str}pub const BUILD_TIMESTAMP: &'static str = {build_timestamp_str:?};
pub const VERSION: &'static str = {version_str:?};
/// pub const VERSION_STR: &'static str = {version_str:?};
pub const VERSION: Version = {version:?};
pub const IS_PRERELEASE: bool = {is_prerelease};
pub const IS_DEBUG: bool = {is_debug};
#[cfg(unix)]
pub const BUILD_EPOCH: std::time::SystemTime = unsafe {{
#[allow(dead_code)]
struct UnixSystemTime {{
tv_sec: i64,
tv_nsec: u32,
}}
::core::mem::transmute(UnixSystemTime {{
tv_sec: {build_timestamp},
tv_nsec: 0,
}})
}};
pub const BUILD_EPOCH: std::time::SystemTime =
unsafe {{ ::core::intrinsics::transmute(({build_timestamp}i64, 0u32)) }};
#[cfg(windows)]
pub const BUILD_EPOCH: std::time::SystemTime = unsafe {{
#[allow(dead_code)]
struct WindowsFileTime {{
dw_low_date_time: u32,
dw_high_date_time: u32,
}}
const INTERVALS_PER_SEC: u64 = 10_000_000;
const INTERVALS_TO_UNIX_EPOCH: u64 = 11_644_473_600 * INTERVALS_PER_SEC;
const TARGET_INTERVALS: u64 = INTERVALS_TO_UNIX_EPOCH + {build_timestamp} * INTERVALS_PER_SEC;
::core::mem::transmute(WindowsFileTime {{
dw_low_date_time: TARGET_INTERVALS as u32,
dw_high_date_time: (TARGET_INTERVALS >> 32) as u32,
}})
::core::intrinsics::transmute((
TARGET_INTERVALS as u32,
(TARGET_INTERVALS >> 32) as u32,
))
}};
"#,
is_prerelease = cfg!(feature = "__preview"),

View File

@@ -0,0 +1,13 @@
[package]
name = "grpc-stream"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
description.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
bytes = "1"
flate2 = "1"
prost = "0.14"

View File

@@ -0,0 +1,195 @@
//! 内部缓冲区管理
use core::iter::FusedIterator;
use bytes::{Buf as _, BytesMut};
use crate::frame::RawMessage;
/// 消息缓冲区(内部使用)
pub struct Buffer {
inner: BytesMut,
}
impl Buffer {
#[inline]
pub fn new() -> Self { Self { inner: BytesMut::new() } }
#[inline]
pub fn with_capacity(capacity: usize) -> Self {
Self { inner: BytesMut::with_capacity(capacity) }
}
#[inline]
pub fn len(&self) -> usize { self.inner.len() }
#[inline]
pub fn is_empty(&self) -> bool { self.inner.is_empty() }
#[inline]
pub fn extend_from_slice(&mut self, data: &[u8]) { self.inner.extend_from_slice(data) }
#[inline]
pub fn advance(&mut self, cnt: usize) { self.inner.advance(cnt) }
}
impl Default for Buffer {
#[inline]
fn default() -> Self { Self::new() }
}
impl AsRef<[u8]> for Buffer {
#[inline]
fn as_ref(&self) -> &[u8] { self.inner.as_ref() }
}
/// 消息迭代器(内部使用)
#[derive(Debug, Clone)]
pub struct MessageIter<'b> {
buffer: &'b [u8],
offset: usize,
}
impl<'b> MessageIter<'b> {
/// 返回当前已消耗的字节数
#[inline]
pub fn offset(&self) -> usize { self.offset }
}
impl<'b> Iterator for MessageIter<'b> {
type Item = RawMessage<'b>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
// 至少需要 5 字节1 字节 type + 4 字节 length
if self.offset + 5 > self.buffer.len() {
return None;
}
let r#type = unsafe {
let ptr: *const u8 =
::core::intrinsics::slice_get_unchecked(self.buffer as *const [u8], self.offset);
*ptr
};
let msg_len = u32::from_be_bytes(unsafe {
*get_offset_len_noubcheck(self.buffer, self.offset + 1, 4).cast()
}) as usize;
// 检查消息是否完整
if self.offset + 5 + msg_len > self.buffer.len() {
return None;
}
self.offset += 5;
let data = unsafe { &*get_offset_len_noubcheck(self.buffer, self.offset, msg_len) };
self.offset += msg_len;
Some(RawMessage { r#type, data })
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
// 精确计算剩余完整消息数量
let mut count = 0;
let mut offset = self.offset;
while offset + 5 <= self.buffer.len() {
let msg_len = u32::from_be_bytes(unsafe {
*get_offset_len_noubcheck(self.buffer, offset + 1, 4).cast()
}) as usize;
if offset + 5 + msg_len > self.buffer.len() {
break;
}
count += 1;
offset += 5 + msg_len;
}
(count, Some(count)) // 精确值
}
}
// 实现 ExactSizeIterator
impl<'b> ExactSizeIterator for MessageIter<'b> {
#[inline]
fn len(&self) -> usize {
// size_hint() 已经返回精确值,直接使用
self.size_hint().0
}
}
// 实现 FusedIterator
impl<'b> FusedIterator for MessageIter<'b> {}
impl<'b> IntoIterator for &'b Buffer {
type Item = RawMessage<'b>;
type IntoIter = MessageIter<'b>;
#[inline]
fn into_iter(self) -> Self::IntoIter { MessageIter { buffer: self.inner.as_ref(), offset: 0 } }
}
#[inline(always)]
const unsafe fn get_offset_len_noubcheck<T>(
ptr: *const [T],
offset: usize,
len: usize,
) -> *const [T] {
let ptr = ptr as *const T;
// SAFETY: The caller already checked these preconditions
let ptr = unsafe { ::core::intrinsics::offset(ptr, offset) };
::core::intrinsics::aggregate_raw_ptr(ptr, len)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_exact_size_iterator() {
let mut buffer = Buffer::new();
// 构造两个消息type=0, len=3, data="abc"
buffer.extend_from_slice(&[0, 0, 0, 0, 3, b'a', b'b', b'c']);
buffer.extend_from_slice(&[0, 0, 0, 0, 2, b'x', b'y']);
let iter = (&buffer).into_iter();
// 验证 ExactSizeIterator
assert_eq!(iter.len(), 2);
assert_eq!(iter.size_hint(), (2, Some(2)));
let messages: Vec<_> = iter.collect();
assert_eq!(messages.len(), 2);
}
#[test]
fn test_fused_iterator() {
let buffer = Buffer::new(); // 空缓冲区
let mut iter = (&buffer).into_iter();
// 验证 FusedIterator
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None); // 仍然是 None
assert_eq!(iter.next(), None); // 永远是 None
}
#[test]
fn test_clone_iterator() {
let mut buffer = Buffer::new();
buffer.extend_from_slice(&[0, 0, 0, 0, 3, b'a', b'b', b'c']);
let iter = (&buffer).into_iter();
let iter_clone = iter.clone();
// 消耗原迭代器
assert_eq!(iter.count(), 1);
// 副本仍然可用
assert_eq!(iter_clone.count(), 1);
}
}

View File

@@ -0,0 +1,154 @@
//! 压缩数据处理
use std::io::Read as _;
use flate2::read::GzDecoder;
use crate::MAX_DECOMPRESSED_SIZE_BYTES;
/// 解压 gzip 数据
///
/// # 参数
/// - `data`: gzip 压缩的数据
///
/// # 返回
/// - `Some(Vec<u8>)`: 解压成功
/// - `None`: 不是有效的 gzip 数据或解压失败
///
/// # 最小 GZIP 文件结构
///
/// ```text
/// +----------+-------------+----------+
/// | Header | DEFLATE | Footer |
/// | 10 bytes | 2+ bytes | 8 bytes |
/// +----------+-------------+----------+
/// 最小: 10 + 2 + 8 = 20 字节
/// ```
///
/// # 安全性
/// - 限制解压后大小不超过 `MAX_DECOMPRESSED_SIZE_BYTES`
/// - 防止 gzip 炸弹攻击
pub fn decompress_gzip(data: &[u8]) -> Option<Vec<u8>> {
// 快速路径:拒绝明显无效的数据
// 最小有效 gzip 文件为 20 字节头10 + 数据2 + 尾8
if data.len() < 20 {
return None;
}
// SAFETY: 上面已验证 data.len() >= 20保证索引 0, 1, 2 有效
// 检查 gzip 魔数0x1f 0x8b和压缩方法0x08 = DEFLATE
if unsafe {
*data.get_unchecked(0) != 0x1f
|| *data.get_unchecked(1) != 0x8b
|| *data.get_unchecked(2) != 0x08
} {
return None;
}
// 读取 gzip footer 中的 ISIZE原始大小最后 4 字节,小端序)
// SAFETY: 已验证 data.len() >= 20末尾 4 字节必然有效
let capacity = unsafe {
let ptr = data.as_ptr().add(data.len() - 4) as *const [u8; 4];
u32::from_le_bytes(ptr.read()) as usize
};
// 防止解压炸弹攻击
if capacity > MAX_DECOMPRESSED_SIZE_BYTES {
return None;
}
// 执行实际解压
let mut decoder = GzDecoder::new(data);
let mut decompressed = Vec::with_capacity(capacity);
decoder.read_to_end(&mut decompressed).ok()?;
Some(decompressed)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_too_short() {
// 小于 20 字节的数据应该直接拒绝
assert!(decompress_gzip(&[]).is_none());
assert!(decompress_gzip(&[0x1f, 0x8b, 0x08]).is_none());
assert!(decompress_gzip(&[0u8; 19]).is_none());
}
#[test]
fn test_invalid_magic() {
// 长度足够但魔数错误
let mut data = vec![0u8; 20];
data[0] = 0x00; // 错误的魔数
data[1] = 0x8b;
data[2] = 0x08;
assert!(decompress_gzip(&data).is_none());
// 正确的第一字节,错误的第二字节
data[0] = 0x1f;
data[1] = 0x00;
assert!(decompress_gzip(&data).is_none());
// 前两字节正确,压缩方法错误
data[1] = 0x8b;
data[2] = 0x09; // 非 DEFLATE
assert!(decompress_gzip(&data).is_none());
}
#[test]
fn test_gzip_bomb_protection() {
// 构造声称解压后为 2MB 的假 gzip 数据
let mut fake_gzip = vec![0x1f, 0x8b, 0x08]; // 正确的魔数
fake_gzip.extend_from_slice(&[0u8; 14]); // 填充到 17 字节
// ISIZE 字段(最后 4 字节2MB
let size_2mb = 2 * 1024 * 1024u32;
fake_gzip.extend_from_slice(&size_2mb.to_le_bytes());
assert_eq!(fake_gzip.len(), 21); // 17 + 4
assert!(decompress_gzip(&fake_gzip).is_none());
}
#[test]
fn test_valid_gzip() {
// 使用标准库压缩一些数据
use std::io::Write;
use flate2::write::GzEncoder;
use flate2::Compression;
let original = b"Hello, GZIP!";
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(original).unwrap();
let compressed = encoder.finish().unwrap();
// 验证:压缩数据 >= 20 字节
assert!(compressed.len() >= 20);
// 解压并验证
let decompressed = decompress_gzip(&compressed).unwrap();
assert_eq!(&decompressed, original);
}
#[test]
fn test_empty_gzip() {
// 压缩空数据(最小有效 gzip
use std::io::Write;
use flate2::write::GzEncoder;
use flate2::Compression;
let mut encoder = GzEncoder::new(Vec::new(), Compression::default());
encoder.write_all(&[]).unwrap();
let compressed = encoder.finish().unwrap();
// 验证:最小 gzip 文件 ~20 字节
assert!(compressed.len() >= 20);
let decompressed = decompress_gzip(&compressed).unwrap();
assert_eq!(decompressed.len(), 0);
}
}

View File

@@ -0,0 +1,135 @@
//! 流式消息解码器
use prost::Message;
use crate::buffer::Buffer;
use crate::compression::decompress_gzip;
use crate::frame::RawMessage;
/// gRPC 流式消息解码器
///
/// 处理增量数据块,解析完整的 Protobuf 消息。
///
/// # 示例
///
/// ```no_run
/// use grpc_stream_decoder::StreamDecoder;
/// use prost::Message;
///
/// #[derive(Message, Default)]
/// struct MyMessage {
/// #[prost(string, tag = "1")]
/// content: String,
/// }
///
/// let mut decoder = StreamDecoder::new();
///
/// // 使用默认处理器
/// loop {
/// let chunk = receive_network_data();
/// let messages: Vec<MyMessage> = decoder.decode_default(&chunk);
///
/// for msg in messages {
/// process(msg);
/// }
/// }
///
/// // 使用自定义处理器
/// let messages = decoder.decode(&chunk, |raw_msg| {
/// // 自定义解码逻辑
/// match raw_msg.r#type {
/// 0 => MyMessage::decode(raw_msg.data).ok(),
/// _ => None,
/// }
/// });
/// ```
pub struct StreamDecoder {
buffer: Buffer,
}
impl StreamDecoder {
/// 创建新的解码器
#[inline]
pub fn new() -> Self { Self { buffer: Buffer::new() } }
/// 使用自定义处理器解码数据块
///
/// # 类型参数
/// - `T`: 目标消息类型
/// - `F`: 处理函数,签名为 `Fn(RawMessage<'_>) -> Option<T>`
///
/// # 参数
/// - `data`: 接收到的数据块
/// - `processor`: 自定义处理函数,接收原始消息并返回解码结果
///
/// # 返回
/// 解码成功的消息列表
///
/// # 示例
///
/// ```no_run
/// // 自定义处理:只接受未压缩消息
/// let messages = decoder.decode(&data, |raw_msg| {
/// if raw_msg.r#type == 0 {
/// MyMessage::decode(raw_msg.data).ok()
/// } else {
/// None
/// }
/// });
/// ```
pub fn decode<T, F>(&mut self, data: &[u8], processor: F) -> Vec<T>
where F: Fn(RawMessage<'_>) -> Option<T> {
self.buffer.extend_from_slice(data);
let mut iter = (&self.buffer).into_iter();
let exact_count = iter.len();
let mut messages = Vec::with_capacity(exact_count);
for raw_msg in &mut iter {
if let Some(msg) = processor(raw_msg) {
messages.push(msg);
}
}
self.buffer.advance(iter.offset());
messages
}
/// 使用默认处理器解码数据块
///
/// 默认行为:
/// - 类型 0直接解码 Protobuf 消息
/// - 类型 1先 gzip 解压,再解码
/// - 其他类型:忽略
///
/// # 类型参数
/// - `T`: 实现 `prost::Message + Default` 的消息类型
///
/// # 参数
/// - `data`: 接收到的数据块
///
/// # 返回
/// 解码成功的消息列表
pub fn decode_default<T: Message + Default>(&mut self, data: &[u8]) -> Vec<T> {
self.decode(data, |raw_msg| match raw_msg.r#type {
0 => Self::decode_message(raw_msg.data),
1 => Self::decode_compressed_message(raw_msg.data),
_ => None,
})
}
/// 解码未压缩消息
#[inline]
fn decode_message<T: Message + Default>(data: &[u8]) -> Option<T> { T::decode(data).ok() }
/// 解码 gzip 压缩消息
#[inline]
fn decode_compressed_message<T: Message + Default>(data: &[u8]) -> Option<T> {
let decompressed = decompress_gzip(data)?;
Self::decode_message(&decompressed)
}
}
impl Default for StreamDecoder {
fn default() -> Self { Self::new() }
}

View File

@@ -0,0 +1,54 @@
//! 原始消息帧定义
/// gRPC 流式消息的原始帧
///
/// 包含帧头信息和消息数据的引用。
///
/// # 帧格式
///
/// ```text
/// +------+----------+----------------+
/// | type | length | data |
/// | 1B | 4B (BE) | length bytes |
/// +------+----------+----------------+
/// ```
///
/// - `type`: 消息类型
/// - `0`: 未压缩
/// - `1`: gzip 压缩
/// - `length`: 消息体长度(大端序)
/// - `data`: 消息体数据
///
/// # 字段说明
///
/// - `r#type`: 帧类型标志0=未压缩, 1=gzip
/// - `data`: 消息体数据切片,其长度可通过 `data.len()` 获取
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RawMessage<'b> {
/// 消息类型0=未压缩, 1=gzip
pub r#type: u8,
/// 消息体数据
pub data: &'b [u8],
}
impl RawMessage<'_> {
/// 计算该消息在缓冲区中占用的总字节数
///
/// 包含 5 字节帧头 + 消息体长度
///
/// # 示例
///
/// ```
/// # use grpc_stream_decoder::RawMessage;
/// let msg = RawMessage {
/// r#type: 0,
/// data: &[1, 2, 3],
/// };
/// assert_eq!(msg.total_size(), 8); // 5 + 3
/// ```
#[inline]
pub const fn total_size(&self) -> usize {
5 + self.data.len()
}
}

View File

@@ -0,0 +1,46 @@
//! gRPC 流式消息解码器
//!
//! 提供高性能的 gRPC streaming 消息解析,支持 gzip 压缩。
//!
//! # 示例
//!
//! ```no_run
//! use grpc_stream_decoder::StreamDecoder;
//! use prost::Message;
//!
//! #[derive(Message, Default)]
//! struct MyMessage {
//! #[prost(string, tag = "1")]
//! content: String,
//! }
//!
//! let mut decoder = StreamDecoder::<MyMessage>::new();
//!
//! // 接收到的数据块
//! let chunk = receive_data();
//! let messages = decoder.decode(&chunk);
//!
//! for msg in messages {
//! println!("{}", msg.content);
//! }
//! ```
#![allow(internal_features)]
#![feature(core_intrinsics)]
mod frame;
mod buffer;
mod compression;
mod decoder;
// 公开 API
pub use frame::RawMessage;
pub use buffer::Buffer;
pub use compression::decompress_gzip;
pub use decoder::StreamDecoder;
// 常量
/// 最大解压缩消息大小限制4 MiB
///
/// 对齐gRPC标准的默认最大消息大小防止内存滥用攻击
pub const MAX_DECOMPRESSED_SIZE_BYTES: usize = 0x400000; // 4 * 1024 * 1024

View File

@@ -0,0 +1,54 @@
[package]
name = "interned"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
description.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
# HashMap 实现 - 启用 nightly 优化
hashbrown = { version = "0.16", default-features = false, features = [
"nightly", # 🚀 SIMD 优化、unstable APIs
"raw-entry", # 用于高级 HashMap 操作
"inline-more", # 更激进的内联优化
#"allocator-api2", # 自定义分配器支持
] }
# RwLock 实现 - 启用性能优化
parking_lot = { version = "0.12", features = [
"nightly", # 🚀 unstable 优化
"hardware-lock-elision", # Intel TSX 硬件锁优化(如果可用)
#"send_guard", # 允许跨线程传递 MutexGuard
] }
# 哈希算法 - 启用硬件加速
ahash = { version = "0.8", default-features = false, features = [
"runtime-rng", # 运行时随机种子(安全)
#"nightly-arm-aes", # 🚀 ARM AES 指令优化
] }
manually_init.workspace = true
serde = { version = "1.0", optional = true }
[features]
default = ["serde"]
nightly = []
serde = ["dep:serde", "hashbrown/serde", "ahash/serde"]
#[profile.release]
#opt-level = 3
#lto = "fat" # 全局 LTO
#codegen-units = 1 # 单编译单元,最大优化
#panic = "abort" # 减小二进制体积
#strip = true # 移除符号信息
#[profile.bench]
#inherits = "release"
# Nightly 特性门控
[package.metadata.docs.rs]
rustc-args = ["--cfg", "docsrs"]
all-features = true

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
#![feature(cold_path)]
#![feature(const_trait_impl)]
#![feature(const_convert)]
#![feature(const_cmp)]
#![feature(const_default)]
#![feature(hasher_prefixfree_extras)]
#![feature(const_result_unwrap_unchecked)]
#![feature(core_intrinsics)]
#![allow(internal_features)]
#![allow(unsafe_op_in_unsafe_fn)]
#![allow(non_camel_case_types)]
#![warn(clippy::all)]
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions)]
extern crate alloc;
mod arc_str;
mod str;
pub use arc_str::ArcStr;
pub use str::Str;
pub type InternedStr = ArcStr;
pub type StaticStr = &'static str;
pub type string = Str;
#[inline]
pub fn init() { arc_str::__init() }

1248
crates/interned/src/str.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
[package]
name = "manually_init"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
description.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]
[features]
default = []
sync = []
[package.metadata.docs.rs]
all-features = true

View File

@@ -0,0 +1,363 @@
//! # ManuallyInit - Zero-Cost Manual Memory Initialization
//!
//! A minimalist unsafe abstraction for manual memory initialization, designed for experts who need
//! direct memory control without runtime overhead.
//!
//! ## Design Philosophy
//!
//! `ManuallyInit` is not a safe alternative to `std::sync::OnceLock` or `std::cell::OnceCell`.
//! It is a deliberate choice for scenarios where:
//!
//! - You need zero runtime overhead
//! - You have full control over initialization timing and access patterns
//! - You explicitly choose to manage safety invariants manually
//! - You come from C/C++ and want familiar, direct memory semantics
//!
//! ## The Core Contract
//!
//! **By using this crate, you accept complete responsibility for:**
//!
//! 1. **Initialization state tracking** - You must know when values are initialized
//! 2. **Thread safety** - You must ensure no data races in concurrent environments
//! 3. **Aliasing rules** - You must uphold Rust's borrowing rules manually
//!
//! The library provides ergonomic APIs by marking methods as safe, but they are semantically unsafe.
//! This is a deliberate design choice to reduce syntax noise in controlled unsafe contexts.
//!
//! ## Primary Use Pattern: Single-Thread Init, Multi-Thread Read
//!
//! The most common and recommended pattern:
//! ```rust,no_run
//! use manually_init::ManuallyInit;
//!
//! static GLOBAL: ManuallyInit<Config> = ManuallyInit::new();
//!
//! // In main thread during startup
//! fn initialize() {
//! GLOBAL.init(Config::load());
//! }
//!
//! // In any thread after initialization
//! fn use_config() {
//! let config = GLOBAL.get();
//! // Read-only access is safe
//! }
//! ```
//!
//! ## Choosing the Right Tool
//!
//! | Strategy | Category | Choose For | What You Get | Cost |
//! |----------|----------|------------|--------------|------|
//! | **`const` item** | Compile-time | True constants that can be inlined | Zero runtime cost | Must be const-evaluable |
//! | **`static` item** | Read-only global | Simple immutable data with fixed address | Zero read cost | Must be `'static` and const |
//! | **`std::sync::LazyLock`** | Lazy immutable | **Default choice** for lazy statics | Automatic thread-safe init | Atomic check per access |
//! | **`std::sync::OnceLock`** | Lazy immutable | Manual control over init timing | Thread-safe one-time init | Atomic read per access |
//! | **`std::sync::Mutex`** | Mutable global | Simple exclusive access | One thread at a time | OS-level lock per access |
//! | **`std::sync::RwLock`** | Mutable global | Multiple readers, single writer | Concurrent reads | Reader/writer lock overhead |
//! | **`core::sync::atomic`** | Lock-free | Primitive types without locks | Wait-free operations | Memory ordering complexity |
//! | **`thread_local!`** | Thread-local | Per-thread state | No synchronization needed | Per-thread initialization |
//! | **`parking_lot::Mutex`** | Mutable global | Faster alternative to std::sync::Mutex | Smaller, faster locks | No poisoning, custom features |
//! | **`parking_lot::RwLock`** | Mutable global | Faster alternative to std::sync::RwLock | Better performance | No poisoning, custom features |
//! | **`parking_lot::OnceCell`** | Lazy immutable | Backport/alternative to std version | Same as std, more features | Similar to std version |
//! | **`once_cell::sync::OnceCell`** | Lazy immutable | Pre-1.70 Rust compatibility | Same as std version | Similar to std version |
//! | **`once_cell::sync::Lazy`** | Lazy immutable | Pre-1.80 Rust compatibility | Same as std version | Similar to std version |
//! | **`lazy_static::lazy_static!`** | Lazy immutable | Macro-based lazy statics | Convenient syntax | Extra dependency, macro overhead |
//! | **`crossbeam::atomic::AtomicCell`** | Lock-free | Any `Copy` type atomically | Lock-free for small types | CAS loop overhead |
//! | **`dashmap::DashMap`** | Concurrent map | High-throughput key-value store | Per-shard locking | Higher memory usage |
//! | **`tokio::sync::Mutex`** | Async mutable | Async-aware exclusive access | Works across .await | Async runtime overhead |
//! | **`tokio::sync::RwLock`** | Async mutable | Async-aware read/write lock | Works across .await | Async runtime overhead |
//! | **`tokio::sync::OnceCell`** | Async init | Initialization in async context | Async-aware safety | Async runtime overhead |
//! | **`static_cell::StaticCell`** | Single-thread | no_std mutable statics | Safe mutable statics | Single-threaded only |
//! | **`conquer_once::OnceCell`** | Lock-free init | Wait-free reads after init | no_std compatible | Complex implementation |
//! | **`core::mem::MaybeUninit`** | Unsafe primitive | Building custom abstractions | Maximum control | `unsafe` for every operation |
//! | **`ManuallyInit`** | Unsafe ergonomic | Zero-cost with external safety proof | Safe-looking API, zero overhead | You handle all safety |
//!
//! ## When to Use ManuallyInit
//!
//! Choose `ManuallyInit` only when:
//! - You need absolute zero overhead (no runtime state tracking)
//! - You have complete control over access patterns
//! - You're interfacing with C/C++ code that expects raw memory
//! - You're implementing a custom synchronization primitive
//! - You can prove safety through external invariants (e.g., init-before-threads pattern)
//!
//! ## Types Requiring Extra Care
//!
//! When using `ManuallyInit`, be especially careful with:
//!
//! | Type Category | Examples | Risk | Mitigation |
//! |--------------|----------|------|------------|
//! | **Heap Owners** | `String`, `Vec<T>`, `Box<T>` | Memory leaks on re-init | Call `take()` before re-init |
//! | **Reference Counted** | `Rc<T>`, `Arc<T>` | Reference leaks | Proper cleanup required |
//! | **Interior Mutability** | `Cell<T>`, `RefCell<T>` | Complex aliasing rules | Avoid or handle carefully |
//! | **Async Types** | `Future`, `Waker` | Complex lifetime requirements | Not recommended |
//!
//! ## Feature Flags
//!
//! - `sync` - Enables `Sync` implementation. By enabling this feature, you explicitly accept
//! responsibility for preventing data races in concurrent access patterns.
#![no_std]
#![cfg_attr(
feature = "sync",
doc = "**⚠️ The `sync` feature is enabled. You are responsible for thread safety.**"
)]
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
/// A zero-cost wrapper for manually managed initialization.
///
/// This type provides direct memory access with no runtime checks. It's designed for
/// experts who need precise control over initialization and memory layout.
///
/// # Safety Invariants You Must Uphold
///
/// 1. **Never access uninitialized memory** - Calling `get()`, `deref()`, etc. on an
/// uninitialized instance is undefined behavior.
///
/// 2. **Track initialization state** - The type does not track whether it's initialized.
/// This is entirely your responsibility.
///
/// 3. **Handle concurrent access** - If `sync` feature is enabled, you must ensure:
/// - Initialization happens before any concurrent access
/// - No concurrent mutations occur
/// - Memory barriers are properly established
///
/// 4. **Manage memory lifecycle** - `init()` overwrites without dropping. For types
/// that own heap memory, this causes leaks.
///
/// # Example: Global Configuration
///
/// ```rust,no_run
/// use manually_init::ManuallyInit;
///
/// #[derive(Copy, Clone)]
/// struct Config {
/// max_connections: usize,
/// timeout_ms: u64,
/// }
///
/// static CONFIG: ManuallyInit<Config> = ManuallyInit::new();
///
/// // Called once during startup
/// fn initialize_config() {
/// CONFIG.init(Config {
/// max_connections: 100,
/// timeout_ms: 5000,
/// });
/// }
///
/// // Called from any thread after initialization
/// fn get_timeout() -> u64 {
/// CONFIG.get().timeout_ms
/// }
/// ```
///
/// # Example: FFI Pattern
///
/// ```rust,no_run
/// use manually_init::ManuallyInit;
/// use core::ffi::c_void;
///
/// static FFI_CONTEXT: ManuallyInit<*mut c_void> = ManuallyInit::new();
///
/// extern "C" fn init_library(ctx: *mut c_void) {
/// FFI_CONTEXT.init(ctx);
/// }
///
/// extern "C" fn use_library() {
/// let ctx = *FFI_CONTEXT.get();
/// // Use ctx with FFI functions
/// }
/// ```
#[repr(transparent)]
pub struct ManuallyInit<T> {
value: UnsafeCell<MaybeUninit<T>>,
}
impl<T> ManuallyInit<T> {
/// Creates a new uninitialized instance.
///
/// The memory is not initialized. You must call `init()` before any access.
///
/// # Example
/// ```rust
/// use manually_init::ManuallyInit;
///
/// static DATA: ManuallyInit<i32> = ManuallyInit::new();
/// ```
#[inline]
#[must_use]
#[allow(clippy::new_without_default)]
pub const fn new() -> ManuallyInit<T> {
ManuallyInit { value: UnsafeCell::new(MaybeUninit::uninit()) }
}
/// Creates a new instance initialized with the given value.
///
/// The instance is immediately ready for use.
///
/// # Example
/// ```rust
/// use manually_init::ManuallyInit;
///
/// let cell = ManuallyInit::new_with(42);
/// assert_eq!(*cell.get(), 42);
/// ```
#[inline]
#[must_use]
pub const fn new_with(value: T) -> ManuallyInit<T> {
ManuallyInit { value: UnsafeCell::new(MaybeUninit::new(value)) }
}
/// Initializes or overwrites the value.
///
/// **Critical**: This method does NOT drop the old value. For types that own
/// heap memory (`String`, `Vec`, `Box`, etc.), this causes memory leaks.
///
/// # Memory Safety
///
/// - For `Copy` types: Safe to call repeatedly
/// - For heap-owning types: Call `take()` first or track initialization manually
///
/// # Example
/// ```rust
/// use manually_init::ManuallyInit;
///
/// let cell = ManuallyInit::new();
/// cell.init(42);
///
/// // Safe for Copy types
/// cell.init(100);
/// assert_eq!(*cell.get(), 100);
/// ```
#[inline]
pub const fn init(&self, value: T) {
unsafe { (&mut *self.value.get()).write(value) };
}
/// Gets a shared reference to the value.
///
/// # Safety Contract
///
/// You must ensure the value is initialized. Calling this on uninitialized
/// memory is undefined behavior.
///
/// # Example
/// ```rust
/// use manually_init::ManuallyInit;
///
/// let cell = ManuallyInit::new_with(42);
/// let value: &i32 = cell.get();
/// assert_eq!(*value, 42);
/// ```
#[inline]
pub const fn get(&self) -> &T {
unsafe { (&*self.value.get()).assume_init_ref() }
}
/// Gets a raw mutable pointer to the value.
///
/// This returns a raw pointer to avoid aliasing rule violations. You are
/// responsible for ensuring no aliasing occurs when dereferencing.
///
/// # Safety Contract
///
/// - The value must be initialized before dereferencing
/// - You must ensure no other references exist when creating `&mut T`
/// - You must follow Rust's aliasing rules manually
///
/// # Example
/// ```rust
/// use manually_init::ManuallyInit;
///
/// let cell = ManuallyInit::new_with(42);
/// let ptr = cell.get_ptr();
///
/// // You must ensure no other references exist
/// unsafe {
/// *ptr = 100;
/// }
///
/// assert_eq!(*cell.get(), 100);
/// ```
#[inline]
pub const fn get_ptr(&self) -> *mut T {
unsafe { (&mut *self.value.get()).as_mut_ptr() }
}
/// Consumes the cell and returns the inner value.
///
/// # Safety Contract
///
/// The value must be initialized. Calling this on uninitialized memory
/// is undefined behavior.
///
/// # Example
/// ```rust
/// use manually_init::ManuallyInit;
///
/// let cell = ManuallyInit::new_with(String::from("hello"));
/// let s = cell.into_inner();
/// assert_eq!(s, "hello");
/// ```
#[inline]
pub const fn into_inner(self) -> T {
unsafe { self.value.into_inner().assume_init() }
}
/// Takes the value out, leaving the cell uninitialized.
///
/// After calling this method, the cell is uninitialized. You must not
/// access it until calling `init()` again.
///
/// # Safety Contract
///
/// The value must be initialized. Calling this on uninitialized memory
/// is undefined behavior.
///
/// # Example
/// ```rust
/// use manually_init::ManuallyInit;
///
/// let cell = ManuallyInit::new_with(String::from("hello"));
/// let s = cell.take();
/// assert_eq!(s, "hello");
/// // cell is now uninitialized!
///
/// // Must reinitialize before access
/// cell.init(String::from("world"));
/// ```
#[inline]
pub const fn take(&self) -> T {
unsafe {
let slot = &mut *self.value.get();
let value = slot.assume_init_read();
*slot = MaybeUninit::uninit();
value
}
}
}
unsafe impl<T: Send> Send for ManuallyInit<T> {}
#[cfg(feature = "sync")]
unsafe impl<T: Sync> Sync for ManuallyInit<T> {}
impl<T> ::core::ops::Deref for ManuallyInit<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
self.get()
}
}
impl<T> ::core::ops::DerefMut for ManuallyInit<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
// Safe because we have &mut self, ensuring exclusive access
unsafe { (&mut *self.value.get()).assume_init_mut() }
}
}

View File

@@ -0,0 +1,10 @@
[package]
name = "rep_move"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
description.workspace = true
license.workspace = true
repository.workspace = true
[dependencies]

246
crates/rep_move/src/lib.rs Normal file
View File

@@ -0,0 +1,246 @@
//! Iterator that yields N-1 replications followed by the original value.
//!
//! Optimized for expensive-to-clone types by moving the original on the last iteration.
#![no_std]
#![feature(const_destruct)]
#![feature(const_trait_impl)]
use core::{fmt, iter::FusedIterator, marker::Destruct};
/// Replication strategy for `RepMove`.
pub trait Replicator<T> {
/// Creates a replica with mutable access to the remaining count.
fn replicate(&mut self, source: &T, remaining: &mut usize) -> T;
}
// Blanket impl for simple replicators
impl<T, F> Replicator<T> for F
where F: FnMut(&T) -> T
{
#[inline]
fn replicate(&mut self, source: &T, remaining: &mut usize) -> T {
let item = self(source);
*remaining = remaining.saturating_sub(1);
item
}
}
// Note: Additional blanket impls for FnMut(&T, usize) -> T and FnMut(&T, &mut usize) -> T
// would conflict with the above. Users needing state awareness should implement Replicator directly
// or use a wrapper type.
/// State-aware replicator wrapper for read-only access to remaining count.
pub struct ReadState<F>(pub F);
impl<T, F> Replicator<T> for ReadState<F>
where F: FnMut(&T, usize) -> T
{
#[inline]
fn replicate(&mut self, source: &T, remaining: &mut usize) -> T {
let item = (self.0)(source, *remaining);
*remaining = remaining.saturating_sub(1);
item
}
}
/// State-aware replicator wrapper for mutable access to remaining count.
pub struct MutState<F>(pub F);
impl<T, F> Replicator<T> for MutState<F>
where F: FnMut(&T, &mut usize) -> T
{
#[inline]
fn replicate(&mut self, source: &T, remaining: &mut usize) -> T { (self.0)(source, remaining) }
}
enum State<T, R> {
Active { source: T, remaining: usize, rep_fn: R },
Done,
}
/// Iterator yielding N-1 replicas then the original.
///
/// # Examples
///
/// Simple cloning:
/// ```
/// # use core::num::NonZeroUsize;
/// # use rep_move::RepMove;
/// let v = vec![1, 2, 3];
/// let mut iter = RepMove::new(v, Vec::clone, NonZeroUsize::new(3).unwrap());
///
/// assert_eq!(iter.next(), Some(vec![1, 2, 3]));
/// assert_eq!(iter.next(), Some(vec![1, 2, 3]));
/// assert_eq!(iter.next(), Some(vec![1, 2, 3])); // moved
/// ```
///
/// Read-only state awareness:
/// ```
/// # use core::num::NonZeroUsize;
/// # use rep_move::{RepMove, ReadState};
/// let s = String::from("item");
/// let mut iter = RepMove::new(
/// s,
/// ReadState(|s: &String, n| format!("{}-{}", s, n)),
/// NonZeroUsize::new(3).unwrap()
/// );
///
/// assert_eq!(iter.next(), Some("item-2".to_string()));
/// assert_eq!(iter.next(), Some("item-1".to_string()));
/// assert_eq!(iter.next(), Some("item".to_string()));
/// ```
///
/// Full control over iteration:
/// ```
/// # use core::num::NonZeroUsize;
/// # use rep_move::{RepMove, MutState};
/// let v = vec![1, 2, 3];
/// let mut iter = RepMove::new(
/// v,
/// MutState(|v: &Vec<i32>, remaining: &mut usize| {
/// if v.len() > 10 {
/// *remaining = 0; // Stop early for large vectors
/// } else {
/// *remaining = remaining.saturating_sub(1);
/// }
/// v.clone()
/// }),
/// NonZeroUsize::new(5).unwrap()
/// );
/// // Will yield fewer items due to the custom logic
/// ```
pub struct RepMove<T, R: Replicator<T>> {
state: State<T, R>,
}
impl<T, R: Replicator<T>> RepMove<T, R> {
/// Creates a new replicating iterator.
#[inline]
pub const fn new(source: T, rep_fn: R, count: usize) -> Self
where
T: [const] Destruct,
R: [const] Destruct,
{
if count == 0 {
Self { state: State::Done }
} else {
Self { state: State::Active { source, remaining: count - 1, rep_fn } }
}
}
}
impl<T, R: Replicator<T>> Iterator for RepMove<T, R> {
type Item = T;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let state = core::mem::replace(&mut self.state, State::Done);
match state {
State::Active { source, mut remaining, mut rep_fn } => {
if remaining > 0 {
let item = rep_fn.replicate(&source, &mut remaining);
self.state = State::Active { source, remaining, rep_fn };
Some(item)
} else {
Some(source)
}
}
State::Done => None,
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl<T, R: Replicator<T>> ExactSizeIterator for RepMove<T, R> {
#[inline]
fn len(&self) -> usize {
match &self.state {
State::Active { remaining, .. } => remaining + 1,
State::Done => 0,
}
}
}
impl<T, R: Replicator<T>> FusedIterator for RepMove<T, R> {}
impl<T: fmt::Debug, R: Replicator<T>> fmt::Debug for RepMove<T, R> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.state {
State::Active { source, remaining, .. } => f
.debug_struct("RepMove")
.field("source", source)
.field("remaining", remaining)
.finish_non_exhaustive(),
State::Done => write!(f, "RepMove::Done"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
extern crate alloc;
use alloc::{
format,
string::{String, ToString as _},
vec,
vec::Vec,
};
#[test]
fn test_simple_clone() {
let v = vec![1, 2, 3];
let mut iter = RepMove::new(v, Vec::clone, 3);
assert_eq!(iter.len(), 3);
assert_eq!(iter.next(), Some(vec![1, 2, 3]));
assert_eq!(iter.len(), 2);
assert_eq!(iter.next(), Some(vec![1, 2, 3]));
assert_eq!(iter.len(), 1);
assert_eq!(iter.next(), Some(vec![1, 2, 3]));
assert_eq!(iter.len(), 0);
assert_eq!(iter.next(), None);
}
#[test]
fn test_state_aware() {
let s = String::from("test");
let mut iter = RepMove::new(s, ReadState(|s: &String, n| format!("{}-{}", s, n)), 2);
assert_eq!(iter.next(), Some("test-1".to_string()));
assert_eq!(iter.next(), Some("test".to_string()));
assert_eq!(iter.next(), None);
}
#[test]
fn test_mutable_control() {
let v = vec![1, 2, 3];
let mut iter = RepMove::new(
v,
MutState(|v: &Vec<i32>, remaining: &mut usize| {
if *remaining > 1 {
*remaining = 1; // Skip ahead
} else {
*remaining = remaining.saturating_sub(1);
}
v.clone()
}),
4,
);
// Should yield fewer items due to skipping
assert_eq!(iter.next(), Some(vec![1, 2, 3]));
assert_eq!(iter.next(), Some(vec![1, 2, 3]));
assert_eq!(iter.next(), Some(vec![1, 2, 3]));
assert_eq!(iter.next(), None);
}
}

View File

@@ -1,165 +0,0 @@
#![cfg(all(unix, feature = "clock", feature = "std"))]
use std::{path, process, thread};
#[cfg(target_os = "linux")]
use chrono::Days;
use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone, Timelike};
fn verify_against_date_command_local(path: &'static str, dt: NaiveDateTime) {
let output = process::Command::new(path)
.arg("-d")
.arg(format!("{}-{:02}-{:02} {:02}:05:01", dt.year(), dt.month(), dt.day(), dt.hour()))
.arg("+%Y-%m-%d %H:%M:%S %:z")
.output()
.unwrap();
let date_command_str = String::from_utf8(output.stdout).unwrap();
// The below would be preferred. At this stage neither earliest() or latest()
// seems to be consistent with the output of the `date` command, so we simply
// compare both.
// let local = Local
// .with_ymd_and_hms(year, month, day, hour, 5, 1)
// // looks like the "date" command always returns a given time when it is ambiguous
// .earliest();
// if let Some(local) = local {
// assert_eq!(format!("{}\n", local), date_command_str);
// } else {
// // we are in a "Spring forward gap" due to DST, and so date also returns ""
// assert_eq!("", date_command_str);
// }
// This is used while a decision is made whether the `date` output needs to
// be exactly matched, or whether MappedLocalTime::Ambiguous should be handled
// differently
let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap();
match Local.from_local_datetime(&date.and_hms_opt(dt.hour(), 5, 1).unwrap()) {
chrono::MappedLocalTime::Ambiguous(a, b) => assert!(
format!("{}\n", a) == date_command_str || format!("{}\n", b) == date_command_str
),
chrono::MappedLocalTime::Single(a) => {
assert_eq!(format!("{}\n", a), date_command_str);
}
chrono::MappedLocalTime::None => {
assert_eq!("", date_command_str);
}
}
}
/// path to Unix `date` command. Should work on most Linux and Unixes. Not the
/// path for MacOS (/bin/date) which uses a different version of `date` with
/// different arguments (so it won't run which is okay).
/// for testing only
#[allow(dead_code)]
#[cfg(not(target_os = "aix"))]
const DATE_PATH: &str = "/usr/bin/date";
#[allow(dead_code)]
#[cfg(target_os = "aix")]
const DATE_PATH: &str = "/opt/freeware/bin/date";
#[cfg(test)]
/// test helper to sanity check the date command behaves as expected
/// asserts the command succeeded
fn assert_run_date_version() {
// note environment variable `LANG`
match std::env::var_os("LANG") {
Some(lang) => eprintln!("LANG: {:?}", lang),
None => eprintln!("LANG not set"),
}
let out = process::Command::new(DATE_PATH).arg("--version").output().unwrap();
let stdout = String::from_utf8(out.stdout).unwrap();
let stderr = String::from_utf8(out.stderr).unwrap();
// note the `date` binary version
eprintln!("command: {:?} --version\nstdout: {:?}\nstderr: {:?}", DATE_PATH, stdout, stderr);
assert!(out.status.success(), "command failed: {:?} --version", DATE_PATH);
}
#[test]
fn try_verify_against_date_command() {
if !path::Path::new(DATE_PATH).exists() {
eprintln!("date command {:?} not found, skipping", DATE_PATH);
return;
}
assert_run_date_version();
eprintln!(
"Run command {:?} for every hour from 1975 to 2077, skipping some years...",
DATE_PATH,
);
let mut children = vec![];
for year in [1975, 1976, 1977, 2020, 2021, 2022, 2073, 2074, 2075, 2076, 2077].iter() {
children.push(thread::spawn(|| {
let mut date = NaiveDate::from_ymd_opt(*year, 1, 1).unwrap().and_time(NaiveTime::MIN);
let end = NaiveDate::from_ymd_opt(*year + 1, 1, 1).unwrap().and_time(NaiveTime::MIN);
while date <= end {
verify_against_date_command_local(DATE_PATH, date);
date += chrono::TimeDelta::try_hours(1).unwrap();
}
}));
}
for child in children {
// Wait for the thread to finish. Returns a result.
let _ = child.join();
}
}
#[cfg(target_os = "linux")]
fn verify_against_date_command_format_local(path: &'static str, dt: NaiveDateTime) {
let required_format =
"d%d D%D F%F H%H I%I j%j k%k l%l m%m M%M q%q S%S T%T u%u U%U w%w W%W X%X y%y Y%Y z%:z";
// a%a - depends from localization
// A%A - depends from localization
// b%b - depends from localization
// B%B - depends from localization
// h%h - depends from localization
// c%c - depends from localization
// p%p - depends from localization
// r%r - depends from localization
// x%x - fails, date is dd/mm/yyyy, chrono is dd/mm/yy, same as %D
// Z%Z - too many ways to represent it, will most likely fail
let output = process::Command::new(path)
.env("LANG", "c")
.env("LC_ALL", "c")
.arg("-d")
.arg(format!(
"{}-{:02}-{:02} {:02}:{:02}:{:02}",
dt.year(),
dt.month(),
dt.day(),
dt.hour(),
dt.minute(),
dt.second()
))
.arg(format!("+{}", required_format))
.output()
.unwrap();
let date_command_str = String::from_utf8(output.stdout).unwrap();
let date = NaiveDate::from_ymd_opt(dt.year(), dt.month(), dt.day()).unwrap();
let ldt = Local
.from_local_datetime(&date.and_hms_opt(dt.hour(), dt.minute(), dt.second()).unwrap())
.unwrap();
let formatted_date = format!("{}\n", ldt.format(required_format));
assert_eq!(date_command_str, formatted_date);
}
#[test]
#[cfg(target_os = "linux")]
fn try_verify_against_date_command_format() {
if !path::Path::new(DATE_PATH).exists() {
eprintln!("date command {:?} not found, skipping", DATE_PATH);
return;
}
assert_run_date_version();
let mut date = NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().and_hms_opt(12, 11, 13).unwrap();
while date.year() < 2008 {
verify_against_date_command_format_local(DATE_PATH, date);
date = date + Days::new(55);
}
}

View File

@@ -1,89 +0,0 @@
//! Run this test with:
//! `env TZ="$(date +%z)" NOW="$(date +%s)" wasm-pack test --node -- --features wasmbind`
//!
//! The `TZ` and `NOW` variables are used to compare the results inside the WASM environment with
//! the host system.
//! The check will fail if the local timezone does not match one of the timezones defined below.
#![cfg(all(
target_arch = "wasm32",
feature = "wasmbind",
feature = "clock",
not(any(target_os = "emscripten", target_os = "wasi"))
))]
use chrono::prelude::*;
use wasm_bindgen_test::*;
#[wasm_bindgen_test]
fn now() {
let utc: DateTime<Utc> = Utc::now();
let local: DateTime<Local> = Local::now();
// Ensure time set by the test script is correct
let now = env!("NOW");
let actual = NaiveDateTime::parse_from_str(&now, "%s").unwrap().and_utc();
let diff = utc - actual;
assert!(
diff < chrono::TimeDelta::try_minutes(5).unwrap(),
"expected {} - {} == {} < 5m (env var: {})",
utc,
actual,
diff,
now,
);
let tz = env!("TZ");
eprintln!("testing with tz={}", tz);
// Ensure offset retrieved when getting local time is correct
let expected_offset = match tz {
"ACST-9:30" => FixedOffset::east_opt(19 * 30 * 60).unwrap(),
"Asia/Katmandu" => FixedOffset::east_opt(23 * 15 * 60).unwrap(), // No DST thankfully
"EDT" | "EST4" | "-0400" => FixedOffset::east_opt(-4 * 60 * 60).unwrap(),
"EST" | "-0500" => FixedOffset::east_opt(-5 * 60 * 60).unwrap(),
"UTC0" | "+0000" => FixedOffset::east_opt(0).unwrap(),
tz => panic!("unexpected TZ {}", tz),
};
assert_eq!(
&expected_offset,
local.offset(),
"expected: {:?} local: {:?}",
expected_offset,
local.offset(),
);
}
#[wasm_bindgen_test]
fn from_is_exact() {
let now = js_sys::Date::new_0();
let dt = DateTime::<Utc>::from(now.clone());
assert_eq!(now.get_time() as i64, dt.timestamp_millis());
}
#[wasm_bindgen_test]
fn local_from_local_datetime() {
let now = Local::now();
let ndt = now.naive_local();
let res = match Local.from_local_datetime(&ndt).single() {
Some(v) => v,
None => panic! {"Required for test!"},
};
assert_eq!(now, res);
}
#[wasm_bindgen_test]
fn convert_all_parts_with_milliseconds() {
let time: DateTime<Utc> = "2020-12-01T03:01:55.974Z".parse().unwrap();
let js_date = js_sys::Date::from(time);
assert_eq!(js_date.get_utc_full_year(), 2020);
assert_eq!(js_date.get_utc_month(), 11); // months are numbered 0..=11
assert_eq!(js_date.get_utc_date(), 1);
assert_eq!(js_date.get_utc_hours(), 3);
assert_eq!(js_date.get_utc_minutes(), 1);
assert_eq!(js_date.get_utc_seconds(), 55);
assert_eq!(js_date.get_utc_milliseconds(), 974);
}

View File

@@ -1,28 +0,0 @@
#![cfg(all(windows, feature = "clock", feature = "std"))]
use std::fs;
use windows_bindgen::bindgen;
#[test]
fn gen_bindings() {
let input = "src/offset/local/win_bindings.txt";
let output = "src/offset/local/win_bindings.rs";
let existing = fs::read_to_string(output).unwrap();
bindgen(["--no-deps", "--etc", input]).unwrap();
// Check the output is the same as before.
// Depending on the git configuration the file may have been checked out with `\r\n` newlines or
// with `\n`. Compare line-by-line to ignore this difference.
let mut new = fs::read_to_string(output).unwrap();
if existing.contains("\r\n") && !new.contains("\r\n") {
new = new.replace("\n", "\r\n");
} else if !existing.contains("\r\n") && new.contains("\r\n") {
new = new.replace("\r\n", "\n");
}
similar_asserts::assert_eq!(existing, new);
if !new.lines().eq(existing.lines()) {
panic!("generated file `{output}` is changed.");
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "chrono"
version = "0.4.41"
version = "0.4.42"
description = "Date and time library for Rust"
homepage = "https://github.com/chronotope/chrono"
documentation = "https://docs.rs/chrono/"
@@ -25,14 +25,16 @@ winapi = ["windows-link"]
std = ["alloc"]
clock = ["winapi", "iana-time-zone", "now"]
now = ["std"]
core-error = []
oldtime = []
wasmbind = ["wasm-bindgen", "js-sys"]
unstable-locales = ["pure-rust-locales"]
# Note that rkyv-16, rkyv-32, and rkyv-64 are mutually exclusive.
rkyv = ["dep:rkyv", "rkyv/pointer_width_32"]
rkyv-16 = ["dep:rkyv", "rkyv?/pointer_width_16"]
rkyv-32 = ["dep:rkyv", "rkyv?/pointer_width_32"]
rkyv-64 = ["dep:rkyv", "rkyv?/pointer_width_64"]
rkyv-validation = ["rkyv?/bytecheck"]
rkyv-validation = ["rkyv?/validation"]
# Features for internal use only:
__internal_bench = []
@@ -40,7 +42,7 @@ __internal_bench = []
num-traits = { version = "0.2", default-features = false }
serde = { version = "1.0.99", default-features = false, optional = true }
pure-rust-locales = { version = "0.8", optional = true }
rkyv = { version = "0.8.10", optional = true, default-features = false, features = ["std"]}
rkyv = { version = "0.8", optional = true, default-features = false }
arbitrary = { version = "1.0.0", features = ["derive"], optional = true }
[target.'cfg(all(target_arch = "wasm32", not(any(target_os = "emscripten", target_os = "wasi"))))'.dependencies]
@@ -48,10 +50,10 @@ wasm-bindgen = { version = "0.2", optional = true }
js-sys = { version = "0.3", optional = true } # contains FFI bindings for the JS Date API
[target.'cfg(windows)'.dependencies]
windows-link = { version = "0.1", optional = true }
windows-link = { version = "0.2", optional = true }
[target.'cfg(windows)'.dev-dependencies]
windows-bindgen = { version = "0.62" } # MSRV is 1.74
windows-bindgen = { version = "0.63" } # MSRV is 1.74
[target.'cfg(unix)'.dependencies]
iana-time-zone = { version = "0.1.45", optional = true, features = ["fallback"] }

View File

@@ -10,8 +10,8 @@ use core::cmp::Ordering;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::{fmt, hash};
// #[cfg(feature = "rkyv")]
// use rkyv::{Archive, Deserialize, Serialize};
#[cfg(feature = "rkyv")]
use rkyv::{Archive, Deserialize, Serialize};
#[cfg(all(feature = "unstable-locales", feature = "alloc"))]
use crate::format::Locale;
@@ -54,7 +54,7 @@ use crate::{DateTime, Datelike, TimeDelta, Weekday};
/// even though the raw calculation between `NaiveDate` and `TimeDelta` may not.
#[deprecated(since = "0.4.23", note = "Use `NaiveDate` or `DateTime<Tz>` instead")]
#[derive(Clone)]
// #[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
#[cfg_attr(feature = "rkyv", derive(Archive, Deserialize, Serialize))]
pub struct Date<Tz: TimeZone> {
date: NaiveDate,
offset: Tz::Offset,

View File

@@ -31,7 +31,7 @@ use crate::offset::{FixedOffset, LocalResult, Offset, TimeZone, Utc};
use crate::{Datelike, Months, TimeDelta, Timelike, Weekday};
use crate::{expect, try_opt};
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
/// documented at re-export site
@@ -48,7 +48,7 @@ mod tests;
/// [`TimeZone`](./offset/trait.TimeZone.html) implementations.
#[derive(Clone)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq, PartialOrd))
)]
@@ -713,6 +713,61 @@ impl<Tz: TimeZone> DateTime<Tz> {
}
impl DateTime<Utc> {
/// Makes a new `DateTime<Utc>` from the number of non-leap seconds
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
///
/// This is a convenience wrapper around [`DateTime::from_timestamp`],
/// which is useful in functions like [`Iterator::map`] to avoid a closure.
///
/// This is guaranteed to round-trip with regard to [`timestamp`](DateTime::timestamp).
///
/// If you need to create a `DateTime` with a [`TimeZone`] different from [`Utc`], use
/// [`TimeZone::timestamp_opt`] or [`DateTime::with_timezone`]; if you need to create a
/// `DateTime` with more precision, use [`DateTime::from_timestamp_micros`],
/// [`DateTime::from_timestamp_millis`], or [`DateTime::from_timestamp_nanos`].
///
/// # Errors
///
/// Returns `None` on out-of-range number of seconds,
/// otherwise returns `Some(DateTime {...})`.
///
/// # Examples
///
/// Using [`Option::and_then`]:
///
/// ```
/// # use chrono::DateTime;
/// let maybe_timestamp: Option<i64> = Some(1431648000);
/// let maybe_dt = maybe_timestamp.and_then(DateTime::from_timestamp_secs);
///
/// assert!(maybe_dt.is_some());
/// assert_eq!(maybe_dt.unwrap().to_string(), "2015-05-15 00:00:00 UTC");
/// ```
///
/// Using [`Iterator::map`]:
///
/// ```
/// # use chrono::{DateTime, Utc};
/// let v = vec![i64::MIN, 1_000_000_000, 1_234_567_890, i64::MAX];
/// let timestamps: Vec<Option<DateTime<Utc>>> = v
/// .into_iter()
/// .map(DateTime::from_timestamp_secs)
/// .collect();
///
/// assert_eq!(vec![
/// None,
/// Some(DateTime::parse_from_rfc3339("2001-09-09 01:46:40Z").unwrap().to_utc()),
/// Some(DateTime::parse_from_rfc3339("2009-02-13 23:31:30Z").unwrap().to_utc()),
/// None,
/// ], timestamps);
/// ```
///
#[inline]
#[must_use]
pub const fn from_timestamp_secs(secs: i64) -> Option<Self> {
Self::from_timestamp(secs, 0)
}
/// Makes a new `DateTime<Utc>` from the number of non-leap seconds
/// since January 1, 1970 0:00:00 UTC (aka "UNIX timestamp")
/// and the number of nanoseconds since the last whole non-leap second.
@@ -1930,12 +1985,12 @@ where
}
}
/// Number of days between Januari 1, 1970 and December 31, 1 BCE which we define to be day 0.
/// Number of days between January 1, 1970 and December 31, 1 BCE which we define to be day 0.
/// 4 full leap year cycles until December 31, 1600 4 * 146097 = 584388
/// 1 day until January 1, 1601 1
/// 369 years until Januari 1, 1970 369 * 365 = 134685
/// 369 years until January 1, 1970 369 * 365 = 134685
/// of which floor(369 / 4) are leap years floor(369 / 4) = 92
/// except for 1700, 1800 and 1900 -3 +
/// --------
/// 719163
const UNIX_EPOCH_DAY: i64 = 719_163;
pub(crate) const UNIX_EPOCH_DAY: i64 = 719_163;

View File

@@ -50,8 +50,8 @@ impl<Tz: TimeZone> ser::Serialize for DateTime<Tz> {
}
}
#[doc(hidden)]
#[derive(Debug)]
#[allow(missing_docs)]
#[allow(missing_debug_implementations)]
pub struct DateTimeVisitor;
impl de::Visitor<'_> for DateTimeVisitor {
@@ -244,11 +244,7 @@ pub mod ts_nanoseconds {
where
E: de::Error,
{
DateTime::from_timestamp(
value.div_euclid(1_000_000_000),
(value.rem_euclid(1_000_000_000)) as u32,
)
.ok_or_else(|| invalid_ts(value))
Ok(DateTime::from_timestamp_nanos(value))
}
/// Deserialize a timestamp in nanoseconds since the epoch
@@ -526,11 +522,7 @@ pub mod ts_microseconds {
where
E: de::Error,
{
DateTime::from_timestamp(
value.div_euclid(1_000_000),
(value.rem_euclid(1_000_000) * 1000) as u32,
)
.ok_or_else(|| invalid_ts(value))
DateTime::from_timestamp_micros(value).ok_or_else(|| invalid_ts(value))
}
/// Deserialize a timestamp in milliseconds since the epoch
@@ -1066,7 +1058,7 @@ pub mod ts_seconds {
where
E: de::Error,
{
DateTime::from_timestamp(value, 0).ok_or_else(|| invalid_ts(value))
DateTime::from_timestamp_secs(value).ok_or_else(|| invalid_ts(value))
}
/// Deserialize a timestamp in seconds since the epoch
@@ -1077,7 +1069,7 @@ pub mod ts_seconds {
if value > i64::MAX as u64 {
Err(invalid_ts(value))
} else {
DateTime::from_timestamp(value as i64, 0).ok_or_else(|| invalid_ts(value))
DateTime::from_timestamp_secs(value as i64).ok_or_else(|| invalid_ts(value))
}
}
}

View File

@@ -154,7 +154,10 @@ fn test_datetime_from_timestamp_millis() {
// that of `from_timestamp_opt`.
let secs_test = [0, 1, 2, 1000, 1234, 12345678, -1, -2, -1000, -12345678];
for secs in secs_test.iter().cloned() {
assert_eq!(DateTime::from_timestamp_millis(secs * 1000), DateTime::from_timestamp(secs, 0));
assert_eq!(
DateTime::from_timestamp_millis(secs * 1000),
DateTime::from_timestamp_secs(secs)
);
}
}
@@ -191,7 +194,7 @@ fn test_datetime_from_timestamp_micros() {
for secs in secs_test.iter().copied() {
assert_eq!(
DateTime::from_timestamp_micros(secs * 1_000_000),
DateTime::from_timestamp(secs, 0)
DateTime::from_timestamp_secs(secs)
);
}
}
@@ -242,24 +245,34 @@ fn test_datetime_from_timestamp_nanos() {
for secs in secs_test.iter().copied() {
assert_eq!(
Some(DateTime::from_timestamp_nanos(secs * 1_000_000_000)),
DateTime::from_timestamp(secs, 0)
DateTime::from_timestamp_secs(secs)
);
}
}
#[test]
fn test_datetime_from_timestamp_secs() {
let valid = [-2208936075, 0, 119731017, 1234567890, 2034061609];
for timestamp_secs in valid.iter().copied() {
let datetime = DateTime::from_timestamp_secs(timestamp_secs).unwrap();
assert_eq!(timestamp_secs, datetime.timestamp());
assert_eq!(DateTime::from_timestamp(timestamp_secs, 0).unwrap(), datetime);
}
}
#[test]
fn test_datetime_from_timestamp() {
let from_timestamp = |secs| DateTime::from_timestamp(secs, 0);
let ymdhms = |y, m, d, h, n, s| {
NaiveDate::from_ymd_opt(y, m, d).unwrap().and_hms_opt(h, n, s).unwrap().and_utc()
};
assert_eq!(from_timestamp(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59)));
assert_eq!(from_timestamp(0), Some(ymdhms(1970, 1, 1, 0, 0, 0)));
assert_eq!(from_timestamp(1), Some(ymdhms(1970, 1, 1, 0, 0, 1)));
assert_eq!(from_timestamp(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40)));
assert_eq!(from_timestamp(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7)));
assert_eq!(from_timestamp(i64::MIN), None);
assert_eq!(from_timestamp(i64::MAX), None);
assert_eq!(DateTime::from_timestamp_secs(-1), Some(ymdhms(1969, 12, 31, 23, 59, 59)));
assert_eq!(DateTime::from_timestamp_secs(0), Some(ymdhms(1970, 1, 1, 0, 0, 0)));
assert_eq!(DateTime::from_timestamp_secs(1), Some(ymdhms(1970, 1, 1, 0, 0, 1)));
assert_eq!(DateTime::from_timestamp_secs(1_000_000_000), Some(ymdhms(2001, 9, 9, 1, 46, 40)));
assert_eq!(DateTime::from_timestamp_secs(0x7fffffff), Some(ymdhms(2038, 1, 19, 3, 14, 7)));
assert_eq!(DateTime::from_timestamp_secs(i64::MIN), None);
assert_eq!(DateTime::from_timestamp_secs(i64::MAX), None);
}
#[test]
@@ -1034,7 +1047,7 @@ fn test_parse_datetime_utc() {
Ok(d) => d,
Err(e) => panic!("parsing `{s}` has failed: {e}"),
};
let s_ = format!("{:?}", d);
let s_ = format!("{d:?}");
// `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same
let d_ = match s_.parse::<DateTime<Utc>>() {
Ok(d) => d,

View File

@@ -109,7 +109,7 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
/// let mut buffer = String::new();
/// let _ = df.write_to(&mut buffer);
/// ```
pub fn write_to(&self, w: &mut impl Write) -> fmt::Result {
pub fn write_to(&self, w: &mut (impl Write + ?Sized)) -> fmt::Result {
for item in self.items.clone() {
match *item.borrow() {
Item::Literal(s) | Item::Space(s) => w.write_str(s),
@@ -124,14 +124,19 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
}
#[cfg(feature = "alloc")]
fn format_numeric(&self, w: &mut impl Write, spec: &Numeric, pad: Pad) -> fmt::Result {
fn format_numeric(
&self,
w: &mut (impl Write + ?Sized),
spec: &Numeric,
pad: Pad,
) -> fmt::Result {
use self::Numeric::*;
fn write_one(w: &mut impl Write, v: u8) -> fmt::Result {
fn write_one(w: &mut (impl Write + ?Sized), v: u8) -> fmt::Result {
w.write_char((b'0' + v) as char)
}
fn write_two(w: &mut impl Write, v: u8, pad: Pad) -> fmt::Result {
fn write_two(w: &mut (impl Write + ?Sized), v: u8, pad: Pad) -> fmt::Result {
let ones = b'0' + v % 10;
match (v / 10, pad) {
(0, Pad::None) => {}
@@ -142,7 +147,7 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
}
#[inline]
fn write_year(w: &mut impl Write, year: i32, pad: Pad) -> fmt::Result {
fn write_year(w: &mut (impl Write + ?Sized), year: i32, pad: Pad) -> fmt::Result {
if (1000..=9999).contains(&year) {
// fast path
write_hundreds(w, (year / 100) as u8)?;
@@ -153,7 +158,7 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
}
fn write_n(
w: &mut impl Write,
w: &mut (impl Write + ?Sized),
n: usize,
v: i64,
pad: Pad,
@@ -214,7 +219,7 @@ impl<'a, I: Iterator<Item = B> + Clone, B: Borrow<Item<'a>>> DelayedFormat<I> {
}
#[cfg(feature = "alloc")]
fn format_fixed(&self, w: &mut impl Write, spec: &Fixed) -> fmt::Result {
fn format_fixed(&self, w: &mut (impl Write + ?Sized), spec: &Fixed) -> fmt::Result {
use Fixed::*;
use InternalInternal::*;
@@ -387,7 +392,7 @@ pub fn format_item(
#[cfg(any(feature = "alloc", feature = "serde"))]
impl OffsetFormat {
/// Writes an offset from UTC with the format defined by `self`.
fn format(&self, w: &mut impl Write, off: FixedOffset) -> fmt::Result {
fn format(&self, w: &mut (impl Write + ?Sized), off: FixedOffset) -> fmt::Result {
let off = off.local_minus_utc();
if self.allow_zulu && off == 0 {
w.write_char('Z')?;
@@ -495,8 +500,8 @@ pub enum SecondsFormat {
/// Writes the date, time and offset to the string. same as `%Y-%m-%dT%H:%M:%S%.f%:z`
#[inline]
#[cfg(any(feature = "alloc", feature = "serde"))]
pub(crate) fn write_rfc3339(
w: &mut impl Write,
pub fn write_rfc3339(
w: &mut (impl Write + ?Sized),
dt: NaiveDateTime,
off: FixedOffset,
secform: SecondsFormat,
@@ -560,7 +565,7 @@ pub(crate) fn write_rfc3339(
#[cfg(feature = "alloc")]
/// write datetimes like `Tue, 1 Jul 2003 10:52:37 +0200`, same as `%a, %d %b %Y %H:%M:%S %z`
pub(crate) fn write_rfc2822(
w: &mut impl Write,
w: &mut (impl Write + ?Sized),
dt: NaiveDateTime,
off: FixedOffset,
) -> fmt::Result {
@@ -605,7 +610,7 @@ pub(crate) fn write_rfc2822(
}
/// Equivalent to `{:02}` formatting for n < 100.
pub(crate) fn write_hundreds(w: &mut impl Write, n: u8) -> fmt::Result {
pub(crate) fn write_hundreds(w: &mut (impl Write + ?Sized), n: u8) -> fmt::Result {
if n >= 100 {
return Err(fmt::Error);
}

View File

@@ -33,6 +33,8 @@
#[cfg(all(feature = "alloc", not(feature = "std"), not(test)))]
use alloc::boxed::Box;
#[cfg(all(feature = "core-error", not(feature = "std")))]
use core::error::Error;
use core::fmt;
use core::str::FromStr;
#[cfg(feature = "std")]
@@ -59,7 +61,7 @@ pub(crate) use formatting::write_hundreds;
#[cfg(feature = "alloc")]
pub(crate) use formatting::write_rfc2822;
#[cfg(any(feature = "alloc", feature = "serde"))]
pub(crate) use formatting::write_rfc3339;
pub use formatting::write_rfc3339;
#[cfg(feature = "alloc")]
#[allow(deprecated)]
pub use formatting::{DelayedFormat, format, format_item};
@@ -450,7 +452,7 @@ impl fmt::Display for ParseError {
}
}
#[cfg(feature = "std")]
#[cfg(any(feature = "core-error", feature = "std"))]
impl Error for ParseError {
#[allow(deprecated)]
fn description(&self) -> &str {

View File

@@ -1878,7 +1878,7 @@ mod tests {
if dt != checkdate {
// check for expected result
panic!(
"Date conversion failed for {date}\nReceived: {dt:?}\nExpected: checkdate{:?}"
"Date conversion failed for {date}\nReceived: {dt:?}\nExpected: {checkdate:?}"
);
}
}

View File

@@ -832,7 +832,7 @@ impl Parsed {
// reconstruct date and time fields from timestamp
let ts = timestamp.checked_add(i64::from(offset)).ok_or(OUT_OF_RANGE)?;
let mut datetime = DateTime::from_timestamp(ts, 0).ok_or(OUT_OF_RANGE)?.naive_utc();
let mut datetime = DateTime::from_timestamp_secs(ts).ok_or(OUT_OF_RANGE)?.naive_utc();
// fill year, ordinal, hour, minute and second fields from timestamp.
// if existing fields are consistent, this will allow the full date/time reconstruction.

View File

@@ -253,8 +253,7 @@ impl<'a> StrftimeItems<'a> {
/// const ITEMS: &[Item<'static>] = &[
/// Item::Numeric(Numeric::Year, Pad::Zero),
/// Item::Literal("-"),
/// Item::Literal("%"),
/// Item::Literal("Q"),
/// Item::Literal("%Q"),
/// ];
/// println!("{:?}", strftime_parser.clone().collect::<Vec<_>>());
/// assert!(strftime_parser.eq(ITEMS.iter().cloned()));
@@ -425,9 +424,247 @@ impl<'a> StrftimeItems<'a> {
})
.collect()
}
}
const HAVE_ALTERNATES: &str = "z";
fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> {
use InternalInternal::*;
use Item::{Literal, Space};
use Numeric::*;
let (original, mut remainder) = match remainder.chars().next()? {
// the next item is a specifier
'%' => (remainder, &remainder[1..]),
// the next item is space
c if c.is_whitespace() => {
// `%` is not a whitespace, so `c != '%'` is redundant
let nextspec =
remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len());
assert!(nextspec > 0);
let item = Space(&remainder[..nextspec]);
remainder = &remainder[nextspec..];
return Some((remainder, item));
}
// the next item is literal
_ => {
let nextspec = remainder
.find(|c: char| c.is_whitespace() || c == '%')
.unwrap_or(remainder.len());
assert!(nextspec > 0);
let item = Literal(&remainder[..nextspec]);
remainder = &remainder[nextspec..];
return Some((remainder, item));
}
};
macro_rules! next {
() => {
match remainder.chars().next() {
Some(x) => {
remainder = &remainder[x.len_utf8()..];
x
}
None => return Some((remainder, self.error(original, remainder))), // premature end of string
}
};
}
let spec = next!();
let pad_override = match spec {
'-' => Some(Pad::None),
'0' => Some(Pad::Zero),
'_' => Some(Pad::Space),
_ => None,
};
let is_alternate = spec == '#';
let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
if is_alternate && !HAVE_ALTERNATES.contains(spec) {
return Some((remainder, self.error(original, remainder)));
}
macro_rules! queue {
[$head:expr, $($tail:expr),+ $(,)*] => ({
const QUEUE: &'static [Item<'static>] = &[$($tail),+];
self.queue = QUEUE;
$head
})
}
#[cfg(not(feature = "unstable-locales"))]
macro_rules! queue_from_slice {
($slice:expr) => {{
self.queue = &$slice[1..];
$slice[0].clone()
}};
}
let item = match spec {
'A' => fixed(Fixed::LongWeekdayName),
'B' => fixed(Fixed::LongMonthName),
'C' => num0(YearDiv100),
'D' => {
queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]
}
'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)],
'G' => num0(IsoYear),
'H' => num0(Hour),
'I' => num0(Hour12),
'M' => num0(Minute),
'P' => fixed(Fixed::LowerAmPm),
'R' => queue![num0(Hour), Literal(":"), num0(Minute)],
'S' => num0(Second),
'T' => {
queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]
}
'U' => num0(WeekFromSun),
'V' => num0(IsoWeek),
'W' => num0(WeekFromMon),
#[cfg(not(feature = "unstable-locales"))]
'X' => queue_from_slice!(T_FMT),
#[cfg(feature = "unstable-locales")]
'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT),
'Y' => num0(Year),
'Z' => fixed(Fixed::TimezoneName),
'a' => fixed(Fixed::ShortWeekdayName),
'b' | 'h' => fixed(Fixed::ShortMonthName),
#[cfg(not(feature = "unstable-locales"))]
'c' => queue_from_slice!(D_T_FMT),
#[cfg(feature = "unstable-locales")]
'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT),
'd' => num0(Day),
'e' => nums(Day),
'f' => num0(Nanosecond),
'g' => num0(IsoYearMod100),
'j' => num0(Ordinal),
'k' => nums(Hour),
'l' => nums(Hour12),
'm' => num0(Month),
'n' => Space("\n"),
'p' => fixed(Fixed::UpperAmPm),
'q' => num(Quarter),
#[cfg(not(feature = "unstable-locales"))]
'r' => queue_from_slice!(T_FMT_AMPM),
#[cfg(feature = "unstable-locales")]
'r' => {
if self.locale.is_some() && locales::t_fmt_ampm(self.locale.unwrap()).is_empty() {
// 12-hour clock not supported by this locale. Switch to 24-hour format.
self.switch_to_locale_str(locales::t_fmt, T_FMT)
} else {
self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM)
}
}
's' => num(Timestamp),
't' => Space("\t"),
'u' => num(WeekdayFromMon),
'v' => {
queue![
nums(Day),
Literal("-"),
fixed(Fixed::ShortMonthName),
Literal("-"),
num0(Year)
]
}
'w' => num(NumDaysFromSun),
#[cfg(not(feature = "unstable-locales"))]
'x' => queue_from_slice!(D_FMT),
#[cfg(feature = "unstable-locales")]
'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT),
'y' => num0(YearMod100),
'z' => {
if is_alternate {
internal_fixed(TimezoneOffsetPermissive)
} else {
fixed(Fixed::TimezoneOffset)
}
}
'+' => fixed(Fixed::RFC3339),
':' => {
if remainder.starts_with("::z") {
remainder = &remainder[3..];
fixed(Fixed::TimezoneOffsetTripleColon)
} else if remainder.starts_with(":z") {
remainder = &remainder[2..];
fixed(Fixed::TimezoneOffsetDoubleColon)
} else if remainder.starts_with('z') {
remainder = &remainder[1..];
fixed(Fixed::TimezoneOffsetColon)
} else {
self.error(original, remainder)
}
}
'.' => match next!() {
'3' => match next!() {
'f' => fixed(Fixed::Nanosecond3),
_ => self.error(original, remainder),
},
'6' => match next!() {
'f' => fixed(Fixed::Nanosecond6),
_ => self.error(original, remainder),
},
'9' => match next!() {
'f' => fixed(Fixed::Nanosecond9),
_ => self.error(original, remainder),
},
'f' => fixed(Fixed::Nanosecond),
_ => self.error(original, remainder),
},
'3' => match next!() {
'f' => internal_fixed(Nanosecond3NoDot),
_ => self.error(original, remainder),
},
'6' => match next!() {
'f' => internal_fixed(Nanosecond6NoDot),
_ => self.error(original, remainder),
},
'9' => match next!() {
'f' => internal_fixed(Nanosecond9NoDot),
_ => self.error(original, remainder),
},
'%' => Literal("%"),
_ => self.error(original, remainder),
};
// Adjust `item` if we have any padding modifier.
// Not allowed on non-numeric items or on specifiers composed out of multiple
// formatting items.
if let Some(new_pad) = pad_override {
match item {
Item::Numeric(ref kind, _pad) if self.queue.is_empty() => {
Some((remainder, Item::Numeric(kind.clone(), new_pad)))
}
_ => Some((remainder, self.error(original, remainder))),
}
} else {
Some((remainder, item))
}
}
fn error<'b>(&mut self, original: &'b str, remainder: &'b str) -> Item<'b> {
match self.lenient {
false => Item::Error,
true => Item::Literal(&original[..original.len() - remainder.len()]),
}
}
#[cfg(feature = "unstable-locales")]
fn switch_to_locale_str(
&mut self,
localized_fmt_str: impl Fn(Locale) -> &'static str,
fallback: &'static [Item<'static>],
) -> Item<'a> {
if let Some(locale) = self.locale {
assert!(self.locale_str.is_empty());
let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap();
self.locale_str = fmt_str;
item
} else {
self.queue = &fallback[1..];
fallback[0].clone()
}
}
}
impl<'a> Iterator for StrftimeItems<'a> {
type Item = Item<'a>;
@@ -454,330 +691,46 @@ impl<'a> Iterator for StrftimeItems<'a> {
}
}
impl<'a> StrftimeItems<'a> {
fn error<'b>(
&mut self,
original: &'b str,
error_len: &mut usize,
ch: Option<char>,
) -> (&'b str, Item<'b>) {
if !self.lenient {
return (&original[*error_len..], Item::Error);
}
static D_FMT: &[Item<'static>] = &[
num0(Numeric::Month),
Item::Literal("/"),
num0(Numeric::Day),
Item::Literal("/"),
num0(Numeric::YearMod100),
];
static D_T_FMT: &[Item<'static>] = &[
fixed(Fixed::ShortWeekdayName),
Item::Space(" "),
fixed(Fixed::ShortMonthName),
Item::Space(" "),
nums(Numeric::Day),
Item::Space(" "),
num0(Numeric::Hour),
Item::Literal(":"),
num0(Numeric::Minute),
Item::Literal(":"),
num0(Numeric::Second),
Item::Space(" "),
num0(Numeric::Year),
];
static T_FMT: &[Item<'static>] = &[
num0(Numeric::Hour),
Item::Literal(":"),
num0(Numeric::Minute),
Item::Literal(":"),
num0(Numeric::Second),
];
static T_FMT_AMPM: &[Item<'static>] = &[
num0(Numeric::Hour12),
Item::Literal(":"),
num0(Numeric::Minute),
Item::Literal(":"),
num0(Numeric::Second),
Item::Space(" "),
fixed(Fixed::UpperAmPm),
];
if let Some(c) = ch {
*error_len -= c.len_utf8();
}
(&original[*error_len..], Item::Literal(&original[..*error_len]))
}
fn parse_next_item(&mut self, mut remainder: &'a str) -> Option<(&'a str, Item<'a>)> {
use InternalInternal::*;
use Item::{Literal, Space};
use Numeric::*;
static D_FMT: &[Item<'static>] =
&[num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)];
static D_T_FMT: &[Item<'static>] = &[
fixed(Fixed::ShortWeekdayName),
Space(" "),
fixed(Fixed::ShortMonthName),
Space(" "),
nums(Day),
Space(" "),
num0(Hour),
Literal(":"),
num0(Minute),
Literal(":"),
num0(Second),
Space(" "),
num0(Year),
];
static T_FMT: &[Item<'static>] =
&[num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)];
static T_FMT_AMPM: &[Item<'static>] = &[
num0(Hour12),
Literal(":"),
num0(Minute),
Literal(":"),
num0(Second),
Space(" "),
fixed(Fixed::UpperAmPm),
];
match remainder.chars().next() {
// we are done
None => None,
// the next item is a specifier
Some('%') => {
let original = remainder;
remainder = &remainder[1..];
let mut error_len = 0;
if self.lenient {
error_len += 1;
}
macro_rules! next {
() => {
match remainder.chars().next() {
Some(x) => {
remainder = &remainder[x.len_utf8()..];
if self.lenient {
error_len += x.len_utf8();
}
x
}
None => return Some(self.error(original, &mut error_len, None)), // premature end of string
}
};
}
let spec = next!();
let pad_override = match spec {
'-' => Some(Pad::None),
'0' => Some(Pad::Zero),
'_' => Some(Pad::Space),
_ => None,
};
let is_alternate = spec == '#';
let spec = if pad_override.is_some() || is_alternate { next!() } else { spec };
if is_alternate && !HAVE_ALTERNATES.contains(spec) {
return Some(self.error(original, &mut error_len, Some(spec)));
}
macro_rules! queue {
[$head:expr, $($tail:expr),+ $(,)*] => ({
const QUEUE: &'static [Item<'static>] = &[$($tail),+];
self.queue = QUEUE;
$head
})
}
#[cfg(not(feature = "unstable-locales"))]
macro_rules! queue_from_slice {
($slice:expr) => {{
self.queue = &$slice[1..];
$slice[0].clone()
}};
}
let item = match spec {
'A' => fixed(Fixed::LongWeekdayName),
'B' => fixed(Fixed::LongMonthName),
'C' => num0(YearDiv100),
'D' => {
queue![num0(Month), Literal("/"), num0(Day), Literal("/"), num0(YearMod100)]
}
'F' => queue![num0(Year), Literal("-"), num0(Month), Literal("-"), num0(Day)],
'G' => num0(IsoYear),
'H' => num0(Hour),
'I' => num0(Hour12),
'M' => num0(Minute),
'P' => fixed(Fixed::LowerAmPm),
'R' => queue![num0(Hour), Literal(":"), num0(Minute)],
'S' => num0(Second),
'T' => {
queue![num0(Hour), Literal(":"), num0(Minute), Literal(":"), num0(Second)]
}
'U' => num0(WeekFromSun),
'V' => num0(IsoWeek),
'W' => num0(WeekFromMon),
#[cfg(not(feature = "unstable-locales"))]
'X' => queue_from_slice!(T_FMT),
#[cfg(feature = "unstable-locales")]
'X' => self.switch_to_locale_str(locales::t_fmt, T_FMT),
'Y' => num0(Year),
'Z' => fixed(Fixed::TimezoneName),
'a' => fixed(Fixed::ShortWeekdayName),
'b' | 'h' => fixed(Fixed::ShortMonthName),
#[cfg(not(feature = "unstable-locales"))]
'c' => queue_from_slice!(D_T_FMT),
#[cfg(feature = "unstable-locales")]
'c' => self.switch_to_locale_str(locales::d_t_fmt, D_T_FMT),
'd' => num0(Day),
'e' => nums(Day),
'f' => num0(Nanosecond),
'g' => num0(IsoYearMod100),
'j' => num0(Ordinal),
'k' => nums(Hour),
'l' => nums(Hour12),
'm' => num0(Month),
'n' => Space("\n"),
'p' => fixed(Fixed::UpperAmPm),
'q' => num(Quarter),
#[cfg(not(feature = "unstable-locales"))]
'r' => queue_from_slice!(T_FMT_AMPM),
#[cfg(feature = "unstable-locales")]
'r' => {
if self.locale.is_some()
&& locales::t_fmt_ampm(self.locale.unwrap()).is_empty()
{
// 12-hour clock not supported by this locale. Switch to 24-hour format.
self.switch_to_locale_str(locales::t_fmt, T_FMT)
} else {
self.switch_to_locale_str(locales::t_fmt_ampm, T_FMT_AMPM)
}
}
's' => num(Timestamp),
't' => Space("\t"),
'u' => num(WeekdayFromMon),
'v' => {
queue![
nums(Day),
Literal("-"),
fixed(Fixed::ShortMonthName),
Literal("-"),
num0(Year)
]
}
'w' => num(NumDaysFromSun),
#[cfg(not(feature = "unstable-locales"))]
'x' => queue_from_slice!(D_FMT),
#[cfg(feature = "unstable-locales")]
'x' => self.switch_to_locale_str(locales::d_fmt, D_FMT),
'y' => num0(YearMod100),
'z' => {
if is_alternate {
internal_fixed(TimezoneOffsetPermissive)
} else {
fixed(Fixed::TimezoneOffset)
}
}
'+' => fixed(Fixed::RFC3339),
':' => {
if remainder.starts_with("::z") {
remainder = &remainder[3..];
fixed(Fixed::TimezoneOffsetTripleColon)
} else if remainder.starts_with(":z") {
remainder = &remainder[2..];
fixed(Fixed::TimezoneOffsetDoubleColon)
} else if remainder.starts_with('z') {
remainder = &remainder[1..];
fixed(Fixed::TimezoneOffsetColon)
} else {
self.error(original, &mut error_len, None).1
}
}
'.' => match next!() {
'3' => match next!() {
'f' => fixed(Fixed::Nanosecond3),
c => {
let res = self.error(original, &mut error_len, Some(c));
remainder = res.0;
res.1
}
},
'6' => match next!() {
'f' => fixed(Fixed::Nanosecond6),
c => {
let res = self.error(original, &mut error_len, Some(c));
remainder = res.0;
res.1
}
},
'9' => match next!() {
'f' => fixed(Fixed::Nanosecond9),
c => {
let res = self.error(original, &mut error_len, Some(c));
remainder = res.0;
res.1
}
},
'f' => fixed(Fixed::Nanosecond),
c => {
let res = self.error(original, &mut error_len, Some(c));
remainder = res.0;
res.1
}
},
'3' => match next!() {
'f' => internal_fixed(Nanosecond3NoDot),
c => {
let res = self.error(original, &mut error_len, Some(c));
remainder = res.0;
res.1
}
},
'6' => match next!() {
'f' => internal_fixed(Nanosecond6NoDot),
c => {
let res = self.error(original, &mut error_len, Some(c));
remainder = res.0;
res.1
}
},
'9' => match next!() {
'f' => internal_fixed(Nanosecond9NoDot),
c => {
let res = self.error(original, &mut error_len, Some(c));
remainder = res.0;
res.1
}
},
'%' => Literal("%"),
c => {
let res = self.error(original, &mut error_len, Some(c));
remainder = res.0;
res.1
}
};
// Adjust `item` if we have any padding modifier.
// Not allowed on non-numeric items or on specifiers composed out of multiple
// formatting items.
if let Some(new_pad) = pad_override {
match item {
Item::Numeric(ref kind, _pad) if self.queue.is_empty() => {
Some((remainder, Item::Numeric(kind.clone(), new_pad)))
}
_ => Some(self.error(original, &mut error_len, None)),
}
} else {
Some((remainder, item))
}
}
// the next item is space
Some(c) if c.is_whitespace() => {
// `%` is not a whitespace, so `c != '%'` is redundant
let nextspec =
remainder.find(|c: char| !c.is_whitespace()).unwrap_or(remainder.len());
assert!(nextspec > 0);
let item = Space(&remainder[..nextspec]);
remainder = &remainder[nextspec..];
Some((remainder, item))
}
// the next item is literal
_ => {
let nextspec = remainder
.find(|c: char| c.is_whitespace() || c == '%')
.unwrap_or(remainder.len());
assert!(nextspec > 0);
let item = Literal(&remainder[..nextspec]);
remainder = &remainder[nextspec..];
Some((remainder, item))
}
}
}
#[cfg(feature = "unstable-locales")]
fn switch_to_locale_str(
&mut self,
localized_fmt_str: impl Fn(Locale) -> &'static str,
fallback: &'static [Item<'static>],
) -> Item<'a> {
if let Some(locale) = self.locale {
assert!(self.locale_str.is_empty());
let (fmt_str, item) = self.parse_next_item(localized_fmt_str(locale)).unwrap();
self.locale_str = fmt_str;
item
} else {
self.queue = &fallback[1..];
fallback[0].clone()
}
}
}
const HAVE_ALTERNATES: &str = "z";
#[cfg(test)]
mod tests {
@@ -1246,4 +1199,18 @@ mod tests {
"2014-05-07T12:34:56+0000%Q%.2f%%"
);
}
/// Regression test for https://github.com/chronotope/chrono/issues/1725
#[test]
#[cfg(any(feature = "alloc", feature = "std"))]
fn test_finite() {
let mut i = 0;
for item in StrftimeItems::new("%2f") {
println!("{:?}", item);
i += 1;
if i > 10 {
panic!("infinite loop");
}
}
}
}

View File

@@ -380,7 +380,7 @@
//! use chrono::{DateTime, Utc};
//!
//! // Construct a datetime from epoch:
//! let dt: DateTime<Utc> = DateTime::from_timestamp(1_500_000_000, 0).unwrap();
//! let dt: DateTime<Utc> = DateTime::from_timestamp_secs(1_500_000_000).unwrap();
//! assert_eq!(dt.to_rfc2822(), "Fri, 14 Jul 2017 02:40:00 +0000");
//!
//! // Get epoch value from a datetime:
@@ -512,8 +512,8 @@
extern crate alloc;
mod time_delta;
#[cfg(feature = "std")]
#[doc(no_inline)]
#[cfg(any(feature = "std", feature = "core-error"))]
pub use time_delta::OutOfRangeError;
pub use time_delta::TimeDelta;
@@ -644,7 +644,7 @@ pub mod serde {
/// Zero-copy serialization/deserialization with rkyv.
///
/// This module re-exports the `Archived*` versions of chrono's types.
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
pub mod rkyv {
pub use crate::datetime::ArchivedDateTime;
pub use crate::month::ArchivedMonth;
@@ -690,6 +690,9 @@ impl fmt::Debug for OutOfRange {
#[cfg(feature = "std")]
impl std::error::Error for OutOfRange {}
#[cfg(all(not(feature = "std"), feature = "core-error"))]
impl core::error::Error for OutOfRange {}
/// Workaround because `?` is not (yet) available in const context.
#[macro_export]
#[doc(hidden)]

View File

@@ -1,6 +1,6 @@
use core::fmt;
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
use crate::OutOfRange;
@@ -31,7 +31,7 @@ use crate::naive::NaiveDate;
// Actual implementation is zero-indexed, API intended as 1-indexed for more intuitive behavior.
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash, PartialOrd, Ord)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq, PartialOrd)),
rkyv(attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)))
@@ -272,6 +272,9 @@ pub struct ParseMonthError {
#[cfg(feature = "std")]
impl std::error::Error for ParseMonthError {}
#[cfg(all(not(feature = "std"), feature = "core-error"))]
impl core::error::Error for ParseMonthError {}
impl fmt::Display for ParseMonthError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "ParseMonthError {{ .. }}")

View File

@@ -20,13 +20,15 @@ use core::num::NonZeroI32;
use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::{fmt, str};
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
/// L10n locales.
#[cfg(all(feature = "unstable-locales", feature = "alloc"))]
use pure_rust_locales::Locale;
use super::internals::{Mdf, YearFlags};
use crate::datetime::UNIX_EPOCH_DAY;
#[cfg(feature = "alloc")]
use crate::format::DelayedFormat;
use crate::format::{
@@ -38,8 +40,6 @@ use crate::naive::{Days, IsoWeek, NaiveDateTime, NaiveTime, NaiveWeek};
use crate::{Datelike, TimeDelta, Weekday};
use crate::{expect, try_opt};
use super::internals::{Mdf, YearFlags};
#[cfg(test)]
mod tests;
@@ -93,7 +93,7 @@ mod tests;
/// [proleptic Gregorian date]: crate::NaiveDate#calendar-date
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq, PartialOrd)),
rkyv(attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)))
@@ -384,6 +384,35 @@ impl NaiveDate {
NaiveDate::from_ordinal_and_flags(year_div_400 * 400 + year_mod_400 as i32, ordinal, flags)
}
/// Makes a new `NaiveDate` from a day's number in the proleptic Gregorian calendar, with
/// January 1, 1970 being day 0.
///
/// # Errors
///
/// Returns `None` if the date is out of range.
///
/// # Example
///
/// ```
/// use chrono::NaiveDate;
///
/// let from_ndays_opt = NaiveDate::from_epoch_days;
/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
///
/// assert_eq!(from_ndays_opt(-719_162), Some(from_ymd(1, 1, 1)));
/// assert_eq!(from_ndays_opt(1), Some(from_ymd(1970, 1, 2)));
/// assert_eq!(from_ndays_opt(0), Some(from_ymd(1970, 1, 1)));
/// assert_eq!(from_ndays_opt(-1), Some(from_ymd(1969, 12, 31)));
/// assert_eq!(from_ndays_opt(13036), Some(from_ymd(2005, 9, 10)));
/// assert_eq!(from_ndays_opt(100_000_000), None);
/// assert_eq!(from_ndays_opt(-100_000_000), None);
/// ```
#[must_use]
pub const fn from_epoch_days(days: i32) -> Option<NaiveDate> {
let ce_days = try_opt!(days.checked_add(UNIX_EPOCH_DAY as i32));
NaiveDate::from_num_days_from_ce_opt(ce_days)
}
/// Makes a new `NaiveDate` by counting the number of occurrences of a particular day-of-week
/// since the beginning of the given month. For instance, if you want the 2nd Friday of March
/// 2017, you would use `NaiveDate::from_weekday_of_month(2017, 3, Weekday::Fri, 2)`.
@@ -1407,6 +1436,23 @@ impl NaiveDate {
ndays + self.ordinal() as i32
}
/// Counts the days in the proleptic Gregorian calendar, with January 1, Year 1970 as day 0.
///
/// # Example
///
/// ```
/// use chrono::NaiveDate;
///
/// let from_ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();
///
/// assert_eq!(from_ymd(1, 1, 1).to_epoch_days(), -719162);
/// assert_eq!(from_ymd(1970, 1, 1).to_epoch_days(), 0);
/// assert_eq!(from_ymd(2005, 9, 10).to_epoch_days(), 13036);
/// ```
pub const fn to_epoch_days(&self) -> i32 {
self.num_days_from_ce() - UNIX_EPOCH_DAY as i32
}
/// Create a new `NaiveDate` from a raw year-ordinal-flags `i32`.
///
/// In a valid value an ordinal is never `0`, and neither are the year flags. This method

View File

@@ -301,6 +301,39 @@ fn test_date_from_num_days_from_ce() {
assert_eq!(from_ndays_from_ce(i32::MAX), None);
}
#[test]
fn test_date_from_epoch_days() {
let from_epoch_days = NaiveDate::from_epoch_days;
assert_eq!(from_epoch_days(-719_162), Some(NaiveDate::from_ymd_opt(1, 1, 1).unwrap()));
assert_eq!(from_epoch_days(0), Some(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap()));
assert_eq!(from_epoch_days(1), Some(NaiveDate::from_ymd_opt(1970, 1, 2).unwrap()));
assert_eq!(from_epoch_days(2), Some(NaiveDate::from_ymd_opt(1970, 1, 3).unwrap()));
assert_eq!(from_epoch_days(30), Some(NaiveDate::from_ymd_opt(1970, 1, 31).unwrap()));
assert_eq!(from_epoch_days(31), Some(NaiveDate::from_ymd_opt(1970, 2, 1).unwrap()));
assert_eq!(from_epoch_days(58), Some(NaiveDate::from_ymd_opt(1970, 2, 28).unwrap()));
assert_eq!(from_epoch_days(59), Some(NaiveDate::from_ymd_opt(1970, 3, 1).unwrap()));
assert_eq!(from_epoch_days(364), Some(NaiveDate::from_ymd_opt(1970, 12, 31).unwrap()));
assert_eq!(from_epoch_days(365), Some(NaiveDate::from_ymd_opt(1971, 1, 1).unwrap()));
assert_eq!(from_epoch_days(365 * 2), Some(NaiveDate::from_ymd_opt(1972, 1, 1).unwrap()));
assert_eq!(from_epoch_days(365 * 3 + 1), Some(NaiveDate::from_ymd_opt(1973, 1, 1).unwrap()));
assert_eq!(from_epoch_days(365 * 4 + 1), Some(NaiveDate::from_ymd_opt(1974, 1, 1).unwrap()));
assert_eq!(from_epoch_days(13036), Some(NaiveDate::from_ymd_opt(2005, 9, 10).unwrap()));
assert_eq!(from_epoch_days(-365), Some(NaiveDate::from_ymd_opt(1969, 1, 1).unwrap()));
assert_eq!(from_epoch_days(-366), Some(NaiveDate::from_ymd_opt(1968, 12, 31).unwrap()));
for days in (-9999..10001).map(|x| x * 100) {
assert_eq!(from_epoch_days(days).map(|d| d.to_epoch_days()), Some(days));
}
assert_eq!(from_epoch_days(NaiveDate::MIN.to_epoch_days()), Some(NaiveDate::MIN));
assert_eq!(from_epoch_days(NaiveDate::MIN.to_epoch_days() - 1), None);
assert_eq!(from_epoch_days(NaiveDate::MAX.to_epoch_days()), Some(NaiveDate::MAX));
assert_eq!(from_epoch_days(NaiveDate::MAX.to_epoch_days() + 1), None);
assert_eq!(from_epoch_days(i32::MIN), None);
assert_eq!(from_epoch_days(i32::MAX), None);
}
#[test]
fn test_date_from_weekday_of_month_opt() {
let ymwd = NaiveDate::from_weekday_of_month_opt;
@@ -423,6 +456,18 @@ fn test_date_num_days_from_ce() {
}
}
#[test]
fn test_date_to_epoch_days() {
assert_eq!(NaiveDate::from_ymd_opt(1970, 1, 1).unwrap().to_epoch_days(), 0);
for year in -9999..10001 {
assert_eq!(
NaiveDate::from_ymd_opt(year, 1, 1).unwrap().to_epoch_days(),
NaiveDate::from_ymd_opt(year - 1, 12, 31).unwrap().to_epoch_days() + 1
);
}
}
#[test]
fn test_date_succ() {
let ymd = |y, m, d| NaiveDate::from_ymd_opt(y, m, d).unwrap();

View File

@@ -10,7 +10,7 @@ use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::time::Duration;
use core::{fmt, str};
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
#[cfg(feature = "alloc")]
@@ -66,7 +66,7 @@ pub const MAX_DATETIME: NaiveDateTime = NaiveDateTime::MAX;
/// ```
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq, PartialOrd)),
rkyv(attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)))

View File

@@ -955,7 +955,7 @@ pub mod ts_seconds {
/// }
///
/// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?;
/// let expected = DateTime::from_timestamp(1431684000, 0).unwrap().naive_utc();
/// let expected = DateTime::from_timestamp_secs(1431684000).unwrap().naive_utc();
/// assert_eq!(my_s, S { time: expected });
/// # Ok::<(), serde_json::Error>(())
/// ```
@@ -979,7 +979,7 @@ pub mod ts_seconds {
where
E: de::Error,
{
DateTime::from_timestamp(value, 0)
DateTime::from_timestamp_secs(value)
.map(|dt| dt.naive_utc())
.ok_or_else(|| invalid_ts(value))
}
@@ -991,7 +991,7 @@ pub mod ts_seconds {
if value > i64::MAX as u64 {
Err(invalid_ts(value))
} else {
DateTime::from_timestamp(value as i64, 0)
DateTime::from_timestamp_secs(value as i64)
.map(|dt| dt.naive_utc())
.ok_or_else(|| invalid_ts(value))
}
@@ -1080,7 +1080,7 @@ pub mod ts_seconds_option {
/// }
///
/// let my_s: S = serde_json::from_str(r#"{ "time": 1431684000 }"#)?;
/// let expected = DateTime::from_timestamp(1431684000, 0).unwrap().naive_utc();
/// let expected = DateTime::from_timestamp_secs(1431684000).unwrap().naive_utc();
/// assert_eq!(my_s, S { time: Some(expected) });
/// # Ok::<(), serde_json::Error>(())
/// ```

View File

@@ -7,7 +7,7 @@ use core::fmt;
use super::internals::YearFlags;
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
/// ISO 8601 week.
@@ -18,7 +18,7 @@ use rkyv::{Archive, Deserialize, Serialize};
/// via the [`Datelike::iso_week`](../trait.Datelike.html#tymethod.iso_week) method.
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq, PartialOrd)),
rkyv(attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)))
@@ -176,13 +176,13 @@ mod tests {
assert_eq!(minweek.week(), 1);
assert_eq!(minweek.week0(), 0);
#[cfg(feature = "alloc")]
assert_eq!(format!("{:?}", minweek), NaiveDate::MIN.format("%G-W%V").to_string());
assert_eq!(format!("{minweek:?}"), NaiveDate::MIN.format("%G-W%V").to_string());
assert_eq!(maxweek.year(), date::MAX_YEAR + 1);
assert_eq!(maxweek.week(), 1);
assert_eq!(maxweek.week0(), 0);
#[cfg(feature = "alloc")]
assert_eq!(format!("{:?}", maxweek), NaiveDate::MAX.format("%G-W%V").to_string());
assert_eq!(format!("{maxweek:?}"), NaiveDate::MAX.format("%G-W%V").to_string());
}
#[test]

View File

@@ -9,7 +9,7 @@ use core::ops::{Add, AddAssign, Sub, SubAssign};
use core::time::Duration;
use core::{fmt, str};
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
#[cfg(feature = "alloc")]
@@ -211,7 +211,7 @@ mod tests;
/// **there is absolutely no guarantee that the leap second read has actually happened**.
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord, Copy, Clone)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq, PartialOrd)),
rkyv(attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)))

View File

@@ -283,26 +283,23 @@ fn test_time_from_str() {
"23:59:60.373929310237",
];
for &s in &valid {
eprintln!("test_time_parse_from_str valid {:?}", s);
eprintln!("test_time_parse_from_str valid {s:?}");
let d = match s.parse::<NaiveTime>() {
Ok(d) => d,
Err(e) => panic!("parsing `{}` has failed: {}", s, e),
Err(e) => panic!("parsing `{s}` has failed: {e}"),
};
let s_ = format!("{:?}", d);
let s_ = format!("{d:?}");
// `s` and `s_` may differ, but `s.parse()` and `s_.parse()` must be same
let d_ = match s_.parse::<NaiveTime>() {
Ok(d) => d,
Err(e) => {
panic!("`{}` is parsed into `{:?}`, but reparsing that has failed: {}", s, d, e)
panic!("`{s}` is parsed into `{d:?}`, but reparsing that has failed: {e}")
}
};
assert!(
d == d_,
"`{}` is parsed into `{:?}`, but reparsed result \
`{:?}` does not match",
s,
d,
d_
"`{s}` is parsed into `{d:?}`, but reparsed result \
`{d_:?}` does not match"
);
}
@@ -329,7 +326,7 @@ fn test_time_from_str() {
"09:08:00000000007", // invalid second / invalid fraction format
];
for &s in &invalid {
eprintln!("test_time_parse_from_str invalid {:?}", s);
eprintln!("test_time_parse_from_str invalid {s:?}");
assert!(s.parse::<NaiveTime>().is_err());
}
}

View File

@@ -6,7 +6,7 @@
use core::fmt;
use core::str::FromStr;
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
use super::{MappedLocalTime, Offset, TimeZone};
@@ -21,7 +21,7 @@ use crate::naive::{NaiveDate, NaiveDateTime};
/// [`west_opt`](#method.west_opt) methods for examples.
#[derive(PartialEq, Eq, Hash, Copy, Clone)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq)),
rkyv(attr(derive(Clone, Copy, PartialEq, Eq, Hash, Debug)))

View File

@@ -6,7 +6,7 @@
#[cfg(windows)]
use std::cmp::Ordering;
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
use super::fixed::FixedOffset;
@@ -115,10 +115,10 @@ mod tz_info;
/// ```
#[derive(Copy, Clone, Debug)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq)),
rkyv(attr(derive(Clone, Copy, Debug)))
archive(compare(PartialEq)),
archive_attr(derive(Clone, Copy, Debug))
)]
#[cfg_attr(feature = "rkyv-validation", archive(check_bytes))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
@@ -219,7 +219,7 @@ impl Transition {
#[cfg(windows)]
impl PartialOrd for Transition {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.transition_utc.cmp(&other.transition_utc))
Some(self.cmp(other))
}
}
@@ -343,8 +343,7 @@ mod tests {
// but there are only two sensible options.
assert!(
timestr == "15:02:60" || timestr == "15:03:00",
"unexpected timestr {:?}",
timestr
"unexpected timestr {timestr:?}"
);
}
@@ -352,8 +351,7 @@ mod tests {
let timestr = dt.time().to_string();
assert!(
timestr == "15:02:03.234" || timestr == "15:02:04.234",
"unexpected timestr {:?}",
timestr
"unexpected timestr {timestr:?}"
);
}
}

View File

@@ -134,7 +134,7 @@ impl TimeZone {
}
/// Returns a reference to the time zone
fn as_ref(&'_ self) -> TimeZoneRef<'_> {
fn as_ref(&self) -> TimeZoneRef<'_> {
TimeZoneRef {
transitions: &self.transitions,
local_time_types: &self.local_time_types,

View File

@@ -673,7 +673,7 @@ mod tests {
MappedLocalTime::Single(dt) => {
assert_eq!(dt.to_string(), *expected);
}
e => panic!("Got {:?} instead of an okay answer", e),
e => panic!("Got {e:?} instead of an okay answer"),
}
}
}

View File

@@ -14,7 +14,7 @@ use core::fmt;
))]
use std::time::{SystemTime, UNIX_EPOCH};
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
use super::{FixedOffset, MappedLocalTime, Offset, TimeZone};
@@ -42,7 +42,7 @@ use crate::{Date, DateTime};
/// ```
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq)),
rkyv(attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)))

View File

@@ -109,7 +109,11 @@ pub trait DurationRound: Sized {
type Err: std::error::Error;
/// Error that can occur in rounding or truncating
#[cfg(not(feature = "std"))]
#[cfg(all(not(feature = "std"), feature = "core-error"))]
type Err: core::error::Error;
/// Error that can occur in rounding or truncating
#[cfg(all(not(feature = "std"), not(feature = "core-error")))]
type Err: fmt::Debug + fmt::Display;
/// Return a copy rounded by TimeDelta.
@@ -362,6 +366,14 @@ impl std::error::Error for RoundingError {
}
}
#[cfg(all(not(feature = "std"), feature = "core-error"))]
impl core::error::Error for RoundingError {
#[allow(deprecated)]
fn description(&self) -> &str {
"error from rounding or truncating with DurationRound"
}
}
#[cfg(test)]
mod tests {
use super::{DurationRound, RoundingError, SubsecRound, TimeDelta};

View File

@@ -10,6 +10,8 @@
//! Temporal quantification
#[cfg(all(not(feature = "std"), feature = "core-error"))]
use core::error::Error;
use core::fmt;
use core::ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign};
use core::time::Duration;
@@ -18,7 +20,7 @@ use std::error::Error;
use crate::{expect, try_opt};
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
/// The number of nanoseconds in a microsecond.
@@ -51,7 +53,7 @@ const SECS_PER_WEEK: i64 = 604_800;
/// instance `abs()` can be called without any checks.
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq, PartialOrd)),
rkyv(attr(derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)))
@@ -630,7 +632,7 @@ impl fmt::Display for OutOfRangeError {
}
}
#[cfg(feature = "std")]
#[cfg(any(feature = "std", feature = "core-error"))]
impl Error for OutOfRangeError {
#[allow(deprecated)]
fn description(&self) -> &str {

View File

@@ -366,7 +366,7 @@ mod tests {
///
/// Panics if `div` is not positive.
fn in_between(start: i32, end: i32, div: i32) -> i32 {
assert!(div > 0, "in_between: nonpositive div = {}", div);
assert!(div > 0, "in_between: nonpositive div = {div}");
let start = (start.div_euclid(div), start.rem_euclid(div));
let end = (end.div_euclid(div), end.rem_euclid(div));
// The lowest multiple of `div` greater than or equal to `start`, divided.
@@ -390,16 +390,10 @@ mod tests {
assert_eq!(
jan1_year.num_days_from_ce(),
num_days_from_ce(&jan1_year),
"on {:?}",
jan1_year
"on {jan1_year:?}"
);
let mid_year = jan1_year + Days::new(133);
assert_eq!(
mid_year.num_days_from_ce(),
num_days_from_ce(&mid_year),
"on {:?}",
mid_year
);
assert_eq!(mid_year.num_days_from_ce(), num_days_from_ce(&mid_year), "on {mid_year:?}");
}
}

View File

@@ -1,6 +1,6 @@
use core::fmt;
#[cfg(any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
#[cfg(any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"))]
use rkyv::{Archive, Deserialize, Serialize};
use crate::OutOfRange;
@@ -31,7 +31,7 @@ use crate::OutOfRange;
/// ```
#[derive(PartialEq, Eq, Copy, Clone, Debug, Hash)]
#[cfg_attr(
any(feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
any(feature = "rkyv", feature = "rkyv-16", feature = "rkyv-32", feature = "rkyv-64"),
derive(Archive, Deserialize, Serialize),
rkyv(compare(PartialEq)),
rkyv(attr(derive(Clone, Copy, PartialEq, Eq, Debug, Hash)))
@@ -238,6 +238,9 @@ pub struct ParseWeekdayError {
pub(crate) _dummy: (),
}
#[cfg(all(not(feature = "std"), feature = "core-error"))]
impl core::error::Error for ParseWeekdayError {}
#[cfg(feature = "std")]
impl std::error::Error for ParseWeekdayError {}

View File

@@ -11,10 +11,11 @@ use crate::parse;
pub struct LoadResult {
/// Number of successfully loaded variables
pub loaded: usize,
/// Number of variables that were skipped (for `load` method only)
pub skipped: usize,
/// Number of variables that were overridden (for `load_override` method only)
pub overridden: usize,
// /// Number of variables that were skipped (for `load` method only)
// pub skipped: usize,
// /// Number of variables that were overridden (for `load_override` method only)
// pub overridden: usize,
pub skipped_or_overridden: usize,
}
pub struct Iter<R> {
@@ -57,8 +58,7 @@ impl<R: Read> Iter<R> {
Ok(LoadResult {
loaded,
skipped,
overridden: 0,
skipped_or_overridden: skipped,
})
}
@@ -86,8 +86,7 @@ impl<R: Read> Iter<R> {
Ok(LoadResult {
loaded,
skipped: 0,
overridden,
skipped_or_overridden: overridden,
})
}

10
patch/macros/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "macros"
version = "0.1.0"
edition = "2024"
authors = ["wisdgod <nav@wisdgod.com>"]
license = "MIT OR Apache-2.0"
description = "A Proto3 file dependency analyzer and optimizer"
repository = "https://github.com/wisdgod/ppp"
[dependencies]

105
patch/macros/src/lib.rs Normal file
View File

@@ -0,0 +1,105 @@
/// Batch define constants of the same type with shared attributes.
///
/// # Examples
///
/// ```
/// define_typed_constants! {
/// pub u32 => {
/// MAX_CONNECTIONS = 1024,
/// DEFAULT_TIMEOUT = 30,
/// MIN_BUFFER_SIZE = 256,
/// }
///
/// #[allow(dead_code)]
/// &'static str => {
/// APP_NAME = "server",
/// VERSION = "1.0.0",
/// }
/// }
/// ```
#[macro_export]
macro_rules! define_typed_constants {
// Entry point: process type group with first constant
(
$(#[$group_attr:meta])*
$vis:vis $ty:ty => {
$(#[$attr:meta])*
$name:ident = $value:expr,
$($inner_rest:tt)*
}
$($rest:tt)*
) => {
$(#[$attr])*
$(#[$group_attr])*
$vis const $name: $ty = $value;
$crate::define_typed_constants! {
@same_type
$(#[$group_attr])*
$vis $ty => {
$($inner_rest)*
}
}
$crate::define_typed_constants! {
$($rest)*
}
};
// Process remaining constants of the same type
(
@same_type
$(#[$group_attr:meta])*
$vis:vis $ty:ty => {
$(#[$attr:meta])*
$name:ident = $value:expr,
$($rest:tt)*
}
) => {
$(#[$attr])*
$(#[$group_attr])*
$vis const $name: $ty = $value;
$crate::define_typed_constants! {
@same_type
$(#[$group_attr])*
$vis $ty => {
$($rest)*
}
}
};
// Last constant in type group (no trailing comma)
(
@same_type
$(#[$group_attr:meta])*
$vis:vis $ty:ty => {
$(#[$attr:meta])*
$name:ident = $value:expr
}
) => {
$(#[$attr])*
$(#[$group_attr])*
$vis const $name: $ty = $value;
};
// Empty type group
(@same_type $(#[$group_attr:meta])* $vis:vis $ty:ty => {}) => {};
// Terminal case
() => {};
}
#[macro_export]
macro_rules! transmute_unchecked {
($x:expr) => {
unsafe { ::core::intrinsics::transmute_unchecked($x) }
};
}
#[macro_export]
macro_rules! unwrap_unchecked {
($x:expr) => {
unsafe { $x.unwrap_unchecked() }
};
}

View File

@@ -0,0 +1,87 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.71.1"
name = "prost"
version = "0.14.1"
authors = [
"Dan Burkert <dan@danburkert.com>",
"Lucio Franco <luciofranco14@gmail.com>",
"Casper Meijn <casper@meijn.net>",
"Tokio Contributors <team@tokio.rs>",
]
build = false
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "A Protocol Buffers implementation for the Rust Language."
readme = "README.md"
keywords = [
"protobuf",
"serialization",
]
categories = ["encoding"]
license = "Apache-2.0"
repository = "https://github.com/tokio-rs/prost"
[features]
default = [
"derive",
"std",
]
derive = ["dep:prost-derive"]
no-recursion-limit = []
std = []
indexmap = ["dep:indexmap"]
[lib]
name = "prost"
path = "src/lib.rs"
bench = false
[dependencies.bytes]
version = "1"
default-features = false
[dependencies.prost-derive]
path = "../prost-derive"
optional = true
[dependencies.macros]
path = "../macros"
[dependencies.indexmap]
version = "2"
optional = true
[dependencies.cfg-if]
version = "1.0"
[dependencies.any_all_workaround]
version = "0.1"
[dependencies.serde]
version = "1"
default-features = false
[dev-dependencies.criterion]
version = "0.7"
default-features = false
[dev-dependencies.proptest]
version = "1"
[dev-dependencies.rand]
version = "0.9"

35
patch/prost-0.14.1/Cargo.toml.orig generated Normal file
View File

@@ -0,0 +1,35 @@
[package]
name = "prost"
readme = "README.md"
description = "A Protocol Buffers implementation for the Rust Language."
keywords = ["protobuf", "serialization"]
categories = ["encoding"]
version.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
edition.workspace = true
rust-version.workspace = true
[lib]
# https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options
bench = false
[features]
default = ["derive", "std"]
derive = ["dep:prost-derive"]
no-recursion-limit = []
std = []
[dependencies]
bytes = { version = "1", default-features = false }
prost-derive = { version = "0.14.1", path = "../prost-derive", optional = true }
[dev-dependencies]
criterion = { version = "0.7", default-features = false }
proptest = "1"
rand = "0.9"
[[bench]]
name = "varint"
harness = false

201
patch/prost-0.14.1/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,507 @@
[![continuous integration](https://github.com/tokio-rs/prost/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/tokio-rs/prost/actions/workflows/ci.yml?query=branch%3Amaster)
[![Documentation](https://docs.rs/prost/badge.svg)](https://docs.rs/prost/)
[![Crate](https://img.shields.io/crates/v/prost.svg)](https://crates.io/crates/prost)
[![Dependency Status](https://deps.rs/repo/github/tokio-rs/prost/status.svg)](https://deps.rs/repo/github/tokio-rs/prost)
[![Discord](https://img.shields.io/discord/500028886025895936)](https://discord.gg/tokio)
# *PROST!*
`prost` is a [Protocol Buffers](https://developers.google.com/protocol-buffers/)
implementation for the [Rust Language](https://www.rust-lang.org/). `prost`
generates simple, idiomatic Rust code from `proto2` and `proto3` files.
Compared to other Protocol Buffers implementations, `prost`
* Generates simple, idiomatic, and readable Rust types by taking advantage of
Rust `derive` attributes.
* Retains comments from `.proto` files in generated Rust code.
* Allows existing Rust types (not generated from a `.proto`) to be serialized
and deserialized by adding attributes.
* Uses the [`bytes::{Buf, BufMut}`](https://github.com/carllerche/bytes)
abstractions for serialization instead of `std::io::{Read, Write}`.
* Respects the Protobuf `package` specifier when organizing generated code
into Rust modules.
* Preserves unknown enum values during deserialization.
* Does not include support for runtime reflection or message descriptors.
## Using `prost` in a Cargo Project
First, add `prost` and its public dependencies to your `Cargo.toml`:
```ignore
[dependencies]
prost = "0.14"
# Only necessary if using Protobuf well-known types:
prost-types = "0.14"
```
The recommended way to add `.proto` compilation to a Cargo project is to use the
`prost-build` library. See the [`prost-build` documentation][prost-build] for
more details and examples.
See the [snazzy repository][snazzy] for a simple start-to-finish example.
[prost-build]: https://docs.rs/prost-build/latest/prost_build/
[snazzy]: https://github.com/danburkert/snazzy
### MSRV
`prost` follows the `tokio-rs` project's MSRV model and supports 1.70. For more
information on the tokio msrv policy you can check it out [here][tokio msrv]
[tokio msrv]: https://github.com/tokio-rs/tokio/#supported-rust-versions
## Generated Code
`prost` generates Rust code from source `.proto` files using the `proto2` or
`proto3` syntax. `prost`'s goal is to make the generated code as simple as
possible.
### `protoc`
With `prost-build` v0.11 release, `protoc` will be required to invoke
`compile_protos` (unless `skip_protoc` is enabled). Prost will no longer provide
bundled `protoc` or attempt to compile `protoc` for users. For install
instructions for `protoc`, please check out the [protobuf install] instructions.
[protobuf install]: https://github.com/protocolbuffers/protobuf#protobuf-compiler-installation
### Packages
Prost can now generate code for `.proto` files that don't have a package spec.
`prost` will translate the Protobuf package into
a Rust module. For example, given the `package` specifier:
[package]: https://developers.google.com/protocol-buffers/docs/proto#packages
```protobuf,ignore
package foo.bar;
```
All Rust types generated from the file will be in the `foo::bar` module.
### Messages
Given a simple message declaration:
```protobuf,ignore
// Sample message.
message Foo {
}
```
`prost` will generate the following Rust struct:
```rust,ignore
/// Sample message.
#[derive(Clone, Debug, PartialEq, Message)]
pub struct Foo {
}
```
### Fields
Fields in Protobuf messages are translated into Rust as public struct fields of the
corresponding type.
#### Scalar Values
Scalar value types are converted as follows:
| Protobuf Type | Rust Type |
| --- | --- |
| `double` | `f64` |
| `float` | `f32` |
| `int32` | `i32` |
| `int64` | `i64` |
| `uint32` | `u32` |
| `uint64` | `u64` |
| `sint32` | `i32` |
| `sint64` | `i64` |
| `fixed32` | `u32` |
| `fixed64` | `u64` |
| `sfixed32` | `i32` |
| `sfixed64` | `i64` |
| `bool` | `bool` |
| `string` | `String` |
| `bytes` | `Vec<u8>` |
#### Enumerations
All `.proto` enumeration types convert to the Rust `i32` type. Additionally,
each enumeration type gets a corresponding Rust `enum` type. For example, this
`proto` enum:
```protobuf,ignore
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
```
gets this corresponding Rust enum [^1]:
```rust,ignore
pub enum PhoneType {
Mobile = 0,
Home = 1,
Work = 2,
}
```
[^1]: Annotations have been elided for clarity. See below for a full example.
You can convert a `PhoneType` value to an `i32` by doing:
```rust,ignore
PhoneType::Mobile as i32
```
The `#[derive(::prost::Enumeration)]` annotation added to the generated
`PhoneType` adds these associated functions to the type:
```rust,ignore
impl PhoneType {
pub fn is_valid(value: i32) -> bool { ... }
#[deprecated]
pub fn from_i32(value: i32) -> Option<PhoneType> { ... }
}
```
It also adds an `impl TryFrom<i32> for PhoneType`, so you can convert an `i32` to its corresponding `PhoneType` value by doing,
for example:
```rust,ignore
let phone_type = 2i32;
match PhoneType::try_from(phone_type) {
Ok(PhoneType::Mobile) => ...,
Ok(PhoneType::Home) => ...,
Ok(PhoneType::Work) => ...,
Err(_) => ...,
}
```
Additionally, wherever a `proto` enum is used as a field in a `Message`, the
message will have 'accessor' methods to get/set the value of the field as the
Rust enum type. For instance, this proto `PhoneNumber` message that has a field
named `type` of type `PhoneType`:
```protobuf,ignore
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
```
will become the following Rust type [^2] with methods `type` and `set_type`:
```rust,ignore
pub struct PhoneNumber {
pub number: String,
pub r#type: i32, // the `r#` is needed because `type` is a Rust keyword
}
impl PhoneNumber {
pub fn r#type(&self) -> PhoneType { ... }
pub fn set_type(&mut self, value: PhoneType) { ... }
}
```
Note that the getter methods will return the Rust enum's default value if the
field has an invalid `i32` value.
The `enum` type isn't used directly as a field, because the Protobuf spec
mandates that enumerations values are 'open', and decoding unrecognized
enumeration values must be possible.
[^2]: Annotations have been elided for clarity. See below for a full example.
#### Field Modifiers
Protobuf scalar value and enumeration message fields can have a modifier
depending on the Protobuf version. Modifiers change the corresponding type of
the Rust field:
| `.proto` Version | Modifier | Rust Type |
| --- | --- | --- |
| `proto2` | `optional` | `Option<T>` |
| `proto2` | `required` | `T` |
| `proto3` | default | `T` for scalar types, `Option<T>` otherwise |
| `proto3` | `optional` | `Option<T>` |
| `proto2`/`proto3` | `repeated` | `Vec<T>` |
Note that in `proto3` the default representation for all user-defined message
types is `Option<T>`, and for scalar types just `T` (during decoding, a missing
value is populated by `T::default()`). If you need a witness of the presence of
a scalar type `T`, use the `optional` modifier to enforce an `Option<T>`
representation in the generated Rust struct.
#### Map Fields
Map fields are converted to a Rust `HashMap` with key and value type converted
from the Protobuf key and value types.
#### Message Fields
Message fields are converted to the corresponding struct type. The table of
field modifiers above applies to message fields, except that `proto3` message
fields without a modifier (the default) will be wrapped in an `Option`.
Typically message fields are unboxed. `prost` will automatically box a message
field if the field type and the parent type are recursively nested in order to
avoid an infinite sized struct.
#### Oneof Fields
Oneof fields convert to a Rust enum. Protobuf `oneof`s types are not named, so
`prost` uses the name of the `oneof` field for the resulting Rust enum, and
defines the enum in a module under the struct. For example, a `proto3` message
such as:
```protobuf,ignore
message Foo {
oneof widget {
int32 quux = 1;
string bar = 2;
}
}
```
generates the following Rust[^3]:
```rust,ignore
pub struct Foo {
pub widget: Option<foo::Widget>,
}
pub mod foo {
pub enum Widget {
Quux(i32),
Bar(String),
}
}
```
`oneof` fields are always wrapped in an `Option`.
[^3]: Annotations have been elided for clarity. See below for a full example.
### Services
`prost-build` allows a custom code-generator to be used for processing `service`
definitions. This can be used to output Rust traits according to an
application's specific needs.
### Generated Code Example
Example `.proto` file:
```protobuf,ignore
syntax = "proto3";
package tutorial;
message Person {
string name = 1;
int32 id = 2; // Unique ID number for this person.
string email = 3;
enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}
message PhoneNumber {
string number = 1;
PhoneType type = 2;
}
repeated PhoneNumber phones = 4;
}
// Our address book file is just one of these.
message AddressBook {
repeated Person people = 1;
}
```
and the generated Rust code (`tutorial.rs`):
```rust,ignore
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Person {
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
/// Unique ID number for this person.
#[prost(int32, tag="2")]
pub id: i32,
#[prost(string, tag="3")]
pub email: ::prost::alloc::string::String,
#[prost(message, repeated, tag="4")]
pub phones: ::prost::alloc::vec::Vec<person::PhoneNumber>,
}
/// Nested message and enum types in `Person`.
pub mod person {
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct PhoneNumber {
#[prost(string, tag="1")]
pub number: ::prost::alloc::string::String,
#[prost(enumeration="PhoneType", tag="2")]
pub r#type: i32,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum PhoneType {
Mobile = 0,
Home = 1,
Work = 2,
}
}
/// Our address book file is just one of these.
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct AddressBook {
#[prost(message, repeated, tag="1")]
pub people: ::prost::alloc::vec::Vec<Person>,
}
```
## Accessing the `protoc` `FileDescriptorSet`
The `prost_build::Config::file_descriptor_set_path` option can be used to emit a file descriptor set
during the build & code generation step. When used in conjunction with the `std::include_bytes`
macro and the `prost_types::FileDescriptorSet` type, applications and libraries using Prost can
implement introspection capabilities requiring details from the original `.proto` files.
## Using `prost` in a `no_std` Crate
`prost` is compatible with `no_std` crates. To enable `no_std` support, disable
the `std` features in `prost` and `prost-types`:
```ignore
[dependencies]
prost = { version = "0.14.1", default-features = false, features = ["derive"] }
# Only necessary if using Protobuf well-known types:
prost-types = { version = "0.14.1", default-features = false }
```
Additionally, configure `prost-build` to output `BTreeMap`s instead of `HashMap`s
for all Protobuf `map` fields in your `build.rs`:
```rust,ignore
let mut config = prost_build::Config::new();
config.btree_map(&["."]);
```
When using edition 2015, it may be necessary to add an `extern crate core;`
directive to the crate which includes `prost`-generated code.
## Serializing Existing Types
`prost` uses a custom derive macro to handle encoding and decoding types, which
means that if your existing Rust type is compatible with Protobuf types, you can
serialize and deserialize it by adding the appropriate derive and field
annotations.
Currently the best documentation on adding annotations is to look at the
generated code examples above.
### Tag Inference for Existing Types
Prost automatically infers tags for the struct.
Fields are tagged sequentially in the order they
are specified, starting with `1`.
You may skip tags which have been reserved, or where there are gaps between
sequentially occurring tag values by specifying the tag number to skip to with
the `tag` attribute on the first field after the gap. The following fields will
be tagged sequentially starting from the next number.
```rust,ignore
use prost;
use prost::{Enumeration, Message};
#[derive(Clone, PartialEq, Message)]
struct Person {
#[prost(string, tag = "1")]
pub id: String, // tag=1
// NOTE: Old "name" field has been removed
// pub name: String, // tag=2 (Removed)
#[prost(string, tag = "6")]
pub given_name: String, // tag=6
#[prost(string)]
pub family_name: String, // tag=7
#[prost(string)]
pub formatted_name: String, // tag=8
#[prost(uint32, tag = "3")]
pub age: u32, // tag=3
#[prost(uint32)]
pub height: u32, // tag=4
#[prost(enumeration = "Gender")]
pub gender: i32, // tag=5
// NOTE: Skip to less commonly occurring fields
#[prost(string, tag = "16")]
pub name_prefix: String, // tag=16 (eg. mr/mrs/ms)
#[prost(string)]
pub name_suffix: String, // tag=17 (eg. jr/esq)
#[prost(string)]
pub maiden_name: String, // tag=18
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Enumeration)]
pub enum Gender {
Unknown = 0,
Female = 1,
Male = 2,
}
```
## Nix
The prost project maintains flakes support for local development. Once you have
nix and nix flakes setup you can just run `nix develop` to get a shell
configured with the required dependencies to compile the whole project.
## Feature Flags
- `std`: Enable integration with standard library. Disable this feature for `no_std` support. This feature is enabled by default.
- `derive`: Enable integration with `prost-derive`. Disable this feature to reduce compile times. This feature is enabled by default.
- `prost-derive`: Deprecated. Alias for `derive` feature.
- `no-recursion-limit`: Disable the recursion limit. The recursion limit is 100 and cannot be customized.
## FAQ
1. **Could `prost` be implemented as a serializer for [Serde](https://serde.rs/)?**
Probably not, however I would like to hear from a Serde expert on the matter.
There are two complications with trying to serialize Protobuf messages with
Serde:
- Protobuf fields require a numbered tag, and currently there appears to be no
mechanism suitable for this in `serde`.
- The mapping of Protobuf type to Rust type is not 1-to-1. As a result,
trait-based approaches to dispatching don't work very well. Example: six
different Protobuf field types correspond to a Rust `Vec<i32>`: `repeated
int32`, `repeated sint32`, `repeated sfixed32`, and their packed
counterparts.
But it is possible to place `serde` derive tags onto the generated types, so
the same structure can support both `prost` and `Serde`.
2. **I get errors when trying to run `cargo test` on MacOS**
If the errors are about missing `autoreconf` or similar, you can probably fix
them by running
```ignore
brew install automake
brew install libtool
```
## License
`prost` is distributed under the terms of the Apache License (Version 2.0).
See [LICENSE](https://github.com/tokio-rs/prost/blob/master/LICENSE) for details.
Copyright 2022 Dan Burkert & Tokio Contributors

View File

@@ -0,0 +1,366 @@
use core::borrow::Borrow;
use core::{fmt, ops, str};
use core::str::pattern::{Pattern, ReverseSearcher, Searcher as _};
#[cfg(not(feature = "std"))]
use alloc::string::String;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use bytes::Bytes;
#[allow(unused)]
struct BytesUnsafeView {
ptr: *const u8,
len: usize,
// inlined "trait object"
data: core::sync::atomic::AtomicPtr<()>,
vtable: &'static Vtable,
}
#[allow(unused)]
struct Vtable {
/// fn(data, ptr, len)
clone: unsafe fn(&core::sync::atomic::AtomicPtr<()>, *const u8, usize) -> Bytes,
/// fn(data, ptr, len)
///
/// takes `Bytes` to value
to_vec: unsafe fn(&core::sync::atomic::AtomicPtr<()>, *const u8, usize) -> Vec<u8>,
to_mut: unsafe fn(&core::sync::atomic::AtomicPtr<()>, *const u8, usize) -> bytes::BytesMut,
/// fn(data)
is_unique: unsafe fn(&core::sync::atomic::AtomicPtr<()>) -> bool,
/// fn(data, ptr, len)
drop: unsafe fn(&mut core::sync::atomic::AtomicPtr<()>, *const u8, usize),
}
impl BytesUnsafeView {
#[inline]
const fn from(src: Bytes) -> Self { unsafe { ::core::intrinsics::transmute(src) } }
#[inline]
const fn to(self) -> Bytes { unsafe { ::core::intrinsics::transmute(self) } }
}
#[repr(transparent)]
#[derive(PartialEq, Eq, PartialOrd, Ord)]
pub struct ByteStr {
// Invariant: bytes contains valid UTF-8
bytes: Bytes,
}
impl ByteStr {
#[inline]
pub fn new() -> ByteStr {
ByteStr {
// Invariant: the empty slice is trivially valid UTF-8.
bytes: Bytes::new(),
}
}
#[inline]
pub const fn from_static(val: &'static str) -> ByteStr {
ByteStr {
// Invariant: val is a str so contains valid UTF-8.
bytes: Bytes::from_static(val.as_bytes()),
}
}
#[inline]
/// ## Panics
/// In a debug build this will panic if `bytes` is not valid UTF-8.
///
/// ## Safety
/// `bytes` must contain valid UTF-8. In a release build it is undefined
/// behavior to call this with `bytes` that is not valid UTF-8.
pub unsafe fn from_utf8_unchecked(bytes: Bytes) -> ByteStr {
if cfg!(debug_assertions) {
match str::from_utf8(&bytes.as_ref()) {
Ok(_) => (),
Err(err) => panic!(
"ByteStr::from_utf8_unchecked() with invalid bytes; error = {err}, bytes = {bytes:?}",
),
}
}
// Invariant: assumed by the safety requirements of this function.
ByteStr { bytes }
}
#[inline(always)]
pub fn from_utf8(bytes: Bytes) -> Result<ByteStr, str::Utf8Error> {
str::from_utf8(&bytes)?;
// Invariant: just checked is utf8
Ok(ByteStr { bytes })
}
#[inline]
pub const fn len(&self) -> usize { self.bytes.len() }
#[must_use]
#[inline(always)]
pub const fn as_bytes(&self) -> &Bytes { &self.bytes }
#[must_use]
#[inline]
pub unsafe fn slice_unchecked(&self, range: impl core::ops::RangeBounds<usize>) -> Self {
use core::ops::Bound;
let len = self.len();
let begin = match range.start_bound() {
Bound::Included(&n) => n,
Bound::Excluded(&n) => n + 1,
Bound::Unbounded => 0,
};
let end = match range.end_bound() {
Bound::Included(&n) => n + 1,
Bound::Excluded(&n) => n,
Bound::Unbounded => len,
};
if end == begin {
return ByteStr::new();
}
let mut ret = BytesUnsafeView::from(self.bytes.clone());
ret.len = end - begin;
ret.ptr = unsafe { ret.ptr.add(begin) };
Self { bytes: ret.to() }
}
#[inline]
pub fn split_once<P: Pattern>(&self, delimiter: P) -> Option<(ByteStr, ByteStr)> {
let (start, end) = delimiter.into_searcher(self).next_match()?;
// SAFETY: `Searcher` is known to return valid indices.
unsafe { Some((self.slice_unchecked(..start), self.slice_unchecked(end..))) }
}
#[inline]
pub fn rsplit_once<P: Pattern>(&self, delimiter: P) -> Option<(ByteStr, ByteStr)>
where for<'a> P::Searcher<'a>: ReverseSearcher<'a> {
let (start, end) = delimiter.into_searcher(self).next_match_back()?;
// SAFETY: `Searcher` is known to return valid indices.
unsafe { Some((self.slice_unchecked(..start), self.slice_unchecked(end..))) }
}
#[must_use]
#[inline(always)]
pub const unsafe fn as_bytes_mut(&mut self) -> &mut Bytes { &mut self.bytes }
#[inline]
pub fn clear(&mut self) { self.bytes.clear() }
}
unsafe impl Send for ByteStr {}
unsafe impl Sync for ByteStr {}
impl Clone for ByteStr {
#[inline]
fn clone(&self) -> ByteStr { Self { bytes: self.bytes.clone() } }
}
impl bytes::Buf for ByteStr {
#[inline]
fn remaining(&self) -> usize { self.bytes.remaining() }
#[inline]
fn chunk(&self) -> &[u8] { self.bytes.chunk() }
#[inline]
fn advance(&mut self, cnt: usize) { self.bytes.advance(cnt) }
#[inline]
fn copy_to_bytes(&mut self, len: usize) -> Bytes { self.bytes.copy_to_bytes(len) }
}
impl fmt::Debug for ByteStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&**self, f) }
}
impl fmt::Display for ByteStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&**self, f) }
}
impl ops::Deref for ByteStr {
type Target = str;
#[inline]
fn deref(&self) -> &str {
let b: &[u8] = self.bytes.as_ref();
// Safety: the invariant of `bytes` is that it contains valid UTF-8.
unsafe { str::from_utf8_unchecked(b) }
}
}
impl AsRef<str> for ByteStr {
#[inline]
fn as_ref(&self) -> &str { self }
}
impl AsRef<[u8]> for ByteStr {
#[inline]
fn as_ref(&self) -> &[u8] { self.bytes.as_ref() }
}
impl core::hash::Hash for ByteStr {
#[inline]
fn hash<H>(&self, state: &mut H)
where H: core::hash::Hasher {
self.bytes.hash(state)
}
}
impl Borrow<str> for ByteStr {
#[inline]
fn borrow(&self) -> &str { &**self }
}
impl Borrow<[u8]> for ByteStr {
#[inline]
fn borrow(&self) -> &[u8] { self.bytes.borrow() }
}
impl PartialEq<str> for ByteStr {
#[inline]
fn eq(&self, other: &str) -> bool { &**self == other }
}
impl PartialEq<&str> for ByteStr {
#[inline]
fn eq(&self, other: &&str) -> bool { &**self == *other }
}
impl PartialEq<ByteStr> for str {
#[inline]
fn eq(&self, other: &ByteStr) -> bool { self == &**other }
}
impl PartialEq<ByteStr> for &str {
#[inline]
fn eq(&self, other: &ByteStr) -> bool { *self == &**other }
}
impl PartialEq<String> for ByteStr {
#[inline]
fn eq(&self, other: &String) -> bool { &**self == other.as_str() }
}
impl PartialEq<&String> for ByteStr {
#[inline]
fn eq(&self, other: &&String) -> bool { &**self == other.as_str() }
}
impl PartialEq<ByteStr> for String {
#[inline]
fn eq(&self, other: &ByteStr) -> bool { self.as_str() == &**other }
}
impl PartialEq<ByteStr> for &String {
#[inline]
fn eq(&self, other: &ByteStr) -> bool { self.as_str() == &**other }
}
// impl From
impl Default for ByteStr {
#[inline]
fn default() -> ByteStr { ByteStr::new() }
}
impl From<String> for ByteStr {
#[inline]
fn from(src: String) -> ByteStr {
ByteStr {
// Invariant: src is a String so contains valid UTF-8.
bytes: Bytes::from(src),
}
}
}
impl<'a> From<&'a str> for ByteStr {
#[inline]
fn from(src: &'a str) -> ByteStr {
ByteStr {
// Invariant: src is a str so contains valid UTF-8.
bytes: Bytes::copy_from_slice(src.as_bytes()),
}
}
}
impl From<ByteStr> for Bytes {
#[inline(always)]
fn from(src: ByteStr) -> Self { src.bytes }
}
impl serde::Serialize for ByteStr {
#[inline]
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
serializer.serialize_str(&**self)
}
}
struct ByteStrVisitor;
impl<'de> serde::de::Visitor<'de> for ByteStrVisitor {
type Value = ByteStr;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a UTF-8 string")
}
#[inline]
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where E: serde::de::Error {
Ok(ByteStr::from(v))
}
#[inline]
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where E: serde::de::Error {
Ok(ByteStr::from(v))
}
#[inline]
fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
where E: serde::de::Error {
match str::from_utf8(v) {
Ok(s) => Ok(ByteStr::from(s)),
Err(e) => Err(E::custom(format_args!("invalid UTF-8: {e}"))),
}
}
#[inline]
fn visit_byte_buf<E>(self, v: Vec<u8>) -> Result<Self::Value, E>
where E: serde::de::Error {
match String::from_utf8(v) {
Ok(s) => Ok(ByteStr::from(s)),
Err(e) => Err(E::custom(format_args!("invalid UTF-8: {}", e.utf8_error()))),
}
}
#[inline]
fn visit_seq<V>(self, mut seq: V) -> Result<Self::Value, V::Error>
where V: serde::de::SeqAccess<'de> {
use serde::de::Error as _;
let len = core::cmp::min(seq.size_hint().unwrap_or(0), 4096);
let mut bytes: Vec<u8> = Vec::with_capacity(len);
while let Some(value) = seq.next_element()? {
bytes.push(value);
}
match String::from_utf8(bytes) {
Ok(s) => Ok(ByteStr::from(s)),
Err(e) => Err(V::Error::custom(format_args!("invalid UTF-8: {}", e.utf8_error()))),
}
}
}
impl<'de> serde::Deserialize<'de> for ByteStr {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<ByteStr, D::Error>
where D: serde::Deserializer<'de> {
deserializer.deserialize_string(ByteStrVisitor)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,31 @@
use ::bytes::{Buf, BufMut};
use super::wire_type::WireType;
use crate::error::DecodeError;
use alloc::string::ToString as _;
macro_rules! fixed {
($ty:ty, $proto_ty:ident, $wire_type:ident, $put:ident, $try_get:ident) => {
pub mod $proto_ty {
use super::*;
pub const WIRE_TYPE: WireType = WireType::$wire_type;
pub const SIZE: usize = core::mem::size_of::<$ty>();
#[inline(always)]
pub fn encode_fixed(value: $ty, buf: &mut impl BufMut) { buf.$put(value); }
#[inline(always)]
pub fn decode_fixed(buf: &mut impl Buf) -> Result<$ty, DecodeError> {
buf.$try_get().map_err(|e| DecodeError::new(e.to_string()))
}
}
};
}
fixed!(f32, float, ThirtyTwoBit, put_f32_le, try_get_f32_le);
fixed!(f64, double, SixtyFourBit, put_f64_le, try_get_f64_le);
fixed!(u32, fixed32, ThirtyTwoBit, put_u32_le, try_get_u32_le);
fixed!(u64, fixed64, SixtyFourBit, put_u64_le, try_get_u64_le);
fixed!(i32, sfixed32, ThirtyTwoBit, put_i32_le, try_get_i32_le);
fixed!(i64, sfixed64, SixtyFourBit, put_i64_le, try_get_i64_le);

View File

@@ -0,0 +1,46 @@
pub use crate::{
error::{DecodeError, EncodeError, UnknownEnumValue},
message::Message,
// name::Name,
};
use ::bytes::{Buf, BufMut};
use crate::encoding::varint::usize::{decode_varint, encode_varint, encoded_len_varint};
/// Encodes a length delimiter to the buffer.
///
/// See [Message.encode_length_delimited] for more info.
///
/// An error will be returned if the buffer does not have sufficient capacity to encode the
/// delimiter.
pub fn encode_length_delimiter(length: usize, buf: &mut impl BufMut) -> Result<(), EncodeError> {
let required = encoded_len_varint(length);
let remaining = buf.remaining_mut();
if required > remaining {
return Err(EncodeError::new(required, remaining));
}
encode_varint(length, buf);
Ok(())
}
/// Returns the encoded length of a length delimiter.
///
/// Applications may use this method to ensure sufficient buffer capacity before calling
/// `encode_length_delimiter`. The returned size will be between 1 and 10, inclusive.
#[inline]
pub fn length_delimiter_len(length: usize) -> usize { encoded_len_varint(length) }
/// Decodes a length delimiter from the buffer.
///
/// This method allows the length delimiter to be decoded independently of the message, when the
/// message is encoded with [Message.encode_length_delimited].
///
/// An error may be returned in two cases:
///
/// * If the supplied buffer contains fewer than 10 bytes, then an error indicates that more
/// input is required to decode the full delimiter.
/// * If the supplied buffer contains 10 bytes or more, then the buffer contains an invalid
/// delimiter, and typically the buffer should be considered corrupt.
#[inline]
pub fn decode_length_delimiter(mut buf: impl Buf) -> Result<usize, DecodeError> { decode_varint(&mut buf) }

View File

@@ -0,0 +1,216 @@
#![allow(unused)]
mod ascii;
#[cfg(any(
target_feature = "sse2",
all(target_endian = "little", target_arch = "aarch64"),
all(target_endian = "little", target_feature = "neon")
))]
mod simd_funcs;
use ascii::validate_ascii;
use ::core::intrinsics::likely;
#[inline(always)]
fn in_inclusive_range8(i: u8, start: u8, end: u8) -> bool {
i.wrapping_sub(start) <= (end - start)
}
#[repr(align(64))] // Align to cache lines
pub struct Utf8Data {
pub table: [u8; 384],
}
// BEGIN GENERATED CODE. PLEASE DO NOT EDIT.
// Instead, please regenerate using generate-encoding-data.py
pub static UTF8_DATA: Utf8Data = Utf8Data {
table: [
252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 252, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 84, 148, 148, 148,
148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 148, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164, 164,
164, 164, 164, 164, 164, 164, 164, 164, 164, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252, 252,
252, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 32, 8, 8, 64, 8, 8, 8, 128, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
],
};
// END GENERATED CODE
pub fn utf8_valid_up_to(src: &[u8]) -> usize {
let mut read = 0;
'outer: loop {
let mut byte = {
let src_remaining = &src[read..];
match validate_ascii(src_remaining) {
None => {
return src.len();
}
Some((non_ascii, consumed)) => {
read += consumed;
non_ascii
}
}
};
// Check for the longest sequence to avoid checking twice for the
// multi-byte sequences. This can't overflow with 64-bit address space,
// because full 64 bits aren't in use. In the 32-bit PAE case, for this
// to overflow would mean that the source slice would be so large that
// the address space of the process would not have space for any code.
// Therefore, the slice cannot be so long that this would overflow.
if likely(read + 4 <= src.len()) {
'inner: loop {
// At this point, `byte` is not included in `read`, because we
// don't yet know that a) the UTF-8 sequence is valid and b) that there
// is output space if it is an astral sequence.
// Inspecting the lead byte directly is faster than what the
// std lib does!
if likely(in_inclusive_range8(byte, 0xC2, 0xDF)) {
// Two-byte
let second = unsafe { *(src.get_unchecked(read + 1)) };
if !in_inclusive_range8(second, 0x80, 0xBF) {
break 'outer;
}
read += 2;
// Next lead (manually inlined)
if likely(read + 4 <= src.len()) {
byte = unsafe { *(src.get_unchecked(read)) };
if byte < 0x80 {
read += 1;
continue 'outer;
}
continue 'inner;
}
break 'inner;
}
if likely(byte < 0xF0) {
'three: loop {
// Three-byte
let second = unsafe { *(src.get_unchecked(read + 1)) };
let third = unsafe { *(src.get_unchecked(read + 2)) };
if ((UTF8_DATA.table[usize::from(second)]
& unsafe { *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80)) })
| (third >> 6))
!= 2
{
break 'outer;
}
read += 3;
// Next lead (manually inlined)
if likely(read + 4 <= src.len()) {
byte = unsafe { *(src.get_unchecked(read)) };
if in_inclusive_range8(byte, 0xE0, 0xEF) {
continue 'three;
}
if likely(byte < 0x80) {
read += 1;
continue 'outer;
}
continue 'inner;
}
break 'inner;
}
}
// Four-byte
let second = unsafe { *(src.get_unchecked(read + 1)) };
let third = unsafe { *(src.get_unchecked(read + 2)) };
let fourth = unsafe { *(src.get_unchecked(read + 3)) };
if (u16::from(
UTF8_DATA.table[usize::from(second)]
& unsafe { *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80)) },
) | u16::from(third >> 6)
| (u16::from(fourth & 0xC0) << 2))
!= 0x202
{
break 'outer;
}
read += 4;
// Next lead
if likely(read + 4 <= src.len()) {
byte = unsafe { *(src.get_unchecked(read)) };
if byte < 0x80 {
read += 1;
continue 'outer;
}
continue 'inner;
}
break 'inner;
}
}
// We can't have a complete 4-byte sequence, but we could still have
// one to three shorter sequences.
'tail: loop {
// >= is better for bound check elision than ==
if read >= src.len() {
break 'outer;
}
byte = src[read];
// At this point, `byte` is not included in `read`, because we
// don't yet know that a) the UTF-8 sequence is valid and b) that there
// is output space if it is an astral sequence.
// Inspecting the lead byte directly is faster than what the
// std lib does!
if byte < 0x80 {
read += 1;
continue 'tail;
}
if in_inclusive_range8(byte, 0xC2, 0xDF) {
// Two-byte
let new_read = read + 2;
if new_read > src.len() {
break 'outer;
}
let second = src[read + 1];
if !in_inclusive_range8(second, 0x80, 0xBF) {
break 'outer;
}
read += 2;
continue 'tail;
}
// We need to exclude valid four byte lead bytes, because
// `UTF8_DATA.second_mask` covers
if byte < 0xF0 {
// Three-byte
let new_read = read + 3;
if new_read > src.len() {
break 'outer;
}
let second = src[read + 1];
let third = src[read + 2];
if ((UTF8_DATA.table[usize::from(second)]
& unsafe { *(UTF8_DATA.table.get_unchecked(byte as usize + 0x80)) })
| (third >> 6))
!= 2
{
break 'outer;
}
read += 3;
// `'tail` handles sequences shorter than 4, so
// there can't be another sequence after this one.
break 'outer;
}
break 'outer;
}
}
read
}
#[inline(always)]
pub fn is_vaild_utf8(v:&[u8]) -> bool { utf8_valid_up_to(v) == v.len() }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,347 @@
// Copyright Mozilla Foundation. See the COPYRIGHT
// file at the top-level directory of this distribution.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use any_all_workaround::all_mask16x8;
use any_all_workaround::all_mask8x16;
use any_all_workaround::any_mask16x8;
use any_all_workaround::any_mask8x16;
use core::simd::cmp::SimdPartialEq;
use core::simd::cmp::SimdPartialOrd;
use core::simd::simd_swizzle;
use core::simd::u16x8;
use core::simd::u8x16;
use core::simd::ToBytes;
use cfg_if::cfg_if;
// TODO: Migrate unaligned access to stdlib code if/when the RFC
// https://github.com/rust-lang/rfcs/pull/1725 is implemented.
/// Safety invariant: ptr must be valid for an unaligned read of 16 bytes
#[inline(always)]
pub unsafe fn load16_unaligned(ptr: *const u8) -> u8x16 {
let mut simd = ::core::mem::MaybeUninit::<u8x16>::uninit();
::core::ptr::copy_nonoverlapping(ptr, simd.as_mut_ptr() as *mut u8, 16);
// Safety: copied 16 bytes of initialized memory into this, it is now initialized
simd.assume_init()
}
/// Safety invariant: ptr must be valid for an aligned-for-u8x16 read of 16 bytes
#[allow(dead_code)]
#[inline(always)]
pub unsafe fn load16_aligned(ptr: *const u8) -> u8x16 {
*(ptr as *const u8x16)
}
/// Safety invariant: ptr must be valid for an unaligned store of 16 bytes
#[inline(always)]
pub unsafe fn store16_unaligned(ptr: *mut u8, s: u8x16) {
::core::ptr::copy_nonoverlapping(&s as *const u8x16 as *const u8, ptr, 16);
}
/// Safety invariant: ptr must be valid for an aligned-for-u8x16 store of 16 bytes
#[allow(dead_code)]
#[inline(always)]
pub unsafe fn store16_aligned(ptr: *mut u8, s: u8x16) {
*(ptr as *mut u8x16) = s;
}
/// Safety invariant: ptr must be valid for an unaligned read of 16 bytes
#[inline(always)]
pub unsafe fn load8_unaligned(ptr: *const u16) -> u16x8 {
let mut simd = ::core::mem::MaybeUninit::<u16x8>::uninit();
::core::ptr::copy_nonoverlapping(ptr as *const u8, simd.as_mut_ptr() as *mut u8, 16);
// Safety: copied 16 bytes of initialized memory into this, it is now initialized
simd.assume_init()
}
/// Safety invariant: ptr must be valid for an aligned-for-u16x8 read of 16 bytes
#[allow(dead_code)]
#[inline(always)]
pub unsafe fn load8_aligned(ptr: *const u16) -> u16x8 {
*(ptr as *const u16x8)
}
/// Safety invariant: ptr must be valid for an unaligned store of 16 bytes
#[inline(always)]
pub unsafe fn store8_unaligned(ptr: *mut u16, s: u16x8) {
::core::ptr::copy_nonoverlapping(&s as *const u16x8 as *const u8, ptr as *mut u8, 16);
}
/// Safety invariant: ptr must be valid for an aligned-for-u16x8 store of 16 bytes
#[allow(dead_code)]
#[inline(always)]
pub unsafe fn store8_aligned(ptr: *mut u16, s: u16x8) {
*(ptr as *mut u16x8) = s;
}
cfg_if! {
if #[cfg(all(target_feature = "sse2", target_arch = "x86_64"))] {
use core::arch::x86_64::_mm_movemask_epi8;
use core::arch::x86_64::_mm_packus_epi16;
} else if #[cfg(all(target_feature = "sse2", target_arch = "x86"))] {
use core::arch::x86::_mm_movemask_epi8;
use core::arch::x86::_mm_packus_epi16;
} else if #[cfg(target_arch = "aarch64")]{
use core::arch::aarch64::vmaxvq_u8;
use core::arch::aarch64::vmaxvq_u16;
} else {
}
}
// #[inline(always)]
// fn simd_byte_swap_u8(s: u8x16) -> u8x16 {
// unsafe {
// shuffle!(s, s, [1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14])
// }
// }
// #[inline(always)]
// pub fn simd_byte_swap(s: u16x8) -> u16x8 {
// to_u16_lanes(simd_byte_swap_u8(to_u8_lanes(s)))
// }
#[inline(always)]
pub fn simd_byte_swap(s: u16x8) -> u16x8 {
let left = s << 8;
let right = s >> 8;
left | right
}
#[inline(always)]
pub fn to_u16_lanes(s: u8x16) -> u16x8 {
u16x8::from_ne_bytes(s)
}
cfg_if! {
if #[cfg(target_feature = "sse2")] {
// Expose low-level mask instead of higher-level conclusion,
// because the non-ASCII case would perform less well otherwise.
// Safety-usable invariant: This returned value is whether each high bit is set
#[inline(always)]
pub fn mask_ascii(s: u8x16) -> i32 {
unsafe {
_mm_movemask_epi8(s.into())
}
}
} else {
}
}
cfg_if! {
if #[cfg(target_feature = "sse2")] {
#[inline(always)]
pub fn simd_is_ascii(s: u8x16) -> bool {
unsafe {
// Safety: We have cfg()d the correct platform
_mm_movemask_epi8(s.into()) == 0
}
}
} else if #[cfg(target_arch = "aarch64")]{
#[inline(always)]
pub fn simd_is_ascii(s: u8x16) -> bool {
unsafe {
// Safety: We have cfg()d the correct platform
vmaxvq_u8(s.into()) < 0x80
}
}
} else {
#[inline(always)]
pub fn simd_is_ascii(s: u8x16) -> bool {
// This optimizes better on ARM than
// the lt formulation.
let highest_ascii = u8x16::splat(0x7F);
!any_mask8x16(s.simd_gt(highest_ascii))
}
}
}
cfg_if! {
if #[cfg(target_feature = "sse2")] {
#[inline(always)]
pub fn simd_is_str_latin1(s: u8x16) -> bool {
if simd_is_ascii(s) {
return true;
}
let above_str_latin1 = u8x16::splat(0xC4);
s.simd_lt(above_str_latin1).all()
}
} else if #[cfg(target_arch = "aarch64")]{
#[inline(always)]
pub fn simd_is_str_latin1(s: u8x16) -> bool {
unsafe {
// Safety: We have cfg()d the correct platform
vmaxvq_u8(s.into()) < 0xC4
}
}
} else {
#[inline(always)]
pub fn simd_is_str_latin1(s: u8x16) -> bool {
let above_str_latin1 = u8x16::splat(0xC4);
all_mask8x16(s.simd_lt(above_str_latin1))
}
}
}
cfg_if! {
if #[cfg(target_arch = "aarch64")]{
#[inline(always)]
pub fn simd_is_basic_latin(s: u16x8) -> bool {
unsafe {
// Safety: We have cfg()d the correct platform
vmaxvq_u16(s.into()) < 0x80
}
}
#[inline(always)]
pub fn simd_is_latin1(s: u16x8) -> bool {
unsafe {
// Safety: We have cfg()d the correct platform
vmaxvq_u16(s.into()) < 0x100
}
}
} else {
#[inline(always)]
pub fn simd_is_basic_latin(s: u16x8) -> bool {
let above_ascii = u16x8::splat(0x80);
all_mask16x8(s.simd_lt(above_ascii))
}
#[inline(always)]
pub fn simd_is_latin1(s: u16x8) -> bool {
// For some reason, on SSE2 this formulation
// seems faster in this case while the above
// function is better the other way round...
let highest_latin1 = u16x8::splat(0xFF);
!any_mask16x8(s.simd_gt(highest_latin1))
}
}
}
#[inline(always)]
pub fn contains_surrogates(s: u16x8) -> bool {
let mask = u16x8::splat(0xF800);
let surrogate_bits = u16x8::splat(0xD800);
any_mask16x8((s & mask).simd_eq(surrogate_bits))
}
cfg_if! {
if #[cfg(target_arch = "aarch64")]{
macro_rules! aarch64_return_false_if_below_hebrew {
($s:ident) => ({
unsafe {
// Safety: We have cfg()d the correct platform
if vmaxvq_u16($s.into()) < 0x0590 {
return false;
}
}
})
}
macro_rules! non_aarch64_return_false_if_all {
($s:ident) => ()
}
} else {
macro_rules! aarch64_return_false_if_below_hebrew {
($s:ident) => ()
}
macro_rules! non_aarch64_return_false_if_all {
($s:ident) => ({
if all_mask16x8($s) {
return false;
}
})
}
}
}
macro_rules! in_range16x8 {
($s:ident, $start:expr, $end:expr) => {{
// SIMD sub is wrapping
($s - u16x8::splat($start)).simd_lt(u16x8::splat($end - $start))
}};
}
#[inline(always)]
pub fn is_u16x8_bidi(s: u16x8) -> bool {
// We try to first quickly refute the RTLness of the vector. If that
// fails, we do the real RTL check, so in that case we end up wasting
// the work for the up-front quick checks. Even the quick-check is
// two-fold in order to return `false` ASAP if everything is below
// Hebrew.
aarch64_return_false_if_below_hebrew!(s);
let below_hebrew = s.simd_lt(u16x8::splat(0x0590));
non_aarch64_return_false_if_all!(below_hebrew);
if all_mask16x8(
below_hebrew | in_range16x8!(s, 0x0900, 0x200F) | in_range16x8!(s, 0x2068, 0xD802),
) {
return false;
}
// Quick refutation failed. Let's do the full check.
any_mask16x8(
(in_range16x8!(s, 0x0590, 0x0900)
| in_range16x8!(s, 0xFB1D, 0xFE00)
| in_range16x8!(s, 0xFE70, 0xFEFF)
| in_range16x8!(s, 0xD802, 0xD804)
| in_range16x8!(s, 0xD83A, 0xD83C)
| s.simd_eq(u16x8::splat(0x200F))
| s.simd_eq(u16x8::splat(0x202B))
| s.simd_eq(u16x8::splat(0x202E))
| s.simd_eq(u16x8::splat(0x2067))),
)
}
#[inline(always)]
pub fn simd_unpack(s: u8x16) -> (u16x8, u16x8) {
let first: u8x16 = simd_swizzle!(
s,
u8x16::splat(0),
[0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23]
);
let second: u8x16 = simd_swizzle!(
s,
u8x16::splat(0),
[8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31]
);
(u16x8::from_ne_bytes(first), u16x8::from_ne_bytes(second))
}
cfg_if! {
if #[cfg(target_feature = "sse2")] {
#[inline(always)]
pub fn simd_pack(a: u16x8, b: u16x8) -> u8x16 {
unsafe {
// Safety: We have cfg()d the correct platform
_mm_packus_epi16(a.into(), b.into()).into()
}
}
} else {
#[inline(always)]
pub fn simd_pack(a: u16x8, b: u16x8) -> u8x16 {
let first: u8x16 = a.to_ne_bytes();
let second: u8x16 = b.to_ne_bytes();
simd_swizzle!(
first,
second,
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]
)
}
}
}

View File

@@ -0,0 +1,667 @@
#![allow(unsafe_op_in_unsafe_fn)]
use ::bytes::{Buf, BufMut};
use ::core::intrinsics::{assume, likely, unchecked_shl, unchecked_shr, unlikely};
use crate::error::DecodeError;
/// ZigZag 编码 32 位整数
#[inline(always)]
pub const fn encode_zigzag32(value: i32) -> u32 {
unsafe { (unchecked_shl(value, 1u8) ^ unchecked_shr(value, 31u8)) as u32 }
}
/// ZigZag 解码 32 位整数
#[inline(always)]
pub const fn decode_zigzag32(value: u32) -> i32 {
unsafe { (unchecked_shr(value, 1u8) as i32) ^ (-((value & 1) as i32)) }
}
/// ZigZag 编码 64 位整数
#[inline(always)]
pub const fn encode_zigzag64(value: i64) -> u64 {
unsafe { (unchecked_shl(value, 1u8) ^ unchecked_shr(value, 63u8)) as u64 }
}
/// ZigZag 解码 64 位整数
#[inline(always)]
pub const fn decode_zigzag64(value: u64) -> i64 {
unsafe { (unchecked_shr(value, 1u8) as i64) ^ (-((value & 1) as i64)) }
}
/// The maximum number of bytes a Protobuf Varint can occupy.
const VARINT64_MAX_LEN: usize = 10;
/// Encodes an integer value into LEB128 variable length format, and writes it to the buffer.
///
/// Dispatches to a fast path if the buffer has enough contiguous space,
/// otherwise falls back to a slower, byte-by-byte write.
#[inline]
pub fn encode_varint64(value: u64, buf: &mut impl BufMut) -> usize {
let len = encoded_len_varint64(value);
// If there is enough contiguous space, use the optimized path.
if likely(buf.chunk_mut().len() >= len) {
// Safety: The check above guarantees `buf.chunk_mut()` has at least `len` bytes.
unsafe { encode_varint64_fast(value, len, buf) };
} else {
encode_varint64_slow(value, len, buf);
}
len
}
/// Fast-path for encoding to a contiguous buffer slice.
///
/// ## Safety
///
/// The caller must ensure `buf.chunk_mut().len() >= len`.
#[inline(always)]
unsafe fn encode_varint64_fast(mut value: u64, len: usize, buf: &mut impl BufMut) {
let ptr = buf.chunk_mut().as_mut_ptr();
for i in 0..(len - 1) {
*ptr.add(i) = (value & 0x7F) as u8 | 0x80;
value >>= 7;
}
// After the loop, `value` holds the last byte, which must not have the continuation bit.
// The `encoded_len_varint` logic guarantees this.
assume(value < 0x80);
*ptr.add(len - 1) = value as u8;
// Notify the buffer that `len` bytes have been written.
buf.advance_mut(len);
}
/// Slow-path encoding for buffers that may not be contiguous.
#[cold]
#[inline(never)]
fn encode_varint64_slow(mut value: u64, len: usize, buf: &mut impl BufMut) {
for _ in 0..(len - 1) {
buf.put_u8((value & 0x7F) as u8 | 0x80);
value >>= 7;
}
// After the loop, `value` holds the last byte, which must not have the continuation bit.
// The `encoded_len_varint` logic guarantees this.
unsafe { assume(value < 0x80) };
buf.put_u8(value as u8);
}
/// Returns the encoded length of the value in LEB128 variable length format.
/// The returned value will be between 1 and 10, inclusive.
#[inline]
pub const fn encoded_len_varint64(value: u64) -> usize {
unsafe {
let value = value
.bit_width()
.unchecked_mul(9)
.unbounded_shr(6)
.unchecked_add(1);
assume(value >= 1 && value <= VARINT64_MAX_LEN as u32);
value as usize
}
}
/// Decodes a LEB128-encoded variable length integer from the buffer.
#[inline]
pub fn decode_varint64(buf: &mut impl Buf) -> Result<u64, DecodeError> {
fn inner(buf: &mut impl Buf) -> Option<u64> {
let bytes = buf.chunk();
let len = bytes.len();
if unlikely(len == 0) {
return None;
}
// Fast path for single-byte varints.
let first = unsafe { *bytes.get_unchecked(0) };
if likely(first < 0x80) {
buf.advance(1);
return Some(first as _);
}
// If the chunk is large enough or the varint is known to terminate within it,
// use the fast path which operates on a slice.
if likely(len >= VARINT64_MAX_LEN || bytes[len - 1] < 0x80) {
return decode_varint64_fast(bytes).map(|(value, advance)| {
buf.advance(advance);
value
});
}
// Fallback for varints that cross chunk boundaries.
decode_varint64_slow(buf)
}
inner(buf).ok_or(DecodeError::new("invalid varint64"))
}
/// Fast-path decoding of a varint from a contiguous memory slice.
///
/// ## Safety
///
/// Assumes `bytes` contains a complete varint or is at least `VARINT64_MAX_LEN` bytes long.
#[inline(always)]
fn decode_varint64_fast(bytes: &[u8]) -> Option<(u64, usize)> {
let ptr = bytes.as_ptr();
let mut value = 0u64;
for i in 0..VARINT64_MAX_LEN {
let byte = unsafe { *ptr.add(i) };
value |= ((byte & 0x7F) as u64) << (i * 7);
if byte < 0x80 {
// Check for overlong encoding on the 10th byte.
if unlikely(i == 9 && byte > 1) {
return None;
}
return Some((value, i + 1));
}
}
// A varint must not be longer than 10 bytes.
None
}
/// Slow-path decoding for varints that may cross `Buf` chunk boundaries.
#[cold]
#[inline(never)]
fn decode_varint64_slow(buf: &mut impl Buf) -> Option<u64> {
// Safety: The dispatcher `decode_varint` only calls this function if `bytes[0] >= 0x80`.
// This hint allows the compiler to optimize the first loop iteration.
unsafe { assume(buf.chunk().len() > 0 && buf.chunk()[0] >= 0x80) };
let mut value = 0u64;
for i in 0..VARINT64_MAX_LEN {
if unlikely(!buf.has_remaining()) {
return None; // Unexpected end of buffer.
}
let byte = buf.get_u8();
value |= ((byte & 0x7F) as u64) << (i * 7);
if byte < 0x80 {
// Check for overlong encoding on the 10th byte.
if unlikely(i == 9 && byte > 1) {
return None;
}
return Some(value);
}
}
// A varint must not be longer than 10 bytes.
None
}
/// The maximum number of bytes a Protobuf Varint can occupy.
const VARINT32_MAX_LEN: usize = 5;
/// Encodes an integer value into LEB128 variable length format, and writes it to the buffer.
///
/// Dispatches to a fast path if the buffer has enough contiguous space,
/// otherwise falls back to a slower, byte-by-byte write.
#[inline]
pub fn encode_varint32(value: u32, buf: &mut impl BufMut) -> usize {
let len = encoded_len_varint32(value);
// If there is enough contiguous space, use the optimized path.
if likely(buf.chunk_mut().len() >= len) {
// Safety: The check above guarantees `buf.chunk_mut()` has at least `len` bytes.
unsafe { encode_varint32_fast(value, len, buf) };
} else {
encode_varint32_slow(value, len, buf);
}
len
}
/// Fast-path for encoding to a contiguous buffer slice.
///
/// ## Safety
///
/// The caller must ensure `buf.chunk_mut().len() >= len`.
#[inline(always)]
unsafe fn encode_varint32_fast(mut value: u32, len: usize, buf: &mut impl BufMut) {
let ptr = buf.chunk_mut().as_mut_ptr();
for i in 0..(len - 1) {
*ptr.add(i) = (value & 0x7F) as u8 | 0x80;
value >>= 7;
}
// After the loop, `value` holds the last byte, which must not have the continuation bit.
// The `encoded_len_varint` logic guarantees this.
assume(value < 0x80);
*ptr.add(len - 1) = value as u8;
// Notify the buffer that `len` bytes have been written.
buf.advance_mut(len);
}
/// Slow-path encoding for buffers that may not be contiguous.
#[cold]
#[inline(never)]
fn encode_varint32_slow(mut value: u32, len: usize, buf: &mut impl BufMut) {
for _ in 0..(len - 1) {
buf.put_u8((value & 0x7F) as u8 | 0x80);
value >>= 7;
}
// After the loop, `value` holds the last byte, which must not have the continuation bit.
// The `encoded_len_varint` logic guarantees this.
unsafe { assume(value < 0x80) };
buf.put_u8(value as u8);
}
/// Returns the encoded length of the value in LEB128 variable length format.
/// The returned value will be between 1 and 5, inclusive.
#[inline]
pub const fn encoded_len_varint32(value: u32) -> usize {
unsafe {
let value = value
.bit_width()
.unchecked_mul(9)
.unbounded_shr(6)
.unchecked_add(1);
assume(value >= 1 && value <= VARINT32_MAX_LEN as u32);
value as usize
}
}
/// Decodes a LEB128-encoded variable length integer from the buffer.
#[inline]
pub fn decode_varint32(buf: &mut impl Buf) -> Result<u32, DecodeError> {
#[inline(always)]
fn inner(buf: &mut impl Buf) -> Option<u32> {
let bytes = buf.chunk();
let len = bytes.len();
if unlikely(len == 0) {
return None;
}
// Fast path for single-byte varints.
let first = unsafe { *bytes.get_unchecked(0) };
if likely(first < 0x80) {
buf.advance(1);
return Some(first as _);
}
// If the chunk is large enough or the varint is known to terminate within it,
// use the fast path which operates on a slice.
if likely(len >= VARINT32_MAX_LEN || bytes[len - 1] < 0x80) {
return decode_varint32_fast(bytes).map(|(value, advance)| {
buf.advance(advance);
value
});
}
// Fallback for varints that cross chunk boundaries.
decode_varint32_slow(buf)
}
inner(buf).ok_or(DecodeError::new("invalid varint32"))
}
/// Fast-path decoding of a varint from a contiguous memory slice.
///
/// ## Safety
///
/// Assumes `bytes` contains a complete varint or is at least `VARINT32_MAX_LEN` bytes long.
#[inline(always)]
fn decode_varint32_fast(bytes: &[u8]) -> Option<(u32, usize)> {
let ptr = bytes.as_ptr();
let mut value = 0u32;
for i in 0..VARINT32_MAX_LEN {
let byte = unsafe { *ptr.add(i) };
value |= ((byte & 0x7F) as u32) << (i * 7);
if byte < 0x80 {
// Check for overlong encoding on the 5th byte.
if unlikely(i == 4 && byte > 4) {
return None;
}
return Some((value, i + 1));
}
}
// A varint must not be longer than 5 bytes.
None
}
/// Slow-path decoding for varints that may cross `Buf` chunk boundaries.
#[cold]
#[inline(never)]
fn decode_varint32_slow(buf: &mut impl Buf) -> Option<u32> {
// Safety: The dispatcher `decode_varint` only calls this function if `bytes[0] >= 0x80`.
// This hint allows the compiler to optimize the first loop iteration.
unsafe { assume(buf.chunk().len() > 0 && buf.chunk()[0] >= 0x80) };
let mut value = 0u32;
for i in 0..VARINT32_MAX_LEN {
if unlikely(!buf.has_remaining()) {
return None; // Unexpected end of buffer.
}
let byte = buf.get_u8();
value |= ((byte & 0x7F) as u32) << (i * 7);
if byte < 0x80 {
// Check for overlong encoding on the 5th byte.
if unlikely(i == 4 && byte > 4) {
return None;
}
return Some(value);
}
}
// A varint must not be longer than 5 bytes.
None
}
pub mod usize {
use super::*;
#[cfg(target_pointer_width = "32")]
pub(super) use super::VARINT32_MAX_LEN as VARINT_MAX_LEN;
#[cfg(target_pointer_width = "64")]
pub(super) use super::VARINT64_MAX_LEN as VARINT_MAX_LEN;
#[inline(always)]
pub fn encode_varint(value: usize, buf: &mut impl BufMut) -> usize {
#[cfg(target_pointer_width = "32")]
return encode_varint32(value as u32, buf);
#[cfg(target_pointer_width = "64")]
return encode_varint64(value as u64, buf);
}
#[inline(always)]
pub const fn encoded_len_varint(value: usize) -> usize {
#[cfg(target_pointer_width = "32")]
return encoded_len_varint32(value as u32);
#[cfg(target_pointer_width = "64")]
return encoded_len_varint64(value as u64);
}
#[inline(always)]
pub fn decode_varint(buf: &mut impl Buf) -> Result<usize, DecodeError> {
#[cfg(target_pointer_width = "32")]
return transmute_unchecked!(decode_varint32(buf));
#[cfg(target_pointer_width = "64")]
return transmute_unchecked!(decode_varint64(buf));
}
}
pub mod bool {
use super::*;
#[inline(always)]
pub fn encode_varint(value: bool, buf: &mut impl BufMut) -> usize {
buf.put_u8(value as _);
1
}
#[inline(always)]
pub const fn encoded_len_varint(_value: bool) -> usize { 1 }
#[inline(always)]
pub fn decode_varint(buf: &mut impl Buf) -> Result<bool, DecodeError> {
fn inner(buf: &mut impl Buf) -> Option<bool> {
if unlikely(buf.remaining() == 0) {
return None;
}
let byte = buf.get_u8();
if byte <= 1 { Some(byte != 0) } else { None }
}
inner(buf).ok_or(DecodeError::new("invalid bool"))
}
#[inline]
pub(in super::super) fn encode_packed_fast<B: ReservableBuf>(values: &[bool], buf: &mut B) {
let start_ptr = buf.as_mut().as_mut_ptr();
buf.reserve(usize::VARINT_MAX_LEN);
unsafe {
buf.set_len(buf.len() + usize::VARINT_MAX_LEN);
}
let mut length = 0;
for &value in values {
length += encode_varint(value, buf);
}
let mut length_slice = unsafe {
&mut *(start_ptr as *mut [::core::mem::MaybeUninit<u8>; usize::VARINT_MAX_LEN])
as &mut [::core::mem::MaybeUninit<u8>]
};
let len = usize::encode_varint(length, &mut length_slice);
unsafe {
let dst = start_ptr.add(len);
let src = start_ptr.add(usize::VARINT_MAX_LEN);
::core::ptr::copy(src, dst, length);
buf.set_len(
buf.len()
.unchecked_sub(usize::VARINT_MAX_LEN)
.unchecked_add(len),
);
}
}
}
macro_rules! varint {
($ty:ty, $proto_ty:ident,32) => {
pub mod $proto_ty {
use super::*;
#[inline(always)]
pub fn encode_varint(value: $ty, buf: &mut impl BufMut) -> usize { encode_varint32(value as u32, buf) }
#[inline(always)]
pub const fn encoded_len_varint(value: $ty) -> usize { encoded_len_varint32(value as u32) }
#[inline(always)]
pub fn decode_varint(buf: &mut impl Buf) -> Result<$ty, DecodeError> {
transmute_unchecked!(decode_varint32(buf))
}
#[inline]
pub(in super::super) fn encode_packed_fast(values: &[$ty], buf: &mut impl ReservableBuf) {
let start_ptr = buf.as_mut().as_mut_ptr();
buf.reserve(usize::VARINT_MAX_LEN);
unsafe {
buf.set_len(buf.len() + usize::VARINT_MAX_LEN);
}
let mut length = 0;
for &value in values {
length += encode_varint(value, buf);
}
let mut length_slice = unsafe {
&mut *(start_ptr as *mut [::core::mem::MaybeUninit<u8>; usize::VARINT_MAX_LEN])
as &mut [::core::mem::MaybeUninit<u8>]
};
let len = usize::encode_varint(length, &mut length_slice);
unsafe {
let dst = start_ptr.add(len);
let src = start_ptr.add(usize::VARINT_MAX_LEN);
::core::ptr::copy(src, dst, length);
buf.set_len(
buf.len()
.unchecked_sub(usize::VARINT_MAX_LEN)
.unchecked_add(len),
);
}
}
}
};
($ty:ty, $proto_ty:ident,64) => {
pub mod $proto_ty {
use super::*;
#[inline(always)]
pub fn encode_varint(value: $ty, buf: &mut impl BufMut) -> usize { encode_varint64(value as u64, buf) }
#[inline(always)]
pub const fn encoded_len_varint(value: $ty) -> usize { encoded_len_varint64(value as u64) }
#[inline(always)]
pub fn decode_varint(buf: &mut impl Buf) -> Result<$ty, DecodeError> {
transmute_unchecked!(decode_varint64(buf))
}
#[inline]
pub(in super::super) fn encode_packed_fast(values: &[$ty], buf: &mut impl ReservableBuf) {
let start_ptr = buf.as_mut().as_mut_ptr();
buf.reserve(usize::VARINT_MAX_LEN);
unsafe {
buf.set_len(buf.len() + usize::VARINT_MAX_LEN);
}
let mut length = 0;
for &value in values {
length += encode_varint(value, buf);
}
let mut length_slice = unsafe {
&mut *(start_ptr as *mut [::core::mem::MaybeUninit<u8>; usize::VARINT_MAX_LEN])
as &mut [::core::mem::MaybeUninit<u8>]
};
let len = usize::encode_varint(length, &mut length_slice);
unsafe {
let dst = start_ptr.add(len);
let src = start_ptr.add(usize::VARINT_MAX_LEN);
::core::ptr::copy(src, dst, length);
buf.set_len(
buf.len()
.unchecked_sub(usize::VARINT_MAX_LEN)
.unchecked_add(len),
);
}
}
}
};
($ty:ty, $proto_ty:ident,32, $encode_fn:ident, $decode_fn:ident) => {
pub mod $proto_ty {
use super::*;
#[inline(always)]
pub fn encode_varint(value: $ty, buf: &mut impl BufMut) -> usize {
encode_varint32($encode_fn(value), buf)
}
#[inline(always)]
pub const fn encoded_len_varint(value: $ty) -> usize { encoded_len_varint32($encode_fn(value)) }
#[inline(always)]
pub fn decode_varint(buf: &mut impl Buf) -> Result<$ty, DecodeError> {
decode_varint32(buf).map($decode_fn)
}
#[inline]
pub(in super::super) fn encode_packed_fast(values: &[$ty], buf: &mut impl ReservableBuf) {
let start_ptr = buf.as_mut().as_mut_ptr();
buf.reserve(usize::VARINT_MAX_LEN);
unsafe {
buf.set_len(buf.len() + usize::VARINT_MAX_LEN);
}
let mut length = 0;
for &value in values {
length += encode_varint(value, buf);
}
let mut length_slice = unsafe {
&mut *(start_ptr as *mut [::core::mem::MaybeUninit<u8>; usize::VARINT_MAX_LEN])
as &mut [::core::mem::MaybeUninit<u8>]
};
let len = usize::encode_varint(length, &mut length_slice);
unsafe {
let dst = start_ptr.add(len);
let src = start_ptr.add(usize::VARINT_MAX_LEN);
::core::ptr::copy(src, dst, length);
buf.set_len(
buf.len()
.unchecked_sub(usize::VARINT_MAX_LEN)
.unchecked_add(len),
);
}
}
}
};
($ty:ty, $proto_ty:ident,64, $encode_fn:ident, $decode_fn:ident) => {
pub mod $proto_ty {
use super::*;
#[inline(always)]
pub fn encode_varint(value: $ty, buf: &mut impl BufMut) -> usize {
encode_varint64($encode_fn(value), buf)
}
#[inline(always)]
pub const fn encoded_len_varint(value: $ty) -> usize { encoded_len_varint64($encode_fn(value)) }
#[inline(always)]
pub fn decode_varint(buf: &mut impl Buf) -> Result<$ty, DecodeError> {
decode_varint64(buf).map($decode_fn)
}
#[inline]
pub(in super::super) fn encode_packed_fast(values: &[$ty], buf: &mut impl ReservableBuf) {
let start_ptr = buf.as_mut().as_mut_ptr();
buf.reserve(usize::VARINT_MAX_LEN);
unsafe {
buf.set_len(buf.len() + usize::VARINT_MAX_LEN);
}
let mut length = 0;
for &value in values {
length += encode_varint(value, buf);
}
let mut length_slice = unsafe {
&mut *(start_ptr as *mut [::core::mem::MaybeUninit<u8>; usize::VARINT_MAX_LEN])
as &mut [::core::mem::MaybeUninit<u8>]
};
let len = usize::encode_varint(length, &mut length_slice);
unsafe {
let dst = start_ptr.add(len);
let src = start_ptr.add(usize::VARINT_MAX_LEN);
::core::ptr::copy(src, dst, length);
buf.set_len(
buf.len()
.unchecked_sub(usize::VARINT_MAX_LEN)
.unchecked_add(len),
);
}
}
}
};
}
varint!(i32, int32, 32);
varint!(i64, int64, 64);
varint!(u32, uint32, 32);
varint!(u64, uint64, 64);
varint!(i32, sint32, 32, encode_zigzag32, decode_zigzag32);
varint!(i64, sint64, 64, encode_zigzag64, decode_zigzag64);
pub(super) trait ReservableBuf: Sized + BufMut + AsMut<[u8]> {
fn reserve(&mut self, additional: usize);
fn len(&self) -> usize;
unsafe fn set_len(&mut self, len: usize);
}
impl ReservableBuf for ::bytes::BytesMut {
#[inline(always)]
fn reserve(&mut self, additional: usize) { Self::reserve(self, additional); }
#[inline(always)]
fn len(&self) -> usize { Self::len(self) }
#[inline(always)]
unsafe fn set_len(&mut self, len: usize) { Self::set_len(self, len); }
}
impl ReservableBuf for ::alloc::vec::Vec<u8> {
#[inline(always)]
fn reserve(&mut self, additional: usize) { Self::reserve(self, additional); }
#[inline(always)]
fn len(&self) -> usize { Self::len(self) }
#[inline(always)]
unsafe fn set_len(&mut self, len: usize) { Self::set_len(self, len); }
}

View File

@@ -0,0 +1,70 @@
use alloc::format;
use crate::DecodeError;
/// Represent the wire type for protobuf encoding.
///
/// The integer value is equvilant with the encoded value.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum WireType {
Varint = 0,
SixtyFourBit = 1,
LengthDelimited = 2,
StartGroup = 3,
EndGroup = 4,
ThirtyTwoBit = 5,
}
impl WireType {
#[inline]
const fn try_from(value: u8) -> Option<Self> {
match value {
0 => Some(WireType::Varint),
1 => Some(WireType::SixtyFourBit),
2 => Some(WireType::LengthDelimited),
3 => Some(WireType::StartGroup),
4 => Some(WireType::EndGroup),
5 => Some(WireType::ThirtyTwoBit),
_ => None,
}
}
#[inline]
pub fn try_from_tag(tag: u32) -> Result<(Self, u32), DecodeError> {
let value = (tag & super::WireTypeMask) as u8;
match Self::try_from(value) {
Some(wire_type) => Ok((wire_type, tag >> super::WireTypeBits)),
None => Err(DecodeError::new(format!("invalid wire type value: {value}"))),
}
}
}
impl TryFrom<u32> for WireType {
type Error = DecodeError;
#[inline]
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
0 => Ok(WireType::Varint),
1 => Ok(WireType::SixtyFourBit),
2 => Ok(WireType::LengthDelimited),
3 => Ok(WireType::StartGroup),
4 => Ok(WireType::EndGroup),
5 => Ok(WireType::ThirtyTwoBit),
_ => Err(DecodeError::new(format!("invalid wire type value: {value}"))),
}
}
}
/// Checks that the expected wire type matches the actual wire type,
/// or returns an error result.
#[inline]
pub fn check_wire_type(expected: WireType, actual: WireType) -> Result<(), DecodeError> {
if expected != actual {
return Err(DecodeError::new(format!(
"invalid wire type: {actual:?} (expected {expected:?})",
)));
}
Ok(())
}

View File

@@ -0,0 +1,180 @@
//! Protobuf encoding and decoding errors.
use alloc::borrow::Cow;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use core::fmt;
/// A Protobuf message decoding error.
///
/// `DecodeError` indicates that the input buffer does not contain a valid
/// Protobuf message. The error details should be considered 'best effort': in
/// general it is not possible to exactly pinpoint why data is malformed.
#[derive(Clone, PartialEq, Eq)]
pub struct DecodeError {
inner: Box<Inner>,
}
#[derive(Clone, PartialEq, Eq)]
struct Inner {
/// A 'best effort' root cause description.
description: Cow<'static, str>,
/// A stack of (message, field) name pairs, which identify the specific
/// message type and field where decoding failed. The stack contains an
/// entry per level of nesting.
stack: Vec<(&'static str, &'static str)>,
}
impl DecodeError {
/// Creates a new `DecodeError` with a 'best effort' root cause description.
///
/// Meant to be used only by `Message` implementations.
#[doc(hidden)]
#[cold]
pub fn new(description: impl Into<Cow<'static, str>>) -> DecodeError {
DecodeError {
inner: Box::new(Inner {
description: description.into(),
stack: Vec::new(),
}),
}
}
/// Pushes a (message, field) name location pair on to the location stack.
///
/// Meant to be used only by `Message` implementations.
#[doc(hidden)]
pub fn push(&mut self, message: &'static str, field: &'static str) {
self.inner.stack.push((message, field));
}
}
impl fmt::Debug for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DecodeError")
.field("description", &self.inner.description)
.field("stack", &self.inner.stack)
.finish()
}
}
impl fmt::Display for DecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("failed to decode Protobuf message: ")?;
for &(message, field) in &self.inner.stack {
write!(f, "{}.{}: ", message, field)?;
}
f.write_str(&self.inner.description)
}
}
#[cfg(feature = "std")]
impl std::error::Error for DecodeError {}
#[cfg(feature = "std")]
impl From<DecodeError> for std::io::Error {
fn from(error: DecodeError) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::InvalidData, error)
}
}
/// A Protobuf message encoding error.
///
/// `EncodeError` always indicates that a message failed to encode because the
/// provided buffer had insufficient capacity. Message encoding is otherwise
/// infallible.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct EncodeError {
required: usize,
remaining: usize,
}
impl EncodeError {
/// Creates a new `EncodeError`.
pub(crate) fn new(required: usize, remaining: usize) -> EncodeError {
EncodeError {
required,
remaining,
}
}
/// Returns the required buffer capacity to encode the message.
pub fn required_capacity(&self) -> usize {
self.required
}
/// Returns the remaining length in the provided buffer at the time of encoding.
pub fn remaining(&self) -> usize {
self.remaining
}
}
impl fmt::Display for EncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"failed to encode Protobuf message; insufficient buffer capacity (required: {}, remaining: {})",
self.required, self.remaining
)
}
}
#[cfg(feature = "std")]
impl std::error::Error for EncodeError {}
#[cfg(feature = "std")]
impl From<EncodeError> for std::io::Error {
fn from(error: EncodeError) -> std::io::Error {
std::io::Error::new(std::io::ErrorKind::InvalidInput, error)
}
}
/// An error indicating that an unknown enumeration value was encountered.
///
/// The Protobuf spec mandates that enumeration value sets are open, so this
/// error's value represents an integer value unrecognized by the
/// presently used enum definition.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct UnknownEnumValue(pub i32);
impl fmt::Display for UnknownEnumValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unknown enumeration value {}", self.0)
}
}
#[cfg(feature = "std")]
impl std::error::Error for UnknownEnumValue {}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_push() {
let mut decode_error = DecodeError::new("something failed");
decode_error.push("Foo bad", "bar.foo");
decode_error.push("Baz bad", "bar.baz");
assert_eq!(
decode_error.to_string(),
"failed to decode Protobuf message: Foo bad.bar.foo: Baz bad.bar.baz: something failed"
);
}
#[cfg(feature = "std")]
#[test]
fn test_into_std_io_error() {
let decode_error = DecodeError::new("something failed");
let std_io_error = std::io::Error::from(decode_error);
assert_eq!(std_io_error.kind(), std::io::ErrorKind::InvalidData);
assert_eq!(
std_io_error.to_string(),
"failed to decode Protobuf message: something failed"
);
}
}

View File

@@ -0,0 +1,54 @@
#![allow(internal_features, unsafe_op_in_unsafe_fn)]
#![feature(core_intrinsics, uint_bit_width, portable_simd, pattern, char_internals)]
#![doc(html_root_url = "https://docs.rs/prost/0.14.1")]
#![cfg_attr(not(feature = "std"), no_std)]
#![doc = include_str!("../README.md")]
// Re-export the alloc crate for use within derived code.
#[doc(hidden)]
pub extern crate alloc;
// Re-export the bytes crate for use within derived code.
pub use bytes;
// Re-export the alloc crate for use within derived code.
#[cfg(feature = "indexmap")]
#[doc(hidden)]
pub use indexmap;
mod error;
mod message;
// mod name;
mod types;
mod byte_str;
#[doc(hidden)]
pub mod encoding;
pub use crate::encoding::length_delimiter::{
decode_length_delimiter, encode_length_delimiter, length_delimiter_len,
};
pub use crate::error::{DecodeError, EncodeError, UnknownEnumValue};
pub use crate::message::Message;
// pub use crate::name::Name;
pub use crate::byte_str::ByteStr;
// See `encoding::DecodeContext` for more info.
// 100 is the default recursion limit in the C++ implementation.
#[cfg(not(feature = "no-recursion-limit"))]
const RECURSION_LIMIT: u32 = 100;
// Re-export #[derive(Message, Enumeration, Oneof)].
// Based on serde's equivalent re-export [1], but enabled by default.
//
// [1]: https://github.com/serde-rs/serde/blob/v1.0.89/serde/src/lib.rs#L245-L256
#[cfg(feature = "derive")]
#[allow(unused_imports)]
#[macro_use]
extern crate prost_derive;
#[cfg(feature = "derive")]
#[doc(hidden)]
pub use prost_derive::*;
#[macro_use]
extern crate macros;

View File

@@ -0,0 +1,184 @@
use core::num::NonZeroU32;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use bytes::{Buf, BufMut};
use crate::{
DecodeError, EncodeError,
encoding::{
DecodeContext, decode_tag, message,
varint::usize::{encode_varint, encoded_len_varint},
wire_type::WireType,
},
};
/// A Protocol Buffers message.
pub trait Message: Send + Sync {
/// Encodes the message to a buffer.
///
/// This method will panic if the buffer has insufficient capacity.
///
/// Meant to be used only by `Message` implementations.
#[doc(hidden)]
fn encode_raw(&self, buf: &mut impl BufMut)
where
Self: Sized;
/// Decodes a field from a buffer, and merges it into `self`.
///
/// Meant to be used only by `Message` implementations.
#[doc(hidden)]
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError>
where
Self: Sized;
/// Returns the encoded length of the message without a length delimiter.
fn encoded_len(&self) -> usize;
/// Encodes the message to a buffer.
///
/// An error will be returned if the buffer does not have sufficient capacity.
fn encode(&self, buf: &mut impl BufMut) -> Result<(), EncodeError>
where
Self: Sized,
{
let required = self.encoded_len();
let remaining = buf.remaining_mut();
if required > remaining {
return Err(EncodeError::new(required, remaining));
}
self.encode_raw(buf);
Ok(())
}
/// Encodes the message to a newly allocated buffer.
fn encode_to_vec(&self) -> Vec<u8>
where
Self: Sized,
{
let mut buf = Vec::with_capacity(self.encoded_len());
self.encode_raw(&mut buf);
buf
}
/// Encodes the message with a length-delimiter to a buffer.
///
/// An error will be returned if the buffer does not have sufficient capacity.
fn encode_length_delimited(&self, buf: &mut impl BufMut) -> Result<(), EncodeError>
where
Self: Sized,
{
let len = self.encoded_len();
let required = len + encoded_len_varint(len);
let remaining = buf.remaining_mut();
if required > remaining {
return Err(EncodeError::new(required, remaining));
}
encode_varint(len, buf);
self.encode_raw(buf);
Ok(())
}
/// Encodes the message with a length-delimiter to a newly allocated buffer.
fn encode_length_delimited_to_vec(&self) -> Vec<u8>
where
Self: Sized,
{
let len = self.encoded_len();
let mut buf = Vec::with_capacity(len + encoded_len_varint(len));
encode_varint(len, &mut buf);
self.encode_raw(&mut buf);
buf
}
/// Decodes an instance of the message from a buffer.
///
/// The entire buffer will be consumed.
fn decode(mut buf: impl Buf) -> Result<Self, DecodeError>
where
Self: Default,
{
let mut message = Self::default();
Self::merge(&mut message, &mut buf).map(|_| message)
}
/// Decodes a length-delimited instance of the message from the buffer.
fn decode_length_delimited(buf: impl Buf) -> Result<Self, DecodeError>
where
Self: Default,
{
let mut message = Self::default();
message.merge_length_delimited(buf)?;
Ok(message)
}
/// Decodes an instance of the message from a buffer, and merges it into `self`.
///
/// The entire buffer will be consumed.
fn merge(&mut self, mut buf: impl Buf) -> Result<(), DecodeError>
where
Self: Sized,
{
let ctx = DecodeContext::default();
while buf.has_remaining() {
let (number, wire_type) = decode_tag(&mut buf)?;
self.merge_field(number, wire_type, &mut buf, ctx.clone())?;
}
Ok(())
}
/// Decodes a length-delimited instance of the message from buffer, and
/// merges it into `self`.
fn merge_length_delimited(&mut self, mut buf: impl Buf) -> Result<(), DecodeError>
where
Self: Sized,
{
message::merge(
WireType::LengthDelimited,
self,
&mut buf,
DecodeContext::default(),
)
}
/// Clears the message, resetting all fields to their default.
fn clear(&mut self);
}
impl<M> Message for Box<M>
where
M: Message,
{
fn encode_raw(&self, buf: &mut impl BufMut) { (**self).encode_raw(buf) }
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
(**self).merge_field(number, wire_type, buf, ctx)
}
fn encoded_len(&self) -> usize { (**self).encoded_len() }
fn clear(&mut self) { (**self).clear() }
}
#[cfg(test)]
mod tests {
use super::*;
const _MESSAGE_IS_OBJECT_SAFE: Option<&dyn Message> = None;
}

View File

@@ -0,0 +1,34 @@
//! Support for associating type name information with a [`Message`].
use crate::Message;
#[cfg(not(feature = "std"))]
use alloc::{format, string::String};
/// Associate a type name with a [`Message`] type.
pub trait Name: Message {
/// Simple name for this [`Message`].
/// This name is the same as it appears in the source .proto file, e.g. `FooBar`.
const NAME: &'static str;
/// Package name this message type is contained in. They are domain-like
/// and delimited by `.`, e.g. `google.protobuf`.
const PACKAGE: &'static str;
/// Fully-qualified unique name for this [`Message`].
/// It's prefixed with the package name and names of any parent messages,
/// e.g. `google.rpc.BadRequest.FieldViolation`.
/// By default, this is the package name followed by the message name.
/// Fully-qualified names must be unique within a domain of Type URLs.
fn full_name() -> String {
format!("{}.{}", Self::PACKAGE, Self::NAME)
}
/// Type URL for this [`Message`], which by default is the full name with a
/// leading slash, but may also include a leading domain name, e.g.
/// `type.googleapis.com/google.profile.Person`.
/// This can be used when serializing into the `google.protobuf.Any` type.
fn type_url() -> String {
format!("/{}", Self::full_name())
}
}

View File

@@ -0,0 +1,573 @@
//! Protocol Buffers well-known wrapper types.
//!
//! This module provides implementations of `Message` for Rust standard library types which
//! correspond to a Protobuf well-known wrapper type. The remaining well-known types are defined in
//! the `prost-types` crate in order to avoid a cyclic dependency between `prost` and
//! `prost-build`.
use core::num::NonZeroU32;
// use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use ::bytes::{Buf, BufMut, Bytes};
use crate::encoding::wire_type::WireType;
use crate::encoding::FieldNumber1;
use crate::{
encoding::{
bool, bytes, double, float, int32, int64, skip_field, string, uint32, uint64, DecodeContext,
},
DecodeError, Message,
};
/// `google.protobuf.BoolValue`
impl Message for bool {
fn encode_raw(&self, buf: &mut impl BufMut) {
if *self {
bool::encode(FieldNumber1, self, buf)
}
}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
if number == FieldNumber1 {
bool::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, number, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
if *self {
2
} else {
0
}
}
fn clear(&mut self) {
*self = false;
}
}
// /// `google.protobuf.BoolValue`
// impl Name for bool {
// const NAME: &'static str = "BoolValue";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
/// `google.protobuf.UInt32Value`
impl Message for u32 {
fn encode_raw(&self, buf: &mut impl BufMut) {
if *self != 0 {
uint32::encode(FieldNumber1, self, buf)
}
}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
if number == FieldNumber1 {
uint32::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, number, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
if *self != 0 {
uint32::encoded_len(FieldNumber1, self)
} else {
0
}
}
fn clear(&mut self) {
*self = 0;
}
}
// /// `google.protobuf.UInt32Value`
// impl Name for u32 {
// const NAME: &'static str = "UInt32Value";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
/// `google.protobuf.UInt64Value`
impl Message for u64 {
fn encode_raw(&self, buf: &mut impl BufMut) {
if *self != 0 {
uint64::encode(FieldNumber1, self, buf)
}
}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
if number == FieldNumber1 {
uint64::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, number, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
if *self != 0 {
uint64::encoded_len(FieldNumber1, self)
} else {
0
}
}
fn clear(&mut self) {
*self = 0;
}
}
// /// `google.protobuf.UInt64Value`
// impl Name for u64 {
// const NAME: &'static str = "UInt64Value";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
/// `google.protobuf.Int32Value`
impl Message for i32 {
fn encode_raw(&self, buf: &mut impl BufMut) {
if *self != 0 {
int32::encode(FieldNumber1, self, buf)
}
}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
if number == FieldNumber1 {
int32::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, number, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
if *self != 0 {
int32::encoded_len(FieldNumber1, self)
} else {
0
}
}
fn clear(&mut self) {
*self = 0;
}
}
// /// `google.protobuf.Int32Value`
// impl Name for i32 {
// const NAME: &'static str = "Int32Value";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
/// `google.protobuf.Int64Value`
impl Message for i64 {
fn encode_raw(&self, buf: &mut impl BufMut) {
if *self != 0 {
int64::encode(FieldNumber1, self, buf)
}
}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
if number == FieldNumber1 {
int64::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, number, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
if *self != 0 {
int64::encoded_len(FieldNumber1, self)
} else {
0
}
}
fn clear(&mut self) {
*self = 0;
}
}
// /// `google.protobuf.Int64Value`
// impl Name for i64 {
// const NAME: &'static str = "Int64Value";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
/// `google.protobuf.FloatValue`
impl Message for f32 {
fn encode_raw(&self, buf: &mut impl BufMut) {
if *self != 0.0 {
float::encode(FieldNumber1, self, buf)
}
}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
if number == FieldNumber1 {
float::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, number, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
if *self != 0.0 {
float::encoded_len(FieldNumber1, self)
} else {
0
}
}
fn clear(&mut self) {
*self = 0.0;
}
}
// /// `google.protobuf.FloatValue`
// impl Name for f32 {
// const NAME: &'static str = "FloatValue";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
/// `google.protobuf.DoubleValue`
impl Message for f64 {
fn encode_raw(&self, buf: &mut impl BufMut) {
if *self != 0.0 {
double::encode(FieldNumber1, self, buf)
}
}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
if number == FieldNumber1 {
double::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, number, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
if *self != 0.0 {
double::encoded_len(FieldNumber1, self)
} else {
0
}
}
fn clear(&mut self) {
*self = 0.0;
}
}
// /// `google.protobuf.DoubleValue`
// impl Name for f64 {
// const NAME: &'static str = "DoubleValue";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
/// `google.protobuf.StringValue`
impl Message for String {
fn encode_raw(&self, buf: &mut impl BufMut) {
if !self.is_empty() {
string::encode(FieldNumber1, self, buf)
}
}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
if number == FieldNumber1 {
string::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, number, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
if !self.is_empty() {
string::encoded_len(FieldNumber1, self)
} else {
0
}
}
fn clear(&mut self) {
self.clear();
}
}
// /// `google.protobuf.StringValue`
// impl Name for String {
// const NAME: &'static str = "StringValue";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
/// `google.protobuf.BytesValue`
impl Message for Vec<u8> {
fn encode_raw(&self, buf: &mut impl BufMut) {
if !self.is_empty() {
bytes::encode(FieldNumber1, self, buf)
}
}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
if number == FieldNumber1 {
bytes::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, number, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
if !self.is_empty() {
bytes::encoded_len(FieldNumber1, self)
} else {
0
}
}
fn clear(&mut self) {
self.clear();
}
}
// /// `google.protobuf.BytesValue`
// impl Name for Vec<u8> {
// const NAME: &'static str = "BytesValue";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
/// `google.protobuf.BytesValue`
impl Message for Bytes {
fn encode_raw(&self, buf: &mut impl BufMut) {
if !self.is_empty() {
bytes::encode(FieldNumber1, self, buf)
}
}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
if number == FieldNumber1 {
bytes::merge(wire_type, self, buf, ctx)
} else {
skip_field(wire_type, number, buf, ctx)
}
}
fn encoded_len(&self) -> usize {
if !self.is_empty() {
bytes::encoded_len(FieldNumber1, self)
} else {
0
}
}
fn clear(&mut self) {
self.clear();
}
}
// /// `google.protobuf.BytesValue`
// impl Name for Bytes {
// const NAME: &'static str = "BytesValue";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
/// `google.protobuf.Empty`
impl Message for () {
fn encode_raw(&self, _buf: &mut impl BufMut) {}
fn merge_field(
&mut self,
number: NonZeroU32,
wire_type: WireType,
buf: &mut impl Buf,
ctx: DecodeContext,
) -> Result<(), DecodeError> {
skip_field(wire_type, number, buf, ctx)
}
fn encoded_len(&self) -> usize {
0
}
fn clear(&mut self) {}
}
// /// `google.protobuf.Empty`
// impl Name for () {
// const NAME: &'static str = "Empty";
// const PACKAGE: &'static str = "google.protobuf";
// fn type_url() -> String {
// googleapis_type_url_for::<Self>()
// }
// }
// /// Compute the type URL for the given `google.protobuf` type, using `type.googleapis.com` as the
// /// authority for the URL.
// fn googleapis_type_url_for<T: Name>() -> String {
// format!("type.googleapis.com/{}.{}", T::PACKAGE, T::NAME)
// }
// #[cfg(test)]
// mod tests {
// use super::*;
// #[test]
// fn test_impl_name() {
// assert_eq!("BoolValue", bool::NAME);
// assert_eq!("google.protobuf", bool::PACKAGE);
// assert_eq!("google.protobuf.BoolValue", bool::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.BoolValue",
// bool::type_url()
// );
// assert_eq!("UInt32Value", u32::NAME);
// assert_eq!("google.protobuf", u32::PACKAGE);
// assert_eq!("google.protobuf.UInt32Value", u32::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.UInt32Value",
// u32::type_url()
// );
// assert_eq!("UInt64Value", u64::NAME);
// assert_eq!("google.protobuf", u64::PACKAGE);
// assert_eq!("google.protobuf.UInt64Value", u64::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.UInt64Value",
// u64::type_url()
// );
// assert_eq!("Int32Value", i32::NAME);
// assert_eq!("google.protobuf", i32::PACKAGE);
// assert_eq!("google.protobuf.Int32Value", i32::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.Int32Value",
// i32::type_url()
// );
// assert_eq!("Int64Value", i64::NAME);
// assert_eq!("google.protobuf", i64::PACKAGE);
// assert_eq!("google.protobuf.Int64Value", i64::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.Int64Value",
// i64::type_url()
// );
// assert_eq!("FloatValue", f32::NAME);
// assert_eq!("google.protobuf", f32::PACKAGE);
// assert_eq!("google.protobuf.FloatValue", f32::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.FloatValue",
// f32::type_url()
// );
// assert_eq!("DoubleValue", f64::NAME);
// assert_eq!("google.protobuf", f64::PACKAGE);
// assert_eq!("google.protobuf.DoubleValue", f64::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.DoubleValue",
// f64::type_url()
// );
// assert_eq!("StringValue", String::NAME);
// assert_eq!("google.protobuf", String::PACKAGE);
// assert_eq!("google.protobuf.StringValue", String::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.StringValue",
// String::type_url()
// );
// assert_eq!("BytesValue", Vec::<u8>::NAME);
// assert_eq!("google.protobuf", Vec::<u8>::PACKAGE);
// assert_eq!("google.protobuf.BytesValue", Vec::<u8>::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.BytesValue",
// Vec::<u8>::type_url()
// );
// assert_eq!("BytesValue", Bytes::NAME);
// assert_eq!("google.protobuf", Bytes::PACKAGE);
// assert_eq!("google.protobuf.BytesValue", Bytes::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.BytesValue",
// Bytes::type_url()
// );
// assert_eq!("Empty", <()>::NAME);
// assert_eq!("google.protobuf", <()>::PACKAGE);
// assert_eq!("google.protobuf.Empty", <()>::full_name());
// assert_eq!(
// "type.googleapis.com/google.protobuf.Empty",
// <()>::type_url()
// );
// }
// }

View File

@@ -0,0 +1,25 @@
[package]
name = "prost-derive"
readme = "README.md"
description = "Generate encoding and decoding implementations for Prost annotated types."
version = "0.14.1"
authors = [
"Dan Burkert <dan@danburkert.com>",
"Lucio Franco <luciofranco14@gmail.com>",
"Casper Meijn <casper@meijn.net>",
"Tokio Contributors <team@tokio.rs>",
]
license = "Apache-2.0"
repository = "https://github.com/tokio-rs/prost"
edition = "2021"
rust-version = "1.71.1"
[lib]
proc-macro = true
[dependencies]
anyhow = "1.0.1"
itertools = ">=0.10.1, <=0.14"
proc-macro2 = "1.0"
quote = "1"
syn = { version = "2", features = ["extra-traits"] }

201
patch/prost-derive/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Some files were not shown because too many files have changed in this diff Show More