Files
codex/prs/bolinfest/study/PR-1684-study.md
2025-09-02 15:17:45 -07:00

4.6 KiB
Raw Blame History

DOs

  • Use strum Display with camelCase: Derive Display for EventMsg and set #[strum(serialize_all = "camelCase")] so event.msg.to_string() yields MCP-friendly method names.
use serde::{Deserialize, Serialize};
use strum_macros::Display;

#[derive(Debug, Clone, Deserialize, Serialize, Display)]
#[serde(tag = "type", rename_all = "snake_case")] // old schema tag values
#[strum(serialize_all = "camelCase")]             // new schema method names
pub enum EventMsg {
    SessionConfigured,
    ShutdownComplete,
    // ...
}
  • Emit both schemas during transition: Send the legacy "codex/event" and the new per-event method to stay backward compatible.
let params = Some(serde_json::to_value(event)?);

// Old schema
self.sender.send(OutgoingMessage::Notification(OutgoingNotification {
    method: "codex/event".to_string(),
    params: params.clone(),
})).await.ok();

// New schema (method from enum Display)
self.sender.send(OutgoingMessage::Notification(OutgoingNotification {
    method: event.msg.to_string(), // e.g., "sessionConfigured"
    params,
})).await.ok();
  • Parse both in tests and assert consistency: Accept either method and ensure the same session_id is seen in both.
let mut sid_old: Option<String> = None;
let mut sid_new: Option<String> = None;

if let JSONRPCMessage::Notification(n) = message {
    if let Some(p) = n.params {
        if n.method == "codex/event" {
            if p.get("msg")
                .and_then(|m| m.get("type")).and_then(|t| t.as_str())
                == Some("session_configured")
            {
                sid_old = p.get("msg")
                    .and_then(|m| m.get("session_id"))
                    .and_then(|v| v.as_str())
                    .map(|s| s.to_string());
            }
        }
        if n.method == "sessionConfigured" {
            sid_new = p.get("msg")
                .and_then(|m| m.get("session_id"))
                .and_then(|v| v.as_str())
                .map(|s| s.to_string());
        }
    }
}

if sid_old.is_some() && sid_new.is_some() {
    assert_eq!(sid_old.as_ref().unwrap(), sid_new.as_ref().unwrap());
}
  • Use a single source of truth for method names: Always call event.msg.to_string() instead of repeating string literals.
let method = event.msg.to_string(); // good
// let method = "sessionConfigured".to_string(); // avoid
  • Override per-variant names when needed: Use strum variant attributes to pin specific spellings.
use strum_macros::Display;

#[derive(Display)]
#[strum(serialize_all = "camelCase")]
enum EventMsg {
    #[strum(serialize = "sessionReady")] // custom override
    SessionConfigured,
    ToolRegistered,
}
  • Make the new params shape explicit: Prefer typed params for the new schema to make intent clear.
use serde::Serialize;

#[derive(Serialize)]
struct SessionConfiguredParams<'a> {
    session_id: &'a str,
}

// Later
self.sender.send(OutgoingMessage::Notification(OutgoingNotification {
    method: "sessionConfigured".into(),
    params: Some(serde_json::to_value(SessionConfiguredParams { session_id })?),
})).await.ok();

DONTs

  • Dont use lowercase method names: Avoid "sessionconfigured". MCP favors camelCase like "sessionConfigured".
// BAD
let method = "sessionconfigured";
// GOOD
let method = "sessionConfigured";
  • Dont hand-roll fmt::Display impls: Use #[derive(Display)] from strum instead of custom impl fmt::Display.
// Avoid manual impl; derive instead
#[derive(Display)]
#[strum(serialize_all = "camelCase")]
enum EventMsg { /* ... */ }
  • Dont collapse schemas into one ambiguous path: Keep legacy "codex/event" and new per-event methods distinct; dont replace one with the other prematurely.
// Avoid: only emitting "codex/event" or only the new method during transition
// Do: emit both until consumers migrate
  • Dont assume params stay the same: The new schema may change params. Dont lock into serde_json::Value forever—introduce typed structs as they stabilize.
// Avoid long-term:
let params: Option<serde_json::Value> = Some(serde_json::to_value(event)?);

// Prefer:
#[derive(Serialize)] struct ShutdownCompleteParams;
  • Dont duplicate magic strings in code/tests: Dont repeat method names in multiple places; derive from the enum to prevent drift.
assert_eq!(event.msg.to_string(), "sessionConfigured"); // one reference
  • Dont introduce incidental changes: Avoid unrelated edits (formatting, stray removals). Keep diffs focused so reviewers can disambiguate intentional protocol changes.