Move parameter parsing to associated methods

This commit is contained in:
Joel Wachsler 2022-07-19 10:37:46 +00:00
parent 8db9faf5f0
commit a72d3e5fa7
7 changed files with 694 additions and 97 deletions

View File

@ -5835,18 +5835,15 @@
ReturnTypeParameter {
name: "results",
description: "Array of result objects- see table below",
return_type: ObjectArray(
TypeWithRef {
type_info: TypeInfo {
name: "results",
is_optional: false,
is_list: false,
description: Some(
"Array of result objects- see table below",
),
type_description: None,
},
ref_type: "result",
return_type: StringArray(
TypeInfo {
name: "results",
is_optional: false,
is_list: false,
description: Some(
"Array of result objects- see table below",
),
type_description: None,
},
),
},

View File

@ -0,0 +1,100 @@
ApiMethod {
name: "results",
description: Some(
"The response is a JSON object with the following fields\n\n\n\n\nExample:\n\n```JSON\n{\n \"results\": [\n {\n \"descrLink\": \"http://www.legittorrents.info/index.php?page=torrent-details&id=8d5f512e1acb687029b8d7cc6c5a84dce51d7a41\",\n \"fileName\": \"Ubuntu-10.04-32bit-NeTV.ova\",\n \"fileSize\": -1,\n \"fileUrl\": \"http://www.legittorrents.info/download.php?id=8d5f512e1acb687029b8d7cc6c5a84dce51d7a41&f=Ubuntu-10.04-32bit-NeTV.ova.torrent\",\n \"nbLeechers\": 1,\n \"nbSeeders\": 0,\n \"siteUrl\": \"http://www.legittorrents.info\"\n },\n {\n \"descrLink\": \"http://www.legittorrents.info/index.php?page=torrent-details&id=d5179f53e105dc2c2401bcfaa0c2c4936a6aa475\",\n \"fileName\": \"mangOH-Legato-17_06-Ubuntu-16_04.ova\",\n \"fileSize\": -1,\n \"fileUrl\": \"http://www.legittorrents.info/download.php?id=d5179f53e105dc2c2401bcfaa0c2c4936a6aa475&f=mangOH-Legato-17_06-Ubuntu-16_04.ova.torrent\",\n \"nbLeechers\": 0,\n \"nbSeeders\": 59,\n \"siteUrl\": \"http://www.legittorrents.info\"\n }\n ],\n \"status\": \"Running\",\n \"total\": 2\n}\n```",
),
parameters: Some(
ApiParameters {
mandatory: [
Number(
TypeInfo {
name: "id",
is_optional: false,
is_list: false,
description: Some(
"ID of the search job",
),
type_description: None,
},
),
],
optional: [
Number(
TypeInfo {
name: "limit",
is_optional: true,
is_list: false,
description: Some(
"max number of results to return. 0 or negative means no limit",
),
type_description: None,
},
),
Number(
TypeInfo {
name: "offset",
is_optional: true,
is_list: false,
description: Some(
"result to start at. A negative number means count backwards (e.g. -2 returns the 2 most recent results)",
),
type_description: None,
},
),
],
},
),
return_type: Some(
ReturnType {
is_list: false,
parameters: [
ReturnTypeParameter {
name: "results",
description: "Array of result objects- see table below",
return_type: StringArray(
TypeInfo {
name: "results",
is_optional: false,
is_list: false,
description: Some(
"Array of result objects- see table below",
),
type_description: None,
},
),
},
ReturnTypeParameter {
name: "status",
description: "Current status of the search job (either Running or Stopped)",
return_type: String(
TypeInfo {
name: "status",
is_optional: false,
is_list: false,
description: Some(
"Current status of the search job (either Running or Stopped)",
),
type_description: None,
},
),
},
ReturnTypeParameter {
name: "total",
description: "Total number of results. If the status is Running this number may continue to increase",
return_type: Number(
TypeInfo {
name: "total",
is_optional: false,
is_list: false,
description: Some(
"Total number of results. If the status is Running this number may continue to increase",
),
type_description: None,
},
),
},
],
},
),
url: "results",
}

View File

@ -0,0 +1,68 @@
## Get search results ##
Name: `results`
**Parameters:**
Parameter | Type | Description
----------------------------------|---------|------------
`id` | number | ID of the search job
`limit` _optional_ | number | max number of results to return. 0 or negative means no limit
`offset` _optional_ | number | result to start at. A negative number means count backwards (e.g. `-2` returns the 2 most recent results)
**Returns:**
HTTP Status Code | Scenario
----------------------------------|---------------------
404 | Search job was not found
409 | Offset is too large, or too small (e.g. absolute value of negative number is greater than # results)
200 | All other scenarios- see JSON below
The response is a JSON object with the following fields
Field | Type | Description
----------------------------------|---------|------------
`results` | array | Array of `result` objects- see table below
`status` | string | Current status of the search job (either `Running` or `Stopped`)
`total` | number | Total number of results. If the status is `Running` this number may continue to increase
**Result object:**
Field | Type | Description
----------------------------------|---------|------------
`descrLink` | string | URL of the torrent's description page
`fileName` | string | Name of the file
`fileSize` | number | Size of the file in Bytes
`fileUrl` | string | Torrent download link (usually either .torrent file or magnet link)
`nbLeechers` | number | Number of leechers
`nbSeeders` | number | Number of seeders
`siteUrl` | string | URL of the torrent site
Example:
```JSON
{
"results": [
{
"descrLink": "http://www.legittorrents.info/index.php?page=torrent-details&id=8d5f512e1acb687029b8d7cc6c5a84dce51d7a41",
"fileName": "Ubuntu-10.04-32bit-NeTV.ova",
"fileSize": -1,
"fileUrl": "http://www.legittorrents.info/download.php?id=8d5f512e1acb687029b8d7cc6c5a84dce51d7a41&f=Ubuntu-10.04-32bit-NeTV.ova.torrent",
"nbLeechers": 1,
"nbSeeders": 0,
"siteUrl": "http://www.legittorrents.info"
},
{
"descrLink": "http://www.legittorrents.info/index.php?page=torrent-details&id=d5179f53e105dc2c2401bcfaa0c2c4936a6aa475",
"fileName": "mangOH-Legato-17_06-Ubuntu-16_04.ova",
"fileSize": -1,
"fileUrl": "http://www.legittorrents.info/download.php?id=d5179f53e105dc2c2401bcfaa0c2c4936a6aa475&f=mangOH-Legato-17_06-Ubuntu-16_04.ova.torrent",
"nbLeechers": 0,
"nbSeeders": 59,
"siteUrl": "http://www.legittorrents.info"
}
],
"status": "Running",
"total": 2
}
```

View File

@ -0,0 +1,327 @@
TokenTree {
title: None,
content: [],
children: [
TokenTree {
title: Some(
"Get search results",
),
content: [
Text(
"",
),
Text(
"Name: `results`",
),
Text(
"",
),
Asterisk(
"Parameters:",
),
Text(
"",
),
Table(
Table {
header: TableRow {
raw: "Parameter | Type | Description",
columns: [
"Parameter",
"Type",
"Description",
],
},
split: "----------------------------------|---------|------------",
rows: [
TableRow {
raw: "`id` | number | ID of the search job",
columns: [
"id",
"number",
"ID of the search job",
],
},
TableRow {
raw: "`limit` _optional_ | number | max number of results to return. 0 or negative means no limit",
columns: [
"limit _optional_",
"number",
"max number of results to return. 0 or negative means no limit",
],
},
TableRow {
raw: "`offset` _optional_ | number | result to start at. A negative number means count backwards (e.g. `-2` returns the 2 most recent results)",
columns: [
"offset _optional_",
"number",
"result to start at. A negative number means count backwards (e.g. -2 returns the 2 most recent results)",
],
},
],
},
),
Text(
"",
),
Asterisk(
"Returns:",
),
Text(
"",
),
Table(
Table {
header: TableRow {
raw: "HTTP Status Code | Scenario",
columns: [
"HTTP Status Code",
"Scenario",
],
},
split: "----------------------------------|---------------------",
rows: [
TableRow {
raw: "404 | Search job was not found",
columns: [
"404",
"Search job was not found",
],
},
TableRow {
raw: "409 | Offset is too large, or too small (e.g. absolute value of negative number is greater than # results)",
columns: [
"409",
"Offset is too large, or too small (e.g. absolute value of negative number is greater than # results)",
],
},
TableRow {
raw: "200 | All other scenarios- see JSON below",
columns: [
"200",
"All other scenarios- see JSON below",
],
},
],
},
),
Text(
"",
),
Text(
"The response is a JSON object with the following fields",
),
Text(
"",
),
Table(
Table {
header: TableRow {
raw: "Field | Type | Description",
columns: [
"Field",
"Type",
"Description",
],
},
split: "----------------------------------|---------|------------",
rows: [
TableRow {
raw: "`results` | array | Array of `result` objects- see table below",
columns: [
"results",
"array",
"Array of result objects- see table below",
],
},
TableRow {
raw: "`status` | string | Current status of the search job (either `Running` or `Stopped`)",
columns: [
"status",
"string",
"Current status of the search job (either Running or Stopped)",
],
},
TableRow {
raw: "`total` | number | Total number of results. If the status is `Running` this number may continue to increase",
columns: [
"total",
"number",
"Total number of results. If the status is Running this number may continue to increase",
],
},
],
},
),
Text(
"",
),
Asterisk(
"Result object:",
),
Text(
"",
),
Table(
Table {
header: TableRow {
raw: "Field | Type | Description",
columns: [
"Field",
"Type",
"Description",
],
},
split: "----------------------------------|---------|------------",
rows: [
TableRow {
raw: "`descrLink` | string | URL of the torrent's description page",
columns: [
"descrLink",
"string",
"URL of the torrent's description page",
],
},
TableRow {
raw: "`fileName` | string | Name of the file",
columns: [
"fileName",
"string",
"Name of the file",
],
},
TableRow {
raw: "`fileSize` | number | Size of the file in Bytes",
columns: [
"fileSize",
"number",
"Size of the file in Bytes",
],
},
TableRow {
raw: "`fileUrl` | string | Torrent download link (usually either .torrent file or magnet link)",
columns: [
"fileUrl",
"string",
"Torrent download link (usually either .torrent file or magnet link)",
],
},
TableRow {
raw: "`nbLeechers` | number | Number of leechers",
columns: [
"nbLeechers",
"number",
"Number of leechers",
],
},
TableRow {
raw: "`nbSeeders` | number | Number of seeders",
columns: [
"nbSeeders",
"number",
"Number of seeders",
],
},
TableRow {
raw: "`siteUrl` | string | URL of the torrent site",
columns: [
"siteUrl",
"string",
"URL of the torrent site",
],
},
],
},
),
Text(
"",
),
Text(
"Example:",
),
Text(
"",
),
Text(
"```JSON",
),
Text(
"{",
),
Text(
" \"results\": [",
),
Text(
" {",
),
Text(
" \"descrLink\": \"http://www.legittorrents.info/index.php?page=torrent-details&id=8d5f512e1acb687029b8d7cc6c5a84dce51d7a41\",",
),
Text(
" \"fileName\": \"Ubuntu-10.04-32bit-NeTV.ova\",",
),
Text(
" \"fileSize\": -1,",
),
Text(
" \"fileUrl\": \"http://www.legittorrents.info/download.php?id=8d5f512e1acb687029b8d7cc6c5a84dce51d7a41&f=Ubuntu-10.04-32bit-NeTV.ova.torrent\",",
),
Text(
" \"nbLeechers\": 1,",
),
Text(
" \"nbSeeders\": 0,",
),
Text(
" \"siteUrl\": \"http://www.legittorrents.info\"",
),
Text(
" },",
),
Text(
" {",
),
Text(
" \"descrLink\": \"http://www.legittorrents.info/index.php?page=torrent-details&id=d5179f53e105dc2c2401bcfaa0c2c4936a6aa475\",",
),
Text(
" \"fileName\": \"mangOH-Legato-17_06-Ubuntu-16_04.ova\",",
),
Text(
" \"fileSize\": -1,",
),
Text(
" \"fileUrl\": \"http://www.legittorrents.info/download.php?id=d5179f53e105dc2c2401bcfaa0c2c4936a6aa475&f=mangOH-Legato-17_06-Ubuntu-16_04.ova.torrent\",",
),
Text(
" \"nbLeechers\": 0,",
),
Text(
" \"nbSeeders\": 59,",
),
Text(
" \"siteUrl\": \"http://www.legittorrents.info\"",
),
Text(
" }",
),
Text(
" ],",
),
Text(
" \"status\": \"Running\",",
),
Text(
" \"total\": 2",
),
Text(
"}",
),
Text(
"```",
),
],
children: [],
},
],
}

View File

@ -1,15 +1,15 @@
mod description;
mod parameters;
mod return_type;
mod url;
use std::collections::HashMap;
use crate::{md_parser, parser::util, types};
pub use return_type::ReturnType;
use self::{
description::parse_method_description, parameters::parse_parameters,
return_type::parse_return_type, url::get_method_url,
description::parse_method_description, return_type::parse_return_type, url::get_method_url,
};
#[derive(Debug)]
@ -60,9 +60,10 @@ pub fn parse_api_method(child: &md_parser::TokenTree) -> Option<ApiMethod> {
}
fn to_api_method(child: &md_parser::TokenTree, name: &str) -> ApiMethod {
let tables = child.to_tables();
let method_description = parse_method_description(&child.content);
let return_type = parse_return_type(&child.content);
let parameters = parse_parameters(&child.content).map(ApiParameters::new);
let parameters = tables.parameters().map(ApiParameters::new);
let method_url = get_method_url(&child.content);
ApiMethod {
@ -73,3 +74,152 @@ fn to_api_method(child: &md_parser::TokenTree, name: &str) -> ApiMethod {
url: method_url,
}
}
impl md_parser::TokenTree {
fn to_tables(&self) -> Tables<'_> {
let mut tables = HashMap::new();
let mut prev_prev: Option<&md_parser::MdContent> = None;
let mut prev: Option<&md_parser::MdContent> = None;
for content in &self.content {
if let md_parser::MdContent::Table(table) = content {
let title = match prev_prev {
Some(md_parser::MdContent::Text(text)) => text.clone(),
Some(md_parser::MdContent::Asterisk(text)) => text.clone(),
_ => panic!("Expected table title, found: {:?}", prev_prev),
};
tables.insert(title.replace(':', ""), table);
}
prev_prev = prev;
prev = Some(content);
}
Tables { tables }
}
}
#[derive(Debug)]
struct Tables<'a> {
tables: HashMap<String, &'a md_parser::Table>,
}
impl<'a> Tables<'a> {
fn parameters(&self) -> Option<Vec<types::Type>> {
self.get_type_containing("Parameters")
}
// fn return_type(&self) -> Option<Vec<types::Type>> {
// self.get_type_containing("Returns")
// }
fn get_type_containing(&self, name: &str) -> Option<Vec<types::Type>> {
self.tables
.iter()
.find(|(key, _)| key.contains(name))
.map(|(_, table)| table.to_types())
}
}
impl md_parser::Table {
fn to_types(&self) -> Vec<types::Type> {
self.rows
.iter()
.flat_map(|table_row| table_row.to_type())
.collect()
}
}
impl md_parser::TableRow {
fn to_type(&self) -> Option<types::Type> {
let columns = &self.columns;
let type_map = HashMap::new();
let description = columns.get(2).cloned();
match &columns.get(2) {
// If the description contains a default value it means that the parameter is optional.
Some(desc) if desc.contains("default: ") => {
// type defines a variable as default if it contains: _optional_
let name_with_optional = format!("{} {}", columns[0], types::OPTIONAL);
types::Type::from(&columns[1], &name_with_optional, description, &type_map)
}
_ => types::Type::from(&columns[1], &columns[0], description, &type_map),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use md_parser::TokenTreeFactory;
macro_rules! TEST_DIR {
() => {
"method_tests"
};
}
#[allow(unused_macros)]
macro_rules! run_test {
($test_file:expr) => {
use pretty_assertions::assert_eq;
// given
let input = include_str!(concat!(TEST_DIR!(), "/", $test_file, ".md"));
// when
let tree = TokenTreeFactory::create(input);
let api_method = parse_api_method(&tree.children[0]).unwrap();
// then
let api_method_as_str = format!("{api_method:#?}");
let should_be = include_str!(concat!(TEST_DIR!(), "/", $test_file, ".check"));
assert_eq!(api_method_as_str, should_be);
};
}
// use this macro when creating/updating as test
#[allow(unused_macros)]
macro_rules! update_test {
($test_file:expr) => {
use std::fs;
use std::path::Path;
let input = include_str!(concat!(TEST_DIR!(), "/", $test_file, ".md"));
let tree = TokenTreeFactory::create(input);
let api_method = parse_api_method(&tree.children[0]).unwrap();
let tree_as_str = format!("{tree:#?}");
let api_method_as_str = format!("{api_method:#?}");
let tree_file = concat!(
"src/parser/group/method/",
TEST_DIR!(),
"/",
$test_file,
".tree"
);
let file = concat!(
"src/parser/group/method/",
TEST_DIR!(),
"/",
$test_file,
".check"
);
// prevent user from accidentally leaving the current macro in a test
if Path::new(file).exists() {
panic!("Test case already exists: {file}");
}
fs::write(file, api_method_as_str).unwrap();
fs::write(tree_file, tree_as_str).unwrap();
};
}
#[test]
fn search_result() {
run_test!("search_result");
}
}

View File

@ -1,51 +0,0 @@
use std::collections::HashMap;
use crate::{md_parser, parser::types};
pub fn parse_parameters(content: &[md_parser::MdContent]) -> Option<Vec<types::Type>> {
let mut it = content
.iter()
.skip_while(|row| match row {
md_parser::MdContent::Asterisk(content) | md_parser::MdContent::Text(content) => {
!content.starts_with("Parameters:")
}
_ => true,
})
// Parameters: <-- skip
// <-- skip
// table with parameters <-- take
.skip(2);
let parameter_table = match it.next() {
Some(md_parser::MdContent::Table(table)) => table,
_ => return None,
};
// empty for now
let type_map = HashMap::default();
let table = parameter_table
.rows
.iter()
.flat_map(|row| parse_parameter(row, &type_map))
.collect();
Some(table)
}
fn parse_parameter(
row: &md_parser::TableRow,
type_map: &HashMap<String, types::TypeDescription>,
) -> Option<types::Type> {
let description = row.columns.get(2).cloned();
match &row.columns.get(2) {
// If the description contains a default value it means that the parameter is optional.
Some(desc) if desc.contains("default: ") => {
// type defines a variable as default if it contains: _optional_
let name_with_optional = format!("{} {}", row.columns[0], types::OPTIONAL);
types::Type::from(&row.columns[1], &name_with_optional, description, type_map)
}
_ => types::Type::from(&row.columns[1], &row.columns[0], description, type_map),
}
}

View File

@ -1,4 +1,3 @@
use regex::RegexBuilder;
use std::collections::HashMap;
#[derive(Debug, Clone)]
@ -45,6 +44,12 @@ pub struct TypeWithRef {
pub ref_type: String,
}
#[derive(Debug, Clone)]
pub struct ComplexObject {
pub type_info: TypeInfo,
pub fields: Vec<Type>,
}
pub const OPTIONAL: &str = "_optional_";
#[derive(Debug, Clone)]
@ -54,8 +59,8 @@ pub enum Type {
Bool(TypeInfo),
String(TypeInfo),
StringArray(TypeInfo),
ObjectArray(TypeWithRef),
Object(TypeInfo),
// ComplexObject(ComplexObject),
}
impl Type {
@ -67,7 +72,7 @@ impl Type {
Type::String(_) => "String".into(),
Type::StringArray(_) => "String".into(),
Type::Object(_) => "String".into(),
Type::ObjectArray(_) => panic!("Not implemented for ObjectArray"),
// Type::ComplexObject(_) => panic!("Not implemented for ComplexObject"),
}
}
@ -83,7 +88,7 @@ impl Type {
Type::String(_) => "str".into(),
Type::StringArray(_) => "&[str]".into(),
Type::Object(_) => "str".into(),
Type::ObjectArray(_) => panic!("Not implemented for ObjectArray"),
// Type::ComplexObject(_) => panic!("Not implemented for ComplexObject"),
}
}
@ -99,7 +104,7 @@ impl Type {
Type::String(t) => t,
Type::StringArray(t) => t,
Type::Object(t) => t,
Type::ObjectArray(TypeWithRef { type_info, .. }) => type_info,
// Type::ComplexObject(ComplexObject { type_info, .. }) => type_info,
}
}
@ -131,16 +136,17 @@ impl Type {
"bool" => Some(Type::Bool(create_type_info())),
"integer" | "number" | "int" => Some(Type::Number(create_type_info())),
"string" => Some(Type::String(create_type_info())),
"array" => description
.clone()
.and_then(|ref desc| get_ref_type(desc))
.map(|ref_type| {
Type::ObjectArray(TypeWithRef {
type_info: create_type_info(),
ref_type,
})
})
.or_else(|| Some(Type::StringArray(create_type_info()))),
"array" => Some(Type::StringArray(create_type_info())),
// "array" => description
// .clone()
// .and_then(|ref desc| get_ref_type(desc))
// .map(|ref_type| {
// Type::ObjectArray(TypeWithRef {
// type_info: create_type_info(),
// ref_type,
// })
// })
// .or_else(|| Some(Type::StringArray(create_type_info()))),
"object" => Some(Type::Object(create_type_info())),
"float" => Some(Type::Float(create_type_info())),
_ => None,
@ -148,24 +154,24 @@ impl Type {
}
}
fn get_ref_type(desc: &str) -> Option<String> {
let re = RegexBuilder::new(r".*array of (\w+)\s?.*")
.case_insensitive(true)
.build()
.unwrap();
// fn get_ref_type(desc: &str) -> Option<String> {
// let re = RegexBuilder::new(r".*array of (\w+)\s?.*")
// .case_insensitive(true)
// .build()
// .unwrap();
re.captures(desc)
.and_then(|captures| captures.get(1))
.map(|m| m.as_str().to_owned())
}
// re.captures(desc)
// .and_then(|captures| captures.get(1))
// .map(|m| m.as_str().to_owned())
// }
#[cfg(test)]
mod tests {
use super::*;
// use super::*;
#[test]
fn should_parse_object_array() {
let ref_type = get_ref_type("Array of result objects- see table below");
assert_eq!("result", ref_type.unwrap());
}
// #[test]
// fn should_parse_object_array() {
// let ref_type = get_ref_type("Array of result objects- see table below");
// assert_eq!("result", ref_type.unwrap());
// }
}