style(rust): Format all Rust code with rustfmt
Change-Id: Iab7e00cc26a4f9727d3ab98691ef379921a33052 Reviewed-on: https://cl.tvl.fyi/c/depot/+/5240 Tested-by: BuildkiteCI Reviewed-by: kanepyork <rikingcoding@gmail.com> Reviewed-by: Profpatsch <mail@profpatsch.de> Reviewed-by: grfn <grfn@gws.fyi> Reviewed-by: tazjin <tazjin@tvl.su>
This commit is contained in:
parent
3318982f81
commit
3d8ee62087
42 changed files with 1253 additions and 876 deletions
|
@ -4,17 +4,17 @@ use std::rc::Rc;
|
|||
use std::sync::RwLock;
|
||||
|
||||
struct Defer<F: Fn()> {
|
||||
f: F
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl <F: Fn()> Drop for Defer<F> {
|
||||
impl<F: Fn()> Drop for Defer<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.f)()
|
||||
}
|
||||
}
|
||||
|
||||
// Only added this for Go-syntax familiarity ;-)
|
||||
fn defer<F: Fn()>(f: F) -> Defer<F> {
|
||||
fn defer<F: Fn()>(f: F) -> Defer<F> {
|
||||
Defer { f }
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,9 @@ type ErrorHandle<T> = Rc<RwLock<Option<T>>>;
|
|||
///////////////////
|
||||
|
||||
#[derive(Debug)] // Debug trait for some default way to print the type.
|
||||
enum Error { DropError }
|
||||
enum Error {
|
||||
DropError,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Create a place to store the error.
|
||||
|
@ -60,7 +62,7 @@ fn main() {
|
|||
|
||||
match *drop_err.read().unwrap() {
|
||||
Some(ref err) => println!("Oh no, an error occured: {:?}!", err),
|
||||
None => println!("Phew, everything went well.")
|
||||
None => println!("Phew, everything went well."),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
// Go's defer in Rust!
|
||||
|
||||
struct Defer<F: Fn()> {
|
||||
f: F
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl <F: Fn()> Drop for Defer<F> {
|
||||
impl<F: Fn()> Drop for Defer<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.f)()
|
||||
}
|
||||
}
|
||||
|
||||
// Only added this for Go-syntax familiarity ;-)
|
||||
fn defer<F: Fn()>(f: F) -> Defer<F> {
|
||||
fn defer<F: Fn()>(f: F) -> Defer<F> {
|
||||
Defer { f }
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// Go's defer in Rust, with a little twist!
|
||||
|
||||
struct Defer<F: Fn()> {
|
||||
f: F
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl <F: Fn()> Drop for Defer<F> {
|
||||
impl<F: Fn()> Drop for Defer<F> {
|
||||
fn drop(&mut self) {
|
||||
(self.f)()
|
||||
}
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use crate::models::{Entry, Keyword, NewEntry, NewKeyword};
|
||||
use diesel::pg::PgConnection;
|
||||
use diesel::prelude::*;
|
||||
use failure::format_err;
|
||||
use failure::Error;
|
||||
use failure::{format_err, Error};
|
||||
use std::borrow::Cow;
|
||||
|
||||
/// Maximum number of times we'll follow a `see: ` pointer.
|
||||
|
|
|
@ -7,8 +7,7 @@ use crate::cfg::Config;
|
|||
use crate::keyword::KeywordDetails;
|
||||
use diesel::pg::PgConnection;
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use failure::format_err;
|
||||
use failure::Error;
|
||||
use failure::{format_err, Error};
|
||||
use irc::client::prelude::*;
|
||||
use lazy_static::lazy_static;
|
||||
use log::{debug, info, warn};
|
||||
|
@ -153,8 +152,9 @@ impl App {
|
|||
// Use `nick` here, so things like "grfn: see glittershark" work.
|
||||
let val = if let Some(last) = chan_lastmsgs.get(nick_to_grab) {
|
||||
if last.starts_with("\x01ACTION ") {
|
||||
// Yes, this is inefficient, but it's better than writing some hacky CTCP parsing code
|
||||
// I guess (also, characters are hard, so just blindly slicing seems like a bad idea)
|
||||
// Yes, this is inefficient, but it's better than writing some hacky CTCP parsing
|
||||
// code I guess (also, characters are hard, so just blindly slicing
|
||||
// seems like a bad idea)
|
||||
format!(
|
||||
"* {} {}",
|
||||
nick_to_grab,
|
||||
|
|
|
@ -67,23 +67,24 @@
|
|||
//!
|
||||
//! [JWKS]: https://tools.ietf.org/html/rfc7517
|
||||
|
||||
#[macro_use] extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
extern crate base64;
|
||||
extern crate openssl;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
|
||||
use base64::{URL_SAFE_NO_PAD, Config, DecodeError};
|
||||
use base64::{Config, DecodeError, URL_SAFE_NO_PAD};
|
||||
use openssl::bn::BigNum;
|
||||
use openssl::error::ErrorStack;
|
||||
use openssl::hash::MessageDigest;
|
||||
use openssl::pkey::{Public, PKey};
|
||||
use openssl::pkey::{PKey, Public};
|
||||
use openssl::rsa::Rsa;
|
||||
use openssl::sign::Verifier;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde_json::Value;
|
||||
use std::time::{UNIX_EPOCH, Duration, SystemTime};
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
@ -101,12 +102,16 @@ fn jwt_forgiving() -> Config {
|
|||
/// JWT algorithm used. The only supported algorithm is currently
|
||||
/// RS256.
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
enum KeyAlgorithm { RS256 }
|
||||
enum KeyAlgorithm {
|
||||
RS256,
|
||||
}
|
||||
|
||||
/// Type of key contained in a JWT. The only supported key type is
|
||||
/// currently RSA.
|
||||
#[derive(Clone, Deserialize, Debug)]
|
||||
enum KeyType { RSA }
|
||||
enum KeyType {
|
||||
RSA,
|
||||
}
|
||||
|
||||
/// Representation of a single JSON Web Key. See [RFC
|
||||
/// 7517](https://tools.ietf.org/html/rfc7517#section-4).
|
||||
|
@ -146,7 +151,7 @@ impl JWKS {
|
|||
|
||||
/// Representation of an undecoded JSON Web Token. See [RFC
|
||||
/// 7519](https://tools.ietf.org/html/rfc7519).
|
||||
struct JWT<'a> (&'a str);
|
||||
struct JWT<'a>(&'a str);
|
||||
|
||||
/// Representation of a decoded and validated JSON Web Token.
|
||||
///
|
||||
|
@ -217,15 +222,21 @@ pub enum ValidationError {
|
|||
type JWTResult<T> = Result<T, ValidationError>;
|
||||
|
||||
impl From<ErrorStack> for ValidationError {
|
||||
fn from(err: ErrorStack) -> Self { ValidationError::OpenSSL(err) }
|
||||
fn from(err: ErrorStack) -> Self {
|
||||
ValidationError::OpenSSL(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for ValidationError {
|
||||
fn from(err: serde_json::Error) -> Self { ValidationError::JSON(err) }
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
ValidationError::JSON(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DecodeError> for ValidationError {
|
||||
fn from(err: DecodeError) -> Self { ValidationError::InvalidBase64(err) }
|
||||
fn from(err: DecodeError) -> Self {
|
||||
ValidationError::InvalidBase64(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to extract the `kid`-claim out of a JWT's header claims.
|
||||
|
@ -266,9 +277,7 @@ pub fn token_kid(token: &str) -> JWTResult<Option<String>> {
|
|||
///
|
||||
/// It is the user's task to ensure that the correct JWK is passed in
|
||||
/// for validation.
|
||||
pub fn validate(token: &str,
|
||||
jwk: &JWK,
|
||||
validations: Vec<Validation>) -> JWTResult<ValidJWT> {
|
||||
pub fn validate(token: &str, jwk: &JWK, validations: Vec<Validation>) -> JWTResult<ValidJWT> {
|
||||
let jwt = JWT(token);
|
||||
let public_key = public_key_from_jwk(&jwk)?;
|
||||
validate_jwt_signature(&jwt, public_key)?;
|
||||
|
@ -279,7 +288,7 @@ pub fn validate(token: &str,
|
|||
if parts.len() != 3 {
|
||||
// This is unlikely considering that validation has already
|
||||
// been performed at this point, but better safe than sorry.
|
||||
return Err(ValidationError::InvalidComponents)
|
||||
return Err(ValidationError::InvalidComponents);
|
||||
}
|
||||
|
||||
// Perform claim validations before constructing the valid token:
|
||||
|
@ -351,7 +360,7 @@ fn validate_jwt_signature(jwt: &JWT, key: Rsa<Public>) -> JWTResult<()> {
|
|||
verifier.update(data.as_bytes())?;
|
||||
|
||||
match verifier.verify(&sig)? {
|
||||
true => Ok(()),
|
||||
true => Ok(()),
|
||||
false => Err(ValidationError::InvalidSignature),
|
||||
}
|
||||
}
|
||||
|
@ -362,7 +371,7 @@ fn validate_jwt_signature(jwt: &JWT, key: Rsa<Public>) -> JWTResult<()> {
|
|||
#[serde(untagged)]
|
||||
enum Audience {
|
||||
Single(String),
|
||||
Multi(Vec<String>)
|
||||
Multi(Vec<String>),
|
||||
}
|
||||
|
||||
/// Internal helper struct for claims that are relevant for claim
|
||||
|
@ -376,15 +385,14 @@ struct PartialClaims {
|
|||
}
|
||||
|
||||
/// Apply a single validation to the claim set of a token.
|
||||
fn apply_validation(claims: &PartialClaims,
|
||||
validation: Validation) -> Result<(), &'static str> {
|
||||
fn apply_validation(claims: &PartialClaims, validation: Validation) -> Result<(), &'static str> {
|
||||
match validation {
|
||||
// Validate that an 'iss' claim is present and matches the
|
||||
// supplied value.
|
||||
Validation::Issuer(iss) => {
|
||||
match claims.iss {
|
||||
None => Err("'iss' claim is missing"),
|
||||
Some(ref claim) => if *claim == iss {
|
||||
Validation::Issuer(iss) => match claims.iss {
|
||||
None => Err("'iss' claim is missing"),
|
||||
Some(ref claim) => {
|
||||
if *claim == iss {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("'iss' claim does not match")
|
||||
|
@ -394,15 +402,17 @@ fn apply_validation(claims: &PartialClaims,
|
|||
|
||||
// Validate that an 'aud' claim is present and matches the
|
||||
// supplied value.
|
||||
Validation::Audience(aud) => {
|
||||
match claims.aud {
|
||||
None => Err("'aud' claim is missing"),
|
||||
Some(Audience::Single(ref claim)) => if *claim == aud {
|
||||
Validation::Audience(aud) => match claims.aud {
|
||||
None => Err("'aud' claim is missing"),
|
||||
Some(Audience::Single(ref claim)) => {
|
||||
if *claim == aud {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("'aud' claim does not match")
|
||||
},
|
||||
Some(Audience::Multi(ref claims)) => if claims.contains(&aud) {
|
||||
}
|
||||
}
|
||||
Some(Audience::Multi(ref claims)) => {
|
||||
if claims.contains(&aud) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("'aud' claim does not match")
|
||||
|
@ -447,12 +457,12 @@ fn apply_validation(claims: &PartialClaims,
|
|||
}
|
||||
|
||||
/// Apply all requested validations to a partial claim set.
|
||||
fn validate_claims(claims: PartialClaims,
|
||||
validations: Vec<Validation>) -> JWTResult<()> {
|
||||
let validation_errors: Vec<_> = validations.into_iter()
|
||||
fn validate_claims(claims: PartialClaims, validations: Vec<Validation>) -> JWTResult<()> {
|
||||
let validation_errors: Vec<_> = validations
|
||||
.into_iter()
|
||||
.map(|v| apply_validation(&claims, v))
|
||||
.filter_map(|result| match result {
|
||||
Ok(_) => None,
|
||||
Ok(_) => None,
|
||||
Err(err) => Some(err),
|
||||
})
|
||||
.collect();
|
||||
|
|
|
@ -21,14 +21,19 @@ fn test_fragment_decoding() {
|
|||
let bignum = decode_fragment(fragment).expect("Failed to decode fragment");
|
||||
|
||||
let expected = "19947781743618558124649689124245117083485690334420160711273532766920651190711502679542723943527557680293732686428091794139998732541701457212387600480039297092835433997837314251024513773285252960725418984381935183495143908023024822433135775773958512751261112853383693442999603704969543668619221464654540065497665889289271044207667765128672709218996183649696030570183970367596949687544839066873508106034650634722970893169823917299050098551447676778961773465887890052852528696684907153295689693676910831376066659456592813140662563597179711588277621736656871685099184755908108451080261403193680966083938080206832839445289";
|
||||
assert_eq!(expected, format!("{}", bignum), "Decoded fragment should match ");
|
||||
assert_eq!(
|
||||
expected,
|
||||
format!("{}", bignum),
|
||||
"Decoded fragment should match "
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_find_jwks() {
|
||||
let json = "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"mUjI\\/rIMLLtung35BKZfdbrqtlEAAYJ4JX\\/SKvnLxJc=\",\"n\":\"ngRRjNbXgPW29oNtF0JgsyyfTwPyEL0u_X16s453X2AOc33XGFxVKLEQ7R_TiMenaKcr-tPifYqgps_deyi0XOr4I3SOdOMtAVKDZJCANe--CANOHZb-meIfjKhCHisvT90fm5Apd6qPRVsXsZ7A8pmClZHKM5fwZUkBv8NsPLm2Xy2sGOZIiwP_7z8m3j0abUzniPQsx2b3xcWimB9vRtshFHN1KgPUf1ALQ5xzLfJnlFkCxC7kmOxKC7_NpQ4kJR_DKzKFV_r3HxTqf-jddHcXIrrMcLQXCSyeLQtLaz7whQ4F-EfL42z4XgwPr4ji3sct2gWL13EqlbE5DDxLKQ\",\"e\":\"GK7oLCDbNPAF59LhvyseqcG04hDnPs58qGYolr_HHmaR4lulWJ90ozx6e4Ut363yKG2p9vwvivR5UIC-aLPtqT2qr-OtjhBFzUFVaMGZ6mPCvMKk0AgMYdOHvWTgBSqQtNJTvl1yYLnhcWyoE2fLQhoEbY9qUyCBCEOScXOZRDpnmBtz5I8q5yYMV6a920J24T_IYbxHgkGcEU2SGg-b1cOMD7Rja7vCfV---CQ2pR4leQ0jufzudDoe7z3mziJm-Ihcdrz2Ujy5kPEMdz6R55prJ-ENKrkD_X4u5aSlSRaetwmHS3oAVkjr1JwUNbqnpM-kOqieqHEp8LUmez-Znw\"}]}";
|
||||
let jwks: JWKS = serde_json::from_str(json).expect("Failed to decode JWKS");
|
||||
let jwk = jwks.find("mUjI/rIMLLtung35BKZfdbrqtlEAAYJ4JX/SKvnLxJc=")
|
||||
let jwk = jwks
|
||||
.find("mUjI/rIMLLtung35BKZfdbrqtlEAAYJ4JX/SKvnLxJc=")
|
||||
.expect("Failed to find required JWK");
|
||||
|
||||
public_key_from_jwk(&jwk).expect("Failed to construct public key from JWK");
|
||||
|
@ -39,18 +44,21 @@ fn test_token_kid() {
|
|||
let jwt = "eyJraWQiOiI4ckRxOFB3MEZaY2FvWFdURVZRbzcrVGYyWXpTTDFmQnhOS1BDZWJhYWk0PSIsImFsZyI6IlJTMjU2IiwidHlwIjoiSldUIn0.eyJpc3MiOiJhdXRoLnRlc3QuYXByaWxhLm5vIiwiaWF0IjoxNTM2MDUwNjkzLCJleHAiOjE1MzYwNTQyOTMsInN1YiI6IjQyIiwiZXh0Ijoic21va2V0ZXN0IiwicHJ2IjoiYXJpc3RpIiwic2NwIjoicHJvY2VzcyJ9.gOLsv98109qLkmRK6Dn7WWRHLW7o8W78WZcWvFZoxPLzVO0qvRXXRLYc9h5chpfvcWreLZ4f1cOdvxv31_qnCRSQQPOeQ7r7hj_sPEDzhKjk-q2aoNHaGGJg1vabI--9EFkFsGQfoS7UbMMssS44dgR68XEnKtjn0Vys-Vzbvz_CBSCH6yQhRLik2SU2jR2L7BoFvh4LGZ6EKoQWzm8Z-CHXLGLUs4Hp5aPhF46dGzgAzwlPFW4t9G4DciX1uB4vv1XnfTc5wqJch6ltjKMde1GZwLR757a8dJSBcmGWze3UNE2YH_VLD7NCwH2kkqr3gh8rn7lWKG4AUIYPxsw9CB";
|
||||
|
||||
let kid = token_kid(&jwt).expect("Failed to extract token KID");
|
||||
assert_eq!(Some("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=".into()),
|
||||
kid, "Extracted KID did not match expected KID");
|
||||
assert_eq!(
|
||||
Some("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=".into()),
|
||||
kid,
|
||||
"Extracted KID did not match expected KID"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_jwt() {
|
||||
let jwks_json = "{\"keys\":[{\"kty\":\"RSA\",\"alg\":\"RS256\",\"use\":\"sig\",\"kid\":\"8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=\",\"n\":\"l4UTgk1zr-8C8utt0E57DtBV6qqAPWzVRrIuQS2j0_hp2CviaNl5XzGRDnB8gwk0Hx95YOhJupAe6RNq5ok3fDdxL7DLvppJNRLz3Ag9CsmDLcbXgNEQys33fBJaPw1v3GcaFC4tisU5p-o1f5RfWwvwdBtdBfGiwT1GRvbc5sFx6M4iYjg9uv1lNKW60PqSJW4iDYrfqzZmB0zF1SJ0BL_rnQZ1Wi_UkFmNe9arM8W9tI9T3Ie59HITFuyVSTCt6qQEtSfa1e5PiBaVuV3qoFI2jPBiVZQ6LPGBWEDyz4QtrHLdECPPoTF30NN6TSVwwlRbCuUUrdNdXdjYe2dMFQ\",\"e\":\"DhaD5zC7mzaDvHO192wKT_9sfsVmdy8w8T8C9VG17_b1jG2srd3cmc6Ycw-0blDf53Wrpi9-KGZXKHX6_uIuJK249WhkP7N1SHrTJxO0sUJ8AhK482PLF09Qtu6cUfJqY1X1y1S2vACJZItU4Vjr3YAfiVGQXeA8frAf7Sm4O1CBStCyg6yCcIbGojII0jfh2vSB-GD9ok1F69Nmk-R-bClyqMCV_Oq-5a0gqClVS8pDyGYMgKTww2RHgZaFSUcG13KeLMQsG2UOB2OjSC8FkOXK00NBlAjU3d0Vv-IamaLIszO7FQBY3Oh0uxNOvIE9ofQyCOpB-xIK6V9CTTphxw\"}]}";
|
||||
|
||||
let jwks: JWKS = serde_json::from_str(jwks_json)
|
||||
.expect("Failed to decode JWKS");
|
||||
let jwks: JWKS = serde_json::from_str(jwks_json).expect("Failed to decode JWKS");
|
||||
|
||||
let jwk = jwks.find("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=")
|
||||
let jwk = jwks
|
||||
.find("8rDq8Pw0FZcaoXWTEVQo7+Tf2YzSL1fBxNKPCebaai4=")
|
||||
.expect("Failed to find required JWK");
|
||||
|
||||
let pkey = public_key_from_jwk(&jwk).expect("Failed to construct public key");
|
||||
|
|
|
@ -33,9 +33,12 @@
|
|||
//! use crimp::Request;
|
||||
//!
|
||||
//! let response = Request::get("http://httpbin.org/get")
|
||||
//! .user_agent("crimp test suite").unwrap()
|
||||
//! .send().unwrap()
|
||||
//! .as_string().unwrap();
|
||||
//! .user_agent("crimp test suite")
|
||||
//! .unwrap()
|
||||
//! .send()
|
||||
//! .unwrap()
|
||||
//! .as_string()
|
||||
//! .unwrap();
|
||||
//!
|
||||
//! println!("Status: {}\nBody: {}", response.status, response.body);
|
||||
//! # assert_eq!(response.status, 200);
|
||||
|
@ -54,10 +57,9 @@
|
|||
//!
|
||||
//! All optional features are enabled by default.
|
||||
//!
|
||||
//! * `json`: Adds `Request::json` and `Response::as_json` methods
|
||||
//! which can be used for convenient serialisation of
|
||||
//! request/response bodies using `serde_json`. This feature adds a
|
||||
//! dependency on the `serde` and `serde_json` crates.
|
||||
//! * `json`: Adds `Request::json` and `Response::as_json` methods which can be used for convenient
|
||||
//! serialisation of request/response bodies using `serde_json`. This feature adds a dependency on
|
||||
//! the `serde` and `serde_json` crates.
|
||||
//!
|
||||
//! ## Initialisation
|
||||
//!
|
||||
|
@ -72,32 +74,42 @@
|
|||
|
||||
extern crate curl;
|
||||
|
||||
#[cfg(feature = "json")] extern crate serde;
|
||||
#[cfg(feature = "json")] extern crate serde_json;
|
||||
#[cfg(feature = "json")]
|
||||
extern crate serde;
|
||||
#[cfg(feature = "json")]
|
||||
extern crate serde_json;
|
||||
|
||||
pub use curl::init;
|
||||
|
||||
use curl::easy::{Auth, Easy, Form, List, Transfer, ReadError, WriteError};
|
||||
use curl::easy::{Auth, Easy, Form, List, ReadError, Transfer, WriteError};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
use std::string::{FromUtf8Error, ToString};
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "json")] use serde::Serialize;
|
||||
#[cfg(feature = "json")] use serde::de::DeserializeOwned;
|
||||
#[cfg(feature = "json")]
|
||||
use serde::de::DeserializeOwned;
|
||||
#[cfg(feature = "json")]
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// HTTP method to use for the request.
|
||||
enum Method {
|
||||
Get, Post, Put, Patch, Delete
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Patch,
|
||||
Delete,
|
||||
}
|
||||
|
||||
/// Certificate types for client-certificate key pairs.
|
||||
pub enum CertType {
|
||||
P12, PEM, DER
|
||||
P12,
|
||||
PEM,
|
||||
DER,
|
||||
}
|
||||
|
||||
/// Builder structure for an HTTP request.
|
||||
|
@ -145,7 +157,7 @@ pub struct Response<T> {
|
|||
pub body: T,
|
||||
}
|
||||
|
||||
impl <'a> Request<'a> {
|
||||
impl<'a> Request<'a> {
|
||||
/// Initiate an HTTP request with the given method and URL.
|
||||
fn new(method: Method, url: &'a str) -> Self {
|
||||
Request {
|
||||
|
@ -158,19 +170,29 @@ impl <'a> Request<'a> {
|
|||
}
|
||||
|
||||
/// Initiate a GET request with the given URL.
|
||||
pub fn get(url: &'a str) -> Self { Request::new(Method::Get, url) }
|
||||
pub fn get(url: &'a str) -> Self {
|
||||
Request::new(Method::Get, url)
|
||||
}
|
||||
|
||||
/// Initiate a POST request with the given URL.
|
||||
pub fn post(url: &'a str) -> Self { Request::new(Method::Post, url) }
|
||||
pub fn post(url: &'a str) -> Self {
|
||||
Request::new(Method::Post, url)
|
||||
}
|
||||
|
||||
/// Initiate a PUT request with the given URL.
|
||||
pub fn put(url: &'a str) -> Self { Request::new(Method::Put, url) }
|
||||
pub fn put(url: &'a str) -> Self {
|
||||
Request::new(Method::Put, url)
|
||||
}
|
||||
|
||||
/// Initiate a PATCH request with the given URL.
|
||||
pub fn patch(url: &'a str) -> Self { Request::new(Method::Patch, url) }
|
||||
pub fn patch(url: &'a str) -> Self {
|
||||
Request::new(Method::Patch, url)
|
||||
}
|
||||
|
||||
/// Initiate a DELETE request with the given URL.
|
||||
pub fn delete(url: &'a str) -> Self { Request::new(Method::Delete, url) }
|
||||
pub fn delete(url: &'a str) -> Self {
|
||||
Request::new(Method::Delete, url)
|
||||
}
|
||||
|
||||
/// Add an HTTP header to a request.
|
||||
pub fn header(mut self, k: &str, v: &str) -> Result<Self, curl::Error> {
|
||||
|
@ -188,7 +210,8 @@ impl <'a> Request<'a> {
|
|||
/// Set the `Authorization` header to a `Bearer` value with the
|
||||
/// supplied token.
|
||||
pub fn bearer_auth(mut self, token: &str) -> Result<Self, curl::Error> {
|
||||
self.headers.append(&format!("Authorization: Bearer {}", token))?;
|
||||
self.headers
|
||||
.append(&format!("Authorization: Bearer {}", token))?;
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
|
@ -212,8 +235,11 @@ impl <'a> Request<'a> {
|
|||
/// Consult the documentation for the `ssl_cert` and `ssl_key`
|
||||
/// functions in `curl::easy::Easy2` for details on supported
|
||||
/// formats and defaults.
|
||||
pub fn tls_client_cert<P: AsRef<Path>>(mut self, cert_type: CertType, cert: P)
|
||||
-> Result<Self, curl::Error> {
|
||||
pub fn tls_client_cert<P: AsRef<Path>>(
|
||||
mut self,
|
||||
cert_type: CertType,
|
||||
cert: P,
|
||||
) -> Result<Self, curl::Error> {
|
||||
self.handle.ssl_cert(cert)?;
|
||||
self.handle.ssl_cert_type(match cert_type {
|
||||
CertType::P12 => "P12",
|
||||
|
@ -262,13 +288,17 @@ impl <'a> Request<'a> {
|
|||
/// ```
|
||||
/// # use crimp::Request;
|
||||
/// let response = Request::get("https://httpbin.org/get")
|
||||
/// .with_handle(|mut handle| handle.referer("Example-Referer")).unwrap()
|
||||
/// .send().unwrap();
|
||||
/// .with_handle(|mut handle| handle.referer("Example-Referer"))
|
||||
/// .unwrap()
|
||||
/// .send()
|
||||
/// .unwrap();
|
||||
/// #
|
||||
/// # assert!(response.is_success());
|
||||
/// ```
|
||||
pub fn with_handle<F>(mut self, function: F) -> Result<Self, curl::Error>
|
||||
where F: FnOnce(&mut Easy) -> Result<(), curl::Error> {
|
||||
where
|
||||
F: FnOnce(&mut Easy) -> Result<(), curl::Error>,
|
||||
{
|
||||
function(&mut self.handle)?;
|
||||
Ok(self)
|
||||
}
|
||||
|
@ -293,12 +323,15 @@ impl <'a> Request<'a> {
|
|||
/// let mut form = Form::new();
|
||||
/// form.part("some-name")
|
||||
/// .contents("some-data".as_bytes())
|
||||
/// .add().unwrap();
|
||||
/// .add()
|
||||
/// .unwrap();
|
||||
///
|
||||
/// let response = Request::post("https://httpbin.org/post")
|
||||
/// .user_agent("crimp test suite").unwrap()
|
||||
/// .user_agent("crimp test suite")
|
||||
/// .unwrap()
|
||||
/// .form(form)
|
||||
/// .send().unwrap();
|
||||
/// .send()
|
||||
/// .unwrap();
|
||||
/// #
|
||||
/// # assert_eq!(200, response.status, "form POST should succeed");
|
||||
/// # assert_eq!(
|
||||
|
@ -330,10 +363,10 @@ impl <'a> Request<'a> {
|
|||
self.handle.url(self.url)?;
|
||||
|
||||
match self.method {
|
||||
Method::Get => self.handle.get(true)?,
|
||||
Method::Post => self.handle.post(true)?,
|
||||
Method::Put => self.handle.put(true)?,
|
||||
Method::Patch => self.handle.custom_request("PATCH")?,
|
||||
Method::Get => self.handle.get(true)?,
|
||||
Method::Post => self.handle.post(true)?,
|
||||
Method::Put => self.handle.put(true)?,
|
||||
Method::Patch => self.handle.custom_request("PATCH")?,
|
||||
Method::Delete => self.handle.custom_request("DELETE")?,
|
||||
}
|
||||
|
||||
|
@ -351,21 +384,22 @@ impl <'a> Request<'a> {
|
|||
|
||||
// Optionally set content type if a body payload is configured
|
||||
// and configure the expected body size (or form payload).
|
||||
match self.body {
|
||||
match self.body {
|
||||
Body::Bytes { content_type, data } => {
|
||||
self.handle.post_field_size(data.len() as u64)?;
|
||||
self.headers.append(&format!("Content-Type: {}", content_type))?;
|
||||
},
|
||||
self.headers
|
||||
.append(&format!("Content-Type: {}", content_type))?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
Body::Json(ref data) => {
|
||||
self.handle.post_field_size(data.len() as u64)?;
|
||||
self.headers.append("Content-Type: application/json")?;
|
||||
},
|
||||
}
|
||||
|
||||
// Do not set content-type header at all if there is no
|
||||
// body, or if the form handler was invoked above.
|
||||
_ => (),
|
||||
// Do not set content-type header at all if there is no
|
||||
// body, or if the form handler was invoked above.
|
||||
_ => (),
|
||||
};
|
||||
|
||||
// Configure headers on the request:
|
||||
|
@ -407,9 +441,7 @@ impl <'a> Request<'a> {
|
|||
return true;
|
||||
}
|
||||
|
||||
headers.insert(
|
||||
split[0].trim().to_string(), split[1].trim().to_string()
|
||||
);
|
||||
headers.insert(split[0].trim().to_string(), split[1].trim().to_string());
|
||||
true
|
||||
})?;
|
||||
|
||||
|
@ -427,7 +459,7 @@ impl <'a> Request<'a> {
|
|||
Ok(Response {
|
||||
status: self.handle.response_code()?,
|
||||
headers,
|
||||
body
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -438,13 +470,14 @@ impl <'a> Request<'a> {
|
|||
///
|
||||
/// As we manually set the expected upload size, cURL will call the
|
||||
/// read callback repeatedly until it has all the data it needs.
|
||||
fn chunked_read_function<'easy, 'data>(transfer: &mut Transfer<'easy, 'data>,
|
||||
data: &'data [u8]) -> Result<(), curl::Error> {
|
||||
fn chunked_read_function<'easy, 'data>(
|
||||
transfer: &mut Transfer<'easy, 'data>,
|
||||
data: &'data [u8],
|
||||
) -> Result<(), curl::Error> {
|
||||
let mut data = data;
|
||||
|
||||
transfer.read_function(move |mut into| {
|
||||
let written = into.write(data)
|
||||
.map_err(|_| ReadError::Abort)?;
|
||||
let written = into.write(data).map_err(|_| ReadError::Abort)?;
|
||||
|
||||
data = &data[written..];
|
||||
|
||||
|
@ -452,7 +485,7 @@ fn chunked_read_function<'easy, 'data>(transfer: &mut Transfer<'easy, 'data>,
|
|||
})
|
||||
}
|
||||
|
||||
impl <T> Response<T> {
|
||||
impl<T> Response<T> {
|
||||
/// Check whether the status code of this HTTP response is a
|
||||
/// success (i.e. in the 200-299 range).
|
||||
pub fn is_success(&self) -> bool {
|
||||
|
@ -466,9 +499,11 @@ impl <T> Response<T> {
|
|||
/// This function exists for convenience to avoid having to write
|
||||
/// repetitive `if !response.is_success() { ... }` blocks.
|
||||
pub fn error_for_status<F, E>(self, closure: F) -> Result<Self, E>
|
||||
where F: FnOnce(Self) -> E {
|
||||
where
|
||||
F: FnOnce(Self) -> E,
|
||||
{
|
||||
if !self.is_success() {
|
||||
return Err(closure(self))
|
||||
return Err(closure(self));
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
// docker run --rm -p 4662:80 kennethreitz/httpbin
|
||||
|
||||
use super::*;
|
||||
use serde_json::{Value, json};
|
||||
use serde_json::{json, Value};
|
||||
|
||||
// These tests check whether the correct HTTP method is used in the
|
||||
// requests.
|
||||
|
@ -14,7 +14,8 @@ use serde_json::{Value, json};
|
|||
#[test]
|
||||
fn test_http_get() {
|
||||
let resp = Request::get("http://127.0.0.1:4662/get")
|
||||
.send().expect("failed to send request");
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert!(resp.is_success(), "request should have succeeded");
|
||||
}
|
||||
|
@ -22,7 +23,8 @@ fn test_http_get() {
|
|||
#[test]
|
||||
fn test_http_delete() {
|
||||
let resp = Request::delete("http://127.0.0.1:4662/delete")
|
||||
.send().expect("failed to send request");
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert_eq!(200, resp.status, "response status should be 200 OK");
|
||||
}
|
||||
|
@ -30,7 +32,8 @@ fn test_http_delete() {
|
|||
#[test]
|
||||
fn test_http_put() {
|
||||
let resp = Request::put("http://127.0.0.1:4662/put")
|
||||
.send().expect("failed to send request");
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert_eq!(200, resp.status, "response status should be 200 OK");
|
||||
}
|
||||
|
@ -38,7 +41,8 @@ fn test_http_put() {
|
|||
#[test]
|
||||
fn test_http_patch() {
|
||||
let resp = Request::patch("http://127.0.0.1:4662/patch")
|
||||
.send().expect("failed to send request");
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert_eq!(200, resp.status, "response status should be 200 OK");
|
||||
}
|
||||
|
@ -50,18 +54,25 @@ fn test_http_patch() {
|
|||
fn test_http_post() {
|
||||
let body = "test body";
|
||||
let response = Request::post("http://127.0.0.1:4662/post")
|
||||
.user_agent("crimp test suite").expect("failed to set user-agent")
|
||||
.timeout(Duration::from_secs(5)).expect("failed to set request timeout")
|
||||
.user_agent("crimp test suite")
|
||||
.expect("failed to set user-agent")
|
||||
.timeout(Duration::from_secs(5))
|
||||
.expect("failed to set request timeout")
|
||||
.body("text/plain", &body.as_bytes())
|
||||
.send().expect("failed to send request")
|
||||
.as_json::<Value>().expect("failed to deserialize response");
|
||||
.send()
|
||||
.expect("failed to send request")
|
||||
.as_json::<Value>()
|
||||
.expect("failed to deserialize response");
|
||||
|
||||
let data = response.body;
|
||||
|
||||
assert_eq!(200, response.status, "response status should be 200 OK");
|
||||
|
||||
assert_eq!(data.get("data").unwrap(), &json!("test body"),
|
||||
"test body should have been POSTed");
|
||||
assert_eq!(
|
||||
data.get("data").unwrap(),
|
||||
&json!("test body"),
|
||||
"test body should have been POSTed"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
data.get("headers").unwrap().get("Content-Type").unwrap(),
|
||||
|
@ -70,26 +81,34 @@ fn test_http_post() {
|
|||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")] #[test]
|
||||
#[cfg(feature = "json")]
|
||||
#[test]
|
||||
fn test_http_post_json() {
|
||||
let body = json!({
|
||||
"purpose": "testing!"
|
||||
});
|
||||
|
||||
let response = Request::post("http://127.0.0.1:4662/post")
|
||||
.user_agent("crimp test suite").expect("failed to set user-agent")
|
||||
.timeout(Duration::from_secs(5)).expect("failed to set request timeout")
|
||||
.json(&body).expect("request serialization failed")
|
||||
.send().expect("failed to send request")
|
||||
.as_json::<Value>().expect("failed to deserialize response");
|
||||
|
||||
.user_agent("crimp test suite")
|
||||
.expect("failed to set user-agent")
|
||||
.timeout(Duration::from_secs(5))
|
||||
.expect("failed to set request timeout")
|
||||
.json(&body)
|
||||
.expect("request serialization failed")
|
||||
.send()
|
||||
.expect("failed to send request")
|
||||
.as_json::<Value>()
|
||||
.expect("failed to deserialize response");
|
||||
|
||||
let data = response.body;
|
||||
|
||||
assert_eq!(200, response.status, "response status should be 200 OK");
|
||||
|
||||
assert_eq!(data.get("json").unwrap(), &body,
|
||||
"test body should have been POSTed");
|
||||
assert_eq!(
|
||||
data.get("json").unwrap(),
|
||||
&body,
|
||||
"test body should have been POSTed"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
data.get("headers").unwrap().get("Content-Type").unwrap(),
|
||||
|
@ -104,8 +123,10 @@ fn test_http_post_json() {
|
|||
#[test]
|
||||
fn test_bearer_auth() {
|
||||
let response = Request::get("http://127.0.0.1:4662/bearer")
|
||||
.bearer_auth("some-token").expect("failed to set auth header")
|
||||
.send().expect("failed to send request");
|
||||
.bearer_auth("some-token")
|
||||
.expect("failed to set auth header")
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert!(response.is_success(), "authorized request should succeed");
|
||||
}
|
||||
|
@ -115,8 +136,10 @@ fn test_basic_auth() {
|
|||
let request = Request::get("http://127.0.0.1:4662/basic-auth/alan_watts/oneness");
|
||||
|
||||
let response = request
|
||||
.basic_auth("alan_watts", "oneness").expect("failed to set auth header")
|
||||
.send().expect("failed to send request");
|
||||
.basic_auth("alan_watts", "oneness")
|
||||
.expect("failed to set auth header")
|
||||
.send()
|
||||
.expect("failed to send request");
|
||||
|
||||
assert!(response.is_success(), "authorized request should succeed");
|
||||
}
|
||||
|
@ -129,14 +152,20 @@ fn test_large_body() {
|
|||
|
||||
let resp = Request::post("http://127.0.0.1:4662/post")
|
||||
.body("application/octet-stream", &[0; BODY_SIZE])
|
||||
.send().expect("sending request")
|
||||
.as_json::<Value>().expect("JSON deserialisation");
|
||||
.send()
|
||||
.expect("sending request")
|
||||
.as_json::<Value>()
|
||||
.expect("JSON deserialisation");
|
||||
|
||||
// httpbin returns the uploaded data as a string in the `data`
|
||||
// field.
|
||||
let data = resp.body.get("data").unwrap().as_str().unwrap();
|
||||
|
||||
assert_eq!(BODY_SIZE, data.len(), "uploaded data length should be correct");
|
||||
assert_eq!(
|
||||
BODY_SIZE,
|
||||
data.len(),
|
||||
"uploaded data length should be correct"
|
||||
);
|
||||
}
|
||||
|
||||
// Tests for various other features.
|
||||
|
@ -144,9 +173,13 @@ fn test_large_body() {
|
|||
#[test]
|
||||
fn test_error_for_status() {
|
||||
let response = Request::get("http://127.0.0.1:4662/patch")
|
||||
.send().expect("failed to send request")
|
||||
.send()
|
||||
.expect("failed to send request")
|
||||
.error_for_status(|resp| format!("Response error code: {}", resp.status));
|
||||
|
||||
assert_eq!(Err("Response error code: 405".into()), response,
|
||||
"returned error should be converted into Result::Err");
|
||||
assert_eq!(
|
||||
Err("Response error code: 405".into()),
|
||||
response,
|
||||
"returned error should be converted into Result::Err"
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
extern crate serde_json;
|
||||
|
||||
use serde_json::Value;
|
||||
use std::convert::TryFrom;
|
||||
use std::ffi::OsString;
|
||||
use std::os::unix::ffi::{OsStringExt, OsStrExt};
|
||||
use std::io::{Error, ErrorKind, Write, stdout, stderr};
|
||||
use std::io::{stderr, stdout, Error, ErrorKind, Write};
|
||||
use std::os::unix::ffi::{OsStrExt, OsStringExt};
|
||||
use std::process::Command;
|
||||
use std::convert::{TryFrom};
|
||||
|
||||
fn render_nix_string(s: &OsString) -> OsString {
|
||||
let mut rendered = Vec::new();
|
||||
|
@ -16,8 +16,8 @@ fn render_nix_string(s: &OsString) -> OsString {
|
|||
match char::from(*b) {
|
||||
'\"' => rendered.extend(b"\\\""),
|
||||
'\\' => rendered.extend(b"\\\\"),
|
||||
'$' => rendered.extend(b"\\$"),
|
||||
_ => rendered.push(*b),
|
||||
'$' => rendered.extend(b"\\$"),
|
||||
_ => rendered.push(*b),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,17 +48,14 @@ fn render_nix_list(arr: &[OsString]) -> OsString {
|
|||
macro_rules! handle_set_output {
|
||||
($map_name:ident, $output_name:ident) => {
|
||||
match $map_name.get(stringify!($output_name)) {
|
||||
Some(Value::String(s)) =>
|
||||
$output_name().write_all(s.as_bytes()),
|
||||
Some(_) => Err(
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Attribute {} must be a string!", stringify!($output_name)),
|
||||
)
|
||||
),
|
||||
Some(Value::String(s)) => $output_name().write_all(s.as_bytes()),
|
||||
Some(_) => Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
format!("Attribute {} must be a string!", stringify!($output_name)),
|
||||
)),
|
||||
None => Ok(()),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
|
@ -83,7 +80,7 @@ fn main() -> std::io::Result<()> {
|
|||
}
|
||||
|
||||
if in_args {
|
||||
match(arg.to_str()) {
|
||||
match (arg.to_str()) {
|
||||
Some("--arg") | Some("--argstr") => {
|
||||
nix_args.push(arg);
|
||||
nix_args.push(args.next().unwrap());
|
||||
|
@ -116,9 +113,7 @@ fn main() -> std::io::Result<()> {
|
|||
|
||||
nix_args.push(argv[0].clone());
|
||||
|
||||
let run = Command::new("nix-instantiate")
|
||||
.args(nix_args)
|
||||
.output()?;
|
||||
let run = Command::new("nix-instantiate").args(nix_args).output()?;
|
||||
|
||||
match serde_json::from_slice(&run.stdout[..]) {
|
||||
Ok(Value::String(s)) => stdout().write_all(s.as_bytes()),
|
||||
|
@ -132,25 +127,23 @@ fn main() -> std::io::Result<()> {
|
|||
|
||||
match code {
|
||||
Some(i) => std::process::exit(i),
|
||||
None => Err(
|
||||
Error::new(
|
||||
ErrorKind::Other,
|
||||
"Attribute exit is not an i32"
|
||||
)
|
||||
),
|
||||
None => {
|
||||
Err(Error::new(ErrorKind::Other, "Attribute exit is not an i32"))
|
||||
}
|
||||
}
|
||||
},
|
||||
Some(_) => Err(
|
||||
Error::new(ErrorKind::Other, "exit must be a number")
|
||||
),
|
||||
}
|
||||
Some(_) => Err(Error::new(ErrorKind::Other, "exit must be a number")),
|
||||
None => Ok(()),
|
||||
}
|
||||
},
|
||||
Ok(_) => Err(Error::new(ErrorKind::Other, "output must be a string or an object")),
|
||||
}
|
||||
Ok(_) => Err(Error::new(
|
||||
ErrorKind::Other,
|
||||
"output must be a string or an object",
|
||||
)),
|
||||
_ => {
|
||||
stderr().write_all(&run.stderr[..]);
|
||||
Err(Error::new(ErrorKind::Other, "internal nix error"))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
extern crate pkg_config;
|
||||
|
||||
fn main() {
|
||||
pkg_config::probe_library("libsystemd")
|
||||
.expect("Could not probe libsystemd");
|
||||
pkg_config::probe_library("libsystemd").expect("Could not probe libsystemd");
|
||||
}
|
||||
|
|
|
@ -31,11 +31,16 @@
|
|||
//! `GOOGLE_APPLICATION_CREDENTIALS`, `GOOGLE_CLOUD_PROJECT` and
|
||||
//! `LOG_NAME` environment variables.
|
||||
|
||||
#[macro_use] extern crate failure;
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate serde_derive;
|
||||
#[macro_use] extern crate serde_json;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
||||
extern crate chrono;
|
||||
extern crate env_logger;
|
||||
|
@ -48,13 +53,11 @@ use chrono::offset::LocalResult;
|
|||
use chrono::prelude::{DateTime, TimeZone, Utc};
|
||||
use failure::ResultExt;
|
||||
use serde_json::{from_str, Value};
|
||||
use std::env;
|
||||
use std::fs::{self, File, rename};
|
||||
use std::io::{self, Read, ErrorKind, Write};
|
||||
use std::mem;
|
||||
use std::fs::{self, rename, File};
|
||||
use std::io::{self, ErrorKind, Read, Write};
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{env, mem, process};
|
||||
use systemd::journal::{Journal, JournalFiles, JournalRecord, JournalSeek};
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -62,10 +65,12 @@ mod tests;
|
|||
|
||||
const LOGGING_SERVICE: &str = "https://logging.googleapis.com/google.logging.v2.LoggingServiceV2";
|
||||
const ENTRIES_WRITE_URL: &str = "https://logging.googleapis.com/v2/entries:write";
|
||||
const METADATA_TOKEN_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
|
||||
const METADATA_TOKEN_URL: &str =
|
||||
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token";
|
||||
const METADATA_ID_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/id";
|
||||
const METADATA_ZONE_URL: &str = "http://metadata.google.internal/computeMetadata/v1/instance/zone";
|
||||
const METADATA_PROJECT_URL: &str = "http://metadata.google.internal/computeMetadata/v1/project/project-id";
|
||||
const METADATA_PROJECT_URL: &str =
|
||||
"http://metadata.google.internal/computeMetadata/v1/project/project-id";
|
||||
|
||||
/// Convenience type alias for results using failure's `Error` type.
|
||||
type Result<T> = std::result::Result<T, failure::Error>;
|
||||
|
@ -134,14 +139,17 @@ fn get_metadata(url: &str) -> Result<String> {
|
|||
|
||||
if response.ok() {
|
||||
// Whitespace is trimmed to remove newlines from responses.
|
||||
let body = response.into_string()
|
||||
let body = response
|
||||
.into_string()
|
||||
.context("Failed to decode metadata response")?
|
||||
.trim().to_string();
|
||||
.trim()
|
||||
.to_string();
|
||||
|
||||
Ok(body)
|
||||
} else {
|
||||
let status = response.status_line().to_string();
|
||||
let body = response.into_string()
|
||||
let body = response
|
||||
.into_string()
|
||||
.unwrap_or_else(|e| format!("Metadata body error: {}", e));
|
||||
bail!("Metadata failure: {} ({})", body, status)
|
||||
}
|
||||
|
@ -186,11 +194,9 @@ fn determine_monitored_resource() -> Value {
|
|||
}
|
||||
})
|
||||
} else {
|
||||
let instance_id = get_metadata(METADATA_ID_URL)
|
||||
.expect("Could not determine instance ID");
|
||||
let instance_id = get_metadata(METADATA_ID_URL).expect("Could not determine instance ID");
|
||||
|
||||
let zone = get_metadata(METADATA_ZONE_URL)
|
||||
.expect("Could not determine instance zone");
|
||||
let zone = get_metadata(METADATA_ZONE_URL).expect("Could not determine instance zone");
|
||||
|
||||
json!({
|
||||
"type": "gce_instance",
|
||||
|
@ -253,7 +259,8 @@ fn sign_service_account_token(credentials: &Credentials) -> Result<Token> {
|
|||
use medallion::{Algorithm, Header, Payload};
|
||||
|
||||
let iat = Utc::now();
|
||||
let exp = iat.checked_add_signed(chrono::Duration::seconds(3600))
|
||||
let exp = iat
|
||||
.checked_add_signed(chrono::Duration::seconds(3600))
|
||||
.ok_or_else(|| format_err!("Failed to calculate token expiry"))?;
|
||||
|
||||
let header = Header {
|
||||
|
@ -323,7 +330,9 @@ enum Payload {
|
|||
/// text format.
|
||||
fn message_to_payload(message: Option<String>) -> Payload {
|
||||
match message {
|
||||
None => Payload::TextPayload { text_payload: "empty log entry".into() },
|
||||
None => Payload::TextPayload {
|
||||
text_payload: "empty log entry".into(),
|
||||
},
|
||||
Some(text_payload) => {
|
||||
// Attempt to deserialize the text payload as a generic
|
||||
// JSON value.
|
||||
|
@ -333,7 +342,7 @@ fn message_to_payload(message: Option<String>) -> Payload {
|
|||
// expect other types of JSON payload) and return it
|
||||
// in that case.
|
||||
if json_payload.is_object() {
|
||||
return Payload::JsonPayload { json_payload }
|
||||
return Payload::JsonPayload { json_payload };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -450,9 +459,7 @@ impl From<JournalRecord> for LogEntry {
|
|||
// Journald uses syslogd's concept of priority. No idea if this is
|
||||
// always present, but it's optional in the Stackdriver API, so we just
|
||||
// omit it if we can't find or parse it.
|
||||
let severity = record
|
||||
.remove("PRIORITY")
|
||||
.and_then(priority_to_severity);
|
||||
let severity = record.remove("PRIORITY").and_then(priority_to_severity);
|
||||
|
||||
LogEntry {
|
||||
payload,
|
||||
|
@ -468,8 +475,7 @@ impl From<JournalRecord> for LogEntry {
|
|||
|
||||
/// Attempt to read from the journal. If no new entry is present,
|
||||
/// await the next one up to the specified timeout.
|
||||
fn receive_next_record(timeout: Duration, journal: &mut Journal)
|
||||
-> Result<Option<JournalRecord>> {
|
||||
fn receive_next_record(timeout: Duration, journal: &mut Journal) -> Result<Option<JournalRecord>> {
|
||||
let next_record = journal.next_record()?;
|
||||
if next_record.is_some() {
|
||||
return Ok(next_record);
|
||||
|
@ -525,11 +531,10 @@ fn persist_cursor(cursor: String) -> Result<()> {
|
|||
if cursor.is_empty() {
|
||||
error!("Received empty journald cursor position, refusing to persist!");
|
||||
error!("Please report this message at https://github.com/tazjin/journaldriver/issues/2");
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut file = File::create(&*CURSOR_TMP_FILE)
|
||||
.context("Failed to create cursor file")?;
|
||||
let mut file = File::create(&*CURSOR_TMP_FILE).context("Failed to create cursor file")?;
|
||||
|
||||
write!(file, "{}", cursor).context("Failed to write cursor file")?;
|
||||
|
||||
|
@ -547,9 +552,7 @@ fn persist_cursor(cursor: String) -> Result<()> {
|
|||
///
|
||||
/// If flushing is successful the last cursor position will be
|
||||
/// persisted to disk.
|
||||
fn flush(token: &mut Token,
|
||||
entries: Vec<LogEntry>,
|
||||
cursor: String) -> Result<()> {
|
||||
fn flush(token: &mut Token, entries: Vec<LogEntry>, cursor: String) -> Result<()> {
|
||||
if token.is_expired() {
|
||||
debug!("Refreshing Google metadata access token");
|
||||
let new_token = get_token()?;
|
||||
|
@ -598,7 +601,8 @@ fn write_entries(token: &Token, request: Value) -> Result<()> {
|
|||
Ok(())
|
||||
} else {
|
||||
let status = response.status_line().to_string();
|
||||
let body = response.into_string()
|
||||
let body = response
|
||||
.into_string()
|
||||
.unwrap_or_else(|_| "no response body".into());
|
||||
bail!("Write failure: {} ({})", body, status)
|
||||
}
|
||||
|
@ -624,14 +628,12 @@ fn initial_cursor() -> Result<JournalSeek> {
|
|||
Err(ref err) if err.kind() == ErrorKind::NotFound => {
|
||||
info!("No previous cursor position, reading from journal tail");
|
||||
Ok(JournalSeek::Tail)
|
||||
},
|
||||
Err(err) => {
|
||||
(Err(err).context("Could not read cursor position"))?
|
||||
}
|
||||
Err(err) => (Err(err).context("Could not read cursor position"))?,
|
||||
}
|
||||
}
|
||||
|
||||
fn main () {
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
||||
// The directory in which cursor positions are persisted should
|
||||
|
@ -641,17 +643,17 @@ fn main () {
|
|||
process::exit(1);
|
||||
}
|
||||
|
||||
let cursor_position_dir = CURSOR_FILE.parent()
|
||||
let cursor_position_dir = CURSOR_FILE
|
||||
.parent()
|
||||
.expect("Invalid cursor position file path");
|
||||
|
||||
fs::create_dir_all(cursor_position_dir)
|
||||
.expect("Could not create directory to store cursor position in");
|
||||
|
||||
let mut journal = Journal::open(JournalFiles::All, false, true)
|
||||
.expect("Failed to open systemd journal");
|
||||
let mut journal =
|
||||
Journal::open(JournalFiles::All, false, true).expect("Failed to open systemd journal");
|
||||
|
||||
let seek_position = initial_cursor()
|
||||
.expect("Failed to determine initial cursor position");
|
||||
let seek_position = initial_cursor().expect("Failed to determine initial cursor position");
|
||||
|
||||
match journal.seek(seek_position) {
|
||||
Ok(cursor) => info!("Opened journal at cursor '{}'", cursor),
|
||||
|
|
|
@ -15,7 +15,10 @@ fn test_text_entry_serialization() {
|
|||
let expected = "{\"labels\":null,\"textPayload\":\"test entry\"}";
|
||||
let result = to_string(&entry).expect("serialization failed");
|
||||
|
||||
assert_eq!(expected, result, "Plain text payload should serialize correctly")
|
||||
assert_eq!(
|
||||
expected, result,
|
||||
"Plain text payload should serialize correctly"
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -26,7 +29,7 @@ fn test_json_entry_serialization() {
|
|||
payload: Payload::JsonPayload {
|
||||
json_payload: json!({
|
||||
"message": "JSON test"
|
||||
})
|
||||
}),
|
||||
},
|
||||
severity: None,
|
||||
};
|
||||
|
@ -45,7 +48,10 @@ fn test_plain_text_payload() {
|
|||
text_payload: "plain text payload".into(),
|
||||
};
|
||||
|
||||
assert_eq!(expected, payload, "Plain text payload should be detected correctly");
|
||||
assert_eq!(
|
||||
expected, payload,
|
||||
"Plain text payload should be detected correctly"
|
||||
);
|
||||
}
|
||||
|
||||