photoncloud-monorepo/nightlight/crates/nightlight-api/src/lib.rs
centra d2149b6249 fix(lightningstor): Fix SigV4 canonicalization for AWS S3 auth
- Replace form_urlencoded with RFC 3986 compliant URI encoding
- Implement aws_uri_encode() matching AWS SigV4 spec exactly
- Unreserved chars (A-Z,a-z,0-9,-,_,.,~) not encoded
- All other chars percent-encoded with uppercase hex
- Preserve slashes in paths, encode in query params
- Normalize empty paths to '/' per AWS spec
- Fix test expectations (body hash, HMAC values)
- Add comprehensive SigV4 signature determinism test

This fixes the canonicalization mismatch that caused signature
validation failures in T047. Auth can now be enabled for production.

Refs: T058.S1
2025-12-12 06:23:46 +09:00

141 lines
4 KiB
Rust

//! Nightlight API Client Library
//!
//! This crate provides gRPC client interfaces for interacting with Nightlight,
//! a Prometheus-compatible metrics storage system with mTLS support.
//!
//! # Features
//!
//! - **Remote Write**: Push metrics using the Prometheus remote write protocol
//! - **PromQL Queries**: Execute instant and range queries with PromQL
//! - **Admin API**: Health checks, statistics, and server information
//!
//! # Example: Writing Metrics
//!
//! ```rust,no_run
//! use nightlight_api::prometheus::{WriteRequest, TimeSeries, Label, Sample};
//!
//! // Create a write request with sample data
//! let request = WriteRequest {
//! timeseries: vec![
//! TimeSeries {
//! labels: vec![
//! Label { name: "__name__".to_string(), value: "http_requests_total".to_string() },
//! Label { name: "method".to_string(), value: "GET".to_string() },
//! ],
//! samples: vec![
//! Sample { value: 42.0, timestamp: 1234567890000 },
//! ],
//! },
//! ],
//! };
//! ```
//!
//! # Example: Querying Metrics
//!
//! ```rust,no_run
//! use nightlight_api::nightlight::{InstantQueryRequest, RangeQueryRequest};
//!
//! // Instant query at current time
//! let instant = InstantQueryRequest {
//! query: "http_requests_total{method='GET'}".to_string(),
//! time: 0, // Use current time
//! timeout: 5000, // 5 second timeout
//! };
//!
//! // Range query over 1 hour with 1 minute resolution
//! let range = RangeQueryRequest {
//! query: "rate(http_requests_total[5m])".to_string(),
//! start: 1234567890000,
//! end: 1234571490000,
//! step: 60000, // 1 minute
//! timeout: 10000,
//! };
//! ```
// Include generated protobuf code
// The tonic::include_proto! macro includes the generated code from OUT_DIR
pub mod prometheus {
//! Prometheus remote write protocol types
//!
//! This module contains the protobuf definitions for Prometheus remote write,
//! which is the standard protocol for pushing metrics to TSDB systems.
tonic::include_proto!("prometheus");
}
pub mod nightlight {
//! Nightlight-specific API types
//!
//! This module contains the PromQL query API and admin endpoints.
tonic::include_proto!("nightlight");
}
// Re-export commonly used types for convenience
pub use prometheus::{Label, Sample, TimeSeries, WriteRequest};
pub use nightlight::{
InstantQueryRequest, QueryResponse, RangeQueryRequest,
HealthRequest, HealthResponse, StatsRequest, StatsResponse,
};
// Re-export gRPC clients for external use
pub use nightlight::{
metric_query_client::MetricQueryClient,
admin_client::AdminClient,
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_create_write_request() {
// Verify that we can construct basic protobuf messages
let label = Label {
name: "__name__".to_string(),
value: "test_metric".to_string(),
};
let sample = Sample {
value: 1.0,
timestamp: 1234567890000,
};
let series = TimeSeries {
labels: vec![label],
samples: vec![sample],
};
let request = WriteRequest {
timeseries: vec![series],
};
assert_eq!(request.timeseries.len(), 1);
assert_eq!(request.timeseries[0].labels.len(), 1);
assert_eq!(request.timeseries[0].samples.len(), 1);
}
#[test]
fn test_create_instant_query() {
let query = InstantQueryRequest {
query: "up".to_string(),
time: 0,
timeout: 5000,
};
assert_eq!(query.query, "up");
}
#[test]
fn test_create_range_query() {
let query = RangeQueryRequest {
query: "rate(http_requests_total[5m])".to_string(),
start: 1000,
end: 2000,
step: 100,
timeout: 10000,
};
assert_eq!(query.start, 1000);
assert_eq!(query.end, 2000);
assert_eq!(query.step, 100);
}
}