Skip to content

Commit 6f940ed

Browse files
committed
Make Bounds::try_from() more generic
* Add `Bounds::from` for `[f64; 4]`, `[f32; 4]`, `[i32; 4]` * Add `Bounds::try_from` now also supports `&[f64]`, `&[f32]`, `&[i32]` in addition to `Vec<f64>` * Simplify CI scripts, reducing the number of external dependencies * Reworked `Bounds` string parsing
1 parent b286beb commit 6f940ed

File tree

5 files changed

+195
-67
lines changed

5 files changed

+195
-67
lines changed

.github/workflows/rust.yml

+7-29
Original file line numberDiff line numberDiff line change
@@ -8,45 +8,23 @@ on:
88
pull_request:
99
schedule: [cron: "45 6 * * *"]
1010

11-
env:
12-
RUST_TOOLCHAIN: stable
13-
TOOLCHAIN_PROFILE: default
14-
1511
name: Run tests
1612
jobs:
1713
lints:
1814
name: Run cargo fmt and cargo clippy
1915
runs-on: ubuntu-latest
2016
steps:
2117
- name: Checkout sources
22-
uses: actions/checkout@v2
23-
- name: Install toolchain
24-
uses: actions-rs/toolchain@v1
25-
with:
26-
profile: ${{ env.TOOLCHAIN_PROFILE }}
27-
toolchain: ${{ env.RUST_TOOLCHAIN }}
28-
override: true
29-
components: rustfmt, clippy
30-
- name: Cache
31-
uses: Swatinem/rust-cache@v1
18+
uses: actions/checkout@v3
19+
- name: Install fmt & clippy
20+
run: rustup component add clippy rustfmt
3221
- name: Run cargo fmt
33-
uses: actions-rs/cargo@v1
34-
with:
35-
command: fmt
36-
args: --all -- --check
22+
run: cargo fmt --all -- --check
3723
- name: Run cargo clippy
38-
uses: actions-rs/cargo@v1
39-
with:
40-
command: clippy
41-
args: -- -D warnings
24+
run: cargo clippy --all-targets --all-features -- -D warnings
4225
- name: Run cargo test
43-
uses: actions-rs/cargo@v1
44-
with:
45-
command: test
26+
run: cargo test
4627
- name: Run cargo docs
47-
uses: actions-rs/cargo@v1
28+
run: cargo doc --no-deps
4829
env:
4930
RUSTDOCFLAGS: -D warnings
50-
with:
51-
command: doc
52-
args: --no-deps

CHANGELOG.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
<a name="v0.3.2"></a>
12
### Pending
2-
*
3+
* Add `Bounds::from` for `[f64; 4]`, `[f32; 4]`, `[i32; 4]`
4+
* Add `Bounds::try_from` now also supports `&[f64]`, `&[f32]`, `&[i32]` in addition to `Vec<f64>`
35

