{******************************************************************************} { } { Neon: Serialization Library for Delphi } { Copyright (c) 2018-2019 Paolo Rossi } { https://github.com/paolo-rossi/neon-library } { } {******************************************************************************} { } { Licensed under the Apache License, Version 2.0 (the "License"); } { you may not use this file except in compliance with the License. } { You may obtain a copy of the License at } { } { http://www.apache.org/licenses/LICENSE-2.0 } { } { Unless required by applicable law or agreed to in writing, software } { distributed under the License is distributed on an "AS IS" BASIS, } { WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } { See the License for the specific language governing permissions and } { limitations under the License. } { } {******************************************************************************} unit Neon.Core.Persistence.Swagger; interface {$I Neon.inc} uses System.SysUtils, System.Classes, System.Rtti, System.SyncObjs, System.TypInfo, System.Generics.Collections, System.JSON, Data.DB, Neon.Core.Types, Neon.Core.Attributes, Neon.Core.Persistence, Neon.Core.TypeInfo, Neon.Core.Utils; type /// /// Swagger (OpenAPI 2.0) schema generator /// TNeonSchemaGenerator = class(TNeonBase) private /// /// Writer for members of objects and records /// procedure WriteMembers(AType: TRttiType; AResult: TJSONObject); private /// /// Writer for string types /// function WriteString(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for Boolean types /// function WriteBoolean(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for enums types
///
function WriteEnum(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for Integer types
///
function WriteInteger(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for Integer types
///
function WriteInt64(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for float types /// function WriteFloat(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; function WriteDouble(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for TDate* types /// function WriteDate(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; function WriteDateTime(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for Variant types /// /// /// The variant will be written as string /// function WriteVariant(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for static and dynamic arrays /// function WriteArray(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; function WriteDynArray(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for the set type /// /// /// The output is a string with the values comma separated and enclosed by square brackets /// /// [First,Second,Third] function WriteSet(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for a record type /// /// /// For records the engine serialize the fields by default /// function WriteRecord(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for a standard TObject (descendants) type (no list, stream or streamable) /// function WriteObject(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for an Interface type /// /// /// The object that implements the interface is serialized /// function WriteInterface(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for TStream (descendants) objects /// function WriteStream(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for TDataSet (descendants) objects /// function WriteDataSet(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; /// /// Writer for "Enumerable" objects (Lists, Generic Lists, TStrings, etc...) /// /// /// Objects must have GetEnumerator, Clear, Add methods /// function WriteEnumerable(AType: TRttiType; ANeonObject: TNeonRttiObject; AList: INeonTypeInfoList): TJSONObject; function IsEnumerable(AType: TRttiType; out AList: INeonTypeInfoList): Boolean; /// /// Writer for "Dictionary" objects (TDictionary, TObjectDictionary) /// /// /// Objects must have Keys, Values, GetEnumerator, Clear, Add methods /// function WriteEnumerableMap(AType: TRttiType; ANeonObject: TNeonRttiObject; AMap: INeonTypeInfoMap): TJSONObject; function IsEnumerableMap(AType: TRttiType; out AMap: INeonTypeInfoMap): Boolean; /// /// Writer for "Streamable" objects /// /// /// Objects must have LoadFromStream and SaveToStream methods /// function WriteStreamable(AType: TRttiType; ANeonObject: TNeonRttiObject; AStream: INeonTypeInfoStream): TJSONObject; function IsStreamable(AType: TRttiType; out AStream: INeonTypeInfoStream): Boolean; /// /// Writer for "Nullable" records /// /// /// Record must have HasValue and GetValue methods /// function WriteNullable(AType: TRttiType; ANeonObject: TNeonRttiObject; ANullable: INeonTypeInfoNullable): TJSONObject; function IsNullable(AType: TRttiType; out ANullable: INeonTypeInfoNullable): Boolean; protected /// /// Function to be called by a custom serializer method (ISerializeContext) /// function WriteDataMember(AType: TRttiType): TJSONObject; overload; /// /// This method chooses the right Writer based on the Kind of the AValue parameter /// function WriteDataMember(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; overload; public constructor Create(const AConfig: INeonConfiguration); /// /// Serialize any Delphi type into a JSONValue, the Delphi type must be passed as a TRttiType /// class function TypeToJSONSchema(AType: TRttiType): TJSONObject; overload; class function TypeToJSONSchema(AType: TRttiType; AConfig: INeonConfiguration): TJSONObject; overload; /// /// Serialize any Delphi type into a JSONValue, the Delphi type must be passed as a TRttiType /// class function ClassToJSONSchema(AClass: TClass): TJSONObject; overload; class function ClassToJSONSchema(AClass: TClass; AConfig: INeonConfiguration): TJSONObject; overload; end; implementation uses System.Variants; { TNeonSchemaGenerator } class function TNeonSchemaGenerator.ClassToJSONSchema(AClass: TClass): TJSONObject; begin Result := TypeToJSONSchema(TRttiUtils.Context.GetType(AClass), TNeonConfiguration.Default); end; class function TNeonSchemaGenerator.ClassToJSONSchema(AClass: TClass; AConfig: INeonConfiguration): TJSONObject; begin Result := TypeToJSONSchema(TRttiUtils.Context.GetType(AClass), AConfig); end; constructor TNeonSchemaGenerator.Create(const AConfig: INeonConfiguration); begin inherited Create(AConfig); FOperation := TNeonOperation.Serialize; end; function TNeonSchemaGenerator.IsEnumerable(AType: TRttiType; out AList: INeonTypeInfoList): Boolean; begin AList := TNeonTypeInfoList.GuessType(AType); Result := Assigned(AList); end; function TNeonSchemaGenerator.IsEnumerableMap(AType: TRttiType; out AMap: INeonTypeInfoMap): Boolean; begin AMap := TNeonTypeInfoMap.GuessType(AType); Result := Assigned(AMap); end; function TNeonSchemaGenerator.IsNullable(AType: TRttiType; out ANullable: INeonTypeInfoNullable): Boolean; begin ANullable := TNeonTypeInfoNullable.GuessType(AType); Result := Assigned(ANullable); end; function TNeonSchemaGenerator.IsStreamable(AType: TRttiType; out AStream: INeonTypeInfoStream): Boolean; begin AStream := TNeonTypeInfoStream.GuessType(AType); Result := Assigned(AStream); end; class function TNeonSchemaGenerator.TypeToJSONSchema(AType: TRttiType; AConfig: INeonConfiguration): TJSONObject; var LGenerator: TNeonSchemaGenerator; begin LGenerator := TNeonSchemaGenerator.Create(AConfig); try Result := LGenerator.WriteDataMember(AType); finally LGenerator.Free; end; end; class function TNeonSchemaGenerator.TypeToJSONSchema(AType: TRttiType): TJSONObject; begin Result := TypeToJSONSchema(AType, TNeonConfiguration.Default); end; function TNeonSchemaGenerator.WriteArray(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; var LItems: TJSONObject; begin LItems := WriteDataMember((AType as TRttiArrayType).ElementType); Result := TJSONObject.Create .AddPair('type', 'array') .AddPair('items', LItems) end; function TNeonSchemaGenerator.WriteBoolean(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'boolean'); end; function TNeonSchemaGenerator.WriteDataMember(AType: TRttiType): TJSONObject; var LNeonObject: TNeonRttiObject; begin LNeonObject := TNeonRttiObject.Create(AType, FOperation); LNeonObject.ParseAttributes; try Result := WriteDataMember(AType, LNeonObject); finally LNeonObject.Free; end; end; function TNeonSchemaGenerator.WriteDataMember(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; var LNeonTypeInfo: INeonTypeInfo; LNeonMap: INeonTypeInfoMap absolute LNeonTypeInfo; LNeonList: INeonTypeInfoList absolute LNeonTypeInfo; LNeonStream: INeonTypeInfoStream absolute LNeonTypeInfo; LNeonNullable: INeonTypeInfoNullable absolute LNeonTypeInfo; begin Result := nil; case AType.TypeKind of tkChar, tkWChar, tkString, tkLString, tkWString, tkUString: begin Result := WriteString(AType, ANeonObject); end; tkEnumeration: begin if AType.Handle = System.TypeInfo(Boolean) then Result := WriteBoolean(AType, ANeonObject) else Result := WriteEnum(AType, ANeonObject); end; tkInteger: begin Result := WriteInteger(AType, ANeonObject); end; tkInt64: begin Result := WriteInt64(AType, ANeonObject); end; tkFloat: begin if AType.Handle = TypeInfo(Single) then Result := WriteFloat(AType, ANeonObject) else if AType.Handle = TypeInfo(TDateTime) then Result := WriteDateTime(AType, ANeonObject) else if AType.Handle = TypeInfo(TTime) then Result := WriteDateTime(AType, ANeonObject) else if AType.Handle = TypeInfo(TDate) then Result := WriteDate(AType, ANeonObject) else Result := WriteDouble(AType, ANeonObject); end; tkClass: begin if AType.IsInstance and AType.AsInstance.MetaclassType.InheritsFrom(TDataSet) then Result := WriteDataSet(AType, ANeonObject) else if AType.IsInstance and AType.AsInstance.MetaclassType.InheritsFrom(TStream) then Result := WriteStream(AType, ANeonObject) else if IsEnumerableMap(AType, LNeonMap) then Result := WriteEnumerableMap(AType, ANeonObject, LNeonMap) else if IsEnumerable(AType, LNeonList) then Result := WriteEnumerable(AType, ANeonObject, LNeonList) else if IsStreamable(AType, LNeonStream) then Result := WriteStreamable(AType, ANeonObject, LNeonStream) else Result := WriteObject(AType, ANeonObject); end; tkArray: begin Result := WriteArray(AType, ANeonObject); end; tkDynArray: begin Result := WriteDynArray(AType, ANeonObject); end; tkSet: begin Result := WriteSet(AType, ANeonObject); end; tkRecord: begin if IsNullable(AType, LNeonNullable) then Result := WriteNullable(AType, ANeonObject, LNeonNullable) else Result := WriteRecord(AType, ANeonObject); end; tkInterface: begin Result := WriteInterface(AType, ANeonObject); end; tkVariant: begin Result := WriteVariant(AType, ANeonObject); end; end; end; function TNeonSchemaGenerator.WriteDataSet(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; var LJSONProps: TJSONObject; begin //Result := TDataSetUtils.RecordToJSONSchema(AValue.AsObject as TDataSet, FConfig); LJSONProps := TJSONObject.Create; Result := TJSONObject.Create .AddPair('type', 'object') .AddPair('properties', LJSONProps); end; function TNeonSchemaGenerator.WriteDate(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'string') .AddPair('format', 'date'); end; function TNeonSchemaGenerator.WriteDateTime(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'string') .AddPair('format', 'date-time'); end; function TNeonSchemaGenerator.WriteDouble(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'number') .AddPair('format', 'double'); end; function TNeonSchemaGenerator.WriteDynArray(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; var LItems: TJSONObject; begin LItems := WriteDataMember((AType as TRttiDynamicArrayType).ElementType); Result := TJSONObject.Create .AddPair('type', 'array') .AddPair('items', LItems) end; function TNeonSchemaGenerator.WriteEnum(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'string'); end; function TNeonSchemaGenerator.WriteFloat(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'number') .AddPair('format', 'float'); end; function TNeonSchemaGenerator.WriteInt64(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'integer') .AddPair('format', 'int64'); end; function TNeonSchemaGenerator.WriteInteger(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'integer') .AddPair('format', 'int32'); end; function TNeonSchemaGenerator.WriteInterface(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := nil; end; procedure TNeonSchemaGenerator.WriteMembers(AType: TRttiType; AResult: TJSONObject); var LJSONValue: TJSONObject; LMembers: TNeonRttiMembers; LNeonMember: TNeonRttiMember; begin LMembers := GetNeonMembers(nil, AType); LMembers.FilterSerialize; try for LNeonMember in LMembers do begin if LNeonMember.Serializable then begin try LJSONValue := WriteDataMember(LNeonMember.RttiType, LNeonMember); if Assigned(LJSONValue) then (AResult as TJSONObject).AddPair(GetNameFromMember(LNeonMember), LJSONValue); except LogError(Format('Error converting property [%s] of object [%s]', [LNeonMember.Name, AType.Name])); end; end; end; finally LMembers.Free; end; end; function TNeonSchemaGenerator.WriteNullable(AType: TRttiType; ANeonObject: TNeonRttiObject; ANullable: INeonTypeInfoNullable): TJSONObject; begin Result := nil; if Assigned(ANullable) then Result := WriteDataMember(ANullable.GetBaseType) end; function TNeonSchemaGenerator.WriteObject(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; var LProperties: TJSONObject; begin LProperties := TJSONObject.Create; WriteMembers(AType, LProperties); Result := TJSONObject.Create .AddPair('type', 'object') .AddPair('properties', LProperties); end; function TNeonSchemaGenerator.WriteEnumerable(AType: TRttiType; ANeonObject: TNeonRttiObject; AList: INeonTypeInfoList): TJSONObject; var LJSONItems: TJSONObject; begin // Is not an Enumerable compatible object if not Assigned(AList) then Exit(nil); LJSONItems := WriteDataMember(AList.GetItemType); Result := TJSONObject.Create .AddPair('type', 'array') .AddPair('items', LJSONItems); end; function TNeonSchemaGenerator.WriteEnumerableMap(AType: TRttiType; ANeonObject: TNeonRttiObject; AMap: INeonTypeInfoMap): TJSONObject; var LValueJSON: TJSONObject; begin // Is not an EnumerableMap-compatible object if not Assigned(AMap) then Exit(nil); LValueJSON := WriteDataMember(AMap.GetValueType); Result := TJSONObject.Create .AddPair('type', 'object') .AddPair('additionalProperties', LValueJSON); end; function TNeonSchemaGenerator.WriteRecord(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; var LProperties: TJSONObject; begin LProperties := TJSONObject.Create; WriteMembers(AType, LProperties); Result := TJSONObject.Create .AddPair('type', 'object') .AddPair('properties', LProperties); end; function TNeonSchemaGenerator.WriteSet(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'string'); end; function TNeonSchemaGenerator.WriteStream(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'string') .AddPair('format', 'byte'); end; function TNeonSchemaGenerator.WriteStreamable(AType: TRttiType; ANeonObject: TNeonRttiObject; AStream: INeonTypeInfoStream): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'string') .AddPair('format', 'byte'); end; function TNeonSchemaGenerator.WriteString(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin Result := TJSONObject.Create .AddPair('type', 'string'); end; function TNeonSchemaGenerator.WriteVariant(AType: TRttiType; ANeonObject: TNeonRttiObject): TJSONObject; begin { case ANeonObject.NeonInclude.Value of Include.NotNull: begin if VarIsNull(AValue.AsVariant) then Exit(nil); end; Include.NotEmpty: begin if VarIsEmpty(AValue.AsVariant) then Exit(nil); end; end; } Result :=nil; //TJSONString.Create(AValue.AsVariant); end; end.