2024-08-09 17:52:14 +02:00
|
|
|
/**
|
|
|
|
|
* @fileOverview This file is used by the api class generator in another repo
|
|
|
|
|
* but is checked in here so that it's updated in lockstep with rust code that expects it.
|
|
|
|
|
*/
|
|
|
|
|
|
2024-10-10 15:59:01 +02:00
|
|
|
import { AssociationType, Type, ValueType } from "../src/common/api/common/EntityConstants.js"
|
2024-08-09 17:52:14 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param p {object}
|
|
|
|
|
* @param p.type {import("../src/common/api/common/EntityTypes.js").TypeModel}
|
|
|
|
|
* @param p.modelName {string}
|
|
|
|
|
* @return {string}
|
|
|
|
|
*/
|
|
|
|
|
export function generateRustType({ type, modelName }) {
|
|
|
|
|
let typeName = mapTypeName(type.name, modelName)
|
2024-10-14 16:52:14 +02:00
|
|
|
let buf = `#[derive(uniffi::Record, Clone, Serialize, Deserialize)]
|
|
|
|
|
#[cfg_attr(test, derive(PartialEq, Debug))]
|
2024-08-09 17:52:14 +02:00
|
|
|
pub struct ${typeName} {\n`
|
|
|
|
|
for (let [valueName, valueProperties] of Object.entries(type.values)) {
|
|
|
|
|
const rustType = rustValueType(valueName, type, valueProperties)
|
|
|
|
|
if (valueName === "type") {
|
2024-08-27 17:41:59 +02:00
|
|
|
buf += `\t#[serde(rename = "type")]\n`
|
|
|
|
|
buf += `\tpub r#type: ${rustType},\n`
|
2024-08-09 17:52:14 +02:00
|
|
|
} else if (valueProperties.type === "Bytes") {
|
2024-08-27 17:41:59 +02:00
|
|
|
buf += `\t#[serde(with = "serde_bytes")]\n`
|
|
|
|
|
buf += `\tpub ${valueName}: ${rustType},\n`
|
2024-08-09 17:52:14 +02:00
|
|
|
} else {
|
2024-08-27 17:41:59 +02:00
|
|
|
buf += `\tpub ${valueName}: ${rustType},\n`
|
2024-08-09 17:52:14 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let [associationName, associationProperties] of Object.entries(type.associations)) {
|
|
|
|
|
const innerRustType = rustAssociationType(associationProperties)
|
|
|
|
|
let rustType
|
|
|
|
|
switch (associationProperties.cardinality) {
|
|
|
|
|
case "ZeroOrOne":
|
|
|
|
|
rustType = `Option<${innerRustType}>`
|
|
|
|
|
break
|
|
|
|
|
case "Any":
|
|
|
|
|
rustType = `Vec<${innerRustType}>`
|
|
|
|
|
break
|
|
|
|
|
case "One":
|
|
|
|
|
rustType = innerRustType
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (associationName === "type") {
|
2024-08-27 17:41:59 +02:00
|
|
|
buf += `\t#[serde(rename = "type")]\n`
|
|
|
|
|
buf += `\tpub r#type: ${rustType},\n`
|
2024-08-09 17:52:14 +02:00
|
|
|
} else {
|
2024-08-27 17:41:59 +02:00
|
|
|
buf += `\tpub ${associationName}: ${rustType},\n`
|
2024-08-09 17:52:14 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (type.encrypted) {
|
2024-08-19 17:41:00 +02:00
|
|
|
buf += `\tpub _errors: Option<Errors>,\n`
|
2024-08-20 17:36:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// aggregates do not say whether they are encrypted or not. For some reason!
|
|
|
|
|
if (type.encrypted || Object.values(type.values).some((v) => v.encrypted)) {
|
2024-08-19 17:41:00 +02:00
|
|
|
buf += `\tpub _finalIvs: HashMap<String, FinalIv>,\n`
|
2024-08-09 17:52:14 +02:00
|
|
|
}
|
2024-10-14 16:52:14 +02:00
|
|
|
|
2024-08-09 17:52:14 +02:00
|
|
|
buf += "}"
|
2024-10-14 16:52:14 +02:00
|
|
|
buf += `
|
|
|
|
|
impl Entity for ${typeName} {
|
|
|
|
|
fn type_ref() -> TypeRef {
|
|
|
|
|
TypeRef {
|
|
|
|
|
app: "${modelName}",
|
|
|
|
|
type_: "${typeName}",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
return buf + "\n\n"
|
|
|
|
|
}
|
2024-08-09 17:52:14 +02:00
|
|
|
|
2024-10-14 16:52:14 +02:00
|
|
|
export function generateRustServiceDefinition(appName, appVersion, services) {
|
|
|
|
|
let imports = new Set([
|
|
|
|
|
"#![allow(unused_imports, dead_code, unused_variables)]",
|
|
|
|
|
"use crate::ApiCallError;",
|
|
|
|
|
"use crate::entities::Entity;",
|
|
|
|
|
"use crate::services::{PostService, GetService, PutService, DeleteService, Service, Executor, ExtraServiceParams};",
|
|
|
|
|
"use crate::rest_client::HttpMethod;",
|
|
|
|
|
"use crate::services::hidden::Nothing;",
|
|
|
|
|
])
|
|
|
|
|
const code = services
|
|
|
|
|
.map((s) => {
|
|
|
|
|
let serviceDefinition = `
|
|
|
|
|
pub struct ${s.name};
|
2024-08-09 17:52:14 +02:00
|
|
|
|
2024-10-14 16:52:14 +02:00
|
|
|
crate::service_impl!(declare, ${s.name}, "${appName}/${s.name.toLowerCase()}", ${appVersion});
|
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
function getTypeRef(dataType) {
|
|
|
|
|
if (dataType) {
|
|
|
|
|
return `Some(${dataType}::type_ref())`
|
|
|
|
|
} else {
|
|
|
|
|
return "None"
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function addImports(appName, input, output) {
|
|
|
|
|
if (input) {
|
|
|
|
|
imports.add(`use crate::entities::${appName}::${input};`)
|
|
|
|
|
}
|
|
|
|
|
if (output) {
|
|
|
|
|
imports.add(`use crate::entities::${appName}::${output};`)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function makeImpl(name, input, output) {
|
|
|
|
|
addImports(appName, input, output)
|
|
|
|
|
return `crate::service_impl!(${name}, ${s.name}, ${input ?? "()"}, ${output ?? "()"});\n`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (s.bodyTypes.POST_IN || s.bodyTypes.POST_OUT) {
|
|
|
|
|
serviceDefinition += makeImpl("POST", s.bodyTypes.POST_IN, s.bodyTypes.POST_OUT)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (s.bodyTypes.GET_IN || s.bodyTypes.GET_OUT) {
|
|
|
|
|
serviceDefinition += makeImpl("GET", s.bodyTypes.GET_IN, s.bodyTypes.GET_OUT)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (s.bodyTypes.PUT_IN || s.bodyTypes.PUT_OUT) {
|
|
|
|
|
serviceDefinition += makeImpl("PUT", s.bodyTypes.PUT_IN, s.bodyTypes.PUT_OUT)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (s.bodyTypes.DELETE_IN || s.bodyTypes.DELETE_OUT) {
|
|
|
|
|
serviceDefinition += makeImpl("DELETE", s.bodyTypes.DELETE_IN, s.bodyTypes.DELETE_OUt)
|
|
|
|
|
}
|
2024-08-09 17:52:14 +02:00
|
|
|
|
2024-10-14 16:52:14 +02:00
|
|
|
return serviceDefinition
|
|
|
|
|
})
|
|
|
|
|
.join("\n")
|
|
|
|
|
return Array.from(imports).join("\n") + code
|
2024-08-09 17:52:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param types {string[]}
|
|
|
|
|
* @return {string}
|
|
|
|
|
*/
|
|
|
|
|
export function combineRustTypes(types) {
|
2024-08-27 17:41:59 +02:00
|
|
|
if (types.length === 0) return "\n"
|
2024-08-09 17:52:14 +02:00
|
|
|
return `#![allow(non_snake_case, unused_imports)]
|
|
|
|
|
use super::*;
|
2024-08-27 17:41:59 +02:00
|
|
|
use serde::{Deserialize, Serialize};
|
2024-08-09 17:52:14 +02:00
|
|
|
|
2024-08-27 17:41:59 +02:00
|
|
|
${types.join("\n\n")}
|
|
|
|
|
`
|
2024-08-09 17:52:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param name {string}
|
|
|
|
|
* @param modelName {string}
|
|
|
|
|
* @return {string}
|
|
|
|
|
*/
|
|
|
|
|
function mapTypeName(name, modelName) {
|
|
|
|
|
if (name === "File" && modelName === "tutanota") return "TutanotaFile"
|
|
|
|
|
if (name === "Exception" && modelName === "sys") return "SysException"
|
|
|
|
|
return name
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param valueName {string}
|
|
|
|
|
* @param type {import("../src/common/api/common/EntityTypes.js").TypeModel}
|
|
|
|
|
* @param value {import("../src/common/api/common/EntityTypes.js").ModelValue}
|
|
|
|
|
* @return {string}
|
|
|
|
|
*/
|
|
|
|
|
function rustValueType(valueName, type, value) {
|
|
|
|
|
const ValueToRustTypes = Object.freeze({
|
|
|
|
|
String: "String",
|
|
|
|
|
Number: "i64",
|
|
|
|
|
Bytes: "Vec<u8>",
|
|
|
|
|
Date: "DateTime",
|
|
|
|
|
Boolean: "bool",
|
|
|
|
|
GeneratedId: "GeneratedId",
|
|
|
|
|
CustomId: "CustomId",
|
2024-10-25 15:00:51 +02:00
|
|
|
CompressedString: "String",
|
2024-08-09 17:52:14 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
let innerType
|
|
|
|
|
if (valueName === "_id" && (type.type === Type.ListElement || type.type === Type.BlobElement)) {
|
2024-10-10 15:59:01 +02:00
|
|
|
if (value.type === ValueType.CustomId) {
|
2024-11-05 10:44:56 +01:00
|
|
|
innerType = "Option<IdTupleCustom>"
|
2024-10-10 15:59:01 +02:00
|
|
|
} else {
|
2024-11-05 10:44:56 +01:00
|
|
|
innerType = "Option<IdTupleGenerated>"
|
2024-10-10 15:59:01 +02:00
|
|
|
}
|
2024-11-05 10:44:56 +01:00
|
|
|
} else if (valueName === "_id") {
|
|
|
|
|
innerType = `Option<${ValueToRustTypes[value.type]}>`
|
2024-08-09 17:52:14 +02:00
|
|
|
} else {
|
|
|
|
|
innerType = ValueToRustTypes[value.type]
|
|
|
|
|
}
|
|
|
|
|
if (value.cardinality === "ZeroOrOne") {
|
|
|
|
|
return `Option<${innerType}>`
|
|
|
|
|
} else {
|
|
|
|
|
return innerType
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param association {import("../src/common/api/common/EntityTypes.js").ModelAssociation}
|
|
|
|
|
* @return {string}
|
|
|
|
|
*/
|
|
|
|
|
function rustAssociationType(association) {
|
|
|
|
|
if (association.type === AssociationType.Aggregation) {
|
|
|
|
|
if (association.dependency) {
|
|
|
|
|
return `${association.dependency}::${association.refType}`
|
|
|
|
|
} else {
|
|
|
|
|
return association.refType
|
|
|
|
|
}
|
2024-10-10 15:59:01 +02:00
|
|
|
} else if (association.type === AssociationType.ListElementAssociationCustom) {
|
|
|
|
|
return "IdTupleCustom"
|
|
|
|
|
} else if (association.type === AssociationType.ListElementAssociationGenerated || association.type === AssociationType.BlobElementAssociation) {
|
|
|
|
|
return "IdTupleGenerated" // blob ids are also always generated
|
2024-08-09 17:52:14 +02:00
|
|
|
} else {
|
|
|
|
|
return "GeneratedId"
|
|
|
|
|
}
|
|
|
|
|
}
|