- 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
141 lines
4 KiB
Rust
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);
|
|
}
|
|
}
|