// Copyright 2022, 2023 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. use std::{collections::HashMap, fmt::Display, sync::Arc}; use camino::{Utf8Path, Utf8PathBuf}; use tokio::io::AsyncWriteExt; use tracing::Level; type Client = hyper::Client; mod gen; 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: impl AsRef) -> Utf8PathBuf { let crate_root = Utf8Path::new(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: impl AsRef) -> anyhow::Result<()> { let mut file = tokio::fs::OpenOptions::new() .create(true) .truncate(true) .write(true) .open(path.as_ref()) .await?; tracing::info!("Writing file"); file.write_all(format!("{self}").as_bytes()).await?; Ok(()) } } impl Display for File { #[allow(clippy::too_many_lines)] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { writeln!( f, r#"// Copyright 2023 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"#, self.registry_name, self.registry_url, )?; for section in &self.sections { let Some(list) = self.items.get(section.key) else { continue; }; let is_exhaustive = section.key == "OAuthAuthorizationEndpointResponseType"; writeln!(f)?; self::gen::struct_def(f, section, list, is_exhaustive)?; writeln!(f)?; // Write the Display impl self::gen::display_impl(f, section, list, is_exhaustive)?; writeln!(f)?; // Write the FromStr impl self::gen::from_str_impl(f, section, list, is_exhaustive)?; writeln!(f)?; // Write the Serialize and Deserialize impls self::gen::serde_impl(f, section)?; writeln!(f)?; // Write the JsonSchema impl self::gen::json_schema_impl(f, section, list)?; } Ok(()) } } use self::traits::{EnumEntry, EnumMember, Section}; #[tracing::instrument(skip_all, fields(%path))] async fn generate_jose( client: &Arc, path: impl AsRef + std::fmt::Display, ) -> 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_all, fields(%path))] async fn generate_oauth( client: &Arc, path: impl AsRef + std::fmt::Display, ) -> 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::new(); let client = Arc::new(client); let iana_crate_root = Utf8Path::new("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(()) }