use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
use super::clauses;
use super::docs::{doc_links, model_rules};
use super::facts::{Closures, Inits, scalar_class};
use super::flatbuffers;
use super::names::{dotted_name, joined_camel, spellings};
use super::path::{Root, Step};
use super::resolve::{Scope, is_foreign};
use super::v1;
use crate::name::camel_case;
use crate::projection::flatbuffers as fb;
use crate::projection::size::{self, AbsentCause, Ctx, Encoding, PayloadShape, SizeState};
use crate::rules::ItemRef;
use crate::v2;
pub fn lower(package: &v2::Package, others: &[&v2::Package]) -> v1::Model {
lower_with(package, others, &[])
}
pub fn lower_with(
package: &v2::Package,
others: &[&v2::Package],
compatible: &[[u8; 32]],
) -> v1::Model {
let scope = Scope { package, others };
let mut lowering = Lowering::new(scope);
let declarations = lowering.declarations();
let interfaces = lowering.shapes();
lowering.drain_tuples();
let mut model = v1::Model {
name: Some(dotted_name(&package.name)),
scope: Some(v1::Scope {
package: package.name.clone(),
others: others.iter().map(|other| other.name.clone()).collect(),
}),
declarations,
tuples: lowering
.tuples
.iter()
.map(|entry| entry.lowered.clone().unwrap_or_default())
.collect(),
interfaces,
services: lowering.services(),
catalog: Some(lowering.catalog(compatible)),
flatbuffers: None,
foreign: lowering.foreign.clone(),
tuple_collisions: lowering.collisions.clone(),
podbuf: None,
};
let roots = root_tuples(&model);
let named: Vec<flatbuffers::NamedTuple<'_>> = lowering
.tuples
.iter()
.enumerate()
.filter(|(_, entry)| entry.named)
.map(|(index, entry)| flatbuffers::NamedTuple {
index: index as u32,
wire: entry.wire.as_str(),
source: &entry.source,
home: entry.home,
root: roots.contains(&(index as u32)),
})
.collect();
let projected = flatbuffers::project(scope, &named, &lowering.foreign_at);
patch_flatbuffers(&mut model, &projected);
model.flatbuffers = Some(projected.projection);
let mut keys = super::podbuf::TupleKeys::default();
for (index, entry) in lowering.tuples.iter().enumerate() {
for key in &entry.keys {
if entry.named {
keys.local.insert(key.clone(), index as u32);
} else {
keys.foreign.insert(
key.clone(),
super::podbuf::ForeignTuple {
package: entry.home.name.clone(),
wire: entry.wire.clone(),
entry: index as u32,
},
);
}
}
}
model.podbuf = Some(super::podbuf::project(
scope,
&named,
&lowering.foreign_at,
&keys,
));
model
}
fn root_tuples(model: &v1::Model) -> HashSet<u32> {
let mut roots = HashSet::new();
for interface in &model.interfaces {
for slot in &interface.slots {
let Some(v1::interaction_slot::Occupant::Interaction(interaction)) =
slot.occupant.as_ref()
else {
continue;
};
match interaction.shape.as_ref() {
Some(v1::interaction::Shape::Command(command)) => {
roots.extend(command.request_tuple.as_ref().map(|tuple| tuple.index));
}
Some(v1::interaction::Shape::Query(query)) => {
roots.extend(query.request_tuple.as_ref().map(|tuple| tuple.index));
roots.extend(query.reply_tuple.as_ref().map(|tuple| tuple.index));
}
_ => {}
}
}
}
roots
}
#[derive(Clone)]
pub(super) struct Path {
base: Base,
segments: Vec<String>,
pub(super) key: Option<Vec<Step>>,
rust: String,
wire: String,
visibility: i32,
named: bool,
}
#[derive(Clone, Copy)]
enum Base {
Declaration(u32),
Interaction(u32, u32),
Foreign,
}
impl Path {
pub(super) fn declaration(index: u32, decl: &v2::Decl, owner: &str) -> Self {
Self {
base: Base::Declaration(index),
segments: Vec::new(),
key: Some(vec![Step::Root(Root::Declaration(owner.to_string()))]),
rust: camel_case(owner),
wire: owner.to_string(),
visibility: decl.visibility,
named: true,
}
}
pub(super) fn foreign(package: &str, owner: &str, visibility: i32) -> Self {
Self {
base: Base::Foreign,
segments: Vec::new(),
key: Some(vec![Step::Root(Root::Foreign {
package: package.to_string(),
name: owner.to_string(),
})]),
rust: camel_case(owner),
wire: owner.to_string(),
visibility,
named: false,
}
}
fn interaction(
interface: u32,
slot: u32,
visibility: i32,
owner: &str,
member: &str,
segments: Vec<String>,
) -> Self {
Self {
base: Base::Interaction(interface, slot),
segments,
key: None,
rust: format!("{}{}", camel_case(&joined_camel(owner)), camel_case(member)),
wire: format!("{}{}", joined_camel(owner), joined_camel(member)),
visibility,
named: true,
}
}
pub(super) fn field(&self, name: &str) -> Self {
let mut segments = self.segments.clone();
segments.push(name.to_string());
let key = self.key_with(Some(Step::Field(name.to_string())));
Self {
base: self.base,
segments,
key,
rust: format!("{}{}", self.rust, camel_case(name)),
wire: format!("{}{}", self.wire, joined_camel(name)),
visibility: self.visibility,
named: self.named,
}
}
pub(super) fn step(&self, word: &str) -> Self {
let mut segments = self.segments.clone();
segments.push(word.to_string());
let key = self.key_with(match word {
"Key" => Some(Step::Key),
"Value" => Some(Step::Value),
_ => None,
});
Self {
base: self.base,
segments,
key,
rust: format!("{}{word}", self.rust),
wire: format!("{}{word}", self.wire),
visibility: self.visibility,
named: self.named,
}
}
fn key_with(&self, step: Option<Step>) -> Option<Vec<Step>> {
self.key.as_ref().map(|key| {
let mut key = key.clone();
key.extend(step);
key
})
}
pub(super) fn tuple_field(&self, name: &str, position: usize) -> Self {
let mut segments = self.segments.clone();
segments.push(name.to_string());
let key = self.key_with(Some(Step::Position(position as u32)));
Self {
base: self.base,
segments,
key,
rust: format!("{}{}", self.rust, camel_case(name)),
wire: format!("{}Field{position}", self.wire),
visibility: self.visibility,
named: self.named,
}
}
fn origin(&self) -> Option<v1::induced_tuple::Origin> {
match self.base {
Base::Declaration(index) => {
Some(v1::induced_tuple::Origin::Declaration(v1::TuplePath {
declaration: index,
segments: self.segments.clone(),
}))
}
Base::Interaction(interface, slot) => Some(v1::induced_tuple::Origin::Interaction(
v1::InteractionPath {
interface,
slot,
segments: self.segments.clone(),
},
)),
Base::Foreign => None,
}
}
}
struct TupleEntry<'a> {
source: v2::TupleType,
home: &'a v2::Package,
path: Path,
rust: String,
wire: String,
named: bool,
keys: Vec<Vec<Step>>,
docs: Vec<FieldDoc>,
lowered: Option<v1::InducedTuple>,
}
#[derive(Clone, Default)]
struct FieldDoc {
doc: String,
links: Vec<v1::DocLink>,
see: Vec<v1::DocLink>,
since: Vec<String>,
}
impl FieldDoc {
fn of_param(param: &v2::Param) -> Self {
Self {
doc: param.doc.clone(),
links: doc_links(¶m.links),
see: doc_links(¶m.see),
since: param.since.clone(),
}
}
}
struct Lowering<'a> {
scope: Scope<'a>,
deps: Vec<&'a v2::Package>,
sizes: Ctx<'a>,
inits: Inits<'a>,
closures: Closures<'a>,
foreign: Vec<v1::ForeignDeclaration>,
foreign_at: HashMap<(String, String), u32>,
tuples: Vec<TupleEntry<'a>>,
tuple_at: HashMap<(String, String), usize>,
claims_memo: Memo,
claims_only: bool,
foreign_tuple_at: HashMap<(String, String), Vec<usize>>,
collisions: Vec<v1::TupleCollision>,
}
struct TupleClaim {
source: v2::TupleType,
visibility: i32,
wire: String,
}
type Claims = HashMap<String, Vec<TupleClaim>>;
type Memo = Rc<RefCell<HashMap<String, Rc<Claims>>>>;
#[cfg(test)]
thread_local! {
pub(super) static DECLARING_LOWERINGS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
impl<'a> Lowering<'a> {
fn new(scope: Scope<'a>) -> Self {
Self::with_mode(scope, false)
}
fn with_mode(scope: Scope<'a>, claims_only: bool) -> Self {
Self {
scope,
deps: scope.packages().collect(),
sizes: Ctx::new(scope.package, scope.others),
inits: Inits::new(scope),
closures: Closures::new(scope),
foreign: Vec::new(),
foreign_at: HashMap::new(),
tuples: Vec::new(),
tuple_at: HashMap::new(),
claims_memo: Memo::default(),
claims_only,
foreign_tuple_at: HashMap::new(),
collisions: Vec::new(),
}
}
fn declarations(&mut self) -> Vec<v1::Declaration> {
let package = self.scope.package;
let declarations = package
.decls
.iter()
.enumerate()
.map(|(index, decl)| {
let path = Path::declaration(index as u32, decl, &decl.name);
self.declaration(package, decl, Some(path))
})
.collect();
self.drain_tuples();
declarations
}
fn declaration(
&mut self,
home: &'a v2::Package,
decl: &v2::Decl,
path: Option<Path>,
) -> v1::Declaration {
let is_constant = matches!(decl.kind, Some(v2::decl::Kind::ConstDef(_)));
v1::Declaration {
name: Some(spellings(&decl.name)),
visibility: decl.visibility,
is_error: decl.is_error,
doc: decl.doc.clone(),
labels: decl.labels.clone(),
deprecated: decl.deprecated.clone(),
links: doc_links(&decl.links),
see: doc_links(&decl.see),
since: decl.since.clone(),
rules: model_rules(home, &self.deps, ItemRef::Decl(decl)),
closure: (!is_constant).then(|| self.closures.declaration(home, decl)),
init: (!is_constant).then(|| self.inits.declaration(home, decl)),
proto3: (!is_constant).then(|| {
self.size_state(
home,
&PayloadShape::Named(&decl.name),
Some(&decl.name),
Encoding::Proto3,
)
}),
podbuf: (!is_constant).then(|| {
self.size_state(
home,
&PayloadShape::Named(&decl.name),
Some(&decl.name),
Encoding::Podbuf,
)
}),
kind: self.declaration_kind(home, decl, path),
}
}
fn declaration_kind(
&mut self,
home: &'a v2::Package,
decl: &v2::Decl,
path: Option<Path>,
) -> Option<v1::declaration::Kind> {
let path = path.unwrap_or_else(|| Path::foreign(&home.name, &decl.name, decl.visibility));
match decl.kind.as_ref()? {
v2::decl::Kind::TypeDef(td) => {
Some(v1::declaration::Kind::Scalar(self.scalar(td, true)))
}
v2::decl::Kind::ConstDef(cd) => {
Some(v1::declaration::Kind::Constant(self.constant(home, cd)))
}
v2::decl::Kind::StructDef(def) => Some(v1::declaration::Kind::Struct(v1::Struct {
slots: def
.members
.iter()
.map(|member| self.slot(home, member, &path))
.collect(),
fixed_layout: def.fixed_layout,
})),
v2::decl::Kind::EnumDef(def) => Some(v1::declaration::Kind::Enum(enum_body(def))),
v2::decl::Kind::EnumSetDef(def) => {
Some(v1::declaration::Kind::EnumSet(self.enum_set(home, def)))
}
v2::decl::Kind::UnionDef(def) => {
Some(v1::declaration::Kind::Union(self.union(home, def)))
}
_ => None,
}
}
fn slot(&mut self, home: &'a v2::Package, member: &v2::StructMember, path: &Path) -> v1::Slot {
match &member.member {
Some(v2::struct_member::Member::Field(field)) => v1::Slot {
ordinal: field.ordinal,
occupant: Some(v1::slot::Occupant::Field(Box::new(
self.field(home, field, path),
))),
},
Some(v2::struct_member::Member::Reserved(reserved)) => v1::Slot {
ordinal: reserved.ordinal,
occupant: Some(v1::slot::Occupant::Retired(retired(reserved))),
},
None => v1::Slot {
ordinal: 0,
occupant: None,
},
}
}
fn field(&mut self, home: &'a v2::Package, field: &v2::Field, path: &Path) -> v1::Field {
let at = path.field(&field.name);
v1::Field {
name: Some(spellings(&field.name)),
r#type: field.r#type.as_ref().map(|ty| self.type_at(home, ty, &at)),
declared_init: field.declared_init.clone(),
init: Some(self.inits.field(home, field)),
doc: field.doc.clone(),
labels: field.labels.clone(),
deprecated: field.deprecated.clone(),
links: doc_links(&field.links),
see: doc_links(&field.see),
since: field.since.clone(),
rules: model_rules(home, &self.deps, ItemRef::Field(field)),
}
}
fn scalar(&mut self, td: &v2::TypeDef, declared: bool) -> v1::Scalar {
v1::Scalar {
class: scalar_class(td) as i32,
unit: match td
.backing
.as_ref()
.and_then(|backing| backing.kind.as_ref())
{
Some(v2::backing::Kind::Unit(unit)) => Some(unit.clone()),
_ => None,
},
constraint: td.constraint.as_ref().map(constraint),
vacuous: crate::v2::constraint_is_vacuous(td.constraint.as_ref()),
declared_init: declared.then(|| td.declared_init.clone()).flatten(),
width: td.width.as_ref().map(|width| match width {
v2::type_def::Width::IntWidth(value) => v1::scalar::Width::IntWidth(*value),
v2::type_def::Width::FloatWidth(value) => v1::scalar::Width::FloatWidth(*value),
}),
}
}
fn constant(&mut self, home: &'a v2::Package, cd: &v2::ConstDef) -> v1::Constant {
let typed = match (cd.regex.as_deref(), cd.type_ref.as_deref()) {
(Some(regex), _) => Some(v1::constant::Typed::RegexBody(
strip_regex_delimiters(regex).to_string(),
)),
(None, Some(reference)) => Some(match primitive_keyword(reference) {
Some(primitive) => v1::constant::Typed::Primitive(primitive as i32),
None => match self.type_ref(home, reference) {
reference if reference.resolved => v1::constant::Typed::Named(reference),
reference => v1::constant::Typed::Unresolved(reference.reference),
},
}),
(None, None) => None,
};
v1::Constant {
value: cd.value.clone(),
typed,
}
}
fn enum_set(&mut self, home: &'a v2::Package, def: &v2::EnumSetDef) -> v1::EnumSet {
let mask = def.bits.iter().fold(0i64, |mask, bit| {
if (0..=63).contains(&bit.value) {
mask | (1i64 << bit.value)
} else {
mask
}
});
v1::EnumSet {
backing_enum: def
.backing_enum
.as_deref()
.map(|reference| self.type_ref(home, reference)),
bits: def.bits.iter().map(enum_value).collect(),
width: def.width,
declared_mask: mask,
open: def.open,
}
}
fn union(&mut self, home: &'a v2::Package, def: &v2::UnionDef) -> v1::Union {
v1::Union {
arms: def
.arms
.iter()
.map(|arm| v1::Arm {
name: Some(spellings(&arm.name)),
ordinal: arm.ordinal,
r#type: Some(self.type_ref(home, &arm.type_ref)),
doc: arm.doc.clone(),
discriminant: fb::union_arm_discriminant(arm),
flatbuffers_box: None,
links: doc_links(&arm.links),
see: doc_links(&arm.see),
since: arm.since.clone(),
rules: Vec::new(),
})
.collect(),
retired: def.reserved.iter().map(retired).collect(),
is_result: def.is_result,
}
}
fn type_at(&mut self, home: &'a v2::Package, ty: &v2::FieldType, at: &Path) -> v1::Type {
v1::Type {
optional: ty.optional,
kind: self.type_kind(home, ty, at),
}
}
fn type_kind(
&mut self,
home: &'a v2::Package,
ty: &v2::FieldType,
at: &Path,
) -> Option<v1::r#type::Kind> {
match ty.kind.as_ref()? {
v2::field_type::Kind::Named(reference) => {
Some(v1::r#type::Kind::Named(self.type_ref(home, reference)))
}
v2::field_type::Kind::Primitive(primitive) => {
Some(v1::r#type::Kind::Primitive(*primitive))
}
v2::field_type::Kind::InlineScalar(td) => {
Some(v1::r#type::Kind::Inline(self.scalar(td, false)))
}
v2::field_type::Kind::Tuple(tuple) => {
let index = self.tuple_ref(home, tuple, at);
Some(v1::r#type::Kind::Tuple(v1::TupleRef { index }))
}
v2::field_type::Kind::Array(array) => {
let element = array
.element
.as_deref()
.map(|element| self.type_at(home, element, &at.step("Element")));
Some(v1::r#type::Kind::Array(Box::new(v1::ArrayType {
element: element.map(Box::new),
min: array.min,
max: array.max,
})))
}
v2::field_type::Kind::Map(map) => {
let key = map
.key
.as_deref()
.map(|key| self.type_at(home, key, &at.step("Key")));
let value = map
.value
.as_deref()
.map(|value| self.type_at(home, value, &at.step("Value")));
Some(v1::r#type::Kind::Map(Box::new(v1::MapType {
key: key.map(Box::new),
value: value.map(Box::new),
min: map.min,
max: map.max,
flatbuffers_entry_table: None,
})))
}
v2::field_type::Kind::Stream(stream) => {
Some(v1::r#type::Kind::Stream(self.stream(home, stream)))
}
}
}
fn stream(&mut self, home: &'a v2::Package, stream: &v2::StreamType) -> v1::StreamType {
v1::StreamType {
element: stream.element.as_ref().map(|element| match element {
v2::stream_type::Element::Named(reference) => {
v1::stream_type::Element::Named(self.type_ref(home, reference))
}
v2::stream_type::Element::Primitive(primitive) => {
v1::stream_type::Element::Primitive(*primitive)
}
}),
}
}
fn type_ref(&mut self, home: &'a v2::Package, reference: &str) -> v1::TypeRef {
let Some((decl, declaring)) = self.scope.resolve(home, reference) else {
return v1::TypeRef {
reference: reference.to_string(),
resolved: false,
package: String::new(),
foreign: is_foreign(reference),
index: 0,
kind: v1::DeclKind::Unspecified as i32,
};
};
let kind = decl_kind(decl);
let local = declaring.name == self.scope.package.name;
let index = if local {
declaring
.decls
.iter()
.position(|candidate| candidate.name == decl.name)
.unwrap_or(0) as u32
} else {
self.foreign_declaration(declaring, decl)
};
v1::TypeRef {
reference: reference.to_string(),
resolved: kind != v1::DeclKind::Constant,
package: declaring.name.clone(),
foreign: !local,
index,
kind: kind as i32,
}
}
fn foreign_declaration(&mut self, declaring: &'a v2::Package, decl: &v2::Decl) -> u32 {
let key = (declaring.name.clone(), decl.name.clone());
if let Some(index) = self.foreign_at.get(&key) {
return *index;
}
let index = self.foreign.len() as u32;
self.foreign_at.insert(key, index);
self.foreign.push(v1::ForeignDeclaration {
package: declaring.name.clone(),
declaration: None,
});
let lowered = self.declaration(declaring, decl, None);
self.foreign[index as usize].declaration = Some(lowered);
index
}
fn tuple_ref(&mut self, home: &'a v2::Package, tuple: &v2::TupleType, at: &Path) -> u32 {
if !at.named {
return self.foreign_tuple_ref(home, tuple, at);
}
let claim = (home.name.clone(), at.rust.clone());
if let Some(index) = self.tuple_at.get(&claim).copied() {
let existing = &self.tuples[index];
if existing.source == *tuple && existing.path.visibility == at.visibility {
self.tuples[index].keys.extend(at.key.clone());
return index as u32;
}
self.collisions.push(v1::TupleCollision {
name: at.rust.clone(),
first_fields: existing
.source
.fields
.iter()
.map(|field| field.name.clone())
.collect(),
second_fields: tuple
.fields
.iter()
.map(|field| field.name.clone())
.collect(),
});
}
let index = self.lift(home, tuple, at, at.wire.clone());
self.tuple_at.entry(claim).or_insert(index);
index as u32
}
fn foreign_tuple_ref(
&mut self,
home: &'a v2::Package,
tuple: &v2::TupleType,
at: &Path,
) -> u32 {
let key = (home.name.clone(), at.rust.clone());
let existing = self.foreign_tuple_at.get(&key).and_then(|entries| {
entries.iter().copied().find(|&index| {
let entry = &self.tuples[index];
entry.source == *tuple && entry.path.visibility == at.visibility
})
});
if let Some(index) = existing {
self.tuples[index].keys.extend(at.key.clone());
return index as u32;
}
let wire = if self.claims_only {
None
} else {
self.claims_of(home)
.get(&at.rust)
.and_then(|claims| {
claims
.iter()
.find(|claim| claim.source == *tuple && claim.visibility == at.visibility)
})
.map(|claim| claim.wire.clone())
};
let index = self.lift(home, tuple, at, wire.unwrap_or_default());
self.foreign_tuple_at.entry(key).or_default().push(index);
index as u32
}
fn claims_of(&mut self, declaring: &'a v2::Package) -> Rc<Claims> {
if let Some(claims) = self.claims_memo.borrow().get(&declaring.name) {
return Rc::clone(claims);
}
#[cfg(test)]
DECLARING_LOWERINGS.with(|count| count.set(count.get() + 1));
let others = self.scope.projection_others(declaring);
let mut lowering = Lowering::with_mode(
Scope {
package: declaring,
others: &others,
},
true,
);
lowering.declarations();
let mut claims = Claims::new();
for entry in lowering.tuples.iter().filter(|entry| entry.named) {
claims
.entry(entry.rust.clone())
.or_default()
.push(TupleClaim {
source: entry.source.clone(),
visibility: entry.path.visibility,
wire: entry.wire.clone(),
});
}
let claims = Rc::new(claims);
self.claims_memo
.borrow_mut()
.insert(declaring.name.clone(), Rc::clone(&claims));
claims
}
fn lift(
&mut self,
home: &'a v2::Package,
tuple: &v2::TupleType,
at: &Path,
wire: String,
) -> usize {
let index = self.tuples.len();
self.tuples.push(TupleEntry {
source: tuple.clone(),
home,
path: at.clone(),
rust: at.rust.clone(),
wire,
named: at.named,
keys: at.key.clone().into_iter().collect(),
docs: Vec::new(),
lowered: None,
});
index
}
fn drain_tuples(&mut self) {
let mut index = 0;
while index < self.tuples.len() {
if self.tuples[index].lowered.is_some() {
index += 1;
continue;
}
let source = self.tuples[index].source.clone();
let home = self.tuples[index].home;
let mut path = self.tuples[index].path.clone();
let named = self.tuples[index].named;
if named {
path.key = Some(vec![Step::Root(Root::Tuple(index as u32))]);
}
let docs = self.tuples[index].docs.clone();
let (rust, wire) = (
self.tuples[index].rust.clone(),
self.tuples[index].wire.clone(),
);
let fields: Vec<v1::Field> = source
.fields
.iter()
.enumerate()
.map(|(position, field)| {
let at = path.tuple_field(&field.name, position + 1);
let documented = docs.get(position).cloned().unwrap_or_default();
v1::Field {
name: Some(spellings(&field.name)),
r#type: field.r#type.as_ref().map(|ty| self.type_at(home, ty, &at)),
declared_init: None,
init: Some(self.inits.tuple_field(home, field)),
doc: documented.doc,
labels: Vec::new(),
deprecated: None,
links: documented.links,
see: documented.see,
since: documented.since,
rules: Vec::new(),
}
})
.collect();
let lowered = v1::InducedTuple {
name: Some(v1::InducedName {
rust: if named { rust } else { String::new() },
wire: if named { wire } else { String::new() },
}),
fields,
visibility: path.visibility,
closure: Some(self.closures.tuple(home, &source)),
init: Some(self.inits.tuple(home, &source)),
flatbuffers_table: None,
origin: path.origin(),
};
self.tuples[index].lowered = Some(lowered);
index += 1;
}
}
fn shapes(&mut self) -> Vec<v1::Interface> {
let package = self.scope.package;
let shapes: Vec<v2::InterfaceShape<'a>> = package.shapes().collect();
shapes
.iter()
.enumerate()
.map(|(index, shape)| self.interface(package, index as u32, shape))
.collect()
}
fn interface(
&mut self,
home: &'a v2::Package,
index: u32,
shape: &v2::InterfaceShape<'a>,
) -> v1::Interface {
let interface = shape.interface;
let visibility = shape.visibility();
let owner = match shape.service {
Some(service) => service.name.clone(),
None => interface.name.clone(),
};
let mut row = 0u32;
let slots: Vec<v1::InteractionSlot> = interface
.interactions
.iter()
.enumerate()
.map(|(slot, decl)| {
let ordinal = decl.ordinal;
match decl.kind.as_ref() {
Some(v2::decl::Kind::ReservedSlot(reserved)) => v1::InteractionSlot {
ordinal,
occupant: Some(v1::interaction_slot::Occupant::Retired(retired(reserved))),
},
_ => {
let this_row = row;
row += 1;
v1::InteractionSlot {
ordinal,
occupant: Some(v1::interaction_slot::Occupant::Interaction(Box::new(
self.interaction(
home,
decl,
index,
slot as u32,
this_row,
visibility,
&owner,
),
))),
}
}
}
})
.collect();
let table_budget = budget(&slots);
let inline_of_service = shape.service.and_then(|service| {
home.services
.iter()
.position(|candidate| candidate.name == service.name)
.map(|position| position as u32)
});
v1::Interface {
number: interface.number,
provisional: interface.provisional,
visibility,
doc: interface.doc.clone(),
labels: interface.labels.clone(),
deprecated: interface.deprecated.clone(),
links: doc_links(&interface.links),
see: doc_links(&interface.see),
since: interface.since.clone(),
rules: Vec::new(),
slots,
table_budget: Some(table_budget),
inline_of_service,
identity: Some(match shape.service {
Some(service) => v1::interface::Identity::Service(dotted_name(&service.name)),
None => v1::interface::Identity::Declared(spellings(&interface.name)),
}),
}
}
#[allow(clippy::too_many_arguments)]
fn interaction(
&mut self,
home: &'a v2::Package,
decl: &v2::Decl,
interface: u32,
slot: u32,
row: u32,
visibility: i32,
owner: &str,
) -> v1::Interaction {
let (kind, timing, shape, payloads, family) = match decl.kind.as_ref() {
Some(v2::decl::Kind::SignalDef(signal)) => {
let payload = self.payload(home, &signal.payload);
let payloads = vec![("payload", signal.payload.clone(), payload.sizes.clone())];
(
v1::Kind::Signal,
signal.timing.as_ref(),
Some(v1::interaction::Shape::Signal(v1::SignalShape {
payload: Some(payload),
declared_init: signal.declared_init.clone(),
init: Some(
self.inits
.signal(signal.init.as_ref(), signal.declared_init.is_some()),
),
})),
Some(payloads),
signal.family,
)
}
Some(v2::decl::Kind::EventDef(event)) => {
let payload = self.payload(home, &event.payload);
let payloads = vec![("payload", event.payload.clone(), payload.sizes.clone())];
(
v1::Kind::Event,
event.timing.as_ref(),
Some(v1::interaction::Shape::Event(v1::EventShape {
payload: Some(payload),
})),
Some(payloads),
event.family,
)
}
Some(v2::decl::Kind::CommandDef(command)) => {
let params = self.params(
home,
&command.params,
interface,
slot,
visibility,
owner,
decl.name.as_str(),
);
let request = single_param_type(&command.params)
.map(|reference| self.payload(home, reference));
let request_tuple = self.request_tuple(
home,
&command.params,
interface,
slot,
visibility,
owner,
decl.name.as_str(),
);
let request_induced = params_as_tuple(&command.params);
let request_sizes = self.sizes(
home,
&request_shape(&command.params, request_induced.as_ref()),
single_param_type(&command.params),
);
let clauses = self.clauses(home, &command.contracts, &command.params, None);
let payloads = vec![(
"request",
params_text(&command.params),
Some(request_sizes.clone()),
)];
(
v1::Kind::Command,
command.timing.as_ref(),
Some(v1::interaction::Shape::Command(v1::CommandShape {
params,
request,
clauses,
request_sizes: Some(request_sizes),
shape: command.shape,
request_tuple,
})),
Some(payloads),
command.family,
)
}
Some(v2::decl::Kind::QueryDef(query)) => {
let params = self.params(
home,
&query.params,
interface,
slot,
visibility,
owner,
decl.name.as_str(),
);
let request =
single_param_type(&query.params).map(|reference| self.payload(home, reference));
let request_tuple = self.request_tuple(
home,
&query.params,
interface,
slot,
visibility,
owner,
decl.name.as_str(),
);
let reply_named = query_reply_type(query);
let reply = query.return_type.as_ref().map(|ret| {
let at = Path::interaction(
interface,
slot,
visibility,
owner,
decl.name.as_str(),
Vec::new(),
)
.step("Reply");
self.reply(home, ret, &at)
});
let request_induced = params_as_tuple(&query.params);
let request_sizes = self.sizes(
home,
&request_shape(&query.params, request_induced.as_ref()),
single_param_type(&query.params),
);
let reply_induced = match reply_named {
Some(_) => None,
None => reply_as_tuple(query.return_type.as_ref()),
};
let reply_sizes = match (&reply_induced, query.return_type.as_ref()) {
(Some(tuple), _) => self.sizes(home, &PayloadShape::Tuple(tuple), None),
(None, Some(ret)) => self.sizes(home, &PayloadShape::Return(ret), reply_named),
(None, None) => absent_sizes(),
};
let mut payloads = vec![(
"request",
params_text(&query.params),
Some(request_sizes.clone()),
)];
if !query.return_type.as_ref().is_some_and(returns_stream) {
payloads.push((
"reply",
query
.return_type
.as_ref()
.map_or_else(|| "()".to_string(), return_text),
Some(reply_sizes.clone()),
));
}
let reply_payload = reply_named.map(|reference| self.payload(home, reference));
let reply_tuple = match reply_named {
Some(_) => None,
None => reply_as_tuple(query.return_type.as_ref()).map(|tuple| {
let at = Path::interaction(
interface,
slot,
visibility,
owner,
decl.name.as_str(),
Vec::new(),
)
.step("Reply");
v1::TupleRef {
index: self.tuple_ref(home, &tuple, &at),
}
}),
};
let clauses = self.clauses(home, &query.contracts, &query.params, reply_named);
(
v1::Kind::Query,
query.timing.as_ref(),
Some(v1::interaction::Shape::Query(Box::new(v1::QueryShape {
params,
request,
reply,
reply_payload,
clauses,
request_sizes: Some(request_sizes),
reply_sizes: Some(reply_sizes),
request_tuple,
reply_tuple,
}))),
Some(payloads),
query.family,
)
}
Some(v2::decl::Kind::FixedDef(fixed)) => {
let at = Path::interaction(
interface,
slot,
visibility,
owner,
decl.name.as_str(),
vec!["payload".to_string()],
);
let payload = fixed.payload.as_ref().map(|ty| self.type_at(home, ty, &at));
let named = fixed
.payload
.as_ref()
.and_then(named_type)
.map(|reference| self.payload(home, reference));
(
v1::Kind::Fixed,
None,
Some(v1::interaction::Shape::Fixed(v1::FixedShape {
payload,
named,
})),
Some(Vec::new()),
v2::Family::Dispatch as i32,
)
}
_ => (
v1::Kind::Unspecified,
None,
None,
None,
v2::Family::Dispatch as i32,
),
};
let member = decl.name.as_str();
let reservation = reserve(member, payloads.as_deref());
let streams = self.streams(home, decl);
v1::Interaction {
name: Some(spellings(&decl.name)),
kind: kind as i32,
row,
doc: decl.doc.clone(),
labels: decl.labels.clone(),
deprecated: decl.deprecated.clone(),
timing: timing.map(timing_of),
reservation: Some(reservation),
family,
shape,
links: doc_links(&decl.links),
see: doc_links(&decl.see),
since: decl.since.clone(),
rules: model_rules(home, &self.deps, ItemRef::Decl(decl)),
limit: decl.limit,
streams,
}
}
fn streams(&mut self, home: &'a v2::Package, decl: &v2::Decl) -> Vec<v1::StreamShape> {
let (params, reply) = match decl.kind.as_ref() {
Some(v2::decl::Kind::CommandDef(command)) => (&command.params, None),
Some(v2::decl::Kind::QueryDef(query)) => {
let reply = query.return_type.as_ref().and_then(|ret| match &ret.kind {
Some(v2::return_type::Kind::Value(ty)) => Some(ty),
_ => None,
});
(&query.params, reply)
}
_ => return Vec::new(),
};
fn carried(ty: &v2::FieldType) -> Option<&v2::StreamType> {
match ty.kind.as_ref() {
Some(v2::field_type::Kind::Stream(stream)) => Some(stream),
_ => None,
}
}
let mut streams = Vec::new();
let mut number = 0;
if let Some(stream) = reply.and_then(carried) {
streams.extend(self.stream_shape(home, number, stream));
}
for stream in params
.iter()
.filter_map(|param| param.r#type.as_ref().and_then(carried))
{
number += 1;
streams.extend(self.stream_shape(home, number, stream));
}
streams
}
fn stream_shape(
&mut self,
home: &'a v2::Package,
no: u32,
stream: &v2::StreamType,
) -> Option<v1::StreamShape> {
match stream.element.as_ref()? {
v2::stream_type::Element::Named(reference) => {
let payload = self.payload(home, reference);
Some(v1::StreamShape {
no,
form: v1::StreamForm::Elements as i32,
element: payload.r#type,
sizes: payload.sizes,
})
}
v2::stream_type::Element::Primitive(primitive) => {
let form = match v2::PrimitiveType::try_from(*primitive).ok()? {
v2::PrimitiveType::Bytes => v1::StreamForm::Bytes,
v2::PrimitiveType::String => v1::StreamForm::Text,
_ => return None,
};
Some(v1::StreamShape {
no,
form: form as i32,
element: None,
sizes: Some(absent_sizes()),
})
}
}
}
#[allow(clippy::too_many_arguments)]
fn request_tuple(
&mut self,
home: &'a v2::Package,
params: &[v2::Param],
interface: u32,
slot: u32,
visibility: i32,
owner: &str,
member: &str,
) -> Option<v1::TupleRef> {
if single_param_type(params).is_some() {
return None;
}
let tuple = params_as_tuple(params)?;
let at = Path::interaction(interface, slot, visibility, owner, member, Vec::new())
.step("Params");
let index = self.tuple_ref(home, &tuple, &at);
self.tuples[index as usize].docs = params
.iter()
.filter(|param| !param.r#type.as_ref().is_some_and(is_stream))
.map(FieldDoc::of_param)
.collect();
Some(v1::TupleRef { index })
}
#[allow(clippy::too_many_arguments)]
fn params(
&mut self,
home: &'a v2::Package,
params: &[v2::Param],
interface: u32,
slot: u32,
visibility: i32,
owner: &str,
member: &str,
) -> Vec<v1::Param> {
params
.iter()
.map(|param| {
let at = Path::interaction(interface, slot, visibility, owner, member, Vec::new())
.step("Param")
.field(¶m.name);
v1::Param {
name: Some(spellings(¶m.name)),
r#type: param.r#type.as_ref().map(|ty| self.type_at(home, ty, &at)),
doc: param.doc.clone(),
links: doc_links(¶m.links),
see: doc_links(¶m.see),
since: param.since.clone(),
rules: model_rules(home, &self.deps, ItemRef::Param(param)),
}
})
.collect()
}
fn reply(&mut self, home: &'a v2::Package, ret: &v2::ReturnType, at: &Path) -> v1::Reply {
v1::Reply {
kind: ret.kind.as_ref().map(|kind| match kind {
v2::return_type::Kind::Value(value) => {
v1::reply::Kind::Value(self.type_at(home, value, at))
}
v2::return_type::Kind::Fallible(fallible) => {
v1::reply::Kind::Fallible(v1::Fallible {
ok: Some(self.type_ref(home, &fallible.ok)),
err: Some(self.type_ref(home, &fallible.err)),
})
}
}),
}
}
fn clauses(
&mut self,
home: &'a v2::Package,
contracts: &[v2::Contract],
params: &[v2::Param],
reply_named: Option<&str>,
) -> Vec<v1::Clause> {
contracts
.iter()
.map(|contract| {
let reply = match v2::ContractKind::try_from(contract.kind) {
Ok(v2::ContractKind::Ensure) => reply_named,
_ => None,
};
v1::Clause {
kind: contract.kind,
source: contract.source.clone(),
signal_refs: contract.signal_refs.clone(),
param_refs: contract.param_refs.clone(),
uses_result: contract.uses_result,
observer_id: contract.observer_id.clone(),
translation: Some(clauses::translate(
self.scope,
home,
&contract.source,
params,
reply,
)),
}
})
.collect()
}
fn payload(&mut self, home: &'a v2::Package, reference: &str) -> v1::Payload {
let max_size = self
.scope
.resolve(home, reference)
.and_then(|(decl, declaring)| {
let others = self.scope.projection_others(declaring);
fb::max_size(
fb::Packages {
package: declaring,
others: &others,
},
decl,
)
});
v1::Payload {
r#type: Some(self.type_ref(home, reference)),
flatbuffers_max_size: max_size.map(|size| u32::try_from(size).unwrap_or(u32::MAX)),
sizes: Some(self.sizes(home, &PayloadShape::Named(reference), Some(reference))),
}
}
fn sizes(
&self,
home: &'a v2::Package,
shape: &PayloadShape<'_>,
reference: Option<&str>,
) -> v1::PayloadSizes {
v1::PayloadSizes {
proto3: Some(self.size_state(home, shape, reference, Encoding::Proto3)),
flatbuffers: Some(self.size_state(home, shape, reference, Encoding::FlatBuffers)),
podbuf: Some(self.size_state(home, shape, reference, Encoding::Podbuf)),
}
}
fn size_state(
&self,
home: &'a v2::Package,
shape: &PayloadShape<'_>,
reference: Option<&str>,
encoding: Encoding,
) -> v1::SizeState {
let state = match size::size_state(home, shape, &self.sizes, encoding) {
SizeState::Bounded(size) => v1::size_state::State::Bounded(size),
SizeState::Unbounded(cause) => {
let attribution = reference
.and_then(|reference| self.scope.resolve(home, reference))
.map(|(decl, declaring)| super::fb_unbounded(declaring, decl))
.unwrap_or_else(|| v1::FbUnbounded {
cause: cause as i32,
..Default::default()
});
v1::size_state::State::Unbounded(attribution)
}
SizeState::Absent(cause) => v1::size_state::State::Absent(v1::SizeAbsent {
cause: absent_cause(cause) as i32,
detail: None,
}),
};
v1::SizeState { state: Some(state) }
}
fn services(&mut self) -> Vec<v1::Service> {
let package = self.scope.package;
let shapes: Vec<&str> = package.shapes().map(|shape| shape.name).collect();
package
.services
.iter()
.map(|service| {
let interface_refs: Vec<String> = service
.shapes
.iter()
.filter_map(|slot| match slot.kind.as_ref()? {
v2::service_shape::Kind::InterfaceRef(reference) => Some(reference.clone()),
v2::service_shape::Kind::Inline(_) => None,
})
.collect();
let inline = service.shapes.iter().any(|slot| {
matches!(slot.kind.as_ref(), Some(v2::service_shape::Kind::Inline(_)))
});
let inline_shape = inline
.then(|| {
shapes
.iter()
.position(|name| *name == service.name)
.map(|position| position as u32)
})
.flatten();
v1::Service {
name: Some(dotted_name(&service.name)),
visibility: service.visibility,
doc: service.doc.clone(),
labels: service.labels.clone(),
deprecated: service.deprecated.clone(),
interface_refs,
inline_shape,
links: doc_links(&service.links),
see: doc_links(&service.see),
since: service.since.clone(),
rules: Vec::new(),
}
})
.collect()
}
fn catalog(&mut self, compatible: &[[u8; 32]]) -> v1::Catalog {
let unit = v2::unit_of(self.scope.package);
let packages: Vec<&v2::Package> = std::iter::once(self.scope.package)
.chain(self.scope.others.iter().copied())
.collect();
v1::Catalog {
package: unit.to_owned(),
hash: crate::catalog_hash::catalog_hash(unit, &packages).to_vec(),
retired: v2::unit_retired(unit, &packages)
.into_iter()
.map(|entry| v1::RetiredInterface {
name: entry.name,
number: entry.number,
})
.collect(),
compatible: compatible.iter().map(|hash| hash.to_vec()).collect(),
}
}
}
fn patch_flatbuffers(model: &mut v1::Model, projected: &flatbuffers::Projected) {
for tuple in &mut model.tuples {
let wire = tuple
.name
.as_ref()
.map(|name| name.wire.clone())
.unwrap_or_default();
tuple.flatbuffers_table = projected.tuple_tables.get(&wire).copied();
for (position, field) in tuple.fields.iter_mut().enumerate() {
let hint = format!("{wire}Field{}", position + 1);
if let Some(ty) = field.r#type.as_mut() {
patch_type(ty, &hint, projected);
}
}
}
for (index, declaration) in model.declarations.iter_mut().enumerate() {
let owner = declaration
.name
.as_ref()
.map(|name| name.declared.clone())
.unwrap_or_default();
match declaration.kind.as_mut() {
Some(v1::declaration::Kind::Struct(def)) => {
for slot in &mut def.slots {
if let Some(v1::slot::Occupant::Field(field)) = slot.occupant.as_mut() {
let hint = format!(
"{owner}{}",
joined_camel(
field
.name
.as_ref()
.map(|name| name.declared.as_str())
.unwrap_or_default()
)
);
if let Some(ty) = field.r#type.as_mut() {
patch_type(ty, &hint, projected);
}
}
}
}
Some(v1::declaration::Kind::Union(def)) => {
for (arm_index, arm) in def.arms.iter_mut().enumerate() {
arm.flatbuffers_box = projected
.arm_boxes
.get(&(index as u32, arm_index as u32))
.copied();
}
}
_ => {}
}
}
}
fn patch_type(ty: &mut v1::Type, hint: &str, projected: &flatbuffers::Projected) {
match ty.kind.as_mut() {
Some(v1::r#type::Kind::Array(array)) => {
if let Some(element) = array.element.as_mut() {
patch_type(element, &format!("{hint}Element"), projected);
}
}
Some(v1::r#type::Kind::Map(map)) => {
map.flatbuffers_entry_table = projected.entry_tables.get(hint).copied();
if let Some(key) = map.key.as_mut() {
patch_type(key, &format!("{hint}Key"), projected);
}
if let Some(value) = map.value.as_mut() {
patch_type(value, &format!("{hint}Value"), projected);
}
}
_ => {}
}
}
fn constraint(source: &v2::Constraint) -> v1::Constraint {
v1::Constraint {
min: source.min.clone(),
max: source.max.clone(),
step: source.step.clone(),
len_min: source.len_min,
len_max: source.len_max,
pattern: source
.pattern
.as_deref()
.map(|pattern| strip_regex_delimiters(pattern).to_string()),
pattern_const: source.pattern_const.clone(),
}
}
pub(super) type MemberPayload = (&'static str, String, Option<v1::PayloadSizes>);
fn params_text(params: &[v2::Param]) -> String {
single_param_type(params).map_or_else(
|| {
let names: Vec<&str> = params
.iter()
.filter(|param| !param.r#type.as_ref().is_some_and(is_stream))
.map(|param| param.name.as_str())
.collect();
format!("({})", names.join(", "))
},
str::to_string,
)
}
fn field_text(ty: &v2::FieldType) -> String {
use v2::field_type::Kind;
match ty.kind.as_ref() {
Some(Kind::Named(name)) => name.clone(),
Some(Kind::Primitive(_)) => "primitive".to_string(),
Some(Kind::InlineScalar(_)) => "inline scalar".to_string(),
Some(Kind::Tuple(_)) => "tuple".to_string(),
Some(Kind::Array(array)) => format!(
"array of {}",
array
.element
.as_ref()
.map_or_else(|| "no type".to_string(), |e| field_text(e))
),
Some(Kind::Map(_)) => "map".to_string(),
Some(Kind::Stream(_)) => "stream".to_string(),
None => "no type".to_string(),
}
}
fn return_text(ret: &v2::ReturnType) -> String {
match ret.kind.as_ref() {
Some(v2::return_type::Kind::Value(value)) => field_text(value),
Some(v2::return_type::Kind::Fallible(fallible)) => {
format!("{} | {}", fallible.ok, fallible.err)
}
None => "()".to_string(),
}
}
pub(super) fn reserve(member: &str, payloads: Option<&[MemberPayload]>) -> v1::Reservation {
let state = |pick: fn(&v1::PayloadSizes) -> &Option<v1::SizeState>| {
let Some(payloads) = payloads else {
return unsized_reservation(format!("{member}: no known payload shape"));
};
let mut total = 0u64;
for (role, text, sizes) in payloads {
let state = sizes
.as_ref()
.and_then(|sizes| pick(sizes).as_ref())
.and_then(|state| state.state.as_ref());
match state {
Some(v1::size_state::State::Bounded(size)) => {
total = total.saturating_add(u64::from(*size));
}
Some(v1::size_state::State::Unbounded(_) | v1::size_state::State::Absent(_))
| None => return unsized_reservation(format!("{member}.{role}: {text}")),
}
}
v1::ReservationState {
state: Some(v1::reservation_state::State::Bytes(total)),
}
};
v1::Reservation {
proto3: Some(state(|sizes| &sizes.proto3)),
flatbuffers: Some(state(|sizes| &sizes.flatbuffers)),
podbuf: Some(state(|sizes| &sizes.podbuf)),
}
}
fn unsized_reservation(by: String) -> v1::ReservationState {
v1::ReservationState {
state: Some(v1::reservation_state::State::Unsized(by)),
}
}
fn budget(slots: &[v1::InteractionSlot]) -> v1::Reservation {
let state = |pick: fn(&v1::Reservation) -> &Option<v1::ReservationState>| {
let mut total = 0u64;
for slot in slots {
let Some(v1::interaction_slot::Occupant::Interaction(member)) = slot.occupant.as_ref()
else {
continue;
};
let state = member
.reservation
.as_ref()
.and_then(|reservation| pick(reservation).as_ref())
.and_then(|state| state.state.as_ref());
match state {
Some(v1::reservation_state::State::Bytes(size)) => {
total = total.saturating_add(*size);
}
Some(v1::reservation_state::State::Unsized(_)) | None => {
let name = member
.name
.as_ref()
.map_or("", |name| name.declared.as_str());
return unsized_reservation(name.to_string());
}
}
}
v1::ReservationState {
state: Some(v1::reservation_state::State::Bytes(total)),
}
};
v1::Reservation {
proto3: Some(state(|reservation| &reservation.proto3)),
flatbuffers: Some(state(|reservation| &reservation.flatbuffers)),
podbuf: Some(state(|reservation| &reservation.podbuf)),
}
}
fn absent_sizes() -> v1::PayloadSizes {
let state = || v1::SizeState {
state: Some(v1::size_state::State::Absent(v1::SizeAbsent {
cause: v1::AbsentCause::EncodingUndefined as i32,
detail: None,
})),
};
v1::PayloadSizes {
proto3: Some(state()),
flatbuffers: Some(state()),
podbuf: Some(state()),
}
}
fn retired(reserved: &v2::Reserved) -> v1::Retired {
v1::Retired {
name: reserved.name.as_deref().map(spellings),
value: reserved.value,
}
}
fn enum_value(value: &v2::EnumValue) -> v1::EnumValue {
v1::EnumValue {
name: Some(spellings(&value.name)),
value: value.value,
doc: value.doc.clone(),
links: doc_links(&value.links),
see: doc_links(&value.see),
since: value.since.clone(),
rules: Vec::new(),
}
}
fn enum_body(def: &v2::EnumDef) -> v1::Enum {
let zero = crate::zero::enum_zero_member(&def.values);
let lowest = def
.values
.iter()
.enumerate()
.min_by_key(|(_, value)| value.value)
.map(|(index, _)| index);
v1::Enum {
values: def.values.iter().map(enum_value).collect(),
retired: def.reserved.iter().map(retired).collect(),
zero_member: zero.map(|index| index as u32),
init_member: zero.or(lowest).map(|index| index as u32),
open: def.open,
}
}
fn timing_of(timing: &v2::Timing) -> v1::Timing {
v1::Timing {
mode: timing.mode,
min_us: timing.min_us.clone(),
max_us: timing.max_us.clone(),
default_applied: timing.default_applied,
}
}
fn decl_kind(decl: &v2::Decl) -> v1::DeclKind {
match decl.kind {
Some(v2::decl::Kind::TypeDef(_)) => v1::DeclKind::Scalar,
Some(v2::decl::Kind::ConstDef(_)) => v1::DeclKind::Constant,
Some(v2::decl::Kind::StructDef(_)) => v1::DeclKind::Struct,
Some(v2::decl::Kind::EnumDef(_)) => v1::DeclKind::Enum,
Some(v2::decl::Kind::EnumSetDef(_)) => v1::DeclKind::EnumSet,
Some(v2::decl::Kind::UnionDef(_)) => v1::DeclKind::Union,
_ => v1::DeclKind::Unspecified,
}
}
pub fn params_as_tuple(params: &[v2::Param]) -> Option<v2::TupleType> {
let mut fields = Vec::with_capacity(params.len());
for param in params {
let ty = param.r#type.as_ref()?;
if is_stream(ty) {
continue;
}
fields.push(v2::TupleField {
name: param.name.clone(),
r#type: Some(ty.clone()),
});
}
Some(v2::TupleType { fields })
}
pub fn reply_as_tuple(ret: Option<&v2::ReturnType>) -> Option<v2::TupleType> {
let Some(ret) = ret else {
return Some(v2::TupleType::default());
};
match ret.kind.as_ref()? {
v2::return_type::Kind::Value(ty) => match ty.kind.as_ref()? {
v2::field_type::Kind::Tuple(tuple) => Some(tuple.clone()),
_ => None,
},
_ => None,
}
}
pub fn request_shape<'a>(
params: &'a [v2::Param],
induced: Option<&'a v2::TupleType>,
) -> PayloadShape<'a> {
match (induced, single_param_type(params)) {
(Some(tuple), None) => PayloadShape::Tuple(tuple),
(_, Some(name)) if params.len() > 1 => PayloadShape::Named(name),
_ => PayloadShape::Params(params),
}
}
fn is_stream(ty: &v2::FieldType) -> bool {
matches!(ty.kind, Some(v2::field_type::Kind::Stream(_)))
}
fn returns_stream(ret: &v2::ReturnType) -> bool {
matches!(&ret.kind, Some(v2::return_type::Kind::Value(ty)) if is_stream(ty))
}
fn single_param_type(params: &[v2::Param]) -> Option<&str> {
let mut requested = params
.iter()
.filter(|param| !param.r#type.as_ref().is_some_and(is_stream));
let param = requested.next()?;
if requested.next().is_some() {
return None;
}
named_type(param.r#type.as_ref()?)
}
pub fn query_reply_type(query: &v2::QueryDef) -> Option<&str> {
match query.return_type.as_ref()?.kind.as_ref()? {
v2::return_type::Kind::Value(value) => named_type(value),
v2::return_type::Kind::Fallible(_) => None,
}
}
fn named_type(ty: &v2::FieldType) -> Option<&str> {
match ty.kind.as_ref()? {
v2::field_type::Kind::Named(name) => Some(name),
_ => None,
}
}
fn primitive_keyword(reference: &str) -> Option<v1::PrimitiveType> {
match reference {
"boolean" => Some(v1::PrimitiveType::Boolean),
"integer" => Some(v1::PrimitiveType::Integer),
"float" => Some(v1::PrimitiveType::Float),
"string" => Some(v1::PrimitiveType::String),
"bytes" => Some(v1::PrimitiveType::Bytes),
_ => None,
}
}
fn strip_regex_delimiters(regex: &str) -> &str {
regex
.strip_prefix('/')
.and_then(|rest| rest.strip_suffix('/'))
.unwrap_or(regex)
}
pub(super) fn absent_cause(cause: AbsentCause) -> v1::AbsentCause {
match cause {
AbsentCause::EncodingUndefined => v1::AbsentCause::EncodingUndefined,
AbsentCause::NoMessage => v1::AbsentCause::NoMessage,
AbsentCause::RefusedMember => v1::AbsentCause::RefusedMember,
AbsentCause::NoBound => v1::AbsentCause::NoBound,
AbsentCause::Overflow => v1::AbsentCause::Overflow,
AbsentCause::Unresolved => v1::AbsentCause::Unresolved,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_foreign_position_with_no_claim_keeps_an_empty_wire_name() {
let a = v2::Package {
name: "a".to_string(),
decls: vec![v2::Decl {
name: "S".to_string(),
visibility: v2::Visibility::Public as i32,
kind: Some(v2::decl::Kind::StructDef(v2::StructDef::default())),
..Default::default()
}],
..Default::default()
};
let b = v2::Package {
name: "b".to_string(),
..Default::default()
};
let others = [&a];
let mut lowering = Lowering::new(Scope {
package: &b,
others: &others,
});
let at = Path::foreign("a", "S", v2::Visibility::Public as i32).field("t");
assert_eq!(at.wire, "ST");
let tuple = v2::TupleType {
fields: vec![v2::TupleField {
name: "x".to_string(),
r#type: None,
}],
};
let index = lowering.foreign_tuple_ref(&a, &tuple, &at) as usize;
assert_eq!(lowering.tuples[index].wire, "");
}
}