strval

Check-in Differences
Login

Check-in Differences

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Difference From strval-0.1.0 To strval-0.1.1

2025-07-09
00:57
Add get_str_vals(). check-in: b50740496c user: jan tags: trunk
00:03
Release maintenance. check-in: 4db9183b11 user: jan tags: strval-0.1.1, trunk
2025-07-08
23:59
Add BinUsizeCount, BinUsizeSize, DecUsizeCount, DecUsizeSize. check-in: 27dde2c763 user: jan tags: trunk
22:47
Move from old repo. check-in: fd050671e7 user: jan tags: strval-0.1.0, trunk
22:06
initial empty check-in check-in: ecf79bc701 user: jan tags: trunk

Changes to .efiles.
1
2
3
4
5
6
7
8
9
10
11
12




13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20












+
+
+
+




Cargo.toml
README.md
www/index.md
www/changelog.md
src/err.rs
src/lib.rs
src/boolean.rs
src/str.rs
src/binu64count.rs
src/binu64size.rs
src/decu64count.rs
src/decu64size.rs
src/binusizecount.rs
src/binusizesize.rs
src/decusizecount.rs
src/decusizesize.rs
src/dur.rs
src/relabslim.rs
src/select.rs
src/percent.rs
Changes to Cargo.toml.
1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







[package]
name = "strval"
version = "0.1.0"
version = "0.1.1"
edition = "2024"
license = "0BSD"
# https://crates.io/category_slugs
categories = ["text-processing"]
keywords = ["parsing", "string", "rusqlite"]
repository = "https://repos.qrnch.tech/pub/strval"
description = "Parse strings into values"
Added src/binusizecount.rs.



























































































































































































































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::{marker::PhantomData, str::FromStr};

#[cfg(feature = "rusqlite")]
use rusqlite::{
  ToSql,
  types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}
};

use parse_size::ByteSuffix;

use crate::{AnyUsize, Controller, StrVal, err::Error};


/// A `usize` value that supports suffixes, like `1k`, `2.5k`, etc.
///
/// This variant treats k as 1024.  See [`DecUsizeCount`](super::DecUsizeCount)
/// for a decimal variant.
#[derive(Debug, Clone)]
pub struct BinUsizeCount<C = AnyUsize> {
  sval: String,
  val: usize,
  _marker: PhantomData<C>
}

impl<C> Default for BinUsizeCount<C> {
  fn default() -> Self {
    Self {
      sval: "0".into(),
      val: 0,
      _marker: PhantomData
    }
  }
}

impl<C> StrVal for BinUsizeCount<C>
where
  C: Controller<Type = usize>
{
  type Type = usize;

  fn set(&mut self, sval: &str) -> Result<Self::Type, Error> {
    let dv = sval.parse::<Self>()?;

    C::validate(&dv.val)?;

    self.sval = sval.to_string();
    self.val = dv.val;
    Ok(dv.val)
  }

  fn get(&self) -> Self::Type {
    self.val
  }

  fn val_str(&self) -> Option<String> {
    Some(self.val.to_string())
  }
}

impl<C> AsRef<str> for BinUsizeCount<C> {
  fn as_ref(&self) -> &str {
    &self.sval
  }
}

impl<C> FromStr for BinUsizeCount<C> {
  type Err = Error;

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    let val = parse_size::Config::default()
      .with_binary()
      .with_byte_suffix(ByteSuffix::Deny)
      .parse_size(s)
      .map_err(|e| Error::Invalid(e.to_string()))?;
    let val =
      usize::try_from(val).map_err(|e| Error::OutOfBounds(e.to_string()))?;
    Ok(Self {
      sval: s.to_string(),
      val,
      _marker: PhantomData
    })
  }
}

#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
impl ToSql for BinUsizeCount {
  fn to_sql(&self) -> Result<ToSqlOutput<'_>, rusqlite::Error> {
    self.sval.to_sql()
  }
}

