From 2872fbd63fb66acd20d4b57a4f47d77a60d5ea9f Mon Sep 17 00:00:00 2001 From: Cathal Mullan Date: Fri, 30 Aug 2024 02:53:55 +0100 Subject: [PATCH] Begin work on OCI example. --- .gitignore | 4 + Cargo.lock | 1 + README.md | 2 +- examples/hyper/Cargo.toml | 2 +- examples/hyper/README.md | 7 ++ examples/hyper/src/main.rs | 90 ++----------------- examples/hyper/src/routes.rs | 32 +++++++ examples/hyper/src/routes/index.rs | 21 +++++ flake.nix | 4 + .../default.nix | 38 ++++++++ src/node/display.rs | 71 +++++---------- src/router.rs | 2 +- 12 files changed, 137 insertions(+), 137 deletions(-) create mode 100644 examples/hyper/README.md create mode 100644 examples/hyper/src/routes.rs create mode 100644 examples/hyper/src/routes/index.rs create mode 100644 nix/pkgs/oci-distribution-spec-conformance/default.nix diff --git a/.gitignore b/.gitignore index 7b56d05a..9e92e3a2 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,10 @@ corpus artifacts coverage +# OCI +junit.xml +report.html + # Nix result result-dev 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/README.md b/README.md index e2f00a11..7b639669 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ fn main() -> Result<(), Box> { router.insert("/user/logout", 12)?; router.insert("/user/{username}", 13)?; - assert_eq!(router.to_string(), ROUTER_DISPLAY.trim_start()); + assert_eq!(router.to_string(), ROUTER_DISPLAY.trim()); Ok(()) } ``` 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 7750daec..61e302a1 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()?; + println!("Router:\n{router}"); let router = Arc::new(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; + }; +} diff --git a/src/node/display.rs b/src/node/display.rs index 3d4daa8a..dff81ab0 100644 --- a/src/node/display.rs +++ b/src/node/display.rs @@ -1,11 +1,11 @@ use super::Node; use crate::node::NodeKind; -use std::fmt::Display; +use std::fmt::{Display, Write}; impl Display for Node { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn debug_node( - f: &mut std::fmt::Formatter, + output: &mut String, node: &Node, padding: &str, is_root: bool, @@ -41,10 +41,10 @@ impl Display for Node { .map_or(String::new(), |_node_data| " [*]".to_string()); if is_root { - writeln!(f, "{key}")?; + writeln!(output, "{key}")?; } else { let branch = if is_last { "╰─" } else { "├─" }; - writeln!(f, "{padding}{branch} {key}{value}")?; + writeln!(output, "{padding}{branch} {key}{value}")?; } // Ensure we align children correctly @@ -57,63 +57,32 @@ impl Display for Node { format!("{padding}│ {extra_spacing}") }; - let has_dynamic_children = !node.dynamic_children.is_empty(); - let has_wildcard_children = !node.wildcard_children.is_empty(); - let has_end_wildcard = !node.end_wildcard_children.is_empty(); - - // Recursively print the static children - let static_count = node.static_children.len(); - for (index, child) in node.static_children.iter().enumerate() { - let is_last = if has_dynamic_children || has_wildcard_children || has_end_wildcard { - false - } else { - index == (static_count - 1) - }; - - debug_node(f, child, &new_prefix, false, is_last)?; - } - - // Recursively print dynamic children - let dynamic_count = node.dynamic_children.len(); - for (index, child) in node.dynamic_children.iter().enumerate() { - let is_last = if has_wildcard_children || has_end_wildcard { - false - } else { - index == (dynamic_count - 1) - }; - - debug_node(f, child, &new_prefix, false, is_last)?; - } - - // Recursively print wildcard children - let wildcard_count = node.wildcard_children.len(); - for (index, child) in node.wildcard_children.iter().enumerate() { - let is_last = if has_end_wildcard { - false - } else { - index == (wildcard_count - 1) - }; - - debug_node(f, child, &new_prefix, false, is_last)?; - } - - // Recursively print end wildcard children - let end_wildcard_count = node.end_wildcard_children.len(); - for (index, child) in node.end_wildcard_children.iter().enumerate() { - let is_last = index == (end_wildcard_count - 1); - debug_node(f, child, &new_prefix, false, is_last)?; + // Chain all children together, in order + let all_children = node + .static_children + .iter() + .chain(node.dynamic_children.iter()) + .chain(node.wildcard_children.iter()) + .chain(node.end_wildcard_children.iter()); + + let total_children = all_children.clone().count(); + for (index, child) in all_children.enumerate() { + let is_last = index == total_children - 1; + debug_node(output, child, &new_prefix, false, is_last)?; } Ok(()) } + let mut output = String::new(); + let padding = if self.prefix.is_empty() { String::new() } else { " ".repeat(self.prefix.len() - 1) }; - debug_node(f, self, &padding, true, true)?; - Ok(()) + debug_node(&mut output, self, &padding, true, true)?; + write!(f, "{}", output.trim_end()) } } diff --git a/src/router.rs b/src/router.rs index 40322858..90c18fe3 100644 --- a/src/router.rs +++ b/src/router.rs @@ -238,7 +238,7 @@ impl Default for Router { } } -impl Display for Router { +impl Display for Router { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.root) }