Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion typify-impl/src/conversions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ pub(crate) struct SchemaCache {

impl SchemaCache {
pub fn insert(&mut self, schema: &SchemaObject, type_name: &String, impls: &[TypeSpaceImpl]) {
let type_entry = TypeEntry::new_native(type_name, TypeSpaceImpl::native_traits(impls));
let type_entry =
TypeEntry::new_consumer_native(type_name, TypeSpaceImpl::native_traits(impls), &[]);
self.schemas.push((
SchemaObject {
metadata: None,
Expand Down
3 changes: 2 additions & 1 deletion typify-impl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,9 +599,10 @@ impl TypeSpace {
}

Some(replace_type) => {
let type_entry = TypeEntry::new_native(
let type_entry = TypeEntry::new_consumer_native(
replace_type.replace_type.clone(),
TypeSpaceImpl::native_traits(&replace_type.impls),
&[],
);
self.id_to_entry.insert(type_id, type_entry);
}
Expand Down
10 changes: 9 additions & 1 deletion typify-impl/src/rust_extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,16 @@ impl TypeSpace {
.collect::<Result<Vec<_>>>()
.ok()?;

Some(TypeEntry::new_native_params(
Some(TypeEntry::new_consumer_native(
format!("::{path}"),
[
typespace::TypespaceTrait::Clone,
typespace::TypespaceTrait::Debug,
typespace::TypespaceTrait::Serialize,
typespace::TypespaceTrait::Deserialize,
]
.into_iter()
.collect(),
&param_ids,
))
}
Expand Down
25 changes: 14 additions & 11 deletions typify-impl/src/type_entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,24 +343,27 @@ impl From<Type<TypeId>> for TypeEntry {
}

impl TypeEntry {
/// A native typify knows: `traits` is everything it implements.
pub(crate) fn new_native<S: ToString>(
type_name: S,
traits: typespace::TypespaceTraitSet,
) -> Self {
Type::Native(Native::new(&type_name.to_string(), traits, Vec::new())).into()
}

pub(crate) fn new_native_params<S: ToString>(type_name: S, params: &[TypeId]) -> Self {
let traits = [
typespace::TypespaceTrait::Clone,
typespace::TypespaceTrait::Debug,
typespace::TypespaceTrait::Serialize,
typespace::TypespaceTrait::Deserialize,
]
.into_iter()
.collect::<typespace::TypespaceTraitSet>();

Type::Native(Native::new(&type_name.to_string(), traits, params.to_vec())).into()
/// A native type that the consumer named, through a replacement, a
/// conversion, or an `x-rust-type` extension. `traits` is what the
/// consumer vouched for. Ultimately the compiler decides whether the
/// generated code is valid...
pub(crate) fn new_consumer_native<S: ToString>(
type_name: S,
traits: typespace::TypespaceTraitSet,
params: &[TypeId],
) -> Self {
Type::Native(
Native::new(&type_name.to_string(), traits, params.to_vec()).with_rest_unknown(),
)
.into()
}

pub(crate) fn new_boolean() -> Self {
Expand Down
53 changes: 53 additions & 0 deletions typify-impl/tests/test_consumer_natives.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2026 Oxide Computer Company

//! A type the consumer names stands in for whatever it implements.

use schemars::schema::RootSchema;
use typespace::TypespaceTrait;
use typify_impl::{TypeSpace, TypeSpaceSettings};

fn schema() -> RootSchema {
serde_json::from_value(serde_json::json!({
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Holder",
"type": "object",
"properties": {
"ids": {
"type": "array",
"items": { "$ref": "#/definitions/Id" },
"uniqueItems": true
}
},
"required": ["ids"],
"definitions": {
"Id": { "type": "string" }
}
}))
.unwrap()
}

/// A set of a replaced type generates. typify cannot see what the
/// replacement implements, so what a set asks of it is left to the
/// compiler, which checks the real type; typify's own refusal was a
/// claim it had no basis for.
#[test]
fn a_set_of_a_replaced_type_generates() {
let mut settings = TypeSpaceSettings::default();
settings.with_replacement("Id", "my_crate::Id", [].into_iter());
let mut type_space = TypeSpace::new(&settings);
type_space.add_root_schema(schema()).unwrap();
let rendered = type_space.to_stream().unwrap().to_string();
assert!(rendered.contains("my_crate :: Id"), "{rendered}");
}

/// The same holds under a crate-wide required trait.
#[test]
fn a_replaced_type_satisfies_a_required_trait() {
let mut settings = TypeSpaceSettings::default();
settings
.with_replacement("Id", "my_crate::Id", [].into_iter())
.map_typespace_settings(|s| s.with_required_trait(TypespaceTrait::PartialEq));
let mut type_space = TypeSpace::new(&settings);
type_space.add_root_schema(schema()).unwrap();
type_space.to_stream().unwrap();
}
Loading