import { mapFirst } from "collection-utils" ; import { TargetLanguage } from "../TargetLanguage" ; import { ConvenienceRenderer , ForbiddenWordsInfo } from "../ConvenienceRenderer" ; import { legalizeCharacters , splitIntoWords , isLetterOrUnderscoreOrDigit , combineWords , allLowerWordStyle , firstUpperWordStyle , intToHex , utf32ConcatMap , escapeNonPrintableMapper , isPrintable , isAscii , isLetterOrUnderscore } from "../support/Strings" ; import { Name , Namer , funPrefixNamer } from "../Naming" ; import { UnionType , Type , ClassType , EnumType } from "../Type" ; import { matchType , nullableFromUnion , removeNullFromUnion } from "../TypeUtils" ; import { Sourcelike , maybeAnnotated } from "../Source" ; import { anyTypeIssueAnnotation , nullTypeIssueAnnotation } from "../Annotation" ; import { BooleanOption , EnumOption , Option , getOptionValues , OptionValues } from "../RendererOptions" ; import { defined } from "../support/Support" ; import { RenderContext } from "../Renderer" ; export enum Density { Normal , Dense } export enum Visibility { Private , Crate , Public } export const rustOptions = { density : new EnumOption ( "density" , "Density" , [ [ "normal" , Density . Normal ] , [ "dense" , Density . Dense ] ] ) , visibility : new EnumOption ( "visibility" , "Field visibility" , [ [ "private" , Visibility . Private ] , [ "crate" , Visibility . Crate ] , [ "public" , Visibility . Public ] ] ) , deriveDebug : new BooleanOption ( "derive-debug" , "Derive Debug impl" , false ) , edition2018 : new BooleanOption ( "edition-2018" , "Edition 2018" , false ) , leadingComments : new BooleanOption ( "leading-comments" , "Leading Comments" , true ) } ; export class RustTargetLanguage extends TargetLanguage { protected makeRenderer ( renderContext , untypedOptionValues ) { return new RustRenderer ( this , renderContext , getOptionValues ( rustOptions , untypedOptionValues ) ) ; } constructor ( ) { super ( "Rust" , [ "rust" , "rs" , "rustlang" ] , "rs" ) ; } protected getOptions ( ) { return [ rustOptions . density , rustOptions . visibility , rustOptions . deriveDebug ] ; } } const keywords = [ "{{root}}" , "$crate" , "as" , "box" , "break" , "const" , "continue" , "crate" , "else" , "enum" , "extern" , "false" , "fn" , "for" , "if" , "impl" , "in" , "let" , "loop" , "match" , "mod" , "move" , "mut" , "pub" , "ref" , "return" , "self" , "Self" , "static" , "struct" , "super" , "trait" , "true" , "type" , "unsafe" , "use" , "where" , "while" , "abstract" , "alignof" , "become" , "do" , "final" , "macro" , "offsetof" , "override" , "priv" , "proc" , "pure" , "sizeof" , "typeof" , "unsized" , "virtual" , "yield" , "catch" , "default" , "dyn" , "'static" , "union" ] ; const isAsciiLetterOrUnderscoreOrDigit = ( codePoint ) : boolean => { if ( ! isAscii ( codePoint ) ) { return false ; } return isLetterOrUnderscoreOrDigit ( codePoint ) ; } ; const isAsciiLetterOrUnderscore = ( codePoint ) : boolean => { if ( ! isAscii ( codePoint ) ) { return false ; } return isLetterOrUnderscore ( codePoint ) ; } ; const legalizeName = legalizeCharacters ( isAsciiLetterOrUnderscoreOrDigit ) ; function rustStyle ( original , isSnakeCase ) { const words = splitIntoWords ( original ) ; const wordStyle = isSnakeCase ? allLowerWordStyle : firstUpperWordStyle ; const combined = combineWords ( words , legalizeName , wordStyle , wordStyle , wordStyle , wordStyle , isSnakeCase ? "_" : "" , isAsciiLetterOrUnderscore ) ; return combined === "_" ? "_underscore" : combined ; } const snakeNamingFunction = funPrefixNamer ( "default" , ( original ) => rustStyle ( original , true ) ) ; const camelNamingFunction = funPrefixNamer ( "camel" , ( original ) => rustStyle ( original , false ) ) ; const standardUnicodeRustEscape = ( codePoint ) : string => { if ( codePoint <= 0xffff ) { return "\\u{" + intToHex ( codePoint , 4 ) + "}" ; } else { return "\\u{" + intToHex ( codePoint , 6 ) + "}" ; } } ; const rustStringEscape = utf32ConcatMap ( escapeNonPrintableMapper ( isPrintable , standardUnicodeRustEscape ) ) ; export class RustRenderer extends ConvenienceRenderer { constructor ( targetLanguage , renderContext , private readonly _options ) { super ( targetLanguage , renderContext ) ; } protected makeNamedTypeNamer ( ) { return camelNamingFunction ; } protected namerForObjectProperty ( ) { return snakeNamingFunction ; } protected makeUnionMemberNamer ( ) { return camelNamingFunction ; } protected makeEnumCaseNamer ( ) { return camelNamingFunction ; } protected forbiddenNamesForGlobalNamespace ( ) { return keywords ; } protected forbiddenForObjectProperties ( _c , _className ) { return { names : [ ] , includeGlobalForbidden : true } ; } protected forbiddenForUnionMembers ( _u , _unionName ) { return { names : [ ] , includeGlobalForbidden : true } ; } protected forbiddenForEnumCases ( _e , _enumName ) { return { names : [ ] , includeGlobalForbidden : true } ; } protected get commentLineStart ( ) : string { return "/// " ; } private nullableRustType ( t , withIssues ) { return [ "Option<" , this . breakCycle ( t , withIssues ) , ">" ] ; } protected isImplicitCycleBreaker ( t ) { const kind = t . kind ; return kind === "array" || kind === "map" ; } private rustType ( t , withIssues = false ) { return matchType < Sourcelike > ( t , _anyType => maybeAnnotated ( withIssues , anyTypeIssueAnnotation , "Option<serde_json::Value>" ) , _nullType => maybeAnnotated ( withIssues , nullTypeIssueAnnotation , "Option<serde_json::Value>" ) , _boolType => "bool" , _integerType => "i64" , _doubleType => "f64" , _stringType => "String" , arrayType => [ "Vec<" , this . rustType ( arrayType . items , withIssues ) , ">" ] , classType => this . nameForNamedType ( classType ) , mapType => [ "HashMap<String, " , this . rustType ( mapType . values , withIssues ) , ">" ] , enumType => this . nameForNamedType ( enumType ) , unionType => { const nullable = nullableFromUnion ( unionType ) ; if ( nullable !== null ) return this . nullableRustType ( nullable , withIssues ) ; const [ hasNull ] = removeNullFromUnion ( unionType ) ; const isCycleBreaker = this . isCycleBreakerType ( unionType ) ; const name = isCycleBreaker ? [ "Box<" , this . nameForNamedType ( unionType ) , ">" ] : this . nameForNamedType ( unionType ) ; return hasNull !== null ? ( [ "Option<" , name , ">" ] as Sourcelike ) : name ; } ) ; } private breakCycle ( t , withIssues ) { const rustType = this . rustType ( t , withIssues ) ; const isCycleBreaker = this . isCycleBreakerType ( t ) ; return isCycleBreaker ? [ "Box<" , rustType , ">" ] : rustType ; } private emitRenameAttribute ( propName , jsonName ) { const escapedName = rustStringEscape ( jsonName ) ; const namesDiffer = this . sourcelikeToString ( propName ) !== escapedName ; if ( namesDiffer || this . _options . density === Density . Normal ) { this . emitLine ( '#[serde(rename = "' , escapedName , '")]' ) ; } } private get visibility ( ) : string { if ( this . _options . visibility === Visibility . Crate ) { return "pub(crate) " ; } else if ( this . _options . visibility === Visibility . Public ) { return "pub " ; } return "" ; } protected emitStructDefinition ( c , className ) { this . emitDescription ( this . descriptionForType ( c ) ) ; this . emitLine ( "#[derive(" , this . _options . deriveDebug ? "Debug, " : "" , "Serialize, Deserialize)]" ) ; const blankLines = this . _options . density === Density . Dense ? "none" : "interposing" ; const structBody = ( ) => this . forEachClassProperty ( c , blankLines , ( name , jsonName , prop ) => { this . emitDescription ( this . descriptionForClassProperty ( c , jsonName ) ) ; this . emitRenameAttribute ( name , jsonName ) ; this . emitLine ( this . visibility , name , ": " , this . breakCycle ( prop . type , true ) , "," ) ; } ) ; this . emitBlock ( [ "pub struct " , className ] , structBody ) ; } protected emitBlock ( line , f ) { this . emitLine ( line , " {" ) ; this . indent ( f ) ; this . emitLine ( "}" ) ; } protected emitUnion ( u , unionName ) { const isMaybeWithSingleType = nullableFromUnion ( u ) ; if ( isMaybeWithSingleType !== null ) { return ; } this . emitDescription ( this . descriptionForType ( u ) ) ; this . emitLine ( "#[derive(" , this . _options . deriveDebug ? "Debug, " : "" , "Serialize, Deserialize)]" ) ; this . emitLine ( "#[serde(untagged)]" ) ; const [ , nonNulls ] = removeNullFromUnion ( u ) ; const blankLines = this . _options . density === Density . Dense ? "none" : "interposing" ; this . emitBlock ( [ "pub enum " , unionName ] , ( ) => this . forEachUnionMember ( u , nonNulls , blankLines , null , ( fieldName , t ) => { const rustType = this . breakCycle ( t , true ) ; this . emitLine ( [ fieldName , "(" , rustType , ")," ] ) ; } ) ) ; } protected emitEnumDefinition ( e , enumName ) { this . emitDescription ( this . descriptionForType ( e ) ) ; this . emitLine ( "#[derive(" , this . _options . deriveDebug ? "Debug, " : "" , "Serialize, Deserialize)]" ) ; const blankLines = this . _options . density === Density . Dense ? "none" : "interposing" ; this . emitBlock ( [ "pub enum " , enumName ] , ( ) => this . forEachEnumCase ( e , blankLines , ( name , jsonName ) => { this . emitRenameAttribute ( name , jsonName ) ; this . emitLine ( [ name , "," ] ) ; } ) ) ; } protected emitTopLevelAlias ( t , name ) { this . emitLine ( "pub type " , name , " = " , this . rustType ( t ) , ";" ) ; } protected emitLeadingComments ( ) { if ( this . leadingComments !== undefined ) { this . emitCommentLines ( this . leadingComments ) ; return ; } const topLevelName = defined ( mapFirst ( this . topLevels ) ) ; this . emitMultiline ( `// Example code that deserializes and serializes the model.
// extern crate serde;
// #[macro_use]
// extern crate serde_derive;
// extern crate serde_json;
//
// use generated_module::${ topLevelName };
//
// fn main() {
//     let json = r#"{"answer": 42}"#;
//     let model: ${ topLevelName } = serde_json::from_str(&json).unwrap();
// }` ) ; } protected emitSourceStructure ( ) { if ( this . _options . leadingComments ) { this . emitLeadingComments ( ) ; } this . ensureBlankLine ( ) ; if ( this . _options . edition2018 ) { this . emitLine ( "use serde::{Serialize, Deserialize};" ) ; } else { this . emitLine ( "extern crate serde_derive;" ) ; } if ( this . haveMaps ) { this . emitLine ( "use std::collections::HashMap;" ) ; } this . forEachTopLevel ( "leading" , ( t , name ) => this . emitTopLevelAlias ( t , name ) , t => this . namedTypeToNameForTopLevel ( t ) === undefined ) ; this . forEachObject ( "leading-and-interposing" , ( c , name ) => this . emitStructDefinition ( c , name ) ) ; this . forEachUnion ( "leading-and-interposing" , ( u , name ) => this . emitUnion ( u , name ) ) ; this . forEachEnum ( "leading-and-interposing" , ( e , name ) => this . emitEnumDefinition ( e , name ) ) ; } }