turbopack_ecmascript_hmr_protocol/
lib.rs1use std::{collections::BTreeMap, fmt::Display, path::PathBuf};
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use turbo_rcstr::RcStr;
6use turbopack_cli_utils::issue::{LogOptions, format_issue};
7use turbopack_core::{
8 issue::{IssueSeverity, IssueStage, PlainIssue, StyledString},
9 source_pos::SourcePos,
10};
11
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
13pub struct ResourceIdentifier {
14 pub path: RcStr,
15 pub headers: Option<BTreeMap<RcStr, RcStr>>,
16}
17
18impl Display for ResourceIdentifier {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 write!(f, "{}", self.path)?;
21 if let Some(headers) = &self.headers {
22 for (key, value) in headers.iter() {
23 write!(f, " [{key}: {value}]")?;
24 }
25 }
26 Ok(())
27 }
28}
29
30#[derive(Deserialize)]
31#[serde(tag = "type")]
32pub enum ClientMessage {
33 #[serde(rename = "turbopack-subscribe")]
34 Subscribe {
35 #[serde(flatten)]
36 resource: ResourceIdentifier,
37 },
38 #[serde(rename = "turbopack-unsubscribe")]
39 Unsubscribe {
40 #[serde(flatten)]
41 resource: ResourceIdentifier,
42 },
43}
44
45#[derive(Serialize)]
46#[serde(rename_all = "camelCase")]
47pub struct ClientUpdateInstruction<'a> {
48 pub resource: &'a ResourceIdentifier,
49 #[serde(flatten)]
50 pub ty: ClientUpdateInstructionType<'a>,
51 pub issues: &'a [Issue<'a>],
52}
53
54pub const EMPTY_ISSUES: &[Issue<'static>] = &[];
55
56impl<'a> ClientUpdateInstruction<'a> {
57 pub fn new(
58 resource: &'a ResourceIdentifier,
59 ty: ClientUpdateInstructionType<'a>,
60 issues: &'a [Issue<'a>],
61 ) -> Self {
62 Self {
63 resource,
64 ty,
65 issues,
66 }
67 }
68
69 pub fn restart(resource: &'a ResourceIdentifier, issues: &'a [Issue<'a>]) -> Self {
70 Self::new(resource, ClientUpdateInstructionType::Restart, issues)
71 }
72
73 pub fn not_found(resource: &'a ResourceIdentifier) -> Self {
76 Self::new(resource, ClientUpdateInstructionType::NotFound, &[])
77 }
78
79 pub fn partial(
80 resource: &'a ResourceIdentifier,
81 instruction: &'a Value,
82 issues: &'a [Issue<'a>],
83 ) -> Self {
84 Self::new(
85 resource,
86 ClientUpdateInstructionType::Partial { instruction },
87 issues,
88 )
89 }
90
91 pub fn issues(resource: &'a ResourceIdentifier, issues: &'a [Issue<'a>]) -> Self {
92 Self::new(resource, ClientUpdateInstructionType::Issues, issues)
93 }
94
95 pub fn with_issues(self, issues: &'a [Issue<'a>]) -> Self {
96 Self {
97 resource: self.resource,
98 ty: self.ty,
99 issues,
100 }
101 }
102}
103
104#[derive(Serialize)]
105#[serde(tag = "type", rename_all = "camelCase")]
106pub enum ClientUpdateInstructionType<'a> {
107 Restart,
108 NotFound,
109 Partial { instruction: &'a Value },
110 Issues,
111}
112
113#[derive(Serialize)]
114#[serde(tag = "type", rename_all = "camelCase")]
115pub enum ServerError {
116 SSR(String),
117 Turbo(String),
118}
119
120#[derive(Serialize)]
121pub struct Asset<'a> {
122 pub path: &'a str,
123}
124
125#[derive(Serialize)]
126pub struct IssueSource<'a> {
127 pub asset: Asset<'a>,
128 pub range: Option<IssueSourceRange>,
129}
130
131#[derive(Serialize)]
132pub struct IssueSourceRange {
133 pub start: SourcePos,
134 pub end: SourcePos,
135}
136
137#[derive(Serialize)]
138pub struct Issue<'a> {
139 pub severity: IssueSeverity,
140 pub file_path: &'a str,
141 pub stage: &'a IssueStage,
142
143 pub title: &'a StyledString,
144 pub description: Option<&'a StyledString>,
145 pub detail: Option<&'a StyledString>,
146 pub documentation_link: &'a str,
147
148 pub source: Option<IssueSource<'a>>,
149
150 pub formatted: String,
151}
152
153impl<'a> From<&'a PlainIssue> for Issue<'a> {
154 fn from(plain: &'a PlainIssue) -> Self {
155 let source = plain.source.as_ref().map(|source| IssueSource {
156 asset: Asset {
157 path: &source.asset.ident,
158 },
159 range: source
160 .range
161 .map(|(start, end)| IssueSourceRange { start, end }),
162 });
163
164 Issue {
165 severity: plain.severity,
166 file_path: &plain.file_path,
167 stage: &plain.stage,
168 title: &plain.title,
169 description: plain.description.as_ref(),
170 documentation_link: &plain.documentation_link,
171 detail: plain.detail.as_ref(),
172 source,
173 formatted: format_issue(
177 plain,
178 None,
179 &LogOptions {
180 current_dir: PathBuf::new(),
181 project_dir: PathBuf::new(),
182 show_all: true,
183 log_detail: true,
184 log_level: IssueSeverity::Info,
185 },
186 ),
187 }
188 }
189}