diff --git a/.gitignore b/.gitignore index 7b56d05a..e8c4adb6 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,10 @@ corpus artifacts coverage +# OCI +junit.xml +report.html + # Nix result result-dev @@ -31,4 +35,3 @@ result-bin # Dir Env .direnv -g diff --git a/CHANGELOG.md b/CHANGELOG.md index df6fe6ae..6baad867 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Fleshed out hyper example. + ### Fixed - Router display no longer relies on generic being displayable. diff --git a/Cargo.lock b/Cargo.lock index 82b78c68..eca65c5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1360,6 +1360,7 @@ dependencies = [ "http-body-util", "hyper", "hyper-util", + "regex", "serde", "serde_json", "tokio", diff --git a/examples/hyper/Cargo.toml b/examples/hyper/Cargo.toml index 2f381e65..f42ff5fd 100644 --- a/examples/hyper/Cargo.toml +++ b/examples/hyper/Cargo.toml @@ -7,7 +7,6 @@ publish = false version.workspace = true authors.workspace = true edition.workspace = true -rust-version.workspace = true repository.workspace = true license.workspace = true keywords.workspace = true @@ -28,3 +27,4 @@ bytes = "1.7" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" anyhow = "1.0" +regex = "1.10" diff --git a/examples/hyper/README.md b/examples/hyper/README.md new file mode 100644 index 00000000..3677f702 --- /dev/null +++ b/examples/hyper/README.md @@ -0,0 +1,7 @@ +# `hyper` example + +This is a mini implementation of an [Open Container Initiative (OCI) Distribution Specification](https://github.com/opencontainers/distribution-spec/blob/main/spec.md) registry. + +## Inspirations: +- https://github.com/mcronce/oci-registry +- https://github.com/Trow-Registry/trow diff --git a/examples/hyper/src/main.rs b/examples/hyper/src/main.rs index a51b76a7..1388bad6 100644 --- a/examples/hyper/src/main.rs +++ b/examples/hyper/src/main.rs @@ -1,54 +1,28 @@ -#![allow(clippy::unused_async)] - -use bytes::Bytes; -use http_body_util::{combinators::BoxBody, BodyExt, Full}; -use hyper::{body::Incoming, header, service::service_fn, Request, Response, StatusCode}; +use hyper::{body::Incoming, service::service_fn, Request}; use hyper_util::{ rt::{TokioExecutor, TokioIo}, server::conn::auto::Builder, }; +use routes::router; use std::{ - convert::Infallible, - future::Future, net::{IpAddr, Ipv4Addr, SocketAddr}, - pin::Pin, sync::Arc, }; use tokio::{net::TcpListener, task::JoinSet}; -use wayfind::{Parameter, Path, Router}; - -type BoxFuture<'a> = Pin< - Box< - dyn Future>, anyhow::Error>> - + Send - + 'a, - >, ->; +use wayfind::Path; -type HandlerFn = - Arc Fn(&'a str, &'a [Parameter<'_, 'a>]) -> BoxFuture<'a> + Send + Sync>; +pub mod routes; #[tokio::main] async fn main() -> Result<(), anyhow::Error> { - let mut router: Router = Router::new(); - router.insert( - "/", - Arc::new(move |path, parameters| Box::pin(index_route(path, parameters))), - )?; - router.insert( - "/hello/{name}", - Arc::new(move |path, parameters| Box::pin(hello_route(path, parameters))), - )?; - router.insert( - "{*catch_all}", - Arc::new(move |path, parameters| Box::pin(not_found(path, parameters))), - )?; - let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 1337); let listener = TcpListener::bind(&socket).await?; println!("Listening on http://{socket}"); + let router = router()?; let router = Arc::new(router); + println!("Router: {router}"); + let mut join_set = JoinSet::new(); loop { @@ -81,53 +55,3 @@ async fn main() -> Result<(), anyhow::Error> { }); } } - -async fn index_route( - _: &'_ str, - _: &'_ [Parameter<'_, '_>], -) -> Result>, anyhow::Error> { - let json = serde_json::json!({ - "hello": "world" - }); - - let body = Full::new(Bytes::from(json.to_string())); - let response = Response::builder() - .header(header::CONTENT_TYPE, "application/json") - .body(body.boxed())?; - - Ok(response) -} - -async fn hello_route( - _: &'_ str, - parameters: &'_ [Parameter<'_, '_>], -) -> Result>, anyhow::Error> { - let name = parameters[0].value; - let json = serde_json::json!({ - "hello": name, - }); - - let body = Full::new(Bytes::from(json.to_string())); - let response = Response::builder() - .header(header::CONTENT_TYPE, "application/json") - .body(body.boxed())?; - - Ok(response) -} - -async fn not_found( - path: &'_ str, - _: &'_ [Parameter<'_, '_>], -) -> Result>, anyhow::Error> { - let json = serde_json::json!({ - "error": "route_not_found", - "route": path, - }); - - let body = Full::new(Bytes::from(json.to_string())); - let response = Response::builder() - .status(StatusCode::NOT_FOUND) - .body(body.boxed())?; - - Ok(response) -} diff --git a/examples/hyper/src/routes.rs b/examples/hyper/src/routes.rs new file mode 100644 index 00000000..e7d7d0cb --- /dev/null +++ b/examples/hyper/src/routes.rs @@ -0,0 +1,32 @@ +#![allow(clippy::unused_async)] + +use bytes::Bytes; +use http_body_util::combinators::BoxBody; +use hyper::Response; +use index::index_route; +use std::{convert::Infallible, future::Future, pin::Pin, sync::Arc}; +use wayfind::{errors::InsertError, Parameter, Router}; + +pub mod index; + +type BoxFuture<'a> = Pin< + Box< + dyn Future>, anyhow::Error>> + + Send + + 'a, + >, +>; + +type HandlerFn = + Arc Fn(&'a str, &'a [Parameter<'_, 'a>]) -> BoxFuture<'a> + Send + Sync>; + +pub fn router() -> Result, InsertError> { + let mut router: Router = Router::new(); + + router.insert( + "/", + Arc::new(move |path, parameters| Box::pin(index_route(path, parameters))), + )?; + + Ok(router) +} diff --git a/examples/hyper/src/routes/index.rs b/examples/hyper/src/routes/index.rs new file mode 100644 index 00000000..f8cb9b87 --- /dev/null +++ b/examples/hyper/src/routes/index.rs @@ -0,0 +1,21 @@ +use bytes::Bytes; +use http_body_util::{combinators::BoxBody, BodyExt, Full}; +use hyper::{header, Response}; +use std::convert::Infallible; +use wayfind::Parameter; + +pub async fn index_route( + _: &'_ str, + _: &'_ [Parameter<'_, '_>], +) -> Result>, anyhow::Error> { + let json = serde_json::json!({ + "hello": "world" + }); + + let body = Full::new(Bytes::from(json.to_string())); + let response = Response::builder() + .header(header::CONTENT_TYPE, "application/json") + .body(body.boxed())?; + + Ok(response) +} diff --git a/flake.nix b/flake.nix index 3265e1a9..5f177517 100644 --- a/flake.nix +++ b/flake.nix @@ -36,6 +36,7 @@ (self: super: { cargo-codspeed = pkgs.callPackage ./nix/pkgs/cargo-codspeed {}; cargo-insta = pkgs.callPackage ./nix/pkgs/cargo-insta {}; + oci-distribution-spec-conformance = pkgs.callPackage ./nix/pkgs/oci-distribution-spec-conformance {}; }) ]; }; @@ -70,6 +71,9 @@ # Release cargo-semver-checks + # OCI + oci-distribution-spec-conformance + # Nix alejandra statix diff --git a/nix/pkgs/oci-distribution-spec-conformance/default.nix b/nix/pkgs/oci-distribution-spec-conformance/default.nix new file mode 100644 index 00000000..8f86c608 --- /dev/null +++ b/nix/pkgs/oci-distribution-spec-conformance/default.nix @@ -0,0 +1,38 @@ +{ + lib, + buildGoModule, + fetchFromGitHub, +}: +buildGoModule rec { + pname = "oci-distribution-spec-conformance"; + version = "1.1.0"; + + src = fetchFromGitHub { + owner = "opencontainers"; + repo = "distribution-spec"; + rev = "v${version}"; + hash = "sha256-GL28YUwDRicxS65E7SDR/Q3tJOWN4iwgq4AGBjwVPzA="; + }; + + sourceRoot = "source/conformance"; + vendorHash = "sha256-5gn9RpjCALZB/GFjlJHDqPs2fIHl7NJr5QjPmsLnnO4="; + + CGO_ENABLED = 0; + + postInstall = '' + go test -c ./... -o oci-distribution-spec-conformance + mkdir -p $out/bin + mv oci-distribution-spec-conformance $out/bin + ''; + + doCheck = false; + + meta = with lib; { + description = " OCI Distribution Specification Conformance Tests"; + mainProgram = "oci-distribution-spec-conformance"; + homepage = "https://opencontainers.org"; + changelog = "https://github.com/opencontainers/distribution-spec/releases/tag/v${version}"; + license = licenses.asl20; + platforms = platforms.all; + }; +}