1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-21 04:52:26 -05:00

Compare commits

...

7 commits

Author SHA1 Message Date
siaeyy
b9e4c6fe2a
Merge 3bc7642dcf into 0d3d4f5466 2025-01-20 21:52:38 +00:00
David Sherret
0d3d4f5466
fix(install/global): remove importMap field from specified config file (#27744)
Closes https://github.com/denoland/deno/issues/27734
2025-01-20 16:50:34 -05:00
Luca Casonato
5e9b3712de
feat(unstable): add basic support for otel trace links (#27727)
Currently only links with no attributes.
2025-01-20 15:39:59 +01:00
siaeyy
3bc7642dcf test(ext/dns): Test for dns resolving 2025-01-15 05:27:52 +03:00
siaeyy
e9a1b8e83a feat(node/dns): Tts for A and AAAA record queries 2025-01-15 05:26:21 +03:00
siaeyy
f5d1f3f038 feat(ext/net): Dns resolving for tts 2025-01-15 05:25:19 +03:00
siaeyy
406162d7b8 feat(ts/dns): RecordWithTtl interface 2025-01-15 05:23:39 +03:00
15 changed files with 431 additions and 78 deletions

View file

@ -115,10 +115,16 @@ exec deno {} "$@"
Ok(()) Ok(())
} }
fn get_installer_root() -> Result<PathBuf, io::Error> { fn get_installer_root() -> Result<PathBuf, AnyError> {
if let Ok(env_dir) = env::var("DENO_INSTALL_ROOT") { if let Some(env_dir) = env::var_os("DENO_INSTALL_ROOT") {
if !env_dir.is_empty() { if !env_dir.is_empty() {
return canonicalize_path_maybe_not_exists(&PathBuf::from(env_dir)); let env_dir = PathBuf::from(env_dir);
return canonicalize_path_maybe_not_exists(&env_dir).with_context(|| {
format!(
"Canonicalizing DENO_INSTALL_ROOT ('{}').",
env_dir.display()
)
});
} }
} }
// Note: on Windows, the $HOME environment variable may be set by users or by // Note: on Windows, the $HOME environment variable may be set by users or by
@ -587,11 +593,22 @@ async fn resolve_shim_data(
let copy_path = get_hidden_file_with_ext(&file_path, "deno.json"); let copy_path = get_hidden_file_with_ext(&file_path, "deno.json");
executable_args.push("--config".to_string()); executable_args.push("--config".to_string());
executable_args.push(copy_path.to_str().unwrap().to_string()); executable_args.push(copy_path.to_str().unwrap().to_string());
extra_files.push(( let mut config_text = fs::read_to_string(config_path)
copy_path, .with_context(|| format!("error reading {config_path}"))?;
fs::read_to_string(config_path) // always remove the import map field because when someone specifies `--import-map` we
.with_context(|| format!("error reading {config_path}"))?, // don't want that file to be attempted to be loaded and when they don't specify that
)); // (which is just something we haven't implemented yet)
if let Some(new_text) = remove_import_map_field_from_text(&config_text) {
if flags.import_map_path.is_none() {
log::warn!(
"{} \"importMap\" field in the specified config file we be ignored. Use the --import-map flag instead.",
crate::colors::yellow("Warning"),
);
}
config_text = new_text;
}
extra_files.push((copy_path, config_text));
} else { } else {
executable_args.push("--no-config".to_string()); executable_args.push("--no-config".to_string());
} }
@ -631,6 +648,16 @@ async fn resolve_shim_data(
}) })
} }
fn remove_import_map_field_from_text(config_text: &str) -> Option<String> {
let value =
jsonc_parser::cst::CstRootNode::parse(config_text, &Default::default())
.ok()?;
let root_value = value.object_value()?;
let import_map_value = root_value.get("importMap")?;
import_map_value.remove();
Some(value.to_string())
}
fn get_hidden_file_with_ext(file_path: &Path, ext: &str) -> PathBuf { fn get_hidden_file_with_ext(file_path: &Path, ext: &str) -> PathBuf {
// use a dot file to prevent the file from showing up in some // use a dot file to prevent the file from showing up in some
// users shell auto-complete since this directory is on the PATH // users shell auto-complete since this directory is on the PATH
@ -1585,4 +1612,17 @@ mod tests {
assert!(!file_path.exists()); assert!(!file_path.exists());
} }
} }
#[test]
fn test_remove_import_map_field_from_text() {
assert_eq!(
remove_import_map_field_from_text(
r#"{
"importMap": "./value.json"
}"#,
)
.unwrap(),
"{}"
);
}
} }

