1use crate::model::{
2 ApiKind, ContractMethod, ContractRenderRequest, ContractService, ContractTypedItem,
3 ReadReturnKind, ValueType,
4};
5use std::fmt::{Display, Formatter};
6
7pub const STRING_BYTE_CAPACITY: usize = 32;
8pub const STRING_REGISTER_WIDTH: u16 = 17;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub enum AreaKind {
12 Coil,
13 DiscreteInput,
14 HoldingRegister,
15 InputRegister,
16}
17
18impl AreaKind {
19 pub fn c_label(&self) -> &'static str {
20 match self {
21 Self::Coil => "线圈",
22 Self::DiscreteInput => "离散输入",
23 Self::HoldingRegister => "保持寄存器",
24 Self::InputRegister => "输入寄存器",
25 }
26 }
27
28 pub fn function_code_label(&self) -> &'static str {
29 match self {
30 Self::Coil => "0x05/0x0F",
31 Self::DiscreteInput => "0x02",
32 Self::HoldingRegister => "0x06/0x10",
33 Self::InputRegister => "0x04",
34 }
35 }
36}
37
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum MethodWireKind {
40 Bit,
41 Register,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum PlannedValue {
46 Dto {
47 type_name: String,
48 items: Vec<ItemPlan>,
49 },
50 Scalar {
51 item: ItemPlan,
52 },
53 Parameters {
54 items: Vec<ItemPlan>,
55 },
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct ItemPlan {
60 pub name: String,
61 pub summary: Option<String>,
62 pub value_type: ValueType,
63 pub offset: u16,
64 pub width: u16,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct MethodPlan {
69 pub api_kind: ApiKind,
70 pub interface_name: String,
71 pub interface_summary: Option<String>,
72 pub method_name: String,
73 pub method_summary: Option<String>,
74 pub c_method_name: String,
75 pub rust_method_name: String,
76 pub area_kind: AreaKind,
77 pub wire_kind: MethodWireKind,
78 pub start_address: u16,
79 pub quantity: u16,
80 pub planned_value: PlannedValue,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct ContractPlan {
85 pub services: Vec<ContractService>,
86 pub methods: Vec<MethodPlan>,
87 pub total_coils: u16,
88 pub total_discrete_inputs: u16,
89 pub total_holding_registers: u16,
90 pub total_input_registers: u16,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub struct ContractPlanError {
95 message: String,
96}
97
98impl ContractPlanError {
99 fn new(message: impl Into<String>) -> Self {
100 Self {
101 message: message.into(),
102 }
103 }
104}
105
106impl Display for ContractPlanError {
107 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
108 formatter.write_str(&self.message)
109 }
110}
111
112impl std::error::Error for ContractPlanError {}
113
114pub fn build_plan(request: &ContractRenderRequest) -> Result<ContractPlan, ContractPlanError> {
115 if request.transports.is_empty() {
116 return Err(ContractPlanError::new("至少选择一种传输类型"));
117 }
118 if request.services.is_empty() {
119 return Err(ContractPlanError::new("至少定义一个 Api 服务"));
120 }
121
122 let mut coil_cursor = 0_u16;
123 let mut discrete_input_cursor = 0_u16;
124 let mut holding_register_cursor = 0_u16;
125 let mut input_register_cursor = 0_u16;
126 let mut methods = Vec::new();
127
128 for service in &request.services {
129 if service.methods.is_empty() {
130 return Err(ContractPlanError::new(format!(
131 "服务 {} 至少需要一个方法",
132 service.interface_name
133 )));
134 }
135 for method in &service.methods {
136 let normalized_interface_name = normalize_type_name(&service.interface_name);
137 let c_method_name = to_snake_case(&method.method_name);
138 let rust_method_name = c_method_name.clone();
139 let (area_kind, wire_kind, planned_value) = plan_method_value(service, method)?;
140 let quantity = planned_value_width(&planned_value);
141 let start_address = match area_kind {
142 AreaKind::Coil => {
143 let start = coil_cursor;
144 coil_cursor = checked_next_cursor(coil_cursor, quantity, "线圈")?;
145 start
146 }
147 AreaKind::DiscreteInput => {
148 let start = discrete_input_cursor;
149 discrete_input_cursor =
150 checked_next_cursor(discrete_input_cursor, quantity, "离散输入")?;
151 start
152 }
153 AreaKind::HoldingRegister => {
154 let start = holding_register_cursor;
155 holding_register_cursor =
156 checked_next_cursor(holding_register_cursor, quantity, "保持寄存器")?;
157 start
158 }
159 AreaKind::InputRegister => {
160 let start = input_register_cursor;
161 input_register_cursor =
162 checked_next_cursor(input_register_cursor, quantity, "输入寄存器")?;
163 start
164 }
165 };
166 methods.push(MethodPlan {
167 api_kind: service.api_kind.clone(),
168 interface_name: normalized_interface_name,
169 interface_summary: trim_option(&service.interface_summary),
170 method_name: method.method_name.trim().to_string(),
171 method_summary: trim_option(&method.summary),
172 c_method_name,
173 rust_method_name,
174 area_kind,
175 wire_kind,
176 start_address,
177 quantity,
178 planned_value,
179 });
180 }
181 }
182
183 Ok(ContractPlan {
184 services: request.services.clone(),
185 methods,
186 total_coils: coil_cursor,
187 total_discrete_inputs: discrete_input_cursor,
188 total_holding_registers: holding_register_cursor,
189 total_input_registers: input_register_cursor,
190 })
191}
192
193fn checked_next_cursor(cursor: u16, quantity: u16, label: &str) -> Result<u16, ContractPlanError> {
194 cursor
195 .checked_add(quantity)
196 .ok_or_else(|| ContractPlanError::new(format!("{label}地址规划超出 16 位地址上限")))
197}
198
199fn plan_method_value(
200 service: &ContractService,
201 method: &ContractMethod,
202) -> Result<(AreaKind, MethodWireKind, PlannedValue), ContractPlanError> {
203 let method_name = method.method_name.trim();
204 if method_name.is_empty() {
205 return Err(ContractPlanError::new("方法名不能为空"));
206 }
207
208 match service.api_kind {
209 ApiKind::Write => {
210 if method.parameters.is_empty() {
211 return Err(ContractPlanError::new(format!(
212 "写方法 {method_name} 至少需要一个参数"
213 )));
214 }
215 let bit_only = method
216 .parameters
217 .iter()
218 .all(|parameter| matches!(parameter.value_type, ValueType::Boolean));
219 let items = build_item_plans(&method.parameters, bit_only)?;
220 let planned_value = PlannedValue::Parameters { items };
221 Ok((
222 if bit_only {
223 AreaKind::Coil
224 } else {
225 AreaKind::HoldingRegister
226 },
227 if bit_only {
228 MethodWireKind::Bit
229 } else {
230 MethodWireKind::Register
231 },
232 planned_value,
233 ))
234 }
235 ApiKind::StaticRead | ApiKind::RuntimeRead => {
236 let read_return_kind = method
237 .read_return_kind
238 .clone()
239 .unwrap_or(ReadReturnKind::Dto);
240 let planned_value = match read_return_kind {
241 ReadReturnKind::Dto => {
242 if method.read_fields.is_empty() {
243 return Err(ContractPlanError::new(format!(
244 "读方法 {method_name} 选择 DTO 返回时至少需要一个字段"
245 )));
246 }
247 let dto_type_name = normalize_type_name(
248 method
249 .read_return_type_name
250 .as_deref()
251 .unwrap_or(method_name),
252 );
253 let bit_only = method
254 .read_fields
255 .iter()
256 .all(|field| matches!(field.value_type, ValueType::Boolean));
257 let items = build_item_plans(&method.read_fields, bit_only)?;
258 (
259 if matches!(service.api_kind, ApiKind::RuntimeRead) && bit_only {
260 AreaKind::DiscreteInput
261 } else {
262 AreaKind::InputRegister
263 },
264 if bit_only {
265 MethodWireKind::Bit
266 } else {
267 MethodWireKind::Register
268 },
269 PlannedValue::Dto {
270 type_name: dto_type_name,
271 items,
272 },
273 )
274 }
275 other => {
276 let scalar_type = read_return_kind_to_value_type(&other);
277 let bit_only = matches!(scalar_type, ValueType::Boolean)
278 && matches!(service.api_kind, ApiKind::RuntimeRead);
279 let item = ItemPlan {
280 name: "value".to_string(),
281 summary: trim_option(&method.summary),
282 value_type: scalar_type.clone(),
283 offset: 0,
284 width: value_type_width(&scalar_type, bit_only),
285 };
286 (
287 if bit_only {
288 AreaKind::DiscreteInput
289 } else {
290 AreaKind::InputRegister
291 },
292 if bit_only {
293 MethodWireKind::Bit
294 } else {
295 MethodWireKind::Register
296 },
297 PlannedValue::Scalar { item },
298 )
299 }
300 };
301 Ok(planned_value)
302 }
303 }
304}
305
306fn build_item_plans(
307 items: &[ContractTypedItem],
308 bit_only: bool,
309) -> Result<Vec<ItemPlan>, ContractPlanError> {
310 let mut offset = 0_u16;
311 let mut planned = Vec::with_capacity(items.len());
312 for item in items {
313 let item_name = item.name.trim();
314 if item_name.is_empty() {
315 return Err(ContractPlanError::new("字段或参数名不能为空"));
316 }
317 let width = value_type_width(&item.value_type, bit_only);
318 planned.push(ItemPlan {
319 name: to_snake_case(item_name),
320 summary: trim_option(&item.summary),
321 value_type: item.value_type.clone(),
322 offset,
323 width,
324 });
325 offset = checked_next_cursor(offset, width, "方法块")?;
326 }
327 Ok(planned)
328}
329
330fn planned_value_width(value: &PlannedValue) -> u16 {
331 match value {
332 PlannedValue::Dto { items, .. } | PlannedValue::Parameters { items } => items
333 .iter()
334 .map(|item| item.width)
335 .fold(0_u16, |summary, width| summary.saturating_add(width)),
336 PlannedValue::Scalar { item } => item.width,
337 }
338}
339
340fn value_type_width(value_type: &ValueType, bit_only: bool) -> u16 {
341 if bit_only {
342 return 1;
343 }
344 match value_type {
345 ValueType::Boolean => 1,
346 ValueType::Int => 2,
347 ValueType::String | ValueType::Bytes => STRING_REGISTER_WIDTH,
348 }
349}
350
351fn read_return_kind_to_value_type(read_return_kind: &ReadReturnKind) -> ValueType {
352 match read_return_kind {
353 ReadReturnKind::Boolean => ValueType::Boolean,
354 ReadReturnKind::Int => ValueType::Int,
355 ReadReturnKind::String => ValueType::String,
356 ReadReturnKind::Bytes => ValueType::Bytes,
357 ReadReturnKind::Dto => ValueType::Bytes,
358 }
359}
360
361pub fn trim_option(value: &Option<String>) -> Option<String> {
362 value.as_ref().and_then(|item| {
363 let normalized = item.trim();
364 if normalized.is_empty() {
365 None
366 } else {
367 Some(normalized.to_string())
368 }
369 })
370}
371
372pub fn normalize_type_name(raw: &str) -> String {
373 let words = split_words(raw);
374 if words.is_empty() {
375 return "GeneratedType".to_string();
376 }
377 words
378 .into_iter()
379 .map(|word| {
380 let mut chars = word.chars();
381 match chars.next() {
382 Some(first) => {
383 let mut normalized = String::new();
384 normalized.extend(first.to_uppercase());
385 normalized.push_str(chars.as_str());
386 normalized
387 }
388 None => String::new(),
389 }
390 })
391 .collect::<Vec<_>>()
392 .join("")
393}
394
395pub fn to_upper_snake_case(raw: &str) -> String {
396 let words = split_words(raw);
397 if words.is_empty() {
398 return "GENERATED_ITEM".to_string();
399 }
400 words
401 .into_iter()
402 .map(|word| word.to_uppercase())
403 .collect::<Vec<_>>()
404 .join("_")
405}
406
407pub fn to_snake_case(raw: &str) -> String {
408 let words = split_words(raw);
409 if words.is_empty() {
410 return "generated_item".to_string();
411 }
412 let normalized = words
413 .into_iter()
414 .map(|word| word.to_lowercase())
415 .collect::<Vec<_>>()
416 .join("_");
417 if normalized
418 .chars()
419 .next()
420 .map(|character| character.is_ascii_digit())
421 .unwrap_or(false)
422 {
423 format!("generated_{normalized}")
424 } else {
425 normalized
426 }
427}
428
429fn split_words(raw: &str) -> Vec<String> {
430 let mut words = Vec::new();
431 let mut current = String::new();
432 let mut previous_is_lower_or_digit = false;
433
434 for character in raw.chars() {
435 if !character.is_ascii_alphanumeric() {
436 if !current.is_empty() {
437 words.push(current.clone());
438 current.clear();
439 }
440 previous_is_lower_or_digit = false;
441 continue;
442 }
443
444 let is_boundary = character.is_ascii_uppercase() && previous_is_lower_or_digit;
445 if is_boundary && !current.is_empty() {
446 words.push(current.clone());
447 current.clear();
448 }
449
450 current.push(character);
451 previous_is_lower_or_digit = character.is_ascii_lowercase() || character.is_ascii_digit();
452 }
453
454 if !current.is_empty() {
455 words.push(current);
456 }
457
458 words
459}