Skip to content

Commit 27354b8

Browse files
committed
Migrate playground-utils to ffi_support
This is a test run of using the patterns encouraged by mozilla's ffi-support crate (https://docs.rs/ffi-support). I wasn't totally sure how this would go, but it was relatively painless and I'm encouraged by the code. While I was in here I also did some general cleanup on the swift side
1 parent 69aa983 commit 27354b8

File tree

6 files changed

+68
-95
lines changed

6 files changed

+68
-95
lines changed

RustPlayground/RustPlayground/Playground.swift

+26-51
Original file line numberDiff line numberDiff line change
@@ -12,46 +12,25 @@ import Foundation
1212

1313
struct PlaygroundError {
1414
let message: String
15-
let code: Int
16-
17-
init?(json: [String: AnyObject]) {
18-
if let message = json["message"] as? String,
19-
let code = json["code"] as? Int {
20-
self.message = message
21-
self.code = code
22-
} else {
23-
return nil
24-
}
25-
}
15+
let code: Int32
2616
}
2717

2818
extension PlaygroundError: Error {}
2919

3020
func listToolchains() -> Result<[Toolchain], PlaygroundError> {
31-
let response = JsonResponse.from(externFn: playgroundGetToolchains)
32-
switch response {
33-
case .ok(let result):
34-
let data = (result as! [AnyObject])
35-
let toolchains = data.map { Toolchain.fromJson(json: $0)! }
36-
return .success(toolchains)
37-
case .error(let error):
38-
return .failure(error)
39-
}
21+
let response = PlaygroundResult.from { err in playgroundGetToolchains(err) }
22+
return response.map({ (toolchains) -> [Toolchain] in
23+
let data = toolchains as! [AnyObject]
24+
return data.map { Toolchain.fromJson(json: $0)! }
25+
})
4026
}
4127

4228
func executeTask(inDirectory buildDir: URL, task: CompilerTask, stderr: @escaping ((String) -> ())) -> Result<CompilerResult, PlaygroundError> {
4329
GlobalTaskContext.stderrCallback = stderr
4430
let buildPath = buildDir.path
4531
let taskJson = task.toJson()
46-
let response = JsonResponse.from { playgroundExecuteTask(buildPath, taskJson, stderrCallback) }
47-
48-
switch response {
49-
case .ok(let result):
50-
let data = result as! [String: AnyObject]
51-
return .success(CompilerResult.fromJson(data))
52-
case .error(let err):
53-
return .failure(err)
54-
}
32+
let response = PlaygroundResult.from { err in playgroundExecuteTask(buildPath, taskJson, stderrCallback, err) }
33+
return response.map { CompilerResult.fromJson($0 as! [String: AnyObject]) }
5534
}
5635

