mod adt;
mod attrs;
pub mod docs;
#[cfg(test)]
mod docs_tests;
#[cfg(test)]
mod extract_tests;
pub mod foreign;
pub mod graphql_attrs;
#[cfg(test)]
mod graphql_tests;
mod naming;
pub mod route_sig;
#[cfg(test)]
mod route_sig_tests;
pub mod serde_attrs;
#[cfg(test)]
mod transparent_tests;
pub mod types;
mod wire_names;
use std::collections::{BTreeMap, BTreeSet};
use serde_json::Value;
pub use docs::Docs;
pub use foreign::{TypeMapping, TypeTable};
pub use route_sig::{Extractors, Overrides, RouteShape};
pub use types::Resolver;
#[derive(Debug, Clone)]
pub struct Extraction {
pub schema: Value,
pub components: BTreeMap<String, Value>,
pub gaps: Vec<Gap>,
pub aux_resolved: BTreeSet<String>,
}
pub fn extract_named<'a>(
docs: impl Into<Docs<'a>>,
table: &'a TypeTable,
name: &str,
) -> Result<Extraction, String> {
let mut resolver = Resolver::new(docs, table)?;
let schema = resolver
.resolve_by_name(name, name)
.ok_or_else(|| format!("no struct or enum named `{name}` in this crate's public API"))?;
Ok(Extraction {
schema,
components: resolver.components,
gaps: resolver.gaps,
aux_resolved: resolver.aux_resolved,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Gap {
Erased {
at: String,
bound: String,
},
CustomSerializer {
at: String,
with: String,
},
UnmappedForeign {
at: String,
path: String,
},
UnknownRenameRule { at: String, rule: String },
StrippedFields { at: String },
TransparentWithoutArgument {
at: String,
path: String,
arg: usize,
},
ComplexFieldArgument {
at: String,
argument: String,
},
}
impl Gap {
pub fn at(&self) -> &str {
match self {
Self::Erased { at, .. }
| Self::CustomSerializer { at, .. }
| Self::UnmappedForeign { at, .. }
| Self::UnknownRenameRule { at, .. }
| Self::TransparentWithoutArgument { at, .. }
| Self::ComplexFieldArgument { at, .. }
| Self::StrippedFields { at } => at,
}
}
pub fn explain(&self) -> String {
match self {
Self::Erased { at, bound } => format!(
"{at}: returns an erased type (bound `{bound}`) — the concrete \
shape is not in rustdoc JSON; annotate `response = \"...\"` \
on #[route] or let runtime capture fill it"
),
Self::CustomSerializer { at, with } => format!(
"{at}: `#[serde(with = \"{with}\")]` defines the wire shape in \
code, so the field type does not predict it; emitted open"
),
Self::UnmappedForeign { at, path } => format!(
"{at}: foreign type `{path}` has no mapping; add one under \
[spec.types] in soothfast.toml"
),
Self::UnknownRenameRule { at, rule } => format!(
"{at}: unsupported rename rule `\"{rule}\"`; names left under \
the framework's default rather than renamed wrongly"
),
Self::TransparentWithoutArgument { at, path, arg } => format!(
"{at}: `{path}` is mapped `transparent` under [spec.types] but \
is used with no type argument at position {arg}, so nothing \
says what it wraps; emitted open"
),
Self::StrippedFields { at } => format!(
"{at}: has private fields that rustdoc omits but serde still \
serializes; emitted open (regenerate with private items \
documented for the full field set)"
),
Self::ComplexFieldArgument { at, argument } => format!(
"{at}: the #[ComplexObject] resolver for this field takes a \
required argument `{argument}` with no default a JSON \
Schema can express; emitted open"
),
}
}
}