Skip to content

Commit

Permalink
Add JSON validator sample for WinRT (#2824)
Browse files Browse the repository at this point in the history
  • Loading branch information
kennykerr authored Jan 31, 2024
1 parent cfe0541 commit ccfc98a
Show file tree
Hide file tree
Showing 14 changed files with 578 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .github/workflows/clippy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ jobs:
cargo clippy -p sample_com_uri &&
cargo clippy -p sample_component_hello_world &&
cargo clippy -p sample_component_json_validator &&
cargo clippy -p sample_component_json_validator_winrt &&
cargo clippy -p sample_component_json_validator_winrt_client &&
cargo clippy -p sample_consent &&
cargo clippy -p sample_core_app &&
cargo clippy -p sample_counter &&
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ jobs:
cargo test -p sample_com_uri &&
cargo test -p sample_component_hello_world &&
cargo test -p sample_component_json_validator &&
cargo test -p sample_component_json_validator_winrt &&
cargo test -p sample_component_json_validator_winrt_client &&
cargo test -p sample_consent &&
cargo test -p sample_core_app &&
cargo test -p sample_counter &&
Expand Down Expand Up @@ -102,8 +104,8 @@ jobs:
cargo test -p test_deprecated &&
cargo test -p test_dispatch &&
cargo test -p test_does_not_return &&
cargo test -p test_enums &&
cargo clean &&
cargo test -p test_enums &&
cargo test -p test_error &&
cargo test -p test_event &&
cargo test -p test_extensions &&
Expand Down
27 changes: 27 additions & 0 deletions crates/samples/components/json_validator_winrt/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "sample_component_json_validator_winrt"
version = "0.0.0"
edition = "2021"
publish = false

[lib]
name = "sample"
crate-type = ["cdylib"]

[dependencies]
jsonschema = "0.17"
serde_json = "1.0"

[dependencies.windows]
path = "../../../libs/windows"
features = [
"implement",
"Win32_Foundation",
"Win32_System_WinRT",
]

[dependencies.windows-core]
path = "../../../libs/core"

[build-dependencies.windows-bindgen]
path = "../../../libs/bindgen"
37 changes: 37 additions & 0 deletions crates/samples/components/json_validator_winrt/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
fn main() {
println!("cargo:rerun-if-changed=src/sample.idl");
let metadata_dir = format!("{}\\System32\\WinMetadata", env!("windir"));
let mut command = std::process::Command::new("midlrt.exe");

command.args([
"/winrt",
"/nomidl",
"/h",
"nul",
"/metadata_dir",
&metadata_dir,
"/reference",
&format!("{metadata_dir}\\Windows.Foundation.winmd"),
"/winmd",
"sample.winmd",
"src/sample.idl",
]);

if !command.status().unwrap().success() {
panic!("Failed to run midlrt");
}

if let Err(error) = windows_bindgen::bindgen([
"--in",
"sample.winmd",
&metadata_dir,
"--out",
"src/bindings.rs",
"--filter",
"Sample",
"--config",
"implement",
]) {
panic!("{error}");
}
}
1 change: 1 addition & 0 deletions crates/samples/components/json_validator_winrt/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Sample for upcoming entry in [Getting Started with Rust](https://kennykerr.ca/rust-getting-started).
190 changes: 190 additions & 0 deletions crates/samples/components/json_validator_winrt/src/bindings.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// Bindings generated by `windows-bindgen` 0.52.0

#![allow(
non_snake_case,
non_upper_case_globals,
non_camel_case_types,
dead_code,
clippy::all
)]
::windows_core::imp::com_interface!(
IJsonValidator,
IJsonValidator_Vtbl,
0xe09cb12b_b13c_5139_8c99_6140bf80deb9
);
#[repr(C)]
#[doc(hidden)]
pub struct IJsonValidator_Vtbl {
pub base__: ::windows_core::IInspectable_Vtbl,
pub Validate: unsafe extern "system" fn(
*mut ::core::ffi::c_void,
::std::mem::MaybeUninit<::windows_core::HSTRING>,
*mut ::std::mem::MaybeUninit<::windows_core::HSTRING>,
) -> ::windows_core::HRESULT,
}
::windows_core::imp::com_interface!(
IJsonValidatorFactory,
IJsonValidatorFactory_Vtbl,
0x1cf4464e_ae9e_55d5_9539_0af4d8fc35aa
);
#[repr(C)]
#[doc(hidden)]
pub struct IJsonValidatorFactory_Vtbl {
pub base__: ::windows_core::IInspectable_Vtbl,
pub CreateInstance: unsafe extern "system" fn(
*mut ::core::ffi::c_void,
::std::mem::MaybeUninit<::windows_core::HSTRING>,
*mut *mut ::core::ffi::c_void,
) -> ::windows_core::HRESULT,
}
#[repr(transparent)]
#[derive(::core::cmp::PartialEq, ::core::cmp::Eq, ::core::fmt::Debug, ::core::clone::Clone)]
pub struct JsonValidator(::windows_core::IUnknown);
::windows_core::imp::interface_hierarchy!(
JsonValidator,
::windows_core::IUnknown,
::windows_core::IInspectable
);
impl JsonValidator {
pub fn Validate(
&self,
value: &::windows_core::HSTRING,
) -> ::windows_core::Result<::windows_core::HSTRING> {
let this = self;
unsafe {
let mut result__ = ::std::mem::zeroed();
(::windows_core::Interface::vtable(this).Validate)(
::windows_core::Interface::as_raw(this),
::core::mem::transmute_copy(value),
&mut result__,
)
.from_abi(result__)
}
}
pub fn CreateInstance(
schema: &::windows_core::HSTRING,
) -> ::windows_core::Result<JsonValidator> {
Self::IJsonValidatorFactory(|this| unsafe {
let mut result__ = ::std::mem::zeroed();
(::windows_core::Interface::vtable(this).CreateInstance)(
::windows_core::Interface::as_raw(this),
::core::mem::transmute_copy(schema),
&mut result__,
)
.from_abi(result__)
})
}
#[doc(hidden)]
pub fn IJsonValidatorFactory<
R,
F: FnOnce(&IJsonValidatorFactory) -> ::windows_core::Result<R>,
>(
callback: F,
) -> ::windows_core::Result<R> {
static SHARED: ::windows_core::imp::FactoryCache<JsonValidator, IJsonValidatorFactory> =
::windows_core::imp::FactoryCache::new();
SHARED.call(callback)
}
}
impl ::windows_core::RuntimeType for JsonValidator {
const SIGNATURE: ::windows_core::imp::ConstBuffer =
::windows_core::imp::ConstBuffer::for_class::<Self>();
}
unsafe impl ::windows_core::Interface for JsonValidator {
type Vtable = IJsonValidator_Vtbl;
const IID: ::windows_core::GUID = <IJsonValidator as ::windows_core::Interface>::IID;
}
impl ::windows_core::RuntimeName for JsonValidator {
const NAME: &'static str = "Sample.JsonValidator";
}
unsafe impl ::core::marker::Send for JsonValidator {}
unsafe impl ::core::marker::Sync for JsonValidator {}
pub trait IJsonValidator_Impl: Sized {
fn Validate(
&self,
value: &::windows_core::HSTRING,
) -> ::windows_core::Result<::windows_core::HSTRING>;
}
impl ::windows_core::RuntimeName for IJsonValidator {
const NAME: &'static str = "Sample.IJsonValidator";
}
impl IJsonValidator_Vtbl {
pub const fn new<
Identity: ::windows_core::IUnknownImpl<Impl = Impl>,
Impl: IJsonValidator_Impl,
const OFFSET: isize,
>() -> IJsonValidator_Vtbl {
unsafe extern "system" fn Validate<
Identity: ::windows_core::IUnknownImpl<Impl = Impl>,
Impl: IJsonValidator_Impl,
const OFFSET: isize,
>(
this: *mut ::core::ffi::c_void,
value: ::std::mem::MaybeUninit<::windows_core::HSTRING>,
result__: *mut ::std::mem::MaybeUninit<::windows_core::HSTRING>,
) -> ::windows_core::HRESULT {
let this = (this as *const *const ()).offset(OFFSET) as *const Identity;
let this = (*this).get_impl();
match this.Validate(::core::mem::transmute(&value)) {
::core::result::Result::Ok(ok__) => {
::core::ptr::write(result__, ::core::mem::transmute_copy(&ok__));
::core::mem::forget(ok__);
::windows_core::HRESULT(0)
}
::core::result::Result::Err(err) => err.into(),
}
}
Self {
base__: ::windows_core::IInspectable_Vtbl::new::<Identity, IJsonValidator, OFFSET>(),
Validate: Validate::<Identity, Impl, OFFSET>,
}
}
pub fn matches(iid: &::windows_core::GUID) -> bool {
iid == &<IJsonValidator as ::windows_core::Interface>::IID
}
}
pub trait IJsonValidatorFactory_Impl: Sized {
fn CreateInstance(
&self,
schema: &::windows_core::HSTRING,
) -> ::windows_core::Result<JsonValidator>;
}
impl ::windows_core::RuntimeName for IJsonValidatorFactory {
const NAME: &'static str = "Sample.IJsonValidatorFactory";
}
impl IJsonValidatorFactory_Vtbl {
pub const fn new<
Identity: ::windows_core::IUnknownImpl<Impl = Impl>,
Impl: IJsonValidatorFactory_Impl,
const OFFSET: isize,
>() -> IJsonValidatorFactory_Vtbl {
unsafe extern "system" fn CreateInstance<
Identity: ::windows_core::IUnknownImpl<Impl = Impl>,
Impl: IJsonValidatorFactory_Impl,
const OFFSET: isize,
>(
this: *mut ::core::ffi::c_void,
schema: ::std::mem::MaybeUninit<::windows_core::HSTRING>,
result__: *mut *mut ::core::ffi::c_void,
) -> ::windows_core::HRESULT {
let this = (this as *const *const ()).offset(OFFSET) as *const Identity;
let this = (*this).get_impl();
match this.CreateInstance(::core::mem::transmute(&schema)) {
::core::result::Result::Ok(ok__) => {
::core::ptr::write(result__, ::core::mem::transmute_copy(&ok__));
::core::mem::forget(ok__);
::windows_core::HRESULT(0)
}
::core::result::Result::Err(err) => err.into(),
}
}
Self {
base__: ::windows_core::IInspectable_Vtbl::new::<Identity, IJsonValidatorFactory, OFFSET>(
),
CreateInstance: CreateInstance::<Identity, Impl, OFFSET>,
}
}
pub fn matches(iid: &::windows_core::GUID) -> bool {
iid == &<IJsonValidatorFactory as ::windows_core::Interface>::IID
}
}
94 changes: 94 additions & 0 deletions crates/samples/components/json_validator_winrt/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
mod bindings;
use jsonschema::JSONSchema;
use windows::{core::*, Win32::Foundation::*, Win32::System::WinRT::*};

