Skip to content
This repository has been archived by the owner on Jun 21, 2024. It is now read-only.

feat(flags): Basic flags service #31

Merged
merged 11 commits into from
May 7, 2024
Merged

feat(flags): Basic flags service #31

merged 11 commits into from
May 7, 2024

Conversation

neilkakkar
Copy link
Contributor

@neilkakkar neilkakkar commented May 1, 2024

Please be as brutal and nitty as you can be with the reviews, you'll help me learn rust faster :D

This is mostly a boiler plate copy from the capture endpoint which sets up a basic server + route responses. Most of this will change, will get refactored once I have the actual flags service running, but until then this seemed like a reasonable place to begin.

@neilkakkar neilkakkar marked this pull request as ready for review May 3, 2024 08:50
@neilkakkar neilkakkar requested review from Phanatic, jurajmajerik and a team May 3, 2024 08:51
// .allow_origin(AllowOrigin::mirror_request());

let router = Router::new()
.route("/flags", post(v0_endpoint::flags).get(v0_endpoint::flags))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

endpoint name tbd

shutdown: Arc<Notify>,
}

impl ServerHandle {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried not re-building this and just using axum-test, but ran into annoyances there around the IP extraction not working well, what capture does sans axum-test-helper indeed seems the most flexible + useful here 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

axum-test-helper is currently outdated and we switched to a fork but should move away from it anyway.
It's short enough to not bother trying to make it generic, let's just copy it as you did.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant this one: https://docs.rs/axum-test/latest/axum_test/ but indeed control over all the boilerplate here, given its not a lot is 👍

shutdown: Arc<Notify>,
}

impl ServerHandle {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

axum-test-helper is currently outdated and we switched to a fork but should move away from it anyway.
It's short enough to not bother trying to make it generic, let's just copy it as you did.

// TODO: Find if there's a better way around this.
// mockall got really annoying with async and results so I'm just gonna do my own
#[derive(Clone)]
pub struct MockRedisClient {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default, all cargo tests in a module run concurrently, so you indeed want isolation. Mocking in rust is hard, so I recommend using the real data stores from the compose stack:

  • for Redis, my recommendation is to have the RedisClient have a configurable key prefix (passed as a Option<String>) that it adds on all commands. This way, we can generate random prefixes and clean them on Drop. Happy to pair on that.
  • for PG, you should be able to get enough isolation by generating new teams?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I don't even need to go that far for redis, since all redis keys I care about are scoped by team tokens anyway, so generating a new team ought to be sufficient for both redis & pg.

Will dive into this more once I actually connect - but I do like your idea of not using these mocks - just copied this for now to get a feel for how capture does it, but indeed not too happy with these mocks right now.

// .allow_origin(AllowOrigin::mirror_request());

let router = Router::new()
.route("/flags", post(v0_endpoint::flags).get(v0_endpoint::flags))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you'll need to wire readiness, liveness and metrics too. Can be done later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Added to list here: PostHog/posthog#22131

@@ -28,7 +28,7 @@ pub struct ServerHandle {

impl ServerHandle {
pub async fn for_config(config: Config) -> ServerHandle {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let listener = TcpListener::bind("127.0.0.1:3001").await.unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Port zero is intentional, it tells the OS to assign a random port that's available, avoiding collision with other processes and other tests in parallel. That's why we then call let addr = listener.local_addr().unwrap(); to get the port to query.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whoops, great catch, thanks! I was trying something here, forgot to revert 🤦

tracing::Span::current().record("version", meta.version.clone());
tracing::Span::current().record("method", method.as_str());
tracing::Span::current().record("path", path.as_str().trim_end_matches('/'));
tracing::Span::current().record("ip", ip.to_string());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be useful to annotate the span with the distinct_id too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

absolutely, will clean this up, and add this once I add functionality to extract distinct id

.get("content-type")
.map_or("", |v| v.to_str().unwrap_or(""))
{
"application/x-www-form-urlencoded" => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this might be better off just using an if statement outside this match block with condition for application/json content-type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah good shout, since I'm not going to handle anything else in v1. Will do this in a follow up where I add all parsing req logic


let token = request.extract_and_verify_token()?;

tracing::Span::current().record("token", &token);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be the API token sent by the client? Maybe we can log the sha256 hash of the token instead of the actual token since that's PII.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, these are public tokens which determine which team to send data to - we want them to correlate data with the right team.

#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct FlagsResponse {
pub error_while_computing_flags: bool,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should consider making this a string and renaming the field to just error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.. to give client more info about the error?

This would usually only be a db error, so internal, not very useful to expose to clients.

For now I want the same shape as the existing endpoint, so will keep this as is

@neilkakkar neilkakkar merged commit 871441b into main May 7, 2024
4 checks passed
@neilkakkar neilkakkar deleted the flag-boilerplate branch May 7, 2024 20:13
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants