Commit 95f19533 authored by Nugine's avatar Nugine
Browse files

feat(model): extra error codes

parent fe7c8686
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -2114,6 +2114,7 @@ name = "s3s-codegen"
version = "0.0.0"
dependencies = [
 "heck",
 "http 1.2.0",
 "nugine-rust-utils",
 "numeric_cast",
 "regex",
+1 −0
Original line number Diff line number Diff line
@@ -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"
+60 −14
Original line number Diff line number Diff line
@@ -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>>,
@@ -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 {
@@ -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) {
@@ -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;",
@@ -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() {
+1944 −218

File changed.

Preview size limit exceeded, changes collapsed.

+58 −45
Original line number Diff line number Diff line
@@ -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

@@ -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
@@ -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