geopackage_core/version.rs
1//! GeoPackage file identity: SQLite `application_id` and `user_version` pragmas.
2
3use std::fmt;
4
5/// `application_id` for GeoPackage 1.2+: "GPKG".
6pub const APPLICATION_ID_GPKG: u32 = 0x4750_4B47;
7/// Legacy `application_id` for GeoPackage 1.0: "GP10".
8pub const APPLICATION_ID_GP10: u32 = 0x4750_3130;
9/// Legacy `application_id` for GeoPackage 1.1: "GP11".
10pub const APPLICATION_ID_GP11: u32 = 0x4750_3131;
11
12/// GeoPackage specification version, as declared by a file's pragmas.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
14#[non_exhaustive]
15pub enum GpkgVersion {
16 /// GeoPackage 1.0 (`application_id` "GP10").
17 V1_0,
18 /// GeoPackage 1.1 (`application_id` "GP11").
19 V1_1,
20 /// GeoPackage 1.2.x (`user_version` 102xx).
21 V1_2,
22 /// GeoPackage 1.3.x (`user_version` 103xx).
23 V1_3,
24 /// GeoPackage 1.4.x (`user_version` 104xx).
25 V1_4,
26}
27
28impl GpkgVersion {
29 /// Classifies a file from its `application_id` and `user_version` pragmas.
30 ///
31 /// Returns `None` if the pragmas do not identify a GeoPackage.
32 /// `user_version` values above the newest known 104xx range map to
33 /// `V1_4`, because the spec says clients should attempt to read newer minor
34 /// versions.
35 ///
36 /// # Examples
37 ///
38 /// ```
39 /// use geopackage_core::GpkgVersion;
40 /// use geopackage_core::version::APPLICATION_ID_GPKG;
41 ///
42 /// let version = GpkgVersion::from_pragmas(APPLICATION_ID_GPKG, 10400);
43 /// assert_eq!(version, Some(GpkgVersion::V1_4));
44 /// ```
45 pub fn from_pragmas(application_id: u32, user_version: u32) -> Option<Self> {
46 match application_id {
47 APPLICATION_ID_GP10 => Some(Self::V1_0),
48 APPLICATION_ID_GP11 => Some(Self::V1_1),
49 APPLICATION_ID_GPKG => match user_version / 100 {
50 102 => Some(Self::V1_2),
51 103 => Some(Self::V1_3),
52 v if v >= 104 => Some(Self::V1_4),
53 _ => None,
54 },
55 _ => None,
56 }
57 }
58
59 /// Returns the `user_version` this crate writes for the given version, or
60 /// `None` for 1.0 and 1.1, which predate `user_version` use.
61 pub fn user_version(self) -> Option<u32> {
62 match self {
63 Self::V1_0 | Self::V1_1 => None,
64 Self::V1_2 => Some(10200),
65 Self::V1_3 => Some(10300),
66 Self::V1_4 => Some(10400),
67 }
68 }
69
70 /// Returns the version as written in the specification, such as `"1.4"`.
71 ///
72 /// The patch level is not included. `user_version` records it, but the
73 /// classification drops it: a `user_version` of 10401 and one of 10400
74 /// both classify as [`Self::V1_4`].
75 pub fn as_str(self) -> &'static str {
76 match self {
77 Self::V1_0 => "1.0",
78 Self::V1_1 => "1.1",
79 Self::V1_2 => "1.2",
80 Self::V1_3 => "1.3",
81 Self::V1_4 => "1.4",
82 }
83 }
84}
85
86impl fmt::Display for GpkgVersion {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 f.write_str(self.as_str())
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 #[test]
97 fn classification() {
98 assert_eq!(
99 GpkgVersion::from_pragmas(APPLICATION_ID_GPKG, 10400),
100 Some(GpkgVersion::V1_4)
101 );
102 assert_eq!(
103 GpkgVersion::from_pragmas(APPLICATION_ID_GPKG, 10201),
104 Some(GpkgVersion::V1_2)
105 );
106 // future minor versions read as newest-known
107 assert_eq!(
108 GpkgVersion::from_pragmas(APPLICATION_ID_GPKG, 10500),
109 Some(GpkgVersion::V1_4)
110 );
111 assert_eq!(
112 GpkgVersion::from_pragmas(APPLICATION_ID_GP10, 0),
113 Some(GpkgVersion::V1_0)
114 );
115 assert_eq!(GpkgVersion::from_pragmas(0x1234_5678, 10400), None);
116 assert_eq!(GpkgVersion::from_pragmas(APPLICATION_ID_GPKG, 0), None);
117 }
118}