Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/feature-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
on:
pull_request:
push:
branches:
- main
name: Feature combinations check

jobs:
cargo_hack:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Rust toolchain and cache
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install cargo-hack
uses: taiki-e/install-action@cargo-hack
- name: Check all feature combinations (couchbase-connstr)
run: cargo hack check --feature-powerset --no-dev-deps -p couchbase-connstr
Comment thread
Matt-Woz marked this conversation as resolved.
- name: Check all feature combinations (couchbase-core)
run: |
cargo hack check --feature-powerset --no-dev-deps \
--exclude-features dhat-heap \
--group-features rustls-tls,native-tls \
--at-least-one-of rustls-tls,native-tls \
-p couchbase-core
- name: Check all feature combinations (couchbase)
run: |
cargo hack check --feature-powerset --no-dev-deps \
--exclude-features internal \
--group-features rustls-tls,native-tls \
--at-least-one-of rustls-tls,native-tls \
-p couchbase
- name: Check minimal build (couchbase-connstr, no default features)
run: cargo check --no-default-features -p couchbase-connstr
- name: Check minimal build (couchbase-core, rustls-tls only)
run: cargo check --no-default-features --features rustls-tls -p couchbase-core
- name: Check minimal build (couchbase, rustls-tls only)
run: cargo check --no-default-features --features rustls-tls -p couchbase
7 changes: 6 additions & 1 deletion sdk/couchbase-connstr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ authors = [

[dependencies]
form_urlencoded = "1.2"
hickory-resolver = { version = "0.25.2", features = ["tokio", "system-config"], default-features = false }
hickory-resolver = { version = "0.25.2", features = ["tokio", "system-config"], default-features = false, optional = true }
regex = "1.12"
tracing = { version = "0.1.44", features = ["log"] }
url = "2.5"

[dev-dependencies]
tokio = { version = "1.50", features = ["rt", "net", "macros"] }

[features]
default = ["dns-srv"]
dns-srv = ["dep:hickory-resolver"]

9 changes: 8 additions & 1 deletion sdk/couchbase-connstr/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ impl Error {
#[non_exhaustive]
pub enum ErrorKind {
Parse(String),
InvalidArgument { msg: String, arg: String },
InvalidArgument {
msg: String,
arg: String,
},
Io(io::Error),
#[cfg(feature = "dns-srv")]
Resolve(hickory_resolver::ResolveError),
}

Expand All @@ -50,6 +54,7 @@ impl Clone for ErrorKind {
arg: arg.clone(),
},
ErrorKind::Io(e) => Self::Io(io::Error::from(e.kind())),
#[cfg(feature = "dns-srv")]
ErrorKind::Resolve(e) => Self::Resolve(e.clone()),
}
}
Expand All @@ -69,6 +74,7 @@ impl Display for ErrorKind {
write!(f, "Invalid argument: {msg} ({arg})")
}
ErrorKind::Io(e) => write!(f, "IO error: {e}"),
#[cfg(feature = "dns-srv")]
ErrorKind::Resolve(e) => write!(f, "Resolve error: {e}"),
}
}
Expand All @@ -82,6 +88,7 @@ impl From<io::Error> for Error {
}
}

