datafusion_doc/lib.rs
1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements. See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership. The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License. You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied. See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18#![doc(
19 html_logo_url = "https://raw.githubusercontent.com/apache/datafusion/19fe44cf2f30cbdd63d4a4f52c74055163c6cc38/docs/logos/standalone_logo/logo_original.svg",
20 html_favicon_url = "https://raw.githubusercontent.com/apache/datafusion/19fe44cf2f30cbdd63d4a4f52c74055163c6cc38/docs/logos/standalone_logo/logo_original.svg"
21)]
22#![cfg_attr(docsrs, feature(doc_auto_cfg))]
23
24#[allow(rustdoc::broken_intra_doc_links)]
25/// Documentation for use by [`ScalarUDFImpl`](ScalarUDFImpl),
26/// [`AggregateUDFImpl`](AggregateUDFImpl) and [`WindowUDFImpl`](WindowUDFImpl) functions.
27///
28/// See the [`DocumentationBuilder`] to create a new [`Documentation`] struct.
29///
30/// The DataFusion [SQL function documentation] is automatically generated from these structs.
31/// The name of the udf will be pulled from the [`ScalarUDFImpl::name`](ScalarUDFImpl::name),
32/// [`AggregateUDFImpl::name`](AggregateUDFImpl::name) or [`WindowUDFImpl::name`](WindowUDFImpl::name)
33/// function as appropriate.
34///
35/// All strings in the documentation are required to be
36/// in [markdown format](https://www.markdownguide.org/basic-syntax/).
37///
38/// Currently, documentation only supports a single language
39/// thus all text should be in English.
40///
41/// [SQL function documentation]: https://datafusion.apache.org/user-guide/sql/index.html
42#[derive(Debug, Clone)]
43pub struct Documentation {
44 /// The section in the documentation where the UDF will be documented
45 pub doc_section: DocSection,
46 /// The description for the UDF
47 pub description: String,
48 /// A brief example of the syntax. For example "ascii(str)"
49 pub syntax_example: String,
50 /// A sql example for the UDF, usually in the form of a sql prompt
51 /// query and output. It is strongly recommended to provide an
52 /// example for anything but the most basic UDF's
53 pub sql_example: Option<String>,
54 /// Arguments for the UDF which will be displayed in array order.
55 /// Left member of a pair is the argument name, right is a
56 /// description for the argument
57 pub arguments: Option<Vec<(String, String)>>,
58 /// A list of alternative syntax examples for a function
59 pub alternative_syntax: Option<Vec<String>>,
60 /// Related functions if any. Values should match the related
61 /// udf's name exactly. Related udf's must be of the same
62 /// UDF type (scalar, aggregate or window) for proper linking to
63 /// occur
64 pub related_udfs: Option<Vec<String>>,
65}
66
67impl Documentation {
68 /// Returns a new [`DocumentationBuilder`] with no options set.
69 pub fn builder(
70 doc_section: DocSection,
71 description: impl Into<String>,
72 syntax_example: impl Into<String>,
73 ) -> DocumentationBuilder {
74 DocumentationBuilder::new_with_details(doc_section, description, syntax_example)
75 }
76
77 /// Output the `Documentation` struct in form of custom Rust documentation attributes
78 /// It is useful to semi automate during tmigration of UDF documentation
79 /// generation from code based to attribute based and can be safely removed after
80 pub fn to_doc_attribute(&self) -> String {
81 let mut result = String::new();
82
83 result.push_str("#[user_doc(");
84 // Doc Section
85 result.push_str(
86 format!(
87 "\n doc_section({}label = \"{}\"{}),",
88 if !self.doc_section.include {
89 "include = \"false\", "
90 } else {
91 ""
92 },
93 self.doc_section.label,
94 self.doc_section
95 .description
96 .map(|s| format!(", description = \"{}\"", s))
97 .unwrap_or_default(),
98 )
99 .as_ref(),
100 );
101
102 // Description
103 result.push_str(format!("\n description=\"{}\",", self.description).as_ref());
104 // Syntax Example
105 result.push_str(
106 format!("\n syntax_example=\"{}\",", self.syntax_example).as_ref(),
107 );
108 // SQL Example
109 result.push_str(
110 &self
111 .sql_example
112 .clone()
113 .map(|s| format!("\n sql_example = r#\"{}\"#,", s))
114 .unwrap_or_default(),
115 );
116
117 let st_arg_token = " expression to operate on. Can be a constant, column, or function, and any combination of operators.";
118 // Standard Arguments
119 if let Some(args) = self.arguments.clone() {
120 args.iter().for_each(|(name, value)| {
121 if value.contains(st_arg_token) {
122 if name.starts_with("The ") {
123 result.push_str(format!("\n standard_argument(\n name = \"{}\"),", name).as_ref());
124 } else {
125 result.push_str(format!("\n standard_argument(\n name = \"{}\",\n prefix = \"{}\"\n ),", name, value.replace(st_arg_token, "")).as_ref());
126 }
127 }
128 });
129 }
130
131 // Arguments
132 if let Some(args) = self.arguments.clone() {
133 args.iter().for_each(|(name, value)| {
134 if !value.contains(st_arg_token) {
135 result.push_str(format!("\n argument(\n name = \"{}\",\n description = \"{}\"\n ),", name, value).as_ref());
136 }
137 });
138 }
139
140 if let Some(alt_syntax) = self.alternative_syntax.clone() {
141 alt_syntax.iter().for_each(|syntax| {
142 result.push_str(
143 format!("\n alternative_syntax = \"{}\",", syntax).as_ref(),
144 );
145 });
146 }
147
148 // Related UDFs
149 if let Some(related_udf) = self.related_udfs.clone() {
150 related_udf.iter().for_each(|udf| {
151 result
152 .push_str(format!("\n related_udf(name = \"{}\"),", udf).as_ref());
153 });
154 }
155
156 result.push_str("\n)]");
157
158 result
159 }
160}
161
162#[derive(Debug, Clone, PartialEq)]
163pub struct DocSection {
164 /// True to include this doc section in the public
165 /// documentation, false otherwise
166 pub include: bool,
167 /// A display label for the doc section. For example: "Math Expressions"
168 pub label: &'static str,
169 /// An optional description for the doc section
170 pub description: Option<&'static str>,
171}
172
173impl Default for DocSection {
174 /// Returns a "default" Doc section.
175 ///
176 /// This is suitable for user defined functions that do not appear in the
177 /// DataFusion documentation.
178 fn default() -> Self {
179 Self {
180 include: true,
181 label: "Default",
182 description: None,
183 }
184 }
185}
186
187/// A builder for [`Documentation`]'s.
188///
189/// Example:
190///
191/// ```rust
192///
193/// # fn main() {
194/// use datafusion_doc::{DocSection, Documentation};
195/// let doc_section = DocSection {
196/// include: true,
197/// label: "Display Label",
198/// description: None,
199/// };
200///
201/// let documentation = Documentation::builder(doc_section, "Add one to an int32".to_owned(), "add_one(2)".to_owned())
202/// .with_argument("arg_1", "The int32 number to add one to")
203/// .build();
204/// # }
205pub struct DocumentationBuilder {
206 pub doc_section: DocSection,
207 pub description: String,
208 pub syntax_example: String,
209 pub sql_example: Option<String>,
210 pub arguments: Option<Vec<(String, String)>>,
211 pub alternative_syntax: Option<Vec<String>>,
212 pub related_udfs: Option<Vec<String>>,
213}
214
215impl DocumentationBuilder {
216 #[allow(clippy::new_without_default)]
217 #[deprecated(
218 since = "44.0.0",
219 note = "please use `DocumentationBuilder::new_with_details` instead"
220 )]
221 pub fn new() -> Self {
222 Self::new_with_details(DocSection::default(), "<no description>", "<no example>")
223 }
224
225 /// Creates a new [`DocumentationBuilder`] with all required fields
226 pub fn new_with_details(
227 doc_section: DocSection,
228 description: impl Into<String>,
229 syntax_example: impl Into<String>,
230 ) -> Self {
231 Self {
232 doc_section,
233 description: description.into(),
234 syntax_example: syntax_example.into(),
235 sql_example: None,
236 arguments: None,
237 alternative_syntax: None,
238 related_udfs: None,
239 }
240 }
241
242 pub fn with_doc_section(mut self, doc_section: DocSection) -> Self {
243 self.doc_section = doc_section;
244 self
245 }
246
247 pub fn with_description(mut self, description: impl Into<String>) -> Self {
248 self.description = description.into();
249 self
250 }
251
252 pub fn with_syntax_example(mut self, syntax_example: impl Into<String>) -> Self {
253 self.syntax_example = syntax_example.into();
254 self
255 }
256
257 pub fn with_sql_example(mut self, sql_example: impl Into<String>) -> Self {
258 self.sql_example = Some(sql_example.into());
259 self
260 }
261
262 /// Adds documentation for a specific argument to the documentation.
263 ///
264 /// Arguments are displayed in the order they are added.
265 pub fn with_argument(
266 mut self,
267 arg_name: impl Into<String>,
268 arg_description: impl Into<String>,
269 ) -> Self {
270 let mut args = self.arguments.unwrap_or_default();
271 args.push((arg_name.into(), arg_description.into()));
272 self.arguments = Some(args);
273 self
274 }
275
276 /// Add a standard "expression" argument to the documentation
277 ///
278 /// The argument is rendered like below if Some() is passed through:
279 ///
280 /// ```text
281 /// <arg_name>:
282 /// <expression_type> expression to operate on. Can be a constant, column, or function, and any combination of operators.
283 /// ```
284 ///
285 /// The argument is rendered like below if None is passed through:
286 ///
287 /// ```text
288 /// <arg_name>:
289 /// The expression to operate on. Can be a constant, column, or function, and any combination of operators.
290 /// ```
291 pub fn with_standard_argument(
292 self,
293 arg_name: impl Into<String>,
294 expression_type: Option<&str>,
295 ) -> Self {
296 let description = format!(
297 "{} expression to operate on. Can be a constant, column, or function, and any combination of operators.",
298 expression_type.unwrap_or("The")
299 );
300 self.with_argument(arg_name, description)
301 }
302
303 pub fn with_alternative_syntax(mut self, syntax_name: impl Into<String>) -> Self {
304 let mut alternative_syntax_array = self.alternative_syntax.unwrap_or_default();
305 alternative_syntax_array.push(syntax_name.into());
306 self.alternative_syntax = Some(alternative_syntax_array);
307 self
308 }
309
310 pub fn with_related_udf(mut self, related_udf: impl Into<String>) -> Self {
311 let mut related = self.related_udfs.unwrap_or_default();
312 related.push(related_udf.into());
313 self.related_udfs = Some(related);
314 self
315 }
316
317 /// Build the documentation from provided components
318 ///
319 /// Panics if `doc_section`, `description` or `syntax_example` is not set
320 pub fn build(self) -> Documentation {
321 let Self {
322 doc_section,
323 description,
324 syntax_example,
325 sql_example,
326 arguments,
327 alternative_syntax,
328 related_udfs,
329 } = self;
330
331 Documentation {
332 doc_section,
333 description,
334 syntax_example,
335 sql_example,
336 arguments,
337 alternative_syntax,
338 related_udfs,
339 }
340 }
341}