46
<a name="v0.3.1"></a>
57
### v0.3.1 (2022-05-29)

Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tilejson"
3-
version = "0.3.1"
3+
version = "0.3.2"
44
description = "Library for serializing the TileJSON file format"
55
authors = [
66
"Stepan Kuzmin <to.stepan.kuzmin@gmail.com>",

src/bounds.rs

+177-29
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::ParseBoundsError::{BadLen, ParseCoordError};
12
use serde_tuple::{Deserialize_tuple, Serialize_tuple};
23
use std::fmt::{Display, Formatter};
34
use std::num::ParseFloatError;
@@ -148,10 +149,68 @@ pub enum ParseBoundsError {
148149
impl Display for ParseBoundsError {
149150
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
150151
match self {
151-
ParseBoundsError::BadLen => {
152-
f.write_str("Incorrect number of values. Bounds expects four f64 values.")
153-
}
154-
ParseBoundsError::ParseCoordError(e) => e.fmt(f),
152+
BadLen => f.write_str("Incorrect number of values. Bounds expects four f64 values."),
153+
ParseCoordError(e) => e.fmt(f),
154+
}
155+
}
156+
}
157+
158+
impl From<[f64; 4]> for Bounds {
159+
/// Parse four f64 values as a Bounds value, same order as the [Bounds::new] constructor.
160+
///
161+
/// ```
162+
/// # use tilejson::Bounds;
163+
/// assert_eq!(
164+
/// Bounds::new(1., 2., 3., 4.),
165+
/// Bounds::from([1., 2., 3., 4.])
166+
/// );
167+
/// ```
168+
fn from(value: [f64; 4]) -> Self {
169+
Self {
170+
left: value[0],
171+
bottom: value[1],
172+
right: value[2],
173+
top: value[3],
174+
}
175+
}
176+
}
177+
178+
impl From<[f32; 4]> for Bounds {
179+
/// Parse four f32 values as a Bounds value, same order as the [Bounds::new] constructor.
180+
///
181+
/// ```
182+
/// # use tilejson::Bounds;
183+
/// assert_eq!(
184+
/// Bounds::new(1., 2., 3., 4.),
185+
/// Bounds::from([1.0f32, 2.0f32, 3.0f32, 4.0f32])
186+
/// );
187+
/// ```
188+
fn from(value: [f32; 4]) -> Self {
189+
Self {
190+
left: value[0] as f64,
191+
bottom: value[1] as f64,
192+
right: value[2] as f64,
193+
top: value[3] as f64,
194+
}
195+
}
196+
}
197+
198+
impl From<[i32; 4]> for Bounds {
199+
/// Parse four i32 values as a Bounds value, same order as the [Bounds::new] constructor.
200+
///
201+
/// ```
202+
/// # use tilejson::Bounds;
203+
/// assert_eq!(
204+
/// Bounds::new(1., 2., 3., 4.),
205+
/// Bounds::from([1, 2, 3, 4])
206+
/// );
207+
/// ```
208+
fn from(value: [i32; 4]) -> Self {
209+
Self {
210+
left: value[0] as f64,
211+
bottom: value[1] as f64,
212+
right: value[2] as f64,
213+
top: value[3] as f64,
155214
}
156215
}
157216
}
@@ -160,17 +219,71 @@ impl TryFrom<Vec<f64>> for Bounds {
160219
type Error = ParseBoundsError;
161220

162221
/// Parse four f64 values as a Bounds value, same order as the [Bounds::new] constructor.
222+
///
223+
/// ```
224+
/// # use tilejson::Bounds;
225+
/// assert_eq!(
226+
/// Bounds::new(1., 2., 3., 4.),
227+
/// Bounds::try_from(vec![1., 2., 3., 4.]).unwrap()
228+
/// );
229+
/// ```
163230
fn try_from(value: Vec<f64>) -> Result<Self, Self::Error> {
164-
if value.len() == 4 {
165-
Ok(Self {
166-
left: value[0],
167-
bottom: value[1],
168-
right: value[2],
169-
top: value[3],
170-
})
171-
} else {
172-
Err(ParseBoundsError::BadLen)
173-
}
231+
let arr: [f64; 4] = value.try_into().map_err(|_| BadLen)?;
232+
Ok(arr.into())
233+
}
234+
}
235+
236+
impl TryFrom<&[f64]> for Bounds {
237+
type Error = ParseBoundsError;
238+
239+
/// Parse four f64 values as a Bounds value, same order as the [Bounds::new] constructor.
240+
///
241+
/// ```
242+
/// # use tilejson::Bounds;
243+
/// assert_eq!(
244+
/// Bounds::new(1., 2., 3., 4.),
245+
/// Bounds::try_from(vec![1., 2., 3., 4.].as_slice()).unwrap()
246+
/// );
247+
/// ```
248+
fn try_from(value: &[f64]) -> Result<Self, Self::Error> {
249+
let arr: [f64; 4] = value.try_into().map_err(|_| BadLen)?;
250+
Ok(arr.into())
251+
}
252+
}
253+
254+
impl TryFrom<&[f32]> for Bounds {
255+
type Error = ParseBoundsError;
256+
257+
/// Parse four f32 values as a Bounds value, same order as the [Bounds::new] constructor.
258+
///
259+
/// ```
260+
/// # use tilejson::Bounds;
261+
/// assert_eq!(
262+
/// Bounds::new(1., 2., 3., 4.),
263+
/// Bounds::try_from(vec![1.0f32, 2.0f32, 3.0f32, 4.0f32].as_slice()).unwrap()
264+
/// );
265+
/// ```
266+
fn try_from(value: &[f32]) -> Result<Self, Self::Error> {
267+
let arr: [f32; 4] = value.try_into().map_err(|_| BadLen)?;
268+
Ok(arr.into())
269+
}
270+
}
271+
272+
impl TryFrom<&[i32]> for Bounds {
273+
type Error = ParseBoundsError;
274+
275+
/// Parse four i32 values as a Bounds value, same order as the [Bounds::new] constructor.
276+
///
277+
/// ```
278+
/// # use tilejson::Bounds;
279+
/// assert_eq!(
280+
/// Bounds::new(1., 2., 3., 4.),
281+
/// Bounds::try_from(vec![1, 2, 3, 4].as_slice()).unwrap()
282+
/// );
283+
/// ```
284+
fn try_from(value: &[i32]) -> Result<Self, Self::Error> {
285+
let arr: [i32; 4] = value.try_into().map_err(|_| BadLen)?;
286+
Ok(arr.into())
174287
}
175288
}
176289

@@ -188,28 +301,26 @@ impl FromStr for Bounds {
188301
/// assert_eq!(bounds, Bounds::new(-1.0, -2.0, 3.0, 4.0));
189302
/// ```
190303
fn from_str(s: &str) -> Result<Self, Self::Err> {
191-
let mut vals = s.split(',').map(|s| s.trim());
192-
let mut next_val = || {
193-
vals.next().map_or(Err(ParseBoundsError::BadLen), |v| {
194-
v.parse().map_err(ParseBoundsError::ParseCoordError)
195-
})
196-
};
197-
let bounds = Self {
198-
left: next_val()?,
199-
bottom: next_val()?,
200-
right: next_val()?,
201-
top: next_val()?,
202-
};
203-
match vals.next() {
204-
Some(_) => Err(ParseBoundsError::BadLen),
205-
None => Ok(bounds),
304+
let mut values = s.split(',');
305+
let mut result = [0.; 4];
306+
for i in 0..4 {
307+
result[i] = values
308+
.next()
309+
.ok_or(ParseBoundsError::BadLen)?
310+
.trim()
311+
.parse()
312+
.map_err(ParseBoundsError::ParseCoordError)?;
206313
}
314+
values
315+
.next()
316+
.map_or(Ok(result.into()), |_| Err(ParseBoundsError::BadLen))
207317
}
208318
}
209319

210320
#[cfg(test)]
211321
mod tests {
212322
use super::*;
323+
use crate::ParseBoundsError::BadLen;
213324

214325
#[test]
215326
fn test_parse_err() {
@@ -232,4 +343,41 @@ mod tests {
232343
assert_eq!(val("0,0,0,0"), Bounds::new(0.0, 0.0, 0.0, 0.0));
233344
assert_eq!(val(" 1 ,2.0, 3.0, 4.0 "), Bounds::new(1.0, 2.0, 3.0, 4.0));
234345
}
346+
347+
#[test]
348+
fn test_parse_errors() {
349+
let err = |s| Bounds::from_str(s).unwrap_err();
350+
assert_eq!(err("0,0,0"), BadLen);
351+
assert_eq!(err("0,0,0,0,0"), BadLen);
352+
assert!(matches!(err(""), ParseCoordError(_)));
353+
assert!(matches!(err("a"), ParseCoordError(_)));
354+
assert!(matches!(err("0,0,0,1a"), ParseCoordError(_)));
355+
}
356+
357+
#[test]
358+
fn test_from() -> Result<(), ParseBoundsError> {
359+
let exp = Bounds::new(1.0, 2.0, 3.0, 4.0);
360+
assert_eq!(exp, Bounds::from([1.0, 2.0, 3.0, 4.0]));
361+
assert_eq!(exp, Bounds::try_from([1.0, 2.0, 3.0, 4.0].as_slice())?);
362+
assert_eq!(exp, Bounds::try_from(vec![1.0, 2.0, 3.0, 4.0])?);
363+
let val = vec![1.0, 2.0, 3.0, 4.0];
364+
assert_eq!(exp, Bounds::try_from((&val).as_slice())?);
365+
assert_eq!(exp, Bounds::try_from(val.as_slice())?);
366+
367+
// f32
368+
assert_eq!(exp, Bounds::from([1.0f32, 2.0f32, 3.0f32, 4.0f32]));
369+
let val_array = [1.0f32, 2.0f32, 3.0f32, 4.0f32];
370+
assert_eq!(exp, Bounds::try_from(val_array.as_slice())?);
371+
let val = vec![1.0f32, 2.0f32, 3.0f32, 4.0f32];
372+
assert_eq!(exp, Bounds::try_from((&val).as_slice())?);
373+
assert_eq!(exp, Bounds::try_from(val.as_slice())?);
374+
375+
// i32
376+
assert_eq!(exp, Bounds::from([1, 2, 3, 4]));
377+
assert_eq!(exp, Bounds::try_from([1, 2, 3, 4].as_slice())?);
378+
let val = vec![1, 2, 3, 4];
379+
assert_eq!(exp, Bounds::try_from((&val).as_slice())?);
380+
assert_eq!(exp, Bounds::try_from(val.as_slice())?);
381+
Ok(())
382+
}
235383
}

src/tilejson.rs

+7-7
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ mod tests {
315315
]
316316
}"#;
317317

318-
let mut tilejson: TileJSON = serde_json::from_str(&tilejson_str).unwrap();
318+
let mut tilejson: TileJSON = serde_json::from_str(tilejson_str).unwrap();
319319

320320
assert_eq!(
321321
tilejson,
@@ -388,11 +388,11 @@ mod tests {
388388

389389
#[test]
390390
fn test_bad_json() {
391-
parse(&r#"{"tilejson":"3.0.0", "tiles":["x"], "center":[]}"#).unwrap_err();
392-
parse(&r#"{"tilejson":"3.0.0", "tiles":["x"], "center":[1,2]}"#).unwrap_err();
393-
parse(&r#"{"tilejson":"3.0.0", "tiles":["x"], "center":[1,2,3,4]}"#).unwrap_err();
394-
parse(&r#"{"tilejson":"3.0.0", "tiles":["x"], "bounds":[]}"#).unwrap_err();
395-
parse(&r#"{"tilejson":"3.0.0", "tiles":["x"], "bounds":[1,2,3]}"#).unwrap_err();
396-
parse(&r#"{"tilejson":"3.0.0", "tiles":["x"], "bounds":[1,2,3,4,5]}"#).unwrap_err();
391+
parse(r#"{"tilejson":"3.0.0", "tiles":["x"], "center":[]}"#).unwrap_err();
392+
parse(r#"{"tilejson":"3.0.0", "tiles":["x"], "center":[1,2]}"#).unwrap_err();
393+
parse(r#"{"tilejson":"3.0.0", "tiles":["x"], "center":[1,2,3,4]}"#).unwrap_err();
394+
parse(r#"{"tilejson":"3.0.0", "tiles":["x"], "bounds":[]}"#).unwrap_err();
395+
parse(r#"{"tilejson":"3.0.0", "tiles":["x"], "bounds":[1,2,3]}"#).unwrap_err();
396+
parse(r#"{"tilejson":"3.0.0", "tiles":["x"], "bounds":[1,2,3,4,5]}"#).unwrap_err();
397397
}
398398
}

0 commit comments

Comments
 (0)