xtask/rmcp_release_monitor/
options.rs1use anyhow::{Context, Result, bail};
8use std::fs;
9use std::path::PathBuf;
10
11use super::DEFAULT_MAX_BODY_BYTES;
12use super::conformance::ConformanceMonitorInput;
13use super::schema::SchemaMonitorInput;
14
15#[derive(Debug)]
16pub(super) struct Options {
17 pub(super) crate_json: PathBuf,
18 pub(super) releases_json: PathBuf,
19 pub(super) issue_body: PathBuf,
20 schema_baseline: Option<PathBuf>,
21 schema_upstream: Option<PathBuf>,
22 schema_commits_json: Option<PathBuf>,
23 schema_url: String,
24 conformance_baseline: Option<PathBuf>,
25 conformance_head_json: Option<PathBuf>,
26 conformance_compare_json: Option<PathBuf>,
27 conformance_url: String,
28 pub(super) current_version: Option<String>,
29 pub(super) max_body_bytes: usize,
30}
31
32impl Options {
33 pub(super) fn parse(args: &[String]) -> Result<Self> {
34 let mut crate_json = None;
35 let mut releases_json = None;
36 let mut issue_body = None;
37 let mut schema_baseline = None;
38 let mut schema_upstream = None;
39 let mut schema_commits_json = None;
40 let mut schema_url =
41 "https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.ts"
42 .to_owned();
43 let mut conformance_baseline = None;
44 let mut conformance_head_json = None;
45 let mut conformance_compare_json = None;
46 let mut conformance_url = "https://github.com/modelcontextprotocol/conformance".to_owned();
47 let mut current_version = None;
48 let mut max_body_bytes = DEFAULT_MAX_BODY_BYTES;
49 let mut index = 0usize;
50 while index < args.len() {
51 match args[index].as_str() {
52 "--crate-json" => {
53 index += 1;
54 crate_json = Some(PathBuf::from(value_arg(args, index, "--crate-json")?));
55 }
56 "--releases-json" => {
57 index += 1;
58 releases_json = Some(PathBuf::from(value_arg(args, index, "--releases-json")?));
59 }
60 "--issue-body" => {
61 index += 1;
62 issue_body = Some(PathBuf::from(value_arg(args, index, "--issue-body")?));
63 }
64 "--schema-baseline" => {
65 index += 1;
66 schema_baseline =
67 Some(PathBuf::from(value_arg(args, index, "--schema-baseline")?));
68 }
69 "--schema-upstream" => {
70 index += 1;
71 schema_upstream =
72 Some(PathBuf::from(value_arg(args, index, "--schema-upstream")?));
73 }
74 "--schema-commits-json" => {
75 index += 1;
76 schema_commits_json = Some(PathBuf::from(value_arg(
77 args,
78 index,
79 "--schema-commits-json",
80 )?));
81 }
82 "--schema-url" => {
83 index += 1;
84 schema_url = value_arg(args, index, "--schema-url")?.to_owned();
85 }
86 "--conformance-baseline" => {
87 index += 1;
88 conformance_baseline = Some(PathBuf::from(value_arg(
89 args,
90 index,
91 "--conformance-baseline",
92 )?));
93 }
94 "--conformance-head-json" => {
95 index += 1;
96 conformance_head_json = Some(PathBuf::from(value_arg(
97 args,
98 index,
99 "--conformance-head-json",
100 )?));
101 }
102 "--conformance-compare-json" => {
103 index += 1;
104 conformance_compare_json = Some(PathBuf::from(value_arg(
105 args,
106 index,
107 "--conformance-compare-json",
108 )?));
109 }
110 "--conformance-url" => {
111 index += 1;
112 conformance_url = value_arg(args, index, "--conformance-url")?.to_owned();
113 }
114 "--current-version" => {
115 index += 1;
116 current_version = Some(value_arg(args, index, "--current-version")?.to_owned());
117 }
118 "--max-body-bytes" => {
119 index += 1;
120 max_body_bytes = value_arg(args, index, "--max-body-bytes")?
121 .parse::<usize>()
122 .context("--max-body-bytes must be an integer")?;
123 }
124 "--help" | "-h" => bail!(
125 "Usage: cargo xtask rmcp-release-monitor --crate-json rmcp.json --releases-json releases.json --issue-body issue.md [--schema-baseline schema.ts --schema-upstream upstream.ts] [--conformance-baseline main.sha --conformance-head-json head.json] [--current-version VERSION] [--max-body-bytes N]"
126 ),
127 unknown => bail!("unknown rmcp-release-monitor option: {unknown}"),
128 }
129 index += 1;
130 }
131 Ok(Self {
132 crate_json: crate_json.context("--crate-json is required")?,
133 releases_json: releases_json.context("--releases-json is required")?,
134 issue_body: issue_body.context("--issue-body is required")?,
135 schema_baseline,
136 schema_upstream,
137 schema_commits_json,
138 schema_url,
139 conformance_baseline,
140 conformance_head_json,
141 conformance_compare_json,
142 conformance_url,
143 current_version,
144 max_body_bytes,
145 })
146 }
147
148 pub(super) fn schema_input(&self) -> Result<Option<SchemaMonitorInput>> {
149 match (&self.schema_baseline, &self.schema_upstream) {
150 (Some(baseline), Some(upstream)) => Ok(Some(SchemaMonitorInput {
151 baseline: fs::read_to_string(baseline)
152 .with_context(|| format!("failed to read {}", baseline.display()))?,
153 upstream: fs::read_to_string(upstream)
154 .with_context(|| format!("failed to read {}", upstream.display()))?,
155 commits_json: self
156 .schema_commits_json
157 .as_ref()
158 .map(|path| {
159 fs::read_to_string(path)
160 .with_context(|| format!("failed to read {}", path.display()))
161 })
162 .transpose()?,
163 url: self.schema_url.clone(),
164 repo_root: PathBuf::from("."),
165 })),
166 (None, None) => Ok(None),
167 _ => bail!("--schema-baseline and --schema-upstream must be provided together"),
168 }
169 }
170
171 pub(super) fn conformance_input(&self) -> Result<Option<ConformanceMonitorInput>> {
172 match (&self.conformance_baseline, &self.conformance_head_json) {
173 (Some(baseline), Some(head_json)) => Ok(Some(ConformanceMonitorInput {
174 baseline_sha: fs::read_to_string(baseline)
175 .with_context(|| format!("failed to read {}", baseline.display()))?,
176 head_json: fs::read_to_string(head_json)
177 .with_context(|| format!("failed to read {}", head_json.display()))?,
178 compare_json: self
179 .conformance_compare_json
180 .as_ref()
181 .map(|path| {
182 fs::read_to_string(path)
183 .with_context(|| format!("failed to read {}", path.display()))
184 })
185 .transpose()?,
186 url: self.conformance_url.clone(),
187 repo_root: PathBuf::from("."),
188 })),
189 (None, None) => Ok(None),
190 _ => bail!(
191 "--conformance-baseline and --conformance-head-json must be provided together"
192 ),
193 }
194 }
195}
196
197fn value_arg<'a>(args: &'a [String], index: usize, flag: &str) -> Result<&'a str> {
198 args.get(index)
199 .map(String::as_str)
200 .with_context(|| format!("{flag} requires a value"))
201}