Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API View] Generate API Report in JSON #2019

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ members = [
"eng/test/mock_transport",
"sdk/storage",
"sdk/storage/azure_storage_blob",
"sdk/temp-project/docs",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want this part of the main workspace. We should put this under eng/tools which can have a separate workspace, if even needed there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now. This is for a "template" project. We could use what @hallipr wants as well, which I plan to basically be Key Vault Secrets anyway, which is what most other languages use for their "template" projects.

]

[workspace.package]
Expand Down
13 changes: 13 additions & 0 deletions eng/tools/generate_api_report/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "generate_api_report"
authors = ["Microsoft"]
edition = "2021"
license = "MIT"
repository = "https://github.com/azure/azure-sdk-for-rust"
rust-version = "1.80"

[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[workspace]
37 changes: 37 additions & 0 deletions eng/tools/generate_api_report/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Azure Rust API Exporter

## Generate API Report

This tool generates a JSON report of the API documentation for a specified Rust package.
It uses the following command `cargo +nightly rustdoc -Z unstable-options --output-format json --package {package_name} --all-features` to generate the documentation in JSON format, processes the JSON to remove unnecessary attributes, and outputs a cleaned-up version of the JSON.

## Usage

To run the tool, navigate to the root of the `azure-sdk-for-rust` repository and use the following command:

```sh
cargo run --manifest-path eng/tools/generate_api_report/Cargo.toml -- --package package_name
```

Generates `package_name.rust.json` in the `target/doc/` directory, adjacent to the rustdoc JSON (`package_name.json`) output.

For example, to generate the report for a package named `docs`, run:

```bash
cargo run --manifest-path eng/tools/generate_api_report/Cargo.toml -- --package docs
```

## Functionality

1. **Check for Existing JSON File**: The tool checks if the JSON documentation file for the specified package exists in the `target/doc/` directory.
2. **Generate JSON Documentation**: If the file does not exist, the tool runs `cargo +nightly rustdoc ...` to generate the JSON documentation.
3. **Process JSON**: The tool reads the JSON file, removes the `span` attribute from each item, and retains important attributes like `deprecation`, `inner`, `format_version`, and `paths`.
4. **Output Cleaned JSON**: The tool writes the cleaned-up JSON to a new file in the doc directory with a `.rust.json` suffix.

## Contributing

Contributions are welcome! Please open an issue or submit a pull request with your changes.

## License

This project is licensed under the MIT License.
68 changes: 68 additions & 0 deletions eng/tools/generate_api_report/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::env;
use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::error::Error;
use std::process::Command;
use crate::models::Crate;

mod models;

fn main() -> Result<(), Box<dyn Error>> {
// Get the package name from command-line arguments
let args: Vec<String> = env::args().collect();
if args.len() != 3 || args[1] != "--package" {
eprintln!("Usage: {} --package <package_name>", args[0]);
std::process::exit(1);
}
let package_name = &args[2];
let path_str = format!("./target/doc/{}.json", package_name);
let path = Path::new(&path_str);

// Call cargo +nightly rustdoc to generate the JSON file
let output = Command::new("cargo")
.arg("+nightly")
.arg("rustdoc")
.arg("-Z")
.arg("unstable-options")
.arg("--output-format")
.arg("json")
.arg("--package")
.arg(package_name)
.arg("--all-features")
.output()?;

if !output.status.success() {
eprintln!("Failed to generate JSON file: {}", String::from_utf8_lossy(&output.stderr));
std::process::exit(1);
}

let mut file = File::open(path)?;
let mut contents = String::new();
file.read_to_string(&mut contents)?;

// -------- Prettifying package_name.json - starts -----
// Parse the JSON to ensure it's valid
let json_value: serde_json::Value = serde_json::from_str(&contents)?;

// Write the pretty-printed JSON back to the file
let mut file = File::create(path)?;
serde_json::to_writer_pretty(&mut file, &json_value)?;
// -------- Prettifying package_name.json - ends -----

let mut root: Crate = serde_json::from_str(&contents)?;

// Remove the span attribute from each item
for item in root.index.values_mut() {
item.span = None;
}

let output_path_str = format!("./target/doc/{}.rust.json", package_name);
let output_path = Path::new(&output_path_str);
let mut output_file = File::create(output_path)?;
serde_json::to_writer_pretty(&mut output_file, &root)?;

println!("File has been generated at: {}", output_path_str);

Ok(())
}
28 changes: 28 additions & 0 deletions eng/tools/generate_api_report/src/models.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;

#[derive(Serialize, Deserialize)]
pub struct Item {
pub id: u32,
pub crate_id: u32,
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub span: Option<Value>,
pub visibility: Value,
pub docs: Option<String>,
pub links: HashMap<String, Value>,
pub attrs: Vec<String>,
pub deprecation: Value,
pub inner: Value,
}

#[derive(Serialize, Deserialize)]
pub struct Crate {
pub root: u32,
pub crate_version: Option<String>,
pub index: HashMap<String, Item>,
pub paths: HashMap<String, Value>,
pub external_crates: Value,
pub format_version: u32
}
13 changes: 13 additions & 0 deletions sdk/temp-project/docs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "docs"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
rust-version.workspace = true

[dependencies]

[lints]
workspace = true
79 changes: 79 additions & 0 deletions sdk/temp-project/docs/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
pub mod module_example;

/// This is a sample module
pub mod sample_module {
/// This is a sample function
pub fn sample_function() {
// function implementation
}

/// This is a sample struct
pub struct SampleStruct {
/// This is a sample field
pub field: i32,
}

/// This is a sample struct
#[derive(Debug)]
pub struct SampleStructWithDebug {
/// This is a sample field
pub field: i32,
}
}

#[allow(dead_code)]
static GLOBAL_FIELD1: i32 = 0;

pub trait MyTrait {
// Define some methods or associated functions here
fn example_method(&self);
}

pub fn foo<T: MyTrait, V: MyTrait>(v: &T) {
// function implementation
todo!()
}

pub fn bar<T, V>(v: &T)
where
T: MyTrait,
V: MyTrait,
{
// function implementation
todo!()
}

pub static GLOBAL_FIELD2: &(dyn MyTrait + Sync) = &DummyTrait;
struct DummyTrait;

unsafe impl Sync for DummyTrait {}

impl MyTrait for DummyTrait {
fn example_method(&self) {
// method implementation
}
}

// ...existing code...

/// This is a sample struct that implements Clone
#[derive(Debug, Clone)]
pub struct SampleStructWithClone {
/// This is a sample field
pub field: i32,
}

impl MyTrait for SampleStructWithClone {
fn example_method(&self) {
// method implementation
println!("Example method called on SampleStructWithClone with field: {}", self.field);
}
}

// Example usage of the Clone implementation
pub fn clone_example() {
let original = SampleStructWithClone { field: 42 };
let cloned = original.clone();
println!("Original: {:?}", original);
println!("Cloned: {:?}", cloned);
}
25 changes: 25 additions & 0 deletions sdk/temp-project/docs/src/module_example/lease.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/// This is a sample module
pub mod sample_module2 {
/// This is a sample function
pub fn sample_function2() {
// function implementation
}

/// This is a sample struct
pub struct SampleStruct2 {
// Define the fields of the struct
pub field1: String,
pub field2: i32,
}
}

// Example function that constructs and uses SampleStruct2
pub fn use_sample_struct2() {
let instance = sample_module2::SampleStruct2 {
field1: String::from("example"),
field2: 42,
};

// Use the instance in some way
println!("SampleStruct2: field1 = {}, field2 = {}", instance.field1, instance.field2);
}
2 changes: 2 additions & 0 deletions sdk/temp-project/docs/src/module_example/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

pub mod lease;
Loading