View file

@ -4674,6 +4674,22 @@ declare namespace Deno {
* and the promise returned will be rejected with an AbortError. * and the promise returned will be rejected with an AbortError.
*/ */
signal?: AbortSignal; signal?: AbortSignal;
/** */
ttl?: boolean;
}
export interface RecordWithTtl {
/** */
data:
| string
| CaaRecord
| MxRecord
| NaptrRecord
| SoaRecord
| SrvRecord
| string[];
/** */
ttl: number;
} }
/** If {@linkcode Deno.resolveDns} is called with `"CAA"` record type /** If {@linkcode Deno.resolveDns} is called with `"CAA"` record type
@ -4748,6 +4764,37 @@ declare namespace Deno {
target: string; target: string;
} }
/**
* Performs DNS resolution against the given query, returning resolved
* records with ttl values.
*
* Fails in the cases such as:
*
* - the query is in invalid format.
* - the options have an invalid parameter. For example `nameServer.port` is
* beyond the range of 16-bit unsigned integer.
* - the request timed out.
*
* ```ts
* const a = await Deno.resolveDns("example.com", "A", { ttl: true });
*
* const aaaa = await Deno.resolveDns("example.com", "AAAA", {
* nameServer: { ipAddr: "8.8.8.8", port: 53 },
* ttl: true,
* });
* ```
*
* Requires `allow-net` permission.
*
* @tags allow-net
* @category Network
*/
export function resolveDns(
query: string,
recordType: RecordType,
options?: { ttl: true } & ResolveDnsOptions,
): Promise<RecordWithTtl[]>;
/** /**
* Performs DNS resolution against the given query, returning resolved * Performs DNS resolution against the given query, returning resolved
* records. * records.
@ -4994,6 +5041,7 @@ declare namespace Deno {
| SoaRecord[] | SoaRecord[]
| SrvRecord[] | SrvRecord[]
| string[][] | string[][]
| RecordWithTtl[]
>; >;
/** /**

View file

@ -561,7 +561,7 @@ where
#[derive(Serialize, Eq, PartialEq, Debug)] #[derive(Serialize, Eq, PartialEq, Debug)]
#[serde(untagged)] #[serde(untagged)]
pub enum DnsReturnRecord { pub enum DnsRecordData {
A(String), A(String),
Aaaa(String), Aaaa(String),
Aname(String), Aname(String),
@ -603,6 +603,20 @@ pub enum DnsReturnRecord {
Txt(Vec<String>), Txt(Vec<String>),
} }
#[derive(Serialize, Eq, PartialEq, Debug)]
#[serde()]
pub struct DnsRecordWithTtl {
pub data: DnsRecordData,
pub ttl: u32,
}
#[derive(Serialize, Eq, PartialEq, Debug)]
#[serde(untagged)]
pub enum DnsReturnRecord {
WithoutTtl(DnsRecordData),
WithTtl(DnsRecordWithTtl),
}
#[derive(Deserialize)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ResolveAddrArgs { pub struct ResolveAddrArgs {
@ -616,6 +630,7 @@ pub struct ResolveAddrArgs {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ResolveDnsOption { pub struct ResolveDnsOption {
name_server: Option<NameServer>, name_server: Option<NameServer>,
ttl: Option<bool>,
} }
fn default_port() -> u16 { fn default_port() -> u16 {
@ -662,6 +677,8 @@ where
system_conf::read_system_conf()? system_conf::read_system_conf()?
}; };
let ttl = options.as_ref().and_then(|o| o.ttl).unwrap_or(false);
{ {
let mut s = state.borrow_mut(); let mut s = state.borrow_mut();
let perm = s.borrow_mut::<NP>(); let perm = s.borrow_mut::<NP>();
@ -720,8 +737,20 @@ where
} }
_ => NetError::Dns(e), _ => NetError::Dns(e),
})? })?
.records()
.iter() .iter()
.filter_map(|rdata| rdata_to_return_record(record_type)(rdata).transpose()) .filter_map(|rec| {
let data = format_rdata(record_type)(rec.data()).unwrap();
if ttl {
Some(DnsReturnRecord::WithTtl(DnsRecordWithTtl {
data: data?,
ttl: rec.ttl(),
}))
} else {
Some(DnsReturnRecord::WithoutTtl(data?))
}
})
.map(Ok)
.collect::<Result<Vec<DnsReturnRecord>, NetError>>() .collect::<Result<Vec<DnsReturnRecord>, NetError>>()
} }
@ -765,22 +794,22 @@ pub fn op_set_keepalive_inner(
resource.set_keepalive(keepalive).map_err(NetError::Map) resource.set_keepalive(keepalive).map_err(NetError::Map)
} }
fn rdata_to_return_record( fn format_rdata(
ty: RecordType, ty: RecordType,
) -> impl Fn(&RData) -> Result<Option<DnsReturnRecord>, NetError> { ) -> impl Fn(&RData) -> Result<Option<DnsRecordData>, NetError> {
use RecordType::*; use RecordType::*;
move |r: &RData| -> Result<Option<DnsReturnRecord>, NetError> { move |r: &RData| -> Result<Option<DnsRecordData>, NetError> {
let record = match ty { let record = match ty {
A => r.as_a().map(ToString::to_string).map(DnsReturnRecord::A), A => r.as_a().map(ToString::to_string).map(DnsRecordData::A),
AAAA => r AAAA => r
.as_aaaa() .as_aaaa()
.map(ToString::to_string) .map(ToString::to_string)
.map(DnsReturnRecord::Aaaa), .map(DnsRecordData::Aaaa),
ANAME => r ANAME => r
.as_aname() .as_aname()
.map(ToString::to_string) .map(ToString::to_string)
.map(DnsReturnRecord::Aname), .map(DnsRecordData::Aname),
CAA => r.as_caa().map(|caa| DnsReturnRecord::Caa { CAA => r.as_caa().map(|caa| DnsRecordData::Caa {
critical: caa.issuer_critical(), critical: caa.issuer_critical(),
tag: caa.tag().to_string(), tag: caa.tag().to_string(),
value: match caa.value() { value: match caa.value() {
@ -807,12 +836,12 @@ fn rdata_to_return_record(
CNAME => r CNAME => r
.as_cname() .as_cname()
.map(ToString::to_string) .map(ToString::to_string)
.map(DnsReturnRecord::Cname), .map(DnsRecordData::Cname),
MX => r.as_mx().map(|mx| DnsReturnRecord::Mx { MX => r.as_mx().map(|mx| DnsRecordData::Mx {
preference: mx.preference(), preference: mx.preference(),
exchange: mx.exchange().to_string(), exchange: mx.exchange().to_string(),
}), }),
NAPTR => r.as_naptr().map(|naptr| DnsReturnRecord::Naptr { NAPTR => r.as_naptr().map(|naptr| DnsRecordData::Naptr {
order: naptr.order(), order: naptr.order(),
preference: naptr.preference(), preference: naptr.preference(),
flags: String::from_utf8(naptr.flags().to_vec()).unwrap(), flags: String::from_utf8(naptr.flags().to_vec()).unwrap(),
@ -820,12 +849,9 @@ fn rdata_to_return_record(
regexp: String::from_utf8(naptr.regexp().to_vec()).unwrap(), regexp: String::from_utf8(naptr.regexp().to_vec()).unwrap(),
replacement: naptr.replacement().to_string(), replacement: naptr.replacement().to_string(),
}), }),
NS => r.as_ns().map(ToString::to_string).map(DnsReturnRecord::Ns), NS => r.as_ns().map(ToString::to_string).map(DnsRecordData::Ns),
PTR => r PTR => r.as_ptr().map(ToString::to_string).map(DnsRecordData::Ptr),
.as_ptr() SOA => r.as_soa().map(|soa| DnsRecordData::Soa {
.map(ToString::to_string)
.map(DnsReturnRecord::Ptr),
SOA => r.as_soa().map(|soa| DnsReturnRecord::Soa {
mname: soa.mname().to_string(), mname: soa.mname().to_string(),
rname: soa.rname().to_string(), rname: soa.rname().to_string(),
serial: soa.serial(), serial: soa.serial(),
@ -834,7 +860,7 @@ fn rdata_to_return_record(
expire: soa.expire(), expire: soa.expire(),
minimum: soa.minimum(), minimum: soa.minimum(),
}), }),
SRV => r.as_srv().map(|srv| DnsReturnRecord::Srv { SRV => r.as_srv().map(|srv| DnsRecordData::Srv {
priority: srv.priority(), priority: srv.priority(),
weight: srv.weight(), weight: srv.weight(),
port: srv.port(), port: srv.port(),
@ -848,7 +874,7 @@ fn rdata_to_return_record(
bytes.iter().map(|&b| b as char).collect::<String>() bytes.iter().map(|&b| b as char).collect::<String>()
}) })
.collect(); .collect();
DnsReturnRecord::Txt(texts) DnsRecordData::Txt(texts)
}), }),
_ => return Err(NetError::UnsupportedRecordType), _ => return Err(NetError::UnsupportedRecordType),
}; };
@ -891,37 +917,37 @@ mod tests {
#[test] #[test]
fn rdata_to_return_record_a() { fn rdata_to_return_record_a() {
let func = rdata_to_return_record(RecordType::A); let func = format_rdata(RecordType::A);
let rdata = RData::A(A(Ipv4Addr::new(127, 0, 0, 1))); let rdata = RData::A(A(Ipv4Addr::new(127, 0, 0, 1)));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::A("127.0.0.1".to_string())) Some(DnsRecordData::A("127.0.0.1".to_string()))
); );
} }
#[test] #[test]
fn rdata_to_return_record_aaaa() { fn rdata_to_return_record_aaaa() {
let func = rdata_to_return_record(RecordType::AAAA); let func = format_rdata(RecordType::AAAA);
let rdata = RData::AAAA(AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1))); let rdata = RData::AAAA(AAAA(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1)));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Aaaa("::1".to_string())) Some(DnsRecordData::Aaaa("::1".to_string()))
); );
} }
#[test] #[test]
fn rdata_to_return_record_aname() { fn rdata_to_return_record_aname() {
let func = rdata_to_return_record(RecordType::ANAME); let func = format_rdata(RecordType::ANAME);
let rdata = RData::ANAME(ANAME(Name::new())); let rdata = RData::ANAME(ANAME(Name::new()));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Aname("".to_string())) Some(DnsRecordData::Aname("".to_string()))
); );
} }
#[test] #[test]
fn rdata_to_return_record_caa() { fn rdata_to_return_record_caa() {
let func = rdata_to_return_record(RecordType::CAA); let func = format_rdata(RecordType::CAA);
let rdata = RData::CAA(CAA::new_issue( let rdata = RData::CAA(CAA::new_issue(
false, false,
Some(Name::parse("example.com", None).unwrap()), Some(Name::parse("example.com", None).unwrap()),
@ -929,7 +955,7 @@ mod tests {
)); ));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Caa { Some(DnsRecordData::Caa {
critical: false, critical: false,
tag: "issue".to_string(), tag: "issue".to_string(),
value: "example.com; account=123456".to_string(), value: "example.com; account=123456".to_string(),
@ -939,21 +965,21 @@ mod tests {
#[test] #[test]
fn rdata_to_return_record_cname() { fn rdata_to_return_record_cname() {
let func = rdata_to_return_record(RecordType::CNAME); let func = format_rdata(RecordType::CNAME);
let rdata = RData::CNAME(CNAME(Name::new())); let rdata = RData::CNAME(CNAME(Name::new()));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Cname("".to_string())) Some(DnsRecordData::Cname("".to_string()))
); );
} }
#[test] #[test]
fn rdata_to_return_record_mx() { fn rdata_to_return_record_mx() {
let func = rdata_to_return_record(RecordType::MX); let func = format_rdata(RecordType::MX);
let rdata = RData::MX(MX::new(10, Name::new())); let rdata = RData::MX(MX::new(10, Name::new()));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Mx { Some(DnsRecordData::Mx {
preference: 10, preference: 10,
exchange: "".to_string() exchange: "".to_string()
}) })
@ -962,7 +988,7 @@ mod tests {
#[test] #[test]
fn rdata_to_return_record_naptr() { fn rdata_to_return_record_naptr() {
let func = rdata_to_return_record(RecordType::NAPTR); let func = format_rdata(RecordType::NAPTR);
let rdata = RData::NAPTR(NAPTR::new( let rdata = RData::NAPTR(NAPTR::new(
1, 1,
2, 2,
@ -973,7 +999,7 @@ mod tests {
)); ));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Naptr { Some(DnsRecordData::Naptr {
order: 1, order: 1,
preference: 2, preference: 2,
flags: "".to_string(), flags: "".to_string(),
@ -986,27 +1012,27 @@ mod tests {
#[test] #[test]
fn rdata_to_return_record_ns() { fn rdata_to_return_record_ns() {
let func = rdata_to_return_record(RecordType::NS); let func = format_rdata(RecordType::NS);
let rdata = RData::NS(NS(Name::new())); let rdata = RData::NS(NS(Name::new()));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Ns("".to_string())) Some(DnsRecordData::Ns("".to_string()))
); );
} }
#[test] #[test]
fn rdata_to_return_record_ptr() { fn rdata_to_return_record_ptr() {
let func = rdata_to_return_record(RecordType::PTR); let func = format_rdata(RecordType::PTR);
let rdata = RData::PTR(PTR(Name::new())); let rdata = RData::PTR(PTR(Name::new()));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Ptr("".to_string())) Some(DnsRecordData::Ptr("".to_string()))
); );
} }
#[test] #[test]
fn rdata_to_return_record_soa() { fn rdata_to_return_record_soa() {
let func = rdata_to_return_record(RecordType::SOA); let func = format_rdata(RecordType::SOA);
let rdata = RData::SOA(SOA::new( let rdata = RData::SOA(SOA::new(
Name::new(), Name::new(),
Name::new(), Name::new(),
@ -1018,7 +1044,7 @@ mod tests {
)); ));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Soa { Some(DnsRecordData::Soa {
mname: "".to_string(), mname: "".to_string(),
rname: "".to_string(), rname: "".to_string(),
serial: 0, serial: 0,
@ -1032,11 +1058,11 @@ mod tests {
#[test] #[test]
fn rdata_to_return_record_srv() { fn rdata_to_return_record_srv() {
let func = rdata_to_return_record(RecordType::SRV); let func = format_rdata(RecordType::SRV);
let rdata = RData::SRV(SRV::new(1, 2, 3, Name::new())); let rdata = RData::SRV(SRV::new(1, 2, 3, Name::new()));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Srv { Some(DnsRecordData::Srv {
priority: 1, priority: 1,
weight: 2, weight: 2,
port: 3, port: 3,
@ -1047,7 +1073,7 @@ mod tests {
#[test] #[test]
fn rdata_to_return_record_txt() { fn rdata_to_return_record_txt() {
let func = rdata_to_return_record(RecordType::TXT); let func = format_rdata(RecordType::TXT);
let rdata = RData::TXT(TXT::from_bytes(vec![ let rdata = RData::TXT(TXT::from_bytes(vec![
"foo".as_bytes(), "foo".as_bytes(),
"bar".as_bytes(), "bar".as_bytes(),
@ -1056,7 +1082,7 @@ mod tests {
])); ]));
assert_eq!( assert_eq!(
func(&rdata).unwrap(), func(&rdata).unwrap(),
Some(DnsReturnRecord::Txt(vec![ Some(DnsRecordData::Txt(vec![
"foo".to_string(), "foo".to_string(),
"bar".to_string(), "bar".to_string(),
"£".to_string(), "£".to_string(),

View file

@ -186,20 +186,22 @@ export class ChannelWrap extends AsyncWrap implements ChannelWrapQuery {
this.#tries = tries; this.#tries = tries;
} }
async #query(query: string, recordType: Deno.RecordType) { async #query(query: string, recordType: Deno.RecordType, ttl?: boolean) {
// TODO(@bartlomieju): TTL logic.
let code: number; let code: number;
let ret: Awaited<ReturnType<typeof Deno.resolveDns>>; let ret: Awaited<ReturnType<typeof Deno.resolveDns>>;
const resolveOptions: Deno.ResolveDnsOptions = {
...ttl !== undefined ? { ttl } : {},
};
if (this.#servers.length) { if (this.#servers.length) {
for (const [ipAddr, port] of this.#servers) { for (const [ipAddr, port] of this.#servers) {
const resolveOptions = { Object.assign(resolveOptions, {
nameServer: { nameServer: {
ipAddr, ipAddr,
port, port,
}, },
}; });
({ code, ret } = await this.#resolve( ({ code, ret } = await this.#resolve(
query, query,
@ -212,7 +214,7 @@ export class ChannelWrap extends AsyncWrap implements ChannelWrapQuery {
} }
} }
} else { } else {
({ code, ret } = await this.#resolve(query, recordType)); ({ code, ret } = await this.#resolve(query, recordType, resolveOptions));
} }
return { code: code!, ret: ret! }; return { code: code!, ret: ret! };
@ -351,18 +353,34 @@ export class ChannelWrap extends AsyncWrap implements ChannelWrapQuery {
} }
queryA(req: QueryReqWrap, name: string): number { queryA(req: QueryReqWrap, name: string): number {
this.#query(name, "A").then(({ code, ret }) => { this.#query(name, "A", req.ttl).then(({ code, ret }) => {
req.oncomplete(code, ret); let recordsWithTtl;
if (req.ttl) {
recordsWithTtl = (ret as Deno.RecordWithTtl[]).map((val) => ({
address: val?.data,
ttl: val?.ttl,
}));
}
req.oncomplete(code, recordsWithTtl ?? ret);
}); });
return 0; return 0;
} }
queryAaaa(req: QueryReqWrap, name: string): number { queryAaaa(req: QueryReqWrap, name: string): number {
this.#query(name, "AAAA").then(({ code, ret }) => { this.#query(name, "AAAA", req.ttl).then(({ code, ret }) => {
const records = (ret as string[]).map((record) => compressIPv6(record)); let recordsWithTtl;
if (req.ttl) {
recordsWithTtl = (ret as Deno.RecordWithTtl[]).map((val) => ({
address: compressIPv6(val?.data as string),
ttl: val?.ttl,
}));
} else {
ret = (ret as string[]).map((record) => compressIPv6(record));
}
req.oncomplete(code, records); req.oncomplete(code, recordsWithTtl ?? ret);
}); });
return 0; return 0;

View file

@ -42,6 +42,7 @@ use opentelemetry::metrics::InstrumentBuilder;
use opentelemetry::metrics::MeterProvider as _; use opentelemetry::metrics::MeterProvider as _;
use opentelemetry::otel_debug; use opentelemetry::otel_debug;
use opentelemetry::otel_error; use opentelemetry::otel_error;
use opentelemetry::trace::Link;
use opentelemetry::trace::SpanContext; use opentelemetry::trace::SpanContext;
use opentelemetry::trace::SpanId; use opentelemetry::trace::SpanId;
use opentelemetry::trace::SpanKind; use opentelemetry::trace::SpanKind;
@ -94,6 +95,7 @@ deno_core::extension!(
op_otel_span_attribute1, op_otel_span_attribute1,
op_otel_span_attribute2, op_otel_span_attribute2,
op_otel_span_attribute3, op_otel_span_attribute3,
op_otel_span_add_link,
op_otel_span_update_name, op_otel_span_update_name,
op_otel_metric_attribute3, op_otel_metric_attribute3,
op_otel_metric_record0, op_otel_metric_record0,
@ -1324,17 +1326,6 @@ impl OtelSpan {
} }
} }
#[fast]
fn drop_link(&self) {
let mut state = self.0.borrow_mut();
match &mut **state {
OtelSpanState::Recording(span) => {
span.links.dropped_count += 1;
}
OtelSpanState::Done(_) => {}
}
}
#[fast] #[fast]
fn end(&self, end_time: f64) { fn end(&self, end_time: f64) {
let end_time = if end_time.is_nan() { let end_time = if end_time.is_nan() {
@ -1448,6 +1439,48 @@ fn op_otel_span_update_name<'s>(
} }
} }
#[op2(fast)]
fn op_otel_span_add_link<'s>(
scope: &mut v8::HandleScope<'s>,
span: v8::Local<'s, v8::Value>,
trace_id: v8::Local<'s, v8::Value>,
span_id: v8::Local<'s, v8::Value>,
#[smi] trace_flags: u8,
is_remote: bool,
#[smi] dropped_attributes_count: u32,
) -> bool {
let trace_id = parse_trace_id(scope, trace_id);
if trace_id == TraceId::INVALID {
return false;
};
let span_id = parse_span_id(scope, span_id);
if span_id == SpanId::INVALID {
return false;
};
let span_context = SpanContext::new(
trace_id,
span_id,
TraceFlags::new(trace_flags),
is_remote,
TraceState::NONE,
);
let Some(span) =
deno_core::_ops::try_unwrap_cppgc_object::<OtelSpan>(scope, span)
else {
return true;
};
let mut state = span.0.borrow_mut();
if let OtelSpanState::Recording(span) = &mut **state {
span.links.links.push(Link::new(
span_context,
vec![],
dropped_attributes_count,
));
}
true
}
struct OtelMeter(opentelemetry::metrics::Meter); struct OtelMeter(opentelemetry::metrics::Meter);
impl deno_core::GarbageCollected for OtelMeter {} impl deno_core::GarbageCollected for OtelMeter {}

View file

@ -15,6 +15,7 @@ import {
op_otel_metric_record2, op_otel_metric_record2,
op_otel_metric_record3, op_otel_metric_record3,
op_otel_metric_wait_to_observe, op_otel_metric_wait_to_observe,
op_otel_span_add_link,
op_otel_span_attribute1, op_otel_span_attribute1,
op_otel_span_attribute2, op_otel_span_attribute2,
op_otel_span_attribute3, op_otel_span_attribute3,
@ -186,7 +187,6 @@ interface OtelSpan {
spanContext(): SpanContext; spanContext(): SpanContext;
setStatus(status: SpanStatusCode, errorDescription: string): void; setStatus(status: SpanStatusCode, errorDescription: string): void;
dropEvent(): void; dropEvent(): void;
dropLink(): void;
end(endTime: number): void; end(endTime: number): void;
} }
@ -359,14 +359,24 @@ class Span {
return this; return this;
} }
addLink(_link: Link): Span { addLink(link: Link): Span {
this.#otelSpan?.dropLink(); const droppedAttributeCount = (link.droppedAttributesCount ?? 0) +
(link.attributes ? ObjectKeys(link.attributes).length : 0);
const valid = op_otel_span_add_link(
this.#otelSpan,
link.context.traceId,
link.context.spanId,
link.context.traceFlags,
link.context.isRemote ?? false,
droppedAttributeCount,
);
if (!valid) return this;
return this; return this;
} }
addLinks(links: Link[]): Span { addLinks(links: Link[]): Span {
for (let i = 0; i < links.length; i++) { for (let i = 0; i < links.length; i++) {
this.#otelSpan?.dropLink(); this.addLink(links[i]);
} }
return this; return this;
} }

View file

@ -22,6 +22,10 @@
}, },
"args": "run -A main.ts metric.ts", "args": "run -A main.ts metric.ts",
"output": "metric.out" "output": "metric.out"
},
"links": {
"args": "run -A main.ts links.ts",
"output": "links.out"
} }
} }
} }

View file

@ -0,0 +1,96 @@
{
"spans": [
{
"traceId": "00000000000000000000000000000001",
"spanId": "0000000000000001",
"traceState": "",
"parentSpanId": "",
"flags": 1,
"name": "example span",
"kind": 1,
"startTimeUnixNano": "[WILDCARD]",
"endTimeUnixNano": "[WILDCARD]",
"attributes": [],
"droppedAttributesCount": 0,
"events": [],
"droppedEventsCount": 0,
"links": [
{
"traceId": "1234567890abcdef1234567890abcdef",
"spanId": "1234567890abcdef",
"traceState": "",
"attributes": [],
"droppedAttributesCount": 0,
"flags": 1
}
],
"droppedLinksCount": 0,
"status": {
"message": "",
"code": 0
}
},
{
"traceId": "00000000000000000000000000000002",
"spanId": "0000000000000002",
"traceState": "",
"parentSpanId": "",
"flags": 1,
"name": "example span",
"kind": 1,
"startTimeUnixNano": "[WILDCARD]",
"endTimeUnixNano": "[WILDCARD]",
"attributes": [],
"droppedAttributesCount": 0,
"events": [],
"droppedEventsCount": 0,
"links": [
{
"traceId": "1234567890abcdef1234567890abcdef",
"spanId": "1234567890abcdef",
"traceState": "",
"attributes": [],
"droppedAttributesCount": 0,
"flags": 1
}
],
"droppedLinksCount": 0,
"status": {
"message": "",
"code": 0
}
},
{
"traceId": "00000000000000000000000000000003",
"spanId": "0000000000000003",
"traceState": "",
"parentSpanId": "",
"flags": 1,
"name": "example span",
"kind": 1,
"startTimeUnixNano": "[WILDCARD]",
"endTimeUnixNano": "[WILDCARD]",
"attributes": [],
"droppedAttributesCount": 0,
"events": [],
"droppedEventsCount": 0,
"links": [
{
"traceId": "1234567890abcdef1234567890abcdef",
"spanId": "1234567890abcdef",
"traceState": "",
"attributes": [],
"droppedAttributesCount": 2,
"flags": 1
}
],
"droppedLinksCount": 0,
"status": {
"message": "",
"code": 0
}
}
],
"logs": [],
"metrics": []
}

View file

@ -0,0 +1,40 @@
// Copyright 2018-2025 the Deno authors. MIT license.
import { trace } from "npm:@opentelemetry/api@1.9.0";
const tracer = trace.getTracer("example-tracer");
const span1 = tracer.startSpan("example span", {
links: [{
context: {
traceId: "1234567890abcdef1234567890abcdef",
spanId: "1234567890abcdef",
traceFlags: 1,
},
}],
});
span1.end();
const span2 = tracer.startSpan("example span");
span2.addLink({
context: {
traceId: "1234567890abcdef1234567890abcdef",
spanId: "1234567890abcdef",
traceFlags: 1,
},
});
span2.end();
const span3 = tracer.startSpan("example span");
span3.addLink({
context: {
traceId: "1234567890abcdef1234567890abcdef",
spanId: "1234567890abcdef",
traceFlags: 1,
},
attributes: {
key: "value",
},
droppedAttributesCount: 1,
});
span3.end();

View file

@ -0,0 +1,5 @@
{
"tempDir": true,
"args": "install -g --root ./folder --config deno.json main.ts --name my-cli",
"output": "install.out"
}

View file

@ -0,0 +1,3 @@
{
"importMap": "./import_map.json"
}

View file

@ -0,0 +1,2 @@
{
}

View file

@ -0,0 +1,3 @@
Warning "importMap" field in the specified config file we be ignored. Use the --import-map flag instead.
✅ Successfully installed my-cli
[WILDCARD]

View file

@ -0,0 +1 @@
console.log(1);

24
tests/unit/dns_test.ts Normal file
View file

@ -0,0 +1,24 @@
// Copyright 2018-2025 the Deno authors. MIT license.
import { resolve4, resolve6 } from "node:dns/promises";
import { assertEquals } from "@std/assert/equals";
Deno.test({
name: "Dns resolving for ttl values, A and AAAA records",
async fn() {
const ARecord = "34.120.54.55";
const AAAARecord = "2600:1901::6d85::";
const ARes1 = await Deno.resolveDns("deno.com", "A", { ttl: true });
const ARes2 = await resolve4("deno.com", { ttl: true });
assertEquals(ARes1[0].data, ARecord);
assertEquals(ARes2[0].address, ARecord);
const AAAARes1 = await Deno.resolveDns("deno.com", "AAAA", { ttl: true });
const AAAARes2 = await resolve6("deno.com", { ttl: true });
assertEquals(AAAARes1[0].data, AAAARecord);
assertEquals(AAAARes2[0].address, AAAARecord);
},
});