#[cfg(feature = "dns-srv")]
impl From<hickory_resolver::ResolveError> for Error {
fn from(e: hickory_resolver::ResolveError) -> Self {
Self {
Expand Down
73 changes: 65 additions & 8 deletions sdk/couchbase-connstr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,25 @@
pub mod error;

use error::ErrorKind;
#[cfg(feature = "dns-srv")]
use hickory_resolver::config::*;
#[cfg(feature = "dns-srv")]
use hickory_resolver::name_server::TokioConnectionProvider;
#[cfg(feature = "dns-srv")]
use hickory_resolver::proto::xfer::Protocol;
#[cfg(feature = "dns-srv")]
use hickory_resolver::system_conf::read_system_conf;
#[cfg(feature = "dns-srv")]
use hickory_resolver::TokioResolver;
use regex::Regex;
use std::collections::HashMap;
use std::fmt;
use std::fmt::{Display, Formatter};
#[cfg(feature = "dns-srv")]
use std::net::SocketAddr;
#[cfg(feature = "dns-srv")]
use std::time::Duration;
#[cfg(feature = "dns-srv")]
use tracing::debug;
use url::form_urlencoded;

Expand All @@ -52,6 +60,7 @@ pub struct Address {
pub port: u16,
}

#[cfg(feature = "dns-srv")]
#[derive(Debug, Clone, PartialEq)]
pub struct DnsConfig {
pub namespace: SocketAddr,
Expand Down Expand Up @@ -81,6 +90,7 @@ pub struct SrvRecord {
pub host: String,
}

#[cfg(feature = "dns-srv")]
impl ConnSpec {
fn srv_record(&self) -> Option<SrvRecord> {
if let Some(scheme_type) = &self.scheme {
Expand Down Expand Up @@ -222,6 +232,7 @@ pub struct ResolvedConnSpec {
pub options: HashMap<String, Vec<String>>,
}

#[cfg(feature = "dns-srv")]
pub async fn resolve(
conn_spec: ConnSpec,
dns_config: impl Into<Option<DnsConfig>>,
Expand Down Expand Up @@ -275,6 +286,40 @@ pub async fn resolve(
};
};

resolve_without_srv(conn_spec, default_port, has_explicit_scheme, use_ssl)
}

#[cfg(not(feature = "dns-srv"))]
pub async fn resolve(conn_spec: ConnSpec) -> error::Result<ResolvedConnSpec> {
let (default_port, has_explicit_scheme, use_ssl) = if let Some(scheme) = &conn_spec.scheme {
match scheme.as_str() {
"couchbase" => (DEFAULT_MEMD_PORT, true, false),
Comment thread
Matt-Woz marked this conversation as resolved.
"couchbases" => (DEFAULT_SSL_MEMD_PORT, true, true),
"couchbase2" => {
return handle_couchbase2_scheme(conn_spec);
}
"" => (DEFAULT_MEMD_PORT, false, false),
_ => {
return Err(ErrorKind::InvalidArgument {
msg: "unrecognized scheme".to_string(),
arg: "scheme".to_string(),
}
.into());
}
}
} else {
(DEFAULT_MEMD_PORT, false, false)
};

resolve_without_srv(conn_spec, default_port, has_explicit_scheme, use_ssl)
}

fn resolve_without_srv(
conn_spec: ConnSpec,
default_port: u16,
has_explicit_scheme: bool,
use_ssl: bool,
) -> error::Result<ResolvedConnSpec> {
if conn_spec.hosts.is_empty() {
let (memd_port, http_port) = if use_ssl {
(DEFAULT_SSL_MEMD_PORT, DEFAULT_LEGACY_HTTPS_PORT)
Expand Down Expand Up @@ -386,6 +431,7 @@ fn handle_couchbase2_scheme(conn_spec: ConnSpec) -> error::Result<ResolvedConnSp
})
}

#[cfg(feature = "dns-srv")]
async fn lookup_srv(
scheme: &str,
proto: &str,
Expand Down Expand Up @@ -431,6 +477,7 @@ async fn lookup_srv(
Ok(addresses)
}

#[cfg(feature = "dns-srv")]
fn host_is_ip_address(host: &str) -> bool {
host.starts_with('[') || host.parse::<std::net::IpAddr>().is_ok()
}
Expand All @@ -448,9 +495,22 @@ mod test {
}

async fn resolve_or_die(conn_spec: ConnSpec) -> ResolvedConnSpec {
resolve(conn_spec.clone(), None)
#[cfg(feature = "dns-srv")]
let result = resolve(conn_spec.clone(), None)
.await
.unwrap_or_else(|e| panic!("Failed to resolve {conn_spec:?}: {e:?}"))
.unwrap_or_else(|e| panic!("Failed to resolve {conn_spec:?}: {e:?}"));
#[cfg(not(feature = "dns-srv"))]
let result = resolve(conn_spec.clone())
.await
.unwrap_or_else(|e| panic!("Failed to resolve {conn_spec:?}: {e:?}"));
result
}

async fn resolve_is_err(conn_spec: ConnSpec) -> bool {
#[cfg(feature = "dns-srv")]
return resolve(conn_spec, None).await.is_err();
#[cfg(not(feature = "dns-srv"))]
return resolve(conn_spec).await.is_err();
}

fn check_address_parsing(
Expand Down Expand Up @@ -667,14 +727,11 @@ mod test {
.await;

let cs = parse_or_die("1.2.3.4:8091");
assert!(
resolve(cs, None).await.is_err(),
"Expected error with http port"
);
assert!(resolve_is_err(cs).await, "Expected error with http port");

let cs = parse_or_die("1.2.3.4:999");
assert!(
resolve(cs, None).await.is_err(),
resolve_is_err(cs).await,
"Expected error with non-default port without scheme"
);
}
Expand Down Expand Up @@ -755,7 +812,7 @@ mod test {

let cs = parse_or_die("couchbase://foo.com:8091");
assert!(
resolve(cs, None).await.is_err(),
resolve_is_err(cs).await,
"Expected error for couchbase://XXX:8091"
);

Expand Down
1 change: 0 additions & 1 deletion sdk/couchbase-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ crc32fast = "1.5"
futures = "0.3.32"
hmac = "0.12.1"
http = "1.4"
lazy_static = "1.5"
rand = "0.10.0"
regex = "1.12"
reqwest = { version = "0.13.2", features = ["json", "stream"], default-features = false }
Expand Down
29 changes: 13 additions & 16 deletions sdk/couchbase-core/src/helpers/durations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,24 +18,21 @@

use std::collections::HashMap;
use std::ops::Add;
use std::sync::LazyLock;
use std::time::Duration;

use lazy_static::lazy_static;

lazy_static! {
static ref UNIT_MAP: HashMap<&'static str, u64> = {
let mut m = HashMap::new();
m.insert("ns", 1);
m.insert("us", 1_000);
m.insert("µs", 1_000);
m.insert("μs", 1_000);
m.insert("ms", 1_000_000);
m.insert("s", 1_000_000_000);
m.insert("m", 60_000_000_000);
m.insert("h", 3_600_000_000_000);
m
};
}
static UNIT_MAP: LazyLock<HashMap<&'static str, u64>> = LazyLock::new(|| {
let mut m = HashMap::new();
m.insert("ns", 1);
m.insert("us", 1_000);
m.insert("µs", 1_000);
m.insert("μs", 1_000);
m.insert("ms", 1_000_000);
m.insert("s", 1_000_000_000);
m.insert("m", 60_000_000_000);
m.insert("h", 3_600_000_000_000);
m
});

// Note that this will not parse negative durations, rust Durations do not support negative values.
pub fn parse_duration_from_golang_string(s: &str) -> Result<Duration, String> {
Expand Down
22 changes: 9 additions & 13 deletions sdk/couchbase-core/src/httpx/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,11 @@ impl Decoder {
self.scan.incr_bytes(-1);
return Ok(scanp - self.scanp);
}
ScanState::EndObject | ScanState::EndArray => {
if self.scan.step(b' ') == ScanState::End {
scanp += 1;
return Ok(scanp - self.scanp);
}
ScanState::EndObject | ScanState::EndArray
if self.scan.step(b' ') == ScanState::End =>
{
scanp += 1;
return Ok(scanp - self.scanp);
}
ScanState::Error => {
let scan_err = self.scan.err().expect("scan state error but no error set");
Expand Down Expand Up @@ -246,10 +246,6 @@ impl Decoder {
}
}

fn input_offset(&self) -> usize {
self.scanned + self.scanp
}

pub async fn token(&mut self) -> HttpxResult<Token> {
loop {
let c = match self.peek().await {
Expand Down Expand Up @@ -354,10 +350,10 @@ impl Decoder {

fn token_error(&self, c: u8) -> HttpxResult<Token> {
let context = match self.token_state {
TokenState::TopValue => " looking for beginning of value",
TokenState::ArrayStart | TokenState::ArrayValue | TokenState::ObjectValue => {
" looking for beginning of value"
}
TokenState::TopValue
| TokenState::ArrayStart
| TokenState::ArrayValue
| TokenState::ObjectValue => " looking for beginning of value",
TokenState::ArrayComma => " after array element",
TokenState::ObjectKey => " looking for beginning of object key string",
TokenState::ObjectColon => " after object key",
Expand Down
2 changes: 0 additions & 2 deletions sdk/couchbase-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
*/

extern crate core;
#[macro_use]
extern crate lazy_static;

pub mod address;
pub mod agent;
Expand Down
30 changes: 14 additions & 16 deletions sdk/couchbase-core/src/mgmtx/mgmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,22 @@ use serde::de::DeserializeOwned;
use serde::{Deserialize, Deserializer};
use serde_json::value::RawValue;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::{Arc, LazyLock};
use std::time::Duration;

lazy_static! {
static ref FIELD_NAME_MAP: HashMap<String, String> = {
HashMap::from([
(
"durability_min_level".to_string(),
"DurabilityMinLevel".to_string(),
),
("ramquota".to_string(), "RamQuotaMB".to_string()),
("replicanumber".to_string(), "ReplicaNumber".to_string()),
("maxttl".to_string(), "MaxTTL".to_string()),
("history".to_string(), "HistoryEnabled".to_string()),
("numvbuckets".to_string(), "numVBuckets".to_string()),
])
};
}
static FIELD_NAME_MAP: LazyLock<HashMap<String, String>> = LazyLock::new(|| {
HashMap::from([
(
"durability_min_level".to_string(),
"DurabilityMinLevel".to_string(),
),
("ramquota".to_string(), "RamQuotaMB".to_string()),
("replicanumber".to_string(), "ReplicaNumber".to_string()),
("maxttl".to_string(), "MaxTTL".to_string()),
("history".to_string(), "HistoryEnabled".to_string()),
("numvbuckets".to_string(), "numVBuckets".to_string()),
])
});

#[derive(Debug)]
pub struct Management<C: Client> {
Expand Down
Loading
Loading