diff --git a/Cargo.lock b/Cargo.lock index b9743f2..34d742f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -421,6 +421,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "debug-ignore" version = "1.0.5" @@ -1099,6 +1134,12 @@ dependencies = [ "syn", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.0.3" @@ -1590,10 +1631,12 @@ dependencies = [ "serde", "slog", "slog-async", + "thiserror 2.0.7", "tokio", "trace-request", "tracing", "tracing-slog", + "validator", ] [[package]] @@ -1655,6 +1698,28 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -3027,6 +3092,36 @@ dependencies = [ "serde", ] +[[package]] +name = "validator" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 563c47d..5686785 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ semver = "1.0.24" serde = { version = "1.0.216", features = ["derive"] } slog = "2.7.0" slog-async = "2.8.0" +thiserror = "2" tokio = { version = "1.42.0", features = ["full"] } tonic = "0.12.3" tracing = "0.1.41" @@ -60,3 +61,4 @@ tracing-subscriber = { version = "0.3.19", default-features = false, features = "fmt", ] } uuid = { version = "1", features = [ "serde", "v4" ] } +validator = { version = "0.19", features = ["derive"] } diff --git a/controller/Cargo.toml b/controller/Cargo.toml index 629468d..64ebc93 100644 --- a/controller/Cargo.toml +++ b/controller/Cargo.toml @@ -15,10 +15,12 @@ schemars.workspace = true serde.workspace = true slog-async.workspace = true slog.workspace = true +thiserror.workspace = true tokio.workspace = true trace-request = { path = "../trace-request" } tracing-slog.workspace = true tracing.workspace = true +validator.workspace = true [package.metadata.cargo-machete] ignored = ["http"] diff --git a/controller/src/api.rs b/controller/src/api.rs index 1906567..ad49650 100644 --- a/controller/src/api.rs +++ b/controller/src/api.rs @@ -4,6 +4,7 @@ use dropshot::ApiDescription; use std::sync::Arc; use crate::context::ControllerContext; +use crate::onboard; use crate::version; type ControllerApiDescription = ApiDescription>; @@ -11,5 +12,6 @@ type ControllerApiDescription = ApiDescription>; pub fn api() -> Result { let mut api = ControllerApiDescription::new(); api.register(version::version)?; + api.register(onboard::onboard)?; Ok(api) } diff --git a/controller/src/lib.rs b/controller/src/lib.rs index 0caaf72..3a7b568 100644 --- a/controller/src/lib.rs +++ b/controller/src/lib.rs @@ -1,4 +1,5 @@ pub mod api; pub mod context; +mod onboard; mod version; diff --git a/controller/src/onboard.rs b/controller/src/onboard.rs new file mode 100644 index 0000000..fb36e99 --- /dev/null +++ b/controller/src/onboard.rs @@ -0,0 +1,76 @@ +use dropshot::{endpoint, HttpError, HttpResponseOk, RequestContext, TypedBody}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use thiserror::Error; +use trace_request::trace_request; +use validator::Validate; + +use std::result::Result; +use std::sync::Arc; + +use crate::context::ControllerContext; + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Validate)] +pub struct OnboardRequest { + #[validate(nested)] + ownership_voucher: OwnershipVoucher, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Validate)] +pub struct OwnershipVoucher { + #[validate(length(min = 3, max = 5))] + name: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +pub struct OnboardResponse { + result: String, +} + +impl OnboardRequest { + pub fn from(body: TypedBody) -> Result { + let req = body.into_inner(); + req.validate()?; + Ok(req) + } +} + +#[derive(Clone, Debug, Error)] +pub enum OnboardError { + #[error("Invalid ownership voucher: {0}")] + ValidationError(#[from] validator::ValidationErrors), +} + +impl From for HttpError { + fn from(e: OnboardError) -> Self { + match e { + OnboardError::ValidationError(msg) => HttpError::for_bad_request( + Some("ValidationError".to_string()), + msg.to_string(), + ), + } + } +} + +/// Onboard new device +#[endpoint { + method = POST, + path = "/onboard", +}] +#[trace_request] +pub(crate) async fn onboard( + rqctx: RequestContext>, + body: TypedBody, +) -> Result, HttpError> { + tracing::info_span!("Hello, onboard!"); + + let req = OnboardRequest::from(body)?; + + tracing::debug!("Got onboarding request: {:?}", req); + + let res = OnboardResponse { + result: "Välkommen ombord!".to_string(), + }; + + Ok(HttpResponseOk(res)) +}