chore: add #[non_exhaustive] to remaining public structs (#768)

* chore: add #[non_exhaustive] to remaining public structs

* chore: add #[non_exhaustive] to remaining public types

* chore: enable exhaustive_structs/enums clippy lints

* test: add untagged ServerResult deserialization regression tests
This commit is contained in:
Dale Seo 2026-03-26 10:23:58 -04:00 • committed by GitHub
commit 6a3b32d3ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 430 additions and 211 deletions

View file

@ -39,6 +39,7 @@ impl Annotations {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct Annotated<T: AnnotateAble> {
#[serde(flatten)]
pub raw: T,

View file

@ -34,6 +34,7 @@ pub type ExtensionCapabilities = BTreeMap<String, JsonObject>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct PromptsCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
@ -42,6 +43,7 @@ pub struct PromptsCapability {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ResourcesCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub subscribe: Option<bool>,
@ -52,6 +54,7 @@ pub struct ResourcesCapability {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ToolsCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
@ -60,6 +63,7 @@ pub struct ToolsCapability {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct RootsCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub list_changed: Option<bool>,
@ -69,6 +73,7 @@ pub struct RootsCapabilities {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct TasksCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub requests: Option<TaskRequestsCapability>,
@ -82,6 +87,7 @@ pub struct TasksCapability {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct TaskRequestsCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub sampling: Option<SamplingTaskCapability>,
@ -94,6 +100,7 @@ pub struct TaskRequestsCapability {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct SamplingTaskCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub create_message: Option<JsonObject>,
@ -102,6 +109,7 @@ pub struct SamplingTaskCapability {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ElicitationTaskCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub create: Option<JsonObject>,
@ -110,6 +118,7 @@ pub struct ElicitationTaskCapability {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ToolsTaskCapability {
#[serde(skip_serializing_if = "Option::is_none")]
pub call: Option<JsonObject>,
@ -190,6 +199,7 @@ impl TasksCapability {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct FormElicitationCapability {
/// Whether the client supports JSON Schema validation for elicitation responses.
/// When true, the client will validate user input against the requested_schema
@ -201,6 +211,7 @@ pub struct FormElicitationCapability {
/// Capability for URL mode elicitation.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct UrlElicitationCapability {}
/// Elicitation allows servers to request interactive input from users during tool execution.
@ -209,6 +220,7 @@ pub struct UrlElicitationCapability {}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ElicitationCapability {
/// Whether client supports form-based elicitation.
#[serde(skip_serializing_if = "Option::is_none")]
@ -222,6 +234,7 @@ pub struct ElicitationCapability {
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct SamplingCapability {
/// Support for `tools` and `toolChoice` parameters
#[serde(skip_serializing_if = "Option::is_none")]
@ -310,10 +323,12 @@ macro_rules! builder {
($Target: ident {$($f: ident: $T: ty),* $(,)?}) => {
paste! {
#[derive(Default, Clone, Copy, Debug)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct [<$Target BuilderState>]<
$(const [<$f:upper>]: bool = false,)*
>;
#[derive(Debug, Default)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct [<$Target Builder>]<S = [<$Target BuilderState>]> {
$(pub $f: Option<$T>,)*
pub state: PhantomData<S>

View file

@ -9,6 +9,7 @@ use super::{AnnotateAble, Annotated, resource::ResourceContents};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct RawTextContent {
pub text: String,
/// Optional protocol-level metadata for this content block
@ -19,6 +20,7 @@ pub type TextContent = Annotated<RawTextContent>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct RawImageContent {
/// The base64-encoded image
pub data: String,
@ -32,6 +34,7 @@ pub type ImageContent = Annotated<RawImageContent>;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct RawEmbeddedResource {
/// Optional protocol-level metadata for this content block
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
@ -63,6 +66,7 @@ impl EmbeddedResource {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct RawAudioContent {
pub data: String,
pub mime_type: String,
@ -145,6 +149,7 @@ impl ToolResultContent {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum RawContent {
Text(RawTextContent),
Image(RawImageContent),

View file

@ -49,6 +49,7 @@ const_string!(ArrayTypeConst = "array");
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum PrimitiveSchema {
/// Enum property (explicit enum schema)
Enum(EnumSchema),
@ -70,6 +71,7 @@ pub enum PrimitiveSchema {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "kebab-case")]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum StringFormat {
/// Email address format
Email,
@ -344,6 +346,7 @@ impl NumberSchema {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct IntegerSchema {
/// Type discriminator
#[serde(rename = "type")]
@ -510,6 +513,7 @@ impl BooleanSchema {
/// Represent single entry for titled item
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ConstTitle {
#[serde(rename = "const")]
pub const_: String,
@ -530,6 +534,7 @@ impl ConstTitle {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct LegacyEnumSchema {
#[serde(rename = "type")]
pub type_: StringTypeConst,
@ -595,6 +600,7 @@ impl TitledSingleSelectEnumSchema {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum SingleSelectEnumSchema {
Untitled(UntitledSingleSelectEnumSchema),
Titled(TitledSingleSelectEnumSchema),
@ -603,6 +609,7 @@ pub enum SingleSelectEnumSchema {
/// Items for untitled multi-select options
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct UntitledItems {
#[serde(rename = "type")]
pub type_: StringTypeConst,
@ -613,6 +620,7 @@ pub struct UntitledItems {
/// Items for titled multi-select options
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct TitledItems {
// MCP spec requires "anyOf" for multi-select enums (allows any combination)
// Alias "oneOf" for compatibility with schemars
@ -718,6 +726,7 @@ impl TitledMultiSelectEnumSchema {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum MultiSelectEnumSchema {
Untitled(UntitledMultiSelectEnumSchema),
Titled(TitledMultiSelectEnumSchema),
@ -741,6 +750,7 @@ pub enum MultiSelectEnumSchema {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum EnumSchema {
Single(SingleSelectEnumSchema),
Multi(MultiSelectEnumSchema),
@ -749,9 +759,11 @@ pub enum EnumSchema {
/// Marker type for single-select enum builder
#[derive(Debug)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct SingleSelect;
/// Marker type for multi-select enum builder
#[derive(Debug)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct MultiSelect;
/// Builder for EnumSchema
/// Allows to create various enum schema types (single/multi select, titled/untitled)
@ -1077,6 +1089,7 @@ impl EnumSchema {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase")]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ElicitationSchema {
/// Always "object" for elicitation schemas
#[serde(rename = "type")]
@ -1221,6 +1234,7 @@ impl ElicitationSchema {
/// .build();
/// ```
#[derive(Debug, Default)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct ElicitationSchemaBuilder {
pub properties: BTreeMap<String, PrimitiveSchema>,
pub required: Vec<String>,

View file

@ -195,6 +195,7 @@ variant_extension! {
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(transparent)]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct Meta(pub JsonObject);
const PROGRESS_TOKEN_FIELD: &str = "progressToken";
impl Meta {

View file

@ -138,6 +138,7 @@ impl PromptArgument {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum PromptMessageRole {
User,
Assistant,
@ -147,6 +148,7 @@ pub enum PromptMessageRole {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum PromptMessageContent {
/// Plain text content
Text { text: String },

View file

@ -6,6 +6,7 @@ use super::{Annotated, Icon, Meta};
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct RawResource {
/// URI representing the resource location (e.g., "file:///path/to/file" or "str:///content")
pub uri: String,
@ -39,6 +40,7 @@ pub type Resource = Annotated<RawResource>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct RawResourceTemplate {
pub uri_template: String,
pub name: String,
@ -58,6 +60,7 @@ pub type ResourceTemplate = Annotated<RawResourceTemplate>;
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum ResourceContents {
#[serde(rename_all = "camelCase")]
TextResourceContents {

View file

@ -7,6 +7,7 @@ use super::Meta;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum TaskStatus {
/// The receiver accepted the request and is currently working on it.
#[default]
@ -110,6 +111,7 @@ impl CreateTaskResult {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct GetTaskResult {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
@ -160,6 +162,7 @@ impl<'de> serde::Deserialize<'de> for GetTaskPayloadResult {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct CancelTaskResult {
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<Meta>,
@ -171,6 +174,7 @@ pub struct CancelTaskResult {
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_structs, reason = "intentionally exhaustive")]
pub struct TaskList {
pub tasks: Vec<Task>,
#[serde(skip_serializing_if = "Option::is_none")]

View file

@ -51,6 +51,7 @@ pub struct Tool {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[expect(clippy::exhaustive_enums, reason = "intentionally exhaustive")]
pub enum TaskSupport {
/// Clients MUST NOT invoke this tool as a task (default behavior).
#[default]