Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ rust-version = "1.83"
default = []
# Enables constness on nightly; FIXME: re-enable when const convert and const trait impl are back on nightly
# nightly = ["arbitrary-int/const_convert_and_const_trait_impl", "bilge-impl/nightly"]
schemars = ["bilge-impl/schemars", "arbitrary-int/schemars"]
serde = ["bilge-impl/serde", "arbitrary-int/serde"]

[dependencies]
Expand All @@ -53,6 +54,7 @@ rustversion = "1.0"
trybuild = "1.0"
custom_bits = { path = "tests/custom_bits" }
assert_matches = "1.5.0"
schemars = "0.8"
serde = "1.0"
serde_test = "1.0"

Expand Down
1 change: 1 addition & 0 deletions bilge-impl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ proc-macro = true
default = []
# Enables constness, see README.md for the specific nightly version
nightly = []
schemars = []
serde = []

[dependencies]
Expand Down
13 changes: 13 additions & 0 deletions bilge-impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ mod debug_bits;
mod default_bits;
mod fmt_bits;
mod from_bits;
#[cfg(feature = "schemars")]
#[cfg_attr(docsrs, doc(cfg(feature = "schemars")))]
mod schemars_bits;
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
mod serde_bits;
Expand Down Expand Up @@ -78,6 +81,16 @@ pub fn derive_default_bits(item: TokenStream) -> TokenStream {
default_bits::default_bits(item.into()).into()
}

/// Generate an `impl schemars::JsonSchema` for bitfield structs.
///
/// Please use normal #[derive(JsonSchema)] for enums.
#[cfg(feature = "schemars")]
#[proc_macro_error]
#[proc_macro_derive(JsonSchemaBits, attributes(bitsize_internal))]
pub fn json_schema_bits(item: TokenStream) -> TokenStream {
schemars_bits::json_schema_bits(item.into()).into()
}

/// Generate an `impl serde::Serialize` for bitfield structs.
///
/// Please use normal #[derive(Serialize)] for enums.
Expand Down
85 changes: 85 additions & 0 deletions bilge-impl/src/schemars_bits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use proc_macro2::TokenStream;
use proc_macro_error2::abort_call_site;
use quote::quote;
use syn::{Data, Field, Fields};

use crate::shared::{self, unreachable};

fn filter_not_reserved_or_padding(field: &&Field) -> bool {
let field_name_string = field.ident.as_ref().unwrap().to_string();
!field_name_string.starts_with("reserved_") && !field_name_string.starts_with("padding_")
}

