Unverified Commit d4330bf8 authored by Nugine's avatar Nugine
Browse files

codegen: xml

parent 51f5570b
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -28,4 +28,10 @@ impl Codegen {
    pub fn ln(&mut self, line: impl AsRef<str>) {
        writeln!(self.writer, "{}", line.as_ref()).unwrap();
    }

    pub fn lines<'a>(&mut self, lines: impl AsRef<[&'a str]>) {
        for line in lines.as_ref() {
            self.ln(line);
        }
    }
}
+7 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ mod dto;
mod error;
mod headers;
mod ops;
mod xml;

mod aws_conv;
mod aws_proxy;
@@ -59,6 +60,12 @@ fn main() {
        error::codegen(&model, &mut gen);
    }

    {
        let path = "crates/s3s/src/xml/generated.rs";
        let mut gen = Codegen::create_file(path).unwrap();
        xml::codegen(&ops, &rust_types, &mut gen);
    }

    {
        let path = "crates/s3s/src/ops/generated.rs";
        let mut gen = Codegen::create_file(path).unwrap();
+6 −391
Original line number Diff line number Diff line
use crate::dto::RustTypes;
use crate::gen::Codegen;
use crate::rust::codegen_doc;
use crate::rust::{codegen_doc, default_value_literal};
use crate::xml::{is_xml_output, is_xml_payload};
use crate::{default, f, headers, o};
use crate::{dto, rust, smithy};

use std::collections::{BTreeMap, BTreeSet, HashMap, VecDeque};
use std::fmt;
use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::ops::Not;

use heck::ToSnakeCase;
use serde_json::Value;

#[derive(Debug)]
pub struct Operation {
@@ -86,29 +85,22 @@ pub fn collect_operations(model: &smithy::Model) -> Operations {
}

pub fn codegen(ops: &Operations, rust_types: &RustTypes, g: &mut Codegen) {
    let prelude = [
    g.lines([
        "#![allow(clippy::declare_interior_mutable_const)]",
        "#![allow(clippy::borrow_interior_mutable_const)]",
        "",
        "use crate::dto::*;", //
        "use crate::header::names::*;",
        "use crate::http;",
        "use crate::xml;",
        "use crate::error::*;",
        "use crate::path::S3Path;",
        "",
        "use std::io::Write;",
        "use std::borrow::Cow;",
        "",
    ];

    for line in prelude {
        g.ln(line);
    }
    ]);

    codegen_async_trait(ops, g);
    codegen_xml_ser(ops, rust_types, g);
    codegen_xml_de(ops, rust_types, g);

    codegen_http(ops, rust_types, g);
    codegen_router(ops, rust_types, g);
}
@@ -138,383 +130,6 @@ fn codegen_async_trait(ops: &Operations, g: &mut Codegen) {
    g.lf();
}

fn is_xml_payload(field: &rust::StructField) -> bool {
    let streaming = field.type_ == "StreamingBlob" || field.type_ == "SelectObjectContentEventStream";
    field.position == "payload" && field.type_ != "Policy" && streaming.not()
}

fn is_xml_output(ty: &rust::Struct) -> bool {
    ty.xml_name.is_some() || ty.fields.iter().any(|field| field.position == "xml")
}

fn codegen_xml_ser(ops: &Operations, rust_types: &RustTypes, g: &mut Codegen) {
    let mut root_type_names: BTreeSet<&str> = default();
    let mut field_type_names: BTreeSet<&str> = default();

    let mut q: VecDeque<&str> = default();

    for op in ops.values() {
        let ty_name = op.output.as_str();

        let rust_type = &rust_types[ty_name];
        let rust::Type::Struct(ty) = rust_type else { panic!() };

        if is_xml_output(ty) {
            root_type_names.insert(ty_name);
            field_type_names.insert(ty_name);
            q.push_back(ty_name);
        }

        let mut payload_count = 0;
        for field in &ty.fields {
            if is_xml_payload(field) {
                root_type_names.insert(&field.type_);
                field_type_names.insert(&field.type_);
                q.push_back(&field.type_);
                payload_count += 1;
            }
        }
        assert!(payload_count <= 1);
    }

    while let Some(name) = q.pop_front() {
        let rust_type = &rust_types[name];
        match rust_type {
            rust::Type::Struct(ty) => {
                for field in &ty.fields {
                    let is_xml_field = field.position == "xml" || is_xml_payload(field);
                    if is_xml_field.not() {
                        continue;
                    }

                    let field_type = &rust_types[field.type_.as_str()];
                    if let rust::Type::List(list_ty) = field_type {
                        field_type_names.insert(list_ty.member.type_.as_str());
                        q.push_back(list_ty.member.type_.as_str());
                    } else {
                        field_type_names.insert(field.type_.as_str());
                        q.push_back(field.type_.as_str());
                    }
                }
            }
            rust::Type::Alias(_) => {}
            rust::Type::List(ty) => {
                field_type_names.insert(ty.member.type_.as_str());
                q.push_back(ty.member.type_.as_str());
            }
            rust::Type::StrEnum(ty) => {
                field_type_names.insert(ty.name.as_str());
            }
            rust::Type::StructEnum(ty) => {
                for variant in &ty.variants {
                    field_type_names.insert(variant.type_.as_str());
                    q.push_back(variant.type_.as_str());
                }
            }
            rust::Type::Provided(ty) => {
                assert!(matches!(ty.name.as_str(), "Body" | "Event"));
            }
            rust::Type::Map(_) => unimplemented!(),
            rust::Type::Timestamp(_) => {}
        }
    }

    for rust_type in field_type_names.iter().map(|&name| &rust_types[name]) {
        match rust_type {
            rust::Type::Struct(ty) => {
                g.ln(f!("impl xml::SerializeContent for {} {{", ty.name));
                g.ln(f!(
                    "fn serialize_content<W: Write>(&self, {}: &mut xml::Serializer<W>) -> xml::SerResult {{",
                    if ty.fields.is_empty() { '_' } else { 's' }
                ));

                for field in ty.fields.iter().filter(|x| x.position == "xml") {
                    let xml_name = field.xml_name.as_ref().unwrap_or(&field.camel_name);

                    let field_ty = &rust_types[field.type_.as_str()];
                    if let rust::Type::List(list_ty) = field_ty {
                        if field.option_type {
                            g.ln(f!("if let Some(iter) = &self.{} {{", field.name));
                        } else {
                            g.ln("{");
                            g.ln(f!("let iter = &self.{};", field.name));
                        }
                        if field.xml_flattened {
                            g.ln(f!("s.flattened_list(\"{xml_name}\", iter)?;"));
                        } else {
                            let member_xml_name = list_ty.member.xml_name.as_deref().unwrap();
                            g.ln(f!("s.list(\"{xml_name}\", \"{member_xml_name}\", iter)?;"));
                        }
                        g.ln("}");
                    } else if let rust::Type::Timestamp(ts_ty) = field_ty {
                        let fmt = ts_ty.format.as_deref().unwrap_or("DateTime");
                        if field.option_type {
                            g.ln(f!("if let Some(ref val) = self.{} {{", field.name));
                            g.ln(f!("s.timestamp(\"{xml_name}\", val, TimestampFormat::{fmt})?;"));
                            g.ln("}");
                        } else {
                            g.ln(f!("s.timestamp(\"{}\", &self.{}, TimestampFormat::{})?;", xml_name, field.name, fmt));
                        }
                    } else if field.option_type {
                        g.ln(f!("if let Some(ref val) = self.{} {{", field.name));
                        g.ln(f!("s.content(\"{xml_name}\", val)?;"));
                        g.ln("}");
                    } else {
                        g.ln(f!("s.content(\"{}\", &self.{})?;", xml_name, field.name));
                    }
                }

                g.ln("Ok(())");

                g.ln("}");
                g.ln("}");
            }
            rust::Type::StrEnum(ty) => {
                g.ln(f!("impl xml::SerializeContent for {} {{", ty.name));
                g.ln("fn serialize_content<W: Write>(&self, s: &mut xml::Serializer<W>) -> xml::SerResult {");

                g.ln("self.as_str().serialize_content(s)");

                g.ln("}");
                g.ln("}");
            }
            rust::Type::StructEnum(ty) => {
                g.ln(f!("impl xml::SerializeContent for {} {{", ty.name));
                g.ln("fn serialize_content<W: Write>(&self, s: &mut xml::Serializer<W>) -> xml::SerResult {");

                g.ln("match self {");

                for variant in &ty.variants {
                    g.ln(f!("Self::{0}(x) => s.content(\"{0}\", x),", variant.name));
                }

                g.ln("}");

                g.ln("}");
                g.ln("}");
            }
            rust::Type::Alias(_) => {}
            rust::Type::Provided(_) => {}
            rust::Type::Timestamp(_) => {}
            rust::Type::List(_) => panic!(),
            rust::Type::Map(_) => panic!(),
        }
        g.lf();
    }

    for rust_type in root_type_names.iter().map(|&name| &rust_types[name]) {
        let rust::Type::Struct(ty) = rust_type else { panic!("{rust_type:#?}") };

        g.ln(f!("impl xml::Serialize for {} {{", ty.name));
        g.ln("fn serialize<W: Write>(&self, s: &mut xml::Serializer<W>) -> xml::SerResult {");

        let xml_name = ty.xml_name.as_deref().unwrap_or(ty.name.as_str());
        g.ln(f!("s.content(\"{xml_name}\", self)"));

        g.ln("}");
        g.ln("}");

        g.lf();
    }
}

fn default_value_literal(v: &Value) -> &dyn fmt::Display {
    match v {
        Value::Bool(x) => x,
        Value::Number(x) => x,
        _ => unimplemented!(),
    }
}

fn codegen_xml_de(ops: &Operations, rust_types: &RustTypes, g: &mut Codegen) {
    let mut root_type_names: BTreeMap<&str, Option<&str>> = default();
    let mut field_type_names: BTreeSet<&str> = default();

    let mut q: VecDeque<&str> = default();

    for op in ops.values() {
        let ty_name = op.input.as_str();

        let rust_type = &rust_types[ty_name];
        let rust::Type::Struct(ty) = rust_type else { panic!() };
        assert!(ty.xml_name.is_none());

        let mut payload_count = 0;
        for field in &ty.fields {
            if is_xml_payload(field) {
                root_type_names.insert(&field.type_, field.xml_name.as_deref());
                field_type_names.insert(&field.type_);
                q.push_back(&field.type_);
                payload_count += 1;
            }
        }
        assert!(payload_count <= 1);
    }

    while let Some(name) = q.pop_front() {
        let rust_type = &rust_types[name];
        match rust_type {
            rust::Type::Struct(ty) => {
                for field in &ty.fields {
                    let is_xml_field = field.position == "xml" || is_xml_payload(field);
                    if is_xml_field.not() {
                        continue;
                    }

                    let field_type = &rust_types[field.type_.as_str()];

                    if let rust::Type::List(list_ty) = field_type {
                        field_type_names.insert(list_ty.member.type_.as_str());
                        q.push_back(list_ty.member.type_.as_str());
                    } else {
                        field_type_names.insert(field.type_.as_str());
                        q.push_back(field.type_.as_str());
                    }
                }
            }
            rust::Type::Alias(_) => {}
            rust::Type::List(ty) => {
                field_type_names.insert(ty.member.type_.as_str());
                q.push_back(ty.member.type_.as_str());
            }
            rust::Type::StrEnum(ty) => {
                field_type_names.insert(ty.name.as_str());
            }
            rust::Type::StructEnum(ty) => {
                for variant in &ty.variants {
                    field_type_names.insert(variant.type_.as_str());
                    q.push_back(variant.type_.as_str());
                }
            }
            rust::Type::Provided(ty) => {
                assert!(matches!(ty.name.as_str(), "Event"));
            }
            rust::Type::Map(_) => unimplemented!(),
            rust::Type::Timestamp(_) => {}
        }
    }

    for rust_type in field_type_names.iter().map(|&name| &rust_types[name]) {
        match rust_type {
            rust::Type::Struct(ty) => {
                g.ln(f!("impl<'xml> xml::DeserializeContent<'xml> for {} {{", ty.name));
                g.ln(f!(
                    "fn deserialize_content({}: &mut xml::Deserializer<'xml>) -> xml::DeResult<Self> {{",
                    if ty.fields.is_empty() { "_" } else { "d" },
                ));

                for field in &ty.fields {
                    assert!(field.position == "xml");
                }

                for field in &ty.fields {
                    g.ln(f!("let mut {}: Option<{}> = None;", field.name, field.type_));
                }

                if ty.fields.is_empty().not() {
                    g.ln("d.for_each_element(|d, x| match x {");
                    for field in &ty.fields {
                        let xml_name = field.xml_name.as_ref().unwrap_or(&field.camel_name);
                        let field_name = field.name.as_str();
                        let field_type = &rust_types[field.type_.as_str()];

                        g.ln(f!("b\"{xml_name}\" => {{"));

                        if let rust::Type::List(list_ty) = field_type {
                            if field.xml_flattened {
                                g.ln(f!("let ans: {} = d.content()?;", list_ty.member.type_));
                                g.ln(f!("{field_name}.get_or_insert_with(List::new).push(ans);"));
                            } else {
                                g.ln(f!("if {field_name}.is_some() {{ return Err(xml::DeError::DuplicateField); }}"));
                                g.ln(f!("{field_name} = Some(d.list_content(\"member\")?);"));
                            }
                        } else if let rust::Type::Timestamp(ts_ty) = field_type {
                            let fmt = ts_ty.format.as_deref().unwrap_or("DateTime");

                            g.ln(f!("if {field_name}.is_some() {{ return Err(xml::DeError::DuplicateField); }}"));

                            g.ln(f!("{field_name} = Some(d.timestamp(TimestampFormat::{fmt})?);"));
                        } else {
                            g.ln(f!("if {field_name}.is_some() {{ return Err(xml::DeError::DuplicateField); }}"));
                            g.ln(f!("{field_name} = Some(d.content()?);"));
                        }

                        g.ln("Ok(())");
                        g.ln("}");
                    }
                    g.ln("_ => Err(xml::DeError::UnexpectedTagName)");
                    g.ln("})?;");
                }

                g.ln("Ok(Self {");
                for field in &ty.fields {
                    if let Some(ref default_value) = field.default_value {
                        let literal = default_value_literal(default_value);
                        g.ln(f!("{0}: {0}.unwrap_or({1}),", field.name, literal));
                        continue;
                    }

                    if field.option_type {
                        g.ln(f!("{},", field.name));
                    } else {
                        g.ln(f!("{0}: {0}.ok_or(xml::DeError::MissingField)?,", field.name));
                    }
                }
                g.ln("})");

                g.ln("}");
                g.ln("}");
            }
            rust::Type::StrEnum(ty) => {
                g.ln(f!("impl<'xml> xml::DeserializeContent<'xml> for {} {{", ty.name));
                g.ln("fn deserialize_content(d: &mut xml::Deserializer<'xml>) -> xml::DeResult<Self> {");
                g.ln("String::deserialize_content(d).map(Self::from)");
                g.ln("}");
                g.ln("}");
            }
            rust::Type::StructEnum(ty) => {
                g.ln(f!("impl<'xml> xml::DeserializeContent<'xml> for {} {{", ty.name));
                g.ln("fn deserialize_content(d: &mut xml::Deserializer<'xml>) -> xml::DeResult<Self> {");

                g.ln("d.element(|d, x| match x {");
                for variant in &ty.variants {
                    g.ln(f!("b\"{0}\" => Ok(Self::{0}(d.content()?)),", variant.name));
                }
                g.ln("_ => Err(xml::DeError::UnexpectedTagName)");
                g.ln("})");

                g.ln("}");
                g.ln("}");
            }

            rust::Type::Alias(_) => {}
            rust::Type::Provided(ty) => {
                assert!(matches!(ty.name.as_str(), "Event"));
            }
            rust::Type::List(_) => panic!(),
            rust::Type::Map(_) => panic!(),
            rust::Type::Timestamp(_) => {}
        }
        g.lf();
    }

    for (rust_type, xml_name) in root_type_names.iter().map(|(&name, xml_name)| (&rust_types[name], xml_name)) {
        let rust::Type::Struct(ty) = rust_type else { panic!("{rust_type:#?}") };

        g.ln(f!("impl<'xml> xml::Deserialize<'xml> for {} {{", ty.name));
        g.ln("fn deserialize(d: &mut xml::Deserializer<'xml>) -> xml::DeResult<Self> {");

        assert!(ty.xml_name.is_none()); // canary for <https://github.com/Nugine/s3s/issues/2>

        let xml_name = xml_name.or(ty.xml_name.as_deref()).unwrap_or(&ty.name);
        g.ln(f!("d.named_element(\"{xml_name}\", |d|d.content())"));

        g.ln("}");
        g.ln("}");
        g.lf();
    }
}

fn status_code_name(code: u16) -> &'static str {
    match code {
        204 => "NO_CONTENT",
+10 −0
Original line number Diff line number Diff line
use crate::f;
use crate::gen::Codegen;

use std::fmt;

use serde_json::Value;

#[derive(Debug, Clone)]
@@ -176,3 +178,11 @@ pub fn codegen_doc(doc: Option<&str>, g: &mut Codegen) {
        g.ln(f!("/// {line}"));
    }
}

pub fn default_value_literal(v: &Value) -> &dyn fmt::Display {
    match v {
        Value::Bool(x) => x,
        Value::Number(x) => x,
        _ => unimplemented!(),
    }
}

codegen/src/xml.rs

0 → 100644
+393 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading