use std::collections::HashMap;
use std::ops::AddAssign;
use std::time::SystemTime;
use derivative::Derivative;
use http::header::HeaderName;
use itertools::Itertools;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
use url::Url;
use super::metrics::apollo::studio::ContextualizedStats;
use super::metrics::apollo::studio::SingleStats;
use super::metrics::apollo::studio::SingleStatsReport;
use super::tracing::apollo::TracesReport;
use crate::plugin::serde::deserialize_header_name;
use crate::plugin::serde::deserialize_vec_header_name;
use crate::plugins::telemetry::config::SamplerOption;
use crate::spaceport::ReferencedFieldsForType;
use crate::spaceport::ReportHeader;
use crate::spaceport::StatsContext;
use crate::spaceport::Trace;
#[derive(Derivative)]
#[derivative(Debug)]
#[derive(Clone, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub(crate) struct Config {
#[schemars(with = "Option<String>")]
pub(crate) endpoint: Option<Url>,
#[schemars(skip)]
#[serde(skip, default = "apollo_key")]
pub(crate) apollo_key: Option<String>,
#[schemars(skip)]
#[serde(skip, default = "apollo_graph_reference")]
pub(crate) apollo_graph_ref: Option<String>,
#[schemars(with = "Option<String>", default = "client_name_header_default_str")]
#[serde(
deserialize_with = "deserialize_header_name",
default = "client_name_header_default"
)]
pub(crate) client_name_header: HeaderName,
#[schemars(with = "Option<String>", default = "client_version_header_default_str")]
#[serde(
deserialize_with = "deserialize_header_name",
default = "client_version_header_default"
)]
pub(crate) client_version_header: HeaderName,
#[serde(default = "default_buffer_size")]
pub(crate) buffer_size: usize,
pub(crate) field_level_instrumentation_sampler: Option<SamplerOption>,
#[serde(default)]
pub(crate) send_headers: ForwardHeaders,
#[serde(default)]
pub(crate) send_variable_values: ForwardValues,
#[schemars(skip)]
pub(crate) schema_id: String,
}
fn apollo_key() -> Option<String> {
std::env::var("APOLLO_KEY").ok()
}
fn apollo_graph_reference() -> Option<String> {
std::env::var("APOLLO_GRAPH_REF").ok()
}
fn client_name_header_default_str() -> &'static str {
"apollographql-client-name"
}
fn client_name_header_default() -> HeaderName {
HeaderName::from_static(client_name_header_default_str())
}
fn client_version_header_default_str() -> &'static str {
"apollographql-client-version"
}
fn client_version_header_default() -> HeaderName {
HeaderName::from_static(client_version_header_default_str())
}
pub(crate) const fn default_buffer_size() -> usize {
10000
}
impl Default for Config {
fn default() -> Self {
Self {
endpoint: None,
apollo_key: None,
apollo_graph_ref: None,
client_name_header: client_name_header_default(),
client_version_header: client_version_header_default(),
schema_id: "<no_schema_id>".to_string(),
buffer_size: default_buffer_size(),
field_level_instrumentation_sampler: Some(SamplerOption::TraceIdRatioBased(0.01)),
send_headers: ForwardHeaders::None,
send_variable_values: ForwardValues::None,
}
}
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
pub(crate) enum ForwardHeaders {
None,
All,
#[serde(deserialize_with = "deserialize_vec_header_name")]
#[schemars(with = "Vec<String>")]
Only(Vec<HeaderName>),
#[schemars(with = "Vec<String>")]
#[serde(deserialize_with = "deserialize_vec_header_name")]
Except(Vec<HeaderName>),
}
impl Default for ForwardHeaders {
fn default() -> Self {
Self::None
}
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields, rename_all = "snake_case")]
pub(crate) enum ForwardValues {
None,
All,
Only(Vec<String>),
Except(Vec<String>),
}
impl Default for ForwardValues {
fn default() -> Self {
Self::None
}
}
#[derive(Debug, Serialize)]
pub(crate) enum SingleReport {
Stats(SingleStatsReport),
Traces(TracesReport),
}
#[derive(Default, Debug, Serialize)]
pub(crate) struct Report {
pub(crate) traces_per_query: HashMap<String, TracesAndStats>,
pub(crate) operation_count: u64,
}
impl Report {
#[cfg(test)]
pub(crate) fn new(reports: Vec<SingleStatsReport>) -> Report {
let mut aggregated_report = Report::default();
for report in reports {
aggregated_report += report;
}
aggregated_report
}
pub(crate) fn into_report(self, header: ReportHeader) -> crate::spaceport::Report {
let mut report = crate::spaceport::Report {
header: Some(header),
end_time: Some(SystemTime::now().into()),
operation_count: self.operation_count,
..Default::default()
};
for (key, traces_and_stats) in self.traces_per_query {
report.traces_per_query.insert(key, traces_and_stats.into());
}
report
}
}
impl AddAssign<SingleReport> for Report {
fn add_assign(&mut self, report: SingleReport) {
match report {
SingleReport::Stats(stats) => self.add_assign(stats),
SingleReport::Traces(traces) => self.add_assign(traces),
}
}
}
impl AddAssign<TracesReport> for Report {
fn add_assign(&mut self, report: TracesReport) {
self.operation_count += report.traces.len() as u64;
for (operation_signature, trace) in report.traces {
self.traces_per_query
.entry(operation_signature)
.or_default()
.traces
.push(trace);
}
}
}
impl AddAssign<SingleStatsReport> for Report {
fn add_assign(&mut self, report: SingleStatsReport) {
for (k, v) in report.stats {
*self.traces_per_query.entry(k).or_default() += v;
}
self.operation_count += report.operation_count;
}
}
#[derive(Default, Debug, Serialize)]
pub(crate) struct TracesAndStats {
pub(crate) traces: Vec<Trace>,
#[serde(with = "vectorize")]
pub(crate) stats_with_context: HashMap<StatsContext, ContextualizedStats>,
pub(crate) referenced_fields_by_type: HashMap<String, ReferencedFieldsForType>,
}
impl From<TracesAndStats> for crate::spaceport::TracesAndStats {
fn from(stats: TracesAndStats) -> Self {
Self {
stats_with_context: stats.stats_with_context.into_values().map_into().collect(),
referenced_fields_by_type: stats.referenced_fields_by_type,
trace: stats.traces,
..Default::default()
}
}
}
impl AddAssign<SingleStats> for TracesAndStats {
fn add_assign(&mut self, stats: SingleStats) {
*self
.stats_with_context
.entry(stats.stats_with_context.context.clone())
.or_default() += stats.stats_with_context;
self.referenced_fields_by_type = stats.referenced_fields_by_type;
}
}
pub(crate) mod vectorize {
use serde::Serialize;
use serde::Serializer;
pub(crate) fn serialize<'a, T, K, V, S>(target: T, ser: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: IntoIterator<Item = (&'a K, &'a V)>,
K: Serialize + 'a,
V: Serialize + 'a,
{
let container: Vec<_> = target.into_iter().collect();
serde::Serialize::serialize(&container, ser)
}
}