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}