///|
/// A collection of types generated from JSON Schema (upstream `TypeSpace`).
pub struct TypeSpace {
priv mut next_id : Int
priv definitions : Map[RefKey, @schema.Schema]
/// All types by id, in id order.
id_to_entry : @sorted_map.SortedMap[TypeId, TypeEntry]
priv type_to_id : Map[String, TypeId]
priv name_to_id : Map[String, TypeId]
priv ref_to_id : Map[RefKey, TypeId]
mut uses_chrono : Bool
mut uses_uuid : Bool
mut uses_serde_json : Bool
mut uses_regress : Bool
settings : TypeSpaceSettings
/// Schema conversions from settings (upstream `SchemaCache`).
priv cache : Array[(@schema.SchemaObject, TypeEntry)]
/// Shared default functions that generated code may call.
defaults : Array[DefaultImpl]
}
///|
/// Create a type space with the given settings.
pub fn TypeSpace::new(settings : TypeSpaceSettings) -> TypeSpace {
let cache = []
for c in settings.convert {
cache.push(
(
{ ..c.schema, metadata: None, },
TypeEntry::new_native(c.type_name, c.impls),
),
)
}
{
next_id: 1,
definitions: Map([]),
id_to_entry: @sorted_map.SortedMap([]),
type_to_id: Map([]),
name_to_id: Map([]),
ref_to_id: Map([]),
uses_chrono: false,
uses_uuid: false,
uses_serde_json: false,
uses_regress: false,
settings: settings.copy(),
cache,
defaults: [],
}
}
///|
fn TypeSpace::cache_lookup(
self : TypeSpace,
schema : @schema.SchemaObject,
) -> TypeEntry? {
let search = { ..schema, metadata: None, }
for entry in self.cache {
if entry.0 == search {
return Some(entry.1)
}
}
None
}
///|
/// The number of types in the space.
pub fn TypeSpace::type_count(self : TypeSpace) -> Int {
self.id_to_entry.length()
}
///|
/// Look up a type entry.
pub fn TypeSpace::entry(
self : TypeSpace,
id : TypeId,
) -> TypeEntry raise TypifyError {
match self.id_to_entry.get(id) {
Some(e) => e
None => raise InvalidTypeId
}
}
///|
fn TypeSpace::entry_unchecked(
self : TypeSpace,
id : TypeId,
) -> TypeEntry raise TypifyError {
match self.id_to_entry.get(id) {
Some(e) => e
None =>
raise panic_with(
"called `Option::unwrap()` on a `None` value (type \{id})",
)
}
}
///|
/// All type entries in id order.
pub fn TypeSpace::entries(self : TypeSpace) -> Array[(TypeId, TypeEntry)] {
self.id_to_entry.to_array()
}
///|
/// Add named definitions that may reference each other. Each call must be
/// self-contained.
pub fn TypeSpace::add_ref_types(
self : TypeSpace,
type_defs : Array[(String, @schema.Schema)],
) -> Unit raise TypifyError {
self.add_ref_types_impl(type_defs.map(d => (RefKey::Def(d.0), d.1)))
}
///|
fn TypeSpace::add_ref_types_impl(
self : TypeSpace,
definitions : Array[(RefKey, @schema.Schema)],
) -> Unit raise TypifyError {
// Assign ids first so forward and cyclic references resolve.
let base_id = self.next_id
let def_len = definitions.length()
self.next_id += def_len
for index, def in definitions {
self.ref_to_id[def.0] = TypeId(base_id + index)
self.definitions[def.0] = def.1
}
for index, def in definitions {
let (ref_name, schema) = def
let type_id = TypeId(base_id + index)
let maybe_replace = match ref_name {
Root => None
Def(def_name) => self.settings.replace.get(sanitize(def_name, Pascal))
}
match maybe_replace {
None => {
let type_name = match ref_name {
Def(name) => Name::Required(name)
Root => Unknown
}
self.convert_ref_type(type_name, schema, type_id)
}
Some(replace) =>
self.id_to_entry.set(
type_id,
TypeEntry::new_native(replace.replace_type, replace.impls.copy()),
)
}
}
// Break containment cycles; a reference is needed to form a cycle.
self.break_cycles(base_id, base_id + def_len)
self.finalize_range(base_id)
}
///|
/// Finalize all entries with ids in `[base_id, next_id)`.
fn TypeSpace::finalize_range(
self : TypeSpace,
base_id : Int,
) -> Unit raise TypifyError {
let end = self.next_id
for index in base_id.. Unit raise TypifyError {
let (type_entry, metadata) = self.convert_schema(type_name, schema)
let default = metadata_default(metadata)
let type_entry = match type_entry.details {
Enum(_) | Struct(_) | Newtype(_) => type_entry.with_default(default)
// A simple alias to another definition.
Reference(ref_id) =>
TypeEntryNewtype::from_metadata(self, type_name, metadata, ref_id, schema)
Native(native) if native.name_match(type_name) => type_entry
// Unnamed types become newtypes.
_ => {
let subtype_id = self.assign_type(type_entry)
TypeEntryNewtype::from_metadata(
self, type_name, metadata, subtype_id, schema,
)
}
}
if type_entry.name() is Some(entry_name) {
self.name_to_id[entry_name] = type_id
}
self.id_to_entry.set(type_id, type_entry)
}
///|
/// Add a type and return its id.
pub fn TypeSpace::add_type(
self : TypeSpace,
schema : @schema.Schema,
) -> TypeId raise TypifyError {
self.add_type_with_name(schema, None)
}
///|
/// Add a type with a name hint and return its id.
pub fn TypeSpace::add_type_with_name(
self : TypeSpace,
schema : @schema.Schema,
name_hint : String?,
) -> TypeId raise TypifyError {
let base_id = self.next_id
let name : Name = match name_hint {
Some(s) => Suggested(s)
None => Unknown
}
let (type_id, _) = self.id_for_schema(name, schema)
self.finalize_range(base_id)
type_id
}
///|
/// Add all definitions of a root schema, plus the root itself if it has a
/// title. Returns the root's id in that case.
pub fn TypeSpace::add_root_schema(
self : TypeSpace,
root : @schema.RootSchema,
) -> TypeId? raise TypifyError {
let defs : Array[(RefKey, @schema.Schema)] = root.definitions
.to_array()
.map(d => (RefKey::Def(d.0), d.1))
let root_type = metadata_title(root.schema.metadata) is Some(_)
if root_type {
defs.push((Root, Object(root.schema)))
}
self.add_ref_types_impl(defs)
if root_type {
self.ref_to_id.get(Root)
} else {
None
}
}
///|
fn TypeSpace::assign(self : TypeSpace) -> TypeId {
let id = TypeId(self.next_id)
self.next_id += 1
id
}
///|
/// Assign an id to an entry, resolving references and deduplicating: named
/// types by name (first wins), unnamed types structurally.
fn TypeSpace::assign_type(self : TypeSpace, ty : TypeEntry) -> TypeId {
if ty.details is Reference(type_id) {
return type_id
}
match ty.name() {
Some(name) =>
match self.name_to_id.get(name) {
Some(type_id) => type_id
None => {
let type_id = self.assign()
self.name_to_id[name] = type_id
self.id_to_entry.set(type_id, ty)
type_id
}
}
None => {
let key = ty.details.dedup_key()
match self.type_to_id.get(key) {
Some(type_id) => type_id
None => {
let type_id = self.assign()
self.type_to_id[key] = type_id
self.id_to_entry.set(type_id, ty)
type_id
}
}
}
}
}
///|
/// Convert a schema and assign it an id (for sub-types).
fn TypeSpace::id_for_schema(
self : TypeSpace,
type_name : Name,
schema : @schema.Schema,
) -> (TypeId, @schema.Metadata?) raise TypifyError {
let (type_entry, metadata) = self.convert_schema(type_name, schema)
let type_entry = match metadata {
Some(m) => type_entry.with_default(m.default)
None => type_entry
}
(self.assign_type(type_entry), metadata)
}
///|
fn TypeSpace::id_to_option(self : TypeSpace, id : TypeId) -> TypeId {
self.assign_type(TypeEntry::from_details(Option(id)))
}
///|
fn TypeSpace::type_to_option(self : TypeSpace, ty : TypeEntry) -> TypeEntry {
TypeEntry::from_details(Option(self.assign_type(ty)))
}
///|
fn TypeSpace::id_to_box(self : TypeSpace, id : TypeId) -> TypeId {
self.assign_type(TypeEntry::from_details(Box(id)))
}
///|
fn TypeEntry::finalize(
self : TypeEntry,
space : TypeSpace,
) -> TypeEntry raise TypifyError {
let entry = match self.details {
Enum(e) => { ..self, details: Enum(e.finalize(space)), }
_ => self
}
entry.check_defaults(space)
entry
}
///|
fn TypeSpace::add_default_impl(self : TypeSpace, d : DefaultImpl) -> Unit {
if !self.defaults.contains(d) {
self.defaults.push(d)
self.defaults.sort()
}
}
///|
/// A short textual description of a type, for debugging (upstream
/// `TypeEntry::describe`).
pub fn TypeEntry::describe(self : TypeEntry) -> String {
match self.details {
Enum({ name, .. }) => "enum \{name}"
Struct({ name, .. }) => "struct \{name}"
Newtype({ name, type_id, .. }) => "newtype \{name} \{type_id.0}"
Unit => "()"
Option(t) => "option \{t.0}"
Vec(t) => "vec \{t.0}"
Map(k, v) => "map \{k.0} \{v.0}"
Set(t) => "set \{t.0}"
Box(t) => "box \{t.0}"
Tuple(ts) => "tuple (\{ts.map(t => t.0.to_string()).join(", ")})"
Array(t, n) => "array \{t.0}; \{n}"
Boolean => "bool"
Native({ type_name, .. }) | Integer(type_name) | Float(type_name) =>
type_name
String => "string"
JsonValue => "json value"
Reference(_) => "reference"
}
}