Skip to main content

arete_hash/
kind.rs

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}