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’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export on server #218

Merged
merged 3 commits into from
Mar 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ tasks:
- task: client:check
- task: themes:check

fix:ts:
cmds:
- task: docs:fix
- task: client:fix
- task: themes:fix

check:rs:
deps: [base:grammar]
cmds:
Expand Down
30 changes: 17 additions & 13 deletions compiler-core/src/plugin/builtin/export_livesplit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,26 @@ impl PluginRuntime for ExportLiveSplitPlugin {

let mut segments_xml = String::new();
for section in &doc.route {
let length = section.lines.len();
for (i, line) in section.lines.iter().enumerate() {
let mut split_lines = vec![];
for line in &section.lines {
if should_split_on(line, &split_types) {
let mut name = match &line.split_name {
Some(name) => name.to_string(),
None => line.text.to_string(),
};
if subsplit {
if i == length - 1 {
name = format!("{{{}}}{name}", section.name);
} else {
name = format!("-{name}");
}
split_lines.push(line);
}
}
let length = split_lines.len();
for (i, line) in split_lines.iter().enumerate() {
let mut name = match &line.split_name {
Some(name) => name.to_string(),
None => line.text.to_string(),
};
if subsplit {
if i == length - 1 {
name = format!("{{{}}}{name}", section.name);
} else {
name = format!("-{name}");
}
append_segment(&mut segments_xml, &name, line);
}
append_segment(&mut segments_xml, &name, line);
}
}

Expand Down
22 changes: 9 additions & 13 deletions compiler-wasm/src/compiler/export.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use instant::Instant;
use log::{error, info};
use wasm_bindgen::prelude::*;

use celerc::pack::PackError;
use celerc::{Compiler, ExpoDoc, ExportRequest, PluginOptions, PreparedContext};
Expand All @@ -14,14 +13,14 @@ pub async fn export_document(
entry_path: Option<String>,
use_cache: bool,
req: ExportRequest,
) -> Result<ExpoDoc, JsValue> {
) -> ExpoDoc {
info!("exporting document");
let plugin_options = match plugin::get_plugin_options() {
Ok(x) => x,
Err(message) => {
let message = format!("Failed to load user plugin options: {message}");
error!("{message}");
return Ok(ExpoDoc::Error(message));
return ExpoDoc::Error(message);
}
};

Expand All @@ -39,7 +38,7 @@ pub async fn export_document(
let prep_ctx = match super::new_context(entry_path).await {
Ok(x) => x,
Err(e) => {
return Ok(ExpoDoc::Error(e.to_string()));
return ExpoDoc::Error(e.to_string());
}
};
let guard = CachedContextGuard::new(prep_ctx);
Expand All @@ -51,7 +50,7 @@ async fn export_in_context(
start_time: Option<Instant>,
plugin_options: Option<PluginOptions>,
req: ExportRequest,
) -> Result<ExpoDoc, JsValue> {
) -> ExpoDoc {
let mut comp_ctx = prep_ctx.new_compilation(start_time).await;
match comp_ctx.configure_plugins(plugin_options).await {
Err(e) => export_with_pack_error(e),
Expand All @@ -62,18 +61,15 @@ async fn export_in_context(
}
}

fn export_with_pack_error(error: PackError) -> Result<ExpoDoc, JsValue> {
Ok(ExpoDoc::Error(error.to_string()))
fn export_with_pack_error(error: PackError) -> ExpoDoc {
ExpoDoc::Error(error.to_string())
}

async fn export_with_compiler(
compiler: Compiler<'_>,
req: ExportRequest,
) -> Result<ExpoDoc, JsValue> {
async fn export_with_compiler(compiler: Compiler<'_>, req: ExportRequest) -> ExpoDoc {
let mut comp_doc = compiler.compile().await;
if let Some(expo_doc) = comp_doc.run_exporter(&req) {
return Ok(expo_doc);
return expo_doc;
}
let exec_ctx = comp_doc.execute().await;
Ok(exec_ctx.run_exporter(req))
exec_ctx.run_exporter(req)
}
2 changes: 1 addition & 1 deletion compiler-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub async fn export_document(
use_cache: bool,
req: ExportRequest,
) -> Result<ExpoDoc, JsValue> {
compiler::export_document(entry_path, use_cache, req).await
Ok(compiler::export_document(entry_path, use_cache, req).await)
}

/// Set user plugin options
Expand Down
26 changes: 26 additions & 0 deletions docs/src/api/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,29 @@ Otherwise, it will return
"data": "error message here"
}
```

## `GET /export/{owner}/{repo}/{ref}[/{path}]`
Export the document
### Parameters
Same as the `/compile` endpoint.

### Headers
|Name|Description|
|-|-|
|`Celer-Export-Request`|(Required) Base64 encoded JSON ExportRequest object|
|`Celer-Plugin-Options`|(Optional) Base64 encoded JSON PluginOptionsRaw object used to specify extra plugin options|

### Returns
It should always return status `200 OK`.

Returns an ExpoDoc, which could be success or error
```json
{
"success": { ... }
}
```
```json
{
"error": "message here",
}
```
59 changes: 13 additions & 46 deletions server/src/api/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ use axum::extract::Path;
use axum::http::HeaderMap;
use axum::routing;
use axum::{Json, Router};
use base64::Engine;
use instant::Instant;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tower::ServiceBuilder;
use tower_http::compression::CompressionLayer;
use tracing::error;

use crate::compiler;

use super::header;

pub fn init_api() -> Router {
Router::new()
.route(
Expand All @@ -38,86 +38,53 @@ async fn compile_owner_repo_ref(
Path((owner, repo, reference)): Path<(String, String, String)>,
headers: HeaderMap,
) -> Json<CompileResponse> {
let plugin_options = match get_plugin_options_from_headers(&headers) {
let plugin_options = match header::get_plugin_options(&headers) {
Ok(v) => v,
Err(e) => return Json(CompileResponse::Failure(e)),
};
let response = compile_internal(&owner, &repo, None, &reference, plugin_options).await;
let response = compile_internal(&owner, &repo, None, &reference, &plugin_options).await;
Json(response)
}

async fn compile_owner_repo_ref_path(
Path((owner, repo, reference, path)): Path<(String, String, String, String)>,
headers: HeaderMap,
) -> Json<CompileResponse> {
let plugin_options = match get_plugin_options_from_headers(&headers) {
let plugin_options = match header::get_plugin_options(&headers) {
Ok(v) => v,
Err(e) => return Json(CompileResponse::Failure(e)),
};
let response = compile_internal(&owner, &repo, Some(&path), &reference, plugin_options).await;
let response = compile_internal(&owner, &repo, Some(&path), &reference, &plugin_options).await;
Json(response)
}

fn get_plugin_options_from_headers(headers: &HeaderMap) -> Result<Option<String>, String> {
let header_value = match headers.get("Celer-Plugin-Options") {
None => return Ok(None),
Some(v) => v,
};
let header_value = match header_value.to_str() {
Ok(s) => s,
Err(e) => {
error!("Invalid Celer-Plugin-Options header: {e}");
return Err("Invalid Celer-Plugin-Options header".to_string());
}
};
if header_value.is_empty() {
return Ok(None);
}

let header_decoded = match base64::engine::general_purpose::STANDARD.decode(header_value) {
Ok(v) => v,
Err(e) => {
error!("Failed to decode Celer-Plugin-Options header: {e}");
return Err("Invalid Celer-Plugin-Options header".to_string());
}
};

let header_str = match String::from_utf8(header_decoded) {
Ok(s) => s,
Err(e) => {
error!("Celer-Plugin-Options header is not valid UTF-8: {e}");
return Err("Invalid Celer-Plugin-Options header".to_string());
}
};

Ok(Some(header_str))
}

async fn compile_internal(
owner: &str,
repo: &str,
path: Option<&str>,
reference: &str,
plugin_options_json: Option<String>,
plugin_options_json: &str,
) -> CompileResponse {
let start_time = Instant::now();
let prep_ctx = match compiler::get_context(owner, repo, path, reference).await {
Ok(ctx) => ctx,
Err(e) => return CompileResponse::Failure(e.to_string()),
};

let plugin_options = match plugin_options_json {
None => None,
Some(s) => match compiler::parse_plugin_options(&s, &prep_ctx.project_res).await {
let plugin_options = if plugin_options_json.is_empty() {
None
} else {
match compiler::parse_plugin_options(plugin_options_json, &prep_ctx.project_res).await {
Ok(options) => Some(options),
Err(e) => return CompileResponse::Failure(e),
},
}
};

let expo_ctx = compiler::compile(&prep_ctx, Some(start_time), plugin_options).await;
let expo_ctx_json = match serde_json::to_value(expo_ctx) {
Ok(v) => v,
Err(e) => return CompileResponse::Failure(e.to_string()),
};

CompileResponse::Success(expo_ctx_json)
}
83 changes: 83 additions & 0 deletions server/src/api/export.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use axum::extract::Path;
use axum::http::HeaderMap;
use axum::routing;
use axum::{Json, Router};
use celerc::{ExpoDoc, ExportRequest};
use instant::Instant;
use tower::ServiceBuilder;
use tower_http::compression::CompressionLayer;

use crate::compiler;

use super::header;

pub fn init_api() -> Router {
Router::new()
.route(
"/:owner/:repo/:reference",
routing::get(export_owner_repo_ref),
)
.route(
"/:owner/:repo/:reference/*path",
routing::get(export_owner_repo_ref_path),
)
.layer(ServiceBuilder::new().layer(CompressionLayer::new()))
}

async fn export_owner_repo_ref(
Path((owner, repo, reference)): Path<(String, String, String)>,
headers: HeaderMap,
) -> Json<ExpoDoc> {
let plugin_options = match header::get_plugin_options(&headers) {
Ok(v) => v,
Err(e) => return Json(ExpoDoc::Error(e)),
};
let req = match header::get_export_request(&headers) {
Ok(v) => v,
Err(e) => return Json(ExpoDoc::Error(e)),
};
let response = export_internal(&owner, &repo, None, &reference, &plugin_options, req).await;
Json(response)
}

async fn export_owner_repo_ref_path(
Path((owner, repo, reference, path)): Path<(String, String, String, String)>,
headers: HeaderMap,
) -> Json<ExpoDoc> {
let plugin_options = match header::get_plugin_options(&headers) {
Ok(v) => v,
Err(e) => return Json(ExpoDoc::Error(e)),
};
let req = match header::get_export_request(&headers) {
Ok(v) => v,
Err(e) => return Json(ExpoDoc::Error(e)),
};
let response =
export_internal(&owner, &repo, Some(&path), &reference, &plugin_options, req).await;
Json(response)
}
async fn export_internal(
owner: &str,
repo: &str,
path: Option<&str>,
reference: &str,
plugin_options_json: &str,
req: ExportRequest,
) -> ExpoDoc {
let start_time = Instant::now();
let prep_ctx = match compiler::get_context(owner, repo, path, reference).await {
Ok(ctx) => ctx,
Err(e) => return ExpoDoc::Error(e.to_string()),
};

let plugin_options = if plugin_options_json.is_empty() {
None
} else {
match compiler::parse_plugin_options(plugin_options_json, &prep_ctx.project_res).await {
Ok(options) => Some(options),
Err(e) => return ExpoDoc::Error(e),
}
};

compiler::export(&prep_ctx, Some(start_time), plugin_options, req).await
}
Loading