5736
/// The least bad way I could think of to pipe callbacks from rust back
@@ -96,30 +75,26 @@ struct Toolchain {
9675
return base
9776
}
9877
}
99-
// TODO: this can probably just be a Result<T, E>
100-
enum JsonResponse {
101-
case error(PlaygroundError)
102-
case ok(Any)
103-
104-
/// Wrapper around an external function that returns json. Handles basic
105-
/// parsing and freeing memory.
106-
///
107-
/// we control all the messages we send and receive, so we assume all
108-
/// messages are well-formed.
109-
static func from(externFn: () -> UnsafePointer<Int8>?) -> JsonResponse {
110-
let cString = externFn()!
78+
79+
typealias PlaygroundResult = Result<Any, PlaygroundError>
80+
81+
extension PlaygroundResult {
82+
/// Call an external function, doing error handling and json parsing.
83+
static func from(externFn: (UnsafeMutablePointer<ExternError>) -> UnsafePointer<Int8>?) -> PlaygroundResult {
84+
var error = ExternError()
85+
let cString = externFn(&error)!
11186
defer { playgroundStringFree(cString) }
11287

113-
let string = String(cString: cString, encoding: .utf8)!
114-
let message = try! JSONSerialization.jsonObject(with: string.data(using: .utf8)!) as! [String: AnyObject]
115-
if let result = message["result"] {
116-
return .ok(result)
117-
} else if let error = message["error"] {
118-
let error = error as! [String: AnyObject]
119-
return .error(PlaygroundError(json: error)!)
120-
} else {
121-
fatalError("invalid json response: \(message)")
88+
if error.code != 0 {
89+
let message = String(cString: error.message, encoding: .utf8)!
90+
let error = PlaygroundError(message: message, code: error.code)
91+
playgroundStringFree(error.message)
92+
return .failure(error)
12293
}
94+
95+
let string = String(cString: cString, encoding: .utf8)!
96+
let message = try! JSONSerialization.jsonObject(with: string.data(using: .utf8)!)
97+
return .success(message)
12398
}
124-
}
12599

100+
}

playground-utils-ffi/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ authors = ["Colin Rofls <[email protected]>"]
55
edition = "2018"
66

77
[dependencies]
8+
ffi-support = "0.3.4"
89
serde_json = "1.0"
910

1011
[dependencies.playground-utils]

playground-utils-ffi/playground.h

+7-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
typedef const char* json;
55
typedef void (*stderr_callback)(const char*);
66

7-
extern json playgroundGetToolchains();
8-
extern json playgroundExecuteTask(const char* path, json, stderr_callback);
7+
typedef struct _ExternError {
8+
int32_t code;
9+
char *message; // note: nullable
10+
} ExternError;
11+
12+
extern json playgroundGetToolchains(ExternError* error);
13+
extern json playgroundExecuteTask(const char* path, json, stderr_callback, ExternError* error);
914

1015
extern void playgroundStringFree(json);
1116

playground-utils-ffi/src/lib.rs

+22-40
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,40 @@
1-
#[macro_use]
2-
extern crate serde_json;
3-
41
use libc::c_char;
52
use std::ffi::{CStr, CString, OsStr};
63
use std::os::unix::ffi::OsStrExt;
74
use std::path::Path;
85

9-
use playground_utils::{do_compile_task, list_toolchains, Error, Task};
6+
use ffi_support::{call_with_result, ExternError};
7+
use playground_utils::{do_compile_task, list_toolchains, Task};
108

119
#[no_mangle]
12-
pub extern "C" fn playgroundGetToolchains() -> *const c_char {
13-
let response = match list_toolchains() {
14-
Ok(toolchains) => json!({ "result": toolchains }),
15-
Err(e) => to_json_error(e),
16-
};
17-
18-
let response_str = serde_json::to_string(&response).unwrap();
19-
let cstring = CString::new(response_str).expect("nul byte in response json");
20-
cstring.into_raw()
10+
pub extern "C" fn playgroundGetToolchains(err: &mut ExternError) -> *const c_char {
11+
call_with_result(err, || {
12+
list_toolchains()
13+
.map(|r| serde_json::to_string(&r).unwrap())
14+
})
2115
}
2216

2317
#[no_mangle]
2418
pub extern "C" fn playgroundExecuteTask(
2519
path: *const c_char,
2620
cmd_json: *const c_char,
2721
std_err_callback: extern "C" fn(*const c_char),
22+
err: &mut ExternError,
2823
) -> *const c_char {
29-
eprintln!("playground execute task");
30-
let path = unsafe { CStr::from_ptr(path) };
31-
let json = unsafe { CStr::from_ptr(cmd_json) };
32-
let path = Path::new(OsStr::from_bytes(path.to_bytes()));
33-
let json = json.to_str().expect("json must be valid utf8");
34-
let task: Task = serde_json::from_str(json).expect("malformed task json");
35-
let response = match do_compile_task(path, task, |stderr| {
36-
let cstring =
37-
CString::new(stderr).unwrap_or_else(|_| CString::new("null byte in stderr").unwrap());
38-
std_err_callback(cstring.as_ptr());
39-
}) {
40-
Ok(result) => json!({ "result": result }),
41-
Err(e) => to_json_error(e),
42-
};
43-
44-
let response_str = serde_json::to_string(&response).unwrap();
45-
let cstring = CString::new(response_str).expect("nul byte in response json");
46-
cstring.into_raw()
24+
call_with_result(err, || {
25+
eprintln!("playground execute task");
26+
let path = unsafe { CStr::from_ptr(path) };
27+
let json = unsafe { CStr::from_ptr(cmd_json) };
28+
let path = Path::new(OsStr::from_bytes(path.to_bytes()));
29+
let json = json.to_str().expect("json must be valid utf8");
30+
let task: Task = serde_json::from_str(json).expect("malformed task json");
31+
do_compile_task(path, task, |stderr| {
32+
let cstring = CString::new(stderr)
33+
.unwrap_or_else(|_| CString::new("null byte in stderr").unwrap());
34+
std_err_callback(cstring.as_ptr());
35+
})
36+
.map(|r| serde_json::to_string(&r).unwrap())
37+
})
4738
}
4839

4940
#[no_mangle]
@@ -56,12 +47,3 @@ pub extern "C" fn playgroundStringFree(ptr: *mut c_char) {
5647
CString::from_raw(ptr);
5748
}
5849
}
59-
60-
fn to_json_error(e: Error) -> serde_json::Value {
61-
json!({
62-
"error": {
63-
"message": e.to_string(),
64-
"code": e.error_code(),
65-
}
66-
})
67-
}

playground-utils/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
[package]
22
name = "playground-utils"
3-
version = "0.1.0"
3+
version = "0.2.0"
44
authors = ["Colin Rofls <[email protected]>"]
55
edition = "2018"
66

77
[dependencies]
88
dirs = "1.0"
9+
ffi-support = "0.3.4"
910
serde = "1.0"
1011
serde_derive = "1.0"
1112
semver = "0.9"

playground-utils/src/error.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use std::io;
33
use std::path::PathBuf;
44
use std::process::Output;
55

6+
use ffi_support::{ExternError, ErrorCode};
7+
68
#[derive(Debug)]
79
pub enum Error {
810
ToolchainParseError(String),
@@ -36,7 +38,7 @@ impl Error {
3638
/// An error code included in the json if this is sent to core.
3739
/// This is not systematic. We just want to be able to easily identify
3840
/// certain cases, such as when Rustup is not installed.
39-
pub fn error_code(&self) -> u32 {
41+
pub fn error_code(&self) -> i32 {
4042
use Error::*;
4143
match self {
4244
BadExit(_) => 1,
@@ -71,3 +73,10 @@ impl fmt::Display for Error {
7173
}
7274

7375
impl std::error::Error for Error {}
76+
77+
impl From<Error> for ExternError {
78+
fn from(e: Error) -> ExternError {
79+
let code = ErrorCode::new(e.error_code());
80+
ExternError::new_error(code, e.to_string())
81+
}
82+
}

0 commit comments

Comments
 (0)