use std::borrow::Cow;
use std::collections::BTreeMap;
use super::{ReplayState, RequestField, Subject, UnsupportedPolicy, Warning, WarningKind};
use crate::capability::sealed::AnyCapability;
use crate::{Capability, CapabilitySupport, Error, Result};
#[derive(Debug, Clone)]
pub struct Degradations {
provider: Cow<'static, str>,
policy: UnsupportedPolicy,
warnings: Vec<Warning>,
blocks: BlockMap,
unknown: Vec<(AnyCapability, Subject)>,
}
impl Degradations {
#[must_use]
pub fn new(provider: impl Into<Cow<'static, str>>, policy: UnsupportedPolicy) -> Self {
Self {
provider: provider.into(),
policy,
warnings: Vec::new(),
blocks: BlockMap::default(),
unknown: Vec::new(),
}
}
#[must_use]
pub fn policy(&self) -> UnsupportedPolicy {
self.policy
}
pub(crate) fn provider(&self) -> &str {
&self.provider
}
pub fn unsupported_hint(
&mut self,
field: RequestField,
message: impl Into<String>,
) -> Result<()> {
self.hint(Subject::Field { field }, message)
}
pub fn hint(&mut self, subject: Subject, message: impl Into<String>) -> Result<()> {
let message = message.into();
match self.policy {
UnsupportedPolicy::Warn => {
self.push(WarningKind::Unsupported, subject, message);
Ok(())
}
UnsupportedPolicy::Error => Err(self.error(&without_outcome(&message), Some(subject))),
}
}
pub(crate) fn coded_hint(
&mut self,
subject: Subject,
message: impl Into<String>,
code: &'static str,
) -> Result<()> {
let message = message.into();
match self.policy {
UnsupportedPolicy::Warn => {
self.warn(Warning::new(WarningKind::Unsupported, subject, message).with_code(code));
Ok(())
}
UnsupportedPolicy::Error => Err(self.error(&without_outcome(&message), Some(subject))),
}
}
#[must_use]
pub fn unsupported(&self, field: RequestField, message: impl Into<String>) -> Error {
self.error(&message.into(), Some(Subject::Field { field }))
}
#[must_use]
pub fn reject(&self, subject: impl Into<Option<Subject>>, message: impl Into<String>) -> Error {
self.error(&message.into(), subject.into())
}
pub fn coded_lossy_hint(
&mut self,
subject: impl Into<Subject>,
message: impl Into<String>,
code: &'static str,
) -> Result<()> {
let subject = subject.into();
let message = message.into();
match self.policy {
UnsupportedPolicy::Warn => {
if !self.join_coded(code, &subject) {
self.warn(
Warning::new(WarningKind::Approximated, subject, message).with_code(code),
);
}
Ok(())
}
UnsupportedPolicy::Error => Err(self.error(&without_outcome(&message), Some(subject))),
}
}
fn join_coded(&mut self, code: &str, subject: &Subject) -> bool {
let Some(existing) = self
.warnings
.iter_mut()
.find(|warning| warning.code.as_deref() == Some(code))
else {
return false;
};
let (Some(mut messages), Some(more)) = (
subject_messages(&existing.subject),
subject_messages(subject),
) else {
return false;
};
for index in more {
if let Err(at) = messages.binary_search(&index) {
messages.insert(at, index);
}
}
if messages.len() > 1 {
existing.subject = Subject::Messages { messages };
}
true
}
pub fn approximated(&mut self, subject: Subject, message: impl Into<String>) {
self.push(WarningKind::Approximated, subject, message.into());
}
pub fn coded_approximated(
&mut self,
subject: impl Into<Subject>,
message: impl Into<String>,
code: &'static str,
) {
let subject = subject.into();
if !self.join_coded(code, &subject) {
self.warn(Warning::new(WarningKind::Approximated, subject, message).with_code(code));
}
}
pub fn dropped(&mut self, subject: Subject, message: impl Into<String>) {
self.push(WarningKind::Dropped, subject, message.into());
}
pub fn dropped_replay_state(
&mut self,
state: ReplayState,
subject: Subject,
message: impl Into<String>,
) {
if self.join_coded(state.code(), &subject) {
return;
}
let mut message = message.into();
if state.is_foreign() {
message.push_str(
"; once a conversation moves to this provider for good, \
`retain_provider_state_for` removes another provider's state",
);
}
self.warn(Warning::new(WarningKind::Dropped, subject, message).with_code(state.code()));
}
pub fn warn(&mut self, mut warning: Warning) {
if warning.provider.is_none() {
warning.provider = Some(self.provider.clone());
}
let warning = self.translate(warning).with_default_code();
self.warnings.push(warning);
}
#[must_use]
pub(crate) fn translate(&self, mut warning: Warning) -> Warning {
warning.subject = self.translate_subject(warning.subject);
warning
}
fn translate_subject(&self, mut subject: Subject) -> Subject {
if let Subject::Content {
message,
block: Some(block),
} = &mut subject
{
*block = self.blocks.original(*message, *block);
}
subject
}
pub(crate) fn record_unknown<C: Capability>(&mut self, capability: C, subject: Subject) {
let capability = capability.into_any();
if self.unknown.iter().any(|(known, _)| *known == capability) {
return;
}
let subject = self.translate_subject(subject);
self.unknown.push((capability, subject));
}
pub(crate) fn refuses<C: Capability>(
&mut self,
capability: C,
support: CapabilitySupport,
subject: impl FnOnce() -> Subject,
) -> bool {
if support == CapabilitySupport::Unknown {
self.record_unknown(capability, subject());
}
support == CapabilitySupport::Unsupported
}
pub(crate) fn unknown<C: Capability>(&self) -> Vec<(C, Subject)> {
self.unknown
.iter()
.filter_map(|(capability, subject)| Some((C::from_any(*capability)?, subject.clone())))
.collect()
}
pub(crate) fn unknown_len(&self) -> usize {
self.unknown.len()
}
pub(crate) fn truncate_unknown(&mut self, len: usize) {
self.unknown.truncate(len);
}
pub(crate) fn record_kept_blocks(&mut self, message: usize, len: usize, kept: Vec<usize>) {
self.blocks.record(message, len, kept);
}
#[must_use]
pub fn warnings(&self) -> &[Warning] {
&self.warnings
}
#[must_use]
pub fn into_warnings(self) -> Vec<Warning> {
self.warnings
}
fn push(&mut self, kind: WarningKind, subject: Subject, message: String) {
let warning = Warning::new(kind, subject, message).with_provider(self.provider.clone());
self.warn(warning);
}
fn error(&self, reason: &str, subject: Option<Subject>) -> Error {
let subject = subject.map(|subject| self.translate_subject(subject));
let message = match &subject {
Some(subject) => format!("cannot send {subject}: {reason}"),
None => format!("cannot send the request: {reason}"),
};
Error::unsupported_before_sending(message, subject)
.authored()
.with_provider(self.provider.to_string())
}
}
fn subject_messages(subject: &Subject) -> Option<Vec<usize>> {
match subject {
Subject::Content { message, .. } => Some(vec![*message]),
Subject::Messages { messages } => Some(messages.clone()),
_ => None,
}
}
fn without_outcome(message: &str) -> Cow<'_, str> {
let mut parts = message.splitn(3, "; ");
let limitation = parts.next().unwrap_or(message);
match (parts.next(), parts.next()) {
(Some(_), Some(remedy)) => Cow::Owned(format!("{limitation}; {remedy}")),
_ => Cow::Borrowed(limitation),
}
}
impl Extend<Warning> for Degradations {
fn extend<I: IntoIterator<Item = Warning>>(&mut self, warnings: I) {
for warning in warnings {
self.warn(warning);
}
}
}
#[derive(Debug, Clone, Default)]
struct BlockMap {
messages: BTreeMap<usize, KeptBlocks>,
}
#[derive(Debug, Clone)]
struct KeptBlocks {
original_len: usize,
kept: Vec<usize>,
}
impl BlockMap {
fn original(&self, message: usize, block: usize) -> usize {
match self.messages.get(&message) {
Some(blocks) => blocks.original(block),
None => block,
}
}
fn record(&mut self, message: usize, len: usize, kept: Vec<usize>) {
let blocks = match self.messages.get(&message) {
Some(earlier) => KeptBlocks {
original_len: earlier.original_len,
kept: kept.into_iter().map(|i| earlier.original(i)).collect(),
},
None => KeptBlocks {
original_len: len,
kept,
},
};
self.messages.insert(message, blocks);
}
}
impl KeptBlocks {
fn original(&self, block: usize) -> usize {
self.kept
.get(block)
.copied()
.unwrap_or_else(|| self.original_len + (block - self.kept.len()))
}
}