Loading Cargo.lock +1 −0 Original line number Diff line number Diff line Loading @@ -2114,6 +2114,7 @@ name = "s3s-codegen" version = "0.0.0" dependencies = [ "heck", "http 1.2.0", "nugine-rust-utils", "numeric_cast", "regex", Loading codegen/Cargo.toml +1 −0 Original line number Diff line number Diff line Loading @@ -18,3 +18,4 @@ serde = { version = "1.0.210", features = ["derive"] } serde_json = { version = "1.0.128", features = ["preserve_order"] } serde_urlencoded = "0.7.1" s3s-model = { version = "0.11.0-dev", path = "../crates/s3s-model" } http = "1.2.0" codegen/src/v1/error.rs +60 −14 Original line number Diff line number Diff line Loading @@ -4,13 +4,17 @@ use super::smithy; use crate::declare_codegen; use std::collections::BTreeMap; use std::fs; use std::ops::Not; use heck::ToShoutySnakeCase; use regex::Regex; use scoped_writer::g; use serde::Deserialize; use serde::Serialize; use stdx::default::default; #[derive(Debug)] struct Error { code: String, description: Vec<Option<String>>, Loading @@ -36,7 +40,7 @@ fn collect_errors(model: &smithy::Model) -> Errors { let Some(cap) = pattern.captures(line) else { continue }; let tag = cap.get(1).unwrap().as_str(); assert_eq!(tag, "Code:"); o(code_pattern.captures(line).unwrap().get(2).unwrap().as_str()) o(code_pattern.captures(line).unwrap().get(2).unwrap().as_str().trim()) }; let description = loop { Loading Loading @@ -121,23 +125,62 @@ fn collect_errors(model: &smithy::Model) -> Errors { errors } #[derive(Debug, Serialize, Deserialize)] struct ErrorCode { code: String, description: String, http_status_code: Option<u16>, } // https://github.com/Nugine/s3s/issues/224 fn patch_extra_errors(errors: &mut Errors) { // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ReplicationErrorCodeList // // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ReplicationErrorCodeList // { // let code = "ReplicationConfigurationNotFoundError"; // let desc = "There is no replication configuration for this bucket."; // let status = "404 Not Found"; // errors.insert( // code.to_owned(), // Error { // code: code.to_owned(), // description: vec![Some(desc.to_owned())], // status: vec![Some(status.to_owned())], // }, // ); // } { let code = "ReplicationConfigurationNotFoundError"; let desc = "There is no replication configuration for this bucket."; let status = "404 Not Found"; let extra: BTreeMap<String, Vec<ErrorCode>> = serde_json::from_str(fs::read_to_string("data/s3_error_codes.json").unwrap().as_str()).unwrap(); for group in extra.values() { for ec in group { if errors.contains_key(&ec.code) { continue; } if ec.code == "503 SlowDown" { continue; } if ec.code.contains('.') { continue; } errors.insert( code.to_owned(), ec.code.clone(), Error { code: code.to_owned(), description: vec![Some(desc.to_owned())], status: vec![Some(status.to_owned())], code: ec.code.clone(), description: vec![Some(ec.description.clone())], status: vec![ec.http_status_code.map(|s| { let status = http::StatusCode::from_u16(s).unwrap(); let reason = status.canonical_reason().unwrap(); format!("{s} {reason}") })], }, ); } } } } #[allow(clippy::too_many_lines)] pub fn codegen(model: &smithy::Model) { Loading @@ -147,6 +190,7 @@ pub fn codegen(model: &smithy::Model) { g([ "#![allow(clippy::doc_markdown)]", "#![allow(clippy::too_many_lines)]", "", "use bytestring::ByteString;", "use hyper::StatusCode;", Loading Loading @@ -177,7 +221,9 @@ pub fn codegen(model: &smithy::Model) { let status = &err.status[0]; if let Some(desc) = desc { g!("/// {desc}"); for line in desc.lines() { g!("/// {}", line); } } if let Some(status) = status { if desc.is_some() { Loading crates/s3s/src/error/generated.rs +1944 −218 File changed.Preview size limit exceeded, changes collapsed. Show changes data/crawl.py +58 −45 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ from bs4 import BeautifulSoup import requests import typer cli = typer.Typer() cli = typer.Typer(pretty_exceptions_show_locals=False) model_dir = Path(__file__).parent Loading Loading @@ -46,8 +46,16 @@ def crawl_error_codes(): soup = BeautifulSoup(html, "lxml") h2_id = "SelectObjectContentErrorCodeList" kinds = [ ("S3", "ErrorCodeList"), ("Replication", "ReplicationErrorCodeList"), ("Tagging", "S3TaggingErrorCodeList"), ("SelectObjectContent", "SelectObjectContentErrorCodeList"), ] data = {} for kind, h2_id in kinds: h2 = soup.css.select(f"#{h2_id}")[0] # type:ignore # find the next table Loading @@ -59,37 +67,42 @@ def crawl_error_codes(): assert table is not None th_list = table.css.select("th") # type:ignore assert th_list[0].text == "Error code" assert th_list[0].text in ("Error code", "Error Code") assert th_list[1].text == "Description" assert th_list[2].text == "HTTP status code" assert th_list[2].text in ("HTTP status code", "HTTP Status Code") tr_list = table.css.select("tr")[1:] # type:ignore tr_list = [[e for e in tr.children if e.name == "td"] for tr in tr_list] ans = [] for td_list in tr_list: t0 = td_list[0].css.select("code")[0].text t1 = td_list[1].text t2 = td_list[2].text t0 = td_list[0].css.select("code")[0].text.strip() t1 = td_list[1].text.strip() t2 = td_list[2].text.strip() error_code = t0 description = re.sub(r"\n\t+", " ", t1).strip() if t2 == "405 Method Not Allowed": http_status_code = 405 if t2 == "N/A": http_status_code = None else: http_status_code = int(t2) m = re.match(r"(\d{3})[\s\S]*", t2) assert m is not None, f"t2: {repr(t2)}" http_status_code = int(m.group(1)) ans.append( { "error_code": error_code, "code": error_code, "description": description, "http_status_code": http_status_code, } ) save_json(model_dir / "s3_error_codes.json", {"SelectObjectContent": ans}) ans.sort(key=lambda x: x["code"]) data[kind] = ans save_json(model_dir / "s3_error_codes.json", data) @cli.command() Loading Loading
Cargo.lock +1 −0 Original line number Diff line number Diff line Loading @@ -2114,6 +2114,7 @@ name = "s3s-codegen" version = "0.0.0" dependencies = [ "heck", "http 1.2.0", "nugine-rust-utils", "numeric_cast", "regex", Loading
codegen/Cargo.toml +1 −0 Original line number Diff line number Diff line Loading @@ -18,3 +18,4 @@ serde = { version = "1.0.210", features = ["derive"] } serde_json = { version = "1.0.128", features = ["preserve_order"] } serde_urlencoded = "0.7.1" s3s-model = { version = "0.11.0-dev", path = "../crates/s3s-model" } http = "1.2.0"
codegen/src/v1/error.rs +60 −14 Original line number Diff line number Diff line Loading @@ -4,13 +4,17 @@ use super::smithy; use crate::declare_codegen; use std::collections::BTreeMap; use std::fs; use std::ops::Not; use heck::ToShoutySnakeCase; use regex::Regex; use scoped_writer::g; use serde::Deserialize; use serde::Serialize; use stdx::default::default; #[derive(Debug)] struct Error { code: String, description: Vec<Option<String>>, Loading @@ -36,7 +40,7 @@ fn collect_errors(model: &smithy::Model) -> Errors { let Some(cap) = pattern.captures(line) else { continue }; let tag = cap.get(1).unwrap().as_str(); assert_eq!(tag, "Code:"); o(code_pattern.captures(line).unwrap().get(2).unwrap().as_str()) o(code_pattern.captures(line).unwrap().get(2).unwrap().as_str().trim()) }; let description = loop { Loading Loading @@ -121,23 +125,62 @@ fn collect_errors(model: &smithy::Model) -> Errors { errors } #[derive(Debug, Serialize, Deserialize)] struct ErrorCode { code: String, description: String, http_status_code: Option<u16>, } // https://github.com/Nugine/s3s/issues/224 fn patch_extra_errors(errors: &mut Errors) { // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ReplicationErrorCodeList // // https://docs.aws.amazon.com/AmazonS3/latest/API/ErrorResponses.html#ReplicationErrorCodeList // { // let code = "ReplicationConfigurationNotFoundError"; // let desc = "There is no replication configuration for this bucket."; // let status = "404 Not Found"; // errors.insert( // code.to_owned(), // Error { // code: code.to_owned(), // description: vec![Some(desc.to_owned())], // status: vec![Some(status.to_owned())], // }, // ); // } { let code = "ReplicationConfigurationNotFoundError"; let desc = "There is no replication configuration for this bucket."; let status = "404 Not Found"; let extra: BTreeMap<String, Vec<ErrorCode>> = serde_json::from_str(fs::read_to_string("data/s3_error_codes.json").unwrap().as_str()).unwrap(); for group in extra.values() { for ec in group { if errors.contains_key(&ec.code) { continue; } if ec.code == "503 SlowDown" { continue; } if ec.code.contains('.') { continue; } errors.insert( code.to_owned(), ec.code.clone(), Error { code: code.to_owned(), description: vec![Some(desc.to_owned())], status: vec![Some(status.to_owned())], code: ec.code.clone(), description: vec![Some(ec.description.clone())], status: vec![ec.http_status_code.map(|s| { let status = http::StatusCode::from_u16(s).unwrap(); let reason = status.canonical_reason().unwrap(); format!("{s} {reason}") })], }, ); } } } } #[allow(clippy::too_many_lines)] pub fn codegen(model: &smithy::Model) { Loading @@ -147,6 +190,7 @@ pub fn codegen(model: &smithy::Model) { g([ "#![allow(clippy::doc_markdown)]", "#![allow(clippy::too_many_lines)]", "", "use bytestring::ByteString;", "use hyper::StatusCode;", Loading Loading @@ -177,7 +221,9 @@ pub fn codegen(model: &smithy::Model) { let status = &err.status[0]; if let Some(desc) = desc { g!("/// {desc}"); for line in desc.lines() { g!("/// {}", line); } } if let Some(status) = status { if desc.is_some() { Loading
crates/s3s/src/error/generated.rs +1944 −218 File changed.Preview size limit exceeded, changes collapsed. Show changes
data/crawl.py +58 −45 Original line number Diff line number Diff line Loading @@ -7,7 +7,7 @@ from bs4 import BeautifulSoup import requests import typer cli = typer.Typer() cli = typer.Typer(pretty_exceptions_show_locals=False) model_dir = Path(__file__).parent Loading Loading @@ -46,8 +46,16 @@ def crawl_error_codes(): soup = BeautifulSoup(html, "lxml") h2_id = "SelectObjectContentErrorCodeList" kinds = [ ("S3", "ErrorCodeList"), ("Replication", "ReplicationErrorCodeList"), ("Tagging", "S3TaggingErrorCodeList"), ("SelectObjectContent", "SelectObjectContentErrorCodeList"), ] data = {} for kind, h2_id in kinds: h2 = soup.css.select(f"#{h2_id}")[0] # type:ignore # find the next table Loading @@ -59,37 +67,42 @@ def crawl_error_codes(): assert table is not None th_list = table.css.select("th") # type:ignore assert th_list[0].text == "Error code" assert th_list[0].text in ("Error code", "Error Code") assert th_list[1].text == "Description" assert th_list[2].text == "HTTP status code" assert th_list[2].text in ("HTTP status code", "HTTP Status Code") tr_list = table.css.select("tr")[1:] # type:ignore tr_list = [[e for e in tr.children if e.name == "td"] for tr in tr_list] ans = [] for td_list in tr_list: t0 = td_list[0].css.select("code")[0].text t1 = td_list[1].text t2 = td_list[2].text t0 = td_list[0].css.select("code")[0].text.strip() t1 = td_list[1].text.strip() t2 = td_list[2].text.strip() error_code = t0 description = re.sub(r"\n\t+", " ", t1).strip() if t2 == "405 Method Not Allowed": http_status_code = 405 if t2 == "N/A": http_status_code = None else: http_status_code = int(t2) m = re.match(r"(\d{3})[\s\S]*", t2) assert m is not None, f"t2: {repr(t2)}" http_status_code = int(m.group(1)) ans.append( { "error_code": error_code, "code": error_code, "description": description, "http_status_code": http_status_code, } ) save_json(model_dir / "s3_error_codes.json", {"SelectObjectContent": ans}) ans.sort(key=lambda x: x["code"]) data[kind] = ans save_json(model_dir / "s3_error_codes.json", data) @cli.command() Loading