Repository: shuttle-hq/synth (language: rust)

Issue:
Unique field that is a text
Hello,

Synth type number  "id" is unique(can be considered as PK) and used as a FK in other collection using type same_as..
"id" does the same as type serial in PG.

Now..in my PG table I have a field as a PK which is not number (i.e, a text or varchar type)...in synth schema how to represent this field as a unique(PK in this case)  ?
synth type "string" dosen't give such an option  &  "faker" dosen't guarantee uniqueness for all generated data.

I guess in type "string" to have a
"type":"string",
 "condition":"unique",
 "pattern" :" any pattern required"

or even :-
{
  "type": "one_of",
  "variants": [ {
   "condition":"unique" ,                              // "weight": 0.5,   this no need
    "type": "string",
    "pattern": "any pattern required"
  }
 ]
}

The point here is to impose the "unique" condition on the generated data to meet the PK unique values condition.

just an idea :)


Discussion / hints:
Thanks for this @beautybird.

You raise a very good point. We have a `unique` generator in the works, which ensures that any element generated by its child is unique. This should be available soon.

In the meantime (if I've understood your requirements correctly), you should be able to use the [UUID](https://getsynth.github.io/synth/content/string#uuid) generator as a unique string PK for your table.

Hope this helps!

Hi,
Apology for late reply.
Good to hear about your efforts on unique.
UUID is not applicable in all scenarios.
Thank you.

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index 7ad1be12..fd113714 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -399,6 +399,12 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
 [[package]]
 name = "bitflags"
 version = "1.2.1"
@@ -464,6 +470,17 @@ dependencies = [
  "once_cell",
 ]
 
+[[package]]
+name = "bloomfilter"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cffe85a8b173164000edb6570ff7cf429acc9460b41d963e797df413f1765b4"
+dependencies = [
+ "bit-vec",
+ "rand 0.8.3",
+ "siphasher",
+]
+
 [[package]]
 name = "bson"
 version = "1.2.2"
@@ -2227,6 +2244,17 @@ dependencies = [
  "vcpkg",
 ]
 
+[[package]]
+name = "ordered-float"
+version = "2.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f100fcfb41e5385e0991f74981732049f9b896821542a219420491046baafdc2"
+dependencies = [
+ "num-traits",
+ "rand 0.8.3",
+ "serde",
+]
+
 [[package]]
 name = "os_info"
 version = "3.0.6"
@@ -3279,11 +3307,12 @@ dependencies = [
 
 [[package]]
 name = "synth-core"
-version = "0.5.0"
+version = "0.5.1"
 dependencies = [
  "anyhow",
  "bimap",
  "bincode",
+ "bloomfilter",
  "chrono",
  "colored",
  "env_logger",
@@ -3307,9 +3336,10 @@ dependencies = [
 
 [[package]]
 name = "synth-gen"
-version = "0.4.0"
+version = "0.4.1"
 dependencies = [
  "fake",
+ "ordered-float",
  "rand 0.8.3",
  "serde",
  "serde_json",
diff --git a/core/Cargo.toml b/core/Cargo.toml
index 22db5cc1..0153909a 100644
--- a/core/Cargo.toml
+++ b/core/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "synth-core"
-version = "0.5.0"
+version = "0.5.1"
 authors = [
   "Damien Broka <damien@getsynth.com>",
   "Christos Hadjiaslanis <christos@getsynth.com>",
@@ -37,3 +37,4 @@ synth-gen = { path = "../gen", features = [ "shared" ] }
 uuid = { version = "0.8.2", features = ["v4"] }
 bimap = { version = "0.6.0", features = [ "std" ] }
 humantime-serde = "1.0.1"
+bloomfilter = "1.0.5"
diff --git a/core/src/graph/mod.rs b/core/src/graph/mod.rs
index 00eb17e0..bcffd349 100644
--- a/core/src/graph/mod.rs
+++ b/core/src/graph/mod.rs
@@ -78,6 +78,9 @@ pub use array::ArrayNode;
 pub mod object;
 pub use object::{KeyValueOrNothing, ObjectNode};
 
+pub mod unique;
+pub use unique::UniqueNode;
+
 pub mod one_of;
 pub(crate) mod series;
 
@@ -174,15 +177,15 @@ where
 }
 
 derive_from! {
-    #[derive(Debug, Clone)]
+    #[derive(Debug, Clone, Hash, PartialEq, Eq)]
     pub enum Value {
-    Null(()),
-    Bool(bool),
-    Number(Number),
-    String(String),
-    DateTime(ChronoValue),
-    Object(BTreeMap<String, Value>),
-    Array(Vec<Value>),
+        Null(()),
+        Bool(bool),
+        Number(Number),
+        String(String),
+        DateTime(ChronoValue),
+        Object(BTreeMap<String, Value>),
+        Array(Vec<Value>),
     }
 }
 
@@ -245,6 +248,7 @@ derive_generator!(
         View(Unwrapped<View<Graph>>),
         Scoped(Scoped<Graph>),
         Series(SeriesNode),
+        Unique(UniqueNode)
     }
 );
 
diff --git a/core/src/graph/string/faker.rs b/core/src/graph/string/faker.rs
index 4c37c6ea..0030894b 100644
--- a/core/src/graph/string/faker.rs
+++ b/core/src/graph/string/faker.rs
@@ -173,12 +173,13 @@ pub struct RandFaker {
 }
 
 impl RandFaker {
-    pub(crate) fn new(generator: String, args: FakerArgs) -> Result<Self, anyhow::Error> {
-        match FAKE_MAP.get(generator.as_str()) {
+    pub(crate) fn new<S: AsRef<str>>(generator: S, args: FakerArgs) -> Result<Self, anyhow::Error> {
+        match FAKE_MAP.get(generator.as_ref()) {
             None => Err(anyhow!(
                 "Generator '{}' does not exist {}",
-                generator,
-                suggest_closest(FAKE_MAP.keys(), &generator).unwrap_or_else(|| "".to_string())
+                generator.as_ref(),
+                suggest_closest(FAKE_MAP.keys(), &generator.as_ref())
+                    .unwrap_or_else(|| "".to_string())
             )),
             Some(generator) => Ok(Self {
                 generator: *generator,
diff --git a/core/src/graph/unique.rs b/core/src/graph/unique.rs
new file mode 100644
index 00000000..d1d9c654
--- /dev/null
+++ b/core/src/graph/unique.rs
@@ -0,0 +1,100 @@
+use crate::graph::prelude::{
+    Error, Generator, GeneratorState, Rng, Token, TryFilterMap, TryGeneratorExt, Value,
+};
+use crate::Graph;
+
+use std::collections::HashMap;
+use std::hash::{BuildHasher, Hash, Hasher};
+
+const MAX_RETRIES: usize = 64;
+
+type ValueFilter =
+    TryFilterMap<Box<Graph>, Box<dyn FnMut(Value) -> Result<Option<Value>, Error>>, Value>;
+
+derive_generator! {
+    yield Token,
+    return Result<Value, Error>,
+    pub enum UniqueNode {
+        Hash(ValueFilter),
+    }
+}
+
+impl UniqueNode {
+    pub fn hash(inner: Graph) -> Self {
+        let mut seen: HashMap<u64, usize> = HashMap::new();
+        let filter = move |value: Value| {
+            let mut hasher = seen.hasher().build_hasher();
+            value.hash(&mut hasher);
+            let hash = hasher.finish();
+
+            let count = seen
+                .entry(hash)
+                .and_modify(|i| {
+                    *i += 1;
+                })
+                .or_insert(0);
+
+            match *count {
+                0 => Ok(Some(value)),
+                x if x < MAX_RETRIES => Ok(None),
+                _ => Err(failed_crate!(
+                    target: Release,
+                    "Could not generate enough unique values from this generator:\
+                         try reducing the number of values generated"
+                )),
+            }
+        };
+        Self::Hash(Box::new(inner).try_filter_map(Box::new(filter)))
+    }
+}
+
+#[cfg(test)]
+pub mod tests {
+    use super::*;
+    use crate::graph::{
+        prelude::{Generator, GeneratorExt},
+        Graph, NumberNode, RandFaker, RandomString, RandomU64, RangeStep, StringNode,
+    };
+    use std::collections::HashSet;
+
+    const NUM_GENERATED: usize = 1024;
+
+    #[test]
+    fn unique_node() {
+        let usernames = Graph::String(StringNode::from(RandomString::from(
+            RandFaker::new("username", Default::default()).unwrap(),
+        )));
+        let mut rng = rand::thread_rng();
+        let output = UniqueNode::hash(usernames)
+            .repeat(NUM_GENERATED)
+            .complete(&mut rng)
+            .into_iter()
+            .collect::<Result<HashSet<_>, _>>()
+            .unwrap();
+        assert_eq!(output.len(), NUM_GENERATED);
+
+        let numbers = Graph::Number(NumberNode::from(
+            RandomU64::range(RangeStep {
+                low: 0,
+                high: NUM_GENERATED as u64,
+                step: 1,
+            })
+            .unwrap(),
+        ));
+        let output = UniqueNode::hash(numbers)
+            .repeat(NUM_GENERATED)
+            .complete(&mut rng)
+            .into_iter()
+            .collect::<Result<HashSet<_>, _>>()
+            .unwrap();
+        assert_eq!(output.len(), NUM_GENERATED);
+
+        let constant = Graph::Number(NumberNode::from(RandomU64::constant(44)));
+        let output = UniqueNode::hash(constant)
+            .repeat(10)
+            .complete(&mut rng)
+            .into_iter()
+            .collect::<Result<HashSet<_>, _>>();
+        assert!(output.is_err());
+    }
+}
diff --git a/core/src/schema/content/mod.rs b/core/src/schema/content/mod.rs
index e05d81f2..e900357d 100644
--- a/core/src/schema/content/mod.rs
+++ b/core/src/schema/content/mod.rs
@@ -43,11 +43,13 @@ pub use categorical::{Categorical, CategoricalType};
 pub use number::Id;
 pub mod prelude;
 pub(crate) mod series;
+pub(crate) mod unique;
 
 use prelude::*;
 
 use super::FieldRef;
 use crate::schema::content::series::SeriesContent;
+use crate::schema::unique::UniqueContent;
 
 pub trait Find<C> {
     fn find<I, R>(&self, reference: I) -> Result<&C>
@@ -98,6 +100,7 @@ pub enum Content {
     SameAs(SameAsContent),
     OneOf(OneOfContent),
     Series(SeriesContent),
+    Unique(UniqueContent),
 }
 
 impl Content {
@@ -107,6 +110,7 @@ impl Content {
 
     pub fn accepts(&self, value: &Value) -> Result<()> {
         match self {
+            Self::Unique(unique_content) => unique_content.content.accepts(value),
             Self::SameAs(_) => Ok(()),
             Self::OneOf(one_of_content) => {
                 let res: Vec<_> = one_of_content
@@ -190,6 +194,7 @@ impl Content {
             Content::SameAs(_) => "same_as",
             Content::OneOf(_) => "one_of",
             Content::Series(_) => "series",
+            Content::Unique(_) => "unique",
         }
     }
 }
@@ -312,6 +317,7 @@ impl Compile for Content {
             Self::SameAs(same_as_content) => same_as_content.compile(compiler),
             Self::OneOf(one_of_content) => one_of_content.compile(compiler),
             Self::Series(series_content) => series_content.compile(compiler),
+            Self::Unique(unique_content) => unique_content.compile(compiler),
             Self::Null => Ok(Graph::null()),
         }
     }
diff --git a/core/src/schema/content/string.rs b/core/src/schema/content/string.rs
index 2fcab7ec..51fe5296 100644
--- a/core/src/schema/content/string.rs
+++ b/core/src/schema/content/string.rs
@@ -186,7 +186,7 @@ pub struct JsonContent {
     content: Box<Content>,
 }
 
-#[derive(Debug, PartialEq, PartialOrd, Clone)]
+#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Hash)]
 pub enum ChronoValue {
     NaiveDate(NaiveDate),
     NaiveTime(NaiveTime),
diff --git a/core/src/schema/content/unique.rs b/core/src/schema/content/unique.rs
new file mode 100644
index 00000000..efdbd66e
--- /dev/null
+++ b/core/src/schema/content/unique.rs
@@ -0,0 +1,34 @@
+use crate::compile::Compile;
+use crate::graph::UniqueNode;
+use crate::{Compiler, Content, Graph};
+use anyhow::Result;
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
+pub enum UniqueAlgorithm {
+    Hash,
+}
+
+impl Default for UniqueAlgorithm {
+    fn default() -> Self {
+        Self::Hash
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
+#[serde(deny_unknown_fields)]
+pub struct UniqueContent {
+    #[serde(default)]
+    pub algorithm: UniqueAlgorithm,
+    pub content: Box<Content>,
+}
+
+impl Compile for UniqueContent {
+    fn compile<'a, C: Compiler<'a>>(&'a self, compiler: C) -> Result<Graph> {
+        let graph = self.content.compile(compiler)?;
+        let node = match self.algorithm {
+            UniqueAlgorithm::Hash => UniqueNode::hash(graph),
+        };
+        Ok(Graph::Unique(node))
+    }
+}
diff --git a/gen/Cargo.toml b/gen/Cargo.toml
index 14c1cc08..422abb5c 100644
--- a/gen/Cargo.toml
+++ b/gen/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "synth-gen"
-version = "0.4.0"
+version = "0.4.1"
 authors = [
   "Damien Broka <damien@getsynth.com>",
   "Christos Hadjiaslanis <christos@getsynth.com>"
@@ -21,5 +21,5 @@ shared = [ ]
 [dependencies]
 serde = { version = "1.0", features = [ "derive" ] }
 rand = "0.8.3"
-
+ordered-float = { version = "2.0", features = ["serde", "rand"] }
 fake = { git = "https://github.com/cksac/fake-rs", features = ["http"], rev = "ab095c1f8cf230cfa94305339b4e08bd02a275d4", optional = true }
diff --git a/gen/src/de.rs b/gen/src/de.rs
index 8c2194c5..6d1887b0 100644
--- a/gen/src/de.rs
+++ b/gen/src/de.rs
@@ -48,8 +48,8 @@ macro_rules! deserialize_number_helper {
     (u32) => (deserialize_number_impl!(u32 => deserialize_u32 visit_u32););
     (u64) => (deserialize_number_impl!(u64 => deserialize_u64 visit_u64););
     (u128) => (deserialize_number_impl!(u128 => deserialize_u128 visit_u128););
-    (f32) => (deserialize_number_impl!(f32 => deserialize_f32 visit_f32););
-    (f64) => (deserialize_number_impl!(f64 => deserialize_f64 visit_f64););
+    (OrderedFloat32) => (deserialize_number_impl!(OrderedFloat32 => deserialize_f32 visit_f32););
+    (OrderedFloat64) => (deserialize_number_impl!(OrderedFloat64 => deserialize_f64 visit_f64););
 }
 
 macro_rules! deserialize_number_impl {
@@ -252,7 +252,20 @@ where
     Vec<u8> => deserialize_byte_buf visit_byte_buf,
     );
 
-    deserialize_number!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64,);
+    deserialize_number!(
+        i8,
+        i16,
+        i32,
+        i64,
+        i128,
+        u8,
+        u16,
+        u32,
+        u64,
+        u128,
+        OrderedFloat32,
+        OrderedFloat64,
+    );
 
     deserialize_redirect!(
     deserialize_str -> deserialize_string,
diff --git a/gen/src/error.rs b/gen/src/error.rs
index 696985b5..7cc07259 100644
--- a/gen/src/error.rs
+++ b/gen/src/error.rs
@@ -2,7 +2,7 @@ use std::fmt::Display;
 
 use crate::value::{IntoToken, Special, Token};
 
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Clone, Debug, PartialEq, Eq, Hash)]
 pub enum Error {
     Type { expected: String, got: String },
     Deserialize { msg: String },
diff --git a/gen/src/generator/try.rs b/gen/src/generator/try.rs
index 7346869f..c68a9482 100644
--- a/gen/src/generator/try.rs
+++ b/gen/src/generator/try.rs
@@ -3,6 +3,7 @@ use rand::Rng;
 use crate::{GeneratorState, Never};
 
 use super::Generator;
+use std::collections::VecDeque;
 
 /// we use this trait to bound `Result`s because we don't have type equality
 /// in `where` clauses yet.
@@ -21,9 +22,15 @@ impl<O, E> GeneratorResult for Result<O, E> {
 
     // inlined to make sure it's optimized out
     #[inline(always)]
-    fn into_result(self) -> Result<O, E> { self }
-    fn from_ok(ok: O) -> Self { Ok(ok) }
-    fn from_err(err: E) -> Self { Err(err) }
+    fn into_result(self) -> Result<O, E> {
+        self
+    }
+    fn from_ok(ok: O) -> Self {
+        Ok(ok)
+    }
+    fn from_err(err: E) -> Self {
+        Err(err)
+    }
 }
 
 /// A convenience trait for [`Generator`](crate::Generator)s that
@@ -171,6 +178,18 @@ where
         }
     }
 
+    fn try_filter_map<F, O>(self, f: F) -> TryFilterMap<Self, F, O>
+    where
+        F: FnMut(Self::Ok) -> Result<Option<O>, Self::Error>,
+    {
+        TryFilterMap {
+            inner: self,
+            yielded: VecDeque::new(),
+            output: None,
+            closure: f,
+        }
+    }
+
     fn try_aggregate(self) -> TryAggregate<Self> {
         TryAggregate {
             inner: self,
@@ -299,6 +318,57 @@ where
     }
 }
 
+pub struct TryFilterMap<TG, F, O>
+where
+    TG: TryGenerator,
+    F: FnMut(TG::Ok) -> Result<Option<O>, TG::Error>,
+{
+    inner: TG,
+    yielded: VecDeque<TG::Yield>,
+    output: Option<O>,
+    closure: F,
+}
+
+impl<TG, F, O> Generator for TryFilterMap<TG, F, O>
+where
+    TG: TryGenerator,
+    F: FnMut(TG::Ok) -> Result<Option<O>, TG::Error>,
+{
+    type Yield = TG::Yield;
+
+    type Return = Result<O, TG::Error>;
+
+    fn next<RR: Rng>(&mut self, rng: &mut RR) -> GeneratorState<Self::Yield, Self::Return> {
+        match self.yielded.pop_front() {
+            Some(fst) => GeneratorState::Yielded(fst),
+            None => match std::mem::replace(&mut self.output, None) {
+                Some(output) => GeneratorState::Complete(Ok(output)),
+                None => {
+                    loop {
+                        match self.inner.try_next(rng) {
+                            GeneratorState::Yielded(yielded) => self.yielded.push_back(yielded),
+                            GeneratorState::Complete(complete) => match complete.into_result() {
+                                Ok(output) => match (self.closure)(output) {
+                                    Ok(Some(value)) => {
+                                        self.output = Some(value);
+                                        break;
+                                    }
+                                    Ok(None) => {
+                                        self.yielded.clear();
+                                    }
+                                    Err(err) => return GeneratorState::Complete(Err(err)),
+                                },
+                                Err(err) => return GeneratorState::Complete(Err(err)),
+                            },
+                        }
+                    }
+                    self.next(rng)
+                }
+            },
+        }
+    }
+}
+
 /// This struct is created by the
 /// [`try_aggregate`](crate::TryGeneratorExt::try_aggregate) method on
 /// [`TryGenerator`](crate::TryGenerator).
diff --git a/gen/src/value.rs b/gen/src/value.rs
index 20a6cc52..442aaf2e 100644
--- a/gen/src/value.rs
+++ b/gen/src/value.rs
@@ -12,6 +12,10 @@ use crate::{
 };
 
 use serde::Deserialize;
+use ordered_float::OrderedFloat;
+
+pub type OrderedFloat32 = OrderedFloat<f32>;
+pub type OrderedFloat64 = OrderedFloat<f64>;
 
 macro_rules! generate_enum {
     {
@@ -212,7 +216,7 @@ macro_rules! data_model_variant_impl_ext {
 generate_enum!(
     /// Token variant for serde primitive numerical types.
     #[allow(missing_docs)]
-    #[derive(Debug, Clone, Copy)]
+    #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
     pub enum Number {
         I8(i8),
         I16(i16),
@@ -224,11 +228,45 @@ generate_enum!(
         U32(u32),
         U64(u64),
         U128(u128),
-        F32(f32),
-        F64(f64),
+        F32(OrderedFloat32),
+        F64(OrderedFloat64),
     }
 );
 
+impl From<f32> for Number {
+    fn from(f: f32) -> Self {
+        Number::F32(OrderedFloat(f))
+    }
+}
+
+impl From<f64> for Number {
+    fn from(f: f64) -> Self {
+        Number::F64(OrderedFloat(f))
+    }
+}
+
+impl TryFrom<Number> for f32 {
+    type Error = Error;
+    fn try_from(value: Number) -> Result<f32, <Self as TryFrom<Number>>::Error> {
+        match value {
+            Number::F32(of32) => Ok(of32.into_inner()),
+            otherwise => Err(Error::type_(stringify!(F32), otherwise)),
+        }
+    }
+}
+
+impl TryFrom<Number> for f64 {
+    type Error = Error;
+    fn try_from(value: Number) -> Result<f64, <Self as TryFrom<Number>>::Error> {
+        match value {
+            Number::F64(of64) => Ok(of64.into_inner()),
+            otherwise => Err(Error::type_(stringify!(F64), otherwise)),
+        }
+    }
+}
+
+
+
 impl serde::Serialize for Number {
     fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
     where
@@ -245,8 +283,8 @@ impl serde::Serialize for Number {
             Self::U32(x) => serializer.serialize_u32(x),
             Self::U64(x) => serializer.serialize_u64(x),
             Self::U128(x) => serializer.serialize_u128(x),
-            Self::F32(x) => serializer.serialize_f32(x),
-            Self::F64(x) => serializer.serialize_f64(x),
+            Self::F32(x) => serializer.serialize_f32(x.into_inner()),
+            Self::F64(x) => serializer.serialize_f64(x.into_inner()),
         }
     }
 }
@@ -254,7 +292,7 @@ impl serde::Serialize for Number {
 generate_enum!(
     /// Token variant for all serde primitive (non-composite) types.
     #[allow(missing_docs)]
-    #[derive(Clone, Debug)]
+    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
     pub enum Primitive {
         Bool(bool),
         String(String),
@@ -285,7 +323,7 @@ generate_special_enum!(
     /// Token variant for serde control flow statements that allow
     /// for building instances of composite types.
     #[allow(missing_docs)]
-    #[derive(Clone, Debug)]
+    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
     pub enum Special {
     BeginMap(Option<usize> as len,) -> is_begin_map begin_map,
     EndMap -> is_end_map end_map,
@@ -306,7 +344,7 @@ generate_special_enum!(
 
 generate_enum!(
     /// A custom tokenization for the serde data model.
-    #[derive(Clone, Debug)]
+    #[derive(Clone, Debug, PartialEq, Eq, Hash)]
     pub enum Token {
         /// A token encoding serde primitive types.
         Primitive(Primitive),
@@ -418,7 +456,7 @@ macro_rules! impl_into_token {
 
 impl_into_token! {
     token_from_number:
-    i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64 => Number => Primitive => Token,
+    i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64, OrderedFloat32, OrderedFloat64 => Number => Primitive => Token,
     token_from_primitives: bool => Primitive => Token,
     token_from_string: String, &str => String => Primitive => Token,
     token_from_null: () => Primitive => Token,
@@ -633,8 +671,8 @@ pub mod tests {
         value_u32<u32>,
         value_u64<u64>,
         value_u128<u128>,
-        value_f32<f32>,
-        value_f64<f64>,
+        value_f32<OrderedFloat32>,
+        value_f64<OrderedFloat64>,
         value_bool<bool>,
     );
 


Test patch: