1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::str::FromStr;
4
5use crate::HashError;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
8#[serde(rename_all = "kebab-case")]
9pub enum CanonicalizationProfile {
10 RawBytesV1,
11 AreteJcsV1,
12 FramedTupleV1,
13 ArtifactTreeV1,
14}
15
16impl CanonicalizationProfile {
17 pub const fn as_str(self) -> &'static str {
18 match self {
19 Self::RawBytesV1 => "raw-bytes-v1",
20 Self::AreteJcsV1 => "arete-jcs-v1",
21 Self::FramedTupleV1 => "framed-tuple-v1",
22 Self::ArtifactTreeV1 => "artifact-tree-v1",
23 }
24 }
25}
26
27impl fmt::Display for CanonicalizationProfile {
28 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
29 formatter.write_str(self.as_str())
30 }
31}
32
33impl FromStr for CanonicalizationProfile {
34 type Err = HashError;
35
36 fn from_str(value: &str) -> Result<Self, Self::Err> {
37 match value {
38 "raw-bytes-v1" => Ok(Self::RawBytesV1),
39 "arete-jcs-v1" => Ok(Self::AreteJcsV1),
40 "framed-tuple-v1" => Ok(Self::FramedTupleV1),
41 "artifact-tree-v1" => Ok(Self::ArtifactTreeV1),
42 _ => Err(HashError::InvalidHashId("unknown canonicalization profile")),
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
48#[serde(rename_all = "kebab-case")]
49pub enum HashKindName {
50 IdlSource,
51 IdlContent,
52 IdlPortable,
53 IdlNormalized,
54 ProgramSpec,
55 AstPortable,
56 RuntimeArtifact,
57 ArtifactFile,
58 DecoderContent,
59 SdkDefinition,
60 SdkExtension,
61 SdkOutputTree,
62 Compiler,
63 ProgramRelease,
64 LiveSpec,
65 StackManifest,
66 DeploymentRelease,
67 DecoderFixtureSet,
68}
69
70impl HashKindName {
71 pub const fn as_str(self) -> &'static str {
72 match self {
73 Self::IdlSource => "idl-source",
74 Self::IdlContent => "idl-content",
75 Self::IdlPortable => "idl-portable",
76 Self::IdlNormalized => "idl-normalized",
77 Self::ProgramSpec => "program-spec",
78 Self::AstPortable => "ast-portable",
79 Self::RuntimeArtifact => "runtime-artifact",
80 Self::ArtifactFile => "artifact-file",
81 Self::DecoderContent => "decoder-content",
82 Self::SdkDefinition => "sdk-definition",
83 Self::SdkExtension => "sdk-extension",
84 Self::SdkOutputTree => "sdk-output-tree",
85 Self::Compiler => "compiler",
86 Self::ProgramRelease => "program-release",
87 Self::LiveSpec => "live-spec",
88 Self::StackManifest => "stack-manifest",
89 Self::DeploymentRelease => "deployment-release",
90 Self::DecoderFixtureSet => "decoder-fixture-set",
91 }
92 }
93}
94
95impl fmt::Display for HashKindName {
96 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
97 formatter.write_str(self.as_str())
98 }
99}
100
101impl FromStr for HashKindName {
102 type Err = HashError;
103
104 fn from_str(value: &str) -> Result<Self, Self::Err> {
105 match value {
106 "idl-source" => Ok(Self::IdlSource),
107 "idl-content" => Ok(Self::IdlContent),
108 "idl-portable" => Ok(Self::IdlPortable),
109 "idl-normalized" => Ok(Self::IdlNormalized),
110 "program-spec" => Ok(Self::ProgramSpec),
111 "ast-portable" => Ok(Self::AstPortable),
112 "runtime-artifact" => Ok(Self::RuntimeArtifact),
113 "artifact-file" => Ok(Self::ArtifactFile),
114 "decoder-content" => Ok(Self::DecoderContent),
115 "sdk-definition" => Ok(Self::SdkDefinition),
116 "sdk-extension" => Ok(Self::SdkExtension),
117 "sdk-output-tree" => Ok(Self::SdkOutputTree),
118 "compiler" => Ok(Self::Compiler),
119 "program-release" => Ok(Self::ProgramRelease),
120 "live-spec" => Ok(Self::LiveSpec),
121 "stack-manifest" => Ok(Self::StackManifest),
122 "deployment-release" => Ok(Self::DeploymentRelease),
123 "decoder-fixture-set" => Ok(Self::DecoderFixtureSet),
124 _ => Err(HashError::UnknownKind(value.to_string())),
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
130#[serde(rename_all = "kebab-case")]
131pub enum Visibility {
132 Public,
133 AuthenticatedOwner,
134 InternalOnly,
135}
136
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "kebab-case")]
139pub enum IdentityClass {
140 ExactSource,
141 CanonicalContent,
142 PortableContent,
143 NormalizedContent,
144 Composite,
145 ArtifactTree,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
149#[serde(rename_all = "camelCase")]
150pub struct IdentityMetadata {
151 pub kind: HashKindName,
152 pub profile: CanonicalizationProfile,
153 pub visibility: Visibility,
154 pub identity_class: IdentityClass,
155 pub api_field: &'static str,
156 pub rust_type: &'static str,
157 pub typescript_type: &'static str,
158 pub projection: &'static str,
159 pub allowed_dto_audiences: &'static [Visibility],
160 pub database_mappings: &'static [&'static str],
161 pub legacy_aliases: &'static [&'static str],
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
165#[serde(rename_all = "camelCase")]
166pub struct NonHashIdentityMetadata {
167 pub api_field: &'static str,
168 pub rust_type: &'static str,
169 pub typescript_type: &'static str,
170 pub projection: &'static str,
171 pub visibility: Visibility,
172 pub allowed_dto_audiences: &'static [Visibility],
173 pub database_mappings: &'static [&'static str],
174 pub legacy_aliases: &'static [&'static str],
175}
176
177const PUBLIC_DTO_AUDIENCES: &[Visibility] = &[
178 Visibility::Public,
179 Visibility::AuthenticatedOwner,
180 Visibility::InternalOnly,
181];
182const OWNER_DTO_AUDIENCES: &[Visibility] =
183 &[Visibility::AuthenticatedOwner, Visibility::InternalOnly];
184const INTERNAL_DTO_AUDIENCES: &[Visibility] = &[Visibility::InternalOnly];
185
186const fn allowed_dto_audiences(visibility: Visibility) -> &'static [Visibility] {
187 match visibility {
188 Visibility::Public => PUBLIC_DTO_AUDIENCES,
189 Visibility::AuthenticatedOwner => OWNER_DTO_AUDIENCES,
190 Visibility::InternalOnly => INTERNAL_DTO_AUDIENCES,
191 }
192}
193
194const fn api_field(kind: HashKindName) -> &'static str {
195 match kind {
196 HashKindName::IdlSource => "sourceIdlHash",
197 HashKindName::IdlContent => "idlContentHash",
198 HashKindName::IdlPortable => "portableIdlHash",
199 HashKindName::IdlNormalized => "normalizedIdlHash",
200 HashKindName::ProgramSpec => "programSpecHash",
201 HashKindName::AstPortable => "portableAstHash",
202 HashKindName::RuntimeArtifact => "runtimeArtifactHash",
203 HashKindName::ArtifactFile => "artifactFileHash",
204 HashKindName::DecoderContent => "decoderContentHash",
205 HashKindName::SdkDefinition => "sdkDefinitionHash",
206 HashKindName::SdkExtension => "sdkExtensionHash",
207 HashKindName::SdkOutputTree => "sdkOutputTreeHash",
208 HashKindName::Compiler => "compilerHash",
209 HashKindName::ProgramRelease => "programReleaseHash",
210 HashKindName::LiveSpec => "liveSpecHash",
211 HashKindName::StackManifest => "stackManifestHash",
212 HashKindName::DeploymentRelease => "deploymentReleaseHash",
213 HashKindName::DecoderFixtureSet => "decoderFixtureSetHash",
214 }
215}
216
217const fn rust_type(kind: HashKindName) -> &'static str {
218 match kind {
219 HashKindName::IdlSource => "HashId<IdlSource>",
220 HashKindName::IdlContent => "HashId<IdlContent>",
221 HashKindName::IdlPortable => "HashId<IdlPortable>",
222 HashKindName::IdlNormalized => "HashId<IdlNormalized>",
223 HashKindName::ProgramSpec => "HashId<ProgramSpec>",
224 HashKindName::AstPortable => "HashId<AstPortable>",
225 HashKindName::RuntimeArtifact => "HashId<RuntimeArtifact>",
226 HashKindName::ArtifactFile => "HashId<ArtifactFile>",
227 HashKindName::DecoderContent => "HashId<DecoderContent>",
228 HashKindName::SdkDefinition => "HashId<SdkDefinition>",
229 HashKindName::SdkExtension => "HashId<SdkExtension>",
230 HashKindName::SdkOutputTree => "HashId<SdkOutputTree>",
231 HashKindName::Compiler => "HashId<Compiler>",
232 HashKindName::ProgramRelease => "HashId<ProgramRelease>",
233 HashKindName::LiveSpec => "HashId<LiveSpec>",
234 HashKindName::StackManifest => "HashId<StackManifest>",
235 HashKindName::DeploymentRelease => "HashId<DeploymentRelease>",
236 HashKindName::DecoderFixtureSet => "HashId<DecoderFixtureSet>",
237 }
238}
239
240const fn typescript_type(kind: HashKindName) -> &'static str {
241 match kind {
242 HashKindName::IdlSource => "IdlSourceHash",
243 HashKindName::IdlContent => "IdlContentHash",
244 HashKindName::IdlPortable => "IdlPortableHash",
245 HashKindName::IdlNormalized => "IdlNormalizedHash",
246 HashKindName::ProgramSpec => "ProgramSpecHash",
247 HashKindName::AstPortable => "AstPortableHash",
248 HashKindName::RuntimeArtifact => "RuntimeArtifactHash",
249 HashKindName::ArtifactFile => "ArtifactFileHash",
250 HashKindName::DecoderContent => "DecoderContentHash",
251 HashKindName::SdkDefinition => "SdkDefinitionHash",
252 HashKindName::SdkExtension => "SdkExtensionHash",
253 HashKindName::SdkOutputTree => "SdkOutputTreeHash",
254 HashKindName::Compiler => "CompilerHash",
255 HashKindName::ProgramRelease => "ProgramReleaseHash",
256 HashKindName::LiveSpec => "LiveSpecHash",
257 HashKindName::StackManifest => "StackManifestHash",
258 HashKindName::DeploymentRelease => "DeploymentReleaseHash",
259 HashKindName::DecoderFixtureSet => "DecoderFixtureSetHash",
260 }
261}
262
263const fn projection(kind: HashKindName) -> &'static str {
264 match kind {
265 HashKindName::IdlSource => "arete.idl-source/exact-bytes-v1",
266 HashKindName::IdlContent => "arete.idl-content/source-json-v1",
267 HashKindName::IdlPortable => "arete.idl-portable/source-json-v1",
268 HashKindName::IdlNormalized => "arete.idl-normalized/v1",
269 HashKindName::ProgramSpec => "arete.program-spec/v1",
270 HashKindName::AstPortable => "arete.ast-portable/self-hash-v1",
271 HashKindName::RuntimeArtifact => "arete.runtime-artifact/v1",
272 HashKindName::ArtifactFile => "arete.artifact-file/exact-bytes-v1",
273 HashKindName::DecoderContent => "arete.decoder-content/exact-bytes-v1",
274 HashKindName::SdkDefinition => "arete.sdk-definition/v1",
275 HashKindName::SdkExtension => "arete.sdk-extension/v1",
276 HashKindName::SdkOutputTree => "arete.sdk-output-tree/artifact-tree-v1",
277 HashKindName::Compiler => "arete.compiler/v1",
278 HashKindName::ProgramRelease => "arete.program-release/v1",
279 HashKindName::LiveSpec => "arete.artifact-envelope/live-spec-v1",
280 HashKindName::StackManifest => "arete.artifact-envelope/stack-manifest-v1",
281 HashKindName::DeploymentRelease => "arete.deployment-release/v1",
282 HashKindName::DecoderFixtureSet => "arete.decoder-fixtures/v2",
283 }
284}
285
286const fn database_mappings(kind: HashKindName) -> &'static [&'static str] {
287 match kind {
288 HashKindName::IdlSource | HashKindName::ArtifactFile => &[],
289 HashKindName::IdlContent => &[
290 "idl_contents.idl_content_hash",
291 "program_releases.idl_content_hash",
292 ],
293 HashKindName::IdlPortable => &["idl_contents.idl_portable_hash"],
294 HashKindName::IdlNormalized => &[
295 "idl_contents.idl_normalized_hash",
296 "decoder_bindings.normalized_idl_hash",
297 "program_releases.normalized_idl_hash",
298 "decoder_fixture_sets.normalized_idl_hash",
299 ],
300 HashKindName::ProgramSpec => &[
301 "idl_contents.program_spec_hash",
302 "program_spec_artifacts.program_spec_hash",
303 "program_releases.program_spec_hash",
304 ],
305 HashKindName::AstPortable => &[
306 "ast_contents.ast_portable_hash",
307 "builds.ast_portable_hash",
308 "deployments.current_ast_portable_hash",
309 ],
310 HashKindName::RuntimeArtifact => &[
311 "runtime_artifacts.runtime_artifact_hash",
312 "builds.runtime_artifact_hash",
313 ],
314 HashKindName::DecoderContent => &[
315 "decoder_contents.content_hash",
316 "decoder_executions.decoder_content_hash",
317 ],
318 HashKindName::SdkDefinition | HashKindName::Compiler => &[],
319 HashKindName::SdkExtension => &["sdk_extension_contents.sdk_extension_hash"],
320 HashKindName::SdkOutputTree => &["sdk_extension_contents.sdk_output_tree_hash"],
321 HashKindName::ProgramRelease => &["program_releases.release_hash"],
322 HashKindName::LiveSpec => &["live_spec_artifacts.live_spec_hash"],
323 HashKindName::StackManifest => &["stack_manifest_artifacts.stack_manifest_hash"],
324 HashKindName::DeploymentRelease => &[
325 "deployment_releases.deployment_release_hash",
326 "builds.deployment_release_hash",
327 "deployments.deployment_release_hash",
328 ],
329 HashKindName::DecoderFixtureSet => &["decoder_fixture_sets.fixture_set_hash"],
330 }
331}
332
333const fn legacy_aliases(kind: HashKindName) -> &'static [&'static str] {
334 match kind {
335 HashKindName::IdlContent => &["legacy_idl_json_sha256"],
336 HashKindName::IdlPortable => &["legacy_idl_json_no_program_sha256"],
337 HashKindName::IdlNormalized => &["legacy_normalized_idl_sha256"],
338 HashKindName::AstPortable => &["legacy_portable_ast_sha256"],
339 HashKindName::RuntimeArtifact => &["legacy_platform_ast_sha256"],
340 HashKindName::DecoderContent => &["legacy_decoder_content_sha256"],
341 HashKindName::SdkExtension => &["legacy_sdk_extension_sha256"],
342 _ => &[],
343 }
344}
345
346mod sealed {
347 pub trait Sealed {}
348}
349
350pub trait Kind: sealed::Sealed + 'static {
351 const NAME: HashKindName;
352 const PROFILE: CanonicalizationProfile;
353 const VISIBILITY: Visibility;
354 const IDENTITY_CLASS: IdentityClass;
355}
356
357macro_rules! define_kinds {
358 ($(($type:ident, $name:ident, $profile:ident, $visibility:ident, $class:ident)),+ $(,)?) => {
359 $(
360 #[derive(Debug)]
361 pub struct $type;
362
363 impl sealed::Sealed for $type {}
364
365 impl Kind for $type {
366 const NAME: HashKindName = HashKindName::$name;
367 const PROFILE: CanonicalizationProfile = CanonicalizationProfile::$profile;
368 const VISIBILITY: Visibility = Visibility::$visibility;
369 const IDENTITY_CLASS: IdentityClass = IdentityClass::$class;
370 }
371 )+
372
373 pub const IDENTITY_REGISTRY: &[IdentityMetadata] = &[
374 $(IdentityMetadata {
375 kind: HashKindName::$name,
376 profile: CanonicalizationProfile::$profile,
377 visibility: Visibility::$visibility,
378 identity_class: IdentityClass::$class,
379 api_field: api_field(HashKindName::$name),
380 rust_type: rust_type(HashKindName::$name),
381 typescript_type: typescript_type(HashKindName::$name),
382 projection: projection(HashKindName::$name),
383 allowed_dto_audiences: allowed_dto_audiences(Visibility::$visibility),
384 database_mappings: database_mappings(HashKindName::$name),
385 legacy_aliases: legacy_aliases(HashKindName::$name),
386 }),+
387 ];
388 };
389}
390
391define_kinds!(
392 (IdlSource, IdlSource, RawBytesV1, Public, ExactSource),
393 (IdlContent, IdlContent, AreteJcsV1, Public, CanonicalContent),
394 (
395 IdlPortable,
396 IdlPortable,
397 AreteJcsV1,
398 Public,
399 PortableContent
400 ),
401 (
402 IdlNormalized,
403 IdlNormalized,
404 AreteJcsV1,
405 Public,
406 NormalizedContent
407 ),
408 (ProgramSpec, ProgramSpec, AreteJcsV1, Public, Composite),
409 (
410 AstPortable,
411 AstPortable,
412 AreteJcsV1,
413 Public,
414 PortableContent
415 ),
416 (
417 RuntimeArtifact,
418 RuntimeArtifact,
419 AreteJcsV1,
420 InternalOnly,
421 Composite
422 ),
423 (
424 ArtifactFile,
425 ArtifactFile,
426 RawBytesV1,
427 Public,
428 CanonicalContent
429 ),
430 (
431 DecoderContent,
432 DecoderContent,
433 RawBytesV1,
434 InternalOnly,
435 CanonicalContent
436 ),
437 (SdkDefinition, SdkDefinition, AreteJcsV1, Public, Composite),
438 (SdkExtension, SdkExtension, AreteJcsV1, Public, Composite),
439 (
440 SdkOutputTree,
441 SdkOutputTree,
442 ArtifactTreeV1,
443 Public,
444 ArtifactTree
445 ),
446 (Compiler, Compiler, FramedTupleV1, Public, Composite),
447 (
448 ProgramRelease,
449 ProgramRelease,
450 AreteJcsV1,
451 Public,
452 Composite
453 ),
454 (LiveSpec, LiveSpec, AreteJcsV1, Public, Composite),
455 (StackManifest, StackManifest, AreteJcsV1, Public, Composite),
456 (
457 DeploymentRelease,
458 DeploymentRelease,
459 AreteJcsV1,
460 AuthenticatedOwner,
461 Composite
462 ),
463 (
464 DecoderFixtureSet,
465 DecoderFixtureSet,
466 AreteJcsV1,
467 InternalOnly,
468 Composite
469 ),
470);
471
472pub const NON_HASH_IDENTITY_REGISTRY: &[NonHashIdentityMetadata] = &[
473 NonHashIdentityMetadata {
474 api_field: "programReadBindingId",
475 rust_type: "ProgramReadBindingId",
476 typescript_type: "ProgramReadBindingId",
477 projection: "arete.program-read-binding/v1",
478 visibility: Visibility::Public,
479 allowed_dto_audiences: PUBLIC_DTO_AUDIENCES,
480 database_mappings: &[
481 "program_read_bindings.id",
482 "program_read_routes.program_read_binding_id",
483 "program_read_usage_events.program_read_binding_id",
484 ],
485 legacy_aliases: &[],
486 },
487 NonHashIdentityMetadata {
488 api_field: "decoderBindingId",
489 rust_type: "internal::DecoderBindingId",
490 typescript_type: "DecoderBindingId",
491 projection: "arete.decoder-binding/v1",
492 visibility: Visibility::InternalOnly,
493 allowed_dto_audiences: INTERNAL_DTO_AUDIENCES,
494 database_mappings: &["decoder_bindings.id", "program_releases.decoder_binding_id"],
495 legacy_aliases: &[],
496 },
497 NonHashIdentityMetadata {
498 api_field: "decoderEngineId",
499 rust_type: "internal::DecoderEngineId",
500 typescript_type: "DecoderEngineId",
501 projection: "arete.decoder-engine/v1",
502 visibility: Visibility::InternalOnly,
503 allowed_dto_audiences: INTERNAL_DTO_AUDIENCES,
504 database_mappings: &[
505 "decoder_executions.decoder_engine_id",
506 "program_releases.decoder_engine_id",
507 "decoder_fixture_sets.decoder_engine_id",
508 ],
509 legacy_aliases: &[],
510 },
511];
512
513pub fn identity_metadata(kind: HashKindName) -> &'static IdentityMetadata {
514 IDENTITY_REGISTRY
515 .iter()
516 .find(|metadata| metadata.kind == kind)
517 .expect("closed hash kind registry is exhaustive")
518}