Skip to content

Instantly share code, notes, and snippets.

@chaoticsmol
Last active April 27, 2017 19:13
Show Gist options
  • Save chaoticsmol/6b62ff1212944bbdc8656f550c263e8a to your computer and use it in GitHub Desktop.
Save chaoticsmol/6b62ff1212944bbdc8656f550c263e8a to your computer and use it in GitHub Desktop.
Rust JSON API Client With Error Handling
[package]
name = "jsontest"
version = "0.1.0"
authors = ["Zack Mullaly <[email protected]>"]
[dependencies]
serde = "^1.0"
serde_json = "^1.0"
serde_derive = "^1.0"
package main
import (
"encoding/json"
"fmt"
"os"
)
// HasError helps us get at the error expected to be in a response.
type HasError interface {
Err() *APIError
}
// APIError contains information we expect to get in an error if one is present.
type APIError struct {
Code uint32 `json:"code"`
Kind string `json:"kind"`
Message string `json:"message"`
}
// TimePeriod is the data we hope to get back from an API endpoint.
// The `Finished` attribute is a pointer because it may be null.
type TimePeriod struct {
Started string
Finished *string
}
// APIExample is what we would deserialize a response to.
type APIExample struct {
Error *APIError `json:"error"`
Started *string `json:"started"`
Finished *string `json:"finished"`
}
// Err is implemented for APIExample so we can get at an error if one occurs.
func (a APIExample) Err() *APIError {
return a.Error
}
// IsNull is used to determine if an error is essentially "nil".
func (e APIError) IsNull() bool {
return e == APIError{}
}
func decodeError() APIError {
return APIError{
0,
"decode_err",
"Failed to decode input.",
}
}
// This is the kind of code that every API endpoint client function would have to contain.
// Beware of nil dereferences.
func exampleEndpt() (TimePeriod, APIError) {
input := os.Stdin
decoded := APIExample{}
decoder := json.NewDecoder(input)
err := decoder.Decode(&decoded)
if err != nil {
return TimePeriod{}, decodeError()
}
if decoded.Error != nil && !decoded.Error.IsNull() {
return TimePeriod{}, *decoded.Error
}
out := TimePeriod{}
if decoded.Started != nil {
out.Started = *decoded.Started
} else {
return TimePeriod{}, decodeError()
}
out.Finished = decoded.Finished
return out, APIError{}
}
// Demonstrates how a user of our Golang API library would call an endpoint function
func main() {
period, err := exampleEndpt()
if !err.IsNull() {
fmt.Printf("ERROR - %v\n", err)
} else {
fmt.Printf(
"SUCCESS - Period started at %s and finished at %v\n",
period.Started,
period.Finished)
}
}
extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
use std::io::{Read, stdin};
// This trait helps us get at the error expected to be in a response.
trait HasError {
fn error(&self) -> &Option<ApiError>;
}
/// Contains information we would expect to be in an en error if one's present.
#[derive(Debug, Clone, Deserialize)]
struct ApiError {
pub code: u32,
pub kind: String,
pub message: String,
}
/// The data we hope to get back from an API endpoint.
/// The `finished` attribute is `Option`al because it may be null.
struct TimePeriod {
pub started: String,
pub finished: Option<String>,
}
/// This is what our client function would return.
/// Each field is optional because they may either not be present or be null.
#[derive(Deserialize)]
struct ApiExample {
pub error: Option<ApiError>,
pub started: Option<String>,
pub finished: Option<String>,
}
impl HasError for ApiExample {
fn error(&self) -> &Option<ApiError> {
&self.error
}
}
fn decode_error() -> ApiError {
ApiError {
code: 0,
kind: "decode_err".to_owned(),
message: "Failed to decode input.".to_owned(),
}
}
/// A helper function that converts responses into more Rustic `Result`s.
fn decode_input<R, O>(input: R) -> Result<O, ApiError>
where R: Read,
O: HasError + serde::de::DeserializeOwned
{
let decoded: O = serde_json::from_reader(input).map_err(|_| decode_error())?;
if let &Some(ref error) = decoded.error() {
return Err(error.clone())
}
Ok(decoded)
}
// This is the kind of code we'd write in a client function.
fn example_endpt() -> Result<TimePeriod, ApiError> {
let input = stdin();
let decoded: Result<ApiExample, ApiError> = decode_input(input);
decoded.and_then(|period| match period {
ApiExample {started: Some(time), finished, .. } => Ok(TimePeriod {
started: time,
finished: finished,
}),
_ => Err(decode_error()),
})
}
fn main() {
let period = example_endpt();
match period {
Ok(period) => println!("SUCCESS - Period started at {} and finished at {:?}", period.started, period.finished),
Err(err) => println!("ERROR - {:?}", err),
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment