Skip to content
Open
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: 1 addition & 2 deletions apps/hash-api/src/graph/ontology/primitive/data-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export const createDataType: ImpureGraphFunction<
const shortname =
webShortname ??
(await getWebShortname(ctx, authentication, {
accountOrAccountGroupId: params.webId,
accountOrAccountGroupId: webId,
}));

const { graphApi } = ctx;
Expand All @@ -81,7 +81,6 @@ export const createDataType: ImpureGraphFunction<
authentication.actorId,
{
schema,
webId,
provenance: {
...ctx.provenance,
...params.provenance,
Expand Down
1 change: 0 additions & 1 deletion apps/hash-api/src/graph/ontology/primitive/entity-type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ export const createEntityType: ImpureGraphFunction<
const { data: metadata } = await graphApi.createEntityType(
authentication.actorId,
{
webId,
schema,
provenance: {
...ctx.provenance,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ export const createPropertyType: ImpureGraphFunction<
const { data: metadata } = await graphApi.createPropertyType(
authentication.actorId,
{
webId,
schema,
provenance: {
...ctx.provenance,
Expand Down
44 changes: 17 additions & 27 deletions apps/hash-api/src/graph/ontology/primitive/util.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import type { VersionedUrl, WebId } from "@blockprotocol/type-system";
import { entityIdFromComponents } from "@blockprotocol/type-system";
import { getWebById } from "@local/hash-graph-sdk/principal/web";
import { frontendUrl } from "@local/hash-isomorphic-utils/environment";
import { isSelfHostedInstance } from "@local/hash-isomorphic-utils/instance";

import type { ImpureGraphFunction } from "../../context-types";
import { getOrgById } from "../../knowledge/system-types/org";
import { getUser } from "../../knowledge/system-types/user";

export const isExternalTypeId = (typeId: VersionedUrl) =>
!typeId.startsWith(frontendUrl) &&
Expand All @@ -19,27 +17,19 @@ export const getWebShortname: ImpureGraphFunction<
accountOrAccountGroupId: WebId;
},
Promise<string>
> = async (ctx, authentication, params) => {
const namespace = (
(await getUser(ctx, authentication, {
entityId: entityIdFromComponents(
params.accountOrAccountGroupId,
params.accountOrAccountGroupId,
),
})) ??
(await getOrgById(ctx, authentication, {
entityId: entityIdFromComponents(
params.accountOrAccountGroupId,
params.accountOrAccountGroupId,
),
}).catch(() => undefined))
)?.shortname;

if (!namespace) {
throw new Error(
`failed to get namespace for owner: ${params.accountOrAccountGroupId}`,
);
}

return namespace;
};
> = (ctx, authentication, params) =>
getWebById(ctx.graphApi, authentication, params.accountOrAccountGroupId).then(
(web) => {
if (!web) {
throw new Error(
`failed to get web for id: ${params.accountOrAccountGroupId}`,
);
}
if (!web.shortname) {
throw new Error(
`Shortname is not set for web: ${params.accountOrAccountGroupId}`,
);
}
return web.shortname;
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,9 @@ pub trait ValidateOntologyType<T> {
fn validate(&self, ontology_type: &T) -> Result<(), Report<DomainValidationError>>;
}

#[expect(dead_code, reason = "We currently don't validate the shortname")]
struct ShortNameAndKind<'a> {
pub short_name: &'a str,
pub kind: &'a str,
short_name: &'a str,
kind: &'a str,
}

/// Responsible for validating Type Urls against a known valid pattern.
Expand Down Expand Up @@ -103,6 +102,19 @@ impl DomainValidator {

Ok(ShortNameAndKind { short_name, kind })
}

/// Extracts the shortname from a type URL.
///
/// # Errors
///
/// - [`DomainValidationError`], if the URL doesn't match or is missing the shortname capture
pub fn extract_shortname<'a>(
&'a self,
url: &'a str,
) -> Result<&'a str, Report<DomainValidationError>> {
self.extract_shortname_and_kind(url)
.map(|result| result.short_name)
}
}

impl ValidateOntologyType<DataType> for DomainValidator {
Expand All @@ -124,10 +136,6 @@ impl ValidateOntologyType<DataType> for DomainValidator {
});
}

// TODO: check that the user has write access to the shortname, this will require us
// making the graph aware of shortnames. We can store them alongside accountIds. We
// should not have to make the graph aware of User entities being a thing however.
// see https://linear.app/hash/issue/H-3010
Ok(())
}
}
Expand All @@ -151,10 +159,6 @@ impl ValidateOntologyType<PropertyType> for DomainValidator {
});
}

