mirror of
https://github.com/wisdgod/cursor-api.git
synced 2025-12-24 13:38:01 +08:00
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:
80
.env.example
80
.env.example
@@ -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
6
.gitignore
vendored
@@ -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
2
.rust-toolchain.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "nightly"
|
||||
@@ -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
|
||||
|
||||
242
Cargo.toml
242
Cargo.toml
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
6
LICENSE
6
LICENSE
@@ -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.
|
||||
202
LICENSE-APACHE
202
LICENSE-APACHE
@@ -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.
|
||||
@@ -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
20
LICENSE.md
Normal 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.
|
||||
178
build.rs
178
build.rs
@@ -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()?;
|
||||
|
||||
201
build_info.rs
201
build_info.rs
@@ -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"),
|
||||
|
||||
13
crates/grpc-stream/Cargo.toml
Normal file
13
crates/grpc-stream/Cargo.toml
Normal 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"
|
||||
195
crates/grpc-stream/src/buffer.rs
Normal file
195
crates/grpc-stream/src/buffer.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
154
crates/grpc-stream/src/compression.rs
Normal file
154
crates/grpc-stream/src/compression.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
135
crates/grpc-stream/src/decoder.rs
Normal file
135
crates/grpc-stream/src/decoder.rs
Normal 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() }
|
||||
}
|
||||
54
crates/grpc-stream/src/frame.rs
Normal file
54
crates/grpc-stream/src/frame.rs
Normal 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()
|
||||
}
|
||||
}
|
||||
46
crates/grpc-stream/src/lib.rs
Normal file
46
crates/grpc-stream/src/lib.rs
Normal 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
|
||||
54
crates/interned/Cargo.toml
Normal file
54
crates/interned/Cargo.toml
Normal 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
|
||||
1119
crates/interned/src/arc_str.rs
Normal file
1119
crates/interned/src/arc_str.rs
Normal file
File diff suppressed because it is too large
Load Diff
29
crates/interned/src/lib.rs
Normal file
29
crates/interned/src/lib.rs
Normal 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
1248
crates/interned/src/str.rs
Normal file
File diff suppressed because it is too large
Load Diff
17
crates/manually_init/Cargo.toml
Normal file
17
crates/manually_init/Cargo.toml
Normal 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
|
||||
363
crates/manually_init/src/lib.rs
Normal file
363
crates/manually_init/src/lib.rs
Normal 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() }
|
||||
}
|
||||
}
|
||||
10
crates/rep_move/Cargo.toml
Normal file
10
crates/rep_move/Cargo.toml
Normal 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
246
crates/rep_move/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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"] }
|
||||
@@ -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,
|
||||
@@ -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;
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -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:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)]
|
||||
@@ -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 {{ .. }}")
|
||||
@@ -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
|
||||
@@ -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();
|
||||
@@ -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)))
|
||||
@@ -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>(())
|
||||
/// ```
|
||||
@@ -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]
|
||||
@@ -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)))
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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)))
|
||||
@@ -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:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)))
|
||||
@@ -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};
|
||||
@@ -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 {
|
||||
@@ -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:?}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
10
patch/macros/Cargo.toml
Normal 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
105
patch/macros/src/lib.rs
Normal 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() }
|
||||
};
|
||||
}
|
||||
87
patch/prost-0.14.1/Cargo.toml
Normal file
87
patch/prost-0.14.1/Cargo.toml
Normal 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
35
patch/prost-0.14.1/Cargo.toml.orig
generated
Normal 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
201
patch/prost-0.14.1/LICENSE
Normal 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.
|
||||
507
patch/prost-0.14.1/README.md
Normal file
507
patch/prost-0.14.1/README.md
Normal file
@@ -0,0 +1,507 @@
|
||||
[](https://github.com/tokio-rs/prost/actions/workflows/ci.yml?query=branch%3Amaster)
|
||||
[](https://docs.rs/prost/)
|
||||
[](https://crates.io/crates/prost)
|
||||
[](https://deps.rs/repo/github/tokio-rs/prost)
|
||||
[](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
|
||||
366
patch/prost-0.14.1/src/byte_str.rs
Normal file
366
patch/prost-0.14.1/src/byte_str.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
1471
patch/prost-0.14.1/src/encoding.rs
Normal file
1471
patch/prost-0.14.1/src/encoding.rs
Normal file
File diff suppressed because it is too large
Load Diff
31
patch/prost-0.14.1/src/encoding/fixed_width.rs
Normal file
31
patch/prost-0.14.1/src/encoding/fixed_width.rs
Normal 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);
|
||||
46
patch/prost-0.14.1/src/encoding/length_delimiter.rs
Normal file
46
patch/prost-0.14.1/src/encoding/length_delimiter.rs
Normal 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) }
|
||||
216
patch/prost-0.14.1/src/encoding/utf8.rs
Normal file
216
patch/prost-0.14.1/src/encoding/utf8.rs
Normal 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() }
|
||||
1847
patch/prost-0.14.1/src/encoding/utf8/ascii.rs
Normal file
1847
patch/prost-0.14.1/src/encoding/utf8/ascii.rs
Normal file
File diff suppressed because it is too large
Load Diff
347
patch/prost-0.14.1/src/encoding/utf8/simd_funcs.rs
Normal file
347
patch/prost-0.14.1/src/encoding/utf8/simd_funcs.rs
Normal 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]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
667
patch/prost-0.14.1/src/encoding/varint.rs
Normal file
667
patch/prost-0.14.1/src/encoding/varint.rs
Normal 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); }
|
||||
}
|
||||
70
patch/prost-0.14.1/src/encoding/wire_type.rs
Normal file
70
patch/prost-0.14.1/src/encoding/wire_type.rs
Normal 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(())
|
||||
}
|
||||
180
patch/prost-0.14.1/src/error.rs
Normal file
180
patch/prost-0.14.1/src/error.rs
Normal 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
54
patch/prost-0.14.1/src/lib.rs
Normal file
54
patch/prost-0.14.1/src/lib.rs
Normal 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;
|
||||
184
patch/prost-0.14.1/src/message.rs
Normal file
184
patch/prost-0.14.1/src/message.rs
Normal 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;
|
||||
}
|
||||
34
patch/prost-0.14.1/src/name.rs
Normal file
34
patch/prost-0.14.1/src/name.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
573
patch/prost-0.14.1/src/types.rs
Normal file
573
patch/prost-0.14.1/src/types.rs
Normal 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()
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
25
patch/prost-derive/Cargo.toml
Normal file
25
patch/prost-derive/Cargo.toml
Normal 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
201
patch/prost-derive/LICENSE
Normal 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
Reference in New Issue
Block a user