#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
impl FromSql for BinUsizeCount {
  #[inline]
  fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
    let s = String::column_result(value)?;
    s.parse::<Self>()
      .map_err(|e| FromSqlError::Other(Box::new(e)))
  }
}


#[cfg(test)]
mod tests {
  use super::*;

  #[cfg(feature = "rusqlite")]
  use rusqlite::{Connection, params};

  struct SomeUsizeBound {}

  impl Controller for SomeUsizeBound {
    type Type = usize;
    fn def() -> String {
      String::from("10")
    }
    fn validate(val: &Self::Type) -> Result<(), Error> {
      if *val < 10 || *val > 256 * 1024 {
        Err(Error::OutOfBounds("Must be 10 - 256K".into()))
      } else {
        Ok(())
      }
    }
  }

  #[test]
  fn default1() {
    #[allow(clippy::default_trait_access)]
    let mut val: BinUsizeCount = Default::default();

    let v = val.set("64k").unwrap();
    assert_eq!(v, 65536);
    assert_eq!(val.get(), 65536);
  }

  #[test]
  fn default2() {
    let mut val = <BinUsizeCount>::default();

    let v = val.set("64k").unwrap();
    assert_eq!(v, 65536);
    assert_eq!(val.get(), 65536);
  }

  #[test]
  fn set_get() {
    let mut val = BinUsizeCount::<SomeUsizeBound>::default();

    let v = val.set("64k").unwrap();
    assert_eq!(v, 65536);
    assert_eq!(val.get(), 65536);
  }

  #[test]
  #[should_panic(expected = "OutOfBounds(\"Must be 10 - 256K\")")]
  fn oob() {
    let mut val = BinUsizeCount::<SomeUsizeBound>::default();

    let _v = val.set("512k").unwrap();
  }

  #[cfg(feature = "rusqlite")]
  fn memdb() -> Result<Connection, rusqlite::Error> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch(
      "CREATE TABLE tbl (id INTEGER PRIMARY KEY, txtval TEXT)"
    )?;
    Ok(conn)
  }

  #[cfg(feature = "rusqlite")]
  #[test]
  fn insert_query() {
    let conn = memdb().unwrap();

    let v = "64k".parse::<BinUsizeCount>().unwrap();
    conn
      .execute("INSERT INTO tbl (id, txtval) VALUES (?, ?);", params![1, v])
      .unwrap();

    let v = "256".parse::<BinUsizeCount>().unwrap();
    conn
      .execute("INSERT INTO tbl (id, txtval) VALUES (?, ?);", params![2, v])
      .unwrap();


    let mut stmt = conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();

    let v: BinUsizeCount = stmt.query_one([1], |row| row.get(0)).unwrap();
    assert_eq!(v.get(), 65536);

    let v: BinUsizeCount = stmt.query_one([2], |row| row.get(0)).unwrap();
    assert_eq!(v.get(), 256);
  }


  #[cfg(feature = "rusqlite")]
  #[test]
  #[should_panic(expected = "FromSqlConversionFailure(0, Text, \
                             Invalid(\"invalid digit found in string\"))")]
  fn bad_query() {
    let conn = memdb().unwrap();

    conn
      .execute(
        "INSERT INTO tbl (id, txtval) VALUES (?, ?);",
        params![1, "notanumber"]
      )
      .unwrap();

    let mut stmt = conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();

    let _v: BinUsizeCount = stmt.query_one([1], |row| row.get(0)).unwrap();
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :
Added src/binusizesize.rs.



























































































































































































































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::{marker::PhantomData, str::FromStr};

#[cfg(feature = "rusqlite")]
use rusqlite::{
  ToSql,
  types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}
};

use parse_size::ByteSuffix;

use crate::{AnyUsize, Controller, StrVal, err::Error};


/// A `usize` value that supports suffixes, like `1kb`, `2.5kb`, etc.
///
/// This variant treats k as 1024.  See [`DecUsizeSize`](super::DecUsizeSize)
/// for a decimal variant.
#[derive(Debug, Clone)]
pub struct BinUsizeSize<C = AnyUsize> {
  sval: String,
  val: usize,
  _marker: PhantomData<C>
}

