Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Use the new API project with breaking changes #5

Merged
merged 28 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cd89e32
feat: Load API
taorepoara Sep 19, 2023
7fa88b4
feat: Manifest and request API
taorepoara Sep 20, 2023
b21a617
feat: Generate API elements
taorepoara Sep 22, 2023
aeb6201
feat: Implement API structs
taorepoara Sep 22, 2023
623f946
feat: Components struct generation
taorepoara Sep 26, 2023
1ea064a
feat: Upgrade API
taorepoara Sep 26, 2023
712c8fb
fix: Build.rs
taorepoara Sep 26, 2023
0ef778d
fix: Separate generation ans build
taorepoara Sep 26, 2023
6eb321d
feat: Into manifest view
taorepoara Sep 26, 2023
489967b
fix: Collections
taorepoara Sep 26, 2023
b31f96c
fix: Api
taorepoara Sep 26, 2023
e0511c8
fix: Listener prop
taorepoara Sep 27, 2023
72938a4
fix: Manifest response
taorepoara Sep 27, 2023
07a9e84
fix: Borring manifest
taorepoara Sep 27, 2023
b837bd7
fix: Manifest
taorepoara Sep 27, 2023
8eff296
fix: View response
taorepoara Sep 28, 2023
da4aa62
fix: Generation
taorepoara Sep 28, 2023
10cd50c
fix: Simplify imports
taorepoara Sep 28, 2023
5494ab8
feat: Macros
taorepoara Sep 28, 2023
9fe709b
fix: Simplify listener
taorepoara Sep 28, 2023
9a034fb
feat: TryInto for params
taorepoara Sep 28, 2023
aacaab9
fix: Listener function
taorepoara Sep 28, 2023
0835137
fix: Macro
taorepoara Sep 28, 2023
4f94062
style: Fix format
taorepoara Sep 28, 2023
0178185
build(deps): Clean unsused dependencies
taorepoara Sep 28, 2023
bf72794
ci: Fix publish
taorepoara Sep 28, 2023
dd57d31
style: Remove unsued file
taorepoara Sep 28, 2023
9e900dd
docs: JUpdate README
taorepoara Sep 28, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,4 @@ jobs:
- name: Publish cargo
shell: bash
if: ${{ steps.release.outputs.new-release-published }}
run: cargo publish --allow-dirty --token "${{ secrets.CARGO_TOKEN }}"
run: cargo publish --allow-dirty --token "${{ secrets.CARGO_TOKEN }}" --package lenra-app
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ Cargo.lock

# These are backup files generated by rustfmt
**/*.rs.bk

/api
28 changes: 5 additions & 23 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,5 @@
[package]
name = "lenra-app"
version = "0.0.0"
edition = "2021"
license = "MIT"
description = "App Lib for Rust Lenra apps"
repository = "https://github.com/lenra-io/app-lib-rust"
keywords = ["lenra", "app", "lib", "api"]
include = [
"**/*.rs",
"Cargo.toml",
]

[lib]
name = "lenra_app"
path = "src/lib.rs"

