From c3639a4139fbc982e8b18697fbe6c14bb1c8e779 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Fri, 9 Dec 2022 07:14:20 -0500 Subject: [PATCH] Reorganize UUID project --- .github/workflows/release-rust.yaml | 6 +- .github/workflows/validation-rust.yaml | 5 +- udf-jsonify/Cargo.toml | 2 +- udf-lipsum/Cargo.toml | 2 +- udf-uuid/Cargo.toml | 2 +- udf-uuid/src/generate.rs | 120 ++++++++++ udf-uuid/src/lib.rs | 316 ++----------------------- udf-uuid/src/namespaces.rs | 126 ++++++++++ udf-uuid/src/valid.rs | 81 +++++++ 9 files changed, 351 insertions(+), 309 deletions(-) create mode 100644 udf-uuid/src/generate.rs create mode 100644 udf-uuid/src/namespaces.rs create mode 100644 udf-uuid/src/valid.rs diff --git a/.github/workflows/release-rust.yaml b/.github/workflows/release-rust.yaml index c660c23..ce1ea96 100644 --- a/.github/workflows/release-rust.yaml +++ b/.github/workflows/release-rust.yaml @@ -20,7 +20,6 @@ jobs: - build: linux os: ubuntu-latest target: x86_64-unknown-linux-gnu - # extension: '' # Unsuccessful compilation; try on local # - build: linux-arm # os: ubuntu-latest @@ -33,12 +32,11 @@ jobs: - build: windows-msvc os: windows-latest target: x86_64-pc-windows-msvc - # extension: .exe env: CARGO: cargo TARGET_DIR: ./target TARGET_FLAGS: "" - + MYSQLCLIENT_LIB_DIR: C:\mysql\lib steps: # Retreive git files - uses: actions/checkout@v2 @@ -141,7 +139,7 @@ jobs: mkdir "$staging" # Remove the "unreleased" section from our changelog - perl -0777 -i -pe "s/\n*(<\!-- next-header -->.*## \[Unreleased\].*?\n)(?=\n*^## )//gms" CHANGELOG.md + perl -0777 -i -pe "s/\n*(<\!-- next-header -->.*## \[Unreleased\].*?\n)(?:## |<\!--)//gms" CHANGELOG.md cp {README.md,LICENSE,CHANGELOG.md} "$staging/" diff --git a/.github/workflows/validation-rust.yaml b/.github/workflows/validation-rust.yaml index f34c6f2..23ac134 100644 --- a/.github/workflows/validation-rust.yaml +++ b/.github/workflows/validation-rust.yaml @@ -52,7 +52,6 @@ jobs: - build: linux os: ubuntu-latest target: x86_64-unknown-linux-musl - # extension: '' # - build: macos # os: macos-latest # target: x86_64-apple-darwin @@ -61,8 +60,10 @@ jobs: os: windows-latest target: x86_64-pc-windows-msvc - name: "Test on ${{ matrix.name }} (cargo test)" + name: "Test on ${{ matrix.os }} (cargo test)" runs-on: ${{ matrix.os }} + env: + MYSQLCLIENT_LIB_DIR: C:\mysql\lib steps: - uses: actions/checkout@v2 - name: List files diff --git a/udf-jsonify/Cargo.toml b/udf-jsonify/Cargo.toml index 9f006f5..43233f3 100644 --- a/udf-jsonify/Cargo.toml +++ b/udf-jsonify/Cargo.toml @@ -8,5 +8,5 @@ publish = false crate-type = ["cdylib"] [dependencies] -udf = { version = "0.4.2", features = ["mock"] } +udf = { version = "0.4.4", features = ["mock"] } serde_json = "1.0" diff --git a/udf-lipsum/Cargo.toml b/udf-lipsum/Cargo.toml index d7f5e1f..2cd6623 100644 --- a/udf-lipsum/Cargo.toml +++ b/udf-lipsum/Cargo.toml @@ -8,5 +8,5 @@ publish = false crate-type = ["cdylib"] [dependencies] -udf = { version = "0.4.2", features = ["mock"] } +udf = { version = "0.4.4", features = ["mock"] } lipsum = "0.8.2" diff --git a/udf-uuid/Cargo.toml b/udf-uuid/Cargo.toml index f99210b..7dea08d 100644 --- a/udf-uuid/Cargo.toml +++ b/udf-uuid/Cargo.toml @@ -8,7 +8,7 @@ publish = false crate-type = ["cdylib"] [dependencies] -udf = { version = "0.4.2", features = ["mock"] } +udf = { version = "0.4.4", features = ["mock"] } uuid = { version = "1.2.1", features = ["v1", "v3", "v4", "v5", "fast-rng"] } mac_address = "1.1.4" rand = "0.8.5" diff --git a/udf-uuid/src/generate.rs b/udf-uuid/src/generate.rs new file mode 100644 index 0000000..f762699 --- /dev/null +++ b/udf-uuid/src/generate.rs @@ -0,0 +1,120 @@ +use mac_address::get_mac_address; +use udf::prelude::*; +use uuid::Uuid; + +use crate::validate_arg_count; + +#[derive(Debug)] +struct UuidGenerateV1 { + /// We save the mac address during the `init` call because that won't change. + /// Saves a few ms, maybe + mac: [u8; 6], +} + +#[register] +impl BasicUdf for UuidGenerateV1 { + type Returns<'a> = String; + + fn init(_cfg: &UdfCfg, args: &ArgList) -> Result { + validate_arg_count(args.len(), 0, "uuid_generate_v1")?; + + Ok(Self { + mac: get_mac_address() + .ok() + .flatten() + .map(|m| m.bytes()) + .unwrap_or([0u8; 6]), + }) + } + + fn process<'a>( + &'a mut self, + _cfg: &UdfCfg, + _args: &ArgList, + _error: Option, + ) -> Result, ProcessError> { + // Try to get the mac address; just return zeroes if there are any issues + Ok(Uuid::now_v1(&self.mac).as_hyphenated().to_string()) + } +} + +// /// V1 UUID with specified MAC address +// struct UuidGenerateV1arg; + +// #[register] +// impl BasicUdf for UuidGenerateV1arg { +// type Returns<'a> = String; + +// fn init(_cfg: &UdfCfg, args: &ArgList) -> Result { +// if args.len() != 1 { +// Err(format!( +// "uuid_generate_v1arg takes 1 argument but got {}", +// args.len() +// )) +// } else { +// args.get(0).unwrap().set_type_coercion(SqlType::String); +// Ok(Self) +// } +// } + +// fn process<'a>( +// &'a mut self, +// _cfg: &UdfCfg, +// _args: &ArgList, +// _error: Option, +// ) -> Result, ProcessError> { + +// Ok(Uuid::now_v1(&fake_mac).as_hyphenated().to_string()) +// } +// } + +/// V1 UUID with randomized MAC address +#[derive(Debug)] +struct UuidGenerateV1mc; + +#[register] +impl BasicUdf for UuidGenerateV1mc { + type Returns<'a> = String; + + fn init(_cfg: &UdfCfg, args: &ArgList) -> Result { + validate_arg_count(args.len(), 0, "uuid_generate_v1mc")?; + Ok(Self) + } + + fn process<'a>( + &'a mut self, + _cfg: &UdfCfg, + _args: &ArgList, + _error: Option, + ) -> Result, ProcessError> { + let mut fake_mac: [u8; 6] = rand::random(); + + // magic bits for multicast address + fake_mac[0..=3].copy_from_slice(&[0x01u8, 0x00, 0x5e]); + + Ok(Uuid::now_v1(&fake_mac).as_hyphenated().to_string()) + } +} + +/// V4 (completely random) UUID +#[derive(Debug)] +struct UuidGenerateV4; + +#[register] +impl BasicUdf for UuidGenerateV4 { + type Returns<'a> = String; + + fn init(_cfg: &UdfCfg, args: &ArgList) -> Result { + validate_arg_count(args.len(), 0, "uuid_generate_v4")?; + Ok(Self) + } + + fn process<'a>( + &'a mut self, + _cfg: &UdfCfg, + _args: &ArgList, + _error: Option, + ) -> Result, ProcessError> { + Ok(Uuid::new_v4().as_hyphenated().to_string()) + } +} diff --git a/udf-uuid/src/lib.rs b/udf-uuid/src/lib.rs index 563b551..422908d 100644 --- a/udf-uuid/src/lib.rs +++ b/udf-uuid/src/lib.rs @@ -1,305 +1,21 @@ -// TODO: v3, v5 +pub mod generate; +pub mod namespaces; +pub mod valid; -use mac_address::get_mac_address; -// use rand::Rng; -use udf::prelude::*; -use uuid::Uuid; - -/// -struct UuidGenerateV1 { - /// We save the mac address during the `init` call because that won't change. - /// Saves a few ms, maybe - mac: [u8; 6], -} - -#[register] -impl BasicUdf for UuidGenerateV1 { - type Returns<'a> = String; - - fn init(_cfg: &UdfCfg, args: &ArgList) -> Result { - if !args.is_empty() { - Err(format!( - "uuid_generate_v1 takes 0 arguments but got {}", - args.len() - )) - } else { - Ok(Self { - mac: get_mac_address() - .ok() - .flatten() - .map(|m| m.bytes()) - .unwrap_or([0u8; 6]), - }) - } - } - - fn process<'a>( - &'a mut self, - _cfg: &UdfCfg, - _args: &ArgList, - _error: Option, - ) -> Result, ProcessError> { - // Try to get the mac address; just return zeroes if there are any issues - Ok(Uuid::now_v1(&self.mac).as_hyphenated().to_string()) - } -} - -/// V1 UUID with randomized MAC address -struct UuidGenerateV1mc; - -#[register] -impl BasicUdf for UuidGenerateV1mc { - type Returns<'a> = String; - - fn init(_cfg: &UdfCfg, args: &ArgList) -> Result { - if !args.is_empty() { - Err(format!( - "uuid_generate_v1mc takes 0 arguments but got {}", - args.len() - )) - } else { - Ok(Self) - } - } - - fn process<'a>( - &'a mut self, - _cfg: &UdfCfg, - _args: &ArgList, - _error: Option, - ) -> Result, ProcessError> { - let mut fake_mac: [u8; 6] = rand::random(); - - // magic bits for multicast address - fake_mac[0..=3].copy_from_slice(&[0x01u8, 0x00, 0x5e]); - - Ok(Uuid::now_v1(&fake_mac).as_hyphenated().to_string()) - } -} - -// /// V1 UUID with randomized MAC address -// struct UuidGenerateV1arg; - -// #[register] -// impl BasicUdf for UuidGenerateV1arg { -// type Returns<'a> = String; - -// fn init(_cfg: &UdfCfg, args: &ArgList) -> Result { -// if args.len() != 1 { -// Err(format!( -// "uuid_generate_v1arg takes 1 argument but got {}", -// args.len() -// )) -// } else { -// args.get(0).unwrap().set_type_coercion(SqlType::String); -// Ok(Self) -// } -// } - -// fn process<'a>( -// &'a mut self, -// _cfg: &UdfCfg, -// _args: &ArgList, -// _error: Option, -// ) -> Result, ProcessError> { - -// Ok(Uuid::now_v1(&fake_mac).as_hyphenated().to_string()) -// } -// } - -struct UuidGenerateV4; - -#[register] -impl BasicUdf for UuidGenerateV4 { - type Returns<'a> = String; - - fn init(_cfg: &UdfCfg, args: &ArgList) -> Result { - if !args.is_empty() { - Err(format!( - "uuid_generate_v4 takes 0 arguments but got {}", - args.len() - )) - } else { - Ok(Self) - } - } - - fn process<'a>( - &'a mut self, - _cfg: &UdfCfg, - _args: &ArgList, - _error: Option, - ) -> Result, ProcessError> { - Ok(Uuid::new_v4().as_hyphenated().to_string()) - } -} - -// ** Namespace helpers ** - -/// Empty UUID -struct UuidNil; - -#[register] -impl BasicUdf for UuidNil { - type Returns<'a> = String; - - fn init(cfg: &UdfCfg, args: &ArgList) -> Result { - if !args.is_empty() { - Err(format!("uuid_nil takes 0 arguments but got {}", args.len())) - } else { - cfg.set_is_const(true); - Ok(Self) - } - } - - fn process<'a>( - &'a mut self, - _cfg: &UdfCfg, - _args: &ArgList, - _error: Option, - ) -> Result, ProcessError> { - Ok(Uuid::nil().as_hyphenated().to_string()) - } -} - -struct UuidNsDns; - -#[register] -impl BasicUdf for UuidNsDns { - type Returns<'a> = String; - - fn init(cfg: &UdfCfg, args: &ArgList) -> Result { - if !args.is_empty() { - Err(format!( - "uuid_ns_dns takes 0 arguments but got {}", - args.len() - )) +/// Validate arg count; return a formatted message if not +pub fn validate_arg_count(count: usize, expected: usize, fn_name: &str) -> Result<(), String> { + if count != expected { + let pluralized = if expected == 1 { + "argument" } else { - cfg.set_is_const(true); - Ok(Self) - } - } + "arguments" + }; - fn process<'a>( - &'a mut self, - _cfg: &UdfCfg, - _args: &ArgList, - _error: Option, - ) -> Result, ProcessError> { - Ok(Uuid::NAMESPACE_DNS.as_hyphenated().to_string()) + Err(format!( + "{} takes {} {} but got {}", + fn_name, expected, pluralized, count + )) + } else { + Ok(()) } } - -struct UuidNsUrl; - -#[register] -impl BasicUdf for UuidNsUrl { - type Returns<'a> = String; - - fn init(cfg: &UdfCfg, args: &ArgList) -> Result { - if !args.is_empty() { - Err(format!( - "uuid_ns_url takes 0 arguments but got {}", - args.len() - )) - } else { - cfg.set_is_const(true); - Ok(Self) - } - } - - fn process<'a>( - &'a mut self, - _cfg: &UdfCfg, - _args: &ArgList, - _error: Option, - ) -> Result, ProcessError> { - Ok(Uuid::NAMESPACE_URL.as_hyphenated().to_string()) - } -} - -struct UuidNsOid; - -#[register] -impl BasicUdf for UuidNsOid { - type Returns<'a> = String; - - fn init(cfg: &UdfCfg, args: &ArgList) -> Result { - if !args.is_empty() { - Err(format!( - "uuid_ns_oid takes 0 arguments but got {}", - args.len() - )) - } else { - cfg.set_is_const(true); - Ok(Self) - } - } - - fn process<'a>( - &'a mut self, - _cfg: &UdfCfg, - _args: &ArgList, - _error: Option, - ) -> Result, ProcessError> { - Ok(Uuid::NAMESPACE_OID.as_hyphenated().to_string()) - } -} - -struct UuidNsX500; - -#[register] -impl BasicUdf for UuidNsX500 { - type Returns<'a> = String; - - fn init(cfg: &UdfCfg, args: &ArgList) -> Result { - if !args.is_empty() { - Err(format!( - "uuid_ns_x500 takes 0 arguments but got {}", - args.len() - )) - } else { - cfg.set_is_const(true); - Ok(Self) - } - } - - fn process<'a>( - &'a mut self, - _cfg: &UdfCfg, - _args: &ArgList, - _error: Option, - ) -> Result, ProcessError> { - Ok(Uuid::NAMESPACE_X500.as_hyphenated().to_string()) - } -} - -// struct UuidIsValid; - -// #[register] -// impl BasicUdf for UuidIsValid { -// type Returns<'a> = i64; - -// fn init(_cfg: &UdfCfg, args: &ArgList) -> Result { -// if args.len() != 1 { -// Err(format!( -// "uuid_generate_v1 takes 1 arguments but got {}", -// args.len() -// )) -// } else { -// args.get(0).unwrap().set_type_coercion(SqlType::String); -// Ok(Self) -// } -// } - -// fn process<'a>( -// &'a mut self, -// _cfg: &UdfCfg, -// args: &ArgList, -// _error: Option, -// ) -> Result, ProcessError> { -// let arg = args.get(0).unwrap().value().as_string().unwrap(); - -// // Ok(Uuid::now_v1(&self.mac).as_hyphenated().to_string()) -// } -// } diff --git a/udf-uuid/src/namespaces.rs b/udf-uuid/src/namespaces.rs new file mode 100644 index 0000000..e199af5 --- /dev/null +++ b/udf-uuid/src/namespaces.rs @@ -0,0 +1,126 @@ +//! UUID namespaces (const) + +use udf::prelude::*; +use uuid::Uuid; + +use crate::validate_arg_count; + +/// Empty UUID +#[derive(Debug, PartialEq)] +struct UuidNil; + +#[register] +impl BasicUdf for UuidNil { + type Returns<'a> = String; + + fn init(cfg: &UdfCfg, args: &ArgList) -> Result { + validate_arg_count(args.len(), 0, "uuid_nil")?; + cfg.set_is_const(true); + Ok(Self) + } + + fn process<'a>( + &'a mut self, + _cfg: &UdfCfg, + _args: &ArgList, + _error: Option, + ) -> Result, ProcessError> { + Ok(Uuid::nil().as_hyphenated().to_string()) + } +} + +/// DNS namespace UUID +#[derive(Debug, PartialEq)] +struct UuidNsDns; + +#[register] +impl BasicUdf for UuidNsDns { + type Returns<'a> = String; + + fn init(cfg: &UdfCfg, args: &ArgList) -> Result { + validate_arg_count(args.len(), 0, "uuid_ns_dns")?; + cfg.set_is_const(true); + Ok(Self) + } + + fn process<'a>( + &'a mut self, + _cfg: &UdfCfg, + _args: &ArgList, + _error: Option, + ) -> Result, ProcessError> { + Ok(Uuid::NAMESPACE_DNS.as_hyphenated().to_string()) + } +} + +/// URL namespace UUID +#[derive(Debug, PartialEq)] +struct UuidNsUrl; + +#[register] +impl BasicUdf for UuidNsUrl { + type Returns<'a> = String; + + fn init(cfg: &UdfCfg, args: &ArgList) -> Result { + validate_arg_count(args.len(), 0, "uuid_ns_url")?; + cfg.set_is_const(true); + Ok(Self) + } + + fn process<'a>( + &'a mut self, + _cfg: &UdfCfg, + _args: &ArgList, + _error: Option, + ) -> Result, ProcessError> { + Ok(Uuid::NAMESPACE_URL.as_hyphenated().to_string()) + } +} + +/// OID namespace UUID +#[derive(Debug, PartialEq)] +struct UuidNsOid; + +#[register] +impl BasicUdf for UuidNsOid { + type Returns<'a> = String; + + fn init(cfg: &UdfCfg, args: &ArgList) -> Result { + validate_arg_count(args.len(), 0, "uuid_ns_oid")?; + cfg.set_is_const(true); + Ok(Self) + } + + fn process<'a>( + &'a mut self, + _cfg: &UdfCfg, + _args: &ArgList, + _error: Option, + ) -> Result, ProcessError> { + Ok(Uuid::NAMESPACE_OID.as_hyphenated().to_string()) + } +} + +/// X.500 namespace UUID +#[derive(Debug, PartialEq)] +struct UuidNsX500; + +#[register] +impl BasicUdf for UuidNsX500 { + type Returns<'a> = String; + + fn init(cfg: &UdfCfg, args: &ArgList) -> Result { + validate_arg_count(args.len(), 0, "uuid_ns_x500")?; + cfg.set_is_const(true); + Ok(Self) + } + + fn process<'a>( + &'a mut self, + _cfg: &UdfCfg, + _args: &ArgList, + _error: Option, + ) -> Result, ProcessError> { + Ok(Uuid::NAMESPACE_X500.as_hyphenated().to_string()) + } +} diff --git a/udf-uuid/src/valid.rs b/udf-uuid/src/valid.rs new file mode 100644 index 0000000..91ea4ce --- /dev/null +++ b/udf-uuid/src/valid.rs @@ -0,0 +1,81 @@ +use udf::prelude::*; +use uuid::Uuid; + +use crate::validate_arg_count; + +/// Check if a given UUID is valid +#[derive(Debug, PartialEq)] +struct UuidIsValid; + +#[register] +impl BasicUdf for UuidIsValid { + type Returns<'a> = i64; + + fn init(_cfg: &UdfCfg, args: &ArgList) -> Result { + validate_arg_count(args.len(), 1, "uuid_is_valid")?; + args.get(0).unwrap().set_type_coercion(SqlType::String); + Ok(Self) + } + + fn process<'a>( + &'a mut self, + _cfg: &UdfCfg, + args: &ArgList, + _error: Option, + ) -> Result, ProcessError> { + let input = args.get(0).unwrap().value(); + let input = input.as_string().unwrap().replace('-', ""); // Remove hyphens + let res = match Uuid::try_parse(&input) { + Ok(_) => 1, + Err(_) => 0, + }; + + Ok(res) + } +} + +#[cfg(test)] +mod tests { + use udf::mock::*; + + use super::*; + + #[test] + fn test_validate_wrong_args() { + // Test with 0 and >1 args + let mut arglists = [ + (mock_args![], 0), + (mock_args![("", "", false), ("", "", false)], 2), + ]; + + let mut cfg = MockUdfCfg::new(); + + for (args, count) in arglists.iter_mut() { + let res = UuidIsValid::init(cfg.as_init(), args.as_init()); + assert_eq!( + res, + Err(format!("uuid_is_valid takes 1 argument but got {}", count)) + ); + } + } + + #[test] + fn test_validate() { + // Test with 0 and >1 args + let mut arglists = [ + (mock_args![("00908d94-c78d-4ea5-8aa5-5a06868f0420", "", false)], 1), + (mock_args![("00908d94c78d4ea58aa55a06868f0420", "", false)], 1), + (mock_args![("00908d94c78d-4ea5-8aa55a06868f0420", "", false)], 1), + (mock_args![("00908d94-c78d-4ea5-8aa5-5a06868f042", "", false)], 0), + (mock_args![("00908d94c78d4ea58aa55a06868f042", "", false)], 0), + ]; + + let mut cfg = MockUdfCfg::new(); + let mut initialized = UuidIsValid::init(cfg.as_init(), arglists[0].0.as_init()).unwrap(); + + for (args, val) in arglists.iter_mut() { + let res = initialized.process(cfg.as_process(), args.as_process(), None).unwrap(); + assert_eq!(res, *val); + } + } +}