Skip to content

Commit

Permalink
docs/Document everything (#52)
Browse files Browse the repository at this point in the history
* Document dependencies

* add bits to manifest

* clean up/unify imports and mods

* Document app

* Fix clippy warning in test helpers

* Fix warnings on generated files

* Add top-level comment for app mod

* Add basic docs to protobuf generated modules

* comment -> intra-doc link

* Feature documentation

* Documentation for utils

* docs/clean feedback

* refactor/unify naming and doc language

* cleanup/O->o

* cleanup/..->.

* docs/clarify user

* fix/doc typos

* refactor/get_rating->rating
  • Loading branch information
ZoopOTheGoop authored Jan 25, 2024
1 parent 62c4563 commit 501b14d
Show file tree
Hide file tree
Showing 44 changed files with 432 additions and 115 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name = "ratings"
version = "0.1.0"
edition = "2021"
rust-version = "1.69.0"
authors = ["Canonical"]
description = "Backend for ratings for the Ubuntu app center."
license = "GPL-3.0-only"

[dependencies]
dotenv = "0.15.0"
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ This is the code repository for **Ratings**, the backend service responsible for

For general details, including installation, getting started and setting up a development environment, head over to our section on [Contributing to the code](CONTRIBUTING.md#contributing-to-the-code).

## Dependencies

In order to run, this needs you to install at minimum `libssl-dev` (for OpenSSL) and `protobuf-compiler` (for `prost`) via `apt`.

## Get involved

This is an [open source](LICENSE) project and we warmly welcome community contributions, suggestions, and constructive feedback. If you're interested in contributing, please take a look at our [Contribution guidelines](CONTRIBUTING.md) first.
Expand Down
20 changes: 16 additions & 4 deletions src/app/context.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
//! Contains definitions for maintaining the current [`AppContext`].
use std::sync::Arc;

use crate::utils::jwt::Claims;
use crate::utils::Config;
use crate::utils::Infrastructure;
use crate::utils::{jwt::Claims, Config, Infrastructure};

/// An atomically reference counted app state.
#[derive(Debug, Clone)]
pub struct AppContext(Arc<AppContextInner>);

#[allow(dead_code)]
impl AppContext {
/// Builds a new [`AppContext`] from a user [`Config`] and [`Infrastructure`].
///
/// The [`Config`] will be cloned.
pub fn new(config: &Config, infra: Infrastructure) -> Self {
let inner = AppContextInner {
infra,
Expand All @@ -17,24 +21,32 @@ impl AppContext {
Self(Arc::new(inner))
}

/// A reference to the held [`Infrastructure`].
pub fn infrastructure(&self) -> &Infrastructure {
&self.0.infra
}

/// A reference to the held [`Config`].
pub fn config(&self) -> &Config {
&self.0.config
}
}

#[allow(dead_code)]
/// Contains the overall state and configuration of the app, only meant to be used
/// in terms of the [`AppContext`].
#[derive(Debug)]
struct AppContextInner {
/// Contains JWT and postgres infrastructure for the app.
infra: Infrastructure,
/// App configuration settings.
config: Config,
}

/// Contains the context for a given request
#[derive(Debug, Clone)]
pub struct RequestContext {
/// The URI this request is from.
pub uri: String,
/// If applicable, the associated JWT claims.
pub claims: Option<Claims>,
}
3 changes: 3 additions & 0 deletions src/app/interfaces/authentication.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! Contains the utilities for authorizing user requests
use http::header;
use tonic::{Request, Status};
use tracing::error;

use crate::app::{context::AppContext, RequestContext};

/// Authorizes the user in the request specified by `req`.
pub fn authentication(req: Request<()>) -> Result<Request<()>, Status> {
let app_ctx = req.extensions().get::<AppContext>().unwrap().clone();

Expand Down
3 changes: 3 additions & 0 deletions src/app/interfaces/errors.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Error implementations for the app interface.
use thiserror::Error;

/// A generic interface error, currently never constructed.
#[derive(Error, Debug)]
pub enum AppInterfaceError {
/// Something unpredictable went wrong
#[error("app interface: unknown error")]
#[allow(dead_code)]
Unknown,
Expand Down
29 changes: 27 additions & 2 deletions src/app/interfaces/middleware.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! The middleware layers for the app context.
use std::pin::Pin;

use hyper::{service::Service, Body};
Expand All @@ -6,19 +8,28 @@ use tower::Layer;

use crate::app::context::{AppContext, RequestContext};

/// Passthrough [`Layer`] containing the [`AppContext`], this is mainly used to construct
/// [`ContextMiddleware`].
#[derive(Clone)]
pub struct ContextMiddlewareLayer {
/// The wrapped context
app_ctx: AppContext,
}

impl ContextMiddlewareLayer {
/// Creates a new layer from the given [`AppContext`].
pub fn new(ctx: AppContext) -> ContextMiddlewareLayer {
ContextMiddlewareLayer { app_ctx: ctx }
}
}

impl<S> Layer<S> for ContextMiddlewareLayer {
impl<S> Layer<S> for ContextMiddlewareLayer
where
S: Service<hyper::Request<Body>, Response = hyper::Response<BoxBody>> + Clone + Send + 'static,
S::Future: Send + 'static,
{
type Service = ContextMiddleware<S>;

fn layer(&self, service: S) -> Self::Service {
ContextMiddleware {
app_ctx: self.app_ctx.clone(),
Expand All @@ -27,12 +38,26 @@ impl<S> Layer<S> for ContextMiddlewareLayer {
}
}

/// A [`Service`] which delegates responses to a request by the inner `S`,
/// which is itself a [`Service`], by calling the inner [`Future`].
///
/// [`Future`]: std::future::Future
#[derive(Clone)]
pub struct ContextMiddleware<S> {
pub struct ContextMiddleware<S>
where
S: Service<hyper::Request<Body>, Response = hyper::Response<BoxBody>> + Clone + Send + 'static,
S::Future: Send + 'static,
{
/// The current context of the app, as passed in from the [`ContextMiddlewareLayer`]
app_ctx: AppContext,

/// The inner [`Service`] containing the [`Future`].
///
/// [`Future`]: std::future::Future
inner: S,
}

/// A type definition which is simply a future that's in a pinned location in the heap.
type BoxFuture<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;

impl<S> Service<hyper::Request<Body>> for ContextMiddleware<S>
Expand Down
5 changes: 4 additions & 1 deletion src/app/interfaces/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
//! Contains submodules with various middleware and utility layers for the app.
pub mod authentication;
mod errors;
pub mod middleware;
pub mod routes;

mod errors;
14 changes: 9 additions & 5 deletions src/app/interfaces/routes.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! Contains definitions for service routers and related components.
use tonic::transport::server::Router;
use tonic_reflection::server::ServerReflection;
use tonic_reflection::server::{ServerReflection, ServerReflectionServer};

use crate::features::{app, chart, user};
use crate::features::{chart, rating, user};

pub fn build_reflection_service(
) -> tonic_reflection::server::ServerReflectionServer<impl ServerReflection> {
/// Creates a new default reflection server for this app
pub fn build_reflection_service() -> ServerReflectionServer<impl ServerReflection> {
let file_descriptor_set = tonic::include_file_descriptor_set!("ratings_descriptor");

tonic_reflection::server::Builder::configure()
Expand All @@ -13,9 +15,11 @@ pub fn build_reflection_service(
.unwrap()
}

/// Registers new services required to make the passed in [`Router`] work,
/// the [`Router`] won't be otherwise modified.
pub fn build_servers<R>(router: Router<R>) -> Router<R> {
let user_service = user::service::build_service();
let app_service = app::service::build_service();
let app_service = rating::service::build_service();
let chart_service = chart::service::build_service();

router
Expand Down
5 changes: 3 additions & 2 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use context::AppContext;
pub use context::RequestContext;
//! Contains all definitions of the main application interface part of the program.
pub use context::{AppContext, RequestContext};
pub use run::run;

mod context;
Expand Down
18 changes: 12 additions & 6 deletions src/app/run.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
//! Contains definitions for running the app context.
use std::{net::SocketAddr, time::Duration};

use crate::app::context::AppContext;
use tonic::transport::Server;
use tower::ServiceBuilder;
use tracing::info;

use crate::utils::{Config, Infrastructure, Migrator};

use super::interfaces::routes::{build_reflection_service, build_servers};
use super::interfaces::{authentication::authentication, middleware::ContextMiddlewareLayer};

use crate::{
app::context::AppContext,
app::interfaces::{
authentication::authentication,
middleware::ContextMiddlewareLayer,
routes::{build_reflection_service, build_servers},
},
utils::{Config, Infrastructure, Migrator},
};

/// Runs the app given the associated [`Config`].
pub async fn run(config: Config) -> Result<(), Box<dyn std::error::Error>> {
let migrator = Migrator::new(&config.migration_postgres_uri).await?;
migrator.run().await?;
Expand Down
5 changes: 0 additions & 5 deletions src/features/app/mod.rs

This file was deleted.

9 changes: 0 additions & 9 deletions src/features/app/service.rs

This file was deleted.

21 changes: 0 additions & 21 deletions src/features/app/use_cases.rs

This file was deleted.

24 changes: 22 additions & 2 deletions src/features/chart/entities.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
//! Contains struct definitions for the charting feature for ratings.
use crate::features::{
common::entities::{calculate_band, Rating, VoteSummary},
pb::chart as pb,
};
use sqlx::FromRow;

/// A chart over a given [`Timeframe`] with the given [`ChartData`].
///
/// [`Timeframe`]: pb::Timeframe
pub struct Chart {
/// The timeframe over which to display the data
pub timeframe: pb::Timeframe,
/// The raw chart data to display
pub chart_data: Vec<ChartData>,
}

impl Chart {
/// Creates a new [`Chart`] over the given [`Timeframe`]. The [`VoteSummary`] will be
/// interpreted as [`ChartData`].
///
/// If there are more than 20 elements, this will always truncate to the top 20.
///
/// [`Timeframe`]: pb::Timeframe
pub fn new(timeframe: pb::Timeframe, data: Vec<VoteSummary>) -> Self {
let mut chart_data: Vec<ChartData> =
data.into_iter().map(ChartData::from_vote_summary).collect();
Expand All @@ -30,13 +42,19 @@ impl Chart {
}
}

/// The actual information to be charted, contains a raw calculated value
/// as well as an overall [`Rating`] with more info struct.
#[derive(Debug, Clone, FromRow)]
pub struct ChartData {
/// The raw rating as it should be charted
pub raw_rating: f32,
/// A complex rating with the band, overall votes, and the snap this rating is for
pub rating: Rating,
}

impl ChartData {
/// Creates a single element of [`ChartData`] from a [`VoteSummary`] -- this calculates the overall
/// rating on demand.
pub fn from_vote_summary(vote_summary: VoteSummary) -> Self {
let (raw_rating, ratings_band) = calculate_band(&vote_summary);
let rating = Rating {
Expand All @@ -48,10 +66,12 @@ impl ChartData {
Self { raw_rating, rating }
}

pub fn into_dto(self) -> pb::ChartData {
/// Converts this [`ChartData`] into its wire version as defined in the
/// protobuf files for transmission.
pub fn into_protobuf_chart_data(self) -> pb::ChartData {
pb::ChartData {
raw_rating: self.raw_rating,
rating: Some(self.rating.into_dto()),
rating: Some(self.rating.into_protobuf_rating()),
}
}
}
11 changes: 9 additions & 2 deletions src/features/chart/errors.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
//! Contains errors for the [`Chart`] features
//!
//! [`Chart`]: crate::features::chart::entities::Chart
use thiserror::Error;

/// An error that can occur while using the chart service
#[derive(Error, Debug)]
pub enum ChartError {
/// Could not retrieve the chart
#[error("failed to get chart for timeframe")]
FailedToGetChart,
#[error("unknown chart error")]
Unknown,
/// There was no data in the given timeframe
#[error("could not find data for given timeframe")]
NotFound,
/// Another unknown error (e.g. network failure)
#[error("unknown chart error")]
Unknown,
}
5 changes: 5 additions & 0 deletions src/features/chart/infrastructure.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
//! Definitions for getting vote summaries for the [`Chart`] implementations
//!
//! [`Chart`]: crate::features::chart::entities::Chart
use crate::{
app::AppContext,
features::{chart::errors::ChartError, common::entities::VoteSummary, pb::chart::Timeframe},
};
use tracing::error;

/// Retrieves the vote summary in the given [`AppContext`] over a given [`Timeframe`]
/// from the database.
pub(crate) async fn get_votes_summary_by_timeframe(
app_ctx: &AppContext,
timeframe: Timeframe,
Expand Down
3 changes: 2 additions & 1 deletion src/features/chart/interface.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//! Contains trait implementations for the chart feature.
use crate::{
app::AppContext,
features::{
Expand Down Expand Up @@ -32,7 +33,7 @@ impl Chart for ChartService {
let ordered_chart_data = result
.chart_data
.into_iter()
.map(|chart_data| chart_data.into_dto())
.map(|chart_data| chart_data.into_protobuf_chart_data())
.collect();

let payload = GetChartResponse {
Expand Down
Loading

0 comments on commit 501b14d

Please sign in to comment.