use serde_json::{Map, Value};
use std::collections::BTreeMap;
use super::records::{camel_ident, FieldKind, Record, Target};
use super::schema_records::SchemaRecords;
use super::support::json_string_literal;
pub(super) const SWIFT_ARTIFACT: &str = "HarnRunSessionView.swift";
pub(super) fn schema() -> Value {
harn_vm::orchestration::run_session_view_schema()
}
pub(super) fn generate_swift(schema: &Value) -> Result<String, String> {
let definitions = schema["$defs"]
.as_object()
.ok_or("view definitions missing")?;
let names: Vec<_> = definitions
.iter()
.filter(|(_, shape)| shape["properties"].is_object())
.map(|(name, _)| (name.as_str(), format!("Harn{name}")))
.collect();
let records = SchemaRecords {
schema,
names: &names,
label: "native run/session view",
require_all: false,
metadata: |_, _, shape| {
if shape == &Value::Bool(true) || shape.as_object().is_some_and(Map::is_empty) {
return Ok(Some(FieldKind::Json));
}
let unsigned = match shape["format"].as_str() {
Some("uint32") => Some("UInt32"),
Some("uint64") => Some("UInt64"),
Some("uint") => Some("UInt"),
_ => None,
};
if let Some(name) = unsigned.filter(|_| shape["type"] == "integer") {
return Ok(Some(FieldKind::Named(name.into())));
}
Ok(match shape["$ref"].as_str() {
Some("#/$defs/EnvironmentPolicyKind") => {
Some(FieldKind::Named("HarnEnvironmentPolicyKind".into()))
}
Some("#/$defs/GrantAudience") => Some(FieldKind::Named("HarnGrantAudience".into())),
Some("#/$defs/FlightValuePolicy") => {
Some(FieldKind::Named("HarnFlightValuePolicy".into()))
}
_ => None,
})
},
}
.load_extensible()?;
let mut out = "// GENERATED by `harn dump-protocol-artifacts` - do not edit by hand.\nimport Foundation\n\n".to_owned();
for (name, definition) in definitions {
let values = definition["enum"]
.as_array()
.map(|values| values.iter().collect::<Vec<_>>())
.or_else(|| {
definition["oneOf"]
.as_array()?
.iter()
.map(|variant| variant.get("const"))
.collect::<Option<Vec<_>>>()
});
if let Some(values) = values {
let values = values
.iter()
.map(|value| {
value
.as_str()
.map(str::to_owned)
.ok_or("view enums must be strings")
})
.collect::<Result<Vec<_>, _>>()?;
out.push_str(&super::swift::swift_enum(&format!("Harn{name}"), &values));
}
}
for (native, public) in &names {
let record = records
.iter()
.find(|record| &record.name == public)
.unwrap();
append_defaulted_record(&mut out, record, &definitions[*native]["properties"])?;
}
for (name, definition) in [
("harnRunViewV1Schema", &definitions["RunView"]),
("harnSessionViewV1Schema", &definitions["SessionView"]),
] {
out.push_str(&format!(
"public let {name} = {}\n",
definition["properties"]["schema"]["const"]
));
out.push_str(&format!(
"public let {name}Version: UInt32 = {}\n",
definition["properties"]["schema_version"]["const"]
));
}
Ok(out)
}
fn append_defaulted_record(
out: &mut String,
record: &Record,
properties: &Value,
) -> Result<(), String> {
let mut projected = record.clone();
let mut body = "\n public init(from decoder: Decoder) throws {\n let values = try decoder.container(keyedBy: CodingKeys.self)\n".to_owned();
let mut defaults = BTreeMap::new();
for field in &mut projected.fields {
let property = &properties[field.wire_name.as_ref()];
let name = camel_ident(&field.wire_name);
let default = if field.required {
None
} else {
match property.get("default") {
Some(value) => Some(default_expression(&field.kind, value)?),
None if matches!(field.kind, FieldKind::Nullable(_)) => Some("nil".into()),
_ => None,
}
};
if let Some(default) = &default {
defaults.insert(field.wire_name.to_string(), default.clone());
field.required = true;
} else if !field.required {
return Err(format!(
"native view field {}.{} has no projected default",
record.name, field.wire_name
));
}
let kind = field.kind.type_name(Target::Swift);
let expression = format!("try values.decode({kind}.self, forKey: .{name})");
if default.is_some() {
body.push_str(&format!(
" if values.contains(.{name}) {{ {name} = {expression} }}\n"
));
} else {
body.push_str(&format!(" {name} = {expression}\n"));
}
}
body.push_str(" }\n");
let required: Vec<_> = record
.fields
.iter()
.filter(|field| field.required)
.collect();
if required.is_empty() {
body.push_str("\n public init() {}\n");
} else {
let parameters = required
.iter()
.map(|field| {
format!(
"{}: {}",
camel_ident(&field.wire_name),
field.kind.type_name(Target::Swift)
)
})
.collect::<Vec<_>>()
.join(", ");
body.push_str(&format!("\n public init({parameters}) {{\n"));
for field in required {
let name = camel_ident(&field.wire_name);
body.push_str(&format!(" self.{name} = {name}\n"));
}
body.push_str(" }\n");
}
projected.append_swift_with_body(out, &body, &defaults);
Ok(())
}
fn default_expression(kind: &FieldKind, value: &Value) -> Result<String, String> {
Ok(match value {
Value::Null => "nil".into(),
Value::Bool(_) | Value::Number(_) => value.to_string(),
Value::String(value) if matches!(kind, FieldKind::Named(_)) => {
format!(".{}", super::swift::swift_case_name(value))
}
Value::String(value) => json_string_literal(value),
Value::Array(values) if values.is_empty() => "[]".into(),
Value::Object(_) if matches!(kind, FieldKind::Named(_)) => ".init()".into(),
Value::Object(values) if values.is_empty() => "[:]".into(),
_ => return Err(format!("unsupported native default {value} for {kind:?}")),
})
}