versa 0.40.0

Versa types and utilities for developing Versa client applications in Rust
Documentation
//! This is a regular crate doc comment, but it also contains a partial
//! Cargo manifest.  Note the use of a *fenced* code block, and the
//! `cargo` "language".
//!
//! ```cargo
//! [dependencies]
//! prettyplease = "0.2"
//! ureq = "3.0.11"
//! schemars = "0.8"
//! serde_json = "1.0"
//! syn = "2.0"
//! typify = "0.2"
//! ```

use std::{fs, path::Path};

use typify::{TypeSpace, TypeSpaceSettings};

const SCHEMA_URL: &'static str = "https://raw.githubusercontent.com/versa-protocol/schema";
const SCHEMA_PATH: &'static str = "data";
const SCHEMA_VERSION: &'static str = "1.11.0"; // Update this to the latest version
const SCHEMAS: [&'static str; 2] = ["receipt", "itinerary"];

const LTS_SCHEMA_VERSIONS: [&'static str; 5] = ["1.7.0", "1.8.0", "1.9.0", "1.10.0", "1.11.0"];

fn get_schema_definition_json(schema_name: &str, version: &str) -> Result<String, ()> {
  let schema_url = format!("{}/{}/{}/{}.schema.json", SCHEMA_URL, version, SCHEMA_PATH, schema_name);

  match ureq::get(&schema_url).call() {
    Ok(mut res) => {
      let response_status = res.status();
      if response_status != ureq::http::StatusCode::OK {
        println!("Error fetching schema version {}: {}", version, response_status);
        return Err(())
      }
      return match res.body_mut().read_to_string() {
      Ok(text) => Ok(text),
      Err(e) => {
        println!("Error fetching schema version {}: {:?}", version, e);
        Err(())
      }
    }
  }
    Err(e) => {
      println!("Error fetching schema version {}: {:?}", version, e);
      return Err(())
    }
  }
}

fn generate_schema_bindings(schema_name: &str) {
  let Ok(content) = get_schema_definition_json(schema_name, SCHEMA_VERSION) else {
    return;
  };

  let schema = serde_json::from_str::<schemars::schema::RootSchema>(&content).unwrap();

  let mut type_space = TypeSpace::new(TypeSpaceSettings::default().with_struct_builder(true));
  type_space.add_root_schema(schema).unwrap();

  let contents = prettyplease::unparse(&syn::parse2::<syn::File>(type_space.to_stream()).unwrap())
    .replace("chrono::naive::NaiveDate", "String");

  let mut out_file = Path::new("src/schema").to_path_buf();
  out_file.push(format!("{}.rs", schema_name));
  fs::write(out_file, contents).unwrap();
}

fn bundle_schema_definition(schema: &str, version: &str) {
  let Ok(content) = get_schema_definition_json(schema, version) else {
    return;
  };
  let safe_version = format!("{}", version.replace('.', "_"));

  // write content to file with safe version name
  let safe_version_name = format!("v{}", safe_version);

  let mut out_file = Path::new("src/schema/json").to_path_buf();

//   let wrapped_content = format!(r###"pub static {}_SCHEMA_{}: &'static str = r##"{}"##;
// "###, schema.to_uppercase(), safe_version, content);
  out_file.push(format!("{}_{}.json", schema, safe_version_name));
  fs::write(out_file, content).unwrap();

  // let mod_file = Path::new("src/schema/json").to_path_buf();
  // get contents and add the new module to the end of the file
  // let mut mod_contents = fs::read_to_string(mod_file.join("mod.rs")).unwrap();
  // mod_contents.push_str(&format!("mod {}_{};\n", schema, safe_version_name));
  // fs::write(mod_file.join("mod.rs"), mod_contents).unwrap();
  println!("Bundled schema definition for {} version {}", schema, version);

}

fn bundle_schema_definitions(schema: &str,) {
  for version in LTS_SCHEMA_VERSIONS {
    bundle_schema_definition(schema, version);
  }
}

fn output_current_schema_version() {
  let mut out_file = Path::new("src/schema").to_path_buf();
  out_file.push("current.rs");
  fs::write(out_file, format!("pub const SCHEMA_VERSION: &str = \"{}\";
", SCHEMA_VERSION)).unwrap();
}

fn main() {
  // delete all json files in schema/json
  let json_dir = Path::new("src/schema/json");
  if json_dir.exists() {
    for entry in fs::read_dir(json_dir).unwrap() {
      let entry = entry.unwrap();
      if entry.path().extension().map(|s| s == "json").unwrap_or(false) {
        fs::remove_file(entry.path()).unwrap();
      }
    }
  } else {
    fs::create_dir_all(json_dir).unwrap();
  }

  output_current_schema_version();

  for schema in SCHEMAS.iter() {
    generate_schema_bindings(schema);
    bundle_schema_definitions(schema);
  }
}