impl<C> Default for BinUsizeSize<C> {
  fn default() -> Self {
    Self {
      sval: "0".into(),
      val: 0,
      _marker: PhantomData
    }
  }
}

impl<C> StrVal for BinUsizeSize<C>
where
  C: Controller<Type = usize>
{
  type Type = usize;

  fn set(&mut self, sval: &str) -> Result<Self::Type, Error> {
    let dv = sval.parse::<Self>()?;

    C::validate(&dv.val)?;

    self.sval = sval.to_string();
    self.val = dv.val;
    Ok(dv.val)
  }

  fn get(&self) -> Self::Type {
    self.val
  }

  fn val_str(&self) -> Option<String> {
    Some(self.val.to_string())
  }
}

impl<C> AsRef<str> for BinUsizeSize<C> {
  fn as_ref(&self) -> &str {
    &self.sval
  }
}

impl<C> FromStr for BinUsizeSize<C> {
  type Err = Error;

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    let val = parse_size::Config::default()
      .with_binary()
      .with_byte_suffix(ByteSuffix::Require)
      .parse_size(s)
      .map_err(|e| Error::Invalid(e.to_string()))?;
    let val =
      usize::try_from(val).map_err(|e| Error::OutOfBounds(e.to_string()))?;
    Ok(Self {
      sval: s.to_string(),
      val,
      _marker: PhantomData
    })
  }
}

#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
impl ToSql for BinUsizeSize {
  fn to_sql(&self) -> Result<ToSqlOutput<'_>, rusqlite::Error> {
    self.sval.to_sql()
  }
}

#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
impl FromSql for BinUsizeSize {
  #[inline]
  fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
    let s = String::column_result(value)?;
    s.parse::<Self>()
      .map_err(|e| FromSqlError::Other(Box::new(e)))
  }
}


#[cfg(test)]
mod tests {
  use super::*;

  #[cfg(feature = "rusqlite")]
  use rusqlite::{Connection, params};

  struct SomeUsizeBound {}

  impl Controller for SomeUsizeBound {
    type Type = usize;
    fn def() -> String {
      String::from("10")
    }
    fn validate(val: &Self::Type) -> Result<(), Error> {
      if *val < 10 || *val > 256 * 1024 {
        Err(Error::OutOfBounds("Must be 10 - 256K".into()))
      } else {
        Ok(())
      }
    }
  }

  #[test]
  fn default1() {
    #[allow(clippy::default_trait_access)]
    let mut val: BinUsizeSize = Default::default();

    let v = val.set("64KB").unwrap();
    assert_eq!(v, 65536);
    assert_eq!(val.get(), 65536);
  }

  #[test]
  fn default2() {
    let mut val = <BinUsizeSize>::default();

    let v = val.set("64kb").unwrap();
    assert_eq!(v, 65536);
    assert_eq!(val.get(), 65536);
  }

  #[test]
  fn set_get() {
    let mut val = BinUsizeSize::<SomeUsizeBound>::default();

    let v = val.set("64kb").unwrap();
    assert_eq!(v, 65536);
    assert_eq!(val.get(), 65536);
  }

  #[test]
  #[should_panic(expected = "OutOfBounds(\"Must be 10 - 256K\")")]
  fn oob() {
    let mut val = BinUsizeSize::<SomeUsizeBound>::default();

    let _v = val.set("512KB").unwrap();
  }