// The `JsonValidator` struct represents the implementation of the `JsonValidator` class.
// The `implement` attribute provides the boilerplate COM and WinRT implementation support.
#[implement(bindings::JsonValidator)]
struct JsonValidator {
schema: JSONSchema,
}

// Implement the `IJsonValidator` interface.
impl bindings::IJsonValidator_Impl for JsonValidator {
fn Validate(&self, value: &HSTRING) -> Result<HSTRING> {
let value = json_from_hstring(value)?;

if self.schema.is_valid(&value) {
// Return the sanitized value.
Ok(value.to_string().into())
} else {
// The `validate` method returns a collection of errors. We'll just return the first
// for simplicity.
let message = self
.schema
.validate(&value)
.unwrap_err()
.next()
.map_or(String::new(), |error| error.to_string());

Err(Error::new(E_INVALIDARG, message.into()))
}
}
}

// The `JsonValidatorFactory` struct represents the implementation of the `JsonValidator` class factory.
#[implement(IActivationFactory, bindings::IJsonValidatorFactory)]
struct JsonValidatorFactory;

// The JsonValidator class doesn't provide a default constructor but WinRT still requires an
// implementation of `IActivationFactory`.
impl IActivationFactory_Impl for JsonValidatorFactory {
fn ActivateInstance(&self) -> Result<IInspectable> {
Err(E_NOTIMPL.into())
}
}

