diff --git a/CHANGELOG.md b/CHANGELOG.md index 5353e2ded5..f76c4398e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Fixed a bug where raw collections requests would not be supported #467 +- Fixes a bug where in memory backing store would not return changed properties to null #243 +- Fixes a bug where generated models would be tied to a specific backing store implementation #400 ## [0.0.7] - 2021-08-04 diff --git a/README.md b/README.md index dac92de4c5..c00e53ed4a 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ Kiota accepts the following parameters during the generation: | Name | Shorthand | Required | Description | Accepted values | Default Value | | ---- | --------- | -------- | ----------- | --------------- | ------------- | -| backing-store | b | no | The fully qualified name for the backing store class to use. | A fully qualified class name like `Microsoft.Kiota.Abstractions.Store.InMemoryBackingStore` (CSharp), `com.microsoft.kiota.store.InMemoryBackingStore` (Java), `@microsoft/kiota-abstractions.InMemoryBackingStore` (TypeScript) | Empty string | +| backing-store | b | no | Enables backing store for models. | Flag. N/A. | false | | class-name | c | no | The class name to use the for main entry point | A valid class name according to the target language specification. | ApiClient | | deserializer | ds | no | The fully qualified class names for deserializers. | This parameter can be passed multiple values. A module name like `Microsoft.Kiota.Serialization.Json` that implementats `IParseNodeFactory`. | `Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory` (csharp), `@microsoft/kiota-serialization-json.JsonParseNodeFactory` (typescript), `com.microsoft.kiota.serialization.JsonParseNodeFactory` (java) | | language | l | no | The programming language to generate the SDK in. | csharp, java, or typescript | csharp | diff --git a/abstractions/dotnet/src/IHttpCore.cs b/abstractions/dotnet/src/IHttpCore.cs index 1f6ab4b198..288077b640 100644 --- a/abstractions/dotnet/src/IHttpCore.cs +++ b/abstractions/dotnet/src/IHttpCore.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions.Store; namespace Microsoft.Kiota.Abstractions { /// @@ -10,7 +11,8 @@ public interface IHttpCore { /// /// Enables the backing store proxies for the SerializationWriters and ParseNodes in use. /// - void EnableBackingStore(); + /// The backing store factory to use. + void EnableBackingStore(IBackingStoreFactory backingStoreFactory); /// /// Gets the serialization writer factory currently in use for the HTTP core service. /// diff --git a/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj b/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj index 19efcc8c81..9ff491ce30 100644 --- a/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj +++ b/abstractions/dotnet/src/Microsoft.Kiota.Abstractions.csproj @@ -4,7 +4,7 @@ net5.0 true https://github.com/microsoft/kiota - 1.0.15 + 1.0.16 diff --git a/abstractions/dotnet/src/RequestInfo.cs b/abstractions/dotnet/src/RequestInfo.cs index 0b588e1e37..acc68fc146 100644 --- a/abstractions/dotnet/src/RequestInfo.cs +++ b/abstractions/dotnet/src/RequestInfo.cs @@ -41,7 +41,7 @@ public class RequestInfo /// /// The middleware option to add. public void AddMiddlewareOptions(params IMiddlewareOption[] options) { - if(!options?.Any() ?? false) return; // it's a no-op if there are no options and this avoid having to check in the code gen. + if(!(options?.Any() ?? false)) return; // it's a no-op if there are no options and this avoid having to check in the code gen. foreach(var option in options.Where(x => x != null)) if(!_middlewareOptions.TryAdd(option.GetType().FullName, option)) _middlewareOptions[option.GetType().FullName] = option; diff --git a/abstractions/dotnet/src/serialization/ISerializationWriter.cs b/abstractions/dotnet/src/serialization/ISerializationWriter.cs index 801b2e3d4b..7b34c1c917 100644 --- a/abstractions/dotnet/src/serialization/ISerializationWriter.cs +++ b/abstractions/dotnet/src/serialization/ISerializationWriter.cs @@ -74,6 +74,11 @@ public interface ISerializationWriter : IDisposable { /// The enum value to be written. void WriteEnumValue(string key, T? value) where T : struct, Enum; /// + /// Writes a null value for the specified key. + /// + /// The key to be used for the written value. May be null. + void WriteNullValue(string key); + /// /// Writes the specified additional data to the stream. /// /// The additional data to be written. @@ -91,5 +96,9 @@ public interface ISerializationWriter : IDisposable { /// Callback called after the serialization process ends. /// Action OnAfterObjectSerialization { get; set; } + /// + /// Callback called right after the serialization process starts. + /// + Action OnStartObjectSerialization { get; set; } } } diff --git a/abstractions/dotnet/src/serialization/SerializationWriterProxyFactory.cs b/abstractions/dotnet/src/serialization/SerializationWriterProxyFactory.cs index ddf76bba17..6bf67a42f8 100644 --- a/abstractions/dotnet/src/serialization/SerializationWriterProxyFactory.cs +++ b/abstractions/dotnet/src/serialization/SerializationWriterProxyFactory.cs @@ -9,22 +9,28 @@ public class SerializationWriterProxyFactory : ISerializationWriterFactory { private readonly ISerializationWriterFactory _concrete; private readonly Action _onBefore; private readonly Action _onAfter; + private readonly Action _onStartSerialization; /// /// Creates a new proxy factory that wraps the specified concrete factory while composing the before and after callbacks. /// /// The concrete factory to wrap. - /// The callback to invoke before the serialization of any model object. - /// The callback to invoke after the serialization of any model object. + /// The callback to invoke before the serialization of any model object. + /// The callback to invoke after the serialization of any model object. + /// The callback to invoke when serialization of the entire model has started. public SerializationWriterProxyFactory(ISerializationWriterFactory concrete, - Action onBeforeSerialization, Action onAfterSerialization) { + Action onBeforeSerialization, + Action onAfterSerialization, + Action onStartSerialization) { _concrete = concrete ?? throw new ArgumentNullException(nameof(concrete)); _onBefore = onBeforeSerialization; _onAfter = onAfterSerialization; + _onStartSerialization = onStartSerialization; } public ISerializationWriter GetSerializationWriter(string contentType) { var writer = _concrete.GetSerializationWriter(contentType); var originalBefore = writer.OnBeforeObjectSerialization; var originalAfter = writer.OnAfterObjectSerialization; + var originalStart = writer.OnStartObjectSerialization; writer.OnBeforeObjectSerialization = (x) => { _onBefore?.Invoke(x); // the callback set by the implementation (e.g. backing store) originalBefore?.Invoke(x); // some callback that might already be set on the target @@ -33,6 +39,10 @@ public ISerializationWriter GetSerializationWriter(string contentType) { _onAfter?.Invoke(x); originalAfter?.Invoke(x); }; + writer.OnStartObjectSerialization = (x, y) => { + _onStartSerialization?.Invoke(x, y); + originalStart?.Invoke(x, y); + }; return writer; } } diff --git a/abstractions/dotnet/src/store/BackingStoreFactorySingleton.cs b/abstractions/dotnet/src/store/BackingStoreFactorySingleton.cs new file mode 100644 index 0000000000..ae235b6dee --- /dev/null +++ b/abstractions/dotnet/src/store/BackingStoreFactorySingleton.cs @@ -0,0 +1,11 @@ +namespace Microsoft.Kiota.Abstractions.Store { + /// + /// This class is used to register the backing store factory. + /// + public class BackingStoreFactorySingleton { + /// + /// The backing store factory singleton instance. + /// + public static IBackingStoreFactory Instance { get; set; } = new InMemoryBackingStoreFactory(); + } +} diff --git a/abstractions/dotnet/src/store/BackingStoreSerializationWriterProxyFactory.cs b/abstractions/dotnet/src/store/BackingStoreSerializationWriterProxyFactory.cs index 25bb8e030b..9dbfd92444 100644 --- a/abstractions/dotnet/src/store/BackingStoreSerializationWriterProxyFactory.cs +++ b/abstractions/dotnet/src/store/BackingStoreSerializationWriterProxyFactory.cs @@ -21,6 +21,12 @@ public BackingStoreSerializationWriterProxyFactory(ISerializationWriterFactory c backedModel.BackingStore.ReturnOnlyChangedValues = false; backedModel.BackingStore.InitializationCompleted = true; } + },(x, y) => { + if(x is IBackedModel backedModel && backedModel.BackingStore != null) { + var nullValues = backedModel.BackingStore.EnumerateKeysForValuesChangedToNull(); + foreach(var key in nullValues) + y.WriteNullValue(key); + } }) {} } } diff --git a/abstractions/dotnet/src/store/IBackingStore.cs b/abstractions/dotnet/src/store/IBackingStore.cs index 734bbb5a5f..00bba32b0c 100644 --- a/abstractions/dotnet/src/store/IBackingStore.cs +++ b/abstractions/dotnet/src/store/IBackingStore.cs @@ -21,6 +21,11 @@ public interface IBackingStore { /// The values available in the backing store. IEnumerable> Enumerate(); /// + /// Enumerates the keys for all values that changed to null. + /// + /// The keys for all values that changed to null. + IEnumerable EnumerateKeysForValuesChangedToNull(); + /// /// Creates a subscription to any data change happening. /// /// Callback to be invoked on data changes where the first parameter is the data key, the second the previous value and the third the new value. diff --git a/abstractions/dotnet/src/store/IBackingStoreFactory.cs b/abstractions/dotnet/src/store/IBackingStoreFactory.cs new file mode 100644 index 0000000000..80877535ed --- /dev/null +++ b/abstractions/dotnet/src/store/IBackingStoreFactory.cs @@ -0,0 +1,12 @@ +namespace Microsoft.Kiota.Abstractions.Store { + /// + /// Defines the contract for a factory that creates backing stores. + /// + public interface IBackingStoreFactory { + /// + /// Creates a new instance of the backing store. + /// + /// A new instance of the backing store. + IBackingStore CreateBackingStore(); + } +} diff --git a/abstractions/dotnet/src/store/InMemoryBackingStore.cs b/abstractions/dotnet/src/store/InMemoryBackingStore.cs index 4344cfeb82..466de55083 100644 --- a/abstractions/dotnet/src/store/InMemoryBackingStore.cs +++ b/abstractions/dotnet/src/store/InMemoryBackingStore.cs @@ -37,6 +37,9 @@ public IEnumerable> Enumerate() { return (ReturnOnlyChangedValues ? store.Where(x => !x.Value.Item1) : store) .Select(x => new KeyValuePair(x.Key, x.Value.Item2)); } + public IEnumerable EnumerateKeysForValuesChangedToNull() { + return store.Where(x => x.Value.Item1 && x.Value.Item2 == null).Select(x => x.Key); + } public string Subscribe(Action callback) { var id = Guid.NewGuid().ToString(); Subscribe(callback, id); diff --git a/abstractions/dotnet/src/store/InMemoryBackingStoreFactory.cs b/abstractions/dotnet/src/store/InMemoryBackingStoreFactory.cs new file mode 100644 index 0000000000..9845964684 --- /dev/null +++ b/abstractions/dotnet/src/store/InMemoryBackingStoreFactory.cs @@ -0,0 +1,10 @@ +namespace Microsoft.Kiota.Abstractions.Store { + /// + /// This class is used to create instances of . + /// + public class InMemoryBackingStoreFactory : IBackingStoreFactory { + public IBackingStore CreateBackingStore() { + return new InMemoryBackingStore(); + } + } +} diff --git a/abstractions/java/lib/build.gradle b/abstractions/java/lib/build.gradle index 35a741340a..dc0e177aa0 100644 --- a/abstractions/java/lib/build.gradle +++ b/abstractions/java/lib/build.gradle @@ -46,7 +46,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-abstractions' - version '1.0.15' + version '1.0.16' from(components.java) } } diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/HttpCore.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/HttpCore.java index 0401f52db6..0ea41d9f31 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/HttpCore.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/HttpCore.java @@ -8,11 +8,15 @@ import com.microsoft.kiota.serialization.Parsable; import com.microsoft.kiota.serialization.SerializationWriterFactory; +import com.microsoft.kiota.store.BackingStoreFactory; /** Service responsible for translating abstract Request Info into concrete native HTTP requests. */ public interface HttpCore { - /** Enables the backing store proxies for the SerializationWriters and ParseNodes in use. */ - void enableBackingStore(); + /** + * Enables the backing store proxies for the SerializationWriters and ParseNodes in use. + * @param backingStoreFactory The backing store factory to use. + */ + void enableBackingStore(@Nullable final BackingStoreFactory backingStoreFactory); /** * Gets the serialization writer factory currently in use for the HTTP core service. * @return the serialization writer factory currently in use for the HTTP core service. diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/serialization/SerializationWriter.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/serialization/SerializationWriter.java index c6b08e821b..f224dc1dee 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/serialization/SerializationWriter.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/serialization/SerializationWriter.java @@ -8,6 +8,7 @@ import java.util.EnumSet; import java.lang.Enum; import java.util.function.Consumer; +import java.util.function.BiConsumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -92,6 +93,11 @@ public interface SerializationWriter extends Closeable { * @param value the value to write to the stream. */ > void writeEnumValue(@Nullable final String key, @Nullable final T value); + /** + * Writes a null value for the specified key. + * @param key the key to write the value with. + */ + void writeNullValue(@Nullable final String key); /** * Writes the specified additional data values to the stream with an optional given key. * @param value the values to write to the stream. @@ -109,6 +115,12 @@ public interface SerializationWriter extends Closeable { */ @Nullable Consumer getOnAfterObjectSerialization(); + /** + * Gets the callback called right after the serialization process starts. + * @return the callback called right after the serialization process starts. + */ + @Nullable + BiConsumer getOnStartObjectSerialization(); /** * Sets the callback called before the objects gets serialized. * @param value the callback called before the objects gets serialized. @@ -119,4 +131,9 @@ public interface SerializationWriter extends Closeable { * @param value the callback called after the objects gets serialized. */ void setOnAfterObjectSerialization(@Nullable final Consumer value); + /** + * Sets the callback called right after the serialization process starts. + * @param value the callback called right after the serialization process starts. + */ + void setOnStartObjectSerialization(@Nullable final BiConsumer value); } \ No newline at end of file diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/serialization/SerializationWriterProxyFactory.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/serialization/SerializationWriterProxyFactory.java index 36fe286315..88dd3cf992 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/serialization/SerializationWriterProxyFactory.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/serialization/SerializationWriterProxyFactory.java @@ -1,6 +1,7 @@ package com.microsoft.kiota.serialization; import java.util.function.Consumer; +import java.util.function.BiConsumer; import java.util.Objects; import javax.annotation.Nonnull; @@ -14,22 +15,28 @@ public String getValidContentType() { private final SerializationWriterFactory _concrete; private final Consumer _onBefore; private final Consumer _onAfter; + private final BiConsumer _onStart; /** * Creates a new proxy factory that wraps the specified concrete factory while composing the before and after callbacks. * @param concreteFactory the concrete factory to wrap - * @param onBefore the callback to invoke before the serialization of any model object. - * @param onAfter the callback to invoke after the serialization of any model object. + * @param onBeforeSerialization the callback to invoke before the serialization of any model object. + * @param onAfterSerialization the callback to invoke after the serialization of any model object. + * @param onStartSerialization the callback to invoke when the serialization of a model object starts. */ public SerializationWriterProxyFactory(@Nonnull final SerializationWriterFactory concrete, - @Nullable final Consumer onBeforeSerialization, @Nullable final Consumer onAfterSerialization) { + @Nullable final Consumer onBeforeSerialization, + @Nullable final Consumer onAfterSerialization, + @Nullable final BiConsumer onStartObjectSerialization) { _concrete = Objects.requireNonNull(concrete); _onBefore = onBeforeSerialization; _onAfter = onAfterSerialization; + _onStart = onStartObjectSerialization; } public SerializationWriter getSerializationWriter(final String contentType) { final SerializationWriter writer = _concrete.getSerializationWriter(contentType); final Consumer originalBefore = writer.getOnBeforeObjectSerialization(); final Consumer originalAfter = writer.getOnAfterObjectSerialization(); + final BiConsumer originalStart = writer.getOnStartObjectSerialization(); writer.setOnBeforeObjectSerialization((x) -> { if(_onBefore != null) { _onBefore.accept(x); // the callback set by the implementation (e.g. backing store) @@ -46,6 +53,14 @@ public SerializationWriter getSerializationWriter(final String contentType) { originalAfter.accept(x); } }); + writer.setOnStartObjectSerialization((x, y) -> { + if(_onStart != null) { + _onStart.accept(x, y); + } + if(originalStart != null) { + originalStart.accept(x, y); + } + }); return writer; } diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStore.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStore.java index 0c6c716bb1..8dc120944d 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStore.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStore.java @@ -32,6 +32,12 @@ public interface BackingStore { @Nonnull Map enumerate(); /** + * Enumerates the keys for all values that changed to null. + * @return The keys for the values that changed to null. + */ + @Nonnull + Iterable enumerateKeysForValuesChangedToNull(); + /** * Creates a subscription to any data change happening. * @param callback Callback to be invoked on data changes where the first parameter is the data key, the second the previous value and the third the new value. * @return The subscription Id to use when removing the subscription diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStoreFactory.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStoreFactory.java new file mode 100644 index 0000000000..627f1ffd34 --- /dev/null +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStoreFactory.java @@ -0,0 +1,13 @@ +package com.microsoft.kiota.store; + +import javax.annotation.Nonnull; + +/** Defines the contract for a factory that creates backing stores. */ +public interface BackingStoreFactory { + /** + * Creates a new instance of the backing store. + * @return a new instance of the backing store. + */ + @Nonnull + BackingStore createBackingStore(); +} \ No newline at end of file diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStoreFactorySingleton.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStoreFactorySingleton.java new file mode 100644 index 0000000000..5702ec3d75 --- /dev/null +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStoreFactorySingleton.java @@ -0,0 +1,7 @@ +package com.microsoft.kiota.store; + +/** This class is used to register the backing store factory. */ +public class BackingStoreFactorySingleton { + /** The backing store factory singleton instance. */ + public static BackingStoreFactory instance = new InMemoryBackingStoreFactory(); +} \ No newline at end of file diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStoreSerializationWriterProxyFactory.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStoreSerializationWriterProxyFactory.java index a504e2fd4c..36ef25fda1 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStoreSerializationWriterProxyFactory.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/BackingStoreSerializationWriterProxyFactory.java @@ -15,16 +15,29 @@ public BackingStoreSerializationWriterProxyFactory(@Nonnull final SerializationW (x) -> { if(x instanceof BackedModel) { final BackedModel backedModel = (BackedModel)x; - if(backedModel.getBackingStore() != null) { - backedModel.getBackingStore().setReturnOnlyChangedValues(true); + final var backingStore = backedModel.getBackingStore(); + if(backingStore != null) { + backingStore.setReturnOnlyChangedValues(true); } } },(x) -> { if(x instanceof BackedModel) { final BackedModel backedModel = (BackedModel)x; - if(backedModel.getBackingStore() != null) { - backedModel.getBackingStore().setReturnOnlyChangedValues(false); - backedModel.getBackingStore().setIsInitializationCompleted(true); + final var backingStore = backedModel.getBackingStore(); + if(backingStore != null) { + backingStore.setReturnOnlyChangedValues(false); + backingStore.setIsInitializationCompleted(true); + } + } + }, (x, y) -> { + if(x instanceof BackedModel) { + final BackedModel backedModel = (BackedModel)x; + final var backingStore = backedModel.getBackingStore(); + if(backingStore != null) { + final var keys = backingStore.enumerateKeysForValuesChangedToNull(); + for(final var key : keys) { + y.writeNullValue(key); + } } } }); diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java index 20d9812c14..6c1e0a0dcf 100644 --- a/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/InMemoryBackingStore.java @@ -2,6 +2,8 @@ import java.lang.ClassCastException; +import java.util.List; +import java.util.ArrayList; import java.util.Map; import java.util.HashMap; import java.util.Objects; @@ -48,6 +50,17 @@ public Map enumerate() { } return result; } + public Iterable enumerateKeysForValuesChangedToNull() { + final List result = new ArrayList<>(); + for(final Map.Entry> entry : this.store.entrySet()) { + final Pair wrapper = entry.getValue(); + final Object value = wrapper.getValue1(); + if(value == null && wrapper.getValue0()) { + result.add(entry.getKey()); + } + } + return result; + } private Object getValueFromWrapper(final Pair wrapper) { if(wrapper != null) { final Boolean hasChanged = wrapper.getValue0(); diff --git a/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/InMemoryBackingStoreFactory.java b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/InMemoryBackingStoreFactory.java new file mode 100644 index 0000000000..ccb48ce6c4 --- /dev/null +++ b/abstractions/java/lib/src/main/java/com/microsoft/kiota/store/InMemoryBackingStoreFactory.java @@ -0,0 +1,9 @@ +package com.microsoft.kiota.store; + +/** This class is used to create instances of InMemoryBackingStore */ +public class InMemoryBackingStoreFactory implements BackingStoreFactory { + @Override + public BackingStore createBackingStore() { + return new InMemoryBackingStore(); + } +} diff --git a/abstractions/typescript/package-lock.json b/abstractions/typescript/package-lock.json index a4425989b7..cb9a04f604 100644 --- a/abstractions/typescript/package-lock.json +++ b/abstractions/typescript/package-lock.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-abstractions", - "version": "1.0.15", + "version": "1.0.16", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/abstractions/typescript/package.json b/abstractions/typescript/package.json index 4bac866b94..a8abc57f07 100644 --- a/abstractions/typescript/package.json +++ b/abstractions/typescript/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-abstractions", - "version": "1.0.15", + "version": "1.0.16", "description": "Core abstractions for kiota generated libraries in TypeScript and JavaScript", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/abstractions/typescript/src/httpCore.ts b/abstractions/typescript/src/httpCore.ts index 80094ed1f8..b8e726885a 100644 --- a/abstractions/typescript/src/httpCore.ts +++ b/abstractions/typescript/src/httpCore.ts @@ -1,6 +1,7 @@ import { RequestInfo } from "./requestInfo"; import { ResponseHandler } from "./responseHandler"; import { Parsable, SerializationWriterFactory } from "./serialization"; +import { BackingStoreFactory } from "./store"; /** Service responsible for translating abstract Request Info into concrete native HTTP requests. */ export interface HttpCore { @@ -43,6 +44,9 @@ export interface HttpCore { * @return a {@link Promise} of void. */ sendNoResponseContentAsync(requestInfo: RequestInfo, responseHandler: ResponseHandler | undefined): Promise; - /** Enables the backing store proxies for the SerializationWriters and ParseNodes in use. */ - enableBackingStore(): void; + /** + * Enables the backing store proxies for the SerializationWriters and ParseNodes in use. + * @param backingStoreFactory the backing store factory to use. + */ + enableBackingStore(backingStoreFactory?: BackingStoreFactory | undefined): void; } \ No newline at end of file diff --git a/abstractions/typescript/src/serialization/serializationWriter.ts b/abstractions/typescript/src/serialization/serializationWriter.ts index a764466db5..19db279731 100644 --- a/abstractions/typescript/src/serialization/serializationWriter.ts +++ b/abstractions/typescript/src/serialization/serializationWriter.ts @@ -57,6 +57,11 @@ export interface SerializationWriter { * @param values the value to write to the stream. */ writeEnumValue(key?: string | undefined, ...values: (T | undefined)[]): void; + /** + * Writes a null value for the specified key. + * @param key the key to write the value with. + */ + writeNullValue(key?: string | undefined) : void; /** * Gets the value of the serialized content. * @return the value of the serialized content. @@ -77,4 +82,9 @@ export interface SerializationWriter { * @return the callback called after the object gets serialized. */ onAfterObjectSerialization: ((value: Parsable) => void) | undefined; + /** + * Gets the callback called right after the serialization process starts. + * @return the callback called right after the serialization process starts. + */ + onStartObjectSerialization: ((value: Parsable, writer: SerializationWriter) => void) | undefined; } \ No newline at end of file diff --git a/abstractions/typescript/src/serialization/serializationWriterProxyFactory.ts b/abstractions/typescript/src/serialization/serializationWriterProxyFactory.ts index 3b3f2b417b..5b39613b8f 100644 --- a/abstractions/typescript/src/serialization/serializationWriterProxyFactory.ts +++ b/abstractions/typescript/src/serialization/serializationWriterProxyFactory.ts @@ -12,10 +12,12 @@ export abstract class SerializationWriterProxyFactory implements SerializationWr * @param _concrete the concrete factory to wrap * @param _onBefore the callback to invoke before the serialization of any model object. * @param _onAfter the callback to invoke after the serialization of any model object. + * @param _onStart the callback to invoke when the serialization of a model object starts */ constructor(private readonly _concrete: SerializationWriterFactory, - private readonly _onBefore: (value: Parsable) => void, - private readonly _onAfter: (value: Parsable) => void) { + private readonly _onBefore?: ((value: Parsable) => void) | undefined, + private readonly _onAfter?: ((value: Parsable) => void) | undefined, + private readonly _onStart?: ((value: Parsable, writer: SerializationWriter) => void) | undefined) { if(!_concrete) throw new Error("_concrete cannot be undefined"); } @@ -23,6 +25,7 @@ export abstract class SerializationWriterProxyFactory implements SerializationWr const writer = this._concrete.getSerializationWriter(contentType); const originalBefore = writer.onBeforeObjectSerialization; const originalAfter = writer.onAfterObjectSerialization; + const originalStart = writer.onStartObjectSerialization; writer.onBeforeObjectSerialization = (value) => { this._onBefore && this._onBefore(value); originalBefore && originalBefore(value); @@ -31,6 +34,10 @@ export abstract class SerializationWriterProxyFactory implements SerializationWr this._onAfter && this._onAfter(value); originalAfter && originalAfter(value); } + writer.onStartObjectSerialization = (value, writer) => { + this._onStart && this._onStart(value, writer); + originalStart && originalStart(value, writer); + } return writer; } diff --git a/abstractions/typescript/src/store/backingStore.ts b/abstractions/typescript/src/store/backingStore.ts index f32dc80c84..09715b898a 100644 --- a/abstractions/typescript/src/store/backingStore.ts +++ b/abstractions/typescript/src/store/backingStore.ts @@ -21,6 +21,11 @@ export interface BackingStore { */ enumerate(): {key: string, value: unknown}[]; /** + * Enumerates the keys for all values that changed to null. + * @return The keys for the values that changed to null. + */ + enumerateKeysForValuesChangedToNull(): string[]; + /** * Creates a subscription to any data change happening. * @param callback Callback to be invoked on data changes where the first parameter is the data key, the second the previous value and the third the new value. * @param subscriptionId The subscription Id to use. diff --git a/abstractions/typescript/src/store/backingStoreFactory.ts b/abstractions/typescript/src/store/backingStoreFactory.ts new file mode 100644 index 0000000000..5803451d85 --- /dev/null +++ b/abstractions/typescript/src/store/backingStoreFactory.ts @@ -0,0 +1,10 @@ +import { BackingStore } from "./backingStore"; + +/** Defines the contract for a factory that creates backing stores. */ +export interface BackingStoreFactory { + /** + * Creates a new instance of the backing store. + * @return a new instance of the backing store. + */ + createBackingStore(): BackingStore; +} \ No newline at end of file diff --git a/abstractions/typescript/src/store/backingStoreFactorySingleton.ts b/abstractions/typescript/src/store/backingStoreFactorySingleton.ts new file mode 100644 index 0000000000..68829335e8 --- /dev/null +++ b/abstractions/typescript/src/store/backingStoreFactorySingleton.ts @@ -0,0 +1,6 @@ +import { BackingStoreFactory } from "./backingStoreFactory"; +import { InMemoryBackingStoreFactory } from "./inMemoryBackingStoreFactory"; + +export class BackingStoreFactorySingleton { + public static instance: BackingStoreFactory = new InMemoryBackingStoreFactory(); +} \ No newline at end of file diff --git a/abstractions/typescript/src/store/backingStoreSerializationWriterProxyFactory.ts b/abstractions/typescript/src/store/backingStoreSerializationWriterProxyFactory.ts index e488bcc435..41d54f7576 100644 --- a/abstractions/typescript/src/store/backingStoreSerializationWriterProxyFactory.ts +++ b/abstractions/typescript/src/store/backingStoreSerializationWriterProxyFactory.ts @@ -20,6 +20,14 @@ export class BackingStoreSerializationWriterProxyFactory extends SerializationWr backedModel.backingStore.returnOnlyChangedValues = false; backedModel.backingStore.initializationCompleted = true; } + }, + (value, writer) => { + const backedModel = value as unknown as BackedModel; + if(backedModel && backedModel.backingStore) { + const keys = backedModel.backingStore.enumerateKeysForValuesChangedToNull(); + for(const key of keys) + writer.writeNullValue(key); + } }); } } \ No newline at end of file diff --git a/abstractions/typescript/src/store/inMemoryBackingStore.ts b/abstractions/typescript/src/store/inMemoryBackingStore.ts index 9a728be265..d179b47b86 100644 --- a/abstractions/typescript/src/store/inMemoryBackingStore.ts +++ b/abstractions/typescript/src/store/inMemoryBackingStore.ts @@ -39,6 +39,15 @@ export class InMemoryBackingStore implements BackingStore { this.store, (_, k) => { return { key: k, value: this.store.get(k)?.value}}); } + public enumerateKeysForValuesChangedToNull(): string[] { + const keys: string[] = []; + for(const [key, entry] of this.store) { + if(entry.changed && !entry.value) { + keys.push(key); + } + } + return keys; + } public subscribe(callback: subscriptionCallback, subscriptionId?: string | undefined): string { if(!callback) throw new Error("callback cannot be undefined"); diff --git a/abstractions/typescript/src/store/inMemoryBackingStoreFactory.ts b/abstractions/typescript/src/store/inMemoryBackingStoreFactory.ts new file mode 100644 index 0000000000..9ff53d26be --- /dev/null +++ b/abstractions/typescript/src/store/inMemoryBackingStoreFactory.ts @@ -0,0 +1,10 @@ +import { BackingStore } from "./backingStore"; +import { BackingStoreFactory } from "./backingStoreFactory"; +import { InMemoryBackingStore } from "./inMemoryBackingStore"; + +/** This class is used to create instances of InMemoryBackingStore */ +export class InMemoryBackingStoreFactory implements BackingStoreFactory { + public createBackingStore(): BackingStore { + return new InMemoryBackingStore(); + } +} \ No newline at end of file diff --git a/abstractions/typescript/src/store/index.ts b/abstractions/typescript/src/store/index.ts index cd14f9cb8c..6d26bb0eda 100644 --- a/abstractions/typescript/src/store/index.ts +++ b/abstractions/typescript/src/store/index.ts @@ -1,5 +1,8 @@ export * from './backedModel'; export * from './backingStore'; +export * from './backingStoreFactory'; +export * from './backingStoreFactorySingleton'; export * from './backingStoreParseNodeFactory'; export * from './backingStoreSerializationWriterProxyFactory'; -export * from './inMemoryBackingStore'; \ No newline at end of file +export * from './inMemoryBackingStore'; +export * from './inMemoryBackingStoreFactory'; \ No newline at end of file diff --git a/http/dotnet/httpclient/src/HttpCore.cs b/http/dotnet/httpclient/src/HttpCore.cs index d8b2300579..d61871737b 100644 --- a/http/dotnet/httpclient/src/HttpCore.cs +++ b/http/dotnet/httpclient/src/HttpCore.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.Kiota.Abstractions; using Microsoft.Kiota.Abstractions.Serialization; +using Microsoft.Kiota.Abstractions.Store; namespace Microsoft.Kiota.Http.HttpClient { @@ -151,12 +152,11 @@ private HttpRequestMessage GetRequestMessageFromRequestInfo(RequestInfo requestI } return message; } - /// - /// Enables the backing store for the registered serialization and parse node factories - /// - public void EnableBackingStore() { + public void EnableBackingStore(IBackingStoreFactory backingStoreFactory) { pNodeFactory = ApiClientBuilder.EnableBackingStoreForParseNodeFactory(pNodeFactory) ?? throw new InvalidOperationException("Could not enable backing store for the parse node factory"); sWriterFactory = ApiClientBuilder.EnableBackingStoreForSerializationWriterFactory(sWriterFactory) ?? throw new InvalidOperationException("Could not enable backing store for the serializer writer factory"); + if(backingStoreFactory != null) + BackingStoreFactorySingleton.Instance = backingStoreFactory; } public void Dispose() { if(createdClient) diff --git a/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj b/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj index 24e945e100..d13aec45a9 100644 --- a/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj +++ b/http/dotnet/httpclient/src/Microsoft.Kiota.Http.HttpClient.csproj @@ -4,11 +4,11 @@ net5.0 true https://github.com/microsoft/kiota - 1.0.5 + 1.0.6 - + diff --git a/http/java/okhttp/lib/build.gradle b/http/java/okhttp/lib/build.gradle index 5456401adc..cfbaa03958 100644 --- a/http/java/okhttp/lib/build.gradle +++ b/http/java/okhttp/lib/build.gradle @@ -36,7 +36,7 @@ dependencies { // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:30.1.1-jre' api 'com.squareup.okhttp3:okhttp:4.9.1' - api 'com.microsoft.kiota:kiota-abstractions:1.0.15' + api 'com.microsoft.kiota:kiota-abstractions:1.0.16' } publishing { @@ -53,7 +53,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-http-okhttp' - version '1.0.5' + version '1.0.6' from(components.java) } } diff --git a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/HttpCore.java b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/HttpCore.java index 820c280820..405f036864 100644 --- a/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/HttpCore.java +++ b/http/java/okhttp/lib/src/main/java/com/microsoft/kiota/http/HttpCore.java @@ -24,6 +24,8 @@ import com.microsoft.kiota.serialization.ParseNodeFactory; import com.microsoft.kiota.serialization.SerializationWriterFactory; import com.microsoft.kiota.serialization.SerializationWriterFactoryRegistry; +import com.microsoft.kiota.store.BackingStoreFactory; +import com.microsoft.kiota.store.BackingStoreFactorySingleton; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -72,9 +74,12 @@ public HttpCore(@Nonnull final AuthenticationProvider authenticationProvider, @N public SerializationWriterFactory getSerializationWriterFactory() { return sWriterFactory; } - public void enableBackingStore() { + public void enableBackingStore(@Nullable final BackingStoreFactory backingStoreFactory) { this.pNodeFactory = Objects.requireNonNull(ApiClientBuilder.enableBackingStoreForParseNodeFactory(pNodeFactory)); this.sWriterFactory = Objects.requireNonNull(ApiClientBuilder.enableBackingStoreForSerializationWriterFactory(sWriterFactory)); + if(backingStoreFactory != null) { + BackingStoreFactorySingleton.instance = backingStoreFactory; + } } @Nonnull public CompletableFuture> sendCollectionAsync(@Nonnull final RequestInfo requestInfo, @Nonnull final Class targetClass, @Nullable final ResponseHandler responseHandler) { diff --git a/http/typescript/fetch/package-lock.json b/http/typescript/fetch/package-lock.json index 2ce53d98b3..e0f6e3a690 100644 --- a/http/typescript/fetch/package-lock.json +++ b/http/typescript/fetch/package-lock.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-http-fetch", - "version": "1.0.5", + "version": "1.0.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/http/typescript/fetch/package.json b/http/typescript/fetch/package.json index 6b8fc1724c..c979296bb2 100644 --- a/http/typescript/fetch/package.json +++ b/http/typescript/fetch/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-http-fetch", - "version": "1.0.5", + "version": "1.0.6", "description": "Kiota HttpCore implementation with fetch", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -29,7 +29,7 @@ "registry": "https://npm.pkg.github.com" }, "dependencies": { - "@microsoft/kiota-abstractions": "^1.0.15", + "@microsoft/kiota-abstractions": "^1.0.16", "cross-fetch": "^3.1.4", "web-streams-polyfill": "^3.1.0" }, diff --git a/http/typescript/fetch/src/httpCore.ts b/http/typescript/fetch/src/httpCore.ts index 44db699ec4..8108b00ed9 100644 --- a/http/typescript/fetch/src/httpCore.ts +++ b/http/typescript/fetch/src/httpCore.ts @@ -1,4 +1,4 @@ -import { AuthenticationProvider, HttpCore as IHttpCore, Parsable, ParseNodeFactory, RequestInfo, ResponseHandler, ParseNodeFactoryRegistry, enableBackingStoreForParseNodeFactory, SerializationWriterFactoryRegistry, enableBackingStoreForSerializationWriterFactory, SerializationWriterFactory } from '@microsoft/kiota-abstractions'; +import { AuthenticationProvider, BackingStoreFactory, BackingStoreFactorySingleton, HttpCore as IHttpCore, Parsable, ParseNodeFactory, RequestInfo, ResponseHandler, ParseNodeFactoryRegistry, enableBackingStoreForParseNodeFactory, SerializationWriterFactoryRegistry, enableBackingStoreForSerializationWriterFactory, SerializationWriterFactory } from '@microsoft/kiota-abstractions'; import { Headers as FetchHeadersCtor } from 'cross-fetch'; import { ReadableStream } from 'web-streams-polyfill'; import { URLSearchParams } from 'url'; @@ -138,11 +138,14 @@ export class HttpCore implements IHttpCore { return await responseHandler.handleResponseAsync(response); } } - public enableBackingStore = (): void => { + public enableBackingStore = (backingStoreFactory?: BackingStoreFactory | undefined): void => { this.parseNodeFactory = enableBackingStoreForParseNodeFactory(this.parseNodeFactory); this.serializationWriterFactory = enableBackingStoreForSerializationWriterFactory(this.serializationWriterFactory); if(!this.serializationWriterFactory || !this.parseNodeFactory) throw new Error("unable to enable backing store"); + if(backingStoreFactory) { + BackingStoreFactorySingleton.instance = backingStoreFactory; + } } private addBearerIfNotPresent = async (requestInfo: RequestInfo): Promise => { if(!requestInfo.URI) { diff --git a/serialization/dotnet/json/src/JsonSerializationWriter.cs b/serialization/dotnet/json/src/JsonSerializationWriter.cs index 406b9a29ee..3db1ac5418 100644 --- a/serialization/dotnet/json/src/JsonSerializationWriter.cs +++ b/serialization/dotnet/json/src/JsonSerializationWriter.cs @@ -17,6 +17,7 @@ public JsonSerializationWriter() } public Action OnBeforeObjectSerialization { get; set; } public Action OnAfterObjectSerialization { get; set; } + public Action OnStartObjectSerialization { get; set; } public Stream GetSerializedContent() { writer.Flush(); stream.Position = 0; @@ -52,6 +53,10 @@ public void WriteDateTimeOffsetValue(string key, DateTimeOffset? value) { if(!string.IsNullOrEmpty(key) && value.HasValue) writer.WritePropertyName(key); if(value.HasValue) writer.WriteStringValue(value.Value); } + public void WriteNullValue(string key) { + if(!string.IsNullOrEmpty(key)) writer.WritePropertyName(key); + writer.WriteNullValue(); + } public void WriteEnumValue(string key, T? value) where T : struct, Enum { if(!string.IsNullOrEmpty(key) && value.HasValue) writer.WritePropertyName(key); if(value.HasValue) { @@ -87,6 +92,7 @@ public void WriteObjectValue(string key, T value) where T : IParsable { if(!string.IsNullOrEmpty(key)) writer.WritePropertyName(key); OnBeforeObjectSerialization?.Invoke(value); writer.WriteStartObject(); + OnStartObjectSerialization?.Invoke(value, this); value.Serialize(this); writer.WriteEndObject(); OnAfterObjectSerialization?.Invoke(value); @@ -142,6 +148,9 @@ private void WriteAnyValue(string key, T value) { case object o: WriteNonParsableObjectValue(key, o); // should we support parsables here too? break; + case null: + WriteNullValue(key); + break; default: throw new InvalidOperationException($"error serialization additional data value with key {key}, unknown type {value?.GetType()}"); } diff --git a/serialization/dotnet/json/src/Microsoft.Kiota.Serialization.Json.csproj b/serialization/dotnet/json/src/Microsoft.Kiota.Serialization.Json.csproj index 0578b14135..b6f470952f 100644 --- a/serialization/dotnet/json/src/Microsoft.Kiota.Serialization.Json.csproj +++ b/serialization/dotnet/json/src/Microsoft.Kiota.Serialization.Json.csproj @@ -4,11 +4,11 @@ net5.0 true https://github.com/microsoft/kiota - 1.0.3 + 1.0.4 - + diff --git a/serialization/java/json/lib/build.gradle b/serialization/java/json/lib/build.gradle index 074a29bf5e..8cd7574bf9 100644 --- a/serialization/java/json/lib/build.gradle +++ b/serialization/java/json/lib/build.gradle @@ -36,7 +36,7 @@ dependencies { // This dependency is used internally, and not exposed to consumers on their own compile classpath. implementation 'com.google.guava:guava:30.1.1-jre' api 'com.google.code.gson:gson:2.8.6' - api 'com.microsoft.kiota:kiota-abstractions:1.0.13' + api 'com.microsoft.kiota:kiota-abstractions:1.0.16' } publishing { @@ -53,7 +53,7 @@ publishing { publications { gpr(MavenPublication) { artifactId 'kiota-serialization-json' - version '1.0.3' + version '1.0.16' from(components.java) } } diff --git a/serialization/java/json/lib/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriter.java b/serialization/java/json/lib/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriter.java index f8be9f5180..c471727951 100644 --- a/serialization/java/json/lib/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriter.java +++ b/serialization/java/json/lib/src/main/java/com/microsoft/kiota/serialization/JsonSerializationWriter.java @@ -22,6 +22,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; +import java.util.function.BiConsumer; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -153,6 +154,9 @@ public void writeObjectValue(final String key, final T valu onBeforeObjectSerialization.accept(value); } writer.beginObject(); + if(onStartObjectSerialization != null) { + onStartObjectSerialization.accept(value, this); + } value.serialize(this); writer.endObject(); if(onAfterObjectSerialization != null) { @@ -176,6 +180,16 @@ public > void writeEnumValue(@Nullable final String key, @Null this.writeStringValue(key, getStringValueFromValuedEnum(value)); } } + public void writeNullValue(@Nullable final String key) { + try { + if(key != null && !key.isEmpty()) { + writer.name(key); + } + writer.nullValue(); + } catch (IOException ex) { + throw new RuntimeException("could not serialize value", ex); + } + } private > String getStringValueFromValuedEnum(final T value) { if(value instanceof ValuedEnum) { final ValuedEnum valued = (ValuedEnum)value; @@ -242,6 +256,8 @@ else if(value instanceof Iterable) this.writeCollectionOfPrimitiveValues(key, (Iterable)value); else if(!valueClass.isPrimitive()) this.writeNonParsableObject(key, value); + else if(value == null) + this.writeNullValue(key); else throw new RuntimeException("unknown type to serialize " + valueClass.getName()); } @@ -255,6 +271,9 @@ public Consumer getOnBeforeObjectSerialization() { public Consumer getOnAfterObjectSerialization() { return this.onAfterObjectSerialization; } + public BiConsumer getOnStartObjectSerialization() { + return this.onStartObjectSerialization; + } private Consumer onBeforeObjectSerialization; public void setOnBeforeObjectSerialization(final Consumer value) { this.onBeforeObjectSerialization = value; @@ -263,4 +282,8 @@ public void setOnBeforeObjectSerialization(final Consumer value) { public void setOnAfterObjectSerialization(final Consumer value) { this.onAfterObjectSerialization = value; } + private BiConsumer onStartObjectSerialization; + public void setOnStartObjectSerialization(final BiConsumer value) { + this.onStartObjectSerialization = value; + } } diff --git a/serialization/typescript/json/package-lock.json b/serialization/typescript/json/package-lock.json index 4b2b95148d..119c84c6c7 100644 --- a/serialization/typescript/json/package-lock.json +++ b/serialization/typescript/json/package-lock.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-serialization-json", - "version": "1.0.3", + "version": "1.0.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/serialization/typescript/json/package.json b/serialization/typescript/json/package.json index 178d1b93c2..60bda8c0c2 100644 --- a/serialization/typescript/json/package.json +++ b/serialization/typescript/json/package.json @@ -1,6 +1,6 @@ { "name": "@microsoft/kiota-serialization-json", - "version": "1.0.3", + "version": "1.0.4", "description": "Implementation of Kiota Serialization interfaces for JSON", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -34,7 +34,7 @@ "registry": "https://npm.pkg.github.com" }, "dependencies": { - "@microsoft/kiota-abstractions": "^1.0.13", + "@microsoft/kiota-abstractions": "^1.0.16", "web-streams-polyfill": "^3.1.0" } } diff --git a/serialization/typescript/json/src/jsonSerializationWriter.ts b/serialization/typescript/json/src/jsonSerializationWriter.ts index 3fffebfb9d..36a16a24d3 100644 --- a/serialization/typescript/json/src/jsonSerializationWriter.ts +++ b/serialization/typescript/json/src/jsonSerializationWriter.ts @@ -7,6 +7,7 @@ export class JsonSerializationWriter implements SerializationWriter { private static propertySeparator = `,`; public onBeforeObjectSerialization: ((value: Parsable) => void) | undefined; public onAfterObjectSerialization: ((value: Parsable) => void) | undefined; + public onStartObjectSerialization: ((value: Parsable, writer: SerializationWriter) => void) | undefined; public writeStringValue = (key?: string, value?: string): void => { key && value && this.writePropertyName(key); value && this.writer.push(`"${value}"`); @@ -35,6 +36,11 @@ export class JsonSerializationWriter implements SerializationWriter { value && this.writer.push(`"${value.toISOString()}"`); key && value && this.writer.push(JsonSerializationWriter.propertySeparator); } + public writeNullValue = (key?: string): void => { + key && this.writePropertyName(key); + this.writer.push(`null`); + key && this.writer.push(JsonSerializationWriter.propertySeparator); + } public writeCollectionOfPrimitiveValues = (key?: string, values?: T[]): void => { if(values) { key && this.writePropertyName(key); @@ -67,8 +73,9 @@ export class JsonSerializationWriter implements SerializationWriter { if(key) { this.writePropertyName(key); } - this.writer.push(`{`); this.onBeforeObjectSerialization && this.onBeforeObjectSerialization(value); + this.writer.push(`{`); + this.onStartObjectSerialization && this.onStartObjectSerialization(value, this); value.serialize(this); this.onAfterObjectSerialization && this.onAfterObjectSerialization(value); if(this.writer.length > 0 && this.writer[this.writer.length - 1] === JsonSerializationWriter.propertySeparator) { //removing the last separator @@ -110,7 +117,9 @@ export class JsonSerializationWriter implements SerializationWriter { private writeAnyValue = (key?: string | undefined, value?: unknown | undefined) : void => { if(value) { const valueType = typeof value; - if(valueType === "boolean") { + if(!value) { + this.writeNullValue(key); + }else if(valueType === "boolean") { this.writeBooleanValue(key, value as any as boolean); } else if (valueType === "string") { this.writeStringValue(key, value as any as string); diff --git a/src/Kiota.Builder/CodeDOM/CodeParameter.cs b/src/Kiota.Builder/CodeDOM/CodeParameter.cs index be3f1480fa..a5bcf9a394 100644 --- a/src/Kiota.Builder/CodeDOM/CodeParameter.cs +++ b/src/Kiota.Builder/CodeDOM/CodeParameter.cs @@ -14,7 +14,8 @@ public enum CodeParameterKind HttpCore, CurrentPath, Options, - Serializer + Serializer, + BackingStore } public class CodeParameter : CodeTerminal, ICloneable, IDocumentedElement diff --git a/src/Kiota.Builder/GenerationConfiguration.cs b/src/Kiota.Builder/GenerationConfiguration.cs index 4faefe532d..4f05dd3dbf 100644 --- a/src/Kiota.Builder/GenerationConfiguration.cs +++ b/src/Kiota.Builder/GenerationConfiguration.cs @@ -10,11 +10,8 @@ public class GenerationConfiguration { public string ApiRootUrl { get; set; } = "https://graph.microsoft.com/v1.0"; public List PropertiesPrefixToStrip { get; set; } = new() { "@odata."}; public HashSet IgnoredRequestContentTypes { get; set; } = new(); - public string BackingStore { get; set; } = string.Empty; + public bool UsesBackingStore { get; set; } public List Serializers { get; set; } = new(); - public bool UsesBackingStore { - get => !string.IsNullOrEmpty(BackingStore); - } public List Deserializers { get; set; } = new(); } } diff --git a/src/Kiota.Builder/KiotaBuilder.cs b/src/Kiota.Builder/KiotaBuilder.cs index 19f1b1e642..45d1c37ece 100644 --- a/src/Kiota.Builder/KiotaBuilder.cs +++ b/src/Kiota.Builder/KiotaBuilder.cs @@ -199,7 +199,7 @@ public void ApplyLanguageRefinement(GenerationConfiguration config, CodeNamespac public async Task CreateLanguageSourceFilesAsync(GenerationLanguage language, CodeNamespace generatedCode) { - var languageWriter = LanguageWriter.GetLanguageWriter(language, this.config.OutputPath, this.config.ClientNamespaceName, this.config.UsesBackingStore); + var languageWriter = LanguageWriter.GetLanguageWriter(language, this.config.OutputPath, this.config.ClientNamespaceName); var stopwatch = new Stopwatch(); stopwatch.Start(); var shouldWriteNamespaceIndices = language == GenerationLanguage.Ruby; @@ -349,6 +349,35 @@ private void CreatePathManagement(CodeClass currentClass, OpenApiUrlTreeNode cur Description = httpCoreProperty.Description, ParameterKind = CodeParameterKind.HttpCore, }); + if(isApiClientClass && config.UsesBackingStore) { + var backingStoreParam = new CodeParameter(constructor) { + Name = "backingStore", + Optional = true, + Description = "The backing store to use for the models.", + ParameterKind = CodeParameterKind.BackingStore, + }; + var factoryInterfaceName = $"{backingStoreInterface}Factory"; + backingStoreParam.Type = new CodeType(backingStoreParam) { + Name = factoryInterfaceName, + IsNullable = true, + }; + constructor.AddParameter(backingStoreParam); + var backingStoreInterfaceUsing = new CodeUsing(currentClass) { + Name = factoryInterfaceName, + }; + backingStoreInterfaceUsing.Declaration = new CodeType(backingStoreInterfaceUsing) { + Name = storeNamespaceName, + IsExternal = true, + }; + var backingStoreSingletonUsing = new CodeUsing(currentClass) { + Name = backingStoreSingleton, + }; + backingStoreSingletonUsing.Declaration = new CodeType(backingStoreSingletonUsing) { + Name = storeNamespaceName, + IsExternal = true, + }; + currentClass.AddUsing(backingStoreInterfaceUsing, backingStoreSingletonUsing); + } } private static Func shortestNamespaceOrder = (x) => x.Parent.Name.Split('.').Length; /// @@ -751,6 +780,7 @@ private void CreatePropertiesForModelClass(OpenApiUrlTreeNode currentNode, OpenA private const string additionalDataPropName = "AdditionalData"; private const string backingStorePropertyName = "BackingStore"; private const string backingStoreInterface = "IBackingStore"; + private const string backingStoreSingleton = "BackingStoreFactorySingleton"; private const string backedModelInterface = "IBackedModel"; private const string storeNamespaceName = "Microsoft.Kiota.Abstractions.Store"; private void AddSerializationMembers(CodeClass model, bool includeAdditionalProperties) { @@ -809,12 +839,10 @@ private void AddSerializationMembers(CodeClass model, bool includeAdditionalProp if(!model.ContainsMember(backingStorePropertyName) && config.UsesBackingStore && !(model.GetGreatestGrandparent(model)?.ContainsMember(backingStorePropertyName) ?? false)) { - var storeImplFragments = config.BackingStore.Split('.'); - var storeImplClassName = storeImplFragments.Last(); var backingStoreProperty = new CodeProperty(model) { Name = backingStorePropertyName, Access = AccessModifier.Public, - DefaultValue = $"new {storeImplClassName}()", + DefaultValue = $"BackingStoreFactorySingleton.Instance.CreateBackingStore()", PropertyKind = CodePropertyKind.BackingStore, Description = "Stores model information.", ReadOnly = true, @@ -841,10 +869,10 @@ private void AddSerializationMembers(CodeClass model, bool includeAdditionalProp IsExternal = true }; var storeImplUsing = new CodeUsing(model) { - Name = storeImplClassName, + Name = backingStoreSingleton, }; storeImplUsing.Declaration = new CodeType(storeImplUsing) { - Name = storeImplFragments.SkipLast(1).Aggregate((x, y) => $"{x}.{y}"), + Name = storeNamespaceName, IsExternal = true, }; model.AddUsing(backingStoreUsing, backedModelUsing, storeImplUsing); diff --git a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs index 77652803ef..3712d7ebbc 100644 --- a/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs +++ b/src/Kiota.Builder/Refiners/CommonLanguageRefiner.cs @@ -61,20 +61,36 @@ private static bool ReplaceSerializationModules(CodeElement generatedCode, Func< } internal const string GetterPrefix = "get-"; internal const string SetterPrefix = "set-"; - protected static void CorrectCoreTypesForBackingStoreUsings(CodeElement currentElement, string storeNamespace) { - if(currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model) + protected static void CorrectCoreTypesForBackingStore(CodeElement currentElement, string storeNamespace, string defaultPropertyValue) { + if(currentElement is CodeClass currentClass && currentClass.IsOfKind(CodeClassKind.Model, CodeClassKind.RequestBuilder) && currentClass.StartBlock is CodeClass.Declaration currentDeclaration) { - foreach(var backingStoreUsing in currentDeclaration.Usings.Where(x => "Microsoft.Kiota.Abstractions.Store".Equals(x.Declaration.Name, StringComparison.OrdinalIgnoreCase))) { - if(backingStoreUsing?.Declaration != null) { - backingStoreUsing.Name = backingStoreUsing.Name[1..]; // removing the "I" - backingStoreUsing.Declaration.Name = storeNamespace; - } - } + CorrectCoreTypeForBackingStoreUsings(currentDeclaration, storeNamespace, defaultPropertyValue); var backedModelImplements = currentDeclaration.Implements.FirstOrDefault(x => "IBackedModel".Equals(x.Name, StringComparison.OrdinalIgnoreCase)); if(backedModelImplements != null) backedModelImplements.Name = backedModelImplements.Name[1..]; //removing the "I" + var backingStoreProperty = currentClass.GetChildElements(true).OfType().FirstOrDefault(x => x.IsOfKind(CodePropertyKind.BackingStore)); + if(backingStoreProperty != null) + backingStoreProperty.DefaultValue = defaultPropertyValue; + + } + CrawlTree(currentElement, (x) => CorrectCoreTypesForBackingStore(x, storeNamespace, defaultPropertyValue)); + } + private static void CorrectCoreTypeForBackingStoreUsings(CodeClass.Declaration currentDeclaration, string storeNamespace, string defaultPropertyValue) { + foreach(var backingStoreUsing in currentDeclaration.Usings.Where(x => "Microsoft.Kiota.Abstractions.Store".Equals(x.Declaration.Name, StringComparison.OrdinalIgnoreCase))) { + if(backingStoreUsing?.Declaration != null) { + if(backingStoreUsing.Name.StartsWith("I")) + backingStoreUsing.Name = backingStoreUsing.Name[1..]; // removing the "I" + backingStoreUsing.Declaration.Name = storeNamespace; + } } - CrawlTree(currentElement, (x) => CorrectCoreTypesForBackingStoreUsings(x, storeNamespace)); + var defaultValueUsing = currentDeclaration + .Usings + .FirstOrDefault(x => "BackingStoreFactorySingleton".Equals(x.Name, StringComparison.OrdinalIgnoreCase) && + x.Declaration != null && + x.Declaration.IsExternal && + x.Declaration.Name.Equals(storeNamespace, StringComparison.OrdinalIgnoreCase)); + if(defaultValueUsing != null) + defaultValueUsing.Name = defaultPropertyValue.Split('.').First(); } private static bool DoesAnyParentHaveAPropertyWithDefaultValue(CodeClass current) { if(current.StartBlock is CodeClass.Declaration currentDeclaration && diff --git a/src/Kiota.Builder/Refiners/JavaRefiner.cs b/src/Kiota.Builder/Refiners/JavaRefiner.cs index f5c15164b3..e6db38bbd9 100644 --- a/src/Kiota.Builder/Refiners/JavaRefiner.cs +++ b/src/Kiota.Builder/Refiners/JavaRefiner.cs @@ -30,7 +30,7 @@ public override void Refine(CodeNamespace generatedCode) CodePropertyKind.BackingStore, }, _configuration.UsesBackingStore, true); AddConstructorsForDefaultValues(generatedCode, true); - CorrectCoreTypesForBackingStoreUsings(generatedCode, "com.microsoft.kiota.store"); + CorrectCoreTypesForBackingStore(generatedCode, "com.microsoft.kiota.store", "BackingStoreFactorySingleton.instance.createBackingStore()"); ReplaceDefaultSerializationModules(generatedCode, "com.microsoft.kiota.serialization.JsonSerializationWriterFactory"); ReplaceDefaultDeserializationModules(generatedCode, "com.microsoft.kiota.serialization.JsonParseNodeFactory"); AddSerializationModulesImport(generatedCode); @@ -134,7 +134,7 @@ private static void CorrectMethodType(CodeMethod currentMethod) { currentMethod.Name = "getFieldDeserializers"; } else if(currentMethod.IsOfKind(CodeMethodKind.ClientConstructor)) - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore)) + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore, CodeParameterKind.BackingStore)) .Where(x => x.Type.Name.StartsWith("I", StringComparison.OrdinalIgnoreCase)) .ToList() .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I" diff --git a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs index 7f3f2016b9..e6572452bf 100644 --- a/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs +++ b/src/Kiota.Builder/Refiners/TypeScriptRefiner.cs @@ -11,7 +11,7 @@ public override void Refine(CodeNamespace generatedCode) AddDefaultImports(generatedCode, Array.Empty>(), defaultNamespacesForModels, defaultNamespacesForRequestBuilders, defaultSymbolsForApiClient); ReplaceIndexersByMethodsWithParameter(generatedCode, generatedCode, "ById"); CorrectCoreType(generatedCode, CorrectMethodType, CorrectPropertyType); - CorrectCoreTypesForBackingStoreUsings(generatedCode, "@microsoft/kiota-abstractions"); + CorrectCoreTypesForBackingStore(generatedCode, "@microsoft/kiota-abstractions", "BackingStoreFactorySingleton.instance.createBackingStore()"); FixReferencesToEntityType(generatedCode); AddPropertiesAndMethodTypesImports(generatedCode, true, true, true); AddParsableInheritanceForModelClasses(generatedCode); @@ -79,7 +79,7 @@ private static void CorrectMethodType(CodeMethod currentMethod) { else if(currentMethod.IsOfKind(CodeMethodKind.Deserializer)) currentMethod.ReturnType.Name = $"Map void>"; else if(currentMethod.IsOfKind(CodeMethodKind.ClientConstructor)) - currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore)) + currentMethod.Parameters.Where(x => x.IsOfKind(CodeParameterKind.HttpCore, CodeParameterKind.BackingStore)) .Where(x => x.Type.Name.StartsWith("I", StringComparison.InvariantCultureIgnoreCase)) .ToList() .ForEach(x => x.Type.Name = x.Type.Name[1..]); // removing the "I" diff --git a/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs b/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs index c04b16a08d..d37bc33762 100644 --- a/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CSharpWriter.cs @@ -2,7 +2,7 @@ { public class CSharpWriter : LanguageWriter { - public CSharpWriter(string rootPath, string clientNamespaceName, bool usesBackingStore) + public CSharpWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new CSharpPathSegmenter(rootPath, clientNamespaceName); var conventionService = new CSharpConventionService(); @@ -10,7 +10,7 @@ public CSharpWriter(string rootPath, string clientNamespaceName, bool usesBackin AddCodeElementWriter(new CodeClassEndWriter(conventionService)); AddCodeElementWriter(new CodeEnumWriter(conventionService)); AddCodeElementWriter(new CodeIndexerWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService, usesBackingStore)); + AddCodeElementWriter(new CodeMethodWriter(conventionService)); AddCodeElementWriter(new CodePropertyWriter(conventionService)); AddCodeElementWriter(new CodeTypeWriter(conventionService)); diff --git a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs index e201e7b767..67a4112772 100644 --- a/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs @@ -6,10 +6,7 @@ namespace Kiota.Builder.Writers.CSharp { public class CodeMethodWriter : BaseElementWriter { - private readonly bool _usesBackingStore; - public CodeMethodWriter(CSharpConventionService conventionService, bool usesBackingStore): base(conventionService) { - _usesBackingStore = usesBackingStore; - } + public CodeMethodWriter(CSharpConventionService conventionService): base(conventionService) { } public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) { if(codeElement == null) throw new ArgumentNullException(nameof(codeElement)); @@ -64,15 +61,16 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri writer.DecreaseIndent(); writer.WriteLine("}"); } - private void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { + private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { var httpCoreProperty = parentClass.GetChildElements(true).OfType().FirstOrDefault(x => x.IsOfKind(CodePropertyKind.HttpCore)); var httpCoreParameter = method.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.HttpCore)); + var backingStoreParameter = method.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.BackingStore)); var httpCorePropertyName = httpCoreProperty.Name.ToFirstCharacterUpperCase(); writer.WriteLine($"{httpCorePropertyName} = {httpCoreParameter.Name};"); WriteSerializationRegistration(method.SerializerModules, writer, "RegisterDefaultSerializer"); WriteSerializationRegistration(method.DeserializerModules, writer, "RegisterDefaultDeserializer"); - if(_usesBackingStore) - writer.WriteLine($"{httpCorePropertyName}.EnableBackingStore();"); + if(backingStoreParameter != null) + writer.WriteLine($"{httpCorePropertyName}.EnableBackingStore({backingStoreParameter.Name});"); } private static void WriteSerializationRegistration(List serializationClassNames, LanguageWriter writer, string methodName) { if(serializationClassNames != null) diff --git a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs index 8e7e32127e..76cdb30b68 100644 --- a/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/Java/CodeMethodWriter.cs @@ -7,10 +7,7 @@ namespace Kiota.Builder.Writers.Java { public class CodeMethodWriter : BaseElementWriter { - private readonly bool _usesBackingStore; - public CodeMethodWriter(JavaConventionService conventionService, bool usesBackingStore) : base(conventionService){ - _usesBackingStore = usesBackingStore; - } + public CodeMethodWriter(JavaConventionService conventionService) : base(conventionService){} public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) { if(codeElement == null) throw new ArgumentNullException(nameof(codeElement)); @@ -76,15 +73,16 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri writer.DecreaseIndent(); writer.WriteLine("}"); } - private void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { + private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { var httpCoreProperty = parentClass.GetChildElements(true).OfType().FirstOrDefault(x => x.IsOfKind(CodePropertyKind.HttpCore)); var httpCoreParameter = method.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.HttpCore)); + var backingStoreParameter = method.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.BackingStore)); var httpCorePropertyName = httpCoreProperty.Name.ToFirstCharacterLowerCase(); writer.WriteLine($"this.{httpCorePropertyName} = {httpCoreParameter.Name};"); WriteSerializationRegistration(method.SerializerModules, writer, "registerDefaultSerializer"); WriteSerializationRegistration(method.DeserializerModules, writer, "registerDefaultDeserializer"); - if(_usesBackingStore) - writer.WriteLine($"this.{httpCorePropertyName}.enableBackingStore();"); + if(backingStoreParameter != null) + writer.WriteLine($"this.{httpCorePropertyName}.enableBackingStore({backingStoreParameter.Name});"); } private static void WriteSerializationRegistration(List serializationModules, LanguageWriter writer, string methodName) { if(serializationModules != null) diff --git a/src/Kiota.Builder/Writers/Java/JavaWriter.cs b/src/Kiota.Builder/Writers/Java/JavaWriter.cs index e64d3b8ef7..b34b5d0441 100644 --- a/src/Kiota.Builder/Writers/Java/JavaWriter.cs +++ b/src/Kiota.Builder/Writers/Java/JavaWriter.cs @@ -2,14 +2,14 @@ namespace Kiota.Builder.Writers.Java { public class JavaWriter : LanguageWriter { - public JavaWriter(string rootPath, string clientNamespaceName, bool usesBackingStore) + public JavaWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new JavaPathSegmenter(rootPath, clientNamespaceName); var conventionService = new JavaConventionService(); AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); AddCodeElementWriter(new CodeClassEndWriter()); AddCodeElementWriter(new CodeEnumWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService, usesBackingStore)); + AddCodeElementWriter(new CodeMethodWriter(conventionService)); AddCodeElementWriter(new CodePropertyWriter(conventionService)); AddCodeElementWriter(new CodeTypeWriter(conventionService)); } diff --git a/src/Kiota.Builder/Writers/LanguageWriter.cs b/src/Kiota.Builder/Writers/LanguageWriter.cs index c54d922987..b87d20696a 100644 --- a/src/Kiota.Builder/Writers/LanguageWriter.cs +++ b/src/Kiota.Builder/Writers/LanguageWriter.cs @@ -110,12 +110,12 @@ protected void AddCodeElementWriter(ICodeElementWriter writer) where T: Co Writers.Add(typeof(T), writer); } private readonly Dictionary Writers = new(); // we have to type as object because dotnet doesn't have type capture i.e eq for `? extends CodeElement` - public static LanguageWriter GetLanguageWriter(GenerationLanguage language, string outputPath, string clientNamespaceName, bool usesBackingStore = false) { + public static LanguageWriter GetLanguageWriter(GenerationLanguage language, string outputPath, string clientNamespaceName) { return language switch { - GenerationLanguage.CSharp => new CSharpWriter(outputPath, clientNamespaceName, usesBackingStore), - GenerationLanguage.Java => new JavaWriter(outputPath, clientNamespaceName, usesBackingStore), - GenerationLanguage.TypeScript => new TypeScriptWriter(outputPath, clientNamespaceName, usesBackingStore), + GenerationLanguage.CSharp => new CSharpWriter(outputPath, clientNamespaceName), + GenerationLanguage.Java => new JavaWriter(outputPath, clientNamespaceName), + GenerationLanguage.TypeScript => new TypeScriptWriter(outputPath, clientNamespaceName), GenerationLanguage.Ruby => new RubyWriter(outputPath, clientNamespaceName), _ => throw new InvalidEnumArgumentException($"{language} language currently not supported."), }; diff --git a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs index 8b7d427be7..90c797d056 100644 --- a/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/CodeMethodWriter.cs @@ -7,11 +7,8 @@ namespace Kiota.Builder.Writers.TypeScript { public class CodeMethodWriter : BaseElementWriter { - public CodeMethodWriter(TypeScriptConventionService conventionService, bool usesBackingStore) : base(conventionService){ - _usesBackingStore = usesBackingStore; - } + public CodeMethodWriter(TypeScriptConventionService conventionService) : base(conventionService){} private TypeScriptConventionService localConventions; - private readonly bool _usesBackingStore; public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter writer) { if(codeElement == null) throw new ArgumentNullException(nameof(codeElement)); @@ -73,15 +70,16 @@ public override void WriteCodeElement(CodeMethod codeElement, LanguageWriter wri writer.DecreaseIndent(); writer.WriteLine("};"); } - private void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { + private static void WriteApiConstructorBody(CodeClass parentClass, CodeMethod method, LanguageWriter writer) { var httpCoreProperty = parentClass.GetChildElements(true).OfType().FirstOrDefault(x => x.IsOfKind(CodePropertyKind.HttpCore)); var httpCoreParameter = method.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.HttpCore)); + var backingStoreParameter = method.Parameters.FirstOrDefault(x => x.IsOfKind(CodeParameterKind.BackingStore)); var httpCorePropertyName = httpCoreProperty.Name.ToFirstCharacterLowerCase(); writer.WriteLine($"this.{httpCorePropertyName} = {httpCoreParameter.Name};"); WriteSerializationRegistration(method.SerializerModules, writer, "registerDefaultSerializer"); WriteSerializationRegistration(method.DeserializerModules, writer, "registerDefaultDeserializer"); - if(_usesBackingStore) - writer.WriteLine($"this.{httpCorePropertyName}.enableBackingStore();"); + if(backingStoreParameter != null) + writer.WriteLine($"this.{httpCorePropertyName}.enableBackingStore({backingStoreParameter.Name});"); } private static void WriteSerializationRegistration(List serializationModules, LanguageWriter writer, string methodName) { if(serializationModules != null) diff --git a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs index c922f659c3..6c4a0c68df 100644 --- a/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs +++ b/src/Kiota.Builder/Writers/TypeScript/TypeScriptWriter.cs @@ -2,14 +2,14 @@ namespace Kiota.Builder.Writers.TypeScript { public class TypeScriptWriter : LanguageWriter { - public TypeScriptWriter(string rootPath, string clientNamespaceName, bool usesBackingStore) + public TypeScriptWriter(string rootPath, string clientNamespaceName) { PathSegmenter = new TypeScriptPathSegmenter(rootPath,clientNamespaceName); var conventionService = new TypeScriptConventionService(null); AddCodeElementWriter(new CodeClassDeclarationWriter(conventionService)); AddCodeElementWriter(new CodeClassEndWriter()); AddCodeElementWriter(new CodeEnumWriter(conventionService)); - AddCodeElementWriter(new CodeMethodWriter(conventionService, usesBackingStore)); + AddCodeElementWriter(new CodeMethodWriter(conventionService)); AddCodeElementWriter(new CodePropertyWriter(conventionService)); AddCodeElementWriter(new CodeTypeWriter(conventionService)); } diff --git a/src/kiota/KiotaHost.cs b/src/kiota/KiotaHost.cs index d9c6ff54e6..3a898cd82f 100644 --- a/src/kiota/KiotaHost.cs +++ b/src/kiota/KiotaHost.cs @@ -36,7 +36,7 @@ public RootCommand GetRootCommand() var descriptionOption = new Option("--openapi", "The path to the OpenAPI description file used to generate the code files.") {Argument = new Argument(() => "openapi.yml")}; descriptionOption.AddAlias("-d"); - var backingStoreOption = new Option("--backing-store", "The fully qualified name for the backing store class to use.") {Argument = new Argument()}; + var backingStoreOption = new Option("--backing-store", "Enables backing store for models.") {Argument = new Argument()}; backingStoreOption.AddAlias("-b"); var serializerOption = new Option>("--serializer", "The fully qualified class names for serializers.") { Argument = new Argument>(() => new List {"Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory"}) }; @@ -63,13 +63,13 @@ private void AssignIfNotNullOrEmpty(string input, Action serializer, List deserializer); - private async Task HandleCommandCall(string output, GenerationLanguage? language, string openapi, string backingstore, string classname, LogLevel loglevel, string namespacename, List serializer, List deserializer) { + private delegate Task HandleCommandCallDel(string output, GenerationLanguage? language, string openapi, bool backingstore, string classname, LogLevel loglevel, string namespacename, List serializer, List deserializer); + private async Task HandleCommandCall(string output, GenerationLanguage? language, string openapi, bool backingstore, string classname, LogLevel loglevel, string namespacename, List serializer, List deserializer) { AssignIfNotNullOrEmpty(output, (c, s) => c.OutputPath = s); AssignIfNotNullOrEmpty(openapi, (c, s) => c.OpenAPIFilePath = s); AssignIfNotNullOrEmpty(classname, (c, s) => c.ClientClassName = s); AssignIfNotNullOrEmpty(namespacename, (c, s) => c.ClientNamespaceName = s); - AssignIfNotNullOrEmpty(backingstore, (c, s) => c.BackingStore = s.TrimQuotes()); //npm modules can start with @ which prompts some terminals to read response files and quotes are not automatically trimmed by the framework + Configuration.UsesBackingStore = backingstore; if (language.HasValue) Configuration.Language = language.Value; if(serializer?.Any() ?? false) diff --git a/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs b/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs index b8054b3f5e..8825307be2 100644 --- a/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs +++ b/tests/Kiota.Builder.IntegrationTests/GenerateSample.cs @@ -7,25 +7,25 @@ namespace Kiota.Builder.integrationtests { public class GenerateSample { - [InlineData(GenerationLanguage.CSharp, null)] - [InlineData(GenerationLanguage.Java, null)] - [InlineData(GenerationLanguage.TypeScript, null)] - [InlineData(GenerationLanguage.Ruby, null)] - [InlineData(GenerationLanguage.CSharp, "Microsoft.Kiota.Abstractions.Store.InMemoryBackingStore")] - [InlineData(GenerationLanguage.Java, "com.microsoft.kiota.store.InMemoryBackingStore")] - [InlineData(GenerationLanguage.TypeScript, "@microsoft/kiota-abstractions.InMemoryBackingStore")] + [InlineData(GenerationLanguage.CSharp, false)] + [InlineData(GenerationLanguage.Java, false)] + [InlineData(GenerationLanguage.TypeScript, false)] + [InlineData(GenerationLanguage.Ruby, false)] + [InlineData(GenerationLanguage.CSharp, true)] + [InlineData(GenerationLanguage.Java, true)] + [InlineData(GenerationLanguage.TypeScript, true)] [Theory] - public async Task GeneratesTodo(GenerationLanguage language, string backingStore) { + public async Task GeneratesTodo(GenerationLanguage language, bool backingStore) { var logger = LoggerFactory.Create((builder) => { }).CreateLogger(); - var backingStoreSuffix = string.IsNullOrEmpty(backingStore) ? string.Empty : "BackingStore"; + var backingStoreSuffix = backingStore ? string.Empty : "BackingStore"; var configuration = new GenerationConfiguration { Language = GenerationLanguage.CSharp, OpenAPIFilePath = "ToDoApi.yaml", OutputPath = $".\\Generated\\{language}{backingStoreSuffix}", - BackingStore = backingStore, + UsesBackingStore = backingStore, }; await new KiotaBuilder(logger, configuration).GenerateSDK(); } diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CSharpWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CSharpWriterTests.cs index 8a7feb6922..e8991130c5 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CSharpWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CSharpWriterTests.cs @@ -5,11 +5,11 @@ namespace Kiota.Builder.Writers.CSharp.Tests { public class CSharpWriterTests { [Fact] public void Instanciates() { - var writer = new CSharpWriter("./", "graph", false); + var writer = new CSharpWriter("./", "graph"); Assert.NotNull(writer); Assert.NotNull(writer.PathSegmenter); - Assert.Throws(() => new CSharpWriter(null, "graph", false)); - Assert.Throws(() => new CSharpWriter("./", null, false)); + Assert.Throws(() => new CSharpWriter(null, "graph")); + Assert.Throws(() => new CSharpWriter("./", null)); } } diff --git a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs index a7a2b1d79d..5e1f7ea401 100644 --- a/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/CSharp/CodeMethodWriterTests.cs @@ -259,7 +259,7 @@ public void WritesMethodSyncDescription() { } [Fact] public void Defensive() { - var codeMethodWriter = new CodeMethodWriter(new CSharpConventionService(), false); + var codeMethodWriter = new CodeMethodWriter(new CSharpConventionService()); Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); var originalParent = method.Parent; @@ -381,7 +381,16 @@ public void WritesApiConstructorWithBackingStore() { ParameterKind = CodeParameterKind.HttpCore, Type = coreProp.Type, }); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.CSharp, defaultPath, defaultName, true); + var backingStoreParam = new CodeParameter(method) { + Name = "backingStore", + ParameterKind = CodeParameterKind.BackingStore, + }; + backingStoreParam.Type = new CodeType(backingStoreParam) { + Name = "IBackingStore", + IsExternal = true, + }; + method.AddParameter(backingStoreParam); + var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.CSharp, defaultPath, defaultName); tempWriter.SetTextWriter(tw); tempWriter.Write(method); var result = tw.ToString(); diff --git a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs index cf2e59cfc7..8c557fa949 100644 --- a/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Java/CodeMethodWriterTests.cs @@ -298,7 +298,7 @@ public void WritesMethodSyncDescription() { } [Fact] public void Defensive() { - var codeMethodWriter = new CodeMethodWriter(new JavaConventionService(), false); + var codeMethodWriter = new CodeMethodWriter(new JavaConventionService()); Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); var originalParent = method.Parent; @@ -474,7 +474,16 @@ public void WritesApiConstructorWithBackingStore() { ParameterKind = CodeParameterKind.HttpCore, Type = coreProp.Type, }); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Java, defaultPath, defaultName, true); + var backingStoreParam = new CodeParameter(method) { + Name = "backingStore", + ParameterKind = CodeParameterKind.BackingStore, + }; + backingStoreParam.Type = new CodeType(backingStoreParam) { + Name = "BackingStore", + IsExternal = true, + }; + method.AddParameter(backingStoreParam); + var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Java, defaultPath, defaultName); tempWriter.SetTextWriter(tw); tempWriter.Write(method); var result = tw.ToString(); diff --git a/tests/Kiota.Builder.Tests/Writers/Java/JavaWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Java/JavaWriterTests.cs index caf7975a82..1ca160bac1 100644 --- a/tests/Kiota.Builder.Tests/Writers/Java/JavaWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Java/JavaWriterTests.cs @@ -5,11 +5,11 @@ namespace Kiota.Builder.Writers.Java.Tests { public class JavaWriterTests { [Fact] public void Instanciates() { - var writer = new JavaWriter("./", "graph", false); + var writer = new JavaWriter("./", "graph"); Assert.NotNull(writer); Assert.NotNull(writer.PathSegmenter); - Assert.Throws(() => new JavaWriter(null, "graph", false)); - Assert.Throws(() => new JavaWriter("./", null, false)); + Assert.Throws(() => new JavaWriter(null, "graph")); + Assert.Throws(() => new JavaWriter("./", null)); } } diff --git a/tests/Kiota.Builder.Tests/Writers/Ruby/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/Ruby/CodeMethodWriterTests.cs index 27be7f04e8..51e2e56648 100644 --- a/tests/Kiota.Builder.Tests/Writers/Ruby/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/Ruby/CodeMethodWriterTests.cs @@ -365,7 +365,16 @@ public void WritesApiConstructorWithBackingStore() { ParameterKind = CodeParameterKind.HttpCore, Type = coreProp.Type, }); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Java, defaultPath, defaultName, true); + var backingStoreParam = new CodeParameter(method) { + Name = "backingStore", + ParameterKind = CodeParameterKind.BackingStore, + }; + backingStoreParam.Type = new CodeType(backingStoreParam) { + Name = "BackingStore", + IsExternal = true, + }; + method.AddParameter(backingStoreParam); + var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.Java, defaultPath, defaultName); tempWriter.SetTextWriter(tw); tempWriter.Write(method); var result = tw.ToString(); diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs index 9329190d7c..a8a7cbcd20 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/CodeMethodWriterTests.cs @@ -268,7 +268,7 @@ public void WritesMethodSyncDescription() { } [Fact] public void Defensive() { - var codeMethodWriter = new CodeMethodWriter(new TypeScriptConventionService(writer), false); + var codeMethodWriter = new CodeMethodWriter(new TypeScriptConventionService(writer)); Assert.Throws(() => codeMethodWriter.WriteCodeElement(null, writer)); Assert.Throws(() => codeMethodWriter.WriteCodeElement(method, null)); var originalParent = method.Parent; @@ -456,7 +456,16 @@ public void WritesApiConstructorWithBackingStore() { ParameterKind = CodeParameterKind.HttpCore, Type = coreProp.Type, }); - var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, defaultPath, defaultName, true); + var backingStoreParam = new CodeParameter(method) { + Name = "backingStore", + ParameterKind = CodeParameterKind.BackingStore, + }; + backingStoreParam.Type = new CodeType(backingStoreParam) { + Name = "BackingStore", + IsExternal = true, + }; + method.AddParameter(backingStoreParam); + var tempWriter = LanguageWriter.GetLanguageWriter(GenerationLanguage.TypeScript, defaultPath, defaultName); tempWriter.SetTextWriter(tw); tempWriter.Write(method); var result = tw.ToString(); diff --git a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptWriterTests.cs b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptWriterTests.cs index bad45cefd0..ae01f3eb16 100644 --- a/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptWriterTests.cs +++ b/tests/Kiota.Builder.Tests/Writers/TypeScript/TypeScriptWriterTests.cs @@ -5,11 +5,11 @@ namespace Kiota.Builder.Writers.TypeScript.Tests { public class TypeScriptWriterTests { [Fact] public void Instanciates() { - var writer = new TypeScriptWriter("./", "graph", false); + var writer = new TypeScriptWriter("./", "graph"); Assert.NotNull(writer); Assert.NotNull(writer.PathSegmenter); - Assert.Throws(() => new TypeScriptWriter(null, "graph", false)); - Assert.Throws(() => new TypeScriptWriter("./", null, false)); + Assert.Throws(() => new TypeScriptWriter(null, "graph")); + Assert.Throws(() => new TypeScriptWriter("./", null)); } }