  #[cfg(feature = "rusqlite")]
  fn memdb() -> Result<Connection, rusqlite::Error> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch(
      "CREATE TABLE tbl (id INTEGER PRIMARY KEY, txtval TEXT)"
    )?;
    Ok(conn)
  }

  #[cfg(feature = "rusqlite")]
  #[test]
  fn insert_query() {
    let conn = memdb().unwrap();

    let v = "64kb".parse::<BinUsizeSize>().unwrap();
    conn
      .execute("INSERT INTO tbl (id, txtval) VALUES (?, ?);", params![1, v])
      .unwrap();

    let v = "256b".parse::<BinUsizeSize>().unwrap();
    conn
      .execute("INSERT INTO tbl (id, txtval) VALUES (?, ?);", params![2, v])
      .unwrap();


    let mut stmt = conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();

    let v: BinUsizeSize = stmt.query_one([1], |row| row.get(0)).unwrap();
    assert_eq!(v.get(), 65536);

    let v: BinUsizeSize = stmt.query_one([2], |row| row.get(0)).unwrap();
    assert_eq!(v.get(), 256);
  }

  #[cfg(feature = "rusqlite")]
  #[test]
  #[should_panic(expected = "FromSqlConversionFailure(0, Text, \
                             Invalid(\"invalid digit found in string\"))")]
  fn bad_query() {
    let conn = memdb().unwrap();

    conn
      .execute(
        "INSERT INTO tbl (id, txtval) VALUES (?, ?);",
        params![1, "notanumber"]
      )
      .unwrap();

    let mut stmt = conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();

    let _v: BinUsizeSize = stmt.query_one([1], |row| row.get(0)).unwrap();
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :
Added src/decusizecount.rs.



























































































































































































































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::{marker::PhantomData, str::FromStr};

#[cfg(feature = "rusqlite")]
use rusqlite::{
  ToSql,
  types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}
};

use parse_size::ByteSuffix;

use crate::{AnyUsize, Controller, StrVal, err::Error};


/// A `usize` value that supports suffixes, like `1k`, `2.5k`, etc.
///
/// This variant treats k as 1000.  See [`BinUsizeCount`](super::BinUsizeCount)
/// for a binary variant.
#[derive(Debug, Clone)]
pub struct DecUsizeCount<C = AnyUsize> {
  sval: String,
  val: usize,
  _marker: PhantomData<C>
}

impl<C> Default for DecUsizeCount<C> {
  fn default() -> Self {
    Self {
      sval: "0".into(),
      val: 0,
      _marker: PhantomData
    }
  }
}

impl<C> StrVal for DecUsizeCount<C>
where
  C: Controller<Type = usize>
{
  type Type = usize;

  fn set(&mut self, sval: &str) -> Result<Self::Type, Error> {
    let dv = sval.parse::<Self>()?;

    C::validate(&dv.val)?;

    self.sval = sval.to_string();
    self.val = dv.val;
    Ok(dv.val)
  }

  fn get(&self) -> Self::Type {
    self.val
  }

  fn val_str(&self) -> Option<String> {
    Some(self.val.to_string())
  }
}

impl<C> AsRef<str> for DecUsizeCount<C> {
  fn as_ref(&self) -> &str {
    &self.sval
  }
}

impl<C> FromStr for DecUsizeCount<C> {
  type Err = Error;

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    let val = parse_size::Config::default()
      .with_decimal()
      .with_byte_suffix(ByteSuffix::Deny)
      .parse_size(s)
      .map_err(|e| Error::Invalid(e.to_string()))?;
    let val =
      usize::try_from(val).map_err(|e| Error::OutOfBounds(e.to_string()))?;
    Ok(Self {
      sval: s.to_string(),
      val,
      _marker: PhantomData
    })
  }
}

#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
impl ToSql for DecUsizeCount {
  fn to_sql(&self) -> Result<ToSqlOutput<'_>, rusqlite::Error> {
    self.sval.to_sql()
  }
}

#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
impl FromSql for DecUsizeCount {
  #[inline]
  fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
    let s = String::column_result(value)?;
    s.parse::<Self>()
      .map_err(|e| FromSqlError::Other(Box::new(e)))
  }
}


#[cfg(test)]
mod tests {
  use super::*;

  #[cfg(feature = "rusqlite")]
  use rusqlite::{Connection, params};

  struct SomeUsizeBound {}