// Implement the `IJsonValidatorFactory` interface.
impl bindings::IJsonValidatorFactory_Impl for JsonValidatorFactory {
fn CreateInstance(&self, schema: &HSTRING) -> Result<bindings::JsonValidator> {
let schema = json_from_hstring(schema)?;

let schema = JSONSchema::compile(&schema)
.map_err(|error| Error::new(E_INVALIDARG, error.to_string().into()))?;

Ok(JsonValidator { schema }.into())
}
}

// Takes care of all the JSON parsing and parameter validation.
fn json_from_hstring(value: &HSTRING) -> Result<serde_json::Value> {
let value = String::try_from(value)?;

serde_json::from_str(&value)
.map_err(|error| Error::new(E_INVALIDARG, format!("{error}").into()))
}

#[no_mangle]
extern "system" fn DllGetActivationFactory(
name: std::mem::ManuallyDrop<HSTRING>,
result: *mut *mut std::ffi::c_void,
) -> HRESULT {
if result.is_null() {
return E_POINTER;
}

let mut factory: Option<IActivationFactory> = None;

if *name == "Sample.JsonValidator" {
factory = Some(JsonValidatorFactory.into());
}

// Dereferencing `result` is safe because we've validated that the pointer is not null and
// transmuting `factory` is safe because `DllGetActivationFactory` is expected to return an
// `IActivationFactory` pointer and that's what `factory` contains.
unsafe {
if let Some(factory) = factory {
*result = std::mem::transmute(factory);
S_OK
} else {
*result = std::ptr::null_mut();
CLASS_E_CLASSNOTAVAILABLE
}
}
}
8 changes: 8 additions & 0 deletions crates/samples/components/json_validator_winrt/src/sample.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Sample
{
runtimeclass JsonValidator
{
JsonValidator(String schema);
String Validate(String value);
}
}
Loading

0 comments on commit ccfc98a

Please sign in to comment.