// Copyright 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![forbid(unsafe_code)] #![deny(clippy::all, rustdoc::broken_intra_doc_links)] #![warn(clippy::pedantic)] use std::{collections::HashMap, fmt::Display, path::PathBuf, sync::Arc}; use reqwest::Client; use tokio::io::AsyncWriteExt; use tracing::Level; pub mod jose; pub mod oauth; pub mod traits; #[derive(Debug)] struct File { client: Arc, registry_name: &'static str, registry_url: &'static str, sections: Vec
, items: HashMap<&'static str, Vec>, } fn resolve_path(relative: PathBuf) -> PathBuf { let crate_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")); let workspace_root = crate_root.parent().unwrap().parent().unwrap(); workspace_root.join(relative) } impl File { #[tracing::instrument(skip(client))] fn new(registry_name: &'static str, registry_url: &'static str, client: Arc) -> Self { tracing::info!("Generating file from IANA registry"); Self { client, registry_name, registry_url, sections: Vec::new(), items: HashMap::new(), } } #[tracing::instrument(skip_all, fields(url))] async fn load(mut self) -> anyhow::Result { tracing::Span::current().record("url", &T::URL); self.sections.extend(T::sections()); for (key, value) in T::fetch(&self.client).await? { self.items.entry(key).or_default().push(value); } Ok(self) } #[tracing::instrument(skip_all)] async fn write(&self, path: PathBuf) -> anyhow::Result<()> { let mut file = tokio::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(path) .await?; tracing::info!("Writing file"); file.write_all(format!("{}", self).as_bytes()).await?; Ok(()) } } impl Display for File { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!( f, r#"// Copyright 2022 The Matrix.org Foundation C.I.C. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Enums from the {:?} IANA registry //! See <{}> // Do not edit this file manually use parse_display::{{Display, FromStr}}; use schemars::JsonSchema; use serde::{{Deserialize, Serialize}};"#, self.registry_name, self.registry_url, )?; for section in &self.sections { let list = if let Some(list) = self.items.get(section.key) { list } else { continue; }; write!( f, r#" /// {} /// /// Source: <{}> #[derive( Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Display, FromStr, Serialize, Deserialize, JsonSchema, )] pub enum {} {{"#, section.doc, section.url.unwrap(), section.key, )?; for member in list { writeln!(f)?; if let Some(description) = &member.description { writeln!(f, " /// {}", description)?; } else { writeln!(f, " /// `{}`", member.value)?; } writeln!(f, " #[serde(rename = \"{}\")]", member.value)?; writeln!(f, " #[display(\"{}\")]", member.value)?; writeln!(f, " {},", member.enum_name)?; } writeln!(f, "}}")?; } Ok(()) } } use self::traits::{EnumEntry, EnumMember, Section}; #[tracing::instrument(skip(client))] async fn generate_jose(client: &Arc, path: PathBuf) -> anyhow::Result<()> { let path = resolve_path(path); let client = client.clone(); let file = File::new( "JSON Object Signing and Encryption", "https://www.iana.org/assignments/oauth-parameters/oauth-parameters.xhtml", client.clone(), ) .load::() .await? .load::() .await? .load::() .await? .load::() .await? .load::() .await? .load::() .await?; file.write(path).await?; Ok(()) } #[tracing::instrument(skip(client))] async fn generate_oauth(client: &Arc, path: PathBuf) -> anyhow::Result<()> { let path = resolve_path(path); let client = client.clone(); let file = File::new( "OAuth Parameters", "https://www.iana.org/assignments/jose/jose.xhtml", client.clone(), ) .load::() .await? .load::() .await? .load::() .await? .load::() .await? .load::() .await?; file.write(path).await?; Ok(()) } #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() .with_max_level(Level::INFO) .pretty() .init(); let client = Client::builder().user_agent("iana-parser/0.0.1").build()?; let client = Arc::new(client); let iana_crate_root = PathBuf::from("crates/iana/"); generate_jose(&client, iana_crate_root.join("src/jose.rs")).await?; generate_oauth(&client, iana_crate_root.join("src/oauth.rs")).await?; Ok(()) }