  impl Controller for SomeUsizeBound {
    type Type = usize;
    fn def() -> String {
      String::from("10")
    }
    fn validate(val: &Self::Type) -> Result<(), Error> {
      if *val < 10 || *val > 256 * 1000 {
        Err(Error::OutOfBounds("Must be 10 - 256K".into()))
      } else {
        Ok(())
      }
    }
  }

  #[test]
  fn default1() {
    #[allow(clippy::default_trait_access)]
    let mut val: DecUsizeCount = Default::default();

    let v = val.set("64k").unwrap();
    assert_eq!(v, 64_000);
    assert_eq!(val.get(), 64_000);
  }

  #[test]
  fn default2() {
    let mut val = <DecUsizeCount>::default();

    let v = val.set("64k").unwrap();
    assert_eq!(v, 64_000);
    assert_eq!(val.get(), 64_000);
  }

  #[test]
  fn set_get() {
    let mut val = DecUsizeCount::<SomeUsizeBound>::default();

    let v = val.set("64k").unwrap();
    assert_eq!(v, 64_000);
    assert_eq!(val.get(), 64_000);
  }

  #[test]
  #[should_panic(expected = "OutOfBounds(\"Must be 10 - 256K\")")]
  fn oob() {
    let mut val = DecUsizeCount::<SomeUsizeBound>::default();

    let _v = val.set("512k").unwrap();
  }

  #[cfg(feature = "rusqlite")]
  fn memdb() -> Result<Connection, rusqlite::Error> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch(
      "CREATE TABLE tbl (id INTEGER PRIMARY KEY, txtval TEXT)"
    )?;
    Ok(conn)
  }

  #[cfg(feature = "rusqlite")]
  #[test]
  fn insert_query() {
    let conn = memdb().unwrap();

    let v = "64k".parse::<DecUsizeCount>().unwrap();
    conn
      .execute("INSERT INTO tbl (id, txtval) VALUES (?, ?);", params![1, v])
      .unwrap();

    let v = "256".parse::<DecUsizeCount>().unwrap();
    conn
      .execute("INSERT INTO tbl (id, txtval) VALUES (?, ?);", params![2, v])
      .unwrap();


    let mut stmt = conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();

    let v: DecUsizeCount = stmt.query_one([1], |row| row.get(0)).unwrap();
    assert_eq!(v.get(), 64_000);

    let v: DecUsizeCount = stmt.query_one([2], |row| row.get(0)).unwrap();
    assert_eq!(v.get(), 256);
  }


  #[cfg(feature = "rusqlite")]
  #[test]
  #[should_panic(expected = "FromSqlConversionFailure(0, Text, \
                             Invalid(\"invalid digit found in string\"))")]
  fn bad_query() {
    let conn = memdb().unwrap();

    conn
      .execute(
        "INSERT INTO tbl (id, txtval) VALUES (?, ?);",
        params![1, "notanumber"]
      )
      .unwrap();

    let mut stmt = conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();

    let _v: DecUsizeCount = stmt.query_one([1], |row| row.get(0)).unwrap();
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :
Added src/decusizesize.rs.























































































































































































































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
use std::{marker::PhantomData, str::FromStr};

use parse_size::ByteSuffix;

#[cfg(feature = "rusqlite")]
use rusqlite::{
  ToSql,
  types::{FromSql, FromSqlError, FromSqlResult, ToSqlOutput, ValueRef}
};

use crate::{AnyUsize, Controller, StrVal, err::Error};

/// A `usize` value that supports suffixes, like `1kb`, `2.5kb`, etc.
///
/// This variant treats k as 1000.  See [`BinUsizeize`](super::BinUsizeSize)
/// for a binary variant.
#[derive(Debug, Clone)]
pub struct DecUsizeSize<C = AnyUsize> {
  sval: String,
  val: usize,
  _marker: PhantomData<C>
}

impl<C> Default for DecUsizeSize<C> {
  fn default() -> Self {
    Self {
      sval: "0".into(),
      val: 0,
      _marker: PhantomData
    }
  }
}

impl<C> StrVal for DecUsizeSize<C>
where
  C: Controller<Type = usize>
{
  type Type = usize;

  fn set(&mut self, sval: &str) -> Result<Self::Type, Error> {
    let dv = sval.parse::<Self>()?;

    C::validate(&dv.val)?;

    self.sval = sval.to_string();
    self.val = dv.val;
    Ok(dv.val)
  }

  fn get(&self) -> Self::Type {
    self.val
  }

  fn val_str(&self) -> Option<String> {
    Some(self.val.to_string())
  }
}

impl<C> AsRef<str> for DecUsizeSize<C> {
  fn as_ref(&self) -> &str {
    &self.sval
  }
}

impl<C> FromStr for DecUsizeSize<C> {
  type Err = Error;

  fn from_str(s: &str) -> Result<Self, Self::Err> {
    let val = parse_size::Config::default()
      .with_decimal()
      .with_byte_suffix(ByteSuffix::Require)
      .parse_size(s)
      .map_err(|e| Error::Invalid(e.to_string()))?;
    let val =
      usize::try_from(val).map_err(|e| Error::OutOfBounds(e.to_string()))?;
    Ok(Self {
      sval: s.to_string(),
      val,
      _marker: PhantomData
    })
  }
}

#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
impl<C> ToSql for DecUsizeSize<C> {
  fn to_sql(&self) -> Result<ToSqlOutput<'_>, rusqlite::Error> {
    self.sval.to_sql()
  }
}