pub(super) fn json_schema_bits(item: TokenStream) -> TokenStream {
let derive_input = shared::parse_derive(item);
let name = &derive_input.ident;
let name_str = name.to_string();
let struct_data = match derive_input.data {
Data::Struct(s) => s,
Data::Enum(_) => abort_call_site!("use derive(JsonSchema) for enums"),
Data::Union(_) => unreachable(()),
};

let json_schema_impl = match struct_data.fields {
Fields::Named(fields) => {
let calls = fields.named.iter().filter(filter_not_reserved_or_padding).map(|f| {
// We can unwrap since this is a named field
let field_name = f.ident.as_ref().unwrap().to_string();
let ty = &f.ty;
quote! {
object_validation
.properties
.insert(<str as ::std::borrow::ToOwned>::to_owned(#field_name), generator.subschema_for::<#ty>());
object_validation.required.insert(<str as ::std::borrow::ToOwned>::to_owned(#field_name));
}
});
quote! {
let mut schema_object = ::schemars::schema::SchemaObject {
instance_type: ::core::option::Option::Some(::schemars::schema::SingleOrVec::Single(::std::boxed::Box::new(::schemars::schema::InstanceType::Object))),
..::core::default::Default::default()
};
let object_validation = schema_object.object();
object_validation.additional_properties =
::core::option::Option::Some(::std::boxed::Box::new(::schemars::schema::Schema::Bool(false)));
#(#calls)*
::schemars::schema::Schema::Object(schema_object)
}
}
Fields::Unnamed(fields) => {
let len = fields.unnamed.len() as u32;
let calls = fields.unnamed.iter().map(|f| {
let ty = &f.ty;
quote!(generator.subschema_for::<#ty>())
});
quote! {
::schemars::schema::Schema::Object(::schemars::schema::SchemaObject {
instance_type: ::core::option::Option::Some(::schemars::schema::SingleOrVec::Single(::std::boxed::Box::new(::schemars::schema::InstanceType::Array))),
array: ::core::option::Option::Some(::std::boxed::Box::new(::schemars::schema::ArrayValidation {
items: ::core::option::Option::Some(::schemars::schema::SingleOrVec::Vec(::std::vec![#(#calls),*])),
max_items: ::core::option::Option::Some(#len),
min_items: ::core::option::Option::Some(#len),
..::core::default::Default::default()
})),
..::core::default::Default::default()
})
}
}
Fields::Unit => todo!("this is a unit struct, which is not supported right now"),
};

quote! {
impl ::schemars::JsonSchema for #name {
fn schema_name() -> ::std::string::String {
<str as ::std::borrow::ToOwned>::to_owned(#name_str)
}

fn schema_id() -> ::std::borrow::Cow<'static, str> {
::std::borrow::Cow::Borrowed(concat!(module_path!(), "::", #name_str))
}

fn json_schema(generator: &mut ::schemars::r#gen::SchemaGenerator) -> ::schemars::schema::Schema {
#json_schema_impl
}
}
}
}
8 changes: 4 additions & 4 deletions examples/zz_experimental_problems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ enum Unfilled {
fn main() {
// This file mostly shows one flaw to still be solved or at least to be made configurable:
// The inner value of a bitfield, which holds invariants, can currently still be changed.
let mut a = CanBeChanged::new(Unfilled::A);
let mut _a = CanBeChanged::new(Unfilled::A);
// There is no enum value for `3` or `0b11`, but we can set it anyways:
a.value = u4::new(3);
_a.value = u4::new(3);
// This panics internally:
// a.val_0();

Expand Down Expand Up @@ -53,8 +53,8 @@ mod somebits {
#[allow(dead_code)]
fn modify_inner() {
let a = 0b10101010;
let mut b = SomeBits::new(true, true, true);
b.value = u3::new(a);
let mut _b = SomeBits::new(true, true, true);
_b.value = u3::new(a);
}
}
use somebits::*;
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use core::fmt;

#[doc(no_inline)]
pub use arbitrary_int;
#[cfg(feature = "schemars")]
pub use bilge_impl::JsonSchemaBits;
pub use bilge_impl::{bitsize, bitsize_internal, BinaryBits, DebugBits, DefaultBits, FromBits, TryFromBits};
#[cfg(feature = "serde")]
pub use bilge_impl::{DeserializeBits, SerializeBits};
Expand All @@ -19,6 +21,8 @@ pub mod prelude {
// we control the version, so this should not be a problem
arbitrary_int::prelude::*,
};
#[cfg(feature = "schemars")]
pub use super::JsonSchemaBits;
#[cfg(feature = "serde")]
pub use super::{DeserializeBits, SerializeBits};
}
Expand Down
56 changes: 56 additions & 0 deletions tests/schemars.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#![cfg(feature = "schemars")]

use bilge::prelude::*;
use schemars::{
schema::{InstanceType, SingleOrVec},
schema_for,
};

#[bitsize(17)]
#[derive(JsonSchemaBits)]
struct BitsStruct {
padding: u1,
reserved: u1,
field1: u8,
padding: u1,
field2: u5,
reserved: u1,
}

#[test]
fn schemars_struct() {
let schema = schema_for!(BitsStruct);
let object = schema.schema.object.expect("named bitfield should generate object schema");
assert_eq!(schema.schema.instance_type, Some(InstanceType::Object.into()));

assert_eq!(object.properties.len(), 2);
assert!(object.properties.contains_key("field1"));
assert!(object.properties.contains_key("field2"));
assert!(!object.properties.contains_key("padding_i"));
assert!(!object.properties.contains_key("reserved_i"));

assert_eq!(object.required.len(), 2);
assert!(object.required.contains("field1"));
assert!(object.required.contains("field2"));
assert_eq!(object.additional_properties, Some(Box::new(false.into())));
}

#[bitsize(13)]
#[derive(JsonSchemaBits)]
struct BitsTupleStruct(u8, u5);

#[test]
fn schemars_tuple_struct() {
let schema = schema_for!(BitsTupleStruct);
let array = schema.schema.array.expect("tuple bitfield should generate array schema");
assert_eq!(schema.schema.instance_type, Some(InstanceType::Array.into()));

assert_eq!(array.min_items, Some(2));
assert_eq!(array.max_items, Some(2));

let items = array.items.expect("tuple bitfield should define tuple items");
match items {
SingleOrVec::Single(_) => panic!("tuple bitfield should have one schema per element"),
SingleOrVec::Vec(items) => assert_eq!(items.len(), 2),
}
}
9 changes: 8 additions & 1 deletion tests/ui/default-should-be-used.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@ error[E0277]: the trait bound `Inner: Default` is not satisfied
--> tests/ui/default-should-be-used.rs:14:8
|
14 | b: Inner,
| ^^^^^ the trait `Default` is not implemented for `Inner`
| ^^^^^ unsatisfied trait bound
|
help: the trait `Default` is not implemented for `Inner`
--> tests/ui/default-should-be-used.rs:17:1
|
17 | #[bitsize(2)]
| ^^^^^^^^^^^^^
= note: this error originates in the attribute macro `::bilge::bitsize_internal` (in Nightly builds, run with -Z macro-backtrace for more info)
2 changes: 1 addition & 1 deletion tests/ui/fallback/more.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ error: `#[fallback]` does not support variants with named fields

--> tests/ui/fallback/more.rs:9:5
|
9 | / #[fallback]
9 | / #[fallback]
10 | | Dee { fallback: u15 },
| |_________________________^

Expand Down
8 changes: 4 additions & 4 deletions tests/ui/vis-privacy-is-respected.stderr
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
error[E0624]: method `val_0` is private
--> tests/ui/vis-privacy-is-respected.rs:29:11
|
5 | #[bitsize(96)]
5 | #[bitsize(96)]
| -------------- private method defined here
...
29 | diary.val_0();
Expand All @@ -10,7 +10,7 @@ error[E0624]: method `val_0` is private
error[E0624]: method `set_val_0` is private
--> tests/ui/vis-privacy-is-respected.rs:31:11
|
5 | #[bitsize(96)]
5 | #[bitsize(96)]
| -------------- private method defined here
...
31 | diary.set_val_0(rusti);
Expand All @@ -19,7 +19,7 @@ error[E0624]: method `set_val_0` is private
error[E0624]: method `val_1_at` is private
--> tests/ui/vis-privacy-is-respected.rs:37:7
|
9 | #[bitsize(8)]
9 | #[bitsize(8)]
| ------------- private method defined here
...
37 | a.val_1_at(1);
Expand All @@ -28,7 +28,7 @@ error[E0624]: method `val_1_at` is private
error[E0624]: method `set_val_1_at` is private
--> tests/ui/vis-privacy-is-respected.rs:38:7
|
9 | #[bitsize(8)]
9 | #[bitsize(8)]
| ------------- private method defined here
...
38 | a.set_val_1_at(1, u2::new(0));
Expand Down