[dependencies]
env_logger = "0.9.0"
log = "0.4.17"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
ureq = { version = "2.5.0", features = ["json"] }
[workspace]
members = [
"lenra-app",
"generate_apis",
]
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ impl Doc for CustomType {
pub struct CustomProps {
value: String,
}
// Use the props! macro to generate the props struct automatically
props!(CustomProps);

fn my_listener(params: ListenerParams<CustomProps, Value>) -> Result<()> {
let my_doc: CustomType = params.api.data.create_doc(
Expand Down
15 changes: 15 additions & 0 deletions generate_apis/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "generate_apis"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
prettyplease = "0.2"
schemars = "0.8"
serde_json = "1.0.106"
syn = "2.0.32"
typify = "0.0.14"
regex = "1.9.5"
convert_case = "0.6.0"
236 changes: 236 additions & 0 deletions generate_apis/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
use regex::Regex;
use schemars::schema::{RootSchema, Schema, SchemaObject};
use std::io::BufRead;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::{
env,
fs::{self, File},
io::BufReader,
};
use typify::{TypeSpace, TypeSpaceSettings, TypeStruct};

fn main() -> Result<(), String> {
// Loop over all files in the list
BufReader::new(File::open("lenra-api.files.txt").expect("Could not open the file list"))
.lines()
.map(|line| line.expect("Could not read line"))
.filter(|line| !line.starts_with("#"))
.map(|line| line.trim().to_string())
.filter(|line| !line.is_empty())
.for_each(|schema| {
generate_structs(schema.as_str());
});
Ok(())
}

fn generate_structs(schema_name: &str) {
println!("Loading schema from {}", schema_name);
if schema_name.ends_with(".schema.json") {
json_schema_to_rust(schema_name);
}
/* else if schema_name.ends_with("-api.yml") {
open_api_to_rust(schema_name);
} */
else {
println!("Skipping {}", schema_name);
}
}

fn json_schema_to_rust(schema_name: &str) {
let schema_path = format!("api/{}", schema_name);
let content = std::fs::read_to_string(schema_path).unwrap();
let regex: Regex = Regex::new(r#"(?m),\n\s+"default":.+$"#).unwrap();
let content = regex.replace_all(&content, "");
let mut schema = serde_json::from_str::<schemars::schema::RootSchema>(&content).unwrap();

let mut type_space = TypeSpace::new(TypeSpaceSettings::default().with_struct_builder(true));
type_space.add_root_schema(schema.clone()).unwrap();

let contents = format!(
"{}\n{}",
"use serde::{Deserialize, Serialize};",
prettyplease::unparse(&syn::parse2::<syn::File>(type_space.to_stream()).unwrap())
);
let target_file_name = schema_name
.replace("-", "_")
.replace("/", "_")
.replace(".schema.json", "");
let out_file = PathBuf::from(format!("lenra-app/src/gen/{}.rs", target_file_name));
let contents = if schema_name.starts_with("components/") {
let additionnal_content = if schema_name.ends_with("lenra.schema.json") {
build_component_functions(&mut type_space, schema)
} else {
let root_title = schema
.schema
.metadata()
.title
.as_ref()
.clone()
.unwrap()
.clone();
let component_type = type_space
.iter_types()
.find(|t| t.name() == root_title)
.unwrap();
let component_struct = match component_type.details() {
typify::TypeDetails::Struct(struct_type) => struct_type,
_ => unreachable!(),
};

build_component_function(&type_space, &component_struct, &mut schema.schema.clone())
};
let mut contents = contents;
contents.push_str("\n\n");
contents.push_str(&additionnal_content);
contents
} else {
contents
};
fs::write(out_file.clone(), contents).unwrap();

if let Some(rustfmt) = rustfmt_path() {
let mut cmd = Command::new(rustfmt);
cmd.arg(out_file)
.stdin(Stdio::piped())
.stdout(Stdio::piped());
if let Err(error) = cmd.output() {
println!("Error while running rustfmt: {}", error);
}
}
}

fn build_component_functions(type_space: &mut TypeSpace, root_schema: RootSchema) -> String {
let components: Vec<SchemaObject> = root_schema
.schema
.subschemas
.unwrap()
.one_of
.unwrap()
.iter()
.map(|s| match s {
schemars::schema::Schema::Bool(_) => panic!("Unexpected bool"),
schemars::schema::Schema::Object(schema) => {
let reference = schema.reference.clone().unwrap();
let reference = reference
.split("/")
.skip(2)
.collect::<Vec<&str>>()
.join("/");
let ref_schema = root_schema
.definitions
.get(&reference)
.expect(format!("Could not find definition for {}", reference).as_str());

match ref_schema {
Schema::Bool(_) => unreachable!("Unexpected bool"),
Schema::Object(schema) => schema.clone(),
}
}
})
.collect();
let components_titles: Vec<String> = components
.iter()
.map(|schema| schema.clone().metadata().title.as_ref().unwrap().clone())
.collect();
type_space
.iter_types()
.filter(|t| components_titles.contains(&t.name()))
.map(|el| {
let schema = components
.iter()
.find(|&schema| {
el.name() == schema.clone().metadata().title.as_ref().unwrap().clone()
})
.expect("Schema not found");
match el.details() {
typify::TypeDetails::Struct(struct_type) => {
build_component_function(type_space, &struct_type, &mut schema.clone())
}
_ => unreachable!(),
}
})
.collect::<Vec<String>>()
.join("\n\n")
}

fn build_component_function(
type_space: &TypeSpace,
struct_type: &TypeStruct,
schema: &mut SchemaObject,
) -> String {
let title = schema
.metadata
.as_ref()
.unwrap()
.title
.as_ref()
.unwrap()
.clone();

let lower_case_title = title.to_lowercase();
let mut params_builder = String::new();
let mut instance_builder = String::new();
let mut type_builder = String::new();
let mut where_builder = String::new();
let schema_object = schema.object.as_ref().unwrap();

let required_props: Vec<String> = schema_object
.required
.iter()
.filter(|prop| prop.clone() != "_type")
.map(normalize_prop_name)
.collect();

let mut type_counter = 0;

struct_type
.properties()
.filter(|(name, _)| {
required_props
.clone()
.contains(&normalize_prop_name(&name.to_string()))
})
.for_each(|(name, type_id)| {
let pos = type_counter;
type_counter += 1;
let type_name = type_space.get_type(&type_id).unwrap().name();
if pos == 0 {
type_builder.push_str("<");
where_builder.push_str(" \nwhere\n");
} else {
type_builder.push_str(", ");
params_builder.push_str(", ");
}
type_builder.push_str(format!("T{pos}").as_str());
where_builder.push_str(format!(" T{pos}: std::convert::TryInto<{type_name}>,\n T{pos}::Error: std::fmt::Display,\n").as_str());
params_builder.push_str(format!("{}: T{pos}", name).as_str());
instance_builder.push_str(format!("\n .{}({})", name, name).as_str());
});
if type_counter > 0 {
type_builder.push_str(">");
}
format!(
r#"pub fn {lower_case_title}{type_builder}({params_builder}) -> builder::{title}{where_builder} {{
{title}::builder()
.type_("{lower_case_title}"){instance_builder}
}}"#
)
}

fn normalize_prop_name(name: &String) -> String {
name.to_lowercase().replace("_", "")
}

fn rustfmt_path() -> Option<PathBuf> {
if let Ok(rustfmt) = env::var("RUSTFMT") {
return Some(rustfmt.into());
}
#[cfg(feature = "which-rustfmt")]
match which::which("rustfmt") {
Ok(p) => Some(p),
Err(e) => None,
}
#[cfg(not(feature = "which-rustfmt"))]
None
}
6 changes: 6 additions & 0 deletions lenra-api.files.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# internal-api.json
manifest.schema.json
# requests/app.schema.json
# components/json.schema.json
# components/listener.schema.json
components/lenra.schema.json
1 change: 1 addition & 0 deletions lenra-api.version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.0.0-beta.23
24 changes: 24 additions & 0 deletions lenra-app/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
[package]
name = "lenra-app"
version = "0.0.0"
edition = "2021"
license = "MIT"
description = "App Lib for Rust Lenra apps"
repository = "https://github.com/lenra-io/app-lib-rust"
keywords = ["lenra", "app", "lib", "api"]
include = [
"**/*.rs",
"Cargo.toml",
]

[lib]
name = "lenra_app"
path = "src/lib.rs"

[dependencies]
env_logger = "0.9.0"
log = "0.4.17"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
ureq = { version = "2.5.0", features = ["json"] }
regress = "0.7.1"
Loading
Loading