Quick.Options.pas 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750
  1. { ***************************************************************************
  2. Copyright (c) 2015-2021 Kike Pérez
  3. Unit : Quick.Options
  4. Description : Configuration group settings
  5. Author : Kike Pérez
  6. Version : 1.0
  7. Created : 18/10/2019
  8. Modified : 15/12/2021
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.Options;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. Classes,
  26. RTTI,
  27. System.TypInfo,
  28. System.SysUtils,
  29. System.Generics.Collections,
  30. Quick.RTTI.Utils,
  31. Quick.Commons,
  32. Quick.FileMonitor;
  33. type
  34. Required = class(TCustomAttribute);
  35. TValidationCustomAttribute = class(TCustomAttribute)
  36. protected
  37. fErrorMsg : string;
  38. public
  39. property ErrorMsg : string read fErrorMsg write fErrorMsg;
  40. end;
  41. Range = class(TValidationCustomAttribute)
  42. private
  43. fRangeMin : Double;
  44. fRangeMax : Double;
  45. public
  46. constructor Create(aMin, aMax : Integer; const aErrorMsg : string = ''); overload;
  47. constructor Create(aMin, aMax : Double; const aErrorMsg : string = ''); overload;
  48. property Min : Double read fRangeMin write fRangeMax;
  49. property Max : Double read fRangeMax write fRangeMax;
  50. end;
  51. StringLength = class(TValidationCustomAttribute)
  52. private
  53. fMaxLength : Integer;
  54. public
  55. constructor Create(aMaxLength : Integer; const aErrorMsg : string = '');
  56. property MaxLength : Integer read fMaxLength write fMaxLength;
  57. end;
  58. TOptionsBase = class(TInterfacedObject)
  59. end;
  60. {$IFDEF DELPHIRX102_UP}
  61. TOptions = class;
  62. TConfigureOptionsProc<T : TOptions> = reference to procedure(aOptions : T);
  63. {$ELSE}
  64. TConfigureOptionsProc<T> = reference to procedure(aOptions : T);
  65. {$ENDIF}
  66. IOptionsValidator = interface
  67. ['{C6A09F78-8E34-4689-B943-83620437B9EF}']
  68. procedure ValidateOptions;
  69. end;
  70. TOptions = class(TInterfacedObject,IOptionsValidator)
  71. private
  72. fName : string;
  73. fHideOptions : Boolean;
  74. procedure DoValidateOptions; virtual;
  75. public
  76. constructor Create; virtual;
  77. property Name : string read fName write fName;
  78. property HideOptions : Boolean read fHideOptions write fHideOptions;
  79. procedure DefaultValues; virtual;
  80. {$IFDEF DELPHIRX102_UP}
  81. function ConfigureOptions<T : TOptions>(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
  82. {$ELSE}
  83. function ConfigureOptions<T>(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
  84. {$ENDIF}
  85. procedure ValidateOptions;
  86. end;
  87. TOptionsValidator = class(TInterfacedObject,IOptionsValidator)
  88. private
  89. fOptions : TOptions;
  90. public
  91. constructor Create(aOptions : TOptions);
  92. procedure ValidateRequired(const aInstance : TObject; aProperty: TRttiProperty);
  93. procedure ValidateStringLength(const aInstance : TObject; aProperty: TRttiProperty; aValidation : StringLength);
  94. procedure ValidateRange(const aInstance : TObject; aProperty: TRttiProperty; aValidation : Range);
  95. procedure ValidateObject(aObj : TObject);
  96. procedure ValidateArray(aValue : TValue);
  97. procedure ValidateOptions;
  98. end;
  99. TOptions<T : TOptions> = record
  100. private
  101. fOptions : T;
  102. public
  103. constructor Create(aOptions : T);
  104. function ConfigureOptions(aOptionsFunc : TConfigureOptionsProc<T>) : IOptionsValidator;
  105. end;
  106. IOptions<T : TOptions> = interface
  107. ['{2779F946-2692-4F74-88AD-F35F5137057A}']
  108. function GetSectionValue : T;
  109. property Value : T read GetSectionValue;
  110. end;
  111. TOptionsClass = class of TOptions;
  112. IOptionsContainer = interface
  113. ['{A593C8BB-53CF-4AA4-9641-BF974E45CBD1}']
  114. function AddSection(aOption : TOptionsClass; const aOptionsName : string = '') : TOptions;
  115. function GetOptions(aOptionClass : TOptionsClass): TOptions;
  116. function GetSection(aOptionsSection : TOptionsClass; var vOptions : TOptions) : Boolean; overload;
  117. procedure AddOption(aOption : TOptions);
  118. function ExistsSection(aOption : TOptionsClass; const aSectionName : string = '') : Boolean;
  119. end;
  120. TSectionList = TObjectList<TOptions>;
  121. IOptionsSerializer = interface
  122. ['{7DECE203-4AAE-4C9D-86C8-B3D583DF7C8B}']
  123. function Load(aSections : TSectionList; aFailOnSectionNotExists : Boolean) : Boolean;
  124. function LoadSection(aSections : TSectionList; aOptions: TOptions) : Boolean;
  125. procedure Save(aSections : TSectionList);
  126. function GetFileSectionNames(out oSections : TArray<string>) : Boolean;
  127. function ConfigExists : Boolean;
  128. end;
  129. IFileOptionsSerializer = interface(IOptionsSerializer)
  130. ['{3417B142-2879-4DA6-86CA-19F0F427A92C}']
  131. function GetFileName : string;
  132. procedure SetFileName(const aFilename : string);
  133. property Filename : string read GetFilename write SetFilename;
  134. end;
  135. TOptionsSerializer = class(TInterfacedObject,IOptionsSerializer)
  136. public
  137. function Load(aSections : TSectionList; aFailOnSectionNotExists : Boolean) : Boolean; virtual; abstract;
  138. function LoadSection(aSections : TSectionList; aOptions: TOptions) : Boolean; virtual; abstract;
  139. procedure Save(aSections : TSectionList); virtual; abstract;
  140. function GetFileSectionNames(out oSections : TArray<string>) : Boolean; virtual; abstract;
  141. function ConfigExists : Boolean; virtual; abstract;
  142. end;
  143. TOptionsFileSerializer = class(TOptionsSerializer,IFileOptionsSerializer)
  144. private
  145. fFilename : string;
  146. function GetFileName : string;
  147. procedure SetFileName(const aFilename : string);
  148. public
  149. property Filename : string read GetFilename write SetFilename;
  150. end;
  151. TFileModifiedEvent = reference to procedure;
  152. TLoadConfigEvent = reference to procedure;
  153. TOptionValue<T : TOptions> = class(TInterfacedObject,IOptions<T>)
  154. private
  155. fValue : T;
  156. function GetSectionValue : T;
  157. public
  158. constructor Create(aValue : T);
  159. property Value : T read GetSectionValue;
  160. end;
  161. TOptionsContainer = class(TInterfacedObject,IOptionsContainer)
  162. private
  163. fSerializer : IOptionsSerializer;
  164. fSections : TSectionList;
  165. fLoaded : Boolean;
  166. fOnConfigLoaded : TLoadConfigEvent;
  167. fOnConfigReloaded : TLoadConfigEvent;
  168. function GetOptions(aOptionClass : TOptionsClass): TOptions; overload;
  169. function GetOptions(aIndex : Integer) : TOptions; overload;
  170. function GetSection(aOptionsSection : TOptionsClass; var vOptions : TOptions) : Boolean; overload;
  171. public
  172. constructor Create(aOptionsSerializer : IOptionsSerializer);
  173. destructor Destroy; override;
  174. property IsLoaded : Boolean read fLoaded;
  175. function ExistsSection(aOption : TOptionsClass; const aSectionName : string = '') : Boolean; overload;
  176. function ExistsSection<T : TOptions>(const aSectionName : string = '') : Boolean; overload;
  177. property Items[aOptionClass : TOptionsClass] : TOptions read GetOptions; default;
  178. property Items[aIndex : Integer] : TOptions read GetOptions; default;
  179. property OnConfigLoaded : TLoadConfigEvent read fOnConfigLoaded write fOnConfigLoaded;
  180. property OnConfigReloaded : TLoadConfigEvent read fOnConfigReloaded write fOnConfigReloaded;
  181. function AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions; overload;
  182. function AddSection<T : TOptions>(const aSectionName : string = '') : TOptions<T>; overload;
  183. procedure AddOption(aOption : TOptions);
  184. function GetSectionInterface<T : TOptions> : IOptions<T>;
  185. function GetSection<T : TOptions>(const aSectionName : string = '') : T; overload;
  186. function GetFileSectionNames(out oSections : TArray<string>) : Boolean;
  187. function Count : Integer;
  188. procedure Load(aFailOnSectionNotExists : Boolean = False); virtual;
  189. procedure LoadSection(aOptions : TOptions);
  190. procedure Save; virtual;
  191. end;
  192. TFileOptionsContainer = class(TOptionsContainer)
  193. private
  194. fFilename : string;
  195. fFileMonitor : TFileMonitor;
  196. fOnFileModified : TFileModifiedEvent;
  197. fReloadIfFileChanged : Boolean;
  198. procedure CreateFileMonitor;
  199. procedure FileModifiedNotify(MonitorNotify : TMonitorNotify);
  200. procedure SetReloadIfFileChanged(const Value: Boolean);
  201. public
  202. constructor Create(aOptionsSerializer : IFileOptionsSerializer; aReloadIfFileChanged : Boolean = False);
  203. destructor Destroy; override;
  204. property FileName : string read fFilename;
  205. property ReloadIfFileChanged : Boolean read fReloadIfFileChanged write SetReloadIfFileChanged;
  206. property OnFileModified : TFileModifiedEvent read fOnFileModified write fOnFileModified;
  207. procedure Save; override;
  208. function GetFileSectionNames(out oSections: TArray<string>): Boolean;
  209. end;
  210. IOptionsBuilder<T : TOptions> = interface
  211. ['{1A1DC9A9-7F2D-4CC4-A772-6C7DBAB34424}']
  212. function Options : T;
  213. end;
  214. TOptionsBuilder<T : TOptions> = class(TInterfacedObject,IOptionsBuilder<T>)
  215. protected
  216. fOptions : T;
  217. public
  218. constructor Create;
  219. function Options : T;
  220. end;
  221. EOptionConfigureError = class(Exception);
  222. EOptionLoadError = class(Exception);
  223. EOptionSaveError = class(Exception);
  224. EOptionValidationError = class(Exception);
  225. implementation
  226. { TCustomOptionsContainer}
  227. function TOptionsContainer.ExistsSection(aOption: TOptionsClass;const aSectionName: string): Boolean;
  228. var
  229. option : TOptions;
  230. begin
  231. Result := False;
  232. for option in fSections do
  233. begin
  234. if CompareText(option.ClassName,aOption.ClassName) = 0 then
  235. begin
  236. if (not aSectionName.IsEmpty) and (CompareText(option.Name,aSectionName) = 0) then Exit(True);
  237. end;
  238. end;
  239. end;
  240. function TOptionsContainer.ExistsSection<T>(const aSectionName: string): Boolean;
  241. begin
  242. Result := GetSection<T>(aSectionName) <> nil;
  243. end;
  244. procedure TOptionsContainer.AddOption(aOption: TOptions);
  245. begin
  246. if aOption.Name.IsEmpty then aOption.Name := Copy(aOption.ClassName,2,aOption.ClassName.Length);
  247. fSections.Add(aOption);
  248. end;
  249. function TOptionsContainer.AddSection(aOption : TOptionsClass; const aSectionName : string = '') : TOptions;
  250. var
  251. option : TOptions;
  252. begin
  253. //if section already exists, returns it
  254. option := Self.GetOptions(aOption);
  255. if option <> nil then Exit(option);
  256. option := aOption.Create;
  257. if aSectionName.IsEmpty then option.Name := Copy(aOption.ClassName,2,aOption.ClassName.Length)
  258. else option.Name := aSectionName;
  259. fSections.Add(option);
  260. Result := option;
  261. end;
  262. function TOptionsContainer.AddSection<T>(const aSectionName: string): TOptions<T>;
  263. var
  264. option : TOptions;
  265. begin
  266. //if section already exists, returns it
  267. option := Self.GetSection<T>(aSectionName);
  268. if option <> nil then Exit(TOptions<T>(option));
  269. //new section
  270. option := TRTTI.CreateInstance<T>;
  271. if aSectionName.IsEmpty then option.Name := Copy(T.ClassName,2,T.ClassName.Length)
  272. else option.Name := aSectionName;
  273. fSections.Add(option);
  274. Result.Create(option);
  275. end;
  276. function TOptionsContainer.Count: Integer;
  277. begin
  278. Result := fSections.Count;
  279. end;
  280. constructor TOptionsContainer.Create(aOptionsSerializer: IOptionsSerializer);
  281. begin
  282. fSerializer := aOptionsSerializer;
  283. fSections := TSectionList.Create(False);
  284. fLoaded := False;
  285. end;
  286. destructor TOptionsContainer.Destroy;
  287. var
  288. option : TOptions;
  289. begin
  290. fSerializer := nil;
  291. for option in fSections do
  292. begin
  293. if option.RefCount = 0 then option.Free;
  294. end;
  295. fSections.Free;
  296. inherited;
  297. end;
  298. function TOptionsContainer.GetFileSectionNames(out oSections: TArray<string>): Boolean;
  299. begin
  300. Result := fSerializer.GetFileSectionNames(oSections);
  301. end;
  302. function TOptionsContainer.GetOptions(aIndex: Integer): TOptions;
  303. begin
  304. Result := fSections[aIndex];
  305. end;
  306. function TOptionsContainer.GetSection(aOptionsSection: TOptionsClass; var vOptions: TOptions): Boolean;
  307. var
  308. option : TOptions;
  309. begin
  310. Result := False;
  311. for option in fSections do
  312. begin
  313. if option is TOptionsClass then
  314. begin
  315. vOptions := option as TOptionsClass;
  316. Exit;
  317. end;
  318. end;
  319. end;
  320. function TOptionsContainer.GetOptions(aOptionClass : TOptionsClass) : TOptions;
  321. var
  322. option : TOptions;
  323. begin
  324. Result := nil;
  325. for option in fSections do
  326. begin
  327. if option is aOptionClass then Result := option as TOptionsClass;
  328. end;
  329. end;
  330. function TOptionsContainer.GetSection<T>(const aSectionName : string = '') : T;
  331. var
  332. option : TOptions;
  333. begin
  334. Result := nil;
  335. for option in fSections do
  336. begin
  337. if option is T then
  338. begin
  339. if (aSectionName.IsEmpty) or (CompareText(option.Name,aSectionName) = 0) then
  340. begin
  341. Result := option as T;
  342. Exit;
  343. end;
  344. end;
  345. end;
  346. end;
  347. function TOptionsContainer.GetSectionInterface<T>: IOptions<T>;
  348. begin
  349. Result := TOptionValue<T>.Create(Self.GetSection<T>);
  350. end;
  351. procedure TOptionsContainer.Load(aFailOnSectionNotExists : Boolean = False);
  352. var
  353. option : TOptions;
  354. begin
  355. if fSerializer.ConfigExists then
  356. begin
  357. if not fSerializer.Load(fSections,aFailOnSectionNotExists) then Save;
  358. if not fLoaded then
  359. begin
  360. fLoaded := True;
  361. if Assigned(fOnConfigLoaded) then fOnConfigLoaded;
  362. end
  363. else if Assigned(fOnConfigReloaded) then fOnConfigReloaded;
  364. end
  365. else
  366. begin
  367. //if config not exists get default values
  368. for option in fSections do option.DefaultValues;
  369. //saves default
  370. Save;
  371. end;
  372. end;
  373. procedure TOptionsContainer.LoadSection(aOptions : TOptions);
  374. begin
  375. if fSerializer.ConfigExists then
  376. begin
  377. if not fSerializer.LoadSection(fSections,aOptions) then Save;
  378. end;
  379. end;
  380. procedure TOptionsContainer.Save;
  381. begin
  382. fSerializer.Save(fSections);
  383. end;
  384. { TOptionsContainer }
  385. constructor TFileOptionsContainer.Create(aOptionsSerializer : IFileOptionsSerializer; aReloadIfFileChanged : Boolean = False);
  386. begin
  387. inherited Create(aOptionsSerializer);
  388. fFilename := aOptionsSerializer.Filename;
  389. if aReloadIfFileChanged then CreateFileMonitor;
  390. end;
  391. procedure TFileOptionsContainer.Save;
  392. var
  393. laststate : Boolean;
  394. begin
  395. //disable filemonitor to avoid detect manual save as a external file change
  396. if fReloadIfFileChanged then
  397. begin
  398. laststate := fFileMonitor.Enabled;
  399. fFileMonitor.Enabled := False;
  400. try
  401. //save config file
  402. inherited;
  403. finally
  404. //set last state
  405. Sleep(0);
  406. fFileMonitor.Enabled := laststate;
  407. end;
  408. end
  409. else inherited;
  410. end;
  411. procedure TFileOptionsContainer.SetReloadIfFileChanged(const Value: Boolean);
  412. begin
  413. if Value = fReloadIfFileChanged then Exit;
  414. fReloadIfFileChanged := Value;
  415. if Assigned(fFileMonitor) then fFileMonitor.Free;
  416. if fReloadIfFileChanged then CreateFileMonitor;
  417. end;
  418. procedure TFileOptionsContainer.CreateFileMonitor;
  419. begin
  420. fFileMonitor := TQuickFileMonitor.Create;
  421. fFileMonitor.FileName := fFilename;
  422. fFileMonitor.Interval := 2000;
  423. fFileMonitor.Notifies := [TMonitorNotify.mnFileModified];
  424. fFileMonitor.OnFileChange := FileModifiedNotify;
  425. fFileMonitor.Enabled := True;
  426. end;
  427. destructor TFileOptionsContainer.Destroy;
  428. begin
  429. if Assigned(fFileMonitor) then fFileMonitor.Free;
  430. inherited;
  431. end;
  432. procedure TFileOptionsContainer.FileModifiedNotify(MonitorNotify: TMonitorNotify);
  433. begin
  434. if MonitorNotify = TMonitorNotify.mnFileModified then
  435. begin
  436. if Assigned(fOnFileModified) then fOnFileModified;
  437. if fReloadIfFileChanged then
  438. begin
  439. Load(False);
  440. end;
  441. end;
  442. end;
  443. function TFileOptionsContainer.GetFileSectionNames(out oSections : TArray<string>) : Boolean;
  444. begin
  445. Result := fSerializer.GetFileSectionNames(oSections);
  446. end;
  447. { TOptions }
  448. function TOptions.ConfigureOptions<T>(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
  449. var
  450. value : TValue;
  451. begin
  452. Result := TOptionsValidator.Create(Self);
  453. if Assigned(aOptionsFunc) then
  454. begin
  455. value := Self;
  456. aOptionsFunc(value.AsType<T>);
  457. end;
  458. end;
  459. constructor TOptions.Create;
  460. begin
  461. fName := '';
  462. fHideOptions := False;
  463. end;
  464. procedure TOptions.DefaultValues;
  465. begin
  466. //nothing
  467. end;
  468. procedure TOptions.DoValidateOptions;
  469. var
  470. ivalidator : IOptionsValidator;
  471. begin
  472. ivalidator := TOptionsValidator.Create(Self);
  473. ivalidator.ValidateOptions;
  474. end;
  475. procedure TOptions.ValidateOptions;
  476. begin
  477. try
  478. DoValidateOptions;
  479. except
  480. on E : Exception do
  481. begin
  482. raise EOptionConfigureError.CreateFmt('Validation Options Error : %s',[e.Message]);
  483. end;
  484. end;
  485. end;
  486. { TOptionsValidator }
  487. procedure TOptionsValidator.ValidateObject(aObj : TObject);
  488. var
  489. ctx : TRttiContext;
  490. rtype : TRttiType;
  491. rprop : TRttiProperty;
  492. attrib : TCustomAttribute;
  493. rvalue : TValue;
  494. begin
  495. rtype := ctx.GetType(aObj.ClassInfo);
  496. for rprop in rtype.GetProperties do
  497. begin
  498. //check only published properties
  499. if rprop.Visibility = TMemberVisibility.mvPublished then
  500. begin
  501. //check validation option attributes
  502. for attrib in rprop.GetAttributes do
  503. begin
  504. if attrib is Required then ValidateRequired(aObj,rprop)
  505. else if attrib is StringLength then ValidateStringLength(aObj,rprop,StringLength(attrib))
  506. else if attrib is Range then ValidateRange(aObj,rprop,Range(attrib));
  507. end;
  508. rvalue := rprop.GetValue(aObj);
  509. if not rvalue.IsEmpty then
  510. begin
  511. case rvalue.Kind of
  512. tkClass : ValidateObject(rvalue.AsObject);
  513. tkDynArray : ValidateArray(rvalue);
  514. end;
  515. end;
  516. end;
  517. end;
  518. end;
  519. constructor TOptionsValidator.Create(aOptions: TOptions);
  520. begin
  521. fOptions := aOptions;
  522. end;
  523. procedure TOptionsValidator.ValidateOptions;
  524. begin
  525. ValidateObject(fOptions);
  526. end;
  527. procedure TOptionsValidator.ValidateArray(aValue : TValue);
  528. type
  529. PPByte = ^PByte;
  530. var
  531. ctx : TRttiContext;
  532. rDynArray : TRttiDynamicArrayType;
  533. itvalue : TValue;
  534. i : Integer;
  535. begin
  536. rDynArray := ctx.GetType(aValue.TypeInfo) as TRTTIDynamicArrayType;
  537. for i := 0 to aValue.GetArrayLength - 1 do
  538. begin
  539. TValue.Make(PPByte(aValue.GetReferenceToRawData)^ + rDynArray.ElementType.TypeSize * i, rDynArray.ElementType.Handle,itvalue);
  540. if not itvalue.IsEmpty then
  541. begin
  542. case itvalue.Kind of
  543. tkClass : ValidateObject(itvalue.AsObject);
  544. tkDynArray : ValidateArray(itvalue);
  545. end;
  546. end;
  547. end;
  548. end;
  549. procedure TOptionsValidator.ValidateRange(const aInstance : TObject; aProperty: TRttiProperty; aValidation : Range);
  550. var
  551. value : TValue;
  552. msg : string;
  553. begin
  554. value := aProperty.GetValue(aInstance);
  555. if not value.IsEmpty then
  556. begin
  557. if value.Kind = tkFloat then
  558. begin
  559. if (value.AsExtended < aValidation.Min) or (value.AsExtended > aValidation.Max) then
  560. begin
  561. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds predefined range (%2f - %2f)',[fOptions.Name,aInstance.ClassName,aProperty.Name,aValidation.Min,aValidation.Max])
  562. else msg := aValidation.ErrorMsg;
  563. raise EOptionValidationError.Create(msg);
  564. end;
  565. end
  566. else if value.Kind in [tkInteger,tkInt64] then
  567. begin
  568. if (value.AsInt64 < aValidation.Min) or (value.AsInt64 > aValidation.Max) then
  569. begin
  570. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds predefined range (%d - %d)',[fOptions.Name,aInstance.ClassName,aProperty.Name,Round(aValidation.Min),Round(aValidation.Max)])
  571. else msg := aValidation.ErrorMsg;
  572. raise EOptionValidationError.Create(msg);
  573. end;
  574. end;
  575. end;
  576. end;
  577. procedure TOptionsValidator.ValidateRequired(const aInstance : TObject; aProperty: TRttiProperty);
  578. begin
  579. if aProperty.GetValue(aInstance).IsEmpty then raise EOptionValidationError.CreateFmt('Option %s "%s.%s" is required',[fOptions.Name,aInstance.ClassName,aProperty.Name]);
  580. end;
  581. procedure TOptionsValidator.ValidateStringLength(const aInstance : TObject; aProperty: TRttiProperty; aValidation : StringLength);
  582. var
  583. value : TValue;
  584. msg : string;
  585. begin
  586. value := aProperty.GetValue(aInstance);
  587. if (not value.IsEmpty) and (value.AsString.Length > aValidation.MaxLength) then
  588. begin
  589. if aValidation.ErrorMsg.IsEmpty then msg := Format('Option %s "%s.%s" exceeds max length (%d)',[fOptions.Name,aInstance.ClassName,aProperty.Name,aValidation.MaxLength])
  590. else msg := aValidation.ErrorMsg;
  591. raise EOptionValidationError.Create(msg);
  592. end;
  593. end;
  594. { Range }
  595. constructor Range.Create(aMin, aMax: Integer; const aErrorMsg : string = '');
  596. begin
  597. fRangeMin := aMin;
  598. fRangeMax := aMax;
  599. fErrorMsg := aErrorMsg;
  600. end;
  601. constructor Range.Create(aMin, aMax: Double; const aErrorMsg: string);
  602. begin
  603. fRangeMin := aMin;
  604. fRangeMax := aMax;
  605. fErrorMsg := aErrorMsg;
  606. end;
  607. { StringLength }
  608. constructor StringLength.Create(aMaxLength: Integer; const aErrorMsg : string = '');
  609. begin
  610. fMaxLength := aMaxLength;
  611. fErrorMsg := aErrorMsg;
  612. end;
  613. { TOptionValue<T> }
  614. constructor TOptionValue<T>.Create(aValue: T);
  615. begin
  616. fValue := aValue;
  617. end;
  618. function TOptionValue<T>.GetSectionValue: T;
  619. begin
  620. Result := fValue;
  621. end;
  622. { TOptions<T> }
  623. function TOptions<T>.ConfigureOptions(aOptionsFunc: TConfigureOptionsProc<T>): IOptionsValidator;
  624. begin
  625. if Assigned(aOptionsFunc) then Result := fOptions.ConfigureOptions<T>(aOptionsFunc)
  626. else Result := TOptionsValidator.Create(fOptions);
  627. end;
  628. constructor TOptions<T>.Create(aOptions: T);
  629. begin
  630. fOptions := aOptions;
  631. end;
  632. { TOptionsBuilder<T> }
  633. constructor TOptionsBuilder<T>.Create;
  634. begin
  635. fOptions := (PTypeInfo(TypeInfo(T)).TypeData.ClassType.Create) as T;
  636. end;
  637. function TOptionsBuilder<T>.Options: T;
  638. begin
  639. Result := fOptions;
  640. end;
  641. { TOptionsFileSerializer }
  642. function TOptionsFileSerializer.GetFileName: string;
  643. begin
  644. Result := fFilename;
  645. end;
  646. procedure TOptionsFileSerializer.SetFileName(const aFilename: string);
  647. begin
  648. fFilename := aFilename;
  649. end;
  650. end.