cik/lib.rs
1//! # cik
2//!
3//! `cik` provides a `CIK` type for working with validated (syntax only)
4//! [Central Index Keys (CIKs)](https://www.sec.gov/page/edgar-glossary#cik) as defined in the
5//! [U.S. Securities and Exchange Commission's (SEC)](https://www.sec.gov)
6//! [Electronic Data Gathering, Analysis, and Retrieval system (EDGAR)](https://www.sec.gov/edgar.shtml)
7//! documentation.
8//!
9//! A CIK is a number of up to 10 digits length. They are sometimes rendered as strings with or without leading zeros, and
10//! sometimes are represented as integers of a sufficient number of bits to represent a 10-digit number (typically 64 bits
11//! because the maximum CIK value is 9,999,999,999 and the maximum value of a signed 32-bit integer is only 2,147,483,64;
12//! and the maximum value of an unsigned 32-bit integer is still to low at 4,294,967,295).
13//!
14//! As of 2022-05-29 the "Company Facts" data set's minimum CIK value is 1,750 and the maximum is 1,923,807. See
15//! the "Bulk data" section on the SEC's
16//! [EDGAR Application Programming Interfaces](https://www.sec.gov/edgar/sec-api-documentation) page for more information on
17//! this data set. Such values fit comfortably in 7 decimal digits or 32 bits (signed or unsigned), so you might encounter
18//! CIKs stored in values of less than 64 bits.
19//!
20//! Nonetheless, this library uses 64-bit values to ensure full conformance with the CIK definition of up to 10 decimal
21//! digits.
22//!
23//! ## Optional features
24//!
25//! This crate provides optional features for extended functionality:
26//!
27//! * `serde` - Enables serialization and deserialization via [serde](https://crates.io/crates/serde).
28//! CIK values serialize as integers and can deserialize from integers or strings.
29//! * `schemars` - Enables JSON Schema generation via [schemars](https://crates.io/crates/schemars).
30//! Generates schemas that accept both integer and string representations.
31//!
32//! ## Related crates
33//!
34//! This crate is part of the Financial Identifiers series:
35//!
36//! * [CIK](https://crates.io/crates/cik): Central Index Key (SEC EDGAR)
37//! * [CUSIP](https://crates.io/crates/cusip): Committee on Uniform Security Identification Procedures (ANSI X9.6-2020)
38//! * [ISIN](https://crates.io/crates/isin): International Securities Identification Number (ISO 6166:2021)
39//! * [LEI](https://crates.io/crates/lei): Legal Entity Identifier (ISO 17442:2020)
40//!
41
42use std::fmt;
43use std::str::FromStr;
44
45pub mod error;
46pub use error::CIKError;
47
48/// Parse a string to a valid CIK or an error message, requiring the string to already be only
49/// digits with no leading or trailing whitespace in addition to being the
50/// right length and format.
51pub fn parse(value: &str) -> Result<CIK, CIKError> {
52 let s: String = value.into();
53
54 if s.is_empty() || s.len() > 10 {
55 Err(CIKError::InvalidLength { was: s.len() })
56 } else {
57 match s.parse::<u64>() {
58 Ok(value) => build(value),
59 Err(_err) => Err(CIKError::InvalidFormat { was: s }),
60 }
61 }
62}
63
64/// Build a CIK from an integer _Value_.
65pub fn build(value: u64) -> Result<CIK, CIKError> {
66 if !(1..=9_999_999_999).contains(&value) {
67 return Err(CIKError::InvalidValue { was: value });
68 }
69
70 Ok(CIK(value))
71}
72
73/// Test whether or not the passed string is in valid CIK format, without producing a CIK struct
74/// value.
75pub fn validate(value: &str) -> bool {
76 if value.is_empty() || value.len() > 10 {
77 println!("Bad length: {value:?}");
78 return false;
79 }
80
81 // We make the preliminary assumption that the string is pure ASCII, so we work with the
82 // underlying bytes. If there is Unicode in the string, the bytes will be outside the
83 // allowed range and format validation will fail.
84
85 let b = value.as_bytes();
86
87 b.iter().all(|b| *b >= b'0' && *b <= b'9')
88}
89
90#[doc = include_str!("../README.md")]
91#[cfg(doctest)]
92pub struct ReadmeDoctests;
93
94/// A CIK in confirmed valid format.
95///
96/// You cannot construct a CIK value manually. This does not compile:
97///
98/// ```compile_fail
99/// use cik;
100/// let cannot_construct = cik::CIK(0);
101/// ```
102#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Hash)]
103#[repr(transparent)]
104#[allow(clippy::upper_case_acronyms)]
105pub struct CIK(u64);
106
107impl fmt::Display for CIK {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 write!(f, "{}", self.0)
110 }
111}
112
113impl fmt::Debug for CIK {
114 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
115 write!(f, "CIK{}", self.0)
116 }
117}
118
119impl FromStr for CIK {
120 type Err = CIKError;
121
122 fn from_str(s: &str) -> Result<Self, Self::Err> {
123 parse(s)
124 }
125}
126
127impl TryFrom<u64> for CIK {
128 type Error = CIKError;
129
130 fn try_from(value: u64) -> Result<Self, Self::Error> {
131 build(value)
132 }
133}
134
135impl CIK {
136 /// Return the underlying integer _Value_ of the CIK.
137 pub fn value(&self) -> u64 {
138 self.0
139 }
140}
141
142#[cfg(feature = "serde")]
143pub mod serde;
144
145#[cfg(feature = "schemars")]
146pub mod schemars;
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151 use proptest::prelude::*;
152
153 #[test]
154 fn parse_cik_for_apple() {
155 match parse("320193") {
156 Ok(cik) => {
157 assert_eq!(cik.to_string(), "320193");
158 assert_eq!(cik.value(), 320193);
159 }
160 Err(err) => panic!("Did not expect parsing to fail: {}", err),
161 }
162 }
163 #[test]
164 fn build_cik_for_apple() {
165 match build(320193) {
166 Ok(cik) => {
167 assert_eq!(cik.to_string(), "320193");
168 assert_eq!(cik.value(), 320193);
169 }
170 Err(err) => panic!("Did not expect building to fail: {}", err),
171 }
172 }
173
174 #[test]
175 fn reject_empty_string() {
176 let res = parse("");
177 assert!(res.is_err());
178 }
179
180 #[test]
181 fn reject_non_digit_string() {
182 let res = parse("-1");
183 assert!(res.is_err());
184 }
185
186 #[test]
187 fn reject_zero_string() {
188 let res = parse("0");
189 assert!(res.is_err());
190 }
191
192 #[test]
193 fn reject_long_string() {
194 let res = parse("10000000000");
195 assert!(res.is_err());
196 }
197
198 #[test]
199 fn reject_zero_value() {
200 let res = build(0);
201 assert!(res.is_err());
202 }
203
204 #[test]
205 fn reject_large_value() {
206 let res = build(10_000_000_000);
207 assert!(res.is_err());
208 }
209
210 proptest! {
211 #[test]
212 #[allow(unused_must_use)]
213 fn doesnt_crash(s in "\\PC*") {
214 parse(&s);
215 }
216 }
217}