#[cfg(feature = "rusqlite")]
#[cfg_attr(docsrs, doc(cfg(feature = "rusqlite")))]
impl<C> FromSql for DecUsizeSize<C> {
  #[inline]
  fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
    let s = String::column_result(value)?;
    s.parse::<Self>()
      .map_err(|e| FromSqlError::Other(Box::new(e)))
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[cfg(feature = "rusqlite")]
  use rusqlite::{Connection, params};

  struct SomeUsizeBound {}

  impl Controller for SomeUsizeBound {
    type Type = usize;
    fn def() -> String {
      String::from("10")
    }
    fn validate(val: &Self::Type) -> Result<(), Error> {
      if *val < 10 || *val > 256 * 1000 {
        Err(Error::OutOfBounds("Must be 10 - 256K".into()))
      } else {
        Ok(())
      }
    }
  }

  #[test]
  fn default1() {
    #[allow(clippy::default_trait_access)]
    let mut val: DecUsizeSize = Default::default();

    let v = val.set("64kb").unwrap();
    assert_eq!(v, 64000);
    assert_eq!(val.get(), 64000);
  }

  #[test]
  fn default2() {
    let mut val = <DecUsizeSize>::default();

    let v = val.set("64kb").unwrap();
    assert_eq!(v, 64000);
    assert_eq!(val.get(), 64000);
  }

  #[test]
  fn set_get() {
    let mut val = DecUsizeSize::<SomeUsizeBound>::default();

    let v = val.set("64kb").unwrap();
    assert_eq!(v, 64000);
    assert_eq!(val.get(), 64000);
  }

  #[test]
  #[should_panic(expected = "OutOfBounds(\"Must be 10 - 256K\")")]
  fn oob() {
    let mut val = DecUsizeSize::<SomeUsizeBound>::default();

    let _v = val.set("512kb").unwrap();
  }

  #[cfg(feature = "rusqlite")]
  fn memdb() -> Result<Connection, rusqlite::Error> {
    let conn = Connection::open_in_memory()?;
    conn.execute_batch(
      "CREATE TABLE tbl (id INTEGER PRIMARY KEY, txtval TEXT)"
    )?;
    Ok(conn)
  }

  #[cfg(feature = "rusqlite")]
  #[test]
  fn insert_query() {
    let conn = memdb().unwrap();

    let v = "10kb".parse::<DecUsizeSize>().unwrap();
    conn
      .execute("INSERT INTO tbl (id, txtval) VALUES (?, ?);", params![1, v])
      .unwrap();

    let v = "256b".parse::<DecUsizeSize>().unwrap();
    conn
      .execute("INSERT INTO tbl (id, txtval) VALUES (?, ?);", params![2, v])
      .unwrap();

    let mut stmt = conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();

    let v: DecUsizeSize = stmt.query_one([1], |row| row.get(0)).unwrap();
    assert_eq!(v.get(), 10_000);

    let v: DecUsizeSize = stmt.query_one([2], |row| row.get(0)).unwrap();
    assert_eq!(v.get(), 256);
  }

  #[cfg(feature = "rusqlite")]
  #[test]
  #[should_panic(expected = "FromSqlConversionFailure(0, Text, \
                             Invalid(\"invalid digit found in string\"))")]
  fn bad_query() {
    let conn = memdb().unwrap();

    conn
      .execute(
        "INSERT INTO tbl (id, txtval) VALUES (?, ?);",
        params![1, "notanumber"]
      )
      .unwrap();

    let mut stmt = conn.prepare("SELECT txtval FROM tbl WHERE id=?;").unwrap();

    let _v: DecUsizeSize = stmt.query_one([1], |row| row.get(0)).unwrap();
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :
Changes to src/lib.rs.
37
38
39
40
41
42
43


44
45
46


47
48
49
50
51
52
53
54
55
56
57
58
59






60
61
62
63
64
65
66
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60



61
62
63
64
65
66
67
68
69
70
71
72
73







+
+



+
+










-
-
-
+
+
+
+
+
+







//! |------------|----------
//! | `rusqlite` | Implement `ToSql`/`FromSql` support for value types.

#![cfg_attr(docsrs, feature(doc_cfg))]

mod binu64count;
mod binu64size;
mod binusizecount;
mod binusizesize;
mod boolean;
mod decu64count;
mod decu64size;
mod decusizecount;
mod decusizesize;
mod dur;
mod err;
mod percent;
mod relabslim;
mod select;
mod str;

use std::str::FromStr;

pub use {
  binu64count::BinU64Count, binu64size::BinU64Size, boolean::Bool,
  decu64count::DecU64Count, decu64size::DecU64Size, dur::Dur, err::Error,
  percent::Percent, relabslim::RelAbsLim, select::Select, str::Str
  binu64count::BinU64Count, binu64size::BinU64Size,
  binusizecount::BinUsizeCount, binusizesize::BinUsizeSize, boolean::Bool,
  decu64count::DecU64Count, decu64size::DecU64Size,
  decusizecount::DecUsizeCount, decusizesize::DecUsizeSize, dur::Dur,
  err::Error, percent::Percent, relabslim::RelAbsLim, select::Select,
  str::Str
};

/// Traits for values that can be set from strings.
pub trait StrVal: AsRef<str> + FromStr {
  type Type;

  /// # Errors
81
82
83
84
85
86
87

88
89
















90
91
92

93
94
95
96
97
98
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123







+


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+



+






  /// # Errors
  fn validate(val: &Self::Type) -> Result<(), Error>;
}


/// Accept any `u64`.
pub struct AnyU64;

impl Controller for AnyU64 {
  type Type = u64;

  fn def() -> String {
    String::from("0")
  }

  fn validate(_val: &Self::Type) -> Result<(), Error> {
    Ok(())
  }
}

/// Accept any `usize`.
pub struct AnyUsize;

impl Controller for AnyUsize {
  type Type = usize;

  fn def() -> String {
    String::from("0")
  }

  fn validate(_val: &Self::Type) -> Result<(), Error> {
    Ok(())
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :
Changes to www/changelog.md.
8
9
10
11
12
13
14










15
16
17
18
19
20
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30







+
+
+
+
+
+
+
+
+
+







### Added

### Changed

### Removed

---

## [0.1.1] - 2025-07-09

[Details](/vdiff?from=strval-0.1.0&to=trunk)

### Added

- `BinUsizeCount`, `BinUsizeSize`, `DecUsizeCount`, `DecUsizeSize`

---

## [0.1.0] - 2025-07-09

Initial release