Skip to main content

fiftyone_javascript_builder/
builder.rs

1/* *********************************************************************
2 * This Original Work is copyright of 51 Degrees Mobile Experts Limited.
3 * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
4 * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
5 *
6 * This Original Work is licensed under the European Union Public Licence
7 * (EUPL) v.1.2 and is subject to its terms as set out below.
8 *
9 * If a copy of the EUPL was not distributed with this file, You can obtain
10 * one at https://opensource.org/licenses/EUPL-1.2.
11 *
12 * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
13 * amended by the European Commission) shall be deemed incompatible for
14 * the purposes of the Work and the provisions of the compatibility
15 * clause in Article 5 of the EUPL shall not apply.
16 *
17 * If using the Work as, or as part of, a network application, by
18 * including the attribution notice(s) required under Article 5 of the EUPL
19 * in the end user terms of the application under an appropriate heading,
20 * such notice(s) shall fulfill the requirements of that article.
21 * ********************************************************************* */
22
23//! The builder for [`JavaScriptBuilderElement`].
24
25use fiftyone_pipeline_core::constants::DEFAULT_JSON_ENDPOINT;
26use fiftyone_pipeline_core::{Error, Result};
27
28use crate::constants::{
29    BUILDER_DEFAULT_ENABLE_COOKIES, BUILDER_DEFAULT_HOST, BUILDER_DEFAULT_MINIFY,
30    BUILDER_DEFAULT_OBJECT_NAME, BUILDER_DEFAULT_PROTOCOL,
31};
32use crate::element::JavaScriptBuilderElement;
33
34/// Configures and constructs a [`JavaScriptBuilderElement`].
35///
36/// The defaults are minification
37/// on, cookies enabled, object name `fod`, the default JSON endpoint, and an
38/// empty host and protocol so the values from request evidence are used.
39///
40/// # Example
41///
42/// ```
43/// use fiftyone_javascript_builder::JavaScriptBuilderElement;
44///
45/// let element = JavaScriptBuilderElement::builder()
46///     .set_object_name("myObj").unwrap()
47///     .set_protocol("https").unwrap()
48///     .set_minify(false)
49///     .build();
50/// ```
51#[derive(Debug, Clone)]
52pub struct JavaScriptBuilderElementBuilder {
53    host: String,
54    endpoint: String,
55    protocol: String,
56    object_name: String,
57    enable_cookies: bool,
58    minify: bool,
59}
60
61impl JavaScriptBuilderElementBuilder {
62    /// Start a builder pre-populated with the defaults.
63    pub fn new() -> Self {
64        JavaScriptBuilderElementBuilder {
65            host: BUILDER_DEFAULT_HOST.to_owned(),
66            endpoint: DEFAULT_JSON_ENDPOINT.to_owned(),
67            protocol: BUILDER_DEFAULT_PROTOCOL.to_owned(),
68            object_name: BUILDER_DEFAULT_OBJECT_NAME.to_owned(),
69            enable_cookies: BUILDER_DEFAULT_ENABLE_COOKIES,
70            minify: BUILDER_DEFAULT_MINIFY,
71        }
72    }
73
74    /// Set whether client-side processing stores results in cookies.
75    ///
76    /// This can also be set per request through the
77    /// `query.fod-js-enable-cookies` evidence key.
78    pub fn set_enable_cookies(mut self, enable_cookies: bool) -> Self {
79        self.enable_cookies = enable_cookies;
80        self
81    }
82
83    /// Set the host the client JavaScript should query for updates. By default
84    /// the host from the request evidence is used.
85    pub fn set_host(mut self, host: impl Into<String>) -> Self {
86        self.host = host.into();
87        self
88    }
89
90    /// Set the endpoint queried on the host, for example `/api/v4/json`.
91    pub fn set_endpoint(mut self, endpoint: impl Into<String>) -> Self {
92        self.endpoint = endpoint.into();
93        self
94    }
95
96    /// Set the protocol the client JavaScript uses when querying for updates.
97    ///
98    /// Only `http` or `https` (case-insensitive) are accepted. Any other value
99    /// is a configuration error.
100    pub fn set_protocol(mut self, protocol: impl Into<String>) -> Result<Self> {
101        let protocol = protocol.into();
102        if protocol.eq_ignore_ascii_case("http") || protocol.eq_ignore_ascii_case("https") {
103            self.protocol = protocol;
104            Ok(self)
105        } else {
106            Err(Error::configuration(format!(
107                "Invalid protocol in configuration ({protocol}), must be 'http' or 'https'"
108            )))
109        }
110    }
111
112    /// Set the default name of the object instantiated by the client
113    /// JavaScript.
114    ///
115    /// The name must be a valid JavaScript identifier (it must match
116    /// `[a-zA-Z_$][0-9a-zA-Z_$]*` in full). An invalid name is a configuration
117    /// error.
118    pub fn set_object_name(mut self, object_name: impl Into<String>) -> Result<Self> {
119        let object_name = object_name.into();
120        if is_valid_object_name(&object_name) {
121            self.object_name = object_name;
122            Ok(self)
123        } else {
124            Err(Error::configuration(format!(
125                "The JavaScript object name '{object_name}' is not valid. It must \
126                 be a valid JavaScript identifier."
127            )))
128        }
129    }
130
131    /// Enable or disable minification of the generated JavaScript.
132    ///
133    /// Minification only takes effect when the crate's `minify` feature is
134    /// enabled (it is on by default). With the feature disabled this flag is
135    /// retained but has no effect.
136    pub fn set_minify(mut self, minify: bool) -> Self {
137        self.minify = minify;
138        self
139    }
140
141    /// Build the configured [`JavaScriptBuilderElement`].
142    pub fn build(self) -> JavaScriptBuilderElement {
143        JavaScriptBuilderElement::from_parts(
144            self.host,
145            self.endpoint,
146            self.protocol,
147            self.object_name,
148            self.enable_cookies,
149            self.minify,
150        )
151    }
152}
153
154impl Default for JavaScriptBuilderElementBuilder {
155    fn default() -> Self {
156        JavaScriptBuilderElementBuilder::new()
157    }
158}
159
160/// True if the string is a valid JavaScript identifier per the
161/// `[a-zA-Z_$][0-9a-zA-Z_$]*` rule.
162///
163/// The first character must be a letter, underscore or dollar sign; the rest may
164/// also be digits. An empty string is invalid.
165fn is_valid_object_name(name: &str) -> bool {
166    let mut chars = name.chars();
167    match chars.next() {
168        Some(first) if is_identifier_start(first) => {}
169        _ => return false,
170    }
171    chars.all(is_identifier_part)
172}
173
174/// True if the character may start a JavaScript identifier (letter, `_` or `$`).
175fn is_identifier_start(c: char) -> bool {
176    c.is_ascii_alphabetic() || c == '_' || c == '$'
177}
178
179/// True if the character may continue a JavaScript identifier (an identifier
180/// start character or an ASCII digit).
181fn is_identifier_part(c: char) -> bool {
182    is_identifier_start(c) || c.is_ascii_digit()
183}