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 = \"{name}\",\n description = \"{value}\"\n ),").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.push_str(format!("\n related_udf(name = \"{udf}\"),").as_ref());
152 });
153 }
154
155 result.push_str("\n)]");
156
157 result
158 }
159}
160
161#[derive(Debug, Clone, PartialEq)]
162pub struct DocSection {
163 /// True to include this doc section in the public
164 /// documentation, false otherwise
165 pub include: bool,
166 /// A display label for the doc section. For example: "Math Expressions"
167 pub label: &'static str,
168 /// An optional description for the doc section
169 pub description: Option<&'static str>,
170}
171
172impl Default for DocSection {
173 /// Returns a "default" Doc section.
174 ///
175 /// This is suitable for user defined functions that do not appear in the
176 /// DataFusion documentation.
177 fn default() -> Self {
178 Self {
179 include: true,
180 label: "Default",
181 description: None,
182 }
183 }
184}
185
186/// A builder for [`Documentation`]'s.
187///
188/// Example:
189///
190/// ```rust
191///
192/// # fn main() {
193/// use datafusion_doc::{DocSection, Documentation};
194/// let doc_section = DocSection {
195/// include: true,
196/// label: "Display Label",
197/// description: None,
198/// };
199///
200/// let documentation = Documentation::builder(doc_section, "Add one to an int32".to_owned(), "add_one(2)".to_owned())
201/// .with_argument("arg_1", "The int32 number to add one to")
202/// .build();
203/// # }
204pub struct DocumentationBuilder {
205 pub doc_section: DocSection,
206 pub description: String,
207 pub syntax_example: String,
208 pub sql_example: Option<String>,
209 pub arguments: Option<Vec<(String, String)>>,
210 pub alternative_syntax: Option<Vec<String>>,
211 pub related_udfs: Option<Vec<String>>,
212}
213
214impl DocumentationBuilder {
215 #[allow(clippy::new_without_default)]
216 #[deprecated(
217 since = "44.0.0",
218 note = "please use `DocumentationBuilder::new_with_details` instead"
219 )]
220 pub fn new() -> Self {
221 Self::new_with_details(DocSection::default(), "<no description>", "<no example>")
222 }
223
224 /// Creates a new [`DocumentationBuilder`] with all required fields
225 pub fn new_with_details(
226 doc_section: DocSection,
227 description: impl Into<String>,
228 syntax_example: impl Into<String>,
229 ) -> Self {
230 Self {
231 doc_section,
232 description: description.into(),
233 syntax_example: syntax_example.into(),
234 sql_example: None,
235 arguments: None,
236 alternative_syntax: None,
237 related_udfs: None,
238 }
239 }
240
241 pub fn with_doc_section(mut self, doc_section: DocSection) -> Self {
242 self.doc_section = doc_section;
243 self
244 }
245
246 pub fn with_description(mut self, description: impl Into<String>) -> Self {
247 self.description = description.into();
248 self
249 }
250
251 pub fn with_syntax_example(mut self, syntax_example: impl Into<String>) -> Self {
252 self.syntax_example = syntax_example.into();
253 self
254 }
255
256 pub fn with_sql_example(mut self, sql_example: impl Into<String>) -> Self {
257 self.sql_example = Some(sql_example.into());
258 self
259 }
260
261 /// Adds documentation for a specific argument to the documentation.
262 ///
263 /// Arguments are displayed in the order they are added.
264 pub fn with_argument(
265 mut self,
266 arg_name: impl Into<String>,
267 arg_description: impl Into<String>,
268 ) -> Self {
269 let mut args = self.arguments.unwrap_or_default();
270 args.push((arg_name.into(), arg_description.into()));
271 self.arguments = Some(args);
272 self
273 }
274
275 /// Add a standard "expression" argument to the documentation
276 ///
277 /// The argument is rendered like below if Some() is passed through:
278 ///
279 /// ```text
280 /// <arg_name>:
281 /// <expression_type> expression to operate on. Can be a constant, column, or function, and any combination of operators.
282 /// ```
283 ///
284 /// The argument is rendered like below if None is passed through:
285 ///
286 /// ```text
287 /// <arg_name>:
288 /// The expression to operate on. Can be a constant, column, or function, and any combination of operators.
289 /// ```
290 pub fn with_standard_argument(
291 self,
292 arg_name: impl Into<String>,
293 expression_type: Option<&str>,
294 ) -> Self {
295 let description = format!(
296 "{} expression to operate on. Can be a constant, column, or function, and any combination of operators.",
297 expression_type.unwrap_or("The")
298 );
299 self.with_argument(arg_name, description)
300 }
301
302 pub fn with_alternative_syntax(mut self, syntax_name: impl Into<String>) -> Self {
303 let mut alternative_syntax_array = self.alternative_syntax.unwrap_or_default();
304 alternative_syntax_array.push(syntax_name.into());
305 self.alternative_syntax = Some(alternative_syntax_array);
306 self
307 }
308
309 pub fn with_related_udf(mut self, related_udf: impl Into<String>) -> Self {
310 let mut related = self.related_udfs.unwrap_or_default();
311 related.push(related_udf.into());
312 self.related_udfs = Some(related);
313 self
314 }
315
316 /// Build the documentation from provided components
317 ///
318 /// Panics if `doc_section`, `description` or `syntax_example` is not set
319 pub fn build(self) -> Documentation {
320 let Self {
321 doc_section,
322 description,
323 syntax_example,
324 sql_example,
325 arguments,
326 alternative_syntax,
327 related_udfs,
328 } = self;
329
330 Documentation {
331 doc_section,
332 description,
333 syntax_example,
334 sql_example,
335 arguments,
336 alternative_syntax,
337 related_udfs,
338 }
339 }
340}