This repository has been archived by the owner on Aug 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 51
feat(proxy): demo warp based REST API endpoints #250
Closed
Closed
Changes from 24 commits
Commits
Show all changes
81 commits
Select commit
Hold shift + click to select a range
fd37997
Bump proxy dependencies
xla 633ba40
Attempt first end-to-end draft
xla aa843ef
Move serialisation to http module
xla 20f9943
Merge branch 'master' into xla/220-warp-implementation
xla 7b45e45
Explore json response assertions
xla a195ce7
Pass state
xla ef919df
Add docs
xla da71cc0
Move project endpoints into own module
xla 695b7c9
Flesh out error responses
xla b9ffe18
Clean up
xla 1f78617
Show case create endpoint
xla ac73fe1
Implement project registration
xla dcad433
Merge branch 'master' into xla/220-warp-implementation
xla 2546e50
Test register
xla 41ec0fa
Implement list endpoint
xla b7e9d0d
Simplify test assertions
xla ee5fccf
Fix reference
xla 7a02e2f
Merge branch 'master' into xla/220-warp-implementation
xla 87ab5a7
Use String in error fields
xla 02c8c22
Remove explicit path end
xla dc6a174
Strings are strings are stirngs
xla 76b1737
Use stronger Url type in the core
xla e4f9a4c
Merge branch 'master' into xla/220-warp-implementation
xla b947b6a
Wire the api in main
xla c754fbf
Use 8080
xla 6e21def
Implement notification endpoint
xla e0a14a8
Wire up some rudimental logging
xla 6bc54c5
chore: document REST API
garbados db653de
Explore mildly how ts state management could look like
xla 0b84ddb
Merge branch 'master' into xla/220-warp-implementation
xla 1f398bb
Merge branch 'xla/220-warp-implementation' into xla/220-meditations-o…
xla 52f45f7
projects store
sarahscott a6a6bf1
messages defined outside of event
sarahscott e667f13
remote data type
sarahscott 238b611
simplify access to readable store
sarahscott fb9f3e9
modifications to RemoteDataStore
sarahscott fa8e53c
remove stray TODOs
sarahscott c7c205f
add another todo for myself
sarahscott 11cd5f7
added more todos
sarahscott 9322187
clean up RemoteDataStore
sarahscott bbd09ea
Merge branch 'master' into xla/220-warp-implementation
xla 5ee5a4e
Enable identities
xla 4b814ab
Clean up
xla 2f24ac7
Add missing docs
xla ce15339
Add transaction listing
xla 4ef7506
Add missing docs
xla a6703e1
Simplify API
xla d21b9a1
Merge branch 'master' into xla/220-warp-implementation
xla b437207
Set up linting for ts
xla e3d5ffe
Improve API access
xla ea0317b
Set cypress cache directory
xla be63d25
Attempt to fix CI
rudolfs 53769de
Revert "Attempt to fix CI"
rudolfs 9413bcd
Simplify event handling
xla cc5bbe4
Make event handling sound
xla daa7ccd
Pass through project stats
xla 437fc4d
Integrate OpenAPI documentation
xla 741d98e
Minimize docs clutter
xla fdb953a
Merge branch 'master' into xla/220-warp-implementation
xla 1ec2631
Expose GraphQL and REST API together
xla 54b4455
Merge branch 'master' into xla/220-warp-implementation
xla 4330574
Fix tests
xla 3948265
sourceBrowserStore & parameterized FileSource
sarahscott 500052a
simplify RevisionSelector
sarahscott 751e88e
update revision event
sarahscott 9a97fa7
simplifies FileList
sarahscott 351fc9a
update source path
sarahscott cf6c842
Implement first source browsing endpoint
xla ab3f0ed
Use string enum for object type documentation
xla ca4423f
Adjust types to match proxy
xla 06991c6
Make prettier check happy
rudolfs cece03b
Correct tye missmatch
xla 88e5854
Fix field
xla 17d6009
Implement commit endpoint
xla 9dd2b79
Implement tree endpoint
xla d381d07
Implement branches endpoint
xla 63d64e9
Implement tags endpoint
xla df138a0
Fix docs
xla 07c8538
Complete first circle of source browsing
xla ebcc13b
blob name and revision event fix
sarahscott f6c6fef
Merge branch 'master' into xla/220-warp-implementation
xla File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
//! HTTP API delivering JSON over `RESTish` endpoints. | ||
|
||
use librad::paths; | ||
use std::convert::Infallible; | ||
use std::sync::Arc; | ||
use tokio::sync::RwLock; | ||
use warp::{path, Filter}; | ||
|
||
use crate::registry; | ||
|
||
mod error; | ||
mod project; | ||
|
||
/// Main entry point for HTTP API. | ||
pub async fn run(librad_paths: paths::Paths, reg: registry::Registry) { | ||
let api = path("v1").and( | ||
project::filters(librad_paths.clone(), Arc::new(RwLock::new(reg))).recover(error::recover), | ||
); | ||
|
||
// TODO(xla): Pass down as configuration with sane defaults. | ||
warp::serve(api).run(([127, 0, 0, 1], 8090)).await; | ||
} | ||
|
||
/// State filter to expose the [`librad::paths::Paths`] to handlers. | ||
#[must_use] | ||
pub fn with_paths( | ||
paths: paths::Paths, | ||
) -> impl Filter<Extract = (paths::Paths,), Error = Infallible> + Clone { | ||
warp::any().map(move || paths.clone()) | ||
} | ||
|
||
/// State filter to expose the [`registry::Registry`] to handlers. | ||
#[must_use] | ||
pub fn with_registry( | ||
reg: Arc<RwLock<registry::Registry>>, | ||
) -> impl Filter<Extract = (Arc<RwLock<registry::Registry>>,), Error = Infallible> + Clone { | ||
warp::any().map(move || Arc::<RwLock<registry::Registry>>::clone(®)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
//! Recovery and conversion of [`error::Error`] to proper JSON responses, which expose variants | ||
//! for API consumers to act on. | ||
|
||
use std::convert::Infallible; | ||
use warp::http::StatusCode; | ||
use warp::{reject, reply, Rejection, Reply}; | ||
|
||
use crate::error; | ||
|
||
impl reject::Reject for error::Error {} | ||
|
||
impl From<error::Error> for Rejection { | ||
fn from(err: error::Error) -> Self { | ||
reject::custom(err) | ||
} | ||
} | ||
|
||
/// Error type to carry context for failed requests. | ||
#[derive(serde_derive::Serialize)] | ||
struct Error { | ||
/// Human readable message to convery error case. | ||
message: String, | ||
/// The triggered error variant. | ||
variant: String, | ||
} | ||
|
||
/// Handler to convert [`error::Error`] to [`Error`] response. | ||
pub async fn recover(err: Rejection) -> Result<impl Reply, Infallible> { | ||
let (code, variant, message) = { | ||
if err.is_not_found() { | ||
(StatusCode::NOT_FOUND, "NOT_FOUND", "Resource not found") | ||
} else if let Some(err) = err.find::<error::Error>() { | ||
match err { | ||
_ => { | ||
// TODO(xla): Match all variants and properly transform similar to | ||
// gaphql::error. | ||
(StatusCode::BAD_REQUEST, "BAD_REQUEST", "Incorrect input") | ||
}, | ||
} | ||
} else { | ||
( | ||
StatusCode::INTERNAL_SERVER_ERROR, | ||
"INTERNAL_ERROR", | ||
"Something went wrong", | ||
) | ||
} | ||
}; | ||
let res = reply::json(&Error { | ||
message: message.to_string(), | ||
variant: variant.to_string(), | ||
}); | ||
|
||
Ok(reply::with_status(res, code)) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use futures::stream::TryStreamExt; | ||
use pretty_assertions::assert_eq; | ||
use serde_json::{json, Value}; | ||
use warp::reply::Reply as _; | ||
use warp::Rejection; | ||
|
||
#[tokio::test] | ||
async fn recover_custom() { | ||
let have: Value = response(warp::reject::custom( | ||
crate::error::Error::InordinateString32(), | ||
)) | ||
.await; | ||
let want = json!({ | ||
"message": "Incorrect input", | ||
"variant": "BAD_REQUEST", | ||
}); | ||
|
||
assert_eq!(have, want); | ||
} | ||
|
||
#[tokio::test] | ||
async fn recover_not_found() { | ||
let have: Value = response(warp::reject::not_found()).await; | ||
let want = json!({ | ||
"message": "Resource not found", | ||
"variant": "NOT_FOUND", | ||
}); | ||
|
||
assert_eq!(have, want); | ||
} | ||
|
||
async fn response(err: Rejection) -> Value { | ||
let res = super::recover(err).await.unwrap(); | ||
|
||
let body = res | ||
.into_response() | ||
.body_mut() | ||
.try_fold(Vec::new(), |mut data, chunk| async move { | ||
data.extend_from_slice(&chunk); | ||
Ok(data) | ||
}) | ||
.await | ||
.unwrap(); | ||
|
||
serde_json::from_slice(&body).unwrap() | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be
path("api").and(path("v1"))
, that is,/api/v1
, in keeping with typical API (1)(2) namespacing.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think there is a general convention, best-practice here. Unless I missed an obvious standard body or something similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having
api
distinctly in the path makes sense if the server offers other endpoints as well. For our case the proxy is literally the API itself.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, how about telemetry or debugging/metrics endpoints? Won't we have those somewhere in there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hypothetically even those metrics and debugging endpoints would be namespaced under
/v1/
, as/v1/stats
in anticipation of a potential/v2/stats
or/v2/stats_but_different
.Mastodon and others use a
/api/v1/
path because they expose other non-API HTTP endpoints. Twitter putsapi
in the subdomain rather than the path because the responding server only handles an API, which is more like our use-case as the proxy is the API.