// TODO: check that the user has write access to the shortname, this will require us
// making the graph aware of shortnames. We can store them alongside accountIds. We
// should not have to make the graph aware of User entities being a thing however.
// see https://linear.app/hash/issue/H-3010
Ok(())
}
}
Expand All @@ -178,10 +182,6 @@ impl ValidateOntologyType<EntityType> for DomainValidator {
});
}

// TODO: check that the user has write access to the shortname, this will require us
// making the graph aware of shortnames. We can store them alongside accountIds. We
// should not have to make the graph aware of User entities being a thing however.
// see https://linear.app/hash/issue/H-3010
Ok(())
}
}
12 changes: 0 additions & 12 deletions libs/@local/graph/api/openapi/openapi.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 42 additions & 22 deletions libs/@local/graph/api/src/rest/data_type.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Web routes for CRU operations on Data Types.

use alloc::sync::Arc;
use std::collections::{HashMap, HashSet};
use std::collections::{HashMap, HashSet, hash_map};

use axum::{
Extension, Router,
Expand All @@ -14,6 +14,7 @@ use hash_graph_postgres_store::{
store::error::{OntologyVersionDoesNotExist, VersionedUrlAlreadyExists},
};
use hash_graph_store::{
account::AccountStore as _,
data_type::{
ArchiveDataTypeParams, CreateDataTypeParams, DataTypeConversionTargets, DataTypeQueryToken,
DataTypeStore, FindDataTypeConversionTargetsParams, FindDataTypeConversionTargetsResponse,
Expand Down Expand Up @@ -143,7 +144,6 @@ impl DataTypeResource {
struct CreateDataTypeRequest {
#[schema(inline)]
schema: MaybeListOfDataType,
web_id: WebId,
provenance: ProvidedOntologyEditionProvenance,
conversions: HashMap<BaseUrl, Conversions>,
}
Expand Down Expand Up @@ -182,33 +182,53 @@ where

let Json(CreateDataTypeRequest {
schema,
web_id,
provenance,
conversions,
}) = body;

let is_list = matches!(&schema, ListOrValue::List(_));

let mut web_cache = HashMap::<String, WebId>::new();
let mut params = Vec::new();
for schema in schema {
domain_validator
.validate(&schema)
.map_err(report_to_response)?;

let shortname = domain_validator
.extract_shortname(schema.id.base_url.as_str())
.map_err(report_to_response)?;

let web_id = match web_cache.entry(shortname.to_owned()) {
hash_map::Entry::Occupied(entry) => *entry.get(),
hash_map::Entry::Vacant(entry) => {
let web_id = store
.get_web_by_shortname(actor_id, shortname)
.await
.map_err(report_to_response)?
.ok_or_else(|| {
status_to_response(Status::<()>::new(
hash_status::StatusCode::NotFound,
Some(format!("No web found for shortname `{shortname}`")),
vec![],
))
})?
.id;
*entry.insert(web_id)
}
};

params.push(CreateDataTypeParams {
schema,
ownership: OntologyOwnership::Local { web_id },
conflict_behavior: ConflictBehavior::Fail,
provenance: provenance.clone(),
conversions: conversions.clone(),
});
}

let mut metadata = store
.create_data_types(
actor_id,
schema
.into_iter()
.map(|schema| {
domain_validator
.validate(&schema)
.map_err(report_to_response)?;

Ok(CreateDataTypeParams {
schema,
ownership: OntologyOwnership::Local { web_id },
conflict_behavior: ConflictBehavior::Fail,
provenance: provenance.clone(),
conversions: conversions.clone(),
})
})
.collect::<Result<Vec<_>, BoxedResponse>>()?,
)
.create_data_types(actor_id, params)
.await
.map_err(report_to_response)?;

Expand Down
Loading
Loading