openpine_vm/script_info/
input.rs

1use std::fmt::Display;
2
3use gc_arena::Mutation;
4use num_enum::FromPrimitive;
5use openpine_compiler::{
6    instructions::{Color, TypeDescriptor, tag},
7    program::Program,
8};
9use openpine_macros::Enum;
10use serde::{Deserialize, Serialize, Serializer};
11use serde_value::Value;
12
13use crate::{
14    TimeFrame,
15    objects::{PineRef, string::PineString},
16    quote_types::Candlestick,
17    raw_value::{RawValue, ToRawValue},
18    visuals::PlotDisplay,
19};
20
21/// A error type for serialization and deserialization of values.
22#[derive(Debug, thiserror::Error, Clone, PartialEq)]
23#[error("{0}")]
24pub(crate) struct ValueSerdeError(String);
25
26impl ValueSerdeError {
27    pub(crate) fn mismatched_type(expected: impl Display, actual: impl Display) -> Self {
28        Self(format!(
29            "mismatched type expected `{expected}`, actual `{actual}`"
30        ))
31    }
32
33    pub(crate) fn not_supported(ty: impl Display) -> Self {
34        Self(format!("`{ty}` is not supported"))
35    }
36}
37
38fn convert_serde_value<'a>(
39    mc: &'a Mutation<'a>,
40    program: &Program,
41    vt: &TypeDescriptor,
42    value: &Value,
43) -> Result<RawValue, ValueSerdeError> {
44    match value {
45        Value::Bool(b) => {
46            if vt.tag() != tag::BOOLEAN {
47                return Err(ValueSerdeError::mismatched_type(
48                    vt.display(program),
49                    "bool",
50                ));
51            }
52            Ok(b.to_raw_value())
53        }
54        Value::I8(v) => convert_serde_value(mc, program, vt, &Value::I64(*v as i64)),
55        Value::I16(v) => convert_serde_value(mc, program, vt, &Value::I64(*v as i64)),
56        Value::I32(v) => convert_serde_value(mc, program, vt, &Value::I64(*v as i64)),
57        Value::I64(v) => {
58            if !matches!(vt.tag(), tag::INTEGER | tag::FLOAT | tag::ENUM) {
59                return Err(ValueSerdeError::mismatched_type(vt.display(program), "int"));
60            }
61            Ok(v.to_raw_value())
62        }
63        Value::U8(v) => convert_serde_value(mc, program, vt, &Value::I64(*v as i64)),
64        Value::U16(v) => convert_serde_value(mc, program, vt, &Value::I64(*v as i64)),
65        Value::U32(v) => convert_serde_value(mc, program, vt, &Value::I64(*v as i64)),
66        Value::U64(v) => {
67            if !matches!(vt.tag(), tag::INTEGER | tag::FLOAT) {
68                return Err(ValueSerdeError::mismatched_type(vt.display(program), "int"));
69            }
70            Ok((*v as i64).to_raw_value())
71        }
72        Value::F32(v) => convert_serde_value(mc, program, vt, &Value::F64(*v as f64)),
73        Value::F64(v) => {
74            if vt.tag() != tag::FLOAT {
75                return Err(ValueSerdeError::mismatched_type(
76                    vt.display(program),
77                    "float",
78                ));
79            }
80            Ok(v.to_raw_value())
81        }
82        Value::String(s) => {
83            if vt.tag() != tag::STRING {
84                return Err(ValueSerdeError::mismatched_type(
85                    vt.display(program),
86                    "string",
87                ));
88            }
89            Ok(PineRef::new(mc, PineString::new(s)).to_raw_value())
90        }
91        Value::Option(None) | Value::Unit => Ok(RawValue::NA),
92        Value::Option(Some(inner)) | Value::Newtype(inner) => {
93            convert_serde_value(mc, program, vt, inner)
94        }
95        other => Err(ValueSerdeError::not_supported(format!("{other:?}"))),
96    }
97}
98
99/// Integer user input metadata.
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct InputInt {
102    /// Input identifier.
103    pub id: i64,
104    /// Default value.
105    pub default_value: i64,
106    /// Optional user-facing title.
107    pub title: Option<String>,
108    /// Optional tooltip text.
109    pub tooltip: Option<String>,
110    /// Optional inline group key.
111    pub inline: Option<String>,
112    /// Optional UI group name.
113    pub group: Option<String>,
114    /// Display mode.
115    pub display: PlotDisplay,
116    /// Whether the input is active.
117    pub active: bool,
118    /// Optional discrete choices.
119    pub options: Vec<i64>,
120    /// Minimum allowed value.
121    pub min_value: Option<i64>,
122    /// Maximum allowed value.
123    pub max_value: Option<i64>,
124    /// Step size.
125    pub step: Option<i64>,
126    /// Whether to require confirmation on change.
127    pub confirm: bool,
128}
129
130/// Floating-point user input metadata.
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct InputFloat {
133    /// Input identifier.
134    pub id: i64,
135    /// Default value.
136    pub default_value: f64,
137    /// Optional user-facing title.
138    pub title: Option<String>,
139    /// Optional tooltip text.
140    pub tooltip: Option<String>,
141    /// Optional inline group key.
142    pub inline: Option<String>,
143    /// Optional UI group name.
144    pub group: Option<String>,
145    /// Display mode.
146    pub display: PlotDisplay,
147    /// Whether the input is active.
148    pub active: bool,
149    /// Optional discrete choices.
150    pub options: Option<Vec<f64>>,
151    /// Minimum allowed value.
152    pub min_value: Option<f64>,
153    /// Maximum allowed value.
154    pub max_value: Option<f64>,
155    /// Step size.
156    pub step: Option<f64>,
157    /// Whether to require confirmation on change.
158    pub confirm: bool,
159}
160
161/// Boolean user input metadata.
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct InputBool {
164    /// Input identifier.
165    pub id: i64,
166    /// Default value.
167    pub default_value: bool,
168    /// Optional user-facing title.
169    pub title: Option<String>,
170    /// Optional tooltip text.
171    pub tooltip: Option<String>,
172    /// Optional inline group key.
173    pub inline: Option<String>,
174    /// Optional UI group name.
175    pub group: Option<String>,
176    /// Display mode.
177    pub display: PlotDisplay,
178    /// Whether the input is active.
179    pub active: bool,
180    /// Whether to require confirmation on change.
181    pub confirm: bool,
182}
183
184/// Color user input metadata.
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct InputColor {
187    /// Input identifier.
188    pub id: i64,
189    /// Default value.
190    pub default_value: Color,
191    /// Optional user-facing title.
192    pub title: Option<String>,
193    /// Optional tooltip text.
194    pub tooltip: Option<String>,
195    /// Optional inline group key.
196    pub inline: Option<String>,
197    /// Optional UI group name.
198    pub group: Option<String>,
199    /// Display mode.
200    pub display: PlotDisplay,
201    /// Whether the input is active.
202    pub active: bool,
203    /// Whether to require confirmation on change.
204    pub confirm: bool,
205}
206
207/// String user input metadata.
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct InputString {
210    /// Input identifier.
211    pub id: i64,
212    /// Default value.
213    pub default_value: String,
214    /// Optional user-facing title.
215    pub title: Option<String>,
216    /// Optional tooltip text.
217    pub tooltip: Option<String>,
218    /// Optional inline group key.
219    pub inline: Option<String>,
220    /// Optional UI group name.
221    pub group: Option<String>,
222    /// Display mode.
223    pub display: PlotDisplay,
224    /// Whether the input is active.
225    pub active: bool,
226    /// Optional discrete choices.
227    pub options: Vec<String>,
228    /// Whether to require confirmation on change.
229    pub confirm: bool,
230}
231
232/// Price (float) user input metadata.
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct InputPrice {
235    /// Input identifier.
236    pub id: i64,
237    /// Default value.
238    pub default_value: f64,
239    /// Optional user-facing title.
240    pub title: Option<String>,
241    /// Optional tooltip text.
242    pub tooltip: Option<String>,
243    /// Optional inline group key.
244    pub inline: Option<String>,
245    /// Optional UI group name.
246    pub group: Option<String>,
247    /// Display mode.
248    pub display: PlotDisplay,
249    /// Whether the input is active.
250    pub active: bool,
251    /// Whether to require confirmation on change.
252    pub confirm: bool,
253}
254
255/// Symbol ticker user input metadata.
256#[derive(Debug, Clone, Serialize, Deserialize)]
257pub struct InputSymbol {
258    /// Input identifier.
259    pub id: i64,
260    /// Default value.
261    pub default_value: String,
262    /// Optional user-facing title.
263    pub title: Option<String>,
264    /// Optional tooltip text.
265    pub tooltip: Option<String>,
266    /// Optional inline group key.
267    pub inline: Option<String>,
268    /// Optional UI group name.
269    pub group: Option<String>,
270    /// Display mode.
271    pub display: PlotDisplay,
272    /// Whether the input is active.
273    pub active: bool,
274    /// Whether to require confirmation on change.
275    pub confirm: bool,
276}
277
278/// Default value for a timeframe input.
279///
280/// In Pine Script, `input.timeframe("")` means "use the chart's current
281/// timeframe". This enum preserves that intent so that [`InputTimeFrame`] can
282/// be inspected without binding to a concrete timeframe too early.
283///
284/// Serializes as a flat string: `""` for [`Chart`](Self::Chart), or the
285/// TradingView timeframe string (e.g. `"D"`, `"5"`) for
286/// [`Custom`](Self::Custom).
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
288pub enum TimeFrameDefaultValue {
289    /// Use the chart's current timeframe (Pine Script `""`).
290    Chart,
291    /// A specific, user-chosen timeframe.
292    Custom(TimeFrame),
293}
294
295impl Serialize for TimeFrameDefaultValue {
296    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
297    where
298        S: Serializer,
299    {
300        match self {
301            Self::Chart => serializer.serialize_str(""),
302            Self::Custom(tf) => serializer.collect_str(tf),
303        }
304    }
305}
306
307impl<'de> Deserialize<'de> for TimeFrameDefaultValue {
308    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
309    where
310        D: serde::Deserializer<'de>,
311    {
312        let s = String::deserialize(deserializer)?;
313        if s.is_empty() {
314            Ok(Self::Chart)
315        } else {
316            s.parse::<TimeFrame>()
317                .map(Self::Custom)
318                .map_err(serde::de::Error::custom)
319        }
320    }
321}
322
323/// Timeframe user input metadata.
324#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct InputTimeFrame {
326    /// Input identifier.
327    pub id: i64,
328    /// Default value.
329    pub default_value: TimeFrameDefaultValue,
330    /// Optional user-facing title.
331    pub title: Option<String>,
332    /// Optional tooltip text.
333    pub tooltip: Option<String>,
334    /// Optional inline group key.
335    pub inline: Option<String>,
336    /// Optional UI group name.
337    pub group: Option<String>,
338    /// Display mode.
339    pub display: PlotDisplay,
340    /// Whether the input is active.
341    pub active: bool,
342    /// Optional discrete choices.
343    pub options: Vec<TimeFrame>,
344    /// Whether to require confirmation on change.
345    pub confirm: bool,
346}
347
348/// Built-in OHLC-derived source types.
349#[derive(Debug, Copy, Clone, PartialEq, Eq, FromPrimitive, Enum)]
350#[openpine(rename_all = "lowercase")]
351#[repr(i64)]
352pub enum SourceType {
353    /// Open price.
354    #[default]
355    Open = 0,
356    /// High price.
357    High = 1,
358    /// Low price.
359    Low = 2,
360    /// Close price.
361    Close = 3,
362    /// Average of high and low.
363    Hl2 = 4,
364    /// Average of high, low, and close.
365    Hlc3 = 5,
366    /// Average of open, high, low, and close.
367    Ohlc4 = 6,
368    /// Average of high, low, and close (close weighted twice).
369    Hlcc4 = 7,
370}
371
372impl Serialize for SourceType {
373    #[inline]
374    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
375    where
376        S: Serializer,
377    {
378        (*self as i64).serialize(serializer)
379    }
380}
381
382impl<'de> Deserialize<'de> for SourceType {
383    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
384    where
385        D: serde::Deserializer<'de>,
386    {
387        let value = i64::deserialize(deserializer)?;
388        Ok(Self::from(value))
389    }
390}
391
392impl SourceType {
393    /// Returns the display name used by UI.
394    #[inline]
395    pub fn display_name(&self) -> &'static str {
396        match self {
397            SourceType::Open => "Open",
398            SourceType::High => "High",
399            SourceType::Low => "Low",
400            SourceType::Close => "Close",
401            SourceType::Hl2 => "(H + L) / 2",
402            SourceType::Hlc3 => "(H + L + C) / 3",
403            SourceType::Ohlc4 => "(O + H + L + C) / 4",
404            SourceType::Hlcc4 => "(H + L + C + C) / 4",
405        }
406    }
407
408    pub(crate) fn value(&self, candlestick: &Candlestick) -> f64 {
409        match self {
410            SourceType::Open => candlestick.open,
411            SourceType::High => candlestick.high,
412            SourceType::Low => candlestick.low,
413            SourceType::Close => candlestick.close,
414            SourceType::Hl2 => candlestick.hl2(),
415            SourceType::Hlc3 => candlestick.hlc3(),
416            SourceType::Ohlc4 => candlestick.ohlc4(),
417            SourceType::Hlcc4 => candlestick.hlcc4(),
418        }
419    }
420}
421
422/// Source (OHLC-derived) user input metadata.
423#[derive(Debug, Clone, Serialize, Deserialize)]
424pub struct InputSource {
425    /// Input identifier.
426    pub id: i64,
427    /// Default source selection.
428    pub default_value: SourceType,
429    /// Optional user-facing title.
430    pub title: Option<String>,
431    /// Optional tooltip text.
432    pub tooltip: Option<String>,
433    /// Optional inline group key.
434    pub inline: Option<String>,
435    /// Optional UI group name.
436    pub group: Option<String>,
437    /// Display mode.
438    pub display: PlotDisplay,
439    /// Whether the input is active.
440    pub active: bool,
441    /// Whether to require confirmation on change.
442    pub confirm: bool,
443}
444
445/// A single option for an enum input.
446#[derive(Debug, Clone, Serialize, Deserialize)]
447pub struct InputEnumOption {
448    /// Runtime integer value of the option.
449    pub value: i64,
450    /// User-facing label.
451    pub title: String,
452}
453
454/// Enum user input metadata.
455#[derive(Debug, Clone, Serialize, Deserialize)]
456pub struct InputEnum {
457    /// Input identifier.
458    pub id: i64,
459    /// Enum type id in the compiled program.
460    pub enum_typeid: usize,
461    /// Default selected enum value.
462    pub default_value: i64,
463    /// Optional user-facing title.
464    pub title: Option<String>,
465    /// Optional tooltip text.
466    pub tooltip: Option<String>,
467    /// Optional inline group key.
468    pub inline: Option<String>,
469    /// Optional UI group name.
470    pub group: Option<String>,
471    /// Display mode.
472    pub display: PlotDisplay,
473    /// Whether the input is active.
474    pub active: bool,
475    /// Available options.
476    pub options: Vec<InputEnumOption>,
477    /// Whether to require confirmation on change.
478    pub confirm: bool,
479}
480
481/// Session string user input metadata.
482#[derive(Debug, Clone, Serialize, Deserialize)]
483pub struct InputSession {
484    /// Input identifier.
485    pub id: i64,
486    /// Default value.
487    pub default_value: String,
488    /// Optional user-facing title.
489    pub title: Option<String>,
490    /// Optional tooltip text.
491    pub tooltip: Option<String>,
492    /// Optional inline group key.
493    pub inline: Option<String>,
494    /// Optional UI group name.
495    pub group: Option<String>,
496    /// Display mode.
497    pub display: PlotDisplay,
498    /// Whether the input is active.
499    pub active: bool,
500    /// Optional discrete choices.
501    pub options: Vec<String>,
502    /// Whether to require confirmation on change.
503    pub confirm: bool,
504}
505
506/// Time (epoch milliseconds) user input metadata.
507#[derive(Debug, Clone, Serialize, Deserialize)]
508pub struct InputTime {
509    /// Input identifier.
510    pub id: i64,
511    /// Default value.
512    pub default_value: i64,
513    /// Optional user-facing title.
514    pub title: Option<String>,
515    /// Optional tooltip text.
516    pub tooltip: Option<String>,
517    /// Optional inline group key.
518    pub inline: Option<String>,
519    /// Optional UI group name.
520    pub group: Option<String>,
521    /// Display mode.
522    pub display: PlotDisplay,
523    /// Whether the input is active.
524    pub active: bool,
525    /// Whether to require confirmation on change.
526    pub confirm: bool,
527}
528
529/// Multi-line text user input metadata.
530#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct InputTextArea {
532    /// Input identifier.
533    pub id: i64,
534    /// Default value.
535    pub default_value: String,
536    /// Optional user-facing title.
537    pub title: Option<String>,
538    /// Optional tooltip text.
539    pub tooltip: Option<String>,
540    /// Optional UI group name.
541    pub group: Option<String>,
542    /// Display mode.
543    pub display: PlotDisplay,
544    /// Whether the input is active.
545    pub active: bool,
546    /// Whether to require confirmation on change.
547    pub confirm: bool,
548}
549
550/// A typed user input.
551#[derive(Debug, Clone, Serialize, Deserialize)]
552pub enum Input {
553    /// Integer input.
554    Int(InputInt),
555    /// Float input.
556    Float(InputFloat),
557    /// Boolean input.
558    Bool(InputBool),
559    /// Color input.
560    Color(InputColor),
561    /// String input.
562    String(InputString),
563    /// Price input.
564    Price(InputPrice),
565    /// Symbol input.
566    Symbol(InputSymbol),
567    /// Timeframe input.
568    TimeFrame(InputTimeFrame),
569    /// Source input.
570    Source(InputSource),
571    /// Enum input.
572    Enum(InputEnum),
573    /// Session input.
574    Session(InputSession),
575    /// Time input.
576    Time(InputTime),
577    /// Text area input.
578    TextArea(InputTextArea),
579}
580
581impl Input {
582    pub(crate) fn value_type(&self) -> TypeDescriptor {
583        match self {
584            Input::Int(_) | Input::Time(_) => TypeDescriptor::INTEGER,
585            Input::Float(_) | Input::Price(_) => TypeDescriptor::FLOAT,
586            Input::Bool(_) => TypeDescriptor::BOOLEAN,
587            Input::Color(_) => TypeDescriptor::COLOR,
588            Input::String(_)
589            | Input::Symbol(_)
590            | Input::TimeFrame(_)
591            | Input::Session(_)
592            | Input::TextArea(_) => TypeDescriptor::STRING,
593            Input::Source(_) => TypeDescriptor::FLOAT,
594            Input::Enum(input) => TypeDescriptor::enum_type(input.enum_typeid),
595        }
596    }
597
598    pub(crate) fn serialize_value<'a>(
599        &self,
600        mc: &'a Mutation<'a>,
601        program: &Program,
602        value: &serde_value::Value,
603    ) -> Result<RawValue, ValueSerdeError> {
604        let value_type = self.value_type();
605        convert_serde_value(mc, program, &value_type, value)
606    }
607
608    pub(crate) fn serialize_default_value<'a>(
609        &self,
610        mc: &'a Mutation<'a>,
611        program: &Program,
612    ) -> Result<RawValue, ValueSerdeError> {
613        let value_type = self.value_type();
614        let sv = match self {
615            Input::Int(input) => serde_value::to_value(input.default_value),
616            Input::Float(input) => serde_value::to_value(input.default_value),
617            Input::Bool(input) => serde_value::to_value(input.default_value),
618            Input::Color(input) => serde_value::to_value(input.default_value),
619            Input::String(input) => serde_value::to_value(&input.default_value),
620            Input::Price(input) => serde_value::to_value(input.default_value),
621            Input::Symbol(input) => serde_value::to_value(&input.default_value),
622            Input::TimeFrame(input) => serde_value::to_value(input.default_value),
623            Input::Source(input) => serde_value::to_value(input.default_value as i64),
624            Input::Enum(input) => serde_value::to_value(input.default_value),
625            Input::Session(input) => serde_value::to_value(&input.default_value),
626            Input::Time(input) => serde_value::to_value(input.default_value),
627            Input::TextArea(input) => serde_value::to_value(&input.default_value),
628        }
629        .unwrap_or(serde_value::Value::Unit);
630        convert_serde_value(mc, program, &value_type, &sv)
631    }
632}