diff --git a/Cargo.lock b/Cargo.lock index e46b19359..24130209c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3694,6 +3694,7 @@ dependencies = [ "strum_macros 0.25.3", "superposition_derives", "superposition_types", + "toml 0.8.8", "url", "wasm-bindgen", "wasm-bindgen-futures", diff --git a/clients/haskell/sdk/Io/Superposition/Command/ImportConfigJson.hs b/clients/haskell/sdk/Io/Superposition/Command/ImportConfigJson.hs new file mode 100644 index 000000000..02556106c --- /dev/null +++ b/clients/haskell/sdk/Io/Superposition/Command/ImportConfigJson.hs @@ -0,0 +1,40 @@ +module Io.Superposition.Command.ImportConfigJson ( + ImportConfigJsonError (..), + importConfigJson +) where +import qualified Data.Aeson +import qualified Data.Maybe +import qualified Data.Text +import qualified GHC.Generics +import qualified GHC.Show +import qualified Io.Superposition.Model.ImportConfigJsonInput +import qualified Io.Superposition.Model.ImportConfigJsonOutput +import qualified Io.Superposition.Model.InternalServerError +import qualified Io.Superposition.SuperpositionClient +import qualified Io.Superposition.Utility + +data ImportConfigJsonError = + InternalServerError Io.Superposition.Model.InternalServerError.InternalServerError + | BuilderError Data.Text.Text + | DeSerializationError Io.Superposition.Utility.HttpMetadata Data.Text.Text + | UnexpectedError (Data.Maybe.Maybe Io.Superposition.Utility.HttpMetadata) Data.Text.Text + deriving (GHC.Generics.Generic, GHC.Show.Show) + +instance Data.Aeson.ToJSON ImportConfigJsonError +instance Io.Superposition.Utility.OperationError ImportConfigJsonError where + mkBuilderError = BuilderError + mkDeSerializationError = DeSerializationError + mkUnexpectedError = UnexpectedError + + getErrorParser status + | status == (Io.Superposition.Utility.expectedStatus @Io.Superposition.Model.InternalServerError.InternalServerError) = Just (fmap InternalServerError (Io.Superposition.Utility.responseParser @Io.Superposition.Model.InternalServerError.InternalServerError)) + | otherwise = Nothing + + +importConfigJson :: Io.Superposition.SuperpositionClient.SuperpositionClient -> Io.Superposition.Model.ImportConfigJsonInput.ImportConfigJsonInputBuilder () -> IO (Either ImportConfigJsonError Io.Superposition.Model.ImportConfigJsonOutput.ImportConfigJsonOutput) +importConfigJson client builder = + let endpoint = Io.Superposition.SuperpositionClient.endpointUri client + manager = Io.Superposition.SuperpositionClient.httpManager client + auth = Io.Superposition.SuperpositionClient.getAuth client + in Io.Superposition.Utility.runOperation endpoint manager auth (Io.Superposition.Model.ImportConfigJsonInput.build builder) + diff --git a/clients/haskell/sdk/Io/Superposition/Command/ImportConfigToml.hs b/clients/haskell/sdk/Io/Superposition/Command/ImportConfigToml.hs new file mode 100644 index 000000000..60c5174d2 --- /dev/null +++ b/clients/haskell/sdk/Io/Superposition/Command/ImportConfigToml.hs @@ -0,0 +1,40 @@ +module Io.Superposition.Command.ImportConfigToml ( + ImportConfigTomlError (..), + importConfigToml +) where +import qualified Data.Aeson +import qualified Data.Maybe +import qualified Data.Text +import qualified GHC.Generics +import qualified GHC.Show +import qualified Io.Superposition.Model.ImportConfigTomlInput +import qualified Io.Superposition.Model.ImportConfigTomlOutput +import qualified Io.Superposition.Model.InternalServerError +import qualified Io.Superposition.SuperpositionClient +import qualified Io.Superposition.Utility + +data ImportConfigTomlError = + InternalServerError Io.Superposition.Model.InternalServerError.InternalServerError + | BuilderError Data.Text.Text + | DeSerializationError Io.Superposition.Utility.HttpMetadata Data.Text.Text + | UnexpectedError (Data.Maybe.Maybe Io.Superposition.Utility.HttpMetadata) Data.Text.Text + deriving (GHC.Generics.Generic, GHC.Show.Show) + +instance Data.Aeson.ToJSON ImportConfigTomlError +instance Io.Superposition.Utility.OperationError ImportConfigTomlError where + mkBuilderError = BuilderError + mkDeSerializationError = DeSerializationError + mkUnexpectedError = UnexpectedError + + getErrorParser status + | status == (Io.Superposition.Utility.expectedStatus @Io.Superposition.Model.InternalServerError.InternalServerError) = Just (fmap InternalServerError (Io.Superposition.Utility.responseParser @Io.Superposition.Model.InternalServerError.InternalServerError)) + | otherwise = Nothing + + +importConfigToml :: Io.Superposition.SuperpositionClient.SuperpositionClient -> Io.Superposition.Model.ImportConfigTomlInput.ImportConfigTomlInputBuilder () -> IO (Either ImportConfigTomlError Io.Superposition.Model.ImportConfigTomlOutput.ImportConfigTomlOutput) +importConfigToml client builder = + let endpoint = Io.Superposition.SuperpositionClient.endpointUri client + manager = Io.Superposition.SuperpositionClient.httpManager client + auth = Io.Superposition.SuperpositionClient.getAuth client + in Io.Superposition.Utility.runOperation endpoint manager auth (Io.Superposition.Model.ImportConfigTomlInput.build builder) + diff --git a/clients/haskell/sdk/Io/Superposition/Model/ImportConfigJsonInput.hs b/clients/haskell/sdk/Io/Superposition/Model/ImportConfigJsonInput.hs new file mode 100644 index 000000000..54bcd13d6 --- /dev/null +++ b/clients/haskell/sdk/Io/Superposition/Model/ImportConfigJsonInput.hs @@ -0,0 +1,166 @@ +module Io.Superposition.Model.ImportConfigJsonInput ( + setWorkspaceId, + setOrgId, + setStrategy, + setOnError, + setDryRun, + setConfigTags, + setJsonConfig, + build, + ImportConfigJsonInputBuilder, + ImportConfigJsonInput, + workspace_id, + org_id, + strategy, + on_error, + dry_run, + config_tags, + json_config +) where +import qualified Control.Applicative +import qualified Control.Monad.State.Strict +import qualified Data.Aeson +import qualified Data.Either +import qualified Data.Eq +import qualified Data.Functor +import qualified Data.Maybe +import qualified Data.Text +import qualified GHC.Generics +import qualified GHC.Show +import qualified Io.Superposition.Model.ImportOnError +import qualified Io.Superposition.Model.ImportStrategy +import qualified Io.Superposition.Utility +import qualified Network.HTTP.Types.Method + +data ImportConfigJsonInput = ImportConfigJsonInput { + workspace_id :: Data.Text.Text, + org_id :: Data.Text.Text, + strategy :: Data.Maybe.Maybe Io.Superposition.Model.ImportStrategy.ImportStrategy, + on_error :: Data.Maybe.Maybe Io.Superposition.Model.ImportOnError.ImportOnError, + dry_run :: Data.Maybe.Maybe Bool, + config_tags :: Data.Maybe.Maybe Data.Text.Text, + json_config :: Data.Text.Text +} deriving ( + GHC.Show.Show, + Data.Eq.Eq, + GHC.Generics.Generic + ) + +instance Data.Aeson.ToJSON ImportConfigJsonInput where + toJSON a = Data.Aeson.object [ + "workspace_id" Data.Aeson..= workspace_id a, + "org_id" Data.Aeson..= org_id a, + "strategy" Data.Aeson..= strategy a, + "on_error" Data.Aeson..= on_error a, + "dry_run" Data.Aeson..= dry_run a, + "config_tags" Data.Aeson..= config_tags a, + "json_config" Data.Aeson..= json_config a + ] + + +instance Io.Superposition.Utility.SerializeBody ImportConfigJsonInput + +instance Data.Aeson.FromJSON ImportConfigJsonInput where + parseJSON = Data.Aeson.withObject "ImportConfigJsonInput" $ \v -> ImportConfigJsonInput + Data.Functor.<$> (v Data.Aeson..: "workspace_id") + Control.Applicative.<*> (v Data.Aeson..: "org_id") + Control.Applicative.<*> (v Data.Aeson..:? "strategy") + Control.Applicative.<*> (v Data.Aeson..:? "on_error") + Control.Applicative.<*> (v Data.Aeson..:? "dry_run") + Control.Applicative.<*> (v Data.Aeson..:? "config_tags") + Control.Applicative.<*> (v Data.Aeson..: "json_config") + + + + +data ImportConfigJsonInputBuilderState = ImportConfigJsonInputBuilderState { + workspace_idBuilderState :: Data.Maybe.Maybe Data.Text.Text, + org_idBuilderState :: Data.Maybe.Maybe Data.Text.Text, + strategyBuilderState :: Data.Maybe.Maybe Io.Superposition.Model.ImportStrategy.ImportStrategy, + on_errorBuilderState :: Data.Maybe.Maybe Io.Superposition.Model.ImportOnError.ImportOnError, + dry_runBuilderState :: Data.Maybe.Maybe Bool, + config_tagsBuilderState :: Data.Maybe.Maybe Data.Text.Text, + json_configBuilderState :: Data.Maybe.Maybe Data.Text.Text +} deriving ( + GHC.Generics.Generic + ) + +defaultBuilderState :: ImportConfigJsonInputBuilderState +defaultBuilderState = ImportConfigJsonInputBuilderState { + workspace_idBuilderState = Data.Maybe.Nothing, + org_idBuilderState = Data.Maybe.Nothing, + strategyBuilderState = Data.Maybe.Nothing, + on_errorBuilderState = Data.Maybe.Nothing, + dry_runBuilderState = Data.Maybe.Nothing, + config_tagsBuilderState = Data.Maybe.Nothing, + json_configBuilderState = Data.Maybe.Nothing +} + +type ImportConfigJsonInputBuilder = Control.Monad.State.Strict.State ImportConfigJsonInputBuilderState + +setWorkspaceId :: Data.Text.Text -> ImportConfigJsonInputBuilder () +setWorkspaceId value = + Control.Monad.State.Strict.modify (\s -> (s { workspace_idBuilderState = Data.Maybe.Just value })) + +setOrgId :: Data.Text.Text -> ImportConfigJsonInputBuilder () +setOrgId value = + Control.Monad.State.Strict.modify (\s -> (s { org_idBuilderState = Data.Maybe.Just value })) + +setStrategy :: Data.Maybe.Maybe Io.Superposition.Model.ImportStrategy.ImportStrategy -> ImportConfigJsonInputBuilder () +setStrategy value = + Control.Monad.State.Strict.modify (\s -> (s { strategyBuilderState = value })) + +setOnError :: Data.Maybe.Maybe Io.Superposition.Model.ImportOnError.ImportOnError -> ImportConfigJsonInputBuilder () +setOnError value = + Control.Monad.State.Strict.modify (\s -> (s { on_errorBuilderState = value })) + +setDryRun :: Data.Maybe.Maybe Bool -> ImportConfigJsonInputBuilder () +setDryRun value = + Control.Monad.State.Strict.modify (\s -> (s { dry_runBuilderState = value })) + +setConfigTags :: Data.Maybe.Maybe Data.Text.Text -> ImportConfigJsonInputBuilder () +setConfigTags value = + Control.Monad.State.Strict.modify (\s -> (s { config_tagsBuilderState = value })) + +setJsonConfig :: Data.Text.Text -> ImportConfigJsonInputBuilder () +setJsonConfig value = + Control.Monad.State.Strict.modify (\s -> (s { json_configBuilderState = Data.Maybe.Just value })) + +build :: ImportConfigJsonInputBuilder () -> Data.Either.Either Data.Text.Text ImportConfigJsonInput +build builder = do + let (_, st) = Control.Monad.State.Strict.runState builder defaultBuilderState + workspace_id' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigJsonInput.ImportConfigJsonInput.workspace_id is a required property.") Data.Either.Right (workspace_idBuilderState st) + org_id' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigJsonInput.ImportConfigJsonInput.org_id is a required property.") Data.Either.Right (org_idBuilderState st) + strategy' <- Data.Either.Right (strategyBuilderState st) + on_error' <- Data.Either.Right (on_errorBuilderState st) + dry_run' <- Data.Either.Right (dry_runBuilderState st) + config_tags' <- Data.Either.Right (config_tagsBuilderState st) + json_config' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigJsonInput.ImportConfigJsonInput.json_config is a required property.") Data.Either.Right (json_configBuilderState st) + Data.Either.Right (ImportConfigJsonInput { + workspace_id = workspace_id', + org_id = org_id', + strategy = strategy', + on_error = on_error', + dry_run = dry_run', + config_tags = config_tags', + json_config = json_config' + }) + + +instance Io.Superposition.Utility.IntoRequestBuilder ImportConfigJsonInput where + intoRequestBuilder self = do + Io.Superposition.Utility.setMethod Network.HTTP.Types.Method.methodPost + Io.Superposition.Utility.setPath [ + "config", + "json", + "import" + ] + + Io.Superposition.Utility.serHeader "x-workspace" (workspace_id self) + Io.Superposition.Utility.serHeader "x-org-id" (org_id self) + Io.Superposition.Utility.serHeader "x-import-on-error" (on_error self) + Io.Superposition.Utility.serHeader "x-import-dry-run" (dry_run self) + Io.Superposition.Utility.serHeader "x-import-strategy" (strategy self) + Io.Superposition.Utility.serHeader "x-config-tags" (config_tags self) + Io.Superposition.Utility.serBody "text/plain" (json_config self) + diff --git a/clients/haskell/sdk/Io/Superposition/Model/ImportConfigJsonOutput.hs b/clients/haskell/sdk/Io/Superposition/Model/ImportConfigJsonOutput.hs new file mode 100644 index 000000000..819b66dad --- /dev/null +++ b/clients/haskell/sdk/Io/Superposition/Model/ImportConfigJsonOutput.hs @@ -0,0 +1,154 @@ +module Io.Superposition.Model.ImportConfigJsonOutput ( + setStrategy, + setDryRun, + setConfigVersion, + setDimensions, + setDefaultConfigs, + setContexts, + build, + ImportConfigJsonOutputBuilder, + ImportConfigJsonOutput, + strategy, + dry_run, + config_version, + dimensions, + default_configs, + contexts +) where +import qualified Control.Applicative +import qualified Control.Monad.State.Strict +import qualified Data.Aeson +import qualified Data.Either +import qualified Data.Eq +import qualified Data.Functor +import qualified Data.Maybe +import qualified Data.Text +import qualified GHC.Generics +import qualified GHC.Show +import qualified Io.Superposition.Model.ImportEntityReport +import qualified Io.Superposition.Utility +import qualified Network.HTTP.Types + +data ImportConfigJsonOutput = ImportConfigJsonOutput { + strategy :: Data.Text.Text, + dry_run :: Bool, + config_version :: Data.Maybe.Maybe Data.Text.Text, + dimensions :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport, + default_configs :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport, + contexts :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport +} deriving ( + GHC.Show.Show, + Data.Eq.Eq, + GHC.Generics.Generic + ) + +instance Data.Aeson.ToJSON ImportConfigJsonOutput where + toJSON a = Data.Aeson.object [ + "strategy" Data.Aeson..= strategy a, + "dry_run" Data.Aeson..= dry_run a, + "config_version" Data.Aeson..= config_version a, + "dimensions" Data.Aeson..= dimensions a, + "default_configs" Data.Aeson..= default_configs a, + "contexts" Data.Aeson..= contexts a + ] + + +instance Io.Superposition.Utility.SerializeBody ImportConfigJsonOutput + +instance Data.Aeson.FromJSON ImportConfigJsonOutput where + parseJSON = Data.Aeson.withObject "ImportConfigJsonOutput" $ \v -> ImportConfigJsonOutput + Data.Functor.<$> (v Data.Aeson..: "strategy") + Control.Applicative.<*> (v Data.Aeson..: "dry_run") + Control.Applicative.<*> (v Data.Aeson..:? "config_version") + Control.Applicative.<*> (v Data.Aeson..: "dimensions") + Control.Applicative.<*> (v Data.Aeson..: "default_configs") + Control.Applicative.<*> (v Data.Aeson..: "contexts") + + + + +data ImportConfigJsonOutputBuilderState = ImportConfigJsonOutputBuilderState { + strategyBuilderState :: Data.Maybe.Maybe Data.Text.Text, + dry_runBuilderState :: Data.Maybe.Maybe Bool, + config_versionBuilderState :: Data.Maybe.Maybe Data.Text.Text, + dimensionsBuilderState :: Data.Maybe.Maybe Io.Superposition.Model.ImportEntityReport.ImportEntityReport, + default_configsBuilderState :: Data.Maybe.Maybe Io.Superposition.Model.ImportEntityReport.ImportEntityReport, + contextsBuilderState :: Data.Maybe.Maybe Io.Superposition.Model.ImportEntityReport.ImportEntityReport +} deriving ( + GHC.Generics.Generic + ) + +defaultBuilderState :: ImportConfigJsonOutputBuilderState +defaultBuilderState = ImportConfigJsonOutputBuilderState { + strategyBuilderState = Data.Maybe.Nothing, + dry_runBuilderState = Data.Maybe.Nothing, + config_versionBuilderState = Data.Maybe.Nothing, + dimensionsBuilderState = Data.Maybe.Nothing, + default_configsBuilderState = Data.Maybe.Nothing, + contextsBuilderState = Data.Maybe.Nothing +} + +type ImportConfigJsonOutputBuilder = Control.Monad.State.Strict.State ImportConfigJsonOutputBuilderState + +setStrategy :: Data.Text.Text -> ImportConfigJsonOutputBuilder () +setStrategy value = + Control.Monad.State.Strict.modify (\s -> (s { strategyBuilderState = Data.Maybe.Just value })) + +setDryRun :: Bool -> ImportConfigJsonOutputBuilder () +setDryRun value = + Control.Monad.State.Strict.modify (\s -> (s { dry_runBuilderState = Data.Maybe.Just value })) + +setConfigVersion :: Data.Maybe.Maybe Data.Text.Text -> ImportConfigJsonOutputBuilder () +setConfigVersion value = + Control.Monad.State.Strict.modify (\s -> (s { config_versionBuilderState = value })) + +setDimensions :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport -> ImportConfigJsonOutputBuilder () +setDimensions value = + Control.Monad.State.Strict.modify (\s -> (s { dimensionsBuilderState = Data.Maybe.Just value })) + +setDefaultConfigs :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport -> ImportConfigJsonOutputBuilder () +setDefaultConfigs value = + Control.Monad.State.Strict.modify (\s -> (s { default_configsBuilderState = Data.Maybe.Just value })) + +setContexts :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport -> ImportConfigJsonOutputBuilder () +setContexts value = + Control.Monad.State.Strict.modify (\s -> (s { contextsBuilderState = Data.Maybe.Just value })) + +build :: ImportConfigJsonOutputBuilder () -> Data.Either.Either Data.Text.Text ImportConfigJsonOutput +build builder = do + let (_, st) = Control.Monad.State.Strict.runState builder defaultBuilderState + strategy' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigJsonOutput.ImportConfigJsonOutput.strategy is a required property.") Data.Either.Right (strategyBuilderState st) + dry_run' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigJsonOutput.ImportConfigJsonOutput.dry_run is a required property.") Data.Either.Right (dry_runBuilderState st) + config_version' <- Data.Either.Right (config_versionBuilderState st) + dimensions' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigJsonOutput.ImportConfigJsonOutput.dimensions is a required property.") Data.Either.Right (dimensionsBuilderState st) + default_configs' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigJsonOutput.ImportConfigJsonOutput.default_configs is a required property.") Data.Either.Right (default_configsBuilderState st) + contexts' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigJsonOutput.ImportConfigJsonOutput.contexts is a required property.") Data.Either.Right (contextsBuilderState st) + Data.Either.Right (ImportConfigJsonOutput { + strategy = strategy', + dry_run = dry_run', + config_version = config_version', + dimensions = dimensions', + default_configs = default_configs', + contexts = contexts' + }) + + +instance Io.Superposition.Utility.FromResponseParser ImportConfigJsonOutput where + expectedStatus = (Network.HTTP.Types.mkStatus 200 "") + responseParser = do + + var0 <- Io.Superposition.Utility.deSerField "dry_run" + var1 <- Io.Superposition.Utility.deSerField "contexts" + var2 <- Io.Superposition.Utility.deSerField "strategy" + var3 <- Io.Superposition.Utility.deSerField "default_configs" + var4 <- Io.Superposition.Utility.deSerField "config_version" + var5 <- Io.Superposition.Utility.deSerField "dimensions" + pure $ ImportConfigJsonOutput { + strategy = var2, + dry_run = var0, + config_version = var4, + dimensions = var5, + default_configs = var3, + contexts = var1 + } + diff --git a/clients/haskell/sdk/Io/Superposition/Model/ImportConfigTomlInput.hs b/clients/haskell/sdk/Io/Superposition/Model/ImportConfigTomlInput.hs new file mode 100644 index 000000000..b8fbee919 --- /dev/null +++ b/clients/haskell/sdk/Io/Superposition/Model/ImportConfigTomlInput.hs @@ -0,0 +1,166 @@ +module Io.Superposition.Model.ImportConfigTomlInput ( + setWorkspaceId, + setOrgId, + setStrategy, + setOnError, + setDryRun, + setConfigTags, + setTomlConfig, + build, + ImportConfigTomlInputBuilder, + ImportConfigTomlInput, + workspace_id, + org_id, + strategy, + on_error, + dry_run, + config_tags, + toml_config +) where +import qualified Control.Applicative +import qualified Control.Monad.State.Strict +import qualified Data.Aeson +import qualified Data.Either +import qualified Data.Eq +import qualified Data.Functor +import qualified Data.Maybe +import qualified Data.Text +import qualified GHC.Generics +import qualified GHC.Show +import qualified Io.Superposition.Model.ImportOnError +import qualified Io.Superposition.Model.ImportStrategy +import qualified Io.Superposition.Utility +import qualified Network.HTTP.Types.Method + +data ImportConfigTomlInput = ImportConfigTomlInput { + workspace_id :: Data.Text.Text, + org_id :: Data.Text.Text, + strategy :: Data.Maybe.Maybe Io.Superposition.Model.ImportStrategy.ImportStrategy, + on_error :: Data.Maybe.Maybe Io.Superposition.Model.ImportOnError.ImportOnError, + dry_run :: Data.Maybe.Maybe Bool, + config_tags :: Data.Maybe.Maybe Data.Text.Text, + toml_config :: Data.Text.Text +} deriving ( + GHC.Show.Show, + Data.Eq.Eq, + GHC.Generics.Generic + ) + +instance Data.Aeson.ToJSON ImportConfigTomlInput where + toJSON a = Data.Aeson.object [ + "workspace_id" Data.Aeson..= workspace_id a, + "org_id" Data.Aeson..= org_id a, + "strategy" Data.Aeson..= strategy a, + "on_error" Data.Aeson..= on_error a, + "dry_run" Data.Aeson..= dry_run a, + "config_tags" Data.Aeson..= config_tags a, + "toml_config" Data.Aeson..= toml_config a + ] + + +instance Io.Superposition.Utility.SerializeBody ImportConfigTomlInput + +instance Data.Aeson.FromJSON ImportConfigTomlInput where + parseJSON = Data.Aeson.withObject "ImportConfigTomlInput" $ \v -> ImportConfigTomlInput + Data.Functor.<$> (v Data.Aeson..: "workspace_id") + Control.Applicative.<*> (v Data.Aeson..: "org_id") + Control.Applicative.<*> (v Data.Aeson..:? "strategy") + Control.Applicative.<*> (v Data.Aeson..:? "on_error") + Control.Applicative.<*> (v Data.Aeson..:? "dry_run") + Control.Applicative.<*> (v Data.Aeson..:? "config_tags") + Control.Applicative.<*> (v Data.Aeson..: "toml_config") + + + + +data ImportConfigTomlInputBuilderState = ImportConfigTomlInputBuilderState { + workspace_idBuilderState :: Data.Maybe.Maybe Data.Text.Text, + org_idBuilderState :: Data.Maybe.Maybe Data.Text.Text, + strategyBuilderState :: Data.Maybe.Maybe Io.Superposition.Model.ImportStrategy.ImportStrategy, + on_errorBuilderState :: Data.Maybe.Maybe Io.Superposition.Model.ImportOnError.ImportOnError, + dry_runBuilderState :: Data.Maybe.Maybe Bool, + config_tagsBuilderState :: Data.Maybe.Maybe Data.Text.Text, + toml_configBuilderState :: Data.Maybe.Maybe Data.Text.Text +} deriving ( + GHC.Generics.Generic + ) + +defaultBuilderState :: ImportConfigTomlInputBuilderState +defaultBuilderState = ImportConfigTomlInputBuilderState { + workspace_idBuilderState = Data.Maybe.Nothing, + org_idBuilderState = Data.Maybe.Nothing, + strategyBuilderState = Data.Maybe.Nothing, + on_errorBuilderState = Data.Maybe.Nothing, + dry_runBuilderState = Data.Maybe.Nothing, + config_tagsBuilderState = Data.Maybe.Nothing, + toml_configBuilderState = Data.Maybe.Nothing +} + +type ImportConfigTomlInputBuilder = Control.Monad.State.Strict.State ImportConfigTomlInputBuilderState + +setWorkspaceId :: Data.Text.Text -> ImportConfigTomlInputBuilder () +setWorkspaceId value = + Control.Monad.State.Strict.modify (\s -> (s { workspace_idBuilderState = Data.Maybe.Just value })) + +setOrgId :: Data.Text.Text -> ImportConfigTomlInputBuilder () +setOrgId value = + Control.Monad.State.Strict.modify (\s -> (s { org_idBuilderState = Data.Maybe.Just value })) + +setStrategy :: Data.Maybe.Maybe Io.Superposition.Model.ImportStrategy.ImportStrategy -> ImportConfigTomlInputBuilder () +setStrategy value = + Control.Monad.State.Strict.modify (\s -> (s { strategyBuilderState = value })) + +setOnError :: Data.Maybe.Maybe Io.Superposition.Model.ImportOnError.ImportOnError -> ImportConfigTomlInputBuilder () +setOnError value = + Control.Monad.State.Strict.modify (\s -> (s { on_errorBuilderState = value })) + +setDryRun :: Data.Maybe.Maybe Bool -> ImportConfigTomlInputBuilder () +setDryRun value = + Control.Monad.State.Strict.modify (\s -> (s { dry_runBuilderState = value })) + +setConfigTags :: Data.Maybe.Maybe Data.Text.Text -> ImportConfigTomlInputBuilder () +setConfigTags value = + Control.Monad.State.Strict.modify (\s -> (s { config_tagsBuilderState = value })) + +setTomlConfig :: Data.Text.Text -> ImportConfigTomlInputBuilder () +setTomlConfig value = + Control.Monad.State.Strict.modify (\s -> (s { toml_configBuilderState = Data.Maybe.Just value })) + +build :: ImportConfigTomlInputBuilder () -> Data.Either.Either Data.Text.Text ImportConfigTomlInput +build builder = do + let (_, st) = Control.Monad.State.Strict.runState builder defaultBuilderState + workspace_id' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigTomlInput.ImportConfigTomlInput.workspace_id is a required property.") Data.Either.Right (workspace_idBuilderState st) + org_id' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigTomlInput.ImportConfigTomlInput.org_id is a required property.") Data.Either.Right (org_idBuilderState st) + strategy' <- Data.Either.Right (strategyBuilderState st) + on_error' <- Data.Either.Right (on_errorBuilderState st) + dry_run' <- Data.Either.Right (dry_runBuilderState st) + config_tags' <- Data.Either.Right (config_tagsBuilderState st) + toml_config' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigTomlInput.ImportConfigTomlInput.toml_config is a required property.") Data.Either.Right (toml_configBuilderState st) + Data.Either.Right (ImportConfigTomlInput { + workspace_id = workspace_id', + org_id = org_id', + strategy = strategy', + on_error = on_error', + dry_run = dry_run', + config_tags = config_tags', + toml_config = toml_config' + }) + + +instance Io.Superposition.Utility.IntoRequestBuilder ImportConfigTomlInput where + intoRequestBuilder self = do + Io.Superposition.Utility.setMethod Network.HTTP.Types.Method.methodPost + Io.Superposition.Utility.setPath [ + "config", + "toml", + "import" + ] + + Io.Superposition.Utility.serHeader "x-workspace" (workspace_id self) + Io.Superposition.Utility.serHeader "x-org-id" (org_id self) + Io.Superposition.Utility.serHeader "x-import-on-error" (on_error self) + Io.Superposition.Utility.serHeader "x-import-dry-run" (dry_run self) + Io.Superposition.Utility.serHeader "x-import-strategy" (strategy self) + Io.Superposition.Utility.serHeader "x-config-tags" (config_tags self) + Io.Superposition.Utility.serBody "text/plain" (toml_config self) + diff --git a/clients/haskell/sdk/Io/Superposition/Model/ImportConfigTomlOutput.hs b/clients/haskell/sdk/Io/Superposition/Model/ImportConfigTomlOutput.hs new file mode 100644 index 000000000..c9926ec37 --- /dev/null +++ b/clients/haskell/sdk/Io/Superposition/Model/ImportConfigTomlOutput.hs @@ -0,0 +1,154 @@ +module Io.Superposition.Model.ImportConfigTomlOutput ( + setStrategy, + setDryRun, + setConfigVersion, + setDimensions, + setDefaultConfigs, + setContexts, + build, + ImportConfigTomlOutputBuilder, + ImportConfigTomlOutput, + strategy, + dry_run, + config_version, + dimensions, + default_configs, + contexts +) where +import qualified Control.Applicative +import qualified Control.Monad.State.Strict +import qualified Data.Aeson +import qualified Data.Either +import qualified Data.Eq +import qualified Data.Functor +import qualified Data.Maybe +import qualified Data.Text +import qualified GHC.Generics +import qualified GHC.Show +import qualified Io.Superposition.Model.ImportEntityReport +import qualified Io.Superposition.Utility +import qualified Network.HTTP.Types + +data ImportConfigTomlOutput = ImportConfigTomlOutput { + strategy :: Data.Text.Text, + dry_run :: Bool, + config_version :: Data.Maybe.Maybe Data.Text.Text, + dimensions :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport, + default_configs :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport, + contexts :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport +} deriving ( + GHC.Show.Show, + Data.Eq.Eq, + GHC.Generics.Generic + ) + +instance Data.Aeson.ToJSON ImportConfigTomlOutput where + toJSON a = Data.Aeson.object [ + "strategy" Data.Aeson..= strategy a, + "dry_run" Data.Aeson..= dry_run a, + "config_version" Data.Aeson..= config_version a, + "dimensions" Data.Aeson..= dimensions a, + "default_configs" Data.Aeson..= default_configs a, + "contexts" Data.Aeson..= contexts a + ] + + +instance Io.Superposition.Utility.SerializeBody ImportConfigTomlOutput + +instance Data.Aeson.FromJSON ImportConfigTomlOutput where + parseJSON = Data.Aeson.withObject "ImportConfigTomlOutput" $ \v -> ImportConfigTomlOutput + Data.Functor.<$> (v Data.Aeson..: "strategy") + Control.Applicative.<*> (v Data.Aeson..: "dry_run") + Control.Applicative.<*> (v Data.Aeson..:? "config_version") + Control.Applicative.<*> (v Data.Aeson..: "dimensions") + Control.Applicative.<*> (v Data.Aeson..: "default_configs") + Control.Applicative.<*> (v Data.Aeson..: "contexts") + + + + +data ImportConfigTomlOutputBuilderState = ImportConfigTomlOutputBuilderState { + strategyBuilderState :: Data.Maybe.Maybe Data.Text.Text, + dry_runBuilderState :: Data.Maybe.Maybe Bool, + config_versionBuilderState :: Data.Maybe.Maybe Data.Text.Text, + dimensionsBuilderState :: Data.Maybe.Maybe Io.Superposition.Model.ImportEntityReport.ImportEntityReport, + default_configsBuilderState :: Data.Maybe.Maybe Io.Superposition.Model.ImportEntityReport.ImportEntityReport, + contextsBuilderState :: Data.Maybe.Maybe Io.Superposition.Model.ImportEntityReport.ImportEntityReport +} deriving ( + GHC.Generics.Generic + ) + +defaultBuilderState :: ImportConfigTomlOutputBuilderState +defaultBuilderState = ImportConfigTomlOutputBuilderState { + strategyBuilderState = Data.Maybe.Nothing, + dry_runBuilderState = Data.Maybe.Nothing, + config_versionBuilderState = Data.Maybe.Nothing, + dimensionsBuilderState = Data.Maybe.Nothing, + default_configsBuilderState = Data.Maybe.Nothing, + contextsBuilderState = Data.Maybe.Nothing +} + +type ImportConfigTomlOutputBuilder = Control.Monad.State.Strict.State ImportConfigTomlOutputBuilderState + +setStrategy :: Data.Text.Text -> ImportConfigTomlOutputBuilder () +setStrategy value = + Control.Monad.State.Strict.modify (\s -> (s { strategyBuilderState = Data.Maybe.Just value })) + +setDryRun :: Bool -> ImportConfigTomlOutputBuilder () +setDryRun value = + Control.Monad.State.Strict.modify (\s -> (s { dry_runBuilderState = Data.Maybe.Just value })) + +setConfigVersion :: Data.Maybe.Maybe Data.Text.Text -> ImportConfigTomlOutputBuilder () +setConfigVersion value = + Control.Monad.State.Strict.modify (\s -> (s { config_versionBuilderState = value })) + +setDimensions :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport -> ImportConfigTomlOutputBuilder () +setDimensions value = + Control.Monad.State.Strict.modify (\s -> (s { dimensionsBuilderState = Data.Maybe.Just value })) + +setDefaultConfigs :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport -> ImportConfigTomlOutputBuilder () +setDefaultConfigs value = + Control.Monad.State.Strict.modify (\s -> (s { default_configsBuilderState = Data.Maybe.Just value })) + +setContexts :: Io.Superposition.Model.ImportEntityReport.ImportEntityReport -> ImportConfigTomlOutputBuilder () +setContexts value = + Control.Monad.State.Strict.modify (\s -> (s { contextsBuilderState = Data.Maybe.Just value })) + +build :: ImportConfigTomlOutputBuilder () -> Data.Either.Either Data.Text.Text ImportConfigTomlOutput +build builder = do + let (_, st) = Control.Monad.State.Strict.runState builder defaultBuilderState + strategy' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigTomlOutput.ImportConfigTomlOutput.strategy is a required property.") Data.Either.Right (strategyBuilderState st) + dry_run' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigTomlOutput.ImportConfigTomlOutput.dry_run is a required property.") Data.Either.Right (dry_runBuilderState st) + config_version' <- Data.Either.Right (config_versionBuilderState st) + dimensions' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigTomlOutput.ImportConfigTomlOutput.dimensions is a required property.") Data.Either.Right (dimensionsBuilderState st) + default_configs' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigTomlOutput.ImportConfigTomlOutput.default_configs is a required property.") Data.Either.Right (default_configsBuilderState st) + contexts' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportConfigTomlOutput.ImportConfigTomlOutput.contexts is a required property.") Data.Either.Right (contextsBuilderState st) + Data.Either.Right (ImportConfigTomlOutput { + strategy = strategy', + dry_run = dry_run', + config_version = config_version', + dimensions = dimensions', + default_configs = default_configs', + contexts = contexts' + }) + + +instance Io.Superposition.Utility.FromResponseParser ImportConfigTomlOutput where + expectedStatus = (Network.HTTP.Types.mkStatus 200 "") + responseParser = do + + var0 <- Io.Superposition.Utility.deSerField "dry_run" + var1 <- Io.Superposition.Utility.deSerField "contexts" + var2 <- Io.Superposition.Utility.deSerField "strategy" + var3 <- Io.Superposition.Utility.deSerField "default_configs" + var4 <- Io.Superposition.Utility.deSerField "config_version" + var5 <- Io.Superposition.Utility.deSerField "dimensions" + pure $ ImportConfigTomlOutput { + strategy = var2, + dry_run = var0, + config_version = var4, + dimensions = var5, + default_configs = var3, + contexts = var1 + } + diff --git a/clients/haskell/sdk/Io/Superposition/Model/ImportEntityReport.hs b/clients/haskell/sdk/Io/Superposition/Model/ImportEntityReport.hs new file mode 100644 index 000000000..cfa0737b4 --- /dev/null +++ b/clients/haskell/sdk/Io/Superposition/Model/ImportEntityReport.hs @@ -0,0 +1,122 @@ +module Io.Superposition.Model.ImportEntityReport ( + setCreated, + setUpdated, + setSkipped, + setDeleted, + setErrors, + build, + ImportEntityReportBuilder, + ImportEntityReport, + created, + updated, + skipped, + deleted, + errors +) where +import qualified Control.Applicative +import qualified Control.Monad.State.Strict +import qualified Data.Aeson +import qualified Data.Either +import qualified Data.Eq +import qualified Data.Functor +import qualified Data.Int +import qualified Data.Maybe +import qualified Data.Text +import qualified GHC.Generics +import qualified GHC.Show +import qualified Io.Superposition.Model.ImportErrorItem +import qualified Io.Superposition.Utility + +data ImportEntityReport = ImportEntityReport { + created :: Data.Int.Int32, + updated :: Data.Int.Int32, + skipped :: Data.Int.Int32, + deleted :: Data.Int.Int32, + errors :: Data.Maybe.Maybe ([] Io.Superposition.Model.ImportErrorItem.ImportErrorItem) +} deriving ( + GHC.Show.Show, + Data.Eq.Eq, + GHC.Generics.Generic + ) + +instance Data.Aeson.ToJSON ImportEntityReport where + toJSON a = Data.Aeson.object [ + "created" Data.Aeson..= created a, + "updated" Data.Aeson..= updated a, + "skipped" Data.Aeson..= skipped a, + "deleted" Data.Aeson..= deleted a, + "errors" Data.Aeson..= errors a + ] + + +instance Io.Superposition.Utility.SerializeBody ImportEntityReport + +instance Data.Aeson.FromJSON ImportEntityReport where + parseJSON = Data.Aeson.withObject "ImportEntityReport" $ \v -> ImportEntityReport + Data.Functor.<$> (v Data.Aeson..: "created") + Control.Applicative.<*> (v Data.Aeson..: "updated") + Control.Applicative.<*> (v Data.Aeson..: "skipped") + Control.Applicative.<*> (v Data.Aeson..: "deleted") + Control.Applicative.<*> (v Data.Aeson..:? "errors") + + + + +data ImportEntityReportBuilderState = ImportEntityReportBuilderState { + createdBuilderState :: Data.Maybe.Maybe Data.Int.Int32, + updatedBuilderState :: Data.Maybe.Maybe Data.Int.Int32, + skippedBuilderState :: Data.Maybe.Maybe Data.Int.Int32, + deletedBuilderState :: Data.Maybe.Maybe Data.Int.Int32, + errorsBuilderState :: Data.Maybe.Maybe ([] Io.Superposition.Model.ImportErrorItem.ImportErrorItem) +} deriving ( + GHC.Generics.Generic + ) + +defaultBuilderState :: ImportEntityReportBuilderState +defaultBuilderState = ImportEntityReportBuilderState { + createdBuilderState = Data.Maybe.Nothing, + updatedBuilderState = Data.Maybe.Nothing, + skippedBuilderState = Data.Maybe.Nothing, + deletedBuilderState = Data.Maybe.Nothing, + errorsBuilderState = Data.Maybe.Nothing +} + +type ImportEntityReportBuilder = Control.Monad.State.Strict.State ImportEntityReportBuilderState + +setCreated :: Data.Int.Int32 -> ImportEntityReportBuilder () +setCreated value = + Control.Monad.State.Strict.modify (\s -> (s { createdBuilderState = Data.Maybe.Just value })) + +setUpdated :: Data.Int.Int32 -> ImportEntityReportBuilder () +setUpdated value = + Control.Monad.State.Strict.modify (\s -> (s { updatedBuilderState = Data.Maybe.Just value })) + +setSkipped :: Data.Int.Int32 -> ImportEntityReportBuilder () +setSkipped value = + Control.Monad.State.Strict.modify (\s -> (s { skippedBuilderState = Data.Maybe.Just value })) + +setDeleted :: Data.Int.Int32 -> ImportEntityReportBuilder () +setDeleted value = + Control.Monad.State.Strict.modify (\s -> (s { deletedBuilderState = Data.Maybe.Just value })) + +setErrors :: Data.Maybe.Maybe ([] Io.Superposition.Model.ImportErrorItem.ImportErrorItem) -> ImportEntityReportBuilder () +setErrors value = + Control.Monad.State.Strict.modify (\s -> (s { errorsBuilderState = value })) + +build :: ImportEntityReportBuilder () -> Data.Either.Either Data.Text.Text ImportEntityReport +build builder = do + let (_, st) = Control.Monad.State.Strict.runState builder defaultBuilderState + created' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportEntityReport.ImportEntityReport.created is a required property.") Data.Either.Right (createdBuilderState st) + updated' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportEntityReport.ImportEntityReport.updated is a required property.") Data.Either.Right (updatedBuilderState st) + skipped' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportEntityReport.ImportEntityReport.skipped is a required property.") Data.Either.Right (skippedBuilderState st) + deleted' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportEntityReport.ImportEntityReport.deleted is a required property.") Data.Either.Right (deletedBuilderState st) + errors' <- Data.Either.Right (errorsBuilderState st) + Data.Either.Right (ImportEntityReport { + created = created', + updated = updated', + skipped = skipped', + deleted = deleted', + errors = errors' + }) + + diff --git a/clients/haskell/sdk/Io/Superposition/Model/ImportErrorItem.hs b/clients/haskell/sdk/Io/Superposition/Model/ImportErrorItem.hs new file mode 100644 index 000000000..f1ff5c841 --- /dev/null +++ b/clients/haskell/sdk/Io/Superposition/Model/ImportErrorItem.hs @@ -0,0 +1,81 @@ +module Io.Superposition.Model.ImportErrorItem ( + setId', + setMessage, + build, + ImportErrorItemBuilder, + ImportErrorItem, + id', + message +) where +import qualified Control.Applicative +import qualified Control.Monad.State.Strict +import qualified Data.Aeson +import qualified Data.Either +import qualified Data.Eq +import qualified Data.Functor +import qualified Data.Maybe +import qualified Data.Text +import qualified GHC.Generics +import qualified GHC.Show +import qualified Io.Superposition.Utility + +data ImportErrorItem = ImportErrorItem { + id' :: Data.Text.Text, + message :: Data.Text.Text +} deriving ( + GHC.Show.Show, + Data.Eq.Eq, + GHC.Generics.Generic + ) + +instance Data.Aeson.ToJSON ImportErrorItem where + toJSON a = Data.Aeson.object [ + "id" Data.Aeson..= id' a, + "message" Data.Aeson..= message a + ] + + +instance Io.Superposition.Utility.SerializeBody ImportErrorItem + +instance Data.Aeson.FromJSON ImportErrorItem where + parseJSON = Data.Aeson.withObject "ImportErrorItem" $ \v -> ImportErrorItem + Data.Functor.<$> (v Data.Aeson..: "id") + Control.Applicative.<*> (v Data.Aeson..: "message") + + + + +data ImportErrorItemBuilderState = ImportErrorItemBuilderState { + id'BuilderState :: Data.Maybe.Maybe Data.Text.Text, + messageBuilderState :: Data.Maybe.Maybe Data.Text.Text +} deriving ( + GHC.Generics.Generic + ) + +defaultBuilderState :: ImportErrorItemBuilderState +defaultBuilderState = ImportErrorItemBuilderState { + id'BuilderState = Data.Maybe.Nothing, + messageBuilderState = Data.Maybe.Nothing +} + +type ImportErrorItemBuilder = Control.Monad.State.Strict.State ImportErrorItemBuilderState + +setId' :: Data.Text.Text -> ImportErrorItemBuilder () +setId' value = + Control.Monad.State.Strict.modify (\s -> (s { id'BuilderState = Data.Maybe.Just value })) + +setMessage :: Data.Text.Text -> ImportErrorItemBuilder () +setMessage value = + Control.Monad.State.Strict.modify (\s -> (s { messageBuilderState = Data.Maybe.Just value })) + +build :: ImportErrorItemBuilder () -> Data.Either.Either Data.Text.Text ImportErrorItem +build builder = do + let (_, st) = Control.Monad.State.Strict.runState builder defaultBuilderState + id'' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportErrorItem.ImportErrorItem.id' is a required property.") Data.Either.Right (id'BuilderState st) + message' <- Data.Maybe.maybe (Data.Either.Left "Io.Superposition.Model.ImportErrorItem.ImportErrorItem.message is a required property.") Data.Either.Right (messageBuilderState st) + Data.Either.Right (ImportErrorItem { + id' = id'', + message = message' + }) + + diff --git a/clients/haskell/sdk/Io/Superposition/Model/ImportOnError.hs b/clients/haskell/sdk/Io/Superposition/Model/ImportOnError.hs new file mode 100644 index 000000000..e367606ec --- /dev/null +++ b/clients/haskell/sdk/Io/Superposition/Model/ImportOnError.hs @@ -0,0 +1,44 @@ +module Io.Superposition.Model.ImportOnError ( + ImportOnError(..) +) where +import qualified Data.Aeson +import qualified Data.Eq +import qualified Data.Text +import qualified Data.Text.Encoding +import qualified GHC.Generics +import qualified GHC.Show +import qualified Io.Superposition.Utility + +-- Enum implementation for ImportOnError +data ImportOnError = + ABORT + | CONTINUE + deriving ( + GHC.Generics.Generic, + Data.Eq.Eq, + GHC.Show.Show + ) + +instance Data.Aeson.ToJSON ImportOnError where + toJSON ABORT = Data.Aeson.String $ Data.Text.pack "abort" + toJSON CONTINUE = Data.Aeson.String $ Data.Text.pack "continue" + +instance Data.Aeson.FromJSON ImportOnError where + parseJSON = Data.Aeson.withText "ImportOnError" $ \v -> + case v of + "abort" -> pure ABORT + "continue" -> pure CONTINUE + _ -> fail $ "Unknown value for ImportOnError: " <> Data.Text.unpack v + + + +instance Io.Superposition.Utility.SerDe ImportOnError where + serializeElement ABORT = Data.Text.Encoding.encodeUtf8 $ Data.Text.pack "abort" + serializeElement CONTINUE = Data.Text.Encoding.encodeUtf8 $ Data.Text.pack "continue" + deSerializeElement bs = case Data.Text.Encoding.decodeUtf8 bs of + "abort" -> Right ABORT + "continue" -> Right CONTINUE + e -> Left ("Failed to de-serialize ImportOnError, encountered unknown variant: " ++ (show bs)) + + + diff --git a/clients/haskell/sdk/Io/Superposition/Model/ImportStrategy.hs b/clients/haskell/sdk/Io/Superposition/Model/ImportStrategy.hs new file mode 100644 index 000000000..305e40d11 --- /dev/null +++ b/clients/haskell/sdk/Io/Superposition/Model/ImportStrategy.hs @@ -0,0 +1,49 @@ +module Io.Superposition.Model.ImportStrategy ( + ImportStrategy(..) +) where +import qualified Data.Aeson +import qualified Data.Eq +import qualified Data.Text +import qualified Data.Text.Encoding +import qualified GHC.Generics +import qualified GHC.Show +import qualified Io.Superposition.Utility + +-- Enum implementation for ImportStrategy +data ImportStrategy = + CREATE_ONLY + | UPSERT + | REPLACE + deriving ( + GHC.Generics.Generic, + Data.Eq.Eq, + GHC.Show.Show + ) + +instance Data.Aeson.ToJSON ImportStrategy where + toJSON CREATE_ONLY = Data.Aeson.String $ Data.Text.pack "create_only" + toJSON UPSERT = Data.Aeson.String $ Data.Text.pack "upsert" + toJSON REPLACE = Data.Aeson.String $ Data.Text.pack "replace" + +instance Data.Aeson.FromJSON ImportStrategy where + parseJSON = Data.Aeson.withText "ImportStrategy" $ \v -> + case v of + "create_only" -> pure CREATE_ONLY + "upsert" -> pure UPSERT + "replace" -> pure REPLACE + _ -> fail $ "Unknown value for ImportStrategy: " <> Data.Text.unpack v + + + +instance Io.Superposition.Utility.SerDe ImportStrategy where + serializeElement CREATE_ONLY = Data.Text.Encoding.encodeUtf8 $ Data.Text.pack "create_only" + serializeElement UPSERT = Data.Text.Encoding.encodeUtf8 $ Data.Text.pack "upsert" + serializeElement REPLACE = Data.Text.Encoding.encodeUtf8 $ Data.Text.pack "replace" + deSerializeElement bs = case Data.Text.Encoding.decodeUtf8 bs of + "create_only" -> Right CREATE_ONLY + "upsert" -> Right UPSERT + "replace" -> Right REPLACE + e -> Left ("Failed to de-serialize ImportStrategy, encountered unknown variant: " ++ (show bs)) + + + diff --git a/clients/haskell/sdk/SuperpositionSDK.cabal b/clients/haskell/sdk/SuperpositionSDK.cabal index b7af69db9..bd77b1ef0 100644 --- a/clients/haskell/sdk/SuperpositionSDK.cabal +++ b/clients/haskell/sdk/SuperpositionSDK.cabal @@ -53,6 +53,7 @@ library Io.Superposition.Model.GetVersionInput, Io.Superposition.Command.Publish, Io.Superposition.Command.UpdateExperimentGroup, + Io.Superposition.Model.ImportConfigJsonInput, Io.Superposition.Model.BulkOperationInput, Io.Superposition.Model.DeleteSecretOutput, Io.Superposition.Model.WebhookFailed, @@ -70,6 +71,7 @@ library Io.Superposition.Model.DeleteExperimentGroupOutput, Io.Superposition.Model.UpdateTypeTemplatesOutput, Io.Superposition.Model.AuditAction, + Io.Superposition.Command.ImportConfigJson, Io.Superposition.Model.UpdateDimensionInput, Io.Superposition.Model.GetConfigJsonInput, Io.Superposition.Model.GetContextOutput, @@ -149,6 +151,7 @@ library Io.Superposition.Model.UpdateDefaultConfigInput, Io.Superposition.Model.UpdateWorkspaceOutput, Io.Superposition.Model.ValidateContextOutput, + Io.Superposition.Model.ImportEntityReport, Io.Superposition.Model.CreateOrganisationOutput, Io.Superposition.Command.AddMembersToGroup, Io.Superposition.Model.CreateWebhookInput, @@ -165,10 +168,12 @@ library Io.Superposition.Model.GetDimensionInput, Io.Superposition.Model.ListVersionsOutput, Io.Superposition.Model.WeightRecomputeInput, + Io.Superposition.Model.ImportConfigTomlOutput, Io.Superposition.Command.CreateTypeTemplates, Io.Superposition.Model.ContextActionOut, Io.Superposition.Command.ListExperiment, Io.Superposition.Model.UpdateDimensionOutput, + Io.Superposition.Command.ImportConfigToml, Io.Superposition.Model.ListDimensionsOutput, Io.Superposition.Command.UpdateSecret, Io.Superposition.Command.RemoveMembersFromGroup, @@ -196,6 +201,7 @@ library Io.Superposition.Model.ListVersionsInput, Io.Superposition.Command.DeleteSecret, Io.Superposition.Command.RotateMasterEncryptionKey, + Io.Superposition.Model.ImportStrategy, Io.Superposition.Model.DeleteTypeTemplatesInput, Io.Superposition.Model.GetResolvedConfigInput, Io.Superposition.Model.Bucket, @@ -212,6 +218,7 @@ library Io.Superposition.Model.ListFunctionInput, Io.Superposition.Model.SortBy, Io.Superposition.Model.ListExperimentGroupsInput, + Io.Superposition.Model.ImportConfigTomlInput, Io.Superposition.Command.UpdateWebhook, Io.Superposition.SuperpositionClient, Io.Superposition.Model.UpdateContextOverrideRequest, @@ -280,6 +287,7 @@ library Io.Superposition.Command.GetVersion, Io.Superposition.Model.ExperimentResponse, Io.Superposition.Model.GetTypeTemplatesListInput, + Io.Superposition.Model.ImportErrorItem, Io.Superposition.Command.ListWorkspace, Io.Superposition.Model.OrganisationResponse, Io.Superposition.Model.DimensionResponse, @@ -294,6 +302,7 @@ library Io.Superposition.Command.UpdateWorkspace, Io.Superposition.Model.GetWebhookByEventInput, Io.Superposition.Model.VariableResponse, + Io.Superposition.Model.ImportOnError, Io.Superposition.Model.CreateDefaultConfigOutput, Io.Superposition.Model.DeleteSecretInput, Io.Superposition.Command.ListVariables, @@ -327,6 +336,7 @@ library Io.Superposition.Model.CreateVariableOutput, Io.Superposition.Model.ResourceNotFound, Io.Superposition.Model.ContextPut, + Io.Superposition.Model.ImportConfigJsonOutput, Io.Superposition.Command.DeleteDimension, Io.Superposition.Command.GetDimension, Io.Superposition.Model.CreateDimensionInput, diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClient.java b/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClient.java index a319ce8f7..2411f90fc 100644 --- a/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClient.java +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClient.java @@ -101,6 +101,10 @@ import io.juspay.superposition.model.GetWebhookOutput; import io.juspay.superposition.model.GetWorkspaceInput; import io.juspay.superposition.model.GetWorkspaceOutput; +import io.juspay.superposition.model.ImportConfigJsonInput; +import io.juspay.superposition.model.ImportConfigJsonOutput; +import io.juspay.superposition.model.ImportConfigTomlInput; +import io.juspay.superposition.model.ImportConfigTomlOutput; import io.juspay.superposition.model.InternalServerError; import io.juspay.superposition.model.ListAuditLogsInput; import io.juspay.superposition.model.ListAuditLogsOutput; @@ -1158,6 +1162,42 @@ default CompletableFuture getWorkspace(GetWorkspaceInput inp */ CompletableFuture getWorkspace(GetWorkspaceInput input, RequestOverrideConfig overrideConfig); + /** + * Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a + * single transaction after validating the document. + * + * @throws InternalServerError + */ + default CompletableFuture importConfigJson(ImportConfigJsonInput input) { + return importConfigJson(input, null); + } + + /** + * Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a + * single transaction after validating the document. + * + * @throws InternalServerError + */ + CompletableFuture importConfigJson(ImportConfigJsonInput input, RequestOverrideConfig overrideConfig); + + /** + * Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a + * single transaction after validating the document. + * + * @throws InternalServerError + */ + default CompletableFuture importConfigToml(ImportConfigTomlInput input) { + return importConfigToml(input, null); + } + + /** + * Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a + * single transaction after validating the document. + * + * @throws InternalServerError + */ + CompletableFuture importConfigToml(ImportConfigTomlInput input, RequestOverrideConfig overrideConfig); + /** * Retrieves a paginated list of audit logs with support for filtering by date range, table names, * actions, and usernames for compliance and monitoring purposes. @@ -1901,12 +1941,12 @@ final class Builder extends Client.Builder { Node.objectNode() ); - private static final HttpBasicAuthTrait httpBasicAuthScheme = new HttpBasicAuthTrait(); - private static final AuthSchemeFactory httpBasicAuthSchemeFactory = new HttpBasicAuthAuthScheme.Factory(); - private static final HttpBearerAuthTrait httpBearerAuthScheme = new HttpBearerAuthTrait(); private static final AuthSchemeFactory httpBearerAuthSchemeFactory = new HttpBearerAuthScheme.Factory(); + private static final HttpBasicAuthTrait httpBasicAuthScheme = new HttpBasicAuthTrait(); + private static final AuthSchemeFactory httpBasicAuthSchemeFactory = new HttpBasicAuthAuthScheme.Factory(); + private Builder() { configBuilder().putSupportedAuthSchemes(httpBasicAuthSchemeFactory.createAuthScheme(httpBasicAuthScheme), httpBearerAuthSchemeFactory.createAuthScheme(httpBearerAuthScheme)); } diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClientImpl.java b/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClientImpl.java index abb26734f..4b915542e 100644 --- a/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClientImpl.java +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionAsyncClientImpl.java @@ -151,6 +151,12 @@ import io.juspay.superposition.model.GetWorkspace; import io.juspay.superposition.model.GetWorkspaceInput; import io.juspay.superposition.model.GetWorkspaceOutput; +import io.juspay.superposition.model.ImportConfigJson; +import io.juspay.superposition.model.ImportConfigJsonInput; +import io.juspay.superposition.model.ImportConfigJsonOutput; +import io.juspay.superposition.model.ImportConfigToml; +import io.juspay.superposition.model.ImportConfigTomlInput; +import io.juspay.superposition.model.ImportConfigTomlOutput; import io.juspay.superposition.model.ListAuditLogs; import io.juspay.superposition.model.ListAuditLogsInput; import io.juspay.superposition.model.ListAuditLogsOutput; @@ -279,8 +285,8 @@ final class SuperpositionAsyncClientImpl extends Client implements SuperpositionAsyncClient { private static final TypeRegistry TYPE_REGISTRY = TypeRegistry.builder() .putType(ValidationException.$ID, ValidationException.class, ValidationException::builder) - .putType(NotAuthorizedException.$ID, NotAuthorizedException.class, NotAuthorizedException::builder) .putType(AccessDeniedException.$ID, AccessDeniedException.class, AccessDeniedException::builder) + .putType(NotAuthorizedException.$ID, NotAuthorizedException.class, NotAuthorizedException::builder) .putType(InternalFailureException.$ID, InternalFailureException.class, InternalFailureException::builder) .putType(UnknownOperationException.$ID, UnknownOperationException.class, UnknownOperationException::builder) .putType(MalformedRequestException.$ID, MalformedRequestException.class, MalformedRequestException::builder) @@ -491,6 +497,14 @@ final class SuperpositionAsyncClientImpl extends Client implements Superposition public CompletableFuture getWorkspace(GetWorkspaceInput input, RequestOverrideConfig overrideConfig) {return call(input, GetWorkspace.instance(), overrideConfig); } + @Override + public CompletableFuture importConfigJson(ImportConfigJsonInput input, RequestOverrideConfig overrideConfig) {return call(input, ImportConfigJson.instance(), overrideConfig); + } + + @Override + public CompletableFuture importConfigToml(ImportConfigTomlInput input, RequestOverrideConfig overrideConfig) {return call(input, ImportConfigToml.instance(), overrideConfig); + } + @Override public CompletableFuture listAuditLogs(ListAuditLogsInput input, RequestOverrideConfig overrideConfig) {return call(input, ListAuditLogs.instance(), overrideConfig); } diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClient.java b/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClient.java index da93167db..cf8d20434 100644 --- a/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClient.java +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClient.java @@ -101,6 +101,10 @@ import io.juspay.superposition.model.GetWebhookOutput; import io.juspay.superposition.model.GetWorkspaceInput; import io.juspay.superposition.model.GetWorkspaceOutput; +import io.juspay.superposition.model.ImportConfigJsonInput; +import io.juspay.superposition.model.ImportConfigJsonOutput; +import io.juspay.superposition.model.ImportConfigTomlInput; +import io.juspay.superposition.model.ImportConfigTomlOutput; import io.juspay.superposition.model.InternalServerError; import io.juspay.superposition.model.ListAuditLogsInput; import io.juspay.superposition.model.ListAuditLogsOutput; @@ -1157,6 +1161,42 @@ default GetWorkspaceOutput getWorkspace(GetWorkspaceInput input) { */ GetWorkspaceOutput getWorkspace(GetWorkspaceInput input, RequestOverrideConfig overrideConfig); + /** + * Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a + * single transaction after validating the document. + * + * @throws InternalServerError + */ + default ImportConfigJsonOutput importConfigJson(ImportConfigJsonInput input) { + return importConfigJson(input, null); + } + + /** + * Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a + * single transaction after validating the document. + * + * @throws InternalServerError + */ + ImportConfigJsonOutput importConfigJson(ImportConfigJsonInput input, RequestOverrideConfig overrideConfig); + + /** + * Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a + * single transaction after validating the document. + * + * @throws InternalServerError + */ + default ImportConfigTomlOutput importConfigToml(ImportConfigTomlInput input) { + return importConfigToml(input, null); + } + + /** + * Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a + * single transaction after validating the document. + * + * @throws InternalServerError + */ + ImportConfigTomlOutput importConfigToml(ImportConfigTomlInput input, RequestOverrideConfig overrideConfig); + /** * Retrieves a paginated list of audit logs with support for filtering by date range, table names, * actions, and usernames for compliance and monitoring purposes. @@ -1900,12 +1940,12 @@ final class Builder extends Client.Builder { Node.objectNode() ); - private static final HttpBasicAuthTrait httpBasicAuthScheme = new HttpBasicAuthTrait(); - private static final AuthSchemeFactory httpBasicAuthSchemeFactory = new HttpBasicAuthAuthScheme.Factory(); - private static final HttpBearerAuthTrait httpBearerAuthScheme = new HttpBearerAuthTrait(); private static final AuthSchemeFactory httpBearerAuthSchemeFactory = new HttpBearerAuthScheme.Factory(); + private static final HttpBasicAuthTrait httpBasicAuthScheme = new HttpBasicAuthTrait(); + private static final AuthSchemeFactory httpBasicAuthSchemeFactory = new HttpBasicAuthAuthScheme.Factory(); + private Builder() { configBuilder().putSupportedAuthSchemes(httpBasicAuthSchemeFactory.createAuthScheme(httpBasicAuthScheme), httpBearerAuthSchemeFactory.createAuthScheme(httpBearerAuthScheme)); } diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClientImpl.java b/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClientImpl.java index 4f2474012..963c91779 100644 --- a/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClientImpl.java +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/client/SuperpositionClientImpl.java @@ -151,6 +151,12 @@ import io.juspay.superposition.model.GetWorkspace; import io.juspay.superposition.model.GetWorkspaceInput; import io.juspay.superposition.model.GetWorkspaceOutput; +import io.juspay.superposition.model.ImportConfigJson; +import io.juspay.superposition.model.ImportConfigJsonInput; +import io.juspay.superposition.model.ImportConfigJsonOutput; +import io.juspay.superposition.model.ImportConfigToml; +import io.juspay.superposition.model.ImportConfigTomlInput; +import io.juspay.superposition.model.ImportConfigTomlOutput; import io.juspay.superposition.model.ListAuditLogs; import io.juspay.superposition.model.ListAuditLogsInput; import io.juspay.superposition.model.ListAuditLogsOutput; @@ -279,8 +285,8 @@ final class SuperpositionClientImpl extends Client implements SuperpositionClient { private static final TypeRegistry TYPE_REGISTRY = TypeRegistry.builder() .putType(ValidationException.$ID, ValidationException.class, ValidationException::builder) - .putType(NotAuthorizedException.$ID, NotAuthorizedException.class, NotAuthorizedException::builder) .putType(AccessDeniedException.$ID, AccessDeniedException.class, AccessDeniedException::builder) + .putType(NotAuthorizedException.$ID, NotAuthorizedException.class, NotAuthorizedException::builder) .putType(InternalFailureException.$ID, InternalFailureException.class, InternalFailureException::builder) .putType(UnknownOperationException.$ID, UnknownOperationException.class, UnknownOperationException::builder) .putType(MalformedRequestException.$ID, MalformedRequestException.class, MalformedRequestException::builder) @@ -741,6 +747,24 @@ public GetWorkspaceOutput getWorkspace(GetWorkspaceInput input, RequestOverrideC } } + @Override + public ImportConfigJsonOutput importConfigJson(ImportConfigJsonInput input, RequestOverrideConfig overrideConfig) { + try { + return call(input, ImportConfigJson.instance(), overrideConfig).join(); + } catch (CompletionException e) { + throw unwrapAndThrow(e); + } + } + + @Override + public ImportConfigTomlOutput importConfigToml(ImportConfigTomlInput input, RequestOverrideConfig overrideConfig) { + try { + return call(input, ImportConfigToml.instance(), overrideConfig).join(); + } catch (CompletionException e) { + throw unwrapAndThrow(e); + } + } + @Override public ListAuditLogsOutput listAuditLogs(ListAuditLogsInput input, RequestOverrideConfig overrideConfig) { try { diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigJson.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigJson.java new file mode 100644 index 000000000..c62688fb9 --- /dev/null +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigJson.java @@ -0,0 +1,94 @@ + +package io.juspay.superposition.model; + +import java.util.List; +import software.amazon.smithy.java.core.schema.ApiOperation; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.TypeRegistry; +import software.amazon.smithy.model.pattern.UriPattern; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.HttpTrait; +import software.amazon.smithy.utils.SmithyGenerated; + +/** + * Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a + * single transaction after validating the document. + */ +@SmithyGenerated +public final class ImportConfigJson implements ApiOperation { + public static final ShapeId $ID = ShapeId.from("io.superposition#ImportConfigJson"); + + private static final ImportConfigJson $INSTANCE = new ImportConfigJson(); + + static final Schema $SCHEMA = Schema.createOperation($ID, + HttpTrait.builder().method("POST").code(200).uri(UriPattern.parse("/config/json/import")).build()); + + private static final TypeRegistry TYPE_REGISTRY = TypeRegistry.builder() + .putType(InternalServerError.$ID, InternalServerError.class, InternalServerError::builder) + .build(); + + private static final List SCHEMES = List.of(ShapeId.from("smithy.api#httpBasicAuth"), ShapeId.from("smithy.api#httpBearerAuth")); + + /** + * Get an instance of this {@code ApiOperation}. + * + * @return An instance of this class. + */ + public static ImportConfigJson instance() { + return $INSTANCE; + } + + private ImportConfigJson() {} + + @Override + public ShapeBuilder inputBuilder() { + return ImportConfigJsonInput.builder(); + } + + @Override + public ShapeBuilder outputBuilder() { + return ImportConfigJsonOutput.builder(); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public Schema inputSchema() { + return ImportConfigJsonInput.$SCHEMA; + } + + @Override + public Schema outputSchema() { + return ImportConfigJsonOutput.$SCHEMA; + } + + @Override + public TypeRegistry errorRegistry() { + return TYPE_REGISTRY; + } + + @Override + public List effectiveAuthSchemes() { + return SCHEMES; + } + + @Override + public Schema inputStreamMember() { + return null; + } + + @Override + public Schema outputStreamMember() { + return null; + } + + @Override + public Schema idempotencyTokenMember() { + return null; + } +} + diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigJsonInput.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigJsonInput.java new file mode 100644 index 000000000..952e8a74b --- /dev/null +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigJsonInput.java @@ -0,0 +1,357 @@ + +package io.juspay.superposition.model; + +import java.util.Objects; +import software.amazon.smithy.java.core.schema.PreludeSchemas; +import software.amazon.smithy.java.core.schema.PresenceTracker; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SchemaUtils; +import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.HttpHeaderTrait; +import software.amazon.smithy.model.traits.HttpPayloadTrait; +import software.amazon.smithy.model.traits.RequiredTrait; +import software.amazon.smithy.utils.SmithyGenerated; + +@SmithyGenerated +public final class ImportConfigJsonInput implements SerializableStruct { + public static final ShapeId $ID = ShapeId.from("io.superposition#ImportConfigJsonInput"); + + public static final Schema $SCHEMA = Schema.structureBuilder($ID) + .putMember("workspace_id", PreludeSchemas.STRING, + new HttpHeaderTrait("x-workspace"), + new RequiredTrait()) + .putMember("org_id", PreludeSchemas.STRING, + new HttpHeaderTrait("x-org-id"), + new RequiredTrait()) + .putMember("strategy", ImportStrategy.$SCHEMA, + new HttpHeaderTrait("x-import-strategy")) + .putMember("on_error", ImportOnError.$SCHEMA, + new HttpHeaderTrait("x-import-on-error")) + .putMember("dry_run", PreludeSchemas.BOOLEAN, + new HttpHeaderTrait("x-import-dry-run")) + .putMember("config_tags", PreludeSchemas.STRING, + new HttpHeaderTrait("x-config-tags")) + .putMember("json_config", PreludeSchemas.STRING, + new RequiredTrait(), + new HttpPayloadTrait()) + .build(); + + private static final Schema $SCHEMA_WORKSPACE_ID = $SCHEMA.member("workspace_id"); + private static final Schema $SCHEMA_ORG_ID = $SCHEMA.member("org_id"); + private static final Schema $SCHEMA_STRATEGY = $SCHEMA.member("strategy"); + private static final Schema $SCHEMA_ON_ERROR = $SCHEMA.member("on_error"); + private static final Schema $SCHEMA_DRY_RUN = $SCHEMA.member("dry_run"); + private static final Schema $SCHEMA_CONFIG_TAGS = $SCHEMA.member("config_tags"); + private static final Schema $SCHEMA_JSON_CONFIG = $SCHEMA.member("json_config"); + + private final transient String workspaceId; + private final transient String orgId; + private final transient ImportStrategy strategy; + private final transient ImportOnError onError; + private final transient Boolean dryRun; + private final transient String configTags; + private final transient String jsonConfig; + + private ImportConfigJsonInput(Builder builder) { + this.workspaceId = builder.workspaceId; + this.orgId = builder.orgId; + this.strategy = builder.strategy; + this.onError = builder.onError; + this.dryRun = builder.dryRun; + this.configTags = builder.configTags; + this.jsonConfig = builder.jsonConfig; + } + + public String workspaceId() { + return workspaceId; + } + + public String orgId() { + return orgId; + } + + /** + * How the import applies file entities to the workspace. Defaults to upsert. + */ + public ImportStrategy strategy() { + return strategy; + } + + /** + * Whether to abort (default) or continue on per-entity errors. + */ + public ImportOnError onError() { + return onError; + } + + /** + * When true, validates and summarises the import without persisting anything. Defaults to false. + */ + public Boolean dryRun() { + return dryRun; + } + + public String configTags() { + return configTags; + } + + public String jsonConfig() { + return jsonConfig; + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + ImportConfigJsonInput that = (ImportConfigJsonInput) other; + return Objects.equals(this.workspaceId, that.workspaceId) + && Objects.equals(this.orgId, that.orgId) + && Objects.equals(this.strategy, that.strategy) + && Objects.equals(this.onError, that.onError) + && Objects.equals(this.dryRun, that.dryRun) + && Objects.equals(this.configTags, that.configTags) + && Objects.equals(this.jsonConfig, that.jsonConfig); + } + + @Override + public int hashCode() { + return Objects.hash(workspaceId, orgId, strategy, onError, dryRun, configTags, jsonConfig); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + serializer.writeString($SCHEMA_WORKSPACE_ID, workspaceId); + serializer.writeString($SCHEMA_ORG_ID, orgId); + if (strategy != null) { + serializer.writeString($SCHEMA_STRATEGY, strategy.value()); + } + if (onError != null) { + serializer.writeString($SCHEMA_ON_ERROR, onError.value()); + } + if (dryRun != null) { + serializer.writeBoolean($SCHEMA_DRY_RUN, dryRun); + } + if (configTags != null) { + serializer.writeString($SCHEMA_CONFIG_TAGS, configTags); + } + serializer.writeString($SCHEMA_JSON_CONFIG, jsonConfig); + } + + @Override + @SuppressWarnings("unchecked") + public T getMemberValue(Schema member) { + return switch (member.memberIndex()) { + case 0 -> (T) SchemaUtils.validateSameMember($SCHEMA_WORKSPACE_ID, member, workspaceId); + case 1 -> (T) SchemaUtils.validateSameMember($SCHEMA_ORG_ID, member, orgId); + case 2 -> (T) SchemaUtils.validateSameMember($SCHEMA_JSON_CONFIG, member, jsonConfig); + case 3 -> (T) SchemaUtils.validateSameMember($SCHEMA_STRATEGY, member, strategy); + case 4 -> (T) SchemaUtils.validateSameMember($SCHEMA_ON_ERROR, member, onError); + case 5 -> (T) SchemaUtils.validateSameMember($SCHEMA_DRY_RUN, member, dryRun); + case 6 -> (T) SchemaUtils.validateSameMember($SCHEMA_CONFIG_TAGS, member, configTags); + default -> throw new IllegalArgumentException("Attempted to get non-existent member: " + member.id()); + }; + } + + /** + * Create a new builder containing all the current property values of this object. + * + *

Note: This method performs only a shallow copy of the original properties. + * + * @return a builder for {@link ImportConfigJsonInput}. + */ + public Builder toBuilder() { + var builder = new Builder(); + builder.workspaceId(this.workspaceId); + builder.orgId(this.orgId); + builder.strategy(this.strategy); + builder.onError(this.onError); + builder.dryRun(this.dryRun); + builder.configTags(this.configTags); + builder.jsonConfig(this.jsonConfig); + return builder; + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link ImportConfigJsonInput}. + */ + public static final class Builder implements ShapeBuilder { + private final PresenceTracker tracker = PresenceTracker.of($SCHEMA); + private String workspaceId; + private String orgId; + private ImportStrategy strategy; + private ImportOnError onError; + private Boolean dryRun; + private String configTags; + private String jsonConfig; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + /** + *

Required + * @return this builder. + */ + public Builder workspaceId(String workspaceId) { + this.workspaceId = Objects.requireNonNull(workspaceId, "workspaceId cannot be null"); + tracker.setMember($SCHEMA_WORKSPACE_ID); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder orgId(String orgId) { + this.orgId = Objects.requireNonNull(orgId, "orgId cannot be null"); + tracker.setMember($SCHEMA_ORG_ID); + return this; + } + + /** + * How the import applies file entities to the workspace. Defaults to upsert. + * + * @return this builder. + */ + public Builder strategy(ImportStrategy strategy) { + this.strategy = strategy; + return this; + } + + /** + * Whether to abort (default) or continue on per-entity errors. + * + * @return this builder. + */ + public Builder onError(ImportOnError onError) { + this.onError = onError; + return this; + } + + /** + * When true, validates and summarises the import without persisting anything. Defaults to false. + * + * @return this builder. + */ + public Builder dryRun(boolean dryRun) { + this.dryRun = dryRun; + return this; + } + + /** + * @return this builder. + */ + public Builder configTags(String configTags) { + this.configTags = configTags; + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder jsonConfig(String jsonConfig) { + this.jsonConfig = Objects.requireNonNull(jsonConfig, "jsonConfig cannot be null"); + tracker.setMember($SCHEMA_JSON_CONFIG); + return this; + } + + @Override + public ImportConfigJsonInput build() { + tracker.validate(); + return new ImportConfigJsonInput(this); + } + + @Override + @SuppressWarnings("unchecked") + public void setMemberValue(Schema member, Object value) { + switch (member.memberIndex()) { + case 0 -> workspaceId((String) SchemaUtils.validateSameMember($SCHEMA_WORKSPACE_ID, member, value)); + case 1 -> orgId((String) SchemaUtils.validateSameMember($SCHEMA_ORG_ID, member, value)); + case 2 -> jsonConfig((String) SchemaUtils.validateSameMember($SCHEMA_JSON_CONFIG, member, value)); + case 3 -> strategy((ImportStrategy) SchemaUtils.validateSameMember($SCHEMA_STRATEGY, member, value)); + case 4 -> onError((ImportOnError) SchemaUtils.validateSameMember($SCHEMA_ON_ERROR, member, value)); + case 5 -> dryRun((boolean) SchemaUtils.validateSameMember($SCHEMA_DRY_RUN, member, value)); + case 6 -> configTags((String) SchemaUtils.validateSameMember($SCHEMA_CONFIG_TAGS, member, value)); + default -> ShapeBuilder.super.setMemberValue(member, value); + } + } + + @Override + public ShapeBuilder errorCorrection() { + if (tracker.allSet()) { + return this; + } + if (!tracker.checkMember($SCHEMA_WORKSPACE_ID)) { + workspaceId(""); + } + if (!tracker.checkMember($SCHEMA_ORG_ID)) { + orgId(""); + } + if (!tracker.checkMember($SCHEMA_JSON_CONFIG)) { + jsonConfig(""); + } + return this; + } + + @Override + public Builder deserialize(ShapeDeserializer decoder) { + decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); + return this; + } + + @Override + public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { + decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); + return this; + } + + private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { + private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); + + @Override + public void accept(Builder builder, Schema member, ShapeDeserializer de) { + switch (member.memberIndex()) { + case 0 -> builder.workspaceId(de.readString(member)); + case 1 -> builder.orgId(de.readString(member)); + case 2 -> builder.jsonConfig(de.readString(member)); + case 3 -> builder.strategy(ImportStrategy.builder().deserializeMember(de, member).build()); + case 4 -> builder.onError(ImportOnError.builder().deserializeMember(de, member).build()); + case 5 -> builder.dryRun(de.readBoolean(member)); + case 6 -> builder.configTags(de.readString(member)); + default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); + } + } + } + } +} + diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigJsonOutput.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigJsonOutput.java new file mode 100644 index 000000000..5bcdab6ea --- /dev/null +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigJsonOutput.java @@ -0,0 +1,325 @@ + +package io.juspay.superposition.model; + +import java.util.Objects; +import software.amazon.smithy.java.core.schema.PreludeSchemas; +import software.amazon.smithy.java.core.schema.PresenceTracker; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SchemaUtils; +import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.RequiredTrait; +import software.amazon.smithy.utils.SmithyGenerated; + +/** + * Summary of what an import created, updated, skipped or deleted. + */ +@SmithyGenerated +public final class ImportConfigJsonOutput implements SerializableStruct { + public static final ShapeId $ID = ShapeId.from("io.superposition#ImportConfigOutput"); + + public static final Schema $SCHEMA = Schema.structureBuilder($ID) + .putMember("strategy", PreludeSchemas.STRING, + new RequiredTrait()) + .putMember("dry_run", PreludeSchemas.BOOLEAN, + new RequiredTrait()) + .putMember("config_version", PreludeSchemas.STRING) + .putMember("dimensions", ImportEntityReport.$SCHEMA, + new RequiredTrait()) + .putMember("default_configs", ImportEntityReport.$SCHEMA, + new RequiredTrait()) + .putMember("contexts", ImportEntityReport.$SCHEMA, + new RequiredTrait()) + .build(); + + private static final Schema $SCHEMA_STRATEGY = $SCHEMA.member("strategy"); + private static final Schema $SCHEMA_DRY_RUN = $SCHEMA.member("dry_run"); + private static final Schema $SCHEMA_CONFIG_VERSION = $SCHEMA.member("config_version"); + private static final Schema $SCHEMA_DIMENSIONS = $SCHEMA.member("dimensions"); + private static final Schema $SCHEMA_DEFAULT_CONFIGS = $SCHEMA.member("default_configs"); + private static final Schema $SCHEMA_CONTEXTS = $SCHEMA.member("contexts"); + + private final transient String strategy; + private final transient boolean dryRun; + private final transient String configVersion; + private final transient ImportEntityReport dimensions; + private final transient ImportEntityReport defaultConfigs; + private final transient ImportEntityReport contexts; + + private ImportConfigJsonOutput(Builder builder) { + this.strategy = builder.strategy; + this.dryRun = builder.dryRun; + this.configVersion = builder.configVersion; + this.dimensions = builder.dimensions; + this.defaultConfigs = builder.defaultConfigs; + this.contexts = builder.contexts; + } + + public String strategy() { + return strategy; + } + + public boolean dryRun() { + return dryRun; + } + + public String configVersion() { + return configVersion; + } + + public ImportEntityReport dimensions() { + return dimensions; + } + + public ImportEntityReport defaultConfigs() { + return defaultConfigs; + } + + public ImportEntityReport contexts() { + return contexts; + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + ImportConfigJsonOutput that = (ImportConfigJsonOutput) other; + return Objects.equals(this.strategy, that.strategy) + && this.dryRun == that.dryRun + && Objects.equals(this.configVersion, that.configVersion) + && Objects.equals(this.dimensions, that.dimensions) + && Objects.equals(this.defaultConfigs, that.defaultConfigs) + && Objects.equals(this.contexts, that.contexts); + } + + @Override + public int hashCode() { + return Objects.hash(strategy, dryRun, configVersion, dimensions, defaultConfigs, contexts); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + serializer.writeString($SCHEMA_STRATEGY, strategy); + serializer.writeBoolean($SCHEMA_DRY_RUN, dryRun); + if (configVersion != null) { + serializer.writeString($SCHEMA_CONFIG_VERSION, configVersion); + } + if (dimensions != null) { + serializer.writeStruct($SCHEMA_DIMENSIONS, dimensions); + } + if (defaultConfigs != null) { + serializer.writeStruct($SCHEMA_DEFAULT_CONFIGS, defaultConfigs); + } + if (contexts != null) { + serializer.writeStruct($SCHEMA_CONTEXTS, contexts); + } + } + + @Override + @SuppressWarnings("unchecked") + public T getMemberValue(Schema member) { + return switch (member.memberIndex()) { + case 0 -> (T) SchemaUtils.validateSameMember($SCHEMA_STRATEGY, member, strategy); + case 1 -> (T) SchemaUtils.validateSameMember($SCHEMA_DRY_RUN, member, dryRun); + case 2 -> (T) SchemaUtils.validateSameMember($SCHEMA_DIMENSIONS, member, dimensions); + case 3 -> (T) SchemaUtils.validateSameMember($SCHEMA_DEFAULT_CONFIGS, member, defaultConfigs); + case 4 -> (T) SchemaUtils.validateSameMember($SCHEMA_CONTEXTS, member, contexts); + case 5 -> (T) SchemaUtils.validateSameMember($SCHEMA_CONFIG_VERSION, member, configVersion); + default -> throw new IllegalArgumentException("Attempted to get non-existent member: " + member.id()); + }; + } + + /** + * Create a new builder containing all the current property values of this object. + * + *

Note: This method performs only a shallow copy of the original properties. + * + * @return a builder for {@link ImportConfigJsonOutput}. + */ + public Builder toBuilder() { + var builder = new Builder(); + builder.strategy(this.strategy); + builder.dryRun(this.dryRun); + builder.configVersion(this.configVersion); + builder.dimensions(this.dimensions); + builder.defaultConfigs(this.defaultConfigs); + builder.contexts(this.contexts); + return builder; + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link ImportConfigJsonOutput}. + */ + public static final class Builder implements ShapeBuilder { + private final PresenceTracker tracker = PresenceTracker.of($SCHEMA); + private String strategy; + private boolean dryRun; + private String configVersion; + private ImportEntityReport dimensions; + private ImportEntityReport defaultConfigs; + private ImportEntityReport contexts; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + /** + *

Required + * @return this builder. + */ + public Builder strategy(String strategy) { + this.strategy = Objects.requireNonNull(strategy, "strategy cannot be null"); + tracker.setMember($SCHEMA_STRATEGY); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder dryRun(boolean dryRun) { + this.dryRun = dryRun; + tracker.setMember($SCHEMA_DRY_RUN); + return this; + } + + /** + * @return this builder. + */ + public Builder configVersion(String configVersion) { + this.configVersion = configVersion; + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder dimensions(ImportEntityReport dimensions) { + this.dimensions = Objects.requireNonNull(dimensions, "dimensions cannot be null"); + tracker.setMember($SCHEMA_DIMENSIONS); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder defaultConfigs(ImportEntityReport defaultConfigs) { + this.defaultConfigs = Objects.requireNonNull(defaultConfigs, "defaultConfigs cannot be null"); + tracker.setMember($SCHEMA_DEFAULT_CONFIGS); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder contexts(ImportEntityReport contexts) { + this.contexts = Objects.requireNonNull(contexts, "contexts cannot be null"); + tracker.setMember($SCHEMA_CONTEXTS); + return this; + } + + @Override + public ImportConfigJsonOutput build() { + tracker.validate(); + return new ImportConfigJsonOutput(this); + } + + @Override + @SuppressWarnings("unchecked") + public void setMemberValue(Schema member, Object value) { + switch (member.memberIndex()) { + case 0 -> strategy((String) SchemaUtils.validateSameMember($SCHEMA_STRATEGY, member, value)); + case 1 -> dryRun((boolean) SchemaUtils.validateSameMember($SCHEMA_DRY_RUN, member, value)); + case 2 -> dimensions((ImportEntityReport) SchemaUtils.validateSameMember($SCHEMA_DIMENSIONS, member, value)); + case 3 -> defaultConfigs((ImportEntityReport) SchemaUtils.validateSameMember($SCHEMA_DEFAULT_CONFIGS, member, value)); + case 4 -> contexts((ImportEntityReport) SchemaUtils.validateSameMember($SCHEMA_CONTEXTS, member, value)); + case 5 -> configVersion((String) SchemaUtils.validateSameMember($SCHEMA_CONFIG_VERSION, member, value)); + default -> ShapeBuilder.super.setMemberValue(member, value); + } + } + + @Override + public ShapeBuilder errorCorrection() { + if (tracker.allSet()) { + return this; + } + if (!tracker.checkMember($SCHEMA_STRATEGY)) { + strategy(""); + } + if (!tracker.checkMember($SCHEMA_DRY_RUN)) { + tracker.setMember($SCHEMA_DRY_RUN); + } + if (!tracker.checkMember($SCHEMA_DIMENSIONS)) { + tracker.setMember($SCHEMA_DIMENSIONS); + } + if (!tracker.checkMember($SCHEMA_DEFAULT_CONFIGS)) { + tracker.setMember($SCHEMA_DEFAULT_CONFIGS); + } + if (!tracker.checkMember($SCHEMA_CONTEXTS)) { + tracker.setMember($SCHEMA_CONTEXTS); + } + return this; + } + + @Override + public Builder deserialize(ShapeDeserializer decoder) { + decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); + return this; + } + + @Override + public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { + decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); + return this; + } + + private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { + private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); + + @Override + public void accept(Builder builder, Schema member, ShapeDeserializer de) { + switch (member.memberIndex()) { + case 0 -> builder.strategy(de.readString(member)); + case 1 -> builder.dryRun(de.readBoolean(member)); + case 2 -> builder.dimensions(ImportEntityReport.builder().deserializeMember(de, member).build()); + case 3 -> builder.defaultConfigs(ImportEntityReport.builder().deserializeMember(de, member).build()); + case 4 -> builder.contexts(ImportEntityReport.builder().deserializeMember(de, member).build()); + case 5 -> builder.configVersion(de.readString(member)); + default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); + } + } + } + } +} + diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigToml.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigToml.java new file mode 100644 index 000000000..85d00fa9a --- /dev/null +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigToml.java @@ -0,0 +1,94 @@ + +package io.juspay.superposition.model; + +import java.util.List; +import software.amazon.smithy.java.core.schema.ApiOperation; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.TypeRegistry; +import software.amazon.smithy.model.pattern.UriPattern; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.HttpTrait; +import software.amazon.smithy.utils.SmithyGenerated; + +/** + * Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a + * single transaction after validating the document. + */ +@SmithyGenerated +public final class ImportConfigToml implements ApiOperation { + public static final ShapeId $ID = ShapeId.from("io.superposition#ImportConfigToml"); + + private static final ImportConfigToml $INSTANCE = new ImportConfigToml(); + + static final Schema $SCHEMA = Schema.createOperation($ID, + HttpTrait.builder().method("POST").code(200).uri(UriPattern.parse("/config/toml/import")).build()); + + private static final TypeRegistry TYPE_REGISTRY = TypeRegistry.builder() + .putType(InternalServerError.$ID, InternalServerError.class, InternalServerError::builder) + .build(); + + private static final List SCHEMES = List.of(ShapeId.from("smithy.api#httpBasicAuth"), ShapeId.from("smithy.api#httpBearerAuth")); + + /** + * Get an instance of this {@code ApiOperation}. + * + * @return An instance of this class. + */ + public static ImportConfigToml instance() { + return $INSTANCE; + } + + private ImportConfigToml() {} + + @Override + public ShapeBuilder inputBuilder() { + return ImportConfigTomlInput.builder(); + } + + @Override + public ShapeBuilder outputBuilder() { + return ImportConfigTomlOutput.builder(); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public Schema inputSchema() { + return ImportConfigTomlInput.$SCHEMA; + } + + @Override + public Schema outputSchema() { + return ImportConfigTomlOutput.$SCHEMA; + } + + @Override + public TypeRegistry errorRegistry() { + return TYPE_REGISTRY; + } + + @Override + public List effectiveAuthSchemes() { + return SCHEMES; + } + + @Override + public Schema inputStreamMember() { + return null; + } + + @Override + public Schema outputStreamMember() { + return null; + } + + @Override + public Schema idempotencyTokenMember() { + return null; + } +} + diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigTomlInput.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigTomlInput.java new file mode 100644 index 000000000..8c132d17f --- /dev/null +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigTomlInput.java @@ -0,0 +1,357 @@ + +package io.juspay.superposition.model; + +import java.util.Objects; +import software.amazon.smithy.java.core.schema.PreludeSchemas; +import software.amazon.smithy.java.core.schema.PresenceTracker; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SchemaUtils; +import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.HttpHeaderTrait; +import software.amazon.smithy.model.traits.HttpPayloadTrait; +import software.amazon.smithy.model.traits.RequiredTrait; +import software.amazon.smithy.utils.SmithyGenerated; + +@SmithyGenerated +public final class ImportConfigTomlInput implements SerializableStruct { + public static final ShapeId $ID = ShapeId.from("io.superposition#ImportConfigTomlInput"); + + public static final Schema $SCHEMA = Schema.structureBuilder($ID) + .putMember("workspace_id", PreludeSchemas.STRING, + new HttpHeaderTrait("x-workspace"), + new RequiredTrait()) + .putMember("org_id", PreludeSchemas.STRING, + new HttpHeaderTrait("x-org-id"), + new RequiredTrait()) + .putMember("strategy", ImportStrategy.$SCHEMA, + new HttpHeaderTrait("x-import-strategy")) + .putMember("on_error", ImportOnError.$SCHEMA, + new HttpHeaderTrait("x-import-on-error")) + .putMember("dry_run", PreludeSchemas.BOOLEAN, + new HttpHeaderTrait("x-import-dry-run")) + .putMember("config_tags", PreludeSchemas.STRING, + new HttpHeaderTrait("x-config-tags")) + .putMember("toml_config", PreludeSchemas.STRING, + new RequiredTrait(), + new HttpPayloadTrait()) + .build(); + + private static final Schema $SCHEMA_WORKSPACE_ID = $SCHEMA.member("workspace_id"); + private static final Schema $SCHEMA_ORG_ID = $SCHEMA.member("org_id"); + private static final Schema $SCHEMA_STRATEGY = $SCHEMA.member("strategy"); + private static final Schema $SCHEMA_ON_ERROR = $SCHEMA.member("on_error"); + private static final Schema $SCHEMA_DRY_RUN = $SCHEMA.member("dry_run"); + private static final Schema $SCHEMA_CONFIG_TAGS = $SCHEMA.member("config_tags"); + private static final Schema $SCHEMA_TOML_CONFIG = $SCHEMA.member("toml_config"); + + private final transient String workspaceId; + private final transient String orgId; + private final transient ImportStrategy strategy; + private final transient ImportOnError onError; + private final transient Boolean dryRun; + private final transient String configTags; + private final transient String tomlConfig; + + private ImportConfigTomlInput(Builder builder) { + this.workspaceId = builder.workspaceId; + this.orgId = builder.orgId; + this.strategy = builder.strategy; + this.onError = builder.onError; + this.dryRun = builder.dryRun; + this.configTags = builder.configTags; + this.tomlConfig = builder.tomlConfig; + } + + public String workspaceId() { + return workspaceId; + } + + public String orgId() { + return orgId; + } + + /** + * How the import applies file entities to the workspace. Defaults to upsert. + */ + public ImportStrategy strategy() { + return strategy; + } + + /** + * Whether to abort (default) or continue on per-entity errors. + */ + public ImportOnError onError() { + return onError; + } + + /** + * When true, validates and summarises the import without persisting anything. Defaults to false. + */ + public Boolean dryRun() { + return dryRun; + } + + public String configTags() { + return configTags; + } + + public String tomlConfig() { + return tomlConfig; + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + ImportConfigTomlInput that = (ImportConfigTomlInput) other; + return Objects.equals(this.workspaceId, that.workspaceId) + && Objects.equals(this.orgId, that.orgId) + && Objects.equals(this.strategy, that.strategy) + && Objects.equals(this.onError, that.onError) + && Objects.equals(this.dryRun, that.dryRun) + && Objects.equals(this.configTags, that.configTags) + && Objects.equals(this.tomlConfig, that.tomlConfig); + } + + @Override + public int hashCode() { + return Objects.hash(workspaceId, orgId, strategy, onError, dryRun, configTags, tomlConfig); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + serializer.writeString($SCHEMA_WORKSPACE_ID, workspaceId); + serializer.writeString($SCHEMA_ORG_ID, orgId); + if (strategy != null) { + serializer.writeString($SCHEMA_STRATEGY, strategy.value()); + } + if (onError != null) { + serializer.writeString($SCHEMA_ON_ERROR, onError.value()); + } + if (dryRun != null) { + serializer.writeBoolean($SCHEMA_DRY_RUN, dryRun); + } + if (configTags != null) { + serializer.writeString($SCHEMA_CONFIG_TAGS, configTags); + } + serializer.writeString($SCHEMA_TOML_CONFIG, tomlConfig); + } + + @Override + @SuppressWarnings("unchecked") + public T getMemberValue(Schema member) { + return switch (member.memberIndex()) { + case 0 -> (T) SchemaUtils.validateSameMember($SCHEMA_WORKSPACE_ID, member, workspaceId); + case 1 -> (T) SchemaUtils.validateSameMember($SCHEMA_ORG_ID, member, orgId); + case 2 -> (T) SchemaUtils.validateSameMember($SCHEMA_TOML_CONFIG, member, tomlConfig); + case 3 -> (T) SchemaUtils.validateSameMember($SCHEMA_STRATEGY, member, strategy); + case 4 -> (T) SchemaUtils.validateSameMember($SCHEMA_ON_ERROR, member, onError); + case 5 -> (T) SchemaUtils.validateSameMember($SCHEMA_DRY_RUN, member, dryRun); + case 6 -> (T) SchemaUtils.validateSameMember($SCHEMA_CONFIG_TAGS, member, configTags); + default -> throw new IllegalArgumentException("Attempted to get non-existent member: " + member.id()); + }; + } + + /** + * Create a new builder containing all the current property values of this object. + * + *

Note: This method performs only a shallow copy of the original properties. + * + * @return a builder for {@link ImportConfigTomlInput}. + */ + public Builder toBuilder() { + var builder = new Builder(); + builder.workspaceId(this.workspaceId); + builder.orgId(this.orgId); + builder.strategy(this.strategy); + builder.onError(this.onError); + builder.dryRun(this.dryRun); + builder.configTags(this.configTags); + builder.tomlConfig(this.tomlConfig); + return builder; + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link ImportConfigTomlInput}. + */ + public static final class Builder implements ShapeBuilder { + private final PresenceTracker tracker = PresenceTracker.of($SCHEMA); + private String workspaceId; + private String orgId; + private ImportStrategy strategy; + private ImportOnError onError; + private Boolean dryRun; + private String configTags; + private String tomlConfig; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + /** + *

Required + * @return this builder. + */ + public Builder workspaceId(String workspaceId) { + this.workspaceId = Objects.requireNonNull(workspaceId, "workspaceId cannot be null"); + tracker.setMember($SCHEMA_WORKSPACE_ID); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder orgId(String orgId) { + this.orgId = Objects.requireNonNull(orgId, "orgId cannot be null"); + tracker.setMember($SCHEMA_ORG_ID); + return this; + } + + /** + * How the import applies file entities to the workspace. Defaults to upsert. + * + * @return this builder. + */ + public Builder strategy(ImportStrategy strategy) { + this.strategy = strategy; + return this; + } + + /** + * Whether to abort (default) or continue on per-entity errors. + * + * @return this builder. + */ + public Builder onError(ImportOnError onError) { + this.onError = onError; + return this; + } + + /** + * When true, validates and summarises the import without persisting anything. Defaults to false. + * + * @return this builder. + */ + public Builder dryRun(boolean dryRun) { + this.dryRun = dryRun; + return this; + } + + /** + * @return this builder. + */ + public Builder configTags(String configTags) { + this.configTags = configTags; + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder tomlConfig(String tomlConfig) { + this.tomlConfig = Objects.requireNonNull(tomlConfig, "tomlConfig cannot be null"); + tracker.setMember($SCHEMA_TOML_CONFIG); + return this; + } + + @Override + public ImportConfigTomlInput build() { + tracker.validate(); + return new ImportConfigTomlInput(this); + } + + @Override + @SuppressWarnings("unchecked") + public void setMemberValue(Schema member, Object value) { + switch (member.memberIndex()) { + case 0 -> workspaceId((String) SchemaUtils.validateSameMember($SCHEMA_WORKSPACE_ID, member, value)); + case 1 -> orgId((String) SchemaUtils.validateSameMember($SCHEMA_ORG_ID, member, value)); + case 2 -> tomlConfig((String) SchemaUtils.validateSameMember($SCHEMA_TOML_CONFIG, member, value)); + case 3 -> strategy((ImportStrategy) SchemaUtils.validateSameMember($SCHEMA_STRATEGY, member, value)); + case 4 -> onError((ImportOnError) SchemaUtils.validateSameMember($SCHEMA_ON_ERROR, member, value)); + case 5 -> dryRun((boolean) SchemaUtils.validateSameMember($SCHEMA_DRY_RUN, member, value)); + case 6 -> configTags((String) SchemaUtils.validateSameMember($SCHEMA_CONFIG_TAGS, member, value)); + default -> ShapeBuilder.super.setMemberValue(member, value); + } + } + + @Override + public ShapeBuilder errorCorrection() { + if (tracker.allSet()) { + return this; + } + if (!tracker.checkMember($SCHEMA_WORKSPACE_ID)) { + workspaceId(""); + } + if (!tracker.checkMember($SCHEMA_ORG_ID)) { + orgId(""); + } + if (!tracker.checkMember($SCHEMA_TOML_CONFIG)) { + tomlConfig(""); + } + return this; + } + + @Override + public Builder deserialize(ShapeDeserializer decoder) { + decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); + return this; + } + + @Override + public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { + decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); + return this; + } + + private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { + private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); + + @Override + public void accept(Builder builder, Schema member, ShapeDeserializer de) { + switch (member.memberIndex()) { + case 0 -> builder.workspaceId(de.readString(member)); + case 1 -> builder.orgId(de.readString(member)); + case 2 -> builder.tomlConfig(de.readString(member)); + case 3 -> builder.strategy(ImportStrategy.builder().deserializeMember(de, member).build()); + case 4 -> builder.onError(ImportOnError.builder().deserializeMember(de, member).build()); + case 5 -> builder.dryRun(de.readBoolean(member)); + case 6 -> builder.configTags(de.readString(member)); + default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); + } + } + } + } +} + diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigTomlOutput.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigTomlOutput.java new file mode 100644 index 000000000..ced56c036 --- /dev/null +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportConfigTomlOutput.java @@ -0,0 +1,325 @@ + +package io.juspay.superposition.model; + +import java.util.Objects; +import software.amazon.smithy.java.core.schema.PreludeSchemas; +import software.amazon.smithy.java.core.schema.PresenceTracker; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SchemaUtils; +import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.RequiredTrait; +import software.amazon.smithy.utils.SmithyGenerated; + +/** + * Summary of what an import created, updated, skipped or deleted. + */ +@SmithyGenerated +public final class ImportConfigTomlOutput implements SerializableStruct { + public static final ShapeId $ID = ShapeId.from("io.superposition#ImportConfigOutput"); + + public static final Schema $SCHEMA = Schema.structureBuilder($ID) + .putMember("strategy", PreludeSchemas.STRING, + new RequiredTrait()) + .putMember("dry_run", PreludeSchemas.BOOLEAN, + new RequiredTrait()) + .putMember("config_version", PreludeSchemas.STRING) + .putMember("dimensions", ImportEntityReport.$SCHEMA, + new RequiredTrait()) + .putMember("default_configs", ImportEntityReport.$SCHEMA, + new RequiredTrait()) + .putMember("contexts", ImportEntityReport.$SCHEMA, + new RequiredTrait()) + .build(); + + private static final Schema $SCHEMA_STRATEGY = $SCHEMA.member("strategy"); + private static final Schema $SCHEMA_DRY_RUN = $SCHEMA.member("dry_run"); + private static final Schema $SCHEMA_CONFIG_VERSION = $SCHEMA.member("config_version"); + private static final Schema $SCHEMA_DIMENSIONS = $SCHEMA.member("dimensions"); + private static final Schema $SCHEMA_DEFAULT_CONFIGS = $SCHEMA.member("default_configs"); + private static final Schema $SCHEMA_CONTEXTS = $SCHEMA.member("contexts"); + + private final transient String strategy; + private final transient boolean dryRun; + private final transient String configVersion; + private final transient ImportEntityReport dimensions; + private final transient ImportEntityReport defaultConfigs; + private final transient ImportEntityReport contexts; + + private ImportConfigTomlOutput(Builder builder) { + this.strategy = builder.strategy; + this.dryRun = builder.dryRun; + this.configVersion = builder.configVersion; + this.dimensions = builder.dimensions; + this.defaultConfigs = builder.defaultConfigs; + this.contexts = builder.contexts; + } + + public String strategy() { + return strategy; + } + + public boolean dryRun() { + return dryRun; + } + + public String configVersion() { + return configVersion; + } + + public ImportEntityReport dimensions() { + return dimensions; + } + + public ImportEntityReport defaultConfigs() { + return defaultConfigs; + } + + public ImportEntityReport contexts() { + return contexts; + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + ImportConfigTomlOutput that = (ImportConfigTomlOutput) other; + return Objects.equals(this.strategy, that.strategy) + && this.dryRun == that.dryRun + && Objects.equals(this.configVersion, that.configVersion) + && Objects.equals(this.dimensions, that.dimensions) + && Objects.equals(this.defaultConfigs, that.defaultConfigs) + && Objects.equals(this.contexts, that.contexts); + } + + @Override + public int hashCode() { + return Objects.hash(strategy, dryRun, configVersion, dimensions, defaultConfigs, contexts); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + serializer.writeString($SCHEMA_STRATEGY, strategy); + serializer.writeBoolean($SCHEMA_DRY_RUN, dryRun); + if (configVersion != null) { + serializer.writeString($SCHEMA_CONFIG_VERSION, configVersion); + } + if (dimensions != null) { + serializer.writeStruct($SCHEMA_DIMENSIONS, dimensions); + } + if (defaultConfigs != null) { + serializer.writeStruct($SCHEMA_DEFAULT_CONFIGS, defaultConfigs); + } + if (contexts != null) { + serializer.writeStruct($SCHEMA_CONTEXTS, contexts); + } + } + + @Override + @SuppressWarnings("unchecked") + public T getMemberValue(Schema member) { + return switch (member.memberIndex()) { + case 0 -> (T) SchemaUtils.validateSameMember($SCHEMA_STRATEGY, member, strategy); + case 1 -> (T) SchemaUtils.validateSameMember($SCHEMA_DRY_RUN, member, dryRun); + case 2 -> (T) SchemaUtils.validateSameMember($SCHEMA_DIMENSIONS, member, dimensions); + case 3 -> (T) SchemaUtils.validateSameMember($SCHEMA_DEFAULT_CONFIGS, member, defaultConfigs); + case 4 -> (T) SchemaUtils.validateSameMember($SCHEMA_CONTEXTS, member, contexts); + case 5 -> (T) SchemaUtils.validateSameMember($SCHEMA_CONFIG_VERSION, member, configVersion); + default -> throw new IllegalArgumentException("Attempted to get non-existent member: " + member.id()); + }; + } + + /** + * Create a new builder containing all the current property values of this object. + * + *

Note: This method performs only a shallow copy of the original properties. + * + * @return a builder for {@link ImportConfigTomlOutput}. + */ + public Builder toBuilder() { + var builder = new Builder(); + builder.strategy(this.strategy); + builder.dryRun(this.dryRun); + builder.configVersion(this.configVersion); + builder.dimensions(this.dimensions); + builder.defaultConfigs(this.defaultConfigs); + builder.contexts(this.contexts); + return builder; + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link ImportConfigTomlOutput}. + */ + public static final class Builder implements ShapeBuilder { + private final PresenceTracker tracker = PresenceTracker.of($SCHEMA); + private String strategy; + private boolean dryRun; + private String configVersion; + private ImportEntityReport dimensions; + private ImportEntityReport defaultConfigs; + private ImportEntityReport contexts; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + /** + *

Required + * @return this builder. + */ + public Builder strategy(String strategy) { + this.strategy = Objects.requireNonNull(strategy, "strategy cannot be null"); + tracker.setMember($SCHEMA_STRATEGY); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder dryRun(boolean dryRun) { + this.dryRun = dryRun; + tracker.setMember($SCHEMA_DRY_RUN); + return this; + } + + /** + * @return this builder. + */ + public Builder configVersion(String configVersion) { + this.configVersion = configVersion; + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder dimensions(ImportEntityReport dimensions) { + this.dimensions = Objects.requireNonNull(dimensions, "dimensions cannot be null"); + tracker.setMember($SCHEMA_DIMENSIONS); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder defaultConfigs(ImportEntityReport defaultConfigs) { + this.defaultConfigs = Objects.requireNonNull(defaultConfigs, "defaultConfigs cannot be null"); + tracker.setMember($SCHEMA_DEFAULT_CONFIGS); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder contexts(ImportEntityReport contexts) { + this.contexts = Objects.requireNonNull(contexts, "contexts cannot be null"); + tracker.setMember($SCHEMA_CONTEXTS); + return this; + } + + @Override + public ImportConfigTomlOutput build() { + tracker.validate(); + return new ImportConfigTomlOutput(this); + } + + @Override + @SuppressWarnings("unchecked") + public void setMemberValue(Schema member, Object value) { + switch (member.memberIndex()) { + case 0 -> strategy((String) SchemaUtils.validateSameMember($SCHEMA_STRATEGY, member, value)); + case 1 -> dryRun((boolean) SchemaUtils.validateSameMember($SCHEMA_DRY_RUN, member, value)); + case 2 -> dimensions((ImportEntityReport) SchemaUtils.validateSameMember($SCHEMA_DIMENSIONS, member, value)); + case 3 -> defaultConfigs((ImportEntityReport) SchemaUtils.validateSameMember($SCHEMA_DEFAULT_CONFIGS, member, value)); + case 4 -> contexts((ImportEntityReport) SchemaUtils.validateSameMember($SCHEMA_CONTEXTS, member, value)); + case 5 -> configVersion((String) SchemaUtils.validateSameMember($SCHEMA_CONFIG_VERSION, member, value)); + default -> ShapeBuilder.super.setMemberValue(member, value); + } + } + + @Override + public ShapeBuilder errorCorrection() { + if (tracker.allSet()) { + return this; + } + if (!tracker.checkMember($SCHEMA_STRATEGY)) { + strategy(""); + } + if (!tracker.checkMember($SCHEMA_DRY_RUN)) { + tracker.setMember($SCHEMA_DRY_RUN); + } + if (!tracker.checkMember($SCHEMA_DIMENSIONS)) { + tracker.setMember($SCHEMA_DIMENSIONS); + } + if (!tracker.checkMember($SCHEMA_DEFAULT_CONFIGS)) { + tracker.setMember($SCHEMA_DEFAULT_CONFIGS); + } + if (!tracker.checkMember($SCHEMA_CONTEXTS)) { + tracker.setMember($SCHEMA_CONTEXTS); + } + return this; + } + + @Override + public Builder deserialize(ShapeDeserializer decoder) { + decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); + return this; + } + + @Override + public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { + decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); + return this; + } + + private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { + private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); + + @Override + public void accept(Builder builder, Schema member, ShapeDeserializer de) { + switch (member.memberIndex()) { + case 0 -> builder.strategy(de.readString(member)); + case 1 -> builder.dryRun(de.readBoolean(member)); + case 2 -> builder.dimensions(ImportEntityReport.builder().deserializeMember(de, member).build()); + case 3 -> builder.defaultConfigs(ImportEntityReport.builder().deserializeMember(de, member).build()); + case 4 -> builder.contexts(ImportEntityReport.builder().deserializeMember(de, member).build()); + case 5 -> builder.configVersion(de.readString(member)); + default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); + } + } + } + } +} + diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportEntityReport.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportEntityReport.java new file mode 100644 index 000000000..8d10c79a7 --- /dev/null +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportEntityReport.java @@ -0,0 +1,299 @@ + +package io.juspay.superposition.model; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import software.amazon.smithy.java.core.schema.PreludeSchemas; +import software.amazon.smithy.java.core.schema.PresenceTracker; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SchemaUtils; +import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.RequiredTrait; +import software.amazon.smithy.utils.SmithyGenerated; + +/** + * Per-entity outcome counts for an import. + */ +@SmithyGenerated +public final class ImportEntityReport implements SerializableStruct { + public static final ShapeId $ID = ShapeId.from("io.superposition#ImportEntityReport"); + + public static final Schema $SCHEMA = Schema.structureBuilder($ID) + .putMember("created", PreludeSchemas.INTEGER, + new RequiredTrait()) + .putMember("updated", PreludeSchemas.INTEGER, + new RequiredTrait()) + .putMember("skipped", PreludeSchemas.INTEGER, + new RequiredTrait()) + .putMember("deleted", PreludeSchemas.INTEGER, + new RequiredTrait()) + .putMember("errors", SharedSchemas.IMPORT_ERROR_LIST) + .build(); + + private static final Schema $SCHEMA_CREATED = $SCHEMA.member("created"); + private static final Schema $SCHEMA_UPDATED = $SCHEMA.member("updated"); + private static final Schema $SCHEMA_SKIPPED = $SCHEMA.member("skipped"); + private static final Schema $SCHEMA_DELETED = $SCHEMA.member("deleted"); + private static final Schema $SCHEMA_ERRORS = $SCHEMA.member("errors"); + + private final transient int created; + private final transient int updated; + private final transient int skipped; + private final transient int deleted; + private final transient List errors; + + private ImportEntityReport(Builder builder) { + this.created = builder.created; + this.updated = builder.updated; + this.skipped = builder.skipped; + this.deleted = builder.deleted; + this.errors = builder.errors == null ? null : Collections.unmodifiableList(builder.errors); + } + + public int created() { + return created; + } + + public int updated() { + return updated; + } + + public int skipped() { + return skipped; + } + + public int deleted() { + return deleted; + } + + public List errors() { + if (errors == null) { + return Collections.emptyList(); + } + return errors; + } + + public boolean hasErrors() { + return errors != null; + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + ImportEntityReport that = (ImportEntityReport) other; + return this.created == that.created + && this.updated == that.updated + && this.skipped == that.skipped + && this.deleted == that.deleted + && Objects.equals(this.errors, that.errors); + } + + @Override + public int hashCode() { + return Objects.hash(created, updated, skipped, deleted, errors); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + serializer.writeInteger($SCHEMA_CREATED, created); + serializer.writeInteger($SCHEMA_UPDATED, updated); + serializer.writeInteger($SCHEMA_SKIPPED, skipped); + serializer.writeInteger($SCHEMA_DELETED, deleted); + if (errors != null) { + serializer.writeList($SCHEMA_ERRORS, errors, errors.size(), SharedSerde.ImportErrorListSerializer.INSTANCE); + } + } + + @Override + @SuppressWarnings("unchecked") + public T getMemberValue(Schema member) { + return switch (member.memberIndex()) { + case 0 -> (T) SchemaUtils.validateSameMember($SCHEMA_CREATED, member, created); + case 1 -> (T) SchemaUtils.validateSameMember($SCHEMA_UPDATED, member, updated); + case 2 -> (T) SchemaUtils.validateSameMember($SCHEMA_SKIPPED, member, skipped); + case 3 -> (T) SchemaUtils.validateSameMember($SCHEMA_DELETED, member, deleted); + case 4 -> (T) SchemaUtils.validateSameMember($SCHEMA_ERRORS, member, errors); + default -> throw new IllegalArgumentException("Attempted to get non-existent member: " + member.id()); + }; + } + + /** + * Create a new builder containing all the current property values of this object. + * + *

Note: This method performs only a shallow copy of the original properties. + * + * @return a builder for {@link ImportEntityReport}. + */ + public Builder toBuilder() { + var builder = new Builder(); + builder.created(this.created); + builder.updated(this.updated); + builder.skipped(this.skipped); + builder.deleted(this.deleted); + builder.errors(this.errors); + return builder; + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link ImportEntityReport}. + */ + public static final class Builder implements ShapeBuilder { + private final PresenceTracker tracker = PresenceTracker.of($SCHEMA); + private int created; + private int updated; + private int skipped; + private int deleted; + private List errors; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + /** + *

Required + * @return this builder. + */ + public Builder created(int created) { + this.created = created; + tracker.setMember($SCHEMA_CREATED); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder updated(int updated) { + this.updated = updated; + tracker.setMember($SCHEMA_UPDATED); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder skipped(int skipped) { + this.skipped = skipped; + tracker.setMember($SCHEMA_SKIPPED); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder deleted(int deleted) { + this.deleted = deleted; + tracker.setMember($SCHEMA_DELETED); + return this; + } + + /** + * @return this builder. + */ + public Builder errors(List errors) { + this.errors = errors; + return this; + } + + @Override + public ImportEntityReport build() { + tracker.validate(); + return new ImportEntityReport(this); + } + + @Override + @SuppressWarnings("unchecked") + public void setMemberValue(Schema member, Object value) { + switch (member.memberIndex()) { + case 0 -> created((int) SchemaUtils.validateSameMember($SCHEMA_CREATED, member, value)); + case 1 -> updated((int) SchemaUtils.validateSameMember($SCHEMA_UPDATED, member, value)); + case 2 -> skipped((int) SchemaUtils.validateSameMember($SCHEMA_SKIPPED, member, value)); + case 3 -> deleted((int) SchemaUtils.validateSameMember($SCHEMA_DELETED, member, value)); + case 4 -> errors((List) SchemaUtils.validateSameMember($SCHEMA_ERRORS, member, value)); + default -> ShapeBuilder.super.setMemberValue(member, value); + } + } + + @Override + public ShapeBuilder errorCorrection() { + if (tracker.allSet()) { + return this; + } + if (!tracker.checkMember($SCHEMA_CREATED)) { + tracker.setMember($SCHEMA_CREATED); + } + if (!tracker.checkMember($SCHEMA_UPDATED)) { + tracker.setMember($SCHEMA_UPDATED); + } + if (!tracker.checkMember($SCHEMA_SKIPPED)) { + tracker.setMember($SCHEMA_SKIPPED); + } + if (!tracker.checkMember($SCHEMA_DELETED)) { + tracker.setMember($SCHEMA_DELETED); + } + return this; + } + + @Override + public Builder deserialize(ShapeDeserializer decoder) { + decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); + return this; + } + + @Override + public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { + decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); + return this; + } + + private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { + private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); + + @Override + public void accept(Builder builder, Schema member, ShapeDeserializer de) { + switch (member.memberIndex()) { + case 0 -> builder.created(de.readInteger(member)); + case 1 -> builder.updated(de.readInteger(member)); + case 2 -> builder.skipped(de.readInteger(member)); + case 3 -> builder.deleted(de.readInteger(member)); + case 4 -> builder.errors(SharedSerde.deserializeImportErrorList(member, de)); + default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); + } + } + } + } +} + diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportErrorItem.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportErrorItem.java new file mode 100644 index 000000000..8b4407529 --- /dev/null +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportErrorItem.java @@ -0,0 +1,204 @@ + +package io.juspay.superposition.model; + +import java.util.Objects; +import software.amazon.smithy.java.core.schema.PreludeSchemas; +import software.amazon.smithy.java.core.schema.PresenceTracker; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SchemaUtils; +import software.amazon.smithy.java.core.schema.SerializableStruct; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.traits.RequiredTrait; +import software.amazon.smithy.utils.SmithyGenerated; + +@SmithyGenerated +public final class ImportErrorItem implements SerializableStruct { + public static final ShapeId $ID = ShapeId.from("io.superposition#ImportErrorItem"); + + public static final Schema $SCHEMA = Schema.structureBuilder($ID) + .putMember("id", PreludeSchemas.STRING, + new RequiredTrait()) + .putMember("message", PreludeSchemas.STRING, + new RequiredTrait()) + .build(); + + private static final Schema $SCHEMA_ID = $SCHEMA.member("id"); + private static final Schema $SCHEMA_MESSAGE = $SCHEMA.member("message"); + + private final transient String id; + private final transient String message; + + private ImportErrorItem(Builder builder) { + this.id = builder.id; + this.message = builder.message; + } + + public String id() { + return id; + } + + public String message() { + return message; + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + ImportErrorItem that = (ImportErrorItem) other; + return Objects.equals(this.id, that.id) + && Objects.equals(this.message, that.message); + } + + @Override + public int hashCode() { + return Objects.hash(id, message); + } + + @Override + public Schema schema() { + return $SCHEMA; + } + + @Override + public void serializeMembers(ShapeSerializer serializer) { + serializer.writeString($SCHEMA_ID, id); + serializer.writeString($SCHEMA_MESSAGE, message); + } + + @Override + @SuppressWarnings("unchecked") + public T getMemberValue(Schema member) { + return switch (member.memberIndex()) { + case 0 -> (T) SchemaUtils.validateSameMember($SCHEMA_ID, member, id); + case 1 -> (T) SchemaUtils.validateSameMember($SCHEMA_MESSAGE, member, message); + default -> throw new IllegalArgumentException("Attempted to get non-existent member: " + member.id()); + }; + } + + /** + * Create a new builder containing all the current property values of this object. + * + *

Note: This method performs only a shallow copy of the original properties. + * + * @return a builder for {@link ImportErrorItem}. + */ + public Builder toBuilder() { + var builder = new Builder(); + builder.id(this.id); + builder.message(this.message); + return builder; + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link ImportErrorItem}. + */ + public static final class Builder implements ShapeBuilder { + private final PresenceTracker tracker = PresenceTracker.of($SCHEMA); + private String id; + private String message; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + /** + *

Required + * @return this builder. + */ + public Builder id(String id) { + this.id = Objects.requireNonNull(id, "id cannot be null"); + tracker.setMember($SCHEMA_ID); + return this; + } + + /** + *

Required + * @return this builder. + */ + public Builder message(String message) { + this.message = Objects.requireNonNull(message, "message cannot be null"); + tracker.setMember($SCHEMA_MESSAGE); + return this; + } + + @Override + public ImportErrorItem build() { + tracker.validate(); + return new ImportErrorItem(this); + } + + @Override + @SuppressWarnings("unchecked") + public void setMemberValue(Schema member, Object value) { + switch (member.memberIndex()) { + case 0 -> id((String) SchemaUtils.validateSameMember($SCHEMA_ID, member, value)); + case 1 -> message((String) SchemaUtils.validateSameMember($SCHEMA_MESSAGE, member, value)); + default -> ShapeBuilder.super.setMemberValue(member, value); + } + } + + @Override + public ShapeBuilder errorCorrection() { + if (tracker.allSet()) { + return this; + } + if (!tracker.checkMember($SCHEMA_ID)) { + id(""); + } + if (!tracker.checkMember($SCHEMA_MESSAGE)) { + message(""); + } + return this; + } + + @Override + public Builder deserialize(ShapeDeserializer decoder) { + decoder.readStruct($SCHEMA, this, $InnerDeserializer.INSTANCE); + return this; + } + + @Override + public Builder deserializeMember(ShapeDeserializer decoder, Schema schema) { + decoder.readStruct(schema.assertMemberTargetIs($SCHEMA), this, $InnerDeserializer.INSTANCE); + return this; + } + + private static final class $InnerDeserializer implements ShapeDeserializer.StructMemberConsumer { + private static final $InnerDeserializer INSTANCE = new $InnerDeserializer(); + + @Override + public void accept(Builder builder, Schema member, ShapeDeserializer de) { + switch (member.memberIndex()) { + case 0 -> builder.id(de.readString(member)); + case 1 -> builder.message(de.readString(member)); + default -> throw new IllegalArgumentException("Unexpected member: " + member.memberName()); + } + } + } + } +} + diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportOnError.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportOnError.java new file mode 100644 index 000000000..1399faa01 --- /dev/null +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportOnError.java @@ -0,0 +1,164 @@ + +package io.juspay.superposition.model; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SerializableShape; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.SmithyGenerated; + +/** + * How an import reacts when an individual entity fails to apply. + */ +@SmithyGenerated +public final class ImportOnError implements SerializableShape { + public static final ShapeId $ID = ShapeId.from("io.superposition#ImportOnError"); + /** + * Roll the whole import back on the first error. + */ + public static final ImportOnError ABORT = new ImportOnError(Type.ABORT, "abort"); + /** + * Apply everything that is valid and report per-entity errors. + */ + public static final ImportOnError CONTINUE = new ImportOnError(Type.CONTINUE, "continue"); + private static final List $TYPES = List.of(ABORT, CONTINUE); + + public static final Schema $SCHEMA = Schema.createEnum($ID, + Set.of(ABORT.value, CONTINUE.value) + ); + + private final String value; + private final Type type; + + private ImportOnError(Type type, String value) { + this.type = Objects.requireNonNull(type, "type cannot be null"); + this.value = Objects.requireNonNull(value, "value cannot be null"); + } + + /** + * Enum representing the possible variants of {@link ImportOnError}. + */ + public enum Type { + $UNKNOWN, + ABORT, + CONTINUE + } + + /** + * Value contained by this Enum. + */ + public String value() { + return value; + } + + /** + * Type of this Enum variant. + */ + public Type type() { + return type; + } + + /** + * Create an Enum of an {@link Type#$UNKNOWN} type containing a value. + * + * @param value value contained by unknown Enum. + */ + public static ImportOnError unknown(String value) { + return new ImportOnError(Type.$UNKNOWN, value); + } + + /** + * Returns an unmodifiable list containing the constants of this enum type, in the order declared. + */ + public static List values() { + return $TYPES; + } + + @Override + public void serialize(ShapeSerializer serializer) { + serializer.writeString($SCHEMA, this.value()); + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + /** + * Returns a {@link ImportOnError} constant with the specified value. + * + * @param value value to create {@code ImportOnError} from. + * @throws IllegalArgumentException if value does not match a known value. + */ + public static ImportOnError from(String value) { + return switch (value) { + case "abort" -> ABORT; + case "continue" -> CONTINUE; + default -> throw new IllegalArgumentException("Unknown value: " + value); + }; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + ImportOnError that = (ImportOnError) other; + return this.value.equals(that.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link ImportOnError}. + */ + public static final class Builder implements ShapeBuilder { + private String value; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + private Builder value(String value) { + this.value = Objects.requireNonNull(value, "Enum value cannot be null"); + return this; + } + + @Override + public ImportOnError build() { + return switch (value) { + case "abort" -> ABORT; + case "continue" -> CONTINUE; + default -> new ImportOnError(Type.$UNKNOWN, value); + }; + } + + @Override + public Builder deserialize(ShapeDeserializer de) { + return value(de.readString($SCHEMA)); + } + } +} + diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportStrategy.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportStrategy.java new file mode 100644 index 000000000..c3fea9211 --- /dev/null +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/ImportStrategy.java @@ -0,0 +1,174 @@ + +package io.juspay.superposition.model; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import software.amazon.smithy.java.core.schema.Schema; +import software.amazon.smithy.java.core.schema.SerializableShape; +import software.amazon.smithy.java.core.schema.ShapeBuilder; +import software.amazon.smithy.java.core.serde.ShapeDeserializer; +import software.amazon.smithy.java.core.serde.ShapeSerializer; +import software.amazon.smithy.java.core.serde.ToStringSerializer; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.SmithyGenerated; + +/** + * How an import applies file entities to the workspace. + */ +@SmithyGenerated +public final class ImportStrategy implements SerializableShape { + public static final ShapeId $ID = ShapeId.from("io.superposition#ImportStrategy"); + /** + * Create entities that are present in the file but missing from the workspace. Existing entities are + * skipped. Nothing is deleted. + */ + public static final ImportStrategy CREATE_ONLY = new ImportStrategy(Type.CREATE_ONLY, "create_only"); + /** + * Create missing entities and update existing entities from the file. Entities absent from the file + * are left untouched. + */ + public static final ImportStrategy UPSERT = new ImportStrategy(Type.UPSERT, "upsert"); + /** + * Mirror the file: create missing entities, update existing entities, and delete dimensions, + * default-configs and contexts that are absent from it. + */ + public static final ImportStrategy REPLACE = new ImportStrategy(Type.REPLACE, "replace"); + private static final List $TYPES = List.of(CREATE_ONLY, UPSERT, REPLACE); + + public static final Schema $SCHEMA = Schema.createEnum($ID, + Set.of(CREATE_ONLY.value, UPSERT.value, REPLACE.value) + ); + + private final String value; + private final Type type; + + private ImportStrategy(Type type, String value) { + this.type = Objects.requireNonNull(type, "type cannot be null"); + this.value = Objects.requireNonNull(value, "value cannot be null"); + } + + /** + * Enum representing the possible variants of {@link ImportStrategy}. + */ + public enum Type { + $UNKNOWN, + CREATE_ONLY, + UPSERT, + REPLACE + } + + /** + * Value contained by this Enum. + */ + public String value() { + return value; + } + + /** + * Type of this Enum variant. + */ + public Type type() { + return type; + } + + /** + * Create an Enum of an {@link Type#$UNKNOWN} type containing a value. + * + * @param value value contained by unknown Enum. + */ + public static ImportStrategy unknown(String value) { + return new ImportStrategy(Type.$UNKNOWN, value); + } + + /** + * Returns an unmodifiable list containing the constants of this enum type, in the order declared. + */ + public static List values() { + return $TYPES; + } + + @Override + public void serialize(ShapeSerializer serializer) { + serializer.writeString($SCHEMA, this.value()); + } + + @Override + public String toString() { + return ToStringSerializer.serialize(this); + } + + /** + * Returns a {@link ImportStrategy} constant with the specified value. + * + * @param value value to create {@code ImportStrategy} from. + * @throws IllegalArgumentException if value does not match a known value. + */ + public static ImportStrategy from(String value) { + return switch (value) { + case "create_only" -> CREATE_ONLY; + case "upsert" -> UPSERT; + case "replace" -> REPLACE; + default -> throw new IllegalArgumentException("Unknown value: " + value); + }; + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + if (other == null || getClass() != other.getClass()) { + return false; + } + ImportStrategy that = (ImportStrategy) other; + return this.value.equals(that.value); + } + + @Override + public int hashCode() { + return value.hashCode(); + } + + /** + * @return returns a new Builder. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link ImportStrategy}. + */ + public static final class Builder implements ShapeBuilder { + private String value; + + private Builder() {} + + @Override + public Schema schema() { + return $SCHEMA; + } + + private Builder value(String value) { + this.value = Objects.requireNonNull(value, "Enum value cannot be null"); + return this; + } + + @Override + public ImportStrategy build() { + return switch (value) { + case "create_only" -> CREATE_ONLY; + case "upsert" -> UPSERT; + case "replace" -> REPLACE; + default -> new ImportStrategy(Type.$UNKNOWN, value); + }; + } + + @Override + public Builder deserialize(ShapeDeserializer de) { + return value(de.readString($SCHEMA)); + } + } +} + diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSchemas.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSchemas.java index c28d3be76..5e9325de4 100644 --- a/clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSchemas.java +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSchemas.java @@ -164,6 +164,10 @@ final class SharedSchemas { .putMember("member", TypeTemplatesResponse.$SCHEMA) .build(); + static final Schema IMPORT_ERROR_LIST = Schema.listBuilder(ShapeId.from("io.superposition#ImportErrorList")) + .putMember("member", ImportErrorItem.$SCHEMA) + .build(); + static final Schema ORGANISATION_LIST = Schema.listBuilder(ShapeId.from("io.superposition#OrganisationList")) .putMember("member", OrganisationResponse.$SCHEMA) .build(); diff --git a/clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSerde.java b/clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSerde.java index 0cb0a253f..cb40ca724 100644 --- a/clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSerde.java +++ b/clients/java/sdk/src/main/java/io/juspay/superposition/model/SharedSerde.java @@ -173,6 +173,37 @@ public void accept(List state, ShapeDeserializer deseriali } } + static final class ImportErrorListSerializer implements BiConsumer, ShapeSerializer> { + static final ImportErrorListSerializer INSTANCE = new ImportErrorListSerializer(); + + @Override + public void accept(List values, ShapeSerializer serializer) { + for (var value : values) { + serializer.writeStruct(SharedSchemas.IMPORT_ERROR_LIST.listMember(), value); + } + } + } + + static List deserializeImportErrorList(Schema schema, ShapeDeserializer deserializer) { + var size = deserializer.containerSize(); + List result = size == -1 ? new ArrayList<>() : new ArrayList<>(size); + deserializer.readList(schema, result, ImportErrorList$MemberDeserializer.INSTANCE); + return result; + } + + private static final class ImportErrorList$MemberDeserializer implements ShapeDeserializer.ListMemberConsumer> { + static final ImportErrorList$MemberDeserializer INSTANCE = new ImportErrorList$MemberDeserializer(); + + @Override + public void accept(List state, ShapeDeserializer deserializer) { + if (deserializer.isNull()) { + + return; + } + state.add(ImportErrorItem.builder().deserializeMember(deserializer, SharedSchemas.IMPORT_ERROR_LIST.listMember()).build()); + } + } + static final class TypeTemplatesListSerializer implements BiConsumer, ShapeSerializer> { static final TypeTemplatesListSerializer INSTANCE = new TypeTemplatesListSerializer(); diff --git a/clients/javascript/sdk/src/Superposition.ts b/clients/javascript/sdk/src/Superposition.ts index 57db6acd6..171dd6f31 100644 --- a/clients/javascript/sdk/src/Superposition.ts +++ b/clients/javascript/sdk/src/Superposition.ts @@ -253,6 +253,16 @@ import { GetWorkspaceCommandInput, GetWorkspaceCommandOutput, } from "./commands/GetWorkspaceCommand"; +import { + ImportConfigJsonCommand, + ImportConfigJsonCommandInput, + ImportConfigJsonCommandOutput, +} from "./commands/ImportConfigJsonCommand"; +import { + ImportConfigTomlCommand, + ImportConfigTomlCommandInput, + ImportConfigTomlCommandOutput, +} from "./commands/ImportConfigTomlCommand"; import { ListAuditLogsCommand, ListAuditLogsCommandInput, @@ -492,6 +502,8 @@ const commands = { GetWebhookCommand, GetWebhookByEventCommand, GetWorkspaceCommand, + ImportConfigJsonCommand, + ImportConfigTomlCommand, ListAuditLogsCommand, ListContextsCommand, ListDefaultConfigsCommand, @@ -1382,6 +1394,40 @@ export interface Superposition { cb: (err: any, data?: GetWorkspaceCommandOutput) => void ): void; + /** + * @see {@link ImportConfigJsonCommand} + */ + importConfigJson( + args: ImportConfigJsonCommandInput, + options?: __HttpHandlerOptions, + ): Promise; + importConfigJson( + args: ImportConfigJsonCommandInput, + cb: (err: any, data?: ImportConfigJsonCommandOutput) => void + ): void; + importConfigJson( + args: ImportConfigJsonCommandInput, + options: __HttpHandlerOptions, + cb: (err: any, data?: ImportConfigJsonCommandOutput) => void + ): void; + + /** + * @see {@link ImportConfigTomlCommand} + */ + importConfigToml( + args: ImportConfigTomlCommandInput, + options?: __HttpHandlerOptions, + ): Promise; + importConfigToml( + args: ImportConfigTomlCommandInput, + cb: (err: any, data?: ImportConfigTomlCommandOutput) => void + ): void; + importConfigToml( + args: ImportConfigTomlCommandInput, + options: __HttpHandlerOptions, + cb: (err: any, data?: ImportConfigTomlCommandOutput) => void + ): void; + /** * @see {@link ListAuditLogsCommand} */ diff --git a/clients/javascript/sdk/src/SuperpositionClient.ts b/clients/javascript/sdk/src/SuperpositionClient.ts index 0428476fa..3d15a387f 100644 --- a/clients/javascript/sdk/src/SuperpositionClient.ts +++ b/clients/javascript/sdk/src/SuperpositionClient.ts @@ -205,6 +205,14 @@ import { GetWorkspaceCommandInput, GetWorkspaceCommandOutput, } from "./commands/GetWorkspaceCommand"; +import { + ImportConfigJsonCommandInput, + ImportConfigJsonCommandOutput, +} from "./commands/ImportConfigJsonCommand"; +import { + ImportConfigTomlCommandInput, + ImportConfigTomlCommandOutput, +} from "./commands/ImportConfigTomlCommand"; import { ListAuditLogsCommandInput, ListAuditLogsCommandOutput, @@ -469,6 +477,8 @@ export type ServiceInputTypes = | GetWebhookByEventCommandInput | GetWebhookCommandInput | GetWorkspaceCommandInput + | ImportConfigJsonCommandInput + | ImportConfigTomlCommandInput | ListAuditLogsCommandInput | ListContextsCommandInput | ListDefaultConfigsCommandInput @@ -561,6 +571,8 @@ export type ServiceOutputTypes = | GetWebhookByEventCommandOutput | GetWebhookCommandOutput | GetWorkspaceCommandOutput + | ImportConfigJsonCommandOutput + | ImportConfigTomlCommandOutput | ListAuditLogsCommandOutput | ListContextsCommandOutput | ListDefaultConfigsCommandOutput diff --git a/clients/javascript/sdk/src/commands/ImportConfigJsonCommand.ts b/clients/javascript/sdk/src/commands/ImportConfigJsonCommand.ts new file mode 100644 index 000000000..a9db222e5 --- /dev/null +++ b/clients/javascript/sdk/src/commands/ImportConfigJsonCommand.ts @@ -0,0 +1,139 @@ +// smithy-typescript generated code +import { + ServiceInputTypes, + ServiceOutputTypes, + SuperpositionClientResolvedConfig, +} from "../SuperpositionClient"; +import { + ImportConfigJsonInput, + ImportConfigOutput, +} from "../models/models_0"; +import { + de_ImportConfigJsonCommand, + se_ImportConfigJsonCommand, +} from "../protocols/Aws_restJson1"; +import { getSerdePlugin } from "@smithy/middleware-serde"; +import { Command as $Command } from "@smithy/smithy-client"; +import { MetadataBearer as __MetadataBearer } from "@smithy/types"; + +/** + * @public + */ +export type { __MetadataBearer }; +export { $Command }; +/** + * @public + * + * The input for {@link ImportConfigJsonCommand}. + */ +export interface ImportConfigJsonCommandInput extends ImportConfigJsonInput {} +/** + * @public + * + * The output of {@link ImportConfigJsonCommand}. + */ +export interface ImportConfigJsonCommandOutput extends ImportConfigOutput, __MetadataBearer {} + +/** + * Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a single transaction after validating the document. + * @example + * Use a bare-bones client and the command you need to make an API call. + * ```javascript + * import { SuperpositionClient, ImportConfigJsonCommand } from "superposition-sdk"; // ES Modules import + * // const { SuperpositionClient, ImportConfigJsonCommand } = require("superposition-sdk"); // CommonJS import + * const client = new SuperpositionClient(config); + * const input = { // ImportConfigJsonInput + * workspace_id: "STRING_VALUE", // required + * org_id: "STRING_VALUE", // required + * strategy: "create_only" || "upsert" || "replace", + * on_error: "abort" || "continue", + * dry_run: true || false, + * config_tags: "STRING_VALUE", + * json_config: "STRING_VALUE", // required + * }; + * const command = new ImportConfigJsonCommand(input); + * const response = await client.send(command); + * // { // ImportConfigOutput + * // strategy: "STRING_VALUE", // required + * // dry_run: true || false, // required + * // config_version: "STRING_VALUE", + * // dimensions: { // ImportEntityReport + * // created: Number("int"), // required + * // updated: Number("int"), // required + * // skipped: Number("int"), // required + * // deleted: Number("int"), // required + * // errors: [ // ImportErrorList + * // { // ImportErrorItem + * // id: "STRING_VALUE", // required + * // message: "STRING_VALUE", // required + * // }, + * // ], + * // }, + * // default_configs: { + * // created: Number("int"), // required + * // updated: Number("int"), // required + * // skipped: Number("int"), // required + * // deleted: Number("int"), // required + * // errors: [ + * // { + * // id: "STRING_VALUE", // required + * // message: "STRING_VALUE", // required + * // }, + * // ], + * // }, + * // contexts: { + * // created: Number("int"), // required + * // updated: Number("int"), // required + * // skipped: Number("int"), // required + * // deleted: Number("int"), // required + * // errors: [ + * // { + * // id: "STRING_VALUE", // required + * // message: "STRING_VALUE", // required + * // }, + * // ], + * // }, + * // }; + * + * ``` + * + * @param ImportConfigJsonCommandInput - {@link ImportConfigJsonCommandInput} + * @returns {@link ImportConfigJsonCommandOutput} + * @see {@link ImportConfigJsonCommandInput} for command's `input` shape. + * @see {@link ImportConfigJsonCommandOutput} for command's `response` shape. + * @see {@link SuperpositionClientResolvedConfig | config} for SuperpositionClient's `config` shape. + * + * @throws {@link InternalServerError} (server fault) + * + * @throws {@link SuperpositionServiceException} + *

Base exception class for all service exceptions from Superposition service.

+ * + * @public + */ +export class ImportConfigJsonCommand extends $Command.classBuilder() + .m(function (this: any, Command: any, cs: any, config: SuperpositionClientResolvedConfig, o: any) { + return [ + + getSerdePlugin(config, this.serialize, this.deserialize), + ]; + }) + .s("Superposition", "ImportConfigJson", { + + }) + .n("SuperpositionClient", "ImportConfigJsonCommand") + .f(void 0, void 0) + .ser(se_ImportConfigJsonCommand) + .de(de_ImportConfigJsonCommand) +.build() { +/** @internal type navigation helper, not in runtime. */ +declare protected static __types: { + api: { + input: ImportConfigJsonInput; + output: ImportConfigOutput; + }; + sdk: { + input: ImportConfigJsonCommandInput; + output: ImportConfigJsonCommandOutput; + }; +}; +} diff --git a/clients/javascript/sdk/src/commands/ImportConfigTomlCommand.ts b/clients/javascript/sdk/src/commands/ImportConfigTomlCommand.ts new file mode 100644 index 000000000..903a928c8 --- /dev/null +++ b/clients/javascript/sdk/src/commands/ImportConfigTomlCommand.ts @@ -0,0 +1,139 @@ +// smithy-typescript generated code +import { + ServiceInputTypes, + ServiceOutputTypes, + SuperpositionClientResolvedConfig, +} from "../SuperpositionClient"; +import { + ImportConfigOutput, + ImportConfigTomlInput, +} from "../models/models_0"; +import { + de_ImportConfigTomlCommand, + se_ImportConfigTomlCommand, +} from "../protocols/Aws_restJson1"; +import { getSerdePlugin } from "@smithy/middleware-serde"; +import { Command as $Command } from "@smithy/smithy-client"; +import { MetadataBearer as __MetadataBearer } from "@smithy/types"; + +/** + * @public + */ +export type { __MetadataBearer }; +export { $Command }; +/** + * @public + * + * The input for {@link ImportConfigTomlCommand}. + */ +export interface ImportConfigTomlCommandInput extends ImportConfigTomlInput {} +/** + * @public + * + * The output of {@link ImportConfigTomlCommand}. + */ +export interface ImportConfigTomlCommandOutput extends ImportConfigOutput, __MetadataBearer {} + +/** + * Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a single transaction after validating the document. + * @example + * Use a bare-bones client and the command you need to make an API call. + * ```javascript + * import { SuperpositionClient, ImportConfigTomlCommand } from "superposition-sdk"; // ES Modules import + * // const { SuperpositionClient, ImportConfigTomlCommand } = require("superposition-sdk"); // CommonJS import + * const client = new SuperpositionClient(config); + * const input = { // ImportConfigTomlInput + * workspace_id: "STRING_VALUE", // required + * org_id: "STRING_VALUE", // required + * strategy: "create_only" || "upsert" || "replace", + * on_error: "abort" || "continue", + * dry_run: true || false, + * config_tags: "STRING_VALUE", + * toml_config: "STRING_VALUE", // required + * }; + * const command = new ImportConfigTomlCommand(input); + * const response = await client.send(command); + * // { // ImportConfigOutput + * // strategy: "STRING_VALUE", // required + * // dry_run: true || false, // required + * // config_version: "STRING_VALUE", + * // dimensions: { // ImportEntityReport + * // created: Number("int"), // required + * // updated: Number("int"), // required + * // skipped: Number("int"), // required + * // deleted: Number("int"), // required + * // errors: [ // ImportErrorList + * // { // ImportErrorItem + * // id: "STRING_VALUE", // required + * // message: "STRING_VALUE", // required + * // }, + * // ], + * // }, + * // default_configs: { + * // created: Number("int"), // required + * // updated: Number("int"), // required + * // skipped: Number("int"), // required + * // deleted: Number("int"), // required + * // errors: [ + * // { + * // id: "STRING_VALUE", // required + * // message: "STRING_VALUE", // required + * // }, + * // ], + * // }, + * // contexts: { + * // created: Number("int"), // required + * // updated: Number("int"), // required + * // skipped: Number("int"), // required + * // deleted: Number("int"), // required + * // errors: [ + * // { + * // id: "STRING_VALUE", // required + * // message: "STRING_VALUE", // required + * // }, + * // ], + * // }, + * // }; + * + * ``` + * + * @param ImportConfigTomlCommandInput - {@link ImportConfigTomlCommandInput} + * @returns {@link ImportConfigTomlCommandOutput} + * @see {@link ImportConfigTomlCommandInput} for command's `input` shape. + * @see {@link ImportConfigTomlCommandOutput} for command's `response` shape. + * @see {@link SuperpositionClientResolvedConfig | config} for SuperpositionClient's `config` shape. + * + * @throws {@link InternalServerError} (server fault) + * + * @throws {@link SuperpositionServiceException} + *

Base exception class for all service exceptions from Superposition service.

+ * + * @public + */ +export class ImportConfigTomlCommand extends $Command.classBuilder() + .m(function (this: any, Command: any, cs: any, config: SuperpositionClientResolvedConfig, o: any) { + return [ + + getSerdePlugin(config, this.serialize, this.deserialize), + ]; + }) + .s("Superposition", "ImportConfigToml", { + + }) + .n("SuperpositionClient", "ImportConfigTomlCommand") + .f(void 0, void 0) + .ser(se_ImportConfigTomlCommand) + .de(de_ImportConfigTomlCommand) +.build() { +/** @internal type navigation helper, not in runtime. */ +declare protected static __types: { + api: { + input: ImportConfigTomlInput; + output: ImportConfigOutput; + }; + sdk: { + input: ImportConfigTomlCommandInput; + output: ImportConfigTomlCommandOutput; + }; +}; +} diff --git a/clients/javascript/sdk/src/commands/index.ts b/clients/javascript/sdk/src/commands/index.ts index cce57fa67..f655df324 100644 --- a/clients/javascript/sdk/src/commands/index.ts +++ b/clients/javascript/sdk/src/commands/index.ts @@ -49,6 +49,8 @@ export * from "./GetVersionCommand"; export * from "./GetWebhookCommand"; export * from "./GetWebhookByEventCommand"; export * from "./GetWorkspaceCommand"; +export * from "./ImportConfigJsonCommand"; +export * from "./ImportConfigTomlCommand"; export * from "./ListAuditLogsCommand"; export * from "./ListContextsCommand"; export * from "./ListDefaultConfigsCommand"; diff --git a/clients/javascript/sdk/src/models/models_0.ts b/clients/javascript/sdk/src/models/models_0.ts index 59ca94f27..0e7bb0d87 100644 --- a/clients/javascript/sdk/src/models/models_0.ts +++ b/clients/javascript/sdk/src/models/models_0.ts @@ -2751,6 +2751,151 @@ export interface GetWorkspaceInput { workspace_name: string | undefined; } +/** + * @public + * @enum + */ +export const ImportOnError = { + /** + * Roll the whole import back on the first error. + */ + ABORT: "abort", + /** + * Apply everything that is valid and report per-entity errors. + */ + CONTINUE: "continue", +} as const +/** + * @public + */ +export type ImportOnError = typeof ImportOnError[keyof typeof ImportOnError] + +/** + * @public + * @enum + */ +export const ImportStrategy = { + /** + * Create entities that are present in the file but missing from the workspace. Existing entities are skipped. Nothing is deleted. + */ + CREATE_ONLY: "create_only", + /** + * Mirror the file: create missing entities, update existing entities, and delete dimensions, default-configs and contexts that are absent from it. + */ + REPLACE: "replace", + /** + * Create missing entities and update existing entities from the file. Entities absent from the file are left untouched. + */ + UPSERT: "upsert", +} as const +/** + * @public + */ +export type ImportStrategy = typeof ImportStrategy[keyof typeof ImportStrategy] + +/** + * @public + */ +export interface ImportConfigJsonInput { + workspace_id: string | undefined; + org_id: string | undefined; + /** + * How the import applies file entities to the workspace. Defaults to upsert. + * @public + */ + strategy?: ImportStrategy | undefined; + + /** + * Whether to abort (default) or continue on per-entity errors. + * @public + */ + on_error?: ImportOnError | undefined; + + /** + * When true, validates and summarises the import without persisting anything. Defaults to false. + * @public + */ + dry_run?: boolean | undefined; + + config_tags?: string | undefined; + json_config: string | undefined; +} + +/** + * @public + */ +export interface ImportErrorItem { + id: string | undefined; + message: string | undefined; +} + +/** + * Per-entity outcome counts for an import. + * @public + */ +export interface ImportEntityReport { + created: number | undefined; + updated: number | undefined; + skipped: number | undefined; + deleted: number | undefined; + errors?: (ImportErrorItem)[] | undefined; +} + +/** + * Summary of what an import created, updated, skipped or deleted. + * @public + */ +export interface ImportConfigOutput { + strategy: string | undefined; + dry_run: boolean | undefined; + config_version?: string | undefined; + /** + * Per-entity outcome counts for an import. + * @public + */ + dimensions: ImportEntityReport | undefined; + + /** + * Per-entity outcome counts for an import. + * @public + */ + default_configs: ImportEntityReport | undefined; + + /** + * Per-entity outcome counts for an import. + * @public + */ + contexts: ImportEntityReport | undefined; +} + +/** + * @public + */ +export interface ImportConfigTomlInput { + workspace_id: string | undefined; + org_id: string | undefined; + /** + * How the import applies file entities to the workspace. Defaults to upsert. + * @public + */ + strategy?: ImportStrategy | undefined; + + /** + * Whether to abort (default) or continue on per-entity errors. + * @public + */ + on_error?: ImportOnError | undefined; + + /** + * When true, validates and summarises the import without persisting anything. Defaults to false. + * @public + */ + dry_run?: boolean | undefined; + + config_tags?: string | undefined; + toml_config: string | undefined; +} + /** * @public */ diff --git a/clients/javascript/sdk/src/protocols/Aws_restJson1.ts b/clients/javascript/sdk/src/protocols/Aws_restJson1.ts index f593b2a38..a16962e32 100644 --- a/clients/javascript/sdk/src/protocols/Aws_restJson1.ts +++ b/clients/javascript/sdk/src/protocols/Aws_restJson1.ts @@ -199,6 +199,14 @@ import { GetWorkspaceCommandInput, GetWorkspaceCommandOutput, } from "../commands/GetWorkspaceCommand"; +import { + ImportConfigJsonCommandInput, + ImportConfigJsonCommandOutput, +} from "../commands/ImportConfigJsonCommand"; +import { + ImportConfigTomlCommandInput, + ImportConfigTomlCommandOutput, +} from "../commands/ImportConfigTomlCommand"; import { ListAuditLogsCommandInput, ListAuditLogsCommandOutput, @@ -1685,6 +1693,62 @@ export const se_GetWorkspaceCommand = async( return b.build(); } +/** + * serializeAws_restJson1ImportConfigJsonCommand + */ +export const se_ImportConfigJsonCommand = async( + input: ImportConfigJsonCommandInput, + context: __SerdeContext +): Promise<__HttpRequest> => { + const b = rb(input, context); + const headers: any = map({}, isSerializableHeaderValue, { + 'content-type': 'text/plain', + [_xw]: input[_wi]!, + [_xoi]: input[_oi]!, + [_xis]: input[_s]!, + [_xioe]: input[_oe]!, + [_xidr]: [() => isSerializableHeaderValue(input[_dr]), () => input[_dr]!.toString()], + [_xct]: input[_ct]!, + }); + b.bp("/config/json/import"); + let body: any; + if (input.json_config !== undefined) { + body = input.json_config; + } + b.m("POST") + .h(headers) + .b(body); + return b.build(); +} + +/** + * serializeAws_restJson1ImportConfigTomlCommand + */ +export const se_ImportConfigTomlCommand = async( + input: ImportConfigTomlCommandInput, + context: __SerdeContext +): Promise<__HttpRequest> => { + const b = rb(input, context); + const headers: any = map({}, isSerializableHeaderValue, { + 'content-type': 'text/plain', + [_xw]: input[_wi]!, + [_xoi]: input[_oi]!, + [_xis]: input[_s]!, + [_xioe]: input[_oe]!, + [_xidr]: [() => isSerializableHeaderValue(input[_dr]), () => input[_dr]!.toString()], + [_xct]: input[_ct]!, + }); + b.bp("/config/toml/import"); + let body: any; + if (input.toml_config !== undefined) { + body = input.toml_config; + } + b.m("POST") + .h(headers) + .b(body); + return b.build(); +} + /** * serializeAws_restJson1ListAuditLogsCommand */ @@ -1825,7 +1889,7 @@ export const se_ListExperimentCommand = async( [_c]: [() => input.count !== void 0, () => (input[_c]!.toString())], [_pa]: [() => input.page !== void 0, () => (input[_pa]!.toString())], [_a]: [() => input.all !== void 0, () => (input[_a]!.toString())], - [_s]: [() => input.status !== void 0, () => ((input[_s]! || []))], + [_st]: [() => input.status !== void 0, () => ((input[_st]! || []))], [_fd]: [() => input.from_date !== void 0, () => (__serializeDateTime(input[_fd]!).toString())], [_td]: [() => input.to_date !== void 0, () => (__serializeDateTime(input[_td]!).toString())], [_en]: [,input[_en]!], @@ -4144,6 +4208,58 @@ export const de_GetWorkspaceCommand = async( return contents; } +/** + * deserializeAws_restJson1ImportConfigJsonCommand + */ +export const de_ImportConfigJsonCommand = async( + output: __HttpResponse, + context: __SerdeContext +): Promise => { + if (output.statusCode !== 200 && output.statusCode >= 300) { + return de_CommandError(output, context); + } + const contents: any = map({ + $metadata: deserializeMetadata(output), + }); + const data: Record = __expectNonNull((__expectObject(await parseBody(output.body, context))), "body"); + const doc = take(data, { + 'config_version': __expectString, + 'contexts': _json, + 'default_configs': _json, + 'dimensions': _json, + 'dry_run': __expectBoolean, + 'strategy': __expectString, + }); + Object.assign(contents, doc); + return contents; +} + +/** + * deserializeAws_restJson1ImportConfigTomlCommand + */ +export const de_ImportConfigTomlCommand = async( + output: __HttpResponse, + context: __SerdeContext +): Promise => { + if (output.statusCode !== 200 && output.statusCode >= 300) { + return de_CommandError(output, context); + } + const contents: any = map({ + $metadata: deserializeMetadata(output), + }); + const data: Record = __expectNonNull((__expectObject(await parseBody(output.body, context))), "body"); + const doc = take(data, { + 'config_version': __expectString, + 'contexts': _json, + 'default_configs': _json, + 'dimensions': _json, + 'dry_run': __expectBoolean, + 'strategy': __expectString, + }); + Object.assign(contents, doc); + return contents; +} + /** * deserializeAws_restJson1ListAuditLogsCommand */ @@ -6005,6 +6121,12 @@ const de_CommandError = async( }) as any; } + // de_ImportEntityReport omitted. + + // de_ImportErrorItem omitted. + + // de_ImportErrorList omitted. + /** * deserializeAws_restJson1ListContextOut */ @@ -6473,6 +6595,7 @@ const de_CommandError = async( const _ci = "context_id"; const _ct = "config_tags"; const _dms = "dimension_match_strategy"; + const _dr = "dry_run"; const _egi = "experiment_group_ids"; const _ei = "experiment_ids"; const _en = "experiment_name"; @@ -6490,15 +6613,17 @@ const de_CommandError = async( const _lmb = "last_modified_by"; const _ms = "merge_strategy"; const _n = "name"; + const _oe = "on_error"; const _oi = "org_id"; const _p = "prefix"; const _pa = "page"; const _pl = "plaintext"; const _rr = "resolve_remote"; - const _s = "status"; + const _s = "strategy"; const _sb = "sort_by"; const _so = "sort_on"; const _sr = "show_reasoning"; + const _st = "status"; const _t = "tables"; const _ta = "table"; const _td = "to_date"; @@ -6508,6 +6633,9 @@ const de_CommandError = async( const _xai = "x-audit-id"; const _xct = "x-config-tags"; const _xcv = "x-config-version"; + const _xidr = "x-import-dry-run"; + const _xioe = "x-import-on-error"; + const _xis = "x-import-strategy"; const _xms = "x-merge-strategy"; const _xoi = "x-org-id"; const _xw = "x-workspace"; diff --git a/clients/python/sdk/superposition_sdk/_private/schemas.py b/clients/python/sdk/superposition_sdk/_private/schemas.py index d3b6d6abd..3a0759d9a 100644 --- a/clients/python/sdk/superposition_sdk/_private/schemas.py +++ b/clients/python/sdk/superposition_sdk/_private/schemas.py @@ -15308,6 +15308,488 @@ ) +IMPORT_ON_ERROR = Schema.collection( + id=ShapeID("io.superposition#ImportOnError"), + shape_type=ShapeType.ENUM, + members={ + "ABORT": { + "target": UNIT, + "index": 0, + "traits": [ + Trait.new(id=ShapeID("smithy.api#enumValue"), value="abort"), + + ], + }, + + "CONTINUE": { + "target": UNIT, + "index": 1, + "traits": [ + Trait.new(id=ShapeID("smithy.api#enumValue"), value="continue"), + + ], + }, + + } +) + +IMPORT_STRATEGY = Schema.collection( + id=ShapeID("io.superposition#ImportStrategy"), + shape_type=ShapeType.ENUM, + members={ + "CREATE_ONLY": { + "target": UNIT, + "index": 0, + "traits": [ + Trait.new(id=ShapeID("smithy.api#enumValue"), value="create_only"), + + ], + }, + + "UPSERT": { + "target": UNIT, + "index": 1, + "traits": [ + Trait.new(id=ShapeID("smithy.api#enumValue"), value="upsert"), + + ], + }, + + "REPLACE": { + "target": UNIT, + "index": 2, + "traits": [ + Trait.new(id=ShapeID("smithy.api#enumValue"), value="replace"), + + ], + }, + + } +) + +IMPORT_CONFIG_JSON_INPUT = Schema.collection( + id=ShapeID("io.superposition#ImportConfigJsonInput"), + + traits=[ + Trait.new(id=ShapeID("smithy.api#input")), + + ], + members={ + "workspace_id": { + "target": STRING, + "index": 0, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-workspace"), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "org_id": { + "target": STRING, + "index": 1, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-org-id"), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "strategy": { + "target": IMPORT_STRATEGY, + "index": 2, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-import-strategy"), + Trait.new(id=ShapeID("smithy.api#notProperty")), + + ], + }, + + "on_error": { + "target": IMPORT_ON_ERROR, + "index": 3, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-import-on-error"), + Trait.new(id=ShapeID("smithy.api#notProperty")), + + ], + }, + + "dry_run": { + "target": BOOLEAN, + "index": 4, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-import-dry-run"), + Trait.new(id=ShapeID("smithy.api#notProperty")), + + ], + }, + + "config_tags": { + "target": STRING, + "index": 5, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-config-tags"), + Trait.new(id=ShapeID("smithy.api#notProperty")), + + ], + }, + + "json_config": { + "target": STRING, + "index": 6, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + Trait.new(id=ShapeID("smithy.api#httpPayload")), + + ], + }, + + } +) + +IMPORT_ERROR_ITEM = Schema.collection( + id=ShapeID("io.superposition#ImportErrorItem"), + + members={ + "id": { + "target": STRING, + "index": 0, + "traits": [ + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "message": { + "target": STRING, + "index": 1, + "traits": [ + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + } +) + +IMPORT_ERROR_LIST = Schema.collection( + id=ShapeID("io.superposition#ImportErrorList"), + shape_type=ShapeType.LIST, + members={ + "member": { + "target": IMPORT_ERROR_ITEM, + "index": 0, + }, + + } +) + +IMPORT_ENTITY_REPORT = Schema.collection( + id=ShapeID("io.superposition#ImportEntityReport"), + + members={ + "created": { + "target": INTEGER, + "index": 0, + "traits": [ + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "updated": { + "target": INTEGER, + "index": 1, + "traits": [ + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "skipped": { + "target": INTEGER, + "index": 2, + "traits": [ + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "deleted": { + "target": INTEGER, + "index": 3, + "traits": [ + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "errors": { + "target": IMPORT_ERROR_LIST, + "index": 4, + }, + + } +) + +IMPORT_CONFIG_JSON_OUTPUT = Schema.collection( + id=ShapeID("io.superposition#ImportConfigJsonOutput"), + + traits=[ + Trait.new(id=ShapeID("smithy.synthetic#originalShapeId"), value="io.superposition#ImportConfigOutput"), + Trait.new(id=ShapeID("smithy.api#output")), + + ], + members={ + "strategy": { + "target": STRING, + "index": 0, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "dry_run": { + "target": BOOLEAN, + "index": 1, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "config_version": { + "target": STRING, + "index": 2, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + + ], + }, + + "dimensions": { + "target": IMPORT_ENTITY_REPORT, + "index": 3, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "default_configs": { + "target": IMPORT_ENTITY_REPORT, + "index": 4, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "contexts": { + "target": IMPORT_ENTITY_REPORT, + "index": 5, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + } +) + +IMPORT_CONFIG_JSON = Schema( + id=ShapeID("io.superposition#ImportConfigJson"), + shape_type=ShapeType.OPERATION, + traits=[ + Trait.new(id=ShapeID("smithy.api#tags"), value=( + "Configuration Management", + )), + Trait.new(id=ShapeID("smithy.api#http"), value=MappingProxyType({ + "method": "POST", + "uri": "/config/json/import", + })), + + ], + +) + +IMPORT_CONFIG_TOML_INPUT = Schema.collection( + id=ShapeID("io.superposition#ImportConfigTomlInput"), + + traits=[ + Trait.new(id=ShapeID("smithy.api#input")), + + ], + members={ + "workspace_id": { + "target": STRING, + "index": 0, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-workspace"), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "org_id": { + "target": STRING, + "index": 1, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-org-id"), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "strategy": { + "target": IMPORT_STRATEGY, + "index": 2, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-import-strategy"), + Trait.new(id=ShapeID("smithy.api#notProperty")), + + ], + }, + + "on_error": { + "target": IMPORT_ON_ERROR, + "index": 3, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-import-on-error"), + Trait.new(id=ShapeID("smithy.api#notProperty")), + + ], + }, + + "dry_run": { + "target": BOOLEAN, + "index": 4, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-import-dry-run"), + Trait.new(id=ShapeID("smithy.api#notProperty")), + + ], + }, + + "config_tags": { + "target": STRING, + "index": 5, + "traits": [ + Trait.new(id=ShapeID("smithy.api#httpHeader"), value="x-config-tags"), + Trait.new(id=ShapeID("smithy.api#notProperty")), + + ], + }, + + "toml_config": { + "target": STRING, + "index": 6, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + Trait.new(id=ShapeID("smithy.api#httpPayload")), + + ], + }, + + } +) + +IMPORT_CONFIG_TOML_OUTPUT = Schema.collection( + id=ShapeID("io.superposition#ImportConfigTomlOutput"), + + traits=[ + Trait.new(id=ShapeID("smithy.synthetic#originalShapeId"), value="io.superposition#ImportConfigOutput"), + Trait.new(id=ShapeID("smithy.api#output")), + + ], + members={ + "strategy": { + "target": STRING, + "index": 0, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "dry_run": { + "target": BOOLEAN, + "index": 1, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "config_version": { + "target": STRING, + "index": 2, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + + ], + }, + + "dimensions": { + "target": IMPORT_ENTITY_REPORT, + "index": 3, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "default_configs": { + "target": IMPORT_ENTITY_REPORT, + "index": 4, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + "contexts": { + "target": IMPORT_ENTITY_REPORT, + "index": 5, + "traits": [ + Trait.new(id=ShapeID("smithy.api#notProperty")), + Trait.new(id=ShapeID("smithy.api#required")), + + ], + }, + + } +) + +IMPORT_CONFIG_TOML = Schema( + id=ShapeID("io.superposition#ImportConfigToml"), + shape_type=ShapeType.OPERATION, + traits=[ + Trait.new(id=ShapeID("smithy.api#tags"), value=( + "Configuration Management", + )), + Trait.new(id=ShapeID("smithy.api#http"), value=MappingProxyType({ + "method": "POST", + "uri": "/config/toml/import", + })), + + ], + +) + LIST_ORGANISATION_INPUT = Schema.collection( id=ShapeID("io.superposition#ListOrganisationInput"), diff --git a/clients/python/sdk/superposition_sdk/client.py b/clients/python/sdk/superposition_sdk/client.py index 687469a7b..f03fc7a28 100644 --- a/clients/python/sdk/superposition_sdk/client.py +++ b/clients/python/sdk/superposition_sdk/client.py @@ -84,6 +84,8 @@ _deserialize_get_webhook, _deserialize_get_webhook_by_event, _deserialize_get_workspace, + _deserialize_import_config_json, + _deserialize_import_config_toml, _deserialize_list_audit_logs, _deserialize_list_contexts, _deserialize_list_default_configs, @@ -273,6 +275,12 @@ GetWebhookOutput, GetWorkspaceInput, GetWorkspaceOutput, + IMPORT_CONFIG_JSON, + IMPORT_CONFIG_TOML, + ImportConfigJsonInput, + ImportConfigJsonOutput, + ImportConfigTomlInput, + ImportConfigTomlOutput, LIST_AUDIT_LOGS, LIST_CONTEXTS, LIST_DEFAULT_CONFIGS, @@ -437,6 +445,8 @@ _serialize_get_webhook, _serialize_get_webhook_by_event, _serialize_get_workspace, + _serialize_import_config_json, + _serialize_import_config_toml, _serialize_list_audit_logs, _serialize_list_contexts, _serialize_list_default_configs, @@ -1853,6 +1863,62 @@ async def get_workspace(self, input: GetWorkspaceInput, plugins: list[Plugin] | operation=GET_WORKSPACE, ) + async def import_config_json(self, input: ImportConfigJsonInput, plugins: list[Plugin] | None = None) -> ImportConfigJsonOutput: + """ + Imports a full config from a JSON document, persisting dimensions, + default-configs and contexts in a single transaction after validating the + document. + + :param input: The operation's input. + + :param plugins: A list of callables that modify the configuration dynamically. + Changes made by these plugins only apply for the duration of the operation + execution and will not affect any other operation invocations. + + """ + operation_plugins: list[Plugin] = [ + + ] + if plugins: + operation_plugins.extend(plugins) + + return await self._execute_operation( + input=input, + plugins=operation_plugins, + serialize=_serialize_import_config_json, + deserialize=_deserialize_import_config_json, + config=self._config, + operation=IMPORT_CONFIG_JSON, + ) + + async def import_config_toml(self, input: ImportConfigTomlInput, plugins: list[Plugin] | None = None) -> ImportConfigTomlOutput: + """ + Imports a full config from a TOML document, persisting dimensions, + default-configs and contexts in a single transaction after validating the + document. + + :param input: The operation's input. + + :param plugins: A list of callables that modify the configuration dynamically. + Changes made by these plugins only apply for the duration of the operation + execution and will not affect any other operation invocations. + + """ + operation_plugins: list[Plugin] = [ + + ] + if plugins: + operation_plugins.extend(plugins) + + return await self._execute_operation( + input=input, + plugins=operation_plugins, + serialize=_serialize_import_config_toml, + deserialize=_deserialize_import_config_toml, + config=self._config, + operation=IMPORT_CONFIG_TOML, + ) + async def list_audit_logs(self, input: ListAuditLogsInput, plugins: list[Plugin] | None = None) -> ListAuditLogsOutput: """ Retrieves a paginated list of audit logs with support for filtering by date diff --git a/clients/python/sdk/superposition_sdk/config.py b/clients/python/sdk/superposition_sdk/config.py index bcd3c6aaf..f2f052ca6 100644 --- a/clients/python/sdk/superposition_sdk/config.py +++ b/clients/python/sdk/superposition_sdk/config.py @@ -116,6 +116,10 @@ GetWebhookOutput, GetWorkspaceInput, GetWorkspaceOutput, + ImportConfigJsonInput, + ImportConfigJsonOutput, + ImportConfigTomlInput, + ImportConfigTomlOutput, ListAuditLogsInput, ListAuditLogsOutput, ListContextsInput, @@ -193,7 +197,7 @@ ) -_ServiceInterceptor = Union[Interceptor[AddMembersToGroupInput, AddMembersToGroupOutput, Any, Any], Interceptor[ApplicableVariantsInput, ApplicableVariantsOutput, Any, Any], Interceptor[BulkOperationInput, BulkOperationOutput, Any, Any], Interceptor[ConcludeExperimentInput, ConcludeExperimentOutput, Any, Any], Interceptor[CreateContextInput, CreateContextOutput, Any, Any], Interceptor[CreateDefaultConfigInput, CreateDefaultConfigOutput, Any, Any], Interceptor[CreateDimensionInput, CreateDimensionOutput, Any, Any], Interceptor[CreateExperimentInput, CreateExperimentOutput, Any, Any], Interceptor[CreateExperimentGroupInput, CreateExperimentGroupOutput, Any, Any], Interceptor[CreateFunctionInput, CreateFunctionOutput, Any, Any], Interceptor[CreateOrganisationInput, CreateOrganisationOutput, Any, Any], Interceptor[CreateSecretInput, CreateSecretOutput, Any, Any], Interceptor[CreateTypeTemplatesInput, CreateTypeTemplatesOutput, Any, Any], Interceptor[CreateVariableInput, CreateVariableOutput, Any, Any], Interceptor[CreateWebhookInput, CreateWebhookOutput, Any, Any], Interceptor[CreateWorkspaceInput, CreateWorkspaceOutput, Any, Any], Interceptor[DeleteContextInput, DeleteContextOutput, Any, Any], Interceptor[DeleteDefaultConfigInput, DeleteDefaultConfigOutput, Any, Any], Interceptor[DeleteDimensionInput, DeleteDimensionOutput, Any, Any], Interceptor[DeleteExperimentGroupInput, DeleteExperimentGroupOutput, Any, Any], Interceptor[DeleteFunctionInput, DeleteFunctionOutput, Any, Any], Interceptor[DeleteSecretInput, DeleteSecretOutput, Any, Any], Interceptor[DeleteTypeTemplatesInput, DeleteTypeTemplatesOutput, Any, Any], Interceptor[DeleteVariableInput, DeleteVariableOutput, Any, Any], Interceptor[DeleteWebhookInput, DeleteWebhookOutput, Any, Any], Interceptor[DiscardExperimentInput, DiscardExperimentOutput, Any, Any], Interceptor[GetConfigInput, GetConfigOutput, Any, Any], Interceptor[GetConfigJsonInput, GetConfigJsonOutput, Any, Any], Interceptor[GetConfigTomlInput, GetConfigTomlOutput, Any, Any], Interceptor[GetContextInput, GetContextOutput, Any, Any], Interceptor[GetContextFromConditionInput, GetContextFromConditionOutput, Any, Any], Interceptor[GetDefaultConfigInput, GetDefaultConfigOutput, Any, Any], Interceptor[GetDetailedResolvedConfigInput, GetDetailedResolvedConfigOutput, Any, Any], Interceptor[GetDimensionInput, GetDimensionOutput, Any, Any], Interceptor[GetExperimentInput, GetExperimentOutput, Any, Any], Interceptor[GetExperimentConfigInput, GetExperimentConfigOutput, Any, Any], Interceptor[GetExperimentGroupInput, GetExperimentGroupOutput, Any, Any], Interceptor[GetFunctionInput, GetFunctionOutput, Any, Any], Interceptor[GetOrganisationInput, GetOrganisationOutput, Any, Any], Interceptor[GetResolvedConfigInput, GetResolvedConfigOutput, Any, Any], Interceptor[GetResolvedConfigExplanationInput, GetResolvedConfigExplanationOutput, Any, Any], Interceptor[GetResolvedConfigWithIdentifierInput, GetResolvedConfigWithIdentifierOutput, Any, Any], Interceptor[GetSecretInput, GetSecretOutput, Any, Any], Interceptor[GetTypeTemplateInput, GetTypeTemplateOutput, Any, Any], Interceptor[GetTypeTemplatesListInput, GetTypeTemplatesListOutput, Any, Any], Interceptor[GetVariableInput, GetVariableOutput, Any, Any], Interceptor[GetVersionInput, GetVersionOutput, Any, Any], Interceptor[GetWebhookInput, GetWebhookOutput, Any, Any], Interceptor[GetWebhookByEventInput, GetWebhookByEventOutput, Any, Any], Interceptor[GetWorkspaceInput, GetWorkspaceOutput, Any, Any], Interceptor[ListAuditLogsInput, ListAuditLogsOutput, Any, Any], Interceptor[ListContextsInput, ListContextsOutput, Any, Any], Interceptor[ListDefaultConfigsInput, ListDefaultConfigsOutput, Any, Any], Interceptor[ListDimensionsInput, ListDimensionsOutput, Any, Any], Interceptor[ListExperimentInput, ListExperimentOutput, Any, Any], Interceptor[ListExperimentGroupsInput, ListExperimentGroupsOutput, Any, Any], Interceptor[ListFunctionInput, ListFunctionOutput, Any, Any], Interceptor[ListOrganisationInput, ListOrganisationOutput, Any, Any], Interceptor[ListSecretsInput, ListSecretsOutput, Any, Any], Interceptor[ListVariablesInput, ListVariablesOutput, Any, Any], Interceptor[ListVersionsInput, ListVersionsOutput, Any, Any], Interceptor[ListWebhookInput, ListWebhookOutput, Any, Any], Interceptor[ListWorkspaceInput, ListWorkspaceOutput, Any, Any], Interceptor[MigrateWorkspaceSchemaInput, MigrateWorkspaceSchemaOutput, Any, Any], Interceptor[MoveContextInput, MoveContextOutput, Any, Any], Interceptor[PauseExperimentInput, PauseExperimentOutput, Any, Any], Interceptor[PublishInput, PublishOutput, Any, Any], Interceptor[RampExperimentInput, RampExperimentOutput, Any, Any], Interceptor[RemoveMembersFromGroupInput, RemoveMembersFromGroupOutput, Any, Any], Interceptor[ResumeExperimentInput, ResumeExperimentOutput, Any, Any], Interceptor[RotateMasterEncryptionKeyInput, RotateMasterEncryptionKeyOutput, Any, Any], Interceptor[RotateWorkspaceEncryptionKeyInput, RotateWorkspaceEncryptionKeyOutput, Any, Any], Interceptor[TestInput, TestOutput, Any, Any], Interceptor[UpdateDefaultConfigInput, UpdateDefaultConfigOutput, Any, Any], Interceptor[UpdateDimensionInput, UpdateDimensionOutput, Any, Any], Interceptor[UpdateExperimentGroupInput, UpdateExperimentGroupOutput, Any, Any], Interceptor[UpdateFunctionInput, UpdateFunctionOutput, Any, Any], Interceptor[UpdateOrganisationInput, UpdateOrganisationOutput, Any, Any], Interceptor[UpdateOverrideInput, UpdateOverrideOutput, Any, Any], Interceptor[UpdateOverridesExperimentInput, UpdateOverridesExperimentOutput, Any, Any], Interceptor[UpdateSecretInput, UpdateSecretOutput, Any, Any], Interceptor[UpdateTypeTemplatesInput, UpdateTypeTemplatesOutput, Any, Any], Interceptor[UpdateVariableInput, UpdateVariableOutput, Any, Any], Interceptor[UpdateWebhookInput, UpdateWebhookOutput, Any, Any], Interceptor[UpdateWorkspaceInput, UpdateWorkspaceOutput, Any, Any], Interceptor[ValidateContextInput, ValidateContextOutput, Any, Any], Interceptor[WeightRecomputeInput, WeightRecomputeOutput, Any, Any]] +_ServiceInterceptor = Union[Interceptor[AddMembersToGroupInput, AddMembersToGroupOutput, Any, Any], Interceptor[ApplicableVariantsInput, ApplicableVariantsOutput, Any, Any], Interceptor[BulkOperationInput, BulkOperationOutput, Any, Any], Interceptor[ConcludeExperimentInput, ConcludeExperimentOutput, Any, Any], Interceptor[CreateContextInput, CreateContextOutput, Any, Any], Interceptor[CreateDefaultConfigInput, CreateDefaultConfigOutput, Any, Any], Interceptor[CreateDimensionInput, CreateDimensionOutput, Any, Any], Interceptor[CreateExperimentInput, CreateExperimentOutput, Any, Any], Interceptor[CreateExperimentGroupInput, CreateExperimentGroupOutput, Any, Any], Interceptor[CreateFunctionInput, CreateFunctionOutput, Any, Any], Interceptor[CreateOrganisationInput, CreateOrganisationOutput, Any, Any], Interceptor[CreateSecretInput, CreateSecretOutput, Any, Any], Interceptor[CreateTypeTemplatesInput, CreateTypeTemplatesOutput, Any, Any], Interceptor[CreateVariableInput, CreateVariableOutput, Any, Any], Interceptor[CreateWebhookInput, CreateWebhookOutput, Any, Any], Interceptor[CreateWorkspaceInput, CreateWorkspaceOutput, Any, Any], Interceptor[DeleteContextInput, DeleteContextOutput, Any, Any], Interceptor[DeleteDefaultConfigInput, DeleteDefaultConfigOutput, Any, Any], Interceptor[DeleteDimensionInput, DeleteDimensionOutput, Any, Any], Interceptor[DeleteExperimentGroupInput, DeleteExperimentGroupOutput, Any, Any], Interceptor[DeleteFunctionInput, DeleteFunctionOutput, Any, Any], Interceptor[DeleteSecretInput, DeleteSecretOutput, Any, Any], Interceptor[DeleteTypeTemplatesInput, DeleteTypeTemplatesOutput, Any, Any], Interceptor[DeleteVariableInput, DeleteVariableOutput, Any, Any], Interceptor[DeleteWebhookInput, DeleteWebhookOutput, Any, Any], Interceptor[DiscardExperimentInput, DiscardExperimentOutput, Any, Any], Interceptor[GetConfigInput, GetConfigOutput, Any, Any], Interceptor[GetConfigJsonInput, GetConfigJsonOutput, Any, Any], Interceptor[GetConfigTomlInput, GetConfigTomlOutput, Any, Any], Interceptor[GetContextInput, GetContextOutput, Any, Any], Interceptor[GetContextFromConditionInput, GetContextFromConditionOutput, Any, Any], Interceptor[GetDefaultConfigInput, GetDefaultConfigOutput, Any, Any], Interceptor[GetDetailedResolvedConfigInput, GetDetailedResolvedConfigOutput, Any, Any], Interceptor[GetDimensionInput, GetDimensionOutput, Any, Any], Interceptor[GetExperimentInput, GetExperimentOutput, Any, Any], Interceptor[GetExperimentConfigInput, GetExperimentConfigOutput, Any, Any], Interceptor[GetExperimentGroupInput, GetExperimentGroupOutput, Any, Any], Interceptor[GetFunctionInput, GetFunctionOutput, Any, Any], Interceptor[GetOrganisationInput, GetOrganisationOutput, Any, Any], Interceptor[GetResolvedConfigInput, GetResolvedConfigOutput, Any, Any], Interceptor[GetResolvedConfigExplanationInput, GetResolvedConfigExplanationOutput, Any, Any], Interceptor[GetResolvedConfigWithIdentifierInput, GetResolvedConfigWithIdentifierOutput, Any, Any], Interceptor[GetSecretInput, GetSecretOutput, Any, Any], Interceptor[GetTypeTemplateInput, GetTypeTemplateOutput, Any, Any], Interceptor[GetTypeTemplatesListInput, GetTypeTemplatesListOutput, Any, Any], Interceptor[GetVariableInput, GetVariableOutput, Any, Any], Interceptor[GetVersionInput, GetVersionOutput, Any, Any], Interceptor[GetWebhookInput, GetWebhookOutput, Any, Any], Interceptor[GetWebhookByEventInput, GetWebhookByEventOutput, Any, Any], Interceptor[GetWorkspaceInput, GetWorkspaceOutput, Any, Any], Interceptor[ImportConfigJsonInput, ImportConfigJsonOutput, Any, Any], Interceptor[ImportConfigTomlInput, ImportConfigTomlOutput, Any, Any], Interceptor[ListAuditLogsInput, ListAuditLogsOutput, Any, Any], Interceptor[ListContextsInput, ListContextsOutput, Any, Any], Interceptor[ListDefaultConfigsInput, ListDefaultConfigsOutput, Any, Any], Interceptor[ListDimensionsInput, ListDimensionsOutput, Any, Any], Interceptor[ListExperimentInput, ListExperimentOutput, Any, Any], Interceptor[ListExperimentGroupsInput, ListExperimentGroupsOutput, Any, Any], Interceptor[ListFunctionInput, ListFunctionOutput, Any, Any], Interceptor[ListOrganisationInput, ListOrganisationOutput, Any, Any], Interceptor[ListSecretsInput, ListSecretsOutput, Any, Any], Interceptor[ListVariablesInput, ListVariablesOutput, Any, Any], Interceptor[ListVersionsInput, ListVersionsOutput, Any, Any], Interceptor[ListWebhookInput, ListWebhookOutput, Any, Any], Interceptor[ListWorkspaceInput, ListWorkspaceOutput, Any, Any], Interceptor[MigrateWorkspaceSchemaInput, MigrateWorkspaceSchemaOutput, Any, Any], Interceptor[MoveContextInput, MoveContextOutput, Any, Any], Interceptor[PauseExperimentInput, PauseExperimentOutput, Any, Any], Interceptor[PublishInput, PublishOutput, Any, Any], Interceptor[RampExperimentInput, RampExperimentOutput, Any, Any], Interceptor[RemoveMembersFromGroupInput, RemoveMembersFromGroupOutput, Any, Any], Interceptor[ResumeExperimentInput, ResumeExperimentOutput, Any, Any], Interceptor[RotateMasterEncryptionKeyInput, RotateMasterEncryptionKeyOutput, Any, Any], Interceptor[RotateWorkspaceEncryptionKeyInput, RotateWorkspaceEncryptionKeyOutput, Any, Any], Interceptor[TestInput, TestOutput, Any, Any], Interceptor[UpdateDefaultConfigInput, UpdateDefaultConfigOutput, Any, Any], Interceptor[UpdateDimensionInput, UpdateDimensionOutput, Any, Any], Interceptor[UpdateExperimentGroupInput, UpdateExperimentGroupOutput, Any, Any], Interceptor[UpdateFunctionInput, UpdateFunctionOutput, Any, Any], Interceptor[UpdateOrganisationInput, UpdateOrganisationOutput, Any, Any], Interceptor[UpdateOverrideInput, UpdateOverrideOutput, Any, Any], Interceptor[UpdateOverridesExperimentInput, UpdateOverridesExperimentOutput, Any, Any], Interceptor[UpdateSecretInput, UpdateSecretOutput, Any, Any], Interceptor[UpdateTypeTemplatesInput, UpdateTypeTemplatesOutput, Any, Any], Interceptor[UpdateVariableInput, UpdateVariableOutput, Any, Any], Interceptor[UpdateWebhookInput, UpdateWebhookOutput, Any, Any], Interceptor[UpdateWorkspaceInput, UpdateWorkspaceOutput, Any, Any], Interceptor[ValidateContextInput, ValidateContextOutput, Any, Any], Interceptor[WeightRecomputeInput, WeightRecomputeOutput, Any, Any]] @dataclass(init=False) class Config: """Configuration for Superposition.""" diff --git a/clients/python/sdk/superposition_sdk/deserialize.py b/clients/python/sdk/superposition_sdk/deserialize.py index 7b9b65797..a46fdb8e9 100644 --- a/clients/python/sdk/superposition_sdk/deserialize.py +++ b/clients/python/sdk/superposition_sdk/deserialize.py @@ -65,6 +65,8 @@ GetWebhookByEventOutput, GetWebhookOutput, GetWorkspaceOutput, + ImportConfigJsonOutput, + ImportConfigTomlOutput, InternalServerError, ListAuditLogsOutput, ListContextsOutput, @@ -1545,6 +1547,56 @@ async def _deserialize_error_get_workspace(http_response: HTTPResponse, config: case _: return UnknownApiError(f"{code}: {message}") +async def _deserialize_import_config_json(http_response: HTTPResponse, config: Config) -> ImportConfigJsonOutput: + if http_response.status != 200 and http_response.status >= 300: + raise await _deserialize_error_import_config_json(http_response, config) + + kwargs: dict[str, Any] = {} + + body = await http_response.consume_body_async() + if body: + codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS) + deserializer = codec.create_deserializer(body) + body_kwargs = ImportConfigJsonOutput.deserialize_kwargs(deserializer) + kwargs.update(body_kwargs) + + return ImportConfigJsonOutput(**kwargs) + +async def _deserialize_error_import_config_json(http_response: HTTPResponse, config: Config) -> ApiError: + code, message, parsed_body = await parse_rest_json_error_info(http_response) + + match code.lower(): + case "internalservererror": + return await _deserialize_error_internal_server_error(http_response, config, parsed_body, message) + + case _: + return UnknownApiError(f"{code}: {message}") + +async def _deserialize_import_config_toml(http_response: HTTPResponse, config: Config) -> ImportConfigTomlOutput: + if http_response.status != 200 and http_response.status >= 300: + raise await _deserialize_error_import_config_toml(http_response, config) + + kwargs: dict[str, Any] = {} + + body = await http_response.consume_body_async() + if body: + codec = JSONCodec(default_timestamp_format=TimestampFormat.EPOCH_SECONDS) + deserializer = codec.create_deserializer(body) + body_kwargs = ImportConfigTomlOutput.deserialize_kwargs(deserializer) + kwargs.update(body_kwargs) + + return ImportConfigTomlOutput(**kwargs) + +async def _deserialize_error_import_config_toml(http_response: HTTPResponse, config: Config) -> ApiError: + code, message, parsed_body = await parse_rest_json_error_info(http_response) + + match code.lower(): + case "internalservererror": + return await _deserialize_error_internal_server_error(http_response, config, parsed_body, message) + + case _: + return UnknownApiError(f"{code}: {message}") + async def _deserialize_list_audit_logs(http_response: HTTPResponse, config: Config) -> ListAuditLogsOutput: if http_response.status != 200 and http_response.status >= 300: raise await _deserialize_error_list_audit_logs(http_response, config) diff --git a/clients/python/sdk/superposition_sdk/models.py b/clients/python/sdk/superposition_sdk/models.py index 7787f10d3..dd9197e7c 100644 --- a/clients/python/sdk/superposition_sdk/models.py +++ b/clients/python/sdk/superposition_sdk/models.py @@ -186,6 +186,14 @@ GET_WORKSPACE as _SCHEMA_GET_WORKSPACE, GET_WORKSPACE_INPUT as _SCHEMA_GET_WORKSPACE_INPUT, GET_WORKSPACE_OUTPUT as _SCHEMA_GET_WORKSPACE_OUTPUT, + IMPORT_CONFIG_JSON as _SCHEMA_IMPORT_CONFIG_JSON, + IMPORT_CONFIG_JSON_INPUT as _SCHEMA_IMPORT_CONFIG_JSON_INPUT, + IMPORT_CONFIG_JSON_OUTPUT as _SCHEMA_IMPORT_CONFIG_JSON_OUTPUT, + IMPORT_CONFIG_TOML as _SCHEMA_IMPORT_CONFIG_TOML, + IMPORT_CONFIG_TOML_INPUT as _SCHEMA_IMPORT_CONFIG_TOML_INPUT, + IMPORT_CONFIG_TOML_OUTPUT as _SCHEMA_IMPORT_CONFIG_TOML_OUTPUT, + IMPORT_ENTITY_REPORT as _SCHEMA_IMPORT_ENTITY_REPORT, + IMPORT_ERROR_ITEM as _SCHEMA_IMPORT_ERROR_ITEM, INTERNAL_SERVER_ERROR as _SCHEMA_INTERNAL_SERVER_ERROR, LIST_AUDIT_LOGS as _SCHEMA_LIST_AUDIT_LOGS, LIST_AUDIT_LOGS_INPUT as _SCHEMA_LIST_AUDIT_LOGS_INPUT, @@ -15252,6 +15260,472 @@ def _consumer(schema: Schema, de: ShapeDeserializer) -> None: ] ) +class ImportOnError(StrEnum): + """ + How an import reacts when an individual entity fails to apply. + + """ + ABORT = "abort" + """ + Roll the whole import back on the first error. + + """ + CONTINUE_ = "continue" + """ + Apply everything that is valid and report per-entity errors. + + """ + +class ImportStrategy(StrEnum): + """ + How an import applies file entities to the workspace. + + """ + CREATE_ONLY = "create_only" + """ + Create entities that are present in the file but missing from the workspace. + Existing entities are skipped. Nothing is deleted. + + """ + UPSERT = "upsert" + """ + Create missing entities and update existing entities from the file. Entities + absent from the file are left untouched. + + """ + REPLACE = "replace" + """ + Mirror the file: create missing entities, update existing entities, and delete + dimensions, default-configs and contexts that are absent from it. + + """ + +@dataclass(kw_only=True) +class ImportConfigJsonInput: + """ + + :param strategy: + How the import applies file entities to the workspace. Defaults to upsert. + + :param on_error: + Whether to abort (default) or continue on per-entity errors. + + :param dry_run: + When true, validates and summarises the import without persisting anything. + Defaults to false. + + """ + + workspace_id: str | None = None + org_id: str | None = None + strategy: str | None = None + on_error: str | None = None + dry_run: bool | None = None + config_tags: str | None = None + json_config: str | None = None + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_IMPORT_CONFIG_JSON_INPUT, self) + + def serialize_members(self, serializer: ShapeSerializer): + pass + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["workspace_id"] = de.read_string(_SCHEMA_IMPORT_CONFIG_JSON_INPUT.members["workspace_id"]) + + case 1: + kwargs["org_id"] = de.read_string(_SCHEMA_IMPORT_CONFIG_JSON_INPUT.members["org_id"]) + + case 2: + kwargs["strategy"] = de.read_string(_SCHEMA_IMPORT_CONFIG_JSON_INPUT.members["strategy"]) + + case 3: + kwargs["on_error"] = de.read_string(_SCHEMA_IMPORT_CONFIG_JSON_INPUT.members["on_error"]) + + case 4: + kwargs["dry_run"] = de.read_boolean(_SCHEMA_IMPORT_CONFIG_JSON_INPUT.members["dry_run"]) + + case 5: + kwargs["config_tags"] = de.read_string(_SCHEMA_IMPORT_CONFIG_JSON_INPUT.members["config_tags"]) + + case 6: + kwargs["json_config"] = de.read_string(_SCHEMA_IMPORT_CONFIG_JSON_INPUT.members["json_config"]) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_IMPORT_CONFIG_JSON_INPUT, consumer=_consumer) + return kwargs + +@dataclass(kw_only=True) +class ImportErrorItem: + + id: str + + message: str + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_IMPORT_ERROR_ITEM, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_string(_SCHEMA_IMPORT_ERROR_ITEM.members["id"], self.id) + serializer.write_string(_SCHEMA_IMPORT_ERROR_ITEM.members["message"], self.message) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["id"] = de.read_string(_SCHEMA_IMPORT_ERROR_ITEM.members["id"]) + + case 1: + kwargs["message"] = de.read_string(_SCHEMA_IMPORT_ERROR_ITEM.members["message"]) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_IMPORT_ERROR_ITEM, consumer=_consumer) + return kwargs + +def _serialize_import_error_list(serializer: ShapeSerializer, schema: Schema, value: list[ImportErrorItem]) -> None: + member_schema = schema.members["member"] + with serializer.begin_list(schema, len(value)) as ls: + for e in value: + ls.write_struct(member_schema, e) + +def _deserialize_import_error_list(deserializer: ShapeDeserializer, schema: Schema) -> list[ImportErrorItem]: + result: list[ImportErrorItem] = [] + def _read_value(d: ShapeDeserializer): + if d.is_null(): + d.read_null() + + else: + result.append(ImportErrorItem.deserialize(d)) + deserializer.read_list(schema, _read_value) + return result + +@dataclass(kw_only=True) +class ImportEntityReport: + """ + Per-entity outcome counts for an import. + + """ + + created: int + + updated: int + + skipped: int + + deleted: int + + errors: list[ImportErrorItem] | None = None + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_IMPORT_ENTITY_REPORT, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_integer(_SCHEMA_IMPORT_ENTITY_REPORT.members["created"], self.created) + serializer.write_integer(_SCHEMA_IMPORT_ENTITY_REPORT.members["updated"], self.updated) + serializer.write_integer(_SCHEMA_IMPORT_ENTITY_REPORT.members["skipped"], self.skipped) + serializer.write_integer(_SCHEMA_IMPORT_ENTITY_REPORT.members["deleted"], self.deleted) + if self.errors is not None: + _serialize_import_error_list(serializer, _SCHEMA_IMPORT_ENTITY_REPORT.members["errors"], self.errors) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["created"] = de.read_integer(_SCHEMA_IMPORT_ENTITY_REPORT.members["created"]) + + case 1: + kwargs["updated"] = de.read_integer(_SCHEMA_IMPORT_ENTITY_REPORT.members["updated"]) + + case 2: + kwargs["skipped"] = de.read_integer(_SCHEMA_IMPORT_ENTITY_REPORT.members["skipped"]) + + case 3: + kwargs["deleted"] = de.read_integer(_SCHEMA_IMPORT_ENTITY_REPORT.members["deleted"]) + + case 4: + kwargs["errors"] = _deserialize_import_error_list(de, _SCHEMA_IMPORT_ENTITY_REPORT.members["errors"]) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_IMPORT_ENTITY_REPORT, consumer=_consumer) + return kwargs + +@dataclass(kw_only=True) +class ImportConfigJsonOutput: + """ + Summary of what an import created, updated, skipped or deleted. + + :param dimensions: + **[Required]** - Per-entity outcome counts for an import. + + :param default_configs: + **[Required]** - Per-entity outcome counts for an import. + + :param contexts: + **[Required]** - Per-entity outcome counts for an import. + + """ + + strategy: str + + dry_run: bool + + dimensions: ImportEntityReport + + default_configs: ImportEntityReport + + contexts: ImportEntityReport + + config_version: str | None = None + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_string(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT.members["strategy"], self.strategy) + serializer.write_boolean(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT.members["dry_run"], self.dry_run) + if self.config_version is not None: + serializer.write_string(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT.members["config_version"], self.config_version) + + serializer.write_struct(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT.members["dimensions"], self.dimensions) + serializer.write_struct(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT.members["default_configs"], self.default_configs) + serializer.write_struct(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT.members["contexts"], self.contexts) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["strategy"] = de.read_string(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT.members["strategy"]) + + case 1: + kwargs["dry_run"] = de.read_boolean(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT.members["dry_run"]) + + case 2: + kwargs["config_version"] = de.read_string(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT.members["config_version"]) + + case 3: + kwargs["dimensions"] = ImportEntityReport.deserialize(de) + + case 4: + kwargs["default_configs"] = ImportEntityReport.deserialize(de) + + case 5: + kwargs["contexts"] = ImportEntityReport.deserialize(de) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_IMPORT_CONFIG_JSON_OUTPUT, consumer=_consumer) + return kwargs + +IMPORT_CONFIG_JSON = APIOperation( + input = ImportConfigJsonInput, + output = ImportConfigJsonOutput, + schema = _SCHEMA_IMPORT_CONFIG_JSON, + input_schema = _SCHEMA_IMPORT_CONFIG_JSON_INPUT, + output_schema = _SCHEMA_IMPORT_CONFIG_JSON_OUTPUT, + error_registry = TypeRegistry({ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ + ShapeID("smithy.api#httpBasicAuth"), +ShapeID("smithy.api#httpBearerAuth") + ] +) + +@dataclass(kw_only=True) +class ImportConfigTomlInput: + """ + + :param strategy: + How the import applies file entities to the workspace. Defaults to upsert. + + :param on_error: + Whether to abort (default) or continue on per-entity errors. + + :param dry_run: + When true, validates and summarises the import without persisting anything. + Defaults to false. + + """ + + workspace_id: str | None = None + org_id: str | None = None + strategy: str | None = None + on_error: str | None = None + dry_run: bool | None = None + config_tags: str | None = None + toml_config: str | None = None + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_IMPORT_CONFIG_TOML_INPUT, self) + + def serialize_members(self, serializer: ShapeSerializer): + pass + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["workspace_id"] = de.read_string(_SCHEMA_IMPORT_CONFIG_TOML_INPUT.members["workspace_id"]) + + case 1: + kwargs["org_id"] = de.read_string(_SCHEMA_IMPORT_CONFIG_TOML_INPUT.members["org_id"]) + + case 2: + kwargs["strategy"] = de.read_string(_SCHEMA_IMPORT_CONFIG_TOML_INPUT.members["strategy"]) + + case 3: + kwargs["on_error"] = de.read_string(_SCHEMA_IMPORT_CONFIG_TOML_INPUT.members["on_error"]) + + case 4: + kwargs["dry_run"] = de.read_boolean(_SCHEMA_IMPORT_CONFIG_TOML_INPUT.members["dry_run"]) + + case 5: + kwargs["config_tags"] = de.read_string(_SCHEMA_IMPORT_CONFIG_TOML_INPUT.members["config_tags"]) + + case 6: + kwargs["toml_config"] = de.read_string(_SCHEMA_IMPORT_CONFIG_TOML_INPUT.members["toml_config"]) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_IMPORT_CONFIG_TOML_INPUT, consumer=_consumer) + return kwargs + +@dataclass(kw_only=True) +class ImportConfigTomlOutput: + """ + Summary of what an import created, updated, skipped or deleted. + + :param dimensions: + **[Required]** - Per-entity outcome counts for an import. + + :param default_configs: + **[Required]** - Per-entity outcome counts for an import. + + :param contexts: + **[Required]** - Per-entity outcome counts for an import. + + """ + + strategy: str + + dry_run: bool + + dimensions: ImportEntityReport + + default_configs: ImportEntityReport + + contexts: ImportEntityReport + + config_version: str | None = None + + def serialize(self, serializer: ShapeSerializer): + serializer.write_struct(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT, self) + + def serialize_members(self, serializer: ShapeSerializer): + serializer.write_string(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT.members["strategy"], self.strategy) + serializer.write_boolean(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT.members["dry_run"], self.dry_run) + if self.config_version is not None: + serializer.write_string(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT.members["config_version"], self.config_version) + + serializer.write_struct(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT.members["dimensions"], self.dimensions) + serializer.write_struct(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT.members["default_configs"], self.default_configs) + serializer.write_struct(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT.members["contexts"], self.contexts) + + @classmethod + def deserialize(cls, deserializer: ShapeDeserializer) -> Self: + return cls(**cls.deserialize_kwargs(deserializer)) + + @classmethod + def deserialize_kwargs(cls, deserializer: ShapeDeserializer) -> dict[str, Any]: + kwargs: dict[str, Any] = {} + + def _consumer(schema: Schema, de: ShapeDeserializer) -> None: + match schema.expect_member_index(): + case 0: + kwargs["strategy"] = de.read_string(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT.members["strategy"]) + + case 1: + kwargs["dry_run"] = de.read_boolean(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT.members["dry_run"]) + + case 2: + kwargs["config_version"] = de.read_string(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT.members["config_version"]) + + case 3: + kwargs["dimensions"] = ImportEntityReport.deserialize(de) + + case 4: + kwargs["default_configs"] = ImportEntityReport.deserialize(de) + + case 5: + kwargs["contexts"] = ImportEntityReport.deserialize(de) + + case _: + logger.debug("Unexpected member schema: %s", schema) + + deserializer.read_struct(_SCHEMA_IMPORT_CONFIG_TOML_OUTPUT, consumer=_consumer) + return kwargs + +IMPORT_CONFIG_TOML = APIOperation( + input = ImportConfigTomlInput, + output = ImportConfigTomlOutput, + schema = _SCHEMA_IMPORT_CONFIG_TOML, + input_schema = _SCHEMA_IMPORT_CONFIG_TOML_INPUT, + output_schema = _SCHEMA_IMPORT_CONFIG_TOML_OUTPUT, + error_registry = TypeRegistry({ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ + ShapeID("smithy.api#httpBasicAuth"), +ShapeID("smithy.api#httpBearerAuth") + ] +) + @dataclass(kw_only=True) class ListOrganisationInput: """ diff --git a/clients/python/sdk/superposition_sdk/serialize.py b/clients/python/sdk/superposition_sdk/serialize.py index 6883b5c96..3f6afeacd 100644 --- a/clients/python/sdk/superposition_sdk/serialize.py +++ b/clients/python/sdk/superposition_sdk/serialize.py @@ -65,6 +65,8 @@ GetWebhookByEventInput, GetWebhookInput, GetWorkspaceInput, + ImportConfigJsonInput, + ImportConfigTomlInput, ListAuditLogsInput, ListContextsInput, ListDefaultConfigsInput, @@ -1917,6 +1919,90 @@ async def _serialize_get_workspace(input: GetWorkspaceInput, config: Config) -> body=body, ) +async def _serialize_import_config_json(input: ImportConfigJsonInput, config: Config) -> HTTPRequest: + path = "/config/json/import" + query: str = f'' + + body: AsyncIterable[bytes] = AsyncBytesReader(b'') + content_length: int = 0 + if input.json_config is not None: + content = input.json_config.encode('utf-8') + content_length = len(content) + body = SeekableAsyncBytesReader(content) + headers = Fields( + [ + Field(name="Content-Type", values=["text/plain"]), + Field(name="Content-Length", values=[str(content_length)]), + + ] + ) + + if input.workspace_id: + headers.extend(Fields([Field(name="x-workspace", values=[input.workspace_id])])) + if input.org_id: + headers.extend(Fields([Field(name="x-org-id", values=[input.org_id])])) + if input.strategy: + headers.extend(Fields([Field(name="x-import-strategy", values=[input.strategy])])) + if input.on_error: + headers.extend(Fields([Field(name="x-import-on-error", values=[input.on_error])])) + if input.dry_run is not None: + headers.extend(Fields([Field(name="x-import-dry-run", values=[('true' if input.dry_run else 'false')])])) + if input.config_tags: + headers.extend(Fields([Field(name="x-config-tags", values=[input.config_tags])])) + return _HTTPRequest( + destination=_URI( + host="", + path=path, + scheme="https", + query=query, + ), + method="POST", + fields=headers, + body=body, + ) + +async def _serialize_import_config_toml(input: ImportConfigTomlInput, config: Config) -> HTTPRequest: + path = "/config/toml/import" + query: str = f'' + + body: AsyncIterable[bytes] = AsyncBytesReader(b'') + content_length: int = 0 + if input.toml_config is not None: + content = input.toml_config.encode('utf-8') + content_length = len(content) + body = SeekableAsyncBytesReader(content) + headers = Fields( + [ + Field(name="Content-Type", values=["text/plain"]), + Field(name="Content-Length", values=[str(content_length)]), + + ] + ) + + if input.workspace_id: + headers.extend(Fields([Field(name="x-workspace", values=[input.workspace_id])])) + if input.org_id: + headers.extend(Fields([Field(name="x-org-id", values=[input.org_id])])) + if input.strategy: + headers.extend(Fields([Field(name="x-import-strategy", values=[input.strategy])])) + if input.on_error: + headers.extend(Fields([Field(name="x-import-on-error", values=[input.on_error])])) + if input.dry_run is not None: + headers.extend(Fields([Field(name="x-import-dry-run", values=[('true' if input.dry_run else 'false')])])) + if input.config_tags: + headers.extend(Fields([Field(name="x-config-tags", values=[input.config_tags])])) + return _HTTPRequest( + destination=_URI( + host="", + path=path, + scheme="https", + query=query, + ), + method="POST", + fields=headers, + body=body, + ) + async def _serialize_list_audit_logs(input: ListAuditLogsInput, config: Config) -> HTTPRequest: path = "/audit" query: str = f'' diff --git a/crates/context_aware_config/src/api/config.rs b/crates/context_aware_config/src/api/config.rs index 62a998ddb..fc07c1737 100644 --- a/crates/context_aware_config/src/api/config.rs +++ b/crates/context_aware_config/src/api/config.rs @@ -1,3 +1,4 @@ mod handlers; pub use handlers::endpoints; pub mod helpers; +pub mod import; diff --git a/crates/context_aware_config/src/api/config/handlers.rs b/crates/context_aware_config/src/api/config/handlers.rs index bd8f73488..0101b62aa 100644 --- a/crates/context_aware_config/src/api/config/handlers.rs +++ b/crates/context_aware_config/src/api/config/handlers.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; use actix_web::{ - HttpRequest, HttpResponse, Scope, get, put, routes, - web::{Data, Header, Json, Path, Query}, + HttpRequest, HttpResponse, Scope, get, post, put, routes, + web::{Bytes, Data, Header, Json, Path, Query}, }; use chrono::{DateTime, Utc}; use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper}; @@ -11,7 +11,9 @@ use serde_json::{Map, Value, json}; use service_utils::{ helpers::{fetch_dimensions_info_map, is_not_modified}, redis::{CONFIG_KEY_SUFFIX, LAST_MODIFIED_KEY_SUFFIX, read_through_cache}, - service::types::{AppHeader, AppState, DbConnection, WorkspaceContext}, + service::types::{ + AppHeader, AppState, CustomHeaders, DbConnection, WorkspaceContext, + }, }; use superposition_core::{ ConfigFormat, JsonFormat, TomlFormat, @@ -69,6 +71,8 @@ pub fn endpoints() -> Scope { .service(get_toml_handler) .service(get_json_handler) .service(detailed_resolve_handler) + .service(import_toml_handler) + .service(import_json_handler) .service(resolve_handler) .service(explain_resolve_handler) .service(reduce_handler) @@ -659,6 +663,60 @@ async fn get_json_handler( Ok(response.body(json_str)) } +/// Imports a full config supplied as a TOML document in the request body. +/// See [`crate::api::config::import`] for the supported `x-import-*` options. +#[allow(clippy::too_many_arguments)] +#[authorized] +#[post("/toml/import")] +async fn import_toml_handler( + req: HttpRequest, + body: Bytes, + user: User, + custom_headers: CustomHeaders, + db_conn: DbConnection, + workspace_context: WorkspaceContext, + state: Data, +) -> superposition::Result { + let DbConnection(mut conn) = db_conn; + super::import::handle_import::( + &body, + &req, + custom_headers, + &user, + &workspace_context, + &state, + &mut conn, + ) + .await +} + +/// Imports a full config supplied as a JSON document in the request body. +/// See [`crate::api::config::import`] for the supported `x-import-*` options. +#[allow(clippy::too_many_arguments)] +#[authorized] +#[post("/json/import")] +async fn import_json_handler( + req: HttpRequest, + body: Bytes, + user: User, + custom_headers: CustomHeaders, + db_conn: DbConnection, + workspace_context: WorkspaceContext, + state: Data, +) -> superposition::Result { + let DbConnection(mut conn) = db_conn; + super::import::handle_import::( + &body, + &req, + custom_headers, + &user, + &workspace_context, + &state, + &mut conn, + ) + .await +} + #[allow(clippy::too_many_arguments)] #[authorized] #[routes] diff --git a/crates/context_aware_config/src/api/config/import.rs b/crates/context_aware_config/src/api/config/import.rs new file mode 100644 index 000000000..9a3c2465b --- /dev/null +++ b/crates/context_aware_config/src/api/config/import.rs @@ -0,0 +1,843 @@ +//! Import support for SuperTOML. +//! +//! This module powers the "import" side of the TOML/JSON config endpoints. +//! When a body is POSTed to `/config/toml` or `/config/json`, it is parsed +//! (and fully validated) into a [`DetailedConfig`] via the format's +//! [`ConfigFormat::parse_into_detailed`] and then persisted to the workspace. +//! +//! The behaviour is controlled through request headers: +//! - `x-import-strategy`: `upsert` (default) creates missing entities and +//! updates existing entities; `create_only` only creates missing entities and +//! skips existing entities; `replace` mirrors the file by also deleting any +//! dimension/default-config/context that is absent from it. +//! - `x-import-on-error`: `abort` (default) fails the whole import on the +//! first error; `continue` records per-entity errors and applies the rest. +//! - `x-import-dry-run`: `true` parses, validates and computes the summary +//! without persisting anything (the transaction is rolled back). + +use std::collections::HashSet; + +use actix_web::{HttpRequest, HttpResponse, web::Data}; +use chrono::Utc; +use diesel::{Connection, ExpressionMethods, QueryDsl, RunQueryDsl}; +use serde::Serialize; +use serde_json::Value; +use service_utils::{ + helpers::{WebhookData, execute_webhook_call, parse_config_tags}, + service::types::{AppState, CustomHeaders, SchemaName, WorkspaceContext}, +}; +use superposition_core::{ConfigFormat, helpers::calculate_context_weight}; +use superposition_macros::{bad_argument, db_error}; +use superposition_types::{ + Context as ConfigContext, DBConnection, DefaultConfigInfo, DimensionInfo, + ExtendedMap, Overrides, Resource, User, + api::webhook::Action, + database::models::{ + ChangeReason, Description, + cac::{Context as DbContext, DefaultConfig, Dimension, Position}, + others::WebhookEvent, + }, + database::schema::{ + contexts::dsl as ctx_dsl, default_configs::dsl as dc_dsl, + dimensions::dsl as dim_dsl, + }, + result as superposition, +}; + +use crate::{ + api::context::operations, + helpers::{add_config_version, put_config_in_redis}, +}; + +// --------------------------------------------------------------------------- +// Options +// --------------------------------------------------------------------------- + +#[derive(Clone, Copy, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum ImportStrategy { + CreateOnly, + Upsert, + Replace, +} + +#[derive(Clone, Copy, PartialEq)] +pub enum OnError { + Abort, + Continue, +} + +#[derive(Clone, Copy)] +pub struct ImportOptions { + pub strategy: ImportStrategy, + pub on_error: OnError, + pub dry_run: bool, +} + +fn header_bool(req: &HttpRequest, name: &str, default: bool) -> bool { + req.headers() + .get(name) + .and_then(|v| v.to_str().ok()) + .and_then(|s| s.parse::().ok()) + .unwrap_or(default) +} + +impl ImportOptions { + pub fn from_request(req: &HttpRequest) -> superposition::Result { + for header in [ + "x-import-mode", + "x-import-overwrite", + "x-import-value-merge", + ] { + if req.headers().contains_key(header) { + return Err(bad_argument!( + "Header '{}' is no longer supported; use x-import-strategy", + header + )); + } + } + + let strategy = match req + .headers() + .get("x-import-strategy") + .and_then(|v| v.to_str().ok()) + { + None => ImportStrategy::Upsert, + Some(s) if s.eq_ignore_ascii_case("create_only") => { + ImportStrategy::CreateOnly + } + Some(s) if s.eq_ignore_ascii_case("upsert") => ImportStrategy::Upsert, + Some(s) if s.eq_ignore_ascii_case("replace") => ImportStrategy::Replace, + Some(s) => { + return Err(bad_argument!( + "Invalid x-import-strategy '{}', expected 'create_only', 'upsert' or 'replace'", + s + )); + } + }; + let on_error = match req + .headers() + .get("x-import-on-error") + .and_then(|v| v.to_str().ok()) + { + None => OnError::Abort, + Some(s) if s.eq_ignore_ascii_case("abort") => OnError::Abort, + Some(s) if s.eq_ignore_ascii_case("continue") => OnError::Continue, + Some(s) => { + return Err(bad_argument!( + "Invalid x-import-on-error '{}', expected 'abort' or 'continue'", + s + )); + } + }; + Ok(Self { + strategy, + on_error, + dry_run: header_bool(req, "x-import-dry-run", false), + }) + } +} + +// --------------------------------------------------------------------------- +// Summary +// --------------------------------------------------------------------------- + +#[derive(Serialize)] +pub struct ImportError { + pub id: String, + pub message: String, +} + +#[derive(Default, Serialize)] +pub struct EntityReport { + pub created: usize, + pub updated: usize, + pub skipped: usize, + pub deleted: usize, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub errors: Vec, +} + +impl EntityReport { + fn record(&mut self, outcome: Outcome) { + match outcome { + Outcome::Created => self.created += 1, + Outcome::Updated => self.updated += 1, + Outcome::Skipped => self.skipped += 1, + Outcome::Deleted => self.deleted += 1, + } + } +} + +#[derive(Serialize)] +pub struct ImportSummary { + pub strategy: ImportStrategy, + pub dry_run: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub config_version: Option, + pub dimensions: EntityReport, + pub default_configs: EntityReport, + pub contexts: EntityReport, +} + +impl ImportSummary { + fn new(opts: &ImportOptions) -> Self { + Self { + strategy: opts.strategy, + dry_run: opts.dry_run, + config_version: None, + dimensions: EntityReport::default(), + default_configs: EntityReport::default(), + contexts: EntityReport::default(), + } + } +} + +#[derive(Clone, Copy)] +enum Outcome { + Created, + Updated, + Skipped, + Deleted, +} + +// --------------------------------------------------------------------------- +// Transaction error plumbing +// --------------------------------------------------------------------------- + +/// Error type used inside the import transaction. `DryRun` carries the summary +/// out of the (deliberately rolled-back) transaction so it can still be +/// returned to the caller. +enum TxError { + App(superposition::AppError), + DryRun(Box), +} + +impl From for TxError { + fn from(e: superposition::AppError) -> Self { + TxError::App(e) + } +} + +impl From for TxError { + fn from(e: diesel::result::Error) -> Self { + TxError::App(db_error!(e)) + } +} + +/// Run `f` within a SAVEPOINT so that a failure rolls back only the work done +/// by `f` (leaving the surrounding transaction usable). This is what makes the +/// `continue-on-error` option safe in the face of Postgres aborting the +/// current transaction on a database error. +fn with_savepoint( + conn: &mut DBConnection, + name: &str, + f: impl FnOnce(&mut DBConnection) -> superposition::Result, +) -> superposition::Result { + diesel::sql_query(format!("SAVEPOINT {name}")) + .execute(conn) + .map_err(|e| db_error!(e))?; + match f(conn) { + Ok(v) => { + diesel::sql_query(format!("RELEASE SAVEPOINT {name}")) + .execute(conn) + .map_err(|e| db_error!(e))?; + Ok(v) + } + Err(e) => { + diesel::sql_query(format!("ROLLBACK TO SAVEPOINT {name}")) + .execute(conn) + .map_err(|e| db_error!(e))?; + Err(e) + } + } +} + +/// Apply the `on-error` policy to a single entity's result, recording it into +/// the report. Returns `Err` only when the policy is `abort`. +fn apply_outcome( + report: &mut EntityReport, + on_error: OnError, + id: &str, + result: superposition::Result, +) -> Result<(), TxError> { + match result { + Ok(outcome) => { + report.record(outcome); + Ok(()) + } + Err(e) => match on_error { + OnError::Continue => { + report.errors.push(ImportError { + id: id.to_string(), + message: e.message(), + }); + Ok(()) + } + OnError::Abort => Err(TxError::App(e)), + }, + } +} + +fn import_change_reason() -> ChangeReason { + ChangeReason::try_from("Imported via SuperTOML config import".to_string()) + .unwrap_or_default() +} + +fn import_description() -> Description { + Description::try_from("Config imported via TOML/JSON import".to_string()) + .unwrap_or_default() +} + +// --------------------------------------------------------------------------- +// Pure builders / decisions (no database access — unit tested below) +// --------------------------------------------------------------------------- + +/// Whether an entity that already exists should be left untouched. +fn should_skip(exists: bool, strategy: ImportStrategy) -> bool { + exists && strategy == ImportStrategy::CreateOnly +} + +fn dimension_position( + name: &str, + info: &DimensionInfo, +) -> superposition::Result { + Position::try_from(info.position) + .map_err(|e| bad_argument!("Invalid position for dimension '{}': {}", name, e)) +} + +fn build_dimension_row( + name: &str, + info: &DimensionInfo, + email: &str, +) -> superposition::Result { + Ok(Dimension { + dimension: name.to_string(), + schema: info.schema.clone(), + position: dimension_position(name, info)?, + dimension_type: info.dimension_type.clone(), + dependency_graph: info.dependency_graph.clone(), + value_compute_function_name: info.value_compute_function_name.clone(), + // Function bindings are not carried by the import file. + value_validation_function_name: None, + created_at: Utc::now(), + created_by: email.to_string(), + last_modified_at: Utc::now(), + last_modified_by: email.to_string(), + description: import_description(), + change_reason: import_change_reason(), + }) +} + +fn build_schema(key: &str, schema: &Value) -> superposition::Result { + ExtendedMap::try_from(schema.clone()) + .map_err(|e| bad_argument!("Invalid schema for default config '{}': {}", key, e)) +} + +fn build_default_config_row( + key: &str, + value: Value, + schema: ExtendedMap, + email: &str, +) -> DefaultConfig { + DefaultConfig { + key: key.to_string(), + value, + schema, + value_validation_function_name: None, + value_compute_function_name: None, + created_at: Utc::now(), + created_by: email.to_string(), + last_modified_at: Utc::now(), + last_modified_by: email.to_string(), + description: import_description(), + change_reason: import_change_reason(), + } +} + +// --------------------------------------------------------------------------- +// Per-entity writers +// --------------------------------------------------------------------------- + +fn write_dimension( + conn: &mut DBConnection, + schema_name: &SchemaName, + name: &str, + info: &DimensionInfo, + opts: &ImportOptions, + email: &str, +) -> superposition::Result { + let exists = dim_dsl::dimensions + .filter(dim_dsl::dimension.eq(name)) + .count() + .schema_name(schema_name) + .get_result::(conn)? + > 0; + + if should_skip(exists, opts.strategy) { + return Ok(Outcome::Skipped); + } + + if exists { + let position = dimension_position(name, info)?; + diesel::update(dim_dsl::dimensions.filter(dim_dsl::dimension.eq(name))) + .set(( + dim_dsl::schema.eq(info.schema.clone()), + dim_dsl::position.eq(position), + dim_dsl::dimension_type.eq(info.dimension_type.clone()), + dim_dsl::dependency_graph.eq(info.dependency_graph.clone()), + dim_dsl::value_compute_function_name + .eq(info.value_compute_function_name.clone()), + dim_dsl::last_modified_at.eq(Utc::now()), + dim_dsl::last_modified_by.eq(email), + dim_dsl::change_reason.eq(import_change_reason()), + )) + .schema_name(schema_name) + .execute(conn)?; + Ok(Outcome::Updated) + } else { + let dimension = build_dimension_row(name, info, email)?; + diesel::insert_into(dim_dsl::dimensions) + .values(&dimension) + .schema_name(schema_name) + .execute(conn)?; + Ok(Outcome::Created) + } +} + +fn write_default_config( + conn: &mut DBConnection, + schema_name: &SchemaName, + key: &str, + info: &DefaultConfigInfo, + opts: &ImportOptions, + email: &str, +) -> superposition::Result { + let exists = dc_dsl::default_configs + .filter(dc_dsl::key.eq(key)) + .count() + .schema_name(schema_name) + .get_result::(conn)? + > 0; + + if should_skip(exists, opts.strategy) { + return Ok(Outcome::Skipped); + } + + let value = info.value.clone(); + let schema = build_schema(key, &info.schema)?; + + if exists { + diesel::update(dc_dsl::default_configs.filter(dc_dsl::key.eq(key))) + .set(( + dc_dsl::value.eq(value), + dc_dsl::schema.eq(schema), + dc_dsl::last_modified_at.eq(Utc::now()), + dc_dsl::last_modified_by.eq(email), + dc_dsl::change_reason.eq(import_change_reason()), + )) + .schema_name(schema_name) + .execute(conn)?; + Ok(Outcome::Updated) + } else { + let default_config = build_default_config_row(key, value, schema, email); + diesel::insert_into(dc_dsl::default_configs) + .values(&default_config) + .schema_name(schema_name) + .execute(conn)?; + Ok(Outcome::Created) + } +} + +#[allow(clippy::too_many_arguments)] +fn write_context( + conn: &mut DBConnection, + workspace_context: &WorkspaceContext, + ctx: &ConfigContext, + overrides: &std::collections::HashMap, + dimensions: &std::collections::HashMap, + opts: &ImportOptions, + user: &User, + email: &str, +) -> superposition::Result { + let schema_name = &workspace_context.schema_name; + let override_key = ctx.override_with_keys.get_key(); + let override_ = overrides.get(override_key).cloned().ok_or_else(|| { + bad_argument!( + "Override '{}' referenced by context '{}' not found in file", + override_key, + ctx.id + ) + })?; + + let exists = ctx_dsl::contexts + .filter(ctx_dsl::id.eq(&ctx.id)) + .count() + .schema_name(schema_name) + .get_result::(conn)? + > 0; + + if should_skip(exists, opts.strategy) { + return Ok(Outcome::Skipped); + } + + let weight = calculate_context_weight(&ctx.condition, dimensions) + .map_err(|e| bad_argument!("Failed to compute context weight: {}", e))?; + + let new_ctx = DbContext { + id: ctx.id.clone(), + value: ctx.condition.clone(), + override_id: override_key.clone(), + override_, + weight, + created_at: Utc::now(), + created_by: email.to_string(), + last_modified_at: Utc::now(), + last_modified_by: email.to_string(), + description: import_description(), + change_reason: import_change_reason(), + }; + + operations::upsert(conn, true, user, workspace_context, true, new_ctx)?; + Ok(if exists { + Outcome::Updated + } else { + Outcome::Created + }) +} + +// --------------------------------------------------------------------------- +// Core import +// --------------------------------------------------------------------------- + +pub async fn import_config( + body: &str, + opts: ImportOptions, + tags: Option>, + user: &User, + workspace_context: &WorkspaceContext, + state: &Data, + conn: &mut DBConnection, +) -> superposition::Result { + let parsed = F::parse_into_detailed(body) + .map_err(|e| bad_argument!("Failed to parse config: {}", e))?; + let schema_name = &workspace_context.schema_name; + let email = user.get_email(); + + let tx_result = conn.transaction::<_, TxError, _>(|conn| { + let mut summary = ImportSummary::new(&opts); + + // Order matters for referential consistency: dimensions and default + // configs are written before contexts that reference them. + for (name, info) in &parsed.dimensions { + let res = with_savepoint(conn, "cac_import_dim", |c| { + write_dimension(c, schema_name, name, info, &opts, &email) + }); + apply_outcome(&mut summary.dimensions, opts.on_error, name, res)?; + } + + for (key, info) in parsed.default_configs.iter() { + let res = with_savepoint(conn, "cac_import_dc", |c| { + write_default_config(c, schema_name, key, info, &opts, &email) + }); + apply_outcome(&mut summary.default_configs, opts.on_error, key, res)?; + } + + for ctx in &parsed.contexts { + let res = with_savepoint(conn, "cac_import_ctx", |c| { + write_context( + c, + workspace_context, + ctx, + &parsed.overrides, + &parsed.dimensions, + &opts, + user, + &email, + ) + }); + apply_outcome(&mut summary.contexts, opts.on_error, &ctx.id, res)?; + } + + // Replace strategy: delete anything in the workspace that is not + // present in the imported file. Contexts first, then the entities they + // can reference. + if opts.strategy == ImportStrategy::Replace { + let file_ctx_ids: HashSet<&String> = + parsed.contexts.iter().map(|c| &c.id).collect(); + let db_ctx_ids: Vec = ctx_dsl::contexts + .select(ctx_dsl::id) + .schema_name(schema_name) + .load::(conn)?; + for id in db_ctx_ids { + if file_ctx_ids.contains(&id) { + continue; + } + let res = with_savepoint(conn, "cac_import_del_ctx", |c| { + diesel::delete(ctx_dsl::contexts.filter(ctx_dsl::id.eq(&id))) + .schema_name(schema_name) + .execute(c) + .map_err(|e| db_error!(e))?; + Ok(Outcome::Deleted) + }); + apply_outcome(&mut summary.contexts, opts.on_error, &id, res)?; + } + + let db_dc_keys: Vec = dc_dsl::default_configs + .select(dc_dsl::key) + .schema_name(schema_name) + .load::(conn)?; + for key in db_dc_keys { + if parsed.default_configs.contains_key(&key) { + continue; + } + let res = with_savepoint(conn, "cac_import_del_dc", |c| { + diesel::delete(dc_dsl::default_configs.filter(dc_dsl::key.eq(&key))) + .schema_name(schema_name) + .execute(c) + .map_err(|e| db_error!(e))?; + Ok(Outcome::Deleted) + }); + apply_outcome(&mut summary.default_configs, opts.on_error, &key, res)?; + } + + let db_dim_names: Vec = dim_dsl::dimensions + .select(dim_dsl::dimension) + .schema_name(schema_name) + .load::(conn)?; + for name in db_dim_names { + if parsed.dimensions.contains_key(&name) { + continue; + } + let res = with_savepoint(conn, "cac_import_del_dim", |c| { + diesel::delete( + dim_dsl::dimensions.filter(dim_dsl::dimension.eq(&name)), + ) + .schema_name(schema_name) + .execute(c) + .map_err(|e| db_error!(e))?; + Ok(Outcome::Deleted) + }); + apply_outcome(&mut summary.dimensions, opts.on_error, &name, res)?; + } + } + + if opts.dry_run { + // Roll back everything; the summary travels out via the error. + return Err(TxError::DryRun(Box::new(summary))); + } + + let config_version = + add_config_version(state, tags, import_description(), conn, schema_name)?; + Ok((summary, config_version)) + }); + + match tx_result { + Ok((mut summary, config_version)) => { + summary.config_version = Some(config_version.id.to_string()); + + let _ = put_config_in_redis(&config_version, state, schema_name, conn).await; + + let data = WebhookData { + payload: &summary, + resource: Resource::Config, + action: Action::Update, + event: WebhookEvent::ConfigChanged, + config_version_opt: Some(config_version.id.to_string()), + }; + let _ = execute_webhook_call(data, workspace_context, state, conn).await; + + Ok(summary) + } + Err(TxError::DryRun(summary)) => Ok(*summary), + Err(TxError::App(e)) => Err(e), + } +} + +/// HTTP entry-point used by the `/config/toml` and `/config/json` handlers when +/// a body is POSTed. Parses options/tags from the request and returns the +/// import summary as JSON. +pub async fn handle_import( + body: &[u8], + req: &HttpRequest, + custom_headers: CustomHeaders, + user: &User, + workspace_context: &WorkspaceContext, + state: &Data, + conn: &mut DBConnection, +) -> superposition::Result { + let body_str = std::str::from_utf8(body) + .map_err(|_| bad_argument!("Request body is not valid UTF-8"))?; + let opts = ImportOptions::from_request(req)?; + let tags = parse_config_tags(custom_headers.config_tags)?; + + let summary = + import_config::(body_str, opts, tags, user, workspace_context, state, conn) + .await?; + + Ok(HttpResponse::Ok().json(summary)) +} + +#[cfg(test)] +mod tests { + use actix_web::test::TestRequest; + use serde_json::json; + use superposition_types::database::models::cac::{DependencyGraph, DimensionType}; + + use super::*; + + fn dim_info(position: i32) -> DimensionInfo { + DimensionInfo { + schema: ExtendedMap::try_from(json!({ "type": "string" })).unwrap(), + position, + dimension_type: DimensionType::Regular {}, + dependency_graph: DependencyGraph::default(), + value_compute_function_name: None, + description: String::new(), + } + } + + #[test] + fn options_default_to_upsert() { + let req = TestRequest::default().to_http_request(); + let opts = ImportOptions::from_request(&req).unwrap(); + assert!(opts.strategy == ImportStrategy::Upsert); + assert!(opts.on_error == OnError::Abort); + assert!(!opts.dry_run); + } + + #[test] + fn options_parsed_from_headers() { + let req = TestRequest::default() + .insert_header(("x-import-strategy", "replace")) + .insert_header(("x-import-on-error", "continue")) + .insert_header(("x-import-dry-run", "true")) + .to_http_request(); + let opts = ImportOptions::from_request(&req).unwrap(); + assert!(opts.strategy == ImportStrategy::Replace); + assert!(opts.on_error == OnError::Continue); + assert!(opts.dry_run); + } + + #[test] + fn invalid_strategy_is_rejected() { + let req = TestRequest::default() + .insert_header(("x-import-strategy", "bogus")) + .to_http_request(); + assert!(ImportOptions::from_request(&req).is_err()); + } + + #[test] + fn removed_import_headers_are_rejected() { + for header in [ + "x-import-mode", + "x-import-overwrite", + "x-import-value-merge", + ] { + let req = TestRequest::default() + .insert_header((header, "true")) + .to_http_request(); + assert!(ImportOptions::from_request(&req).is_err()); + } + } + + #[test] + fn invalid_on_error_is_rejected() { + let req = TestRequest::default() + .insert_header(("x-import-on-error", "maybe")) + .to_http_request(); + assert!(ImportOptions::from_request(&req).is_err()); + } + + #[test] + fn should_skip_only_existing_create_only_entities() { + assert!(should_skip(true, ImportStrategy::CreateOnly)); + assert!(!should_skip(false, ImportStrategy::CreateOnly)); + assert!(!should_skip(true, ImportStrategy::Upsert)); + assert!(!should_skip(true, ImportStrategy::Replace)); + } + + #[test] + fn dimension_position_rejects_negative() { + assert!(dimension_position("city", &dim_info(-1)).is_err()); + assert!(dimension_position("city", &dim_info(0)).is_ok()); + assert!(dimension_position("city", &dim_info(7)).is_ok()); + } + + #[test] + fn build_dimension_row_maps_fields_and_clears_validation_fn() { + let mut info = dim_info(3); + info.value_compute_function_name = Some("compute_fn".to_string()); + + let row = build_dimension_row("city", &info, "tester@example.com").unwrap(); + + assert_eq!(row.dimension, "city"); + assert_eq!(i32::from(row.position), 3); + assert_eq!( + row.value_compute_function_name.as_deref(), + Some("compute_fn") + ); + // validation-fn bindings are not carried by the file + assert!(row.value_validation_function_name.is_none()); + assert_eq!(row.created_by, "tester@example.com"); + assert!(matches!(row.dimension_type, DimensionType::Regular {})); + } + + #[test] + fn build_schema_requires_an_object() { + assert!(build_schema("k", &json!({ "type": "number" })).is_ok()); + // a non-object schema is rejected + assert!(build_schema("k", &json!("not-an-object")).is_err()); + } + + #[test] + fn build_default_config_row_carries_value_and_schema() { + let schema = build_schema("per_km_rate", &json!({ "type": "number" })).unwrap(); + let row = build_default_config_row( + "per_km_rate", + json!(20.0), + schema, + "tester@example.com", + ); + + assert_eq!(row.key, "per_km_rate"); + assert_eq!(row.value, json!(20.0)); + assert!(row.value_validation_function_name.is_none()); + assert!(row.value_compute_function_name.is_none()); + assert_eq!(row.created_by, "tester@example.com"); + } + + #[test] + fn summary_serialises_with_strategy_and_hides_empty_errors() { + let opts = ImportOptions { + strategy: ImportStrategy::Replace, + on_error: OnError::Abort, + dry_run: true, + }; + let summary = ImportSummary::new(&opts); + let value = serde_json::to_value(&summary).unwrap(); + + assert_eq!(value["strategy"], json!("replace")); + assert_eq!(value["dry_run"], json!(true)); + // config_version omitted until the import commits + assert!(value.get("config_version").is_none()); + // no errors array when empty + assert!(value["dimensions"].get("errors").is_none()); + assert_eq!(value["dimensions"]["created"], json!(0)); + } + + #[test] + fn entity_report_records_outcomes() { + let mut report = EntityReport::default(); + report.record(Outcome::Created); + report.record(Outcome::Created); + report.record(Outcome::Updated); + report.record(Outcome::Skipped); + report.record(Outcome::Deleted); + + assert_eq!(report.created, 2); + assert_eq!(report.updated, 1); + assert_eq!(report.skipped, 1); + assert_eq!(report.deleted, 1); + } +} diff --git a/crates/frontend/Cargo.toml b/crates/frontend/Cargo.toml index f3e6b97db..0f40da82d 100644 --- a/crates/frontend/Cargo.toml +++ b/crates/frontend/Cargo.toml @@ -34,6 +34,7 @@ superposition_types = { workspace = true, features = [ "experimentation", "api", ] } +toml = { workspace = true } url = { workspace = true } wasm-bindgen = "0.2.100" wasm-bindgen-futures = "0.4.50" @@ -41,9 +42,13 @@ web-sys = { version = "0.3.64", features = [ "Event", "Worker", "Blob", + "File", + "FileList", + "FileReader", "Window", "Element", "DomRect", + "HtmlInputElement", "HtmlElement", "UiEvent", "MouseEvent", diff --git a/crates/frontend/src/api.rs b/crates/frontend/src/api.rs index 4480ab8b3..63742a836 100644 --- a/crates/frontend/src/api.rs +++ b/crates/frontend/src/api.rs @@ -1,4 +1,5 @@ use reqwest::header::HeaderMap; +use serde::Deserialize; use serde_json::{Map, Value}; use superposition_types::{ Config, PaginatedResponse, @@ -23,7 +24,8 @@ use superposition_types::{ }; use crate::utils::{ - construct_request_headers, parse_json_response, request, use_host_server, + construct_request_headers, parse_json_response, request, request_raw_body, + use_host_server, }; pub mod casbin { @@ -561,6 +563,185 @@ pub async fn fetch_config( parse_json_response(response).await } +pub mod config_import { + use super::*; + + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum ImportFormat { + Json, + Toml, + } + + impl ImportFormat { + pub fn from_file_name(file_name: &str) -> Self { + if file_name.to_lowercase().ends_with(".json") { + Self::Json + } else { + Self::Toml + } + } + + fn content_type(&self) -> &'static str { + match self { + Self::Json => "application/json", + Self::Toml => "application/toml", + } + } + + fn path_segment(&self) -> &'static str { + match self { + Self::Json => "json", + Self::Toml => "toml", + } + } + } + + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum ImportStrategy { + CreateOnly, + Upsert, + Replace, + } + + impl ImportStrategy { + pub fn as_str(&self) -> &'static str { + match self { + Self::CreateOnly => "create_only", + Self::Upsert => "upsert", + Self::Replace => "replace", + } + } + } + + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum ImportOnError { + Abort, + Continue, + } + + impl ImportOnError { + fn as_str(&self) -> &'static str { + match self { + Self::Abort => "abort", + Self::Continue => "continue", + } + } + } + + #[derive(Clone, Debug)] + pub struct ImportOptions { + pub strategy: ImportStrategy, + pub on_error: ImportOnError, + pub dry_run: bool, + pub config_tags: String, + } + + #[derive(Clone, Debug, Default, Deserialize, PartialEq)] + pub struct ImportErrorItem { + pub id: String, + pub message: String, + } + + #[derive(Clone, Debug, Default, Deserialize, PartialEq)] + pub struct ImportEntityReport { + pub created: usize, + pub updated: usize, + pub skipped: usize, + pub deleted: usize, + #[serde(default)] + pub errors: Vec, + } + + impl ImportEntityReport { + pub fn total(&self) -> usize { + self.created + self.updated + self.skipped + self.deleted + } + } + + #[derive(Clone, Debug, Default, Deserialize, PartialEq)] + pub struct ImportSummary { + pub strategy: String, + pub dry_run: bool, + pub config_version: Option, + pub dimensions: ImportEntityReport, + pub default_configs: ImportEntityReport, + pub contexts: ImportEntityReport, + } + + impl ImportSummary { + pub fn total_changes(&self) -> usize { + self.dimensions.total() + self.default_configs.total() + self.contexts.total() + } + + pub fn total_deleted(&self) -> usize { + self.dimensions.deleted + self.default_configs.deleted + self.contexts.deleted + } + + pub fn total_errors(&self) -> usize { + self.dimensions.errors.len() + + self.default_configs.errors.len() + + self.contexts.errors.len() + } + } + + pub async fn import_config( + file_text: String, + format: ImportFormat, + options: ImportOptions, + workspace: &str, + org_id: &str, + ) -> Result { + let host = use_host_server(); + let url = format!("{host}/config/{}/import", format.path_segment()); + let dry_run = options.dry_run.to_string(); + let config_tags = options.config_tags.trim().to_string(); + let mut header_entries = vec![ + ("x-workspace", workspace), + ("x-org-id", org_id), + ("Content-Type", format.content_type()), + ("x-import-strategy", options.strategy.as_str()), + ("x-import-on-error", options.on_error.as_str()), + ("x-import-dry-run", dry_run.as_str()), + ]; + + if !config_tags.is_empty() { + header_entries.push(("x-config-tags", config_tags.as_str())); + } + + let response = request_raw_body( + url, + reqwest::Method::POST, + file_text, + construct_request_headers(&header_entries)?, + ) + .await?; + + parse_json_response(response).await + } + + pub async fn export_config( + format: ImportFormat, + workspace: &str, + org_id: &str, + ) -> Result { + let host = use_host_server(); + let url = format!("{host}/config/{}", format.path_segment()); + + let response = request( + url, + reqwest::Method::GET, + None::<()>, + construct_request_headers(&[ + ("x-workspace", workspace), + ("x-org-id", org_id), + ])?, + ) + .await?; + + response.text().await.map_err(|err| err.to_string()) + } +} + pub async fn fetch_context( pagination: &PaginationParams, context_filters: &ContextListFilters, diff --git a/crates/frontend/src/app.rs b/crates/frontend/src/app.rs index 23a8dd2e1..9418f1a91 100755 --- a/crates/frontend/src/app.rs +++ b/crates/frontend/src/app.rs @@ -28,6 +28,7 @@ use crate::pages::{ default_config_list::DefaultConfigList, experiment::ExperimentPage, home::Home, + import_config::ImportConfig, organisations::Organisations, override_page::{CreateOverride, EditOverride, OverridePage}, type_template::TypePage, @@ -342,6 +343,12 @@ pub fn App(app_envs: Envs) -> impl IntoView { view=Home /> + + Vec { icon: "ri-guide-fill".to_string(), label: "Overrides".to_string(), }, + AppRoute { + key: format!("{base}/admin/{org}/{workspace}/import"), + path: format!("{base}/admin/{org}/{workspace}/import"), + icon: "ri-file-upload-line".to_string(), + label: "Import".to_string(), + }, AppRoute { key: format!("{base}/admin/{org}/{workspace}/compare"), path: format!("{base}/admin/{org}/{workspace}/compare"), diff --git a/crates/frontend/src/pages.rs b/crates/frontend/src/pages.rs index 378cf3498..aa3b29a6c 100644 --- a/crates/frontend/src/pages.rs +++ b/crates/frontend/src/pages.rs @@ -14,6 +14,7 @@ pub mod experiment_groups; pub mod experiment_list; pub mod function; pub mod home; +pub mod import_config; pub mod not_found; pub mod organisations; pub mod override_page; diff --git a/crates/frontend/src/pages/import_config.rs b/crates/frontend/src/pages/import_config.rs new file mode 100644 index 000000000..569e56fb4 --- /dev/null +++ b/crates/frontend/src/pages/import_config.rs @@ -0,0 +1,1301 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use leptos::*; +use leptos_router::A; +use serde_json::{Map, Value}; +use wasm_bindgen::{JsCast, closure::Closure}; +use web_sys::{Event, FileReader, HtmlInputElement}; + +use crate::{ + api::config_import::{ + self, ImportEntityReport, ImportFormat, ImportOnError, ImportOptions, + ImportStrategy, ImportSummary, + }, + components::{ + alert::AlertType, + button::{Button, ButtonStyle}, + modal::PortalModal, + }, + providers::alert_provider::enqueue_alert, + types::{OrganisationId, Workspace}, + utils::use_url_base, +}; + +fn format_file_size(size: u64) -> String { + const KB: f64 = 1024.0; + const MB: f64 = KB * 1024.0; + let size = size as f64; + + if size >= MB { + format!("{:.1} MB", size / MB) + } else if size >= KB { + format!("{:.1} KB", size / KB) + } else { + format!("{} B", size as u64) + } +} + +fn strategy_choice_class(active: bool) -> String { + let state = if active { + "border-purple-400 bg-white text-purple-900 shadow-sm ring-1 ring-purple-100" + } else { + "border-transparent text-gray-700 hover:border-gray-200 hover:bg-white" + }; + + format!( + "min-h-[92px] rounded-md border px-4 py-3 text-left transition-colors {state}" + ) +} + +fn strategy_icon_class(active: bool) -> String { + let state = if active { + "bg-purple-50 text-purple-700" + } else { + "bg-gray-100 text-gray-600" + }; + + format!("mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-md {state}") +} + +fn parse_config_text(text: &str, format: ImportFormat) -> Result { + match format { + ImportFormat::Json => serde_json::from_str(text) + .map_err(|err| format!("Unable to parse JSON preview: {err}")), + ImportFormat::Toml => { + let value: toml::Value = toml::from_str(text) + .map_err(|err| format!("Unable to parse TOML preview: {err}"))?; + serde_json::to_value(value) + .map_err(|err| format!("Unable to normalize TOML preview: {err}")) + } + } +} + +fn object_section(config: &Value, section: &str) -> BTreeMap { + config + .get(section) + .and_then(Value::as_object) + .map(|items| { + items + .iter() + .map(|(key, value)| (key.clone(), value.clone())) + .collect() + }) + .unwrap_or_default() +} + +fn array_section(config: &Value, section: &str) -> Vec { + config + .get(section) + .and_then(Value::as_array) + .cloned() + .unwrap_or_default() +} + +fn value_preview(value: &Value) -> String { + match value { + Value::Null => "null".to_string(), + Value::Bool(value) => value.to_string(), + Value::Number(value) => value.to_string(), + Value::String(value) => value.clone(), + _ => serde_json::to_string(value).unwrap_or_else(|_| "-".to_string()), + } +} + +fn canonical_json(value: &Value) -> String { + serde_json::to_string(value).unwrap_or_default() +} + +fn description_preview(entry: &Value) -> String { + entry + .get("description") + .and_then(Value::as_str) + .filter(|description| !description.trim().is_empty()) + .map(ToString::to_string) + .unwrap_or_else(|| "-".to_string()) +} + +fn default_config_value_preview(entry: &Value) -> String { + entry + .get("value") + .map(value_preview) + .unwrap_or_else(|| value_preview(entry)) +} + +fn context_preview(context: &Value) -> String { + let Some(context) = context.as_object() else { + return "-".to_string(); + }; + + if context.is_empty() { + return "default".to_string(); + } + + context + .iter() + .map(|(key, value)| format!("{key} = {}", value_preview(value))) + .collect::>() + .join(", ") +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ReviewAction { + Create, + Update, + Delete, + Skip, +} + +impl ReviewAction { + fn label(self) -> &'static str { + match self { + Self::Create => "Create", + Self::Update => "Update", + Self::Delete => "Delete", + Self::Skip => "Skip", + } + } + + fn class(self) -> &'static str { + match self { + Self::Create => "border-emerald-100 bg-emerald-50 text-emerald-700", + Self::Update => "border-blue-100 bg-blue-50 text-blue-700", + Self::Delete => "border-rose-100 bg-rose-50 text-rose-700", + Self::Skip => "border-gray-200 bg-gray-50 text-gray-600", + } + } +} + +#[derive(Clone, Debug)] +struct ReviewRow { + name: String, + action: ReviewAction, + description: String, + scope: String, + value: String, +} + +#[derive(Clone, Debug, Default)] +struct ReviewTables { + dimensions: Vec, + default_configs: Vec, + overrides: Vec, +} + +#[derive(Clone, Debug)] +struct OverrideEntry { + name: String, + scope: String, + value: String, + raw: Value, +} + +fn review_action( + imported: Option<&Value>, + current: Option<&Value>, + strategy: ImportStrategy, +) -> Option { + match (imported, current) { + (Some(_), None) => Some(ReviewAction::Create), + (Some(_), Some(_)) if strategy == ImportStrategy::CreateOnly => { + Some(ReviewAction::Skip) + } + (Some(_), Some(_)) => Some(ReviewAction::Update), + (None, Some(_)) if strategy == ImportStrategy::Replace => { + Some(ReviewAction::Delete) + } + (None, Some(_)) => None, + (None, None) => None, + } +} + +fn build_map_review_rows( + imported_config: &Value, + current_config: &Value, + section: &str, + strategy: ImportStrategy, + row_builder: fn(&str, ReviewAction, &Value) -> ReviewRow, +) -> Vec { + let imported = object_section(imported_config, section); + let current = object_section(current_config, section); + let mut keys = BTreeSet::new(); + keys.extend(imported.keys().cloned()); + if strategy == ImportStrategy::Replace { + keys.extend(current.keys().cloned()); + } + + keys.into_iter() + .filter_map(|name| { + let imported_entry = imported.get(&name); + let current_entry = current.get(&name); + let action = review_action(imported_entry, current_entry, strategy)?; + let entry = imported_entry.or(current_entry)?; + + Some(row_builder(&name, action, entry)) + }) + .collect() +} + +fn dimension_review_row(name: &str, action: ReviewAction, entry: &Value) -> ReviewRow { + ReviewRow { + name: name.to_string(), + action, + description: description_preview(entry), + scope: String::new(), + value: String::new(), + } +} + +fn default_config_review_row( + name: &str, + action: ReviewAction, + entry: &Value, +) -> ReviewRow { + ReviewRow { + name: name.to_string(), + action, + description: String::new(), + scope: String::new(), + value: default_config_value_preview(entry), + } +} + +fn flatten_overrides(config: &Value) -> BTreeMap { + let mut entries = BTreeMap::new(); + + for override_block in array_section(config, "overrides") { + let Some(object) = override_block.as_object() else { + continue; + }; + let context = object + .get("_context_") + .cloned() + .unwrap_or_else(|| Value::Object(Map::new())); + let context_key = canonical_json(&context); + let scope = context_preview(&context); + + for (name, value) in object { + if name == "_context_" { + continue; + } + + let mut raw = Map::new(); + raw.insert("_context_".to_string(), context.clone()); + raw.insert(name.clone(), value.clone()); + entries.insert( + format!("{context_key}::{name}"), + OverrideEntry { + name: name.clone(), + scope: scope.clone(), + value: value_preview(value), + raw: Value::Object(raw), + }, + ); + } + } + + entries +} + +fn build_override_review_rows( + imported_config: &Value, + current_config: &Value, + strategy: ImportStrategy, +) -> Vec { + let imported = flatten_overrides(imported_config); + let current = flatten_overrides(current_config); + let mut keys = BTreeSet::new(); + keys.extend(imported.keys().cloned()); + if strategy == ImportStrategy::Replace { + keys.extend(current.keys().cloned()); + } + + keys.into_iter() + .filter_map(|key| { + let imported_entry = imported.get(&key); + let current_entry = current.get(&key); + let imported_raw = imported_entry.map(|entry| &entry.raw); + let current_raw = current_entry.map(|entry| &entry.raw); + let action = review_action(imported_raw, current_raw, strategy)?; + let entry = imported_entry.or(current_entry)?; + + Some(ReviewRow { + name: entry.name.clone(), + action, + description: String::new(), + scope: entry.scope.clone(), + value: entry.value.clone(), + }) + }) + .collect() +} + +fn build_review_tables( + import_text: &str, + current_text: &str, + format: ImportFormat, + strategy: ImportStrategy, +) -> Result { + let imported_config = parse_config_text(import_text, format)?; + let current_config = parse_config_text(current_text, format)?; + + Ok(ReviewTables { + dimensions: build_map_review_rows( + &imported_config, + ¤t_config, + "dimensions", + strategy, + dimension_review_row, + ), + default_configs: build_map_review_rows( + &imported_config, + ¤t_config, + "default-configs", + strategy, + default_config_review_row, + ), + overrides: build_override_review_rows( + &imported_config, + ¤t_config, + strategy, + ), + }) +} + +#[derive(Clone, Debug)] +struct SummaryError { + section: &'static str, + id: String, + message: String, +} + +fn collect_errors(summary: &ImportSummary) -> Vec { + let mut errors = Vec::new(); + for (section, report) in [ + ("Dimensions", &summary.dimensions), + ("Default Config", &summary.default_configs), + ("Overrides", &summary.contexts), + ] { + for error in &report.errors { + errors.push(SummaryError { + section, + id: error.id.clone(), + message: error.message.clone(), + }); + } + } + errors +} + +fn metric_text(value: usize, label: &str) -> String { + format!("{value} {label}") +} + +#[component] +fn SummaryStripItem( + #[prop(into)] title: String, + #[prop(into)] icon: String, + report: ImportEntityReport, +) -> impl IntoView { + view! { +
+ + + +
+
{title}
+
+ + {report.created} + " created" + + + {report.updated} + " updated" + + + {report.skipped} + " skipped" + + + {report.deleted} + " deleted" + +
+
+
+ } +} + +#[component] +fn SummaryStrip(summary: ImportSummary) -> impl IntoView { + view! { +
+ + + +
+ } +} + +#[component] +fn ImportSummaryPanel( + summary: ImportSummary, + #[prop(into)] heading: String, + #[prop(into, default = String::new())] version_href: String, +) -> impl IntoView { + let total_changes = summary.total_changes(); + let total_deleted = summary.total_deleted(); + let total_errors = summary.total_errors(); + let has_deleted = total_deleted > 0; + let has_errors = total_errors > 0; + let strategy = summary.strategy.clone(); + let config_version = summary.config_version.clone(); + let errors = StoredValue::new(collect_errors(&summary)); + + view! { +
+
+
+

{heading}

+
+ {strategy} + + {if summary.dry_run { "Preview" } else { "Applied" }} + + + {metric_text(total_changes, "total")} + + + + {metric_text(total_deleted, "deleted")} + + + + + {metric_text(total_errors, "errors")} + + +
+
+ + + "Open Config Version" + + + +
+ + +
+
+ + "Errors" +
+
+ +
+ {format!("{}: {}", error.section, error.id)} +
+
{error.message}
+
+ } + } + /> +
+ +
+
+ } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ReviewTableKind { + Dimensions, + DefaultConfig, + Overrides, +} + +#[component] +fn ActionBadge(action: ReviewAction) -> impl IntoView { + view! { + {action.label()} + } +} + +#[component] +fn ReviewTableSection( + #[prop(into)] title: String, + #[prop(into)] icon: String, + rows: Vec, + kind: ReviewTableKind, +) -> impl IntoView { + let row_count = rows.len(); + let rows = StoredValue::new(rows); + + view! { +
+
+
+ +

+ {format!("{title} ({row_count})")} +

+
+
+
+ + + + + + {match kind { + ReviewTableKind::Dimensions => { + view! { }.into_view() + } + ReviewTableKind::DefaultConfig => { + view! { }.into_view() + } + ReviewTableKind::Overrides => { + view! { + <> + + + + } + .into_view() + } + }} + + + + + + + } + } + > + { + view! { + + + + + + } + .into_view() + } + ReviewTableKind::DefaultConfig => { + view! { + + + + + + } + .into_view() + } + ReviewTableKind::Overrides => { + view! { + + + + + + + } + .into_view() + } + } + } + /> + + +
"Name""Action""Description""Value""Scope""Value"
+ "No changes in this section." +
+ {row.name} + + + {row.description}
+ {row.name} + + + + {row.value} +
+ {row.name} + + + {row.scope} + {row.value} +
+
+
+ {format!("Showing {row_count} of {row_count}")} +
+
+ } +} + +#[component] +fn ReviewTablesPanel(tables: ReviewTables) -> impl IntoView { + view! { +
+ + + +
+ } +} + +#[component] +pub fn ImportConfig() -> impl IntoView { + let workspace = use_context::>().unwrap(); + let org = use_context::>().unwrap(); + let base = StoredValue::new(use_url_base()); + + let file_name_rws = RwSignal::new(String::new()); + let file_size_rws = RwSignal::new(None::); + let file_text_rws = RwSignal::new(String::new()); + let format_rws = RwSignal::new(ImportFormat::Toml); + let strategy_rws = RwSignal::new(ImportStrategy::Upsert); + let continue_on_error_rws = RwSignal::new(false); + let tags_rws = RwSignal::new(String::new()); + let preview_rws = RwSignal::new(None::); + let applied_rws = RwSignal::new(None::); + let review_tables_rws = RwSignal::new(None::); + let preview_loading_rws = RwSignal::new(false); + let apply_loading_rws = RwSignal::new(false); + let show_confirm_rws = RwSignal::new(false); + let file_input_ref = create_node_ref::(); + + let clear_results = move || { + preview_rws.set(None); + applied_rws.set(None); + review_tables_rws.set(None); + show_confirm_rws.set(false); + }; + + let clear_file = move |_| { + file_name_rws.set(String::new()); + file_size_rws.set(None); + file_text_rws.set(String::new()); + clear_results(); + if let Some(input) = file_input_ref.get() { + input.set_value(""); + } + }; + + let open_file_picker = move |_| { + if let Some(input) = file_input_ref.get() { + input.click(); + } + }; + + let submit_import = Callback::new(move |dry_run: bool| { + let file_text = file_text_rws.get_untracked(); + if file_text.trim().is_empty() { + enqueue_alert( + "Choose a config file before importing.".to_string(), + AlertType::Error, + 3000, + ); + return; + } + + let workspace = workspace.get_untracked().0; + let org_id = org.get_untracked().0; + let format = format_rws.get_untracked(); + let strategy = strategy_rws.get_untracked(); + let on_error = if continue_on_error_rws.get_untracked() { + ImportOnError::Continue + } else { + ImportOnError::Abort + }; + let options = ImportOptions { + strategy, + on_error, + dry_run, + config_tags: tags_rws.get_untracked(), + }; + + if dry_run { + preview_loading_rws.set(true); + } else { + apply_loading_rws.set(true); + show_confirm_rws.set(false); + } + + spawn_local(async move { + let import_text = file_text.clone(); + let result = config_import::import_config( + file_text, format, options, &workspace, &org_id, + ) + .await; + + match result { + Ok(summary) if dry_run => { + match config_import::export_config(format, &workspace, &org_id).await + { + Ok(current_config) => { + match build_review_tables( + &import_text, + ¤t_config, + format, + strategy, + ) { + Ok(tables) => review_tables_rws.set(Some(tables)), + Err(error) => { + review_tables_rws.set(None); + enqueue_alert(error, AlertType::Error, 5000); + } + } + } + Err(error) => { + review_tables_rws.set(None); + enqueue_alert(error, AlertType::Error, 5000); + } + } + preview_rws.set(Some(summary)); + enqueue_alert( + "Import preview is ready.".to_string(), + AlertType::Success, + 3000, + ); + } + Ok(summary) => { + preview_rws.set(None); + review_tables_rws.set(None); + applied_rws.set(Some(summary)); + enqueue_alert( + "Config import applied.".to_string(), + AlertType::Success, + 3000, + ); + } + Err(error) => { + enqueue_alert(error, AlertType::Error, 5000); + } + } + + if dry_run { + preview_loading_rws.set(false); + } else { + apply_loading_rws.set(false); + } + }); + }); + + let on_file_change = + move |ev: Event| { + let input = event_target::(&ev); + let Some(file) = input.files().and_then(|files| files.get(0)) else { + return; + }; + + let file_name = file.name(); + file_name_rws.set(file_name.clone()); + file_size_rws.set(Some(file.size() as u64)); + format_rws.set(ImportFormat::from_file_name(&file_name)); + file_text_rws.set(String::new()); + clear_results(); + + let Ok(reader) = FileReader::new() else { + enqueue_alert( + "Unable to read the selected file.".to_string(), + AlertType::Error, + 3000, + ); + return; + }; + let reader_for_load = reader.clone(); + let onload = Closure::::new(move |_| match reader_for_load + .result() + .ok() + .and_then(|value| value.as_string()) + { + Some(text) => file_text_rws.set(text), + None => enqueue_alert( + "Unable to read the selected file as text.".to_string(), + AlertType::Error, + 3000, + ), + }); + + reader.set_onload(Some(onload.as_ref().unchecked_ref())); + let file_blob: &web_sys::Blob = file.as_ref(); + if reader.read_as_text(file_blob).is_err() { + enqueue_alert( + "Unable to start reading the selected file.".to_string(), + AlertType::Error, + 3000, + ); + } + onload.forget(); + }; + + let apply_preview = move |_| { + let requires_confirmation = preview_rws.with(|summary| { + summary + .as_ref() + .map(|summary| { + strategy_rws.get_untracked() == ImportStrategy::Replace + || summary.total_deleted() > 0 + }) + .unwrap_or(false) + }); + + if requires_confirmation { + show_confirm_rws.set(true); + } else { + submit_import.call(false); + } + }; + + view! { +
+
+
+
+

"Import Config"

+

+ "Import dimensions, default config, and overrides from a Superposition config file." +

+
+
+ +
+
+
+ + +
+
+ + + +
+
+ {move || { + if file_name_rws.with(String::is_empty) { + "No file selected".to_string() + } else { + file_name_rws.get() + } + }} +
+
+ "Choose a Superposition config file." } + } + > + + {move || { + file_size_rws + .get() + .map(format_file_size) + .unwrap_or_default() + }} + + +
+
+
+
+ + +
+
+
+ +
+ +
+ + + +
+
+ +
+ +
+ "Advanced Options" +
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ + + {move || { + preview_rws + .get() + .map(|_| { + view! { +
+
+
+
+

+ "Review Changes" +

+ + + {move || file_name_rws.get()} + + +
+
+ + {move || { + review_tables_rws + .get() + .map(|tables| { + view! { } + }) + }} + +
+
+ } + }) + }} +
+ + + {move || { + applied_rws + .get() + .map(|summary| { + let href = summary + .config_version + .as_ref() + .map(|version| { + format!( + "{}/admin/{}/{}/config/versions/{}", + base.get_value(), + org.get_untracked().0, + workspace.get_untracked().0, + version, + ) + }); + view! { +
+
+ +
+
+ } + }) + }} +
+ + + +
+
+
+ +
+
"Confirm workspace changes"
+
+ {move || { + let deleted = preview_rws + .with(|summary| { + summary + .as_ref() + .map(ImportSummary::total_deleted) + .unwrap_or_default() + }); + if deleted > 0 { + format!( + "This import will delete {deleted} item(s) missing from the file.", + ) + } else { + "This import will apply the previewed replace operation." + .to_string() + } + }} +
+
+
+
+
+
+
+
+
+
+
+ } +} diff --git a/crates/frontend/src/types.rs b/crates/frontend/src/types.rs index ba6412b33..b98f00816 100644 --- a/crates/frontend/src/types.rs +++ b/crates/frontend/src/types.rs @@ -329,6 +329,8 @@ pub enum RouteSegment { Experiments, #[strum(serialize = "function")] Function, + #[strum(serialize = "import")] + Import, #[strum(serialize = "resolve")] Resolve, #[strum(serialize = "secrets")] diff --git a/crates/frontend/src/utils.rs b/crates/frontend/src/utils.rs index 69a5f52ed..b8338f431 100644 --- a/crates/frontend/src/utils.rs +++ b/crates/frontend/src/utils.rs @@ -304,6 +304,59 @@ where request_with_skip_error(url, method, body, headers, &[]).await } +pub async fn request_raw_body( + url: String, + method: reqwest::Method, + body: String, + headers: HeaderMap, +) -> Result { + let ssr_headers = use_context::>().flatten(); + let cookie = ssr_headers.and_then(|h| h.cookie.clone()); + + let mut request_builder = + HTTP_CLIENT.request(method, url).headers(headers).body(body); + + if let Some(cookie_value) = cookie { + request_builder = request_builder.header(reqwest::header::COOKIE, cookie_value); + } + + let response = request_builder + .send() + .await + .map_err(|err| err.to_string())?; + + let status = response.status(); + + if status.is_client_error() { + let error_msg = response + .json::() + .await + .map_or(String::from("Something went wrong"), |error| error.message); + logging::error!("{}", error_msg); + enqueue_alert(error_msg.clone(), AlertType::Error, 5000); + return Err(error_msg); + } + if status.is_server_error() { + if status == 512 { + enqueue_alert( + "Webhook Call Failed, Please Check the Logs.".to_owned(), + AlertType::Error, + 5000, + ); + } else { + let error_msg = response + .json::() + .await + .map_or(String::from("Something went wrong"), |error| error.message); + logging::error!("{}", error_msg); + enqueue_alert(error_msg.clone(), AlertType::Error, 5000); + return Err(error_msg); + } + } + + Ok(response) +} + pub fn unwrap_option_or_default_with_error(option: Option, default: T) -> T { option.unwrap_or_else(|| { enqueue_alert( diff --git a/crates/service_utils/src/observability.rs b/crates/service_utils/src/observability.rs index 82598dc7c..83bba8bff 100644 --- a/crates/service_utils/src/observability.rs +++ b/crates/service_utils/src/observability.rs @@ -85,7 +85,10 @@ impl Observability { // histogram's cardinality. let drop_body_size_view = |i: &Instrument| match i.name() { "http.server.request.body.size" | "http.server.response.body.size" => { - Stream::builder().with_aggregation(Aggregation::Drop).build().ok() + Stream::builder() + .with_aggregation(Aggregation::Drop) + .build() + .ok() } _ => None, }; diff --git a/crates/service_utils/src/observability/instrumentation.rs b/crates/service_utils/src/observability/instrumentation.rs index 80b99f294..a6cda497b 100644 --- a/crates/service_utils/src/observability/instrumentation.rs +++ b/crates/service_utils/src/observability/instrumentation.rs @@ -92,7 +92,10 @@ fn normalize_method(m: &actix_web::http::Method) -> &'static str { } match_known!( m.as_str(), - ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE", "CONNECT"], + [ + "GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "TRACE", + "CONNECT" + ], "_OTHER" ) } @@ -142,7 +145,10 @@ mod tests { #[test] fn formatter_maps_unmatched_to_not_found_sentinel() { - assert_eq!(CardinalityBoundedFormatter.format("default"), ROUTE_NOT_FOUND); + assert_eq!( + CardinalityBoundedFormatter.format("default"), + ROUTE_NOT_FOUND + ); } #[test] diff --git a/crates/service_utils/src/observability/saturation/db_pool.rs b/crates/service_utils/src/observability/saturation/db_pool.rs index 34ddfb153..2008c29fb 100644 --- a/crates/service_utils/src/observability/saturation/db_pool.rs +++ b/crates/service_utils/src/observability/saturation/db_pool.rs @@ -39,10 +39,8 @@ pub fn register(meter: &Meter, pool: DbPoolHandle, pool_name: &'static str) { .u64_observable_gauge("db.client.connections.max") .with_description("Configured maximum size of the DB connection pool.") .with_callback(move |observer| { - observer.observe( - pool.max_size() as u64, - std::slice::from_ref(&max_pool_name), - ); + observer + .observe(pool.max_size() as u64, std::slice::from_ref(&max_pool_name)); }) .build(); } diff --git a/crates/service_utils/tests/observability_integration.rs b/crates/service_utils/tests/observability_integration.rs index 99e426a7e..33e839246 100644 --- a/crates/service_utils/tests/observability_integration.rs +++ b/crates/service_utils/tests/observability_integration.rs @@ -154,11 +154,18 @@ async fn runtime_tokio_metrics_appear_after_register_observers() { .lines() .find(|l| l.starts_with("runtime_tokio_workers ")) .unwrap_or_else(|| panic!("no runtime_tokio_workers in:\n{body}")); - let workers: f64 = workers_line.rsplit_once(' ').unwrap().1.trim().parse().unwrap(); + let workers: f64 = workers_line + .rsplit_once(' ') + .unwrap() + .1 + .trim() + .parse() + .unwrap(); assert!(workers >= 1.0, "expected >=1 worker, got {workers}"); assert!( - body.lines().any(|l| l.starts_with("runtime_tokio_global_queue_depth ")), + body.lines() + .any(|l| l.starts_with("runtime_tokio_global_queue_depth ")), "no runtime_tokio_global_queue_depth in:\n{body}" ); assert!( diff --git a/crates/superposition/src/app_state.rs b/crates/superposition/src/app_state.rs index d6d84d978..60a89b515 100644 --- a/crates/superposition/src/app_state.rs +++ b/crates/superposition/src/app_state.rs @@ -102,12 +102,13 @@ pub async fn get( }, snowflake_generator, app_env, - tenant_middleware_exclusion_list: - get_from_env_unsafe::("TENANT_MIDDLEWARE_EXCLUSION_LIST") - .expect("TENANT_MIDDLEWARE_EXCLUSION_LIST is not set") - .split(',') - .map(String::from) - .collect::>(), + tenant_middleware_exclusion_list: get_from_env_unsafe::( + "TENANT_MIDDLEWARE_EXCLUSION_LIST", + ) + .expect("TENANT_MIDDLEWARE_EXCLUSION_LIST is not set") + .split(',') + .map(String::from) + .collect::>(), service_prefix, superposition_token: get_superposition_token(kms_client, &app_env).await, redis: redis_pool, diff --git a/crates/superposition/src/main.rs b/crates/superposition/src/main.rs index 9da856cc6..d41698556 100644 --- a/crates/superposition/src/main.rs +++ b/crates/superposition/src/main.rs @@ -12,7 +12,7 @@ use actix_files::Files; use actix_web::{ App, HttpRequest, HttpResponse, HttpServer, Scope, middleware::{Compress, Condition}, - web::{self, Data, PathConfig, QueryConfig, get, scope}, + web::{self, Data, PathConfig, PayloadConfig, QueryConfig, get, scope}, }; use context_aware_config::api::*; use experimentation_platform::api::*; @@ -32,9 +32,9 @@ use service_utils::{ workspace_context::OrgWorkspaceMiddlewareFactory, }, observability::{ - FredPoolStats, Observability, ObservabilityConfig, RedisStats, SdkMeterProvider, - build_request_metrics_middleware, set_label_config, - SaturationDeps, register_observers, spawn_metrics_server, + FredPoolStats, Observability, ObservabilityConfig, RedisStats, SaturationDeps, + SdkMeterProvider, build_request_metrics_middleware, register_observers, + set_label_config, spawn_metrics_server, }, service::types::AppEnv, }; @@ -235,6 +235,9 @@ async fn main() -> Result<()> { .app_data(app_state.clone()) .app_data(PathConfig::default().error_handler(|err, _| bad_argument!(err).into())) .app_data(QueryConfig::default().error_handler(|err, _| bad_argument!(err).into())) + // Raise the raw-body limit (default 256KB) so config imports posted + // to /config/toml and /config/json can carry large workspaces. + .app_data(PayloadConfig::new(10 * 1024 * 1024)) .leptos_routes( leptos_options.to_owned(), routes.to_owned(), diff --git a/crates/superposition_sdk/src/client.rs b/crates/superposition_sdk/src/client.rs index 6ba35151b..a603931ff 100644 --- a/crates/superposition_sdk/src/client.rs +++ b/crates/superposition_sdk/src/client.rs @@ -231,6 +231,10 @@ mod get_webhook_by_event; mod get_workspace; +mod import_config_json; + +mod import_config_toml; + mod list_audit_logs; mod list_contexts; diff --git a/crates/superposition_sdk/src/client/customize.rs b/crates/superposition_sdk/src/client/customize.rs index fee84257e..0b5da876d 100644 --- a/crates/superposition_sdk/src/client/customize.rs +++ b/crates/superposition_sdk/src/client/customize.rs @@ -83,6 +83,8 @@ + + diff --git a/crates/superposition_sdk/src/client/import_config_json.rs b/crates/superposition_sdk/src/client/import_config_json.rs new file mode 100644 index 000000000..7618a0583 --- /dev/null +++ b/crates/superposition_sdk/src/client/import_config_json.rs @@ -0,0 +1,25 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +impl super::Client { + /// Constructs a fluent builder for the [`ImportConfigJson`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder) operation. + /// + /// - The fluent builder is configurable: + /// - [`workspace_id(impl Into)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::workspace_id) / [`set_workspace_id(Option)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::set_workspace_id):
required: **true**
(undocumented)
+ /// - [`org_id(impl Into)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::org_id) / [`set_org_id(Option)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::set_org_id):
required: **true**
(undocumented)
+ /// - [`strategy(ImportStrategy)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::strategy) / [`set_strategy(Option)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::set_strategy):
required: **false**
How the import applies file entities to the workspace. Defaults to upsert.
+ /// - [`on_error(ImportOnError)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::on_error) / [`set_on_error(Option)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::set_on_error):
required: **false**
Whether to abort (default) or continue on per-entity errors.
+ /// - [`dry_run(bool)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::dry_run) / [`set_dry_run(Option)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::set_dry_run):
required: **false**
When true, validates and summarises the import without persisting anything. Defaults to false.
+ /// - [`config_tags(impl Into)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::config_tags) / [`set_config_tags(Option)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::set_config_tags):
required: **false**
(undocumented)
+ /// - [`json_config(impl Into)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::json_config) / [`set_json_config(Option)`](crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::set_json_config):
required: **true**
(undocumented)
+ /// - On success, responds with [`ImportConfigJsonOutput`](crate::operation::import_config_json::ImportConfigJsonOutput) with field(s): + /// - [`strategy(String)`](crate::operation::import_config_json::ImportConfigJsonOutput::strategy): (undocumented) + /// - [`dry_run(bool)`](crate::operation::import_config_json::ImportConfigJsonOutput::dry_run): (undocumented) + /// - [`config_version(Option)`](crate::operation::import_config_json::ImportConfigJsonOutput::config_version): (undocumented) + /// - [`dimensions(ImportEntityReport)`](crate::operation::import_config_json::ImportConfigJsonOutput::dimensions): Per-entity outcome counts for an import. + /// - [`default_configs(ImportEntityReport)`](crate::operation::import_config_json::ImportConfigJsonOutput::default_configs): Per-entity outcome counts for an import. + /// - [`contexts(ImportEntityReport)`](crate::operation::import_config_json::ImportConfigJsonOutput::contexts): Per-entity outcome counts for an import. + /// - On failure, responds with [`SdkError`](crate::operation::import_config_json::ImportConfigJsonError) + pub fn import_config_json(&self) -> crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder { + crate::operation::import_config_json::builders::ImportConfigJsonFluentBuilder::new(self.handle.clone()) + } +} + diff --git a/crates/superposition_sdk/src/client/import_config_toml.rs b/crates/superposition_sdk/src/client/import_config_toml.rs new file mode 100644 index 000000000..f690b4dd4 --- /dev/null +++ b/crates/superposition_sdk/src/client/import_config_toml.rs @@ -0,0 +1,25 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +impl super::Client { + /// Constructs a fluent builder for the [`ImportConfigToml`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder) operation. + /// + /// - The fluent builder is configurable: + /// - [`workspace_id(impl Into)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::workspace_id) / [`set_workspace_id(Option)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::set_workspace_id):
required: **true**
(undocumented)
+ /// - [`org_id(impl Into)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::org_id) / [`set_org_id(Option)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::set_org_id):
required: **true**
(undocumented)
+ /// - [`strategy(ImportStrategy)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::strategy) / [`set_strategy(Option)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::set_strategy):
required: **false**
How the import applies file entities to the workspace. Defaults to upsert.
+ /// - [`on_error(ImportOnError)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::on_error) / [`set_on_error(Option)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::set_on_error):
required: **false**
Whether to abort (default) or continue on per-entity errors.
+ /// - [`dry_run(bool)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::dry_run) / [`set_dry_run(Option)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::set_dry_run):
required: **false**
When true, validates and summarises the import without persisting anything. Defaults to false.
+ /// - [`config_tags(impl Into)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::config_tags) / [`set_config_tags(Option)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::set_config_tags):
required: **false**
(undocumented)
+ /// - [`toml_config(impl Into)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::toml_config) / [`set_toml_config(Option)`](crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::set_toml_config):
required: **true**
(undocumented)
+ /// - On success, responds with [`ImportConfigTomlOutput`](crate::operation::import_config_toml::ImportConfigTomlOutput) with field(s): + /// - [`strategy(String)`](crate::operation::import_config_toml::ImportConfigTomlOutput::strategy): (undocumented) + /// - [`dry_run(bool)`](crate::operation::import_config_toml::ImportConfigTomlOutput::dry_run): (undocumented) + /// - [`config_version(Option)`](crate::operation::import_config_toml::ImportConfigTomlOutput::config_version): (undocumented) + /// - [`dimensions(ImportEntityReport)`](crate::operation::import_config_toml::ImportConfigTomlOutput::dimensions): Per-entity outcome counts for an import. + /// - [`default_configs(ImportEntityReport)`](crate::operation::import_config_toml::ImportConfigTomlOutput::default_configs): Per-entity outcome counts for an import. + /// - [`contexts(ImportEntityReport)`](crate::operation::import_config_toml::ImportConfigTomlOutput::contexts): Per-entity outcome counts for an import. + /// - On failure, responds with [`SdkError`](crate::operation::import_config_toml::ImportConfigTomlError) + pub fn import_config_toml(&self) -> crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder { + crate::operation::import_config_toml::builders::ImportConfigTomlFluentBuilder::new(self.handle.clone()) + } +} + diff --git a/crates/superposition_sdk/src/error_meta.rs b/crates/superposition_sdk/src/error_meta.rs index c2f3eb96b..ad8dfcf9b 100644 --- a/crates/superposition_sdk/src/error_meta.rs +++ b/crates/superposition_sdk/src/error_meta.rs @@ -1142,6 +1142,48 @@ impl From for Error { } } } +impl From<::aws_smithy_runtime_api::client::result::SdkError> for Error where R: Send + Sync + std::fmt::Debug + 'static { + fn from(err: ::aws_smithy_runtime_api::client::result::SdkError) -> Self { + match err { + ::aws_smithy_runtime_api::client::result::SdkError::ServiceError(context) => Self::from(context.into_err()), + _ => Error::Unhandled( + crate::error::sealed_unhandled::Unhandled { + meta: ::aws_smithy_types::error::metadata::ProvideErrorMetadata::meta(&err).clone(), + source: err.into(), + } + ), + } + } +} +impl From for Error { + fn from(err: crate::operation::import_config_json::ImportConfigJsonError) -> Self { + match err { + crate::operation::import_config_json::ImportConfigJsonError::InternalServerError(inner) => Error::InternalServerError(inner), + crate::operation::import_config_json::ImportConfigJsonError::Unhandled(inner) => Error::Unhandled(inner), + } + } +} +impl From<::aws_smithy_runtime_api::client::result::SdkError> for Error where R: Send + Sync + std::fmt::Debug + 'static { + fn from(err: ::aws_smithy_runtime_api::client::result::SdkError) -> Self { + match err { + ::aws_smithy_runtime_api::client::result::SdkError::ServiceError(context) => Self::from(context.into_err()), + _ => Error::Unhandled( + crate::error::sealed_unhandled::Unhandled { + meta: ::aws_smithy_types::error::metadata::ProvideErrorMetadata::meta(&err).clone(), + source: err.into(), + } + ), + } + } +} +impl From for Error { + fn from(err: crate::operation::import_config_toml::ImportConfigTomlError) -> Self { + match err { + crate::operation::import_config_toml::ImportConfigTomlError::InternalServerError(inner) => Error::InternalServerError(inner), + crate::operation::import_config_toml::ImportConfigTomlError::Unhandled(inner) => Error::Unhandled(inner), + } + } +} impl From<::aws_smithy_runtime_api::client::result::SdkError> for Error where R: Send + Sync + std::fmt::Debug + 'static { fn from(err: ::aws_smithy_runtime_api::client::result::SdkError) -> Self { match err { diff --git a/crates/superposition_sdk/src/operation.rs b/crates/superposition_sdk/src/operation.rs index 2381c0260..037f520ba 100644 --- a/crates/superposition_sdk/src/operation.rs +++ b/crates/superposition_sdk/src/operation.rs @@ -150,6 +150,12 @@ pub mod get_webhook_by_event; /// Types for the `GetWorkspace` operation. pub mod get_workspace; +/// Types for the `ImportConfigJson` operation. +pub mod import_config_json; + +/// Types for the `ImportConfigToml` operation. +pub mod import_config_toml; + /// Types for the `ListAuditLogs` operation. pub mod list_audit_logs; diff --git a/crates/superposition_sdk/src/operation/import_config_json.rs b/crates/superposition_sdk/src/operation/import_config_json.rs new file mode 100644 index 000000000..110d6040b --- /dev/null +++ b/crates/superposition_sdk/src/operation/import_config_json.rs @@ -0,0 +1,299 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +/// Orchestration and serialization glue logic for `ImportConfigJson`. +#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug)] +#[non_exhaustive] +pub struct ImportConfigJson; +impl ImportConfigJson { + /// Creates a new `ImportConfigJson` + pub fn new() -> Self { + Self + } + pub(crate) async fn orchestrate( + runtime_plugins: &::aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins, + input: crate::operation::import_config_json::ImportConfigJsonInput, + ) -> ::std::result::Result> { + let map_err = |err: ::aws_smithy_runtime_api::client::result::SdkError<::aws_smithy_runtime_api::client::interceptors::context::Error, ::aws_smithy_runtime_api::client::orchestrator::HttpResponse>| { + err.map_service_error(|err| { + err.downcast::().expect("correct error type") + }) + }; + let context = Self::orchestrate_with_stop_point(runtime_plugins, input, ::aws_smithy_runtime::client::orchestrator::StopPoint::None) + .await + .map_err(map_err)?; + let output = context.finalize().map_err(map_err)?; + ::std::result::Result::Ok(output.downcast::().expect("correct output type")) + } + + pub(crate) async fn orchestrate_with_stop_point( + runtime_plugins: &::aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins, + input: crate::operation::import_config_json::ImportConfigJsonInput, + stop_point: ::aws_smithy_runtime::client::orchestrator::StopPoint, + ) -> ::std::result::Result<::aws_smithy_runtime_api::client::interceptors::context::InterceptorContext, ::aws_smithy_runtime_api::client::result::SdkError<::aws_smithy_runtime_api::client::interceptors::context::Error, ::aws_smithy_runtime_api::client::orchestrator::HttpResponse>> { + let input = ::aws_smithy_runtime_api::client::interceptors::context::Input::erase(input); + use ::tracing::Instrument; + ::aws_smithy_runtime::client::orchestrator::invoke_with_stop_point( + "Superposition", + "ImportConfigJson", + input, + runtime_plugins, + stop_point + ) + // Create a parent span for the entire operation. Includes a random, internal-only, + // seven-digit ID for the operation orchestration so that it can be correlated in the logs. + .instrument(::tracing::debug_span!( + "Superposition.ImportConfigJson", + "rpc.service" = "Superposition", + "rpc.method" = "ImportConfigJson", + "sdk_invocation_id" = ::fastrand::u32(1_000_000..10_000_000), + + )) + .await + } + + pub(crate) fn operation_runtime_plugins( + client_runtime_plugins: ::aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins, + client_config: &crate::config::Config, + config_override: ::std::option::Option, + ) -> ::aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins { + let mut runtime_plugins = client_runtime_plugins.with_operation_plugin(Self::new()); + runtime_plugins = runtime_plugins + .with_client_plugin(crate::auth_plugin::DefaultAuthOptionsPlugin::new(vec![::aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID + , ::aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID])); + if let ::std::option::Option::Some(config_override) = config_override { + for plugin in config_override.runtime_plugins.iter().cloned() { + runtime_plugins = runtime_plugins.with_operation_plugin(plugin); + } + runtime_plugins = runtime_plugins.with_operation_plugin( + crate::config::ConfigOverrideRuntimePlugin::new(config_override, client_config.config.clone(), &client_config.runtime_components) + ); + } + runtime_plugins + } +} +impl ::aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin for ImportConfigJson { + fn config(&self) -> ::std::option::Option<::aws_smithy_types::config_bag::FrozenLayer> { + let mut cfg = ::aws_smithy_types::config_bag::Layer::new("ImportConfigJson"); + + cfg.store_put(::aws_smithy_runtime_api::client::ser_de::SharedRequestSerializer::new(ImportConfigJsonRequestSerializer)); + cfg.store_put(::aws_smithy_runtime_api::client::ser_de::SharedResponseDeserializer::new(ImportConfigJsonResponseDeserializer)); + + + cfg.store_put(::aws_smithy_runtime_api::client::auth::AuthSchemeOptionResolverParams::new(::aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptionResolverParams::new())); + + cfg.store_put(::aws_smithy_runtime_api::client::orchestrator::Metadata::new( + "ImportConfigJson", + "Superposition", + )); + + ::std::option::Option::Some(cfg.freeze()) + } + + fn runtime_components(&self, _: &::aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder) -> ::std::borrow::Cow<'_, ::aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder> { + #[allow(unused_mut)] + let mut rcb = ::aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder::new("ImportConfigJson") + .with_interceptor(::aws_smithy_runtime::client::stalled_stream_protection::StalledStreamProtectionInterceptor::default()) +.with_interceptor(ImportConfigJsonEndpointParamsInterceptor) + .with_retry_classifier(::aws_smithy_runtime::client::retries::classifiers::TransientErrorClassifier::::new()) +.with_retry_classifier(::aws_smithy_runtime::client::retries::classifiers::ModeledAsRetryableClassifier::::new()); + + ::std::borrow::Cow::Owned(rcb) + } + } + + +#[derive(Debug)] + struct ImportConfigJsonResponseDeserializer; + impl ::aws_smithy_runtime_api::client::ser_de::DeserializeResponse for ImportConfigJsonResponseDeserializer { + + + fn deserialize_nonstreaming(&self, response: &::aws_smithy_runtime_api::client::orchestrator::HttpResponse) -> ::aws_smithy_runtime_api::client::interceptors::context::OutputOrError { + let (success, status) = (response.status().is_success(), response.status().as_u16()); + let headers = response.headers(); + let body = response.body().bytes().expect("body loaded"); + #[allow(unused_mut)] + let mut force_error = false; + + let parse_result = if !success && status != 200 || force_error { + crate::protocol_serde::shape_import_config_json::de_import_config_json_http_error(status, headers, body) + } else { + crate::protocol_serde::shape_import_config_json::de_import_config_json_http_response(status, headers, body) + }; + crate::protocol_serde::type_erase_result(parse_result) + } + } +#[derive(Debug)] + struct ImportConfigJsonRequestSerializer; + impl ::aws_smithy_runtime_api::client::ser_de::SerializeRequest for ImportConfigJsonRequestSerializer { + #[allow(unused_mut, clippy::let_and_return, clippy::needless_borrow, clippy::useless_conversion)] + fn serialize_input(&self, input: ::aws_smithy_runtime_api::client::interceptors::context::Input, _cfg: &mut ::aws_smithy_types::config_bag::ConfigBag) -> ::std::result::Result<::aws_smithy_runtime_api::client::orchestrator::HttpRequest, ::aws_smithy_runtime_api::box_error::BoxError> { + let input = input.downcast::().expect("correct type"); + let _header_serialization_settings = _cfg.load::().cloned().unwrap_or_default(); + let mut request_builder = { + fn uri_base(_input: &crate::operation::import_config_json::ImportConfigJsonInput, output: &mut ::std::string::String) -> ::std::result::Result<(), ::aws_smithy_types::error::operation::BuildError> { + use ::std::fmt::Write as _; + ::std::write!(output, "/config/json/import").expect("formatting should succeed"); + ::std::result::Result::Ok(()) +} +#[allow(clippy::unnecessary_wraps)] +fn update_http_builder( + input: &crate::operation::import_config_json::ImportConfigJsonInput, + builder: ::http::request::Builder + ) -> ::std::result::Result<::http::request::Builder, ::aws_smithy_types::error::operation::BuildError> { + let mut uri = ::std::string::String::new(); + uri_base(input, &mut uri)?; + let builder = crate::protocol_serde::shape_import_config_json::ser_import_config_json_headers(input, builder)?; + ::std::result::Result::Ok(builder.method("POST").uri(uri)) +} +let mut builder = update_http_builder(&input, ::http::request::Builder::new())?; +builder = _header_serialization_settings.set_default_header(builder, ::http::header::CONTENT_TYPE, "text/plain"); +builder + }; + let body = ::aws_smithy_types::body::SdkBody::from(crate::protocol_serde::shape_import_config_json_input::ser_json_config_http_payload( input.json_config)?); + if let Some(content_length) = body.content_length() { + let content_length = content_length.to_string(); + request_builder = _header_serialization_settings.set_default_header(request_builder, ::http::header::CONTENT_LENGTH, &content_length); + } + ::std::result::Result::Ok(request_builder.body(body).expect("valid request").try_into().unwrap()) + } + } +#[derive(Debug)] + struct ImportConfigJsonEndpointParamsInterceptor; + + impl ::aws_smithy_runtime_api::client::interceptors::Intercept for ImportConfigJsonEndpointParamsInterceptor { + fn name(&self) -> &'static str { + "ImportConfigJsonEndpointParamsInterceptor" + } + + fn read_before_execution( + &self, + context: &::aws_smithy_runtime_api::client::interceptors::context::BeforeSerializationInterceptorContextRef<'_, ::aws_smithy_runtime_api::client::interceptors::context::Input, ::aws_smithy_runtime_api::client::interceptors::context::Output, ::aws_smithy_runtime_api::client::interceptors::context::Error>, + cfg: &mut ::aws_smithy_types::config_bag::ConfigBag, + ) -> ::std::result::Result<(), ::aws_smithy_runtime_api::box_error::BoxError> { + let _input = context.input() + .downcast_ref::() + .ok_or("failed to downcast to ImportConfigJsonInput")?; + + + + let params = crate::config::endpoint::Params::builder() + + .build() + .map_err(|err| ::aws_smithy_runtime_api::client::interceptors::error::ContextAttachedError::new("endpoint params could not be built", err))?; + cfg.interceptor_state().store_put(::aws_smithy_runtime_api::client::endpoint::EndpointResolverParams::new(params)); + ::std::result::Result::Ok(()) + } + } + + // The get_* functions below are generated from JMESPath expressions in the + // operationContextParams trait. They target the operation's input shape. + + + +/// Error type for the `ImportConfigJsonError` operation. +#[non_exhaustive] +#[derive(::std::fmt::Debug)] +pub enum ImportConfigJsonError { + #[allow(missing_docs)] // documentation missing in model + InternalServerError(crate::types::error::InternalServerError), + /// An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code). + #[deprecated(note = "Matching `Unhandled` directly is not forwards compatible. Instead, match using a \ + variable wildcard pattern and check `.code()`: + \ +    `err if err.code() == Some(\"SpecificExceptionCode\") => { /* handle the error */ }` + \ + See [`ProvideErrorMetadata`](#impl-ProvideErrorMetadata-for-ImportConfigJsonError) for what information is available for the error.")] + Unhandled(crate::error::sealed_unhandled::Unhandled), +} +impl ImportConfigJsonError { + /// Creates the `ImportConfigJsonError::Unhandled` variant from any error type. + pub fn unhandled(err: impl ::std::convert::Into<::std::boxed::Box>) -> Self { + Self::Unhandled(crate::error::sealed_unhandled::Unhandled { source: err.into(), meta: ::std::default::Default::default() }) + } + + /// Creates the `ImportConfigJsonError::Unhandled` variant from an [`ErrorMetadata`](::aws_smithy_types::error::ErrorMetadata). + pub fn generic(err: ::aws_smithy_types::error::ErrorMetadata) -> Self { + Self::Unhandled(crate::error::sealed_unhandled::Unhandled { source: err.clone().into(), meta: err }) + } + /// + /// Returns error metadata, which includes the error code, message, + /// request ID, and potentially additional information. + /// + pub fn meta(&self) -> &::aws_smithy_types::error::ErrorMetadata { + match self { + Self::InternalServerError(e) => ::aws_smithy_types::error::metadata::ProvideErrorMetadata::meta(e), + Self::Unhandled(e) => &e.meta, + } + } + /// Returns `true` if the error kind is `ImportConfigJsonError::InternalServerError`. + pub fn is_internal_server_error(&self) -> bool { + matches!(self, Self::InternalServerError(_)) + } +} +impl ::std::error::Error for ImportConfigJsonError { + fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> { + match self { + Self::InternalServerError(_inner) => + ::std::option::Option::Some(_inner) + , + Self::Unhandled(_inner) => { + ::std::option::Option::Some(&*_inner.source) + } + } + } +} +impl ::std::fmt::Display for ImportConfigJsonError { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match self { + Self::InternalServerError(_inner) => + _inner.fmt(f) + , + Self::Unhandled(_inner) => { + if let ::std::option::Option::Some(code) = ::aws_smithy_types::error::metadata::ProvideErrorMetadata::code(self) { + write!(f, "unhandled error ({code})") + } else { + f.write_str("unhandled error") + } + } + } + } +} +impl ::aws_smithy_types::retry::ProvideErrorKind for ImportConfigJsonError { + fn code(&self) -> ::std::option::Option<&str> { + ::aws_smithy_types::error::metadata::ProvideErrorMetadata::code(self) + } + fn retryable_error_kind(&self) -> ::std::option::Option<::aws_smithy_types::retry::ErrorKind> { + ::std::option::Option::None + } +} +impl ::aws_smithy_types::error::metadata::ProvideErrorMetadata for ImportConfigJsonError { + fn meta(&self) -> &::aws_smithy_types::error::ErrorMetadata { + match self { + Self::InternalServerError(_inner) => + ::aws_smithy_types::error::metadata::ProvideErrorMetadata::meta(_inner) + , + Self::Unhandled(_inner) => { + &_inner.meta + } + } + } +} +impl ::aws_smithy_runtime_api::client::result::CreateUnhandledError for ImportConfigJsonError { + fn create_unhandled_error( + source: ::std::boxed::Box, + meta: ::std::option::Option<::aws_smithy_types::error::ErrorMetadata> + ) -> Self { + Self::Unhandled(crate::error::sealed_unhandled::Unhandled { source, meta: meta.unwrap_or_default() }) + } +} + +pub use crate::operation::import_config_json::_import_config_json_output::ImportConfigJsonOutput; + +pub use crate::operation::import_config_json::_import_config_json_input::ImportConfigJsonInput; + +mod _import_config_json_input; + +mod _import_config_json_output; + +/// Builders +pub mod builders; + diff --git a/crates/superposition_sdk/src/operation/import_config_json/_import_config_json_input.rs b/crates/superposition_sdk/src/operation/import_config_json/_import_config_json_input.rs new file mode 100644 index 000000000..3efd5e3b2 --- /dev/null +++ b/crates/superposition_sdk/src/operation/import_config_json/_import_config_json_input.rs @@ -0,0 +1,187 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +#[allow(missing_docs)] // documentation missing in model +#[non_exhaustive] +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::fmt::Debug)] +pub struct ImportConfigJsonInput { + #[allow(missing_docs)] // documentation missing in model + pub workspace_id: ::std::option::Option<::std::string::String>, + #[allow(missing_docs)] // documentation missing in model + pub org_id: ::std::option::Option<::std::string::String>, + /// How the import applies file entities to the workspace. Defaults to upsert. + pub strategy: ::std::option::Option, + /// Whether to abort (default) or continue on per-entity errors. + pub on_error: ::std::option::Option, + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub dry_run: ::std::option::Option, + #[allow(missing_docs)] // documentation missing in model + pub config_tags: ::std::option::Option<::std::string::String>, + #[allow(missing_docs)] // documentation missing in model + pub json_config: ::std::option::Option<::std::string::String>, +} +impl ImportConfigJsonInput { + #[allow(missing_docs)] // documentation missing in model + pub fn workspace_id(&self) -> ::std::option::Option<&str> { + self.workspace_id.as_deref() + } + #[allow(missing_docs)] // documentation missing in model + pub fn org_id(&self) -> ::std::option::Option<&str> { + self.org_id.as_deref() + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn strategy(&self) -> ::std::option::Option<&crate::types::ImportStrategy> { + self.strategy.as_ref() + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn on_error(&self) -> ::std::option::Option<&crate::types::ImportOnError> { + self.on_error.as_ref() + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn dry_run(&self) -> ::std::option::Option { + self.dry_run + } + #[allow(missing_docs)] // documentation missing in model + pub fn config_tags(&self) -> ::std::option::Option<&str> { + self.config_tags.as_deref() + } + #[allow(missing_docs)] // documentation missing in model + pub fn json_config(&self) -> ::std::option::Option<&str> { + self.json_config.as_deref() + } +} +impl ImportConfigJsonInput { + /// Creates a new builder-style object to manufacture [`ImportConfigJsonInput`](crate::operation::import_config_json::ImportConfigJsonInput). + pub fn builder() -> crate::operation::import_config_json::builders::ImportConfigJsonInputBuilder { + crate::operation::import_config_json::builders::ImportConfigJsonInputBuilder::default() + } +} + +/// A builder for [`ImportConfigJsonInput`](crate::operation::import_config_json::ImportConfigJsonInput). +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::default::Default, ::std::fmt::Debug)] +#[non_exhaustive] +pub struct ImportConfigJsonInputBuilder { + pub(crate) workspace_id: ::std::option::Option<::std::string::String>, + pub(crate) org_id: ::std::option::Option<::std::string::String>, + pub(crate) strategy: ::std::option::Option, + pub(crate) on_error: ::std::option::Option, + pub(crate) dry_run: ::std::option::Option, + pub(crate) config_tags: ::std::option::Option<::std::string::String>, + pub(crate) json_config: ::std::option::Option<::std::string::String>, +} +impl ImportConfigJsonInputBuilder { + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn workspace_id(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.workspace_id = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_workspace_id(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.workspace_id = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_workspace_id(&self) -> &::std::option::Option<::std::string::String> { + &self.workspace_id + } + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn org_id(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.org_id = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_org_id(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.org_id = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_org_id(&self) -> &::std::option::Option<::std::string::String> { + &self.org_id + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn strategy(mut self, input: crate::types::ImportStrategy) -> Self { + self.strategy = ::std::option::Option::Some(input); + self + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn set_strategy(mut self, input: ::std::option::Option) -> Self { + self.strategy = input; self + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn get_strategy(&self) -> &::std::option::Option { + &self.strategy + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn on_error(mut self, input: crate::types::ImportOnError) -> Self { + self.on_error = ::std::option::Option::Some(input); + self + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn set_on_error(mut self, input: ::std::option::Option) -> Self { + self.on_error = input; self + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn get_on_error(&self) -> &::std::option::Option { + &self.on_error + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn dry_run(mut self, input: bool) -> Self { + self.dry_run = ::std::option::Option::Some(input); + self + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn set_dry_run(mut self, input: ::std::option::Option) -> Self { + self.dry_run = input; self + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn get_dry_run(&self) -> &::std::option::Option { + &self.dry_run + } + #[allow(missing_docs)] // documentation missing in model + pub fn config_tags(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.config_tags = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_config_tags(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.config_tags = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_config_tags(&self) -> &::std::option::Option<::std::string::String> { + &self.config_tags + } + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn json_config(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.json_config = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_json_config(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.json_config = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_json_config(&self) -> &::std::option::Option<::std::string::String> { + &self.json_config + } + /// Consumes the builder and constructs a [`ImportConfigJsonInput`](crate::operation::import_config_json::ImportConfigJsonInput). + pub fn build(self) -> ::std::result::Result { + ::std::result::Result::Ok( + crate::operation::import_config_json::ImportConfigJsonInput { + workspace_id: self.workspace_id + , + org_id: self.org_id + , + strategy: self.strategy + , + on_error: self.on_error + , + dry_run: self.dry_run + , + config_tags: self.config_tags + , + json_config: self.json_config + , + } + ) + } +} + diff --git a/crates/superposition_sdk/src/operation/import_config_json/_import_config_json_output.rs b/crates/superposition_sdk/src/operation/import_config_json/_import_config_json_output.rs new file mode 100644 index 000000000..eb197239e --- /dev/null +++ b/crates/superposition_sdk/src/operation/import_config_json/_import_config_json_output.rs @@ -0,0 +1,189 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. + +/// Summary of what an import created, updated, skipped or deleted. +#[non_exhaustive] +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::fmt::Debug)] +pub struct ImportConfigJsonOutput { + #[allow(missing_docs)] // documentation missing in model + pub strategy: ::std::string::String, + #[allow(missing_docs)] // documentation missing in model + pub dry_run: bool, + #[allow(missing_docs)] // documentation missing in model + pub config_version: ::std::option::Option<::std::string::String>, + /// Per-entity outcome counts for an import. + pub dimensions: crate::types::ImportEntityReport, + /// Per-entity outcome counts for an import. + pub default_configs: crate::types::ImportEntityReport, + /// Per-entity outcome counts for an import. + pub contexts: crate::types::ImportEntityReport, +} +impl ImportConfigJsonOutput { + #[allow(missing_docs)] // documentation missing in model + pub fn strategy(&self) -> &str { + use std::ops::Deref; self.strategy.deref() + } + #[allow(missing_docs)] // documentation missing in model + pub fn dry_run(&self) -> bool { + self.dry_run + } + #[allow(missing_docs)] // documentation missing in model + pub fn config_version(&self) -> ::std::option::Option<&str> { + self.config_version.as_deref() + } + /// Per-entity outcome counts for an import. + pub fn dimensions(&self) -> &crate::types::ImportEntityReport { + &self.dimensions + } + /// Per-entity outcome counts for an import. + pub fn default_configs(&self) -> &crate::types::ImportEntityReport { + &self.default_configs + } + /// Per-entity outcome counts for an import. + pub fn contexts(&self) -> &crate::types::ImportEntityReport { + &self.contexts + } +} +impl ImportConfigJsonOutput { + /// Creates a new builder-style object to manufacture [`ImportConfigJsonOutput`](crate::operation::import_config_json::ImportConfigJsonOutput). + pub fn builder() -> crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder { + crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder::default() + } +} + +/// A builder for [`ImportConfigJsonOutput`](crate::operation::import_config_json::ImportConfigJsonOutput). +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::default::Default, ::std::fmt::Debug)] +#[non_exhaustive] +pub struct ImportConfigJsonOutputBuilder { + pub(crate) strategy: ::std::option::Option<::std::string::String>, + pub(crate) dry_run: ::std::option::Option, + pub(crate) config_version: ::std::option::Option<::std::string::String>, + pub(crate) dimensions: ::std::option::Option, + pub(crate) default_configs: ::std::option::Option, + pub(crate) contexts: ::std::option::Option, +} +impl ImportConfigJsonOutputBuilder { + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn strategy(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.strategy = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_strategy(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.strategy = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_strategy(&self) -> &::std::option::Option<::std::string::String> { + &self.strategy + } + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn dry_run(mut self, input: bool) -> Self { + self.dry_run = ::std::option::Option::Some(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_dry_run(mut self, input: ::std::option::Option) -> Self { + self.dry_run = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_dry_run(&self) -> &::std::option::Option { + &self.dry_run + } + #[allow(missing_docs)] // documentation missing in model + pub fn config_version(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.config_version = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_config_version(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.config_version = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_config_version(&self) -> &::std::option::Option<::std::string::String> { + &self.config_version + } + /// Per-entity outcome counts for an import. + /// This field is required. + pub fn dimensions(mut self, input: crate::types::ImportEntityReport) -> Self { + self.dimensions = ::std::option::Option::Some(input); + self + } + /// Per-entity outcome counts for an import. + pub fn set_dimensions(mut self, input: ::std::option::Option) -> Self { + self.dimensions = input; self + } + /// Per-entity outcome counts for an import. + pub fn get_dimensions(&self) -> &::std::option::Option { + &self.dimensions + } + /// Per-entity outcome counts for an import. + /// This field is required. + pub fn default_configs(mut self, input: crate::types::ImportEntityReport) -> Self { + self.default_configs = ::std::option::Option::Some(input); + self + } + /// Per-entity outcome counts for an import. + pub fn set_default_configs(mut self, input: ::std::option::Option) -> Self { + self.default_configs = input; self + } + /// Per-entity outcome counts for an import. + pub fn get_default_configs(&self) -> &::std::option::Option { + &self.default_configs + } + /// Per-entity outcome counts for an import. + /// This field is required. + pub fn contexts(mut self, input: crate::types::ImportEntityReport) -> Self { + self.contexts = ::std::option::Option::Some(input); + self + } + /// Per-entity outcome counts for an import. + pub fn set_contexts(mut self, input: ::std::option::Option) -> Self { + self.contexts = input; self + } + /// Per-entity outcome counts for an import. + pub fn get_contexts(&self) -> &::std::option::Option { + &self.contexts + } + /// Consumes the builder and constructs a [`ImportConfigJsonOutput`](crate::operation::import_config_json::ImportConfigJsonOutput). + /// This method will fail if any of the following fields are not set: + /// - [`strategy`](crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder::strategy) + /// - [`dry_run`](crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder::dry_run) + /// - [`dimensions`](crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder::dimensions) + /// - [`default_configs`](crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder::default_configs) + /// - [`contexts`](crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder::contexts) + pub fn build(self) -> ::std::result::Result { + ::std::result::Result::Ok( + crate::operation::import_config_json::ImportConfigJsonOutput { + strategy: self.strategy + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("strategy", "strategy was not specified but it is required when building ImportConfigJsonOutput") + )? + , + dry_run: self.dry_run + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("dry_run", "dry_run was not specified but it is required when building ImportConfigJsonOutput") + )? + , + config_version: self.config_version + , + dimensions: self.dimensions + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("dimensions", "dimensions was not specified but it is required when building ImportConfigJsonOutput") + )? + , + default_configs: self.default_configs + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("default_configs", "default_configs was not specified but it is required when building ImportConfigJsonOutput") + )? + , + contexts: self.contexts + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("contexts", "contexts was not specified but it is required when building ImportConfigJsonOutput") + )? + , + } + ) + } +} + diff --git a/crates/superposition_sdk/src/operation/import_config_json/builders.rs b/crates/superposition_sdk/src/operation/import_config_json/builders.rs new file mode 100644 index 000000000..feb7925cd --- /dev/null +++ b/crates/superposition_sdk/src/operation/import_config_json/builders.rs @@ -0,0 +1,198 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +pub use crate::operation::import_config_json::_import_config_json_output::ImportConfigJsonOutputBuilder; + +pub use crate::operation::import_config_json::_import_config_json_input::ImportConfigJsonInputBuilder; + +impl crate::operation::import_config_json::builders::ImportConfigJsonInputBuilder { + /// Sends a request with this input using the given client. + pub async fn send_with(self, client: &crate::Client) -> ::std::result::Result< + crate::operation::import_config_json::ImportConfigJsonOutput, + ::aws_smithy_runtime_api::client::result::SdkError< + crate::operation::import_config_json::ImportConfigJsonError, + ::aws_smithy_runtime_api::client::orchestrator::HttpResponse + > + > { + let mut fluent_builder = client.import_config_json(); + fluent_builder.inner = self; + fluent_builder.send().await + } + } +/// Fluent builder constructing a request to `ImportConfigJson`. +/// +/// Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a single transaction after validating the document. +#[derive(::std::clone::Clone, ::std::fmt::Debug)] +pub struct ImportConfigJsonFluentBuilder { + handle: ::std::sync::Arc, + inner: crate::operation::import_config_json::builders::ImportConfigJsonInputBuilder, +config_override: ::std::option::Option, + } +impl + crate::client::customize::internal::CustomizableSend< + crate::operation::import_config_json::ImportConfigJsonOutput, + crate::operation::import_config_json::ImportConfigJsonError, + > for ImportConfigJsonFluentBuilder + { + fn send( + self, + config_override: crate::config::Builder, + ) -> crate::client::customize::internal::BoxFuture< + crate::client::customize::internal::SendResult< + crate::operation::import_config_json::ImportConfigJsonOutput, + crate::operation::import_config_json::ImportConfigJsonError, + >, + > { + ::std::boxed::Box::pin(async move { self.config_override(config_override).send().await }) + } + } +impl ImportConfigJsonFluentBuilder { + /// Creates a new `ImportConfigJsonFluentBuilder`. + pub(crate) fn new(handle: ::std::sync::Arc) -> Self { + Self { + handle, + inner: ::std::default::Default::default(), + config_override: ::std::option::Option::None, + } + } + /// Access the ImportConfigJson as a reference. + pub fn as_input(&self) -> &crate::operation::import_config_json::builders::ImportConfigJsonInputBuilder { + &self.inner + } + /// Sends the request and returns the response. + /// + /// If an error occurs, an `SdkError` will be returned with additional details that + /// can be matched against. + /// + /// By default, any retryable failures will be retried twice. Retry behavior + /// is configurable with the [RetryConfig](aws_smithy_types::retry::RetryConfig), which can be + /// set when configuring the client. + pub async fn send(self) -> ::std::result::Result> { + let input = self.inner.build().map_err(::aws_smithy_runtime_api::client::result::SdkError::construction_failure)?; + let runtime_plugins = crate::operation::import_config_json::ImportConfigJson::operation_runtime_plugins( + self.handle.runtime_plugins.clone(), + &self.handle.conf, + self.config_override, + ); + crate::operation::import_config_json::ImportConfigJson::orchestrate(&runtime_plugins, input).await + } + + /// Consumes this builder, creating a customizable operation that can be modified before being sent. + pub fn customize( + self, + ) -> crate::client::customize::CustomizableOperation { + crate::client::customize::CustomizableOperation::new(self) + } + pub(crate) fn config_override( + mut self, + config_override: impl ::std::convert::Into, + ) -> Self { + self.set_config_override(::std::option::Option::Some(config_override.into())); + self + } + + pub(crate) fn set_config_override( + &mut self, + config_override: ::std::option::Option, + ) -> &mut Self { + self.config_override = config_override; + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn workspace_id(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.inner = self.inner.workspace_id(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_workspace_id(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.inner = self.inner.set_workspace_id(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_workspace_id(&self) -> &::std::option::Option<::std::string::String> { + self.inner.get_workspace_id() + } + #[allow(missing_docs)] // documentation missing in model + pub fn org_id(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.inner = self.inner.org_id(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_org_id(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.inner = self.inner.set_org_id(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_org_id(&self) -> &::std::option::Option<::std::string::String> { + self.inner.get_org_id() + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn strategy(mut self, input: crate::types::ImportStrategy) -> Self { + self.inner = self.inner.strategy(input); + self + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn set_strategy(mut self, input: ::std::option::Option) -> Self { + self.inner = self.inner.set_strategy(input); + self + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn get_strategy(&self) -> &::std::option::Option { + self.inner.get_strategy() + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn on_error(mut self, input: crate::types::ImportOnError) -> Self { + self.inner = self.inner.on_error(input); + self + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn set_on_error(mut self, input: ::std::option::Option) -> Self { + self.inner = self.inner.set_on_error(input); + self + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn get_on_error(&self) -> &::std::option::Option { + self.inner.get_on_error() + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn dry_run(mut self, input: bool) -> Self { + self.inner = self.inner.dry_run(input); + self + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn set_dry_run(mut self, input: ::std::option::Option) -> Self { + self.inner = self.inner.set_dry_run(input); + self + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn get_dry_run(&self) -> &::std::option::Option { + self.inner.get_dry_run() + } + #[allow(missing_docs)] // documentation missing in model + pub fn config_tags(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.inner = self.inner.config_tags(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_config_tags(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.inner = self.inner.set_config_tags(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_config_tags(&self) -> &::std::option::Option<::std::string::String> { + self.inner.get_config_tags() + } + #[allow(missing_docs)] // documentation missing in model + pub fn json_config(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.inner = self.inner.json_config(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_json_config(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.inner = self.inner.set_json_config(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_json_config(&self) -> &::std::option::Option<::std::string::String> { + self.inner.get_json_config() + } +} + diff --git a/crates/superposition_sdk/src/operation/import_config_toml.rs b/crates/superposition_sdk/src/operation/import_config_toml.rs new file mode 100644 index 000000000..f3caefea4 --- /dev/null +++ b/crates/superposition_sdk/src/operation/import_config_toml.rs @@ -0,0 +1,299 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +/// Orchestration and serialization glue logic for `ImportConfigToml`. +#[derive(::std::clone::Clone, ::std::default::Default, ::std::fmt::Debug)] +#[non_exhaustive] +pub struct ImportConfigToml; +impl ImportConfigToml { + /// Creates a new `ImportConfigToml` + pub fn new() -> Self { + Self + } + pub(crate) async fn orchestrate( + runtime_plugins: &::aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins, + input: crate::operation::import_config_toml::ImportConfigTomlInput, + ) -> ::std::result::Result> { + let map_err = |err: ::aws_smithy_runtime_api::client::result::SdkError<::aws_smithy_runtime_api::client::interceptors::context::Error, ::aws_smithy_runtime_api::client::orchestrator::HttpResponse>| { + err.map_service_error(|err| { + err.downcast::().expect("correct error type") + }) + }; + let context = Self::orchestrate_with_stop_point(runtime_plugins, input, ::aws_smithy_runtime::client::orchestrator::StopPoint::None) + .await + .map_err(map_err)?; + let output = context.finalize().map_err(map_err)?; + ::std::result::Result::Ok(output.downcast::().expect("correct output type")) + } + + pub(crate) async fn orchestrate_with_stop_point( + runtime_plugins: &::aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins, + input: crate::operation::import_config_toml::ImportConfigTomlInput, + stop_point: ::aws_smithy_runtime::client::orchestrator::StopPoint, + ) -> ::std::result::Result<::aws_smithy_runtime_api::client::interceptors::context::InterceptorContext, ::aws_smithy_runtime_api::client::result::SdkError<::aws_smithy_runtime_api::client::interceptors::context::Error, ::aws_smithy_runtime_api::client::orchestrator::HttpResponse>> { + let input = ::aws_smithy_runtime_api::client::interceptors::context::Input::erase(input); + use ::tracing::Instrument; + ::aws_smithy_runtime::client::orchestrator::invoke_with_stop_point( + "Superposition", + "ImportConfigToml", + input, + runtime_plugins, + stop_point + ) + // Create a parent span for the entire operation. Includes a random, internal-only, + // seven-digit ID for the operation orchestration so that it can be correlated in the logs. + .instrument(::tracing::debug_span!( + "Superposition.ImportConfigToml", + "rpc.service" = "Superposition", + "rpc.method" = "ImportConfigToml", + "sdk_invocation_id" = ::fastrand::u32(1_000_000..10_000_000), + + )) + .await + } + + pub(crate) fn operation_runtime_plugins( + client_runtime_plugins: ::aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins, + client_config: &crate::config::Config, + config_override: ::std::option::Option, + ) -> ::aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugins { + let mut runtime_plugins = client_runtime_plugins.with_operation_plugin(Self::new()); + runtime_plugins = runtime_plugins + .with_client_plugin(crate::auth_plugin::DefaultAuthOptionsPlugin::new(vec![::aws_smithy_runtime_api::client::auth::http::HTTP_BASIC_AUTH_SCHEME_ID + , ::aws_smithy_runtime_api::client::auth::http::HTTP_BEARER_AUTH_SCHEME_ID])); + if let ::std::option::Option::Some(config_override) = config_override { + for plugin in config_override.runtime_plugins.iter().cloned() { + runtime_plugins = runtime_plugins.with_operation_plugin(plugin); + } + runtime_plugins = runtime_plugins.with_operation_plugin( + crate::config::ConfigOverrideRuntimePlugin::new(config_override, client_config.config.clone(), &client_config.runtime_components) + ); + } + runtime_plugins + } +} +impl ::aws_smithy_runtime_api::client::runtime_plugin::RuntimePlugin for ImportConfigToml { + fn config(&self) -> ::std::option::Option<::aws_smithy_types::config_bag::FrozenLayer> { + let mut cfg = ::aws_smithy_types::config_bag::Layer::new("ImportConfigToml"); + + cfg.store_put(::aws_smithy_runtime_api::client::ser_de::SharedRequestSerializer::new(ImportConfigTomlRequestSerializer)); + cfg.store_put(::aws_smithy_runtime_api::client::ser_de::SharedResponseDeserializer::new(ImportConfigTomlResponseDeserializer)); + + + cfg.store_put(::aws_smithy_runtime_api::client::auth::AuthSchemeOptionResolverParams::new(::aws_smithy_runtime_api::client::auth::static_resolver::StaticAuthSchemeOptionResolverParams::new())); + + cfg.store_put(::aws_smithy_runtime_api::client::orchestrator::Metadata::new( + "ImportConfigToml", + "Superposition", + )); + + ::std::option::Option::Some(cfg.freeze()) + } + + fn runtime_components(&self, _: &::aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder) -> ::std::borrow::Cow<'_, ::aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder> { + #[allow(unused_mut)] + let mut rcb = ::aws_smithy_runtime_api::client::runtime_components::RuntimeComponentsBuilder::new("ImportConfigToml") + .with_interceptor(::aws_smithy_runtime::client::stalled_stream_protection::StalledStreamProtectionInterceptor::default()) +.with_interceptor(ImportConfigTomlEndpointParamsInterceptor) + .with_retry_classifier(::aws_smithy_runtime::client::retries::classifiers::TransientErrorClassifier::::new()) +.with_retry_classifier(::aws_smithy_runtime::client::retries::classifiers::ModeledAsRetryableClassifier::::new()); + + ::std::borrow::Cow::Owned(rcb) + } + } + + +#[derive(Debug)] + struct ImportConfigTomlResponseDeserializer; + impl ::aws_smithy_runtime_api::client::ser_de::DeserializeResponse for ImportConfigTomlResponseDeserializer { + + + fn deserialize_nonstreaming(&self, response: &::aws_smithy_runtime_api::client::orchestrator::HttpResponse) -> ::aws_smithy_runtime_api::client::interceptors::context::OutputOrError { + let (success, status) = (response.status().is_success(), response.status().as_u16()); + let headers = response.headers(); + let body = response.body().bytes().expect("body loaded"); + #[allow(unused_mut)] + let mut force_error = false; + + let parse_result = if !success && status != 200 || force_error { + crate::protocol_serde::shape_import_config_toml::de_import_config_toml_http_error(status, headers, body) + } else { + crate::protocol_serde::shape_import_config_toml::de_import_config_toml_http_response(status, headers, body) + }; + crate::protocol_serde::type_erase_result(parse_result) + } + } +#[derive(Debug)] + struct ImportConfigTomlRequestSerializer; + impl ::aws_smithy_runtime_api::client::ser_de::SerializeRequest for ImportConfigTomlRequestSerializer { + #[allow(unused_mut, clippy::let_and_return, clippy::needless_borrow, clippy::useless_conversion)] + fn serialize_input(&self, input: ::aws_smithy_runtime_api::client::interceptors::context::Input, _cfg: &mut ::aws_smithy_types::config_bag::ConfigBag) -> ::std::result::Result<::aws_smithy_runtime_api::client::orchestrator::HttpRequest, ::aws_smithy_runtime_api::box_error::BoxError> { + let input = input.downcast::().expect("correct type"); + let _header_serialization_settings = _cfg.load::().cloned().unwrap_or_default(); + let mut request_builder = { + fn uri_base(_input: &crate::operation::import_config_toml::ImportConfigTomlInput, output: &mut ::std::string::String) -> ::std::result::Result<(), ::aws_smithy_types::error::operation::BuildError> { + use ::std::fmt::Write as _; + ::std::write!(output, "/config/toml/import").expect("formatting should succeed"); + ::std::result::Result::Ok(()) +} +#[allow(clippy::unnecessary_wraps)] +fn update_http_builder( + input: &crate::operation::import_config_toml::ImportConfigTomlInput, + builder: ::http::request::Builder + ) -> ::std::result::Result<::http::request::Builder, ::aws_smithy_types::error::operation::BuildError> { + let mut uri = ::std::string::String::new(); + uri_base(input, &mut uri)?; + let builder = crate::protocol_serde::shape_import_config_toml::ser_import_config_toml_headers(input, builder)?; + ::std::result::Result::Ok(builder.method("POST").uri(uri)) +} +let mut builder = update_http_builder(&input, ::http::request::Builder::new())?; +builder = _header_serialization_settings.set_default_header(builder, ::http::header::CONTENT_TYPE, "text/plain"); +builder + }; + let body = ::aws_smithy_types::body::SdkBody::from(crate::protocol_serde::shape_import_config_toml_input::ser_toml_config_http_payload( input.toml_config)?); + if let Some(content_length) = body.content_length() { + let content_length = content_length.to_string(); + request_builder = _header_serialization_settings.set_default_header(request_builder, ::http::header::CONTENT_LENGTH, &content_length); + } + ::std::result::Result::Ok(request_builder.body(body).expect("valid request").try_into().unwrap()) + } + } +#[derive(Debug)] + struct ImportConfigTomlEndpointParamsInterceptor; + + impl ::aws_smithy_runtime_api::client::interceptors::Intercept for ImportConfigTomlEndpointParamsInterceptor { + fn name(&self) -> &'static str { + "ImportConfigTomlEndpointParamsInterceptor" + } + + fn read_before_execution( + &self, + context: &::aws_smithy_runtime_api::client::interceptors::context::BeforeSerializationInterceptorContextRef<'_, ::aws_smithy_runtime_api::client::interceptors::context::Input, ::aws_smithy_runtime_api::client::interceptors::context::Output, ::aws_smithy_runtime_api::client::interceptors::context::Error>, + cfg: &mut ::aws_smithy_types::config_bag::ConfigBag, + ) -> ::std::result::Result<(), ::aws_smithy_runtime_api::box_error::BoxError> { + let _input = context.input() + .downcast_ref::() + .ok_or("failed to downcast to ImportConfigTomlInput")?; + + + + let params = crate::config::endpoint::Params::builder() + + .build() + .map_err(|err| ::aws_smithy_runtime_api::client::interceptors::error::ContextAttachedError::new("endpoint params could not be built", err))?; + cfg.interceptor_state().store_put(::aws_smithy_runtime_api::client::endpoint::EndpointResolverParams::new(params)); + ::std::result::Result::Ok(()) + } + } + + // The get_* functions below are generated from JMESPath expressions in the + // operationContextParams trait. They target the operation's input shape. + + + +/// Error type for the `ImportConfigTomlError` operation. +#[non_exhaustive] +#[derive(::std::fmt::Debug)] +pub enum ImportConfigTomlError { + #[allow(missing_docs)] // documentation missing in model + InternalServerError(crate::types::error::InternalServerError), + /// An unexpected error occurred (e.g., invalid JSON returned by the service or an unknown error code). + #[deprecated(note = "Matching `Unhandled` directly is not forwards compatible. Instead, match using a \ + variable wildcard pattern and check `.code()`: + \ +    `err if err.code() == Some(\"SpecificExceptionCode\") => { /* handle the error */ }` + \ + See [`ProvideErrorMetadata`](#impl-ProvideErrorMetadata-for-ImportConfigTomlError) for what information is available for the error.")] + Unhandled(crate::error::sealed_unhandled::Unhandled), +} +impl ImportConfigTomlError { + /// Creates the `ImportConfigTomlError::Unhandled` variant from any error type. + pub fn unhandled(err: impl ::std::convert::Into<::std::boxed::Box>) -> Self { + Self::Unhandled(crate::error::sealed_unhandled::Unhandled { source: err.into(), meta: ::std::default::Default::default() }) + } + + /// Creates the `ImportConfigTomlError::Unhandled` variant from an [`ErrorMetadata`](::aws_smithy_types::error::ErrorMetadata). + pub fn generic(err: ::aws_smithy_types::error::ErrorMetadata) -> Self { + Self::Unhandled(crate::error::sealed_unhandled::Unhandled { source: err.clone().into(), meta: err }) + } + /// + /// Returns error metadata, which includes the error code, message, + /// request ID, and potentially additional information. + /// + pub fn meta(&self) -> &::aws_smithy_types::error::ErrorMetadata { + match self { + Self::InternalServerError(e) => ::aws_smithy_types::error::metadata::ProvideErrorMetadata::meta(e), + Self::Unhandled(e) => &e.meta, + } + } + /// Returns `true` if the error kind is `ImportConfigTomlError::InternalServerError`. + pub fn is_internal_server_error(&self) -> bool { + matches!(self, Self::InternalServerError(_)) + } +} +impl ::std::error::Error for ImportConfigTomlError { + fn source(&self) -> ::std::option::Option<&(dyn ::std::error::Error + 'static)> { + match self { + Self::InternalServerError(_inner) => + ::std::option::Option::Some(_inner) + , + Self::Unhandled(_inner) => { + ::std::option::Option::Some(&*_inner.source) + } + } + } +} +impl ::std::fmt::Display for ImportConfigTomlError { + fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result { + match self { + Self::InternalServerError(_inner) => + _inner.fmt(f) + , + Self::Unhandled(_inner) => { + if let ::std::option::Option::Some(code) = ::aws_smithy_types::error::metadata::ProvideErrorMetadata::code(self) { + write!(f, "unhandled error ({code})") + } else { + f.write_str("unhandled error") + } + } + } + } +} +impl ::aws_smithy_types::retry::ProvideErrorKind for ImportConfigTomlError { + fn code(&self) -> ::std::option::Option<&str> { + ::aws_smithy_types::error::metadata::ProvideErrorMetadata::code(self) + } + fn retryable_error_kind(&self) -> ::std::option::Option<::aws_smithy_types::retry::ErrorKind> { + ::std::option::Option::None + } +} +impl ::aws_smithy_types::error::metadata::ProvideErrorMetadata for ImportConfigTomlError { + fn meta(&self) -> &::aws_smithy_types::error::ErrorMetadata { + match self { + Self::InternalServerError(_inner) => + ::aws_smithy_types::error::metadata::ProvideErrorMetadata::meta(_inner) + , + Self::Unhandled(_inner) => { + &_inner.meta + } + } + } +} +impl ::aws_smithy_runtime_api::client::result::CreateUnhandledError for ImportConfigTomlError { + fn create_unhandled_error( + source: ::std::boxed::Box, + meta: ::std::option::Option<::aws_smithy_types::error::ErrorMetadata> + ) -> Self { + Self::Unhandled(crate::error::sealed_unhandled::Unhandled { source, meta: meta.unwrap_or_default() }) + } +} + +pub use crate::operation::import_config_toml::_import_config_toml_output::ImportConfigTomlOutput; + +pub use crate::operation::import_config_toml::_import_config_toml_input::ImportConfigTomlInput; + +mod _import_config_toml_input; + +mod _import_config_toml_output; + +/// Builders +pub mod builders; + diff --git a/crates/superposition_sdk/src/operation/import_config_toml/_import_config_toml_input.rs b/crates/superposition_sdk/src/operation/import_config_toml/_import_config_toml_input.rs new file mode 100644 index 000000000..58add37bc --- /dev/null +++ b/crates/superposition_sdk/src/operation/import_config_toml/_import_config_toml_input.rs @@ -0,0 +1,187 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +#[allow(missing_docs)] // documentation missing in model +#[non_exhaustive] +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::fmt::Debug)] +pub struct ImportConfigTomlInput { + #[allow(missing_docs)] // documentation missing in model + pub workspace_id: ::std::option::Option<::std::string::String>, + #[allow(missing_docs)] // documentation missing in model + pub org_id: ::std::option::Option<::std::string::String>, + /// How the import applies file entities to the workspace. Defaults to upsert. + pub strategy: ::std::option::Option, + /// Whether to abort (default) or continue on per-entity errors. + pub on_error: ::std::option::Option, + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub dry_run: ::std::option::Option, + #[allow(missing_docs)] // documentation missing in model + pub config_tags: ::std::option::Option<::std::string::String>, + #[allow(missing_docs)] // documentation missing in model + pub toml_config: ::std::option::Option<::std::string::String>, +} +impl ImportConfigTomlInput { + #[allow(missing_docs)] // documentation missing in model + pub fn workspace_id(&self) -> ::std::option::Option<&str> { + self.workspace_id.as_deref() + } + #[allow(missing_docs)] // documentation missing in model + pub fn org_id(&self) -> ::std::option::Option<&str> { + self.org_id.as_deref() + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn strategy(&self) -> ::std::option::Option<&crate::types::ImportStrategy> { + self.strategy.as_ref() + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn on_error(&self) -> ::std::option::Option<&crate::types::ImportOnError> { + self.on_error.as_ref() + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn dry_run(&self) -> ::std::option::Option { + self.dry_run + } + #[allow(missing_docs)] // documentation missing in model + pub fn config_tags(&self) -> ::std::option::Option<&str> { + self.config_tags.as_deref() + } + #[allow(missing_docs)] // documentation missing in model + pub fn toml_config(&self) -> ::std::option::Option<&str> { + self.toml_config.as_deref() + } +} +impl ImportConfigTomlInput { + /// Creates a new builder-style object to manufacture [`ImportConfigTomlInput`](crate::operation::import_config_toml::ImportConfigTomlInput). + pub fn builder() -> crate::operation::import_config_toml::builders::ImportConfigTomlInputBuilder { + crate::operation::import_config_toml::builders::ImportConfigTomlInputBuilder::default() + } +} + +/// A builder for [`ImportConfigTomlInput`](crate::operation::import_config_toml::ImportConfigTomlInput). +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::default::Default, ::std::fmt::Debug)] +#[non_exhaustive] +pub struct ImportConfigTomlInputBuilder { + pub(crate) workspace_id: ::std::option::Option<::std::string::String>, + pub(crate) org_id: ::std::option::Option<::std::string::String>, + pub(crate) strategy: ::std::option::Option, + pub(crate) on_error: ::std::option::Option, + pub(crate) dry_run: ::std::option::Option, + pub(crate) config_tags: ::std::option::Option<::std::string::String>, + pub(crate) toml_config: ::std::option::Option<::std::string::String>, +} +impl ImportConfigTomlInputBuilder { + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn workspace_id(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.workspace_id = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_workspace_id(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.workspace_id = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_workspace_id(&self) -> &::std::option::Option<::std::string::String> { + &self.workspace_id + } + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn org_id(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.org_id = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_org_id(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.org_id = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_org_id(&self) -> &::std::option::Option<::std::string::String> { + &self.org_id + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn strategy(mut self, input: crate::types::ImportStrategy) -> Self { + self.strategy = ::std::option::Option::Some(input); + self + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn set_strategy(mut self, input: ::std::option::Option) -> Self { + self.strategy = input; self + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn get_strategy(&self) -> &::std::option::Option { + &self.strategy + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn on_error(mut self, input: crate::types::ImportOnError) -> Self { + self.on_error = ::std::option::Option::Some(input); + self + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn set_on_error(mut self, input: ::std::option::Option) -> Self { + self.on_error = input; self + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn get_on_error(&self) -> &::std::option::Option { + &self.on_error + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn dry_run(mut self, input: bool) -> Self { + self.dry_run = ::std::option::Option::Some(input); + self + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn set_dry_run(mut self, input: ::std::option::Option) -> Self { + self.dry_run = input; self + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn get_dry_run(&self) -> &::std::option::Option { + &self.dry_run + } + #[allow(missing_docs)] // documentation missing in model + pub fn config_tags(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.config_tags = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_config_tags(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.config_tags = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_config_tags(&self) -> &::std::option::Option<::std::string::String> { + &self.config_tags + } + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn toml_config(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.toml_config = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_toml_config(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.toml_config = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_toml_config(&self) -> &::std::option::Option<::std::string::String> { + &self.toml_config + } + /// Consumes the builder and constructs a [`ImportConfigTomlInput`](crate::operation::import_config_toml::ImportConfigTomlInput). + pub fn build(self) -> ::std::result::Result { + ::std::result::Result::Ok( + crate::operation::import_config_toml::ImportConfigTomlInput { + workspace_id: self.workspace_id + , + org_id: self.org_id + , + strategy: self.strategy + , + on_error: self.on_error + , + dry_run: self.dry_run + , + config_tags: self.config_tags + , + toml_config: self.toml_config + , + } + ) + } +} + diff --git a/crates/superposition_sdk/src/operation/import_config_toml/_import_config_toml_output.rs b/crates/superposition_sdk/src/operation/import_config_toml/_import_config_toml_output.rs new file mode 100644 index 000000000..38a4997a5 --- /dev/null +++ b/crates/superposition_sdk/src/operation/import_config_toml/_import_config_toml_output.rs @@ -0,0 +1,189 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. + +/// Summary of what an import created, updated, skipped or deleted. +#[non_exhaustive] +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::fmt::Debug)] +pub struct ImportConfigTomlOutput { + #[allow(missing_docs)] // documentation missing in model + pub strategy: ::std::string::String, + #[allow(missing_docs)] // documentation missing in model + pub dry_run: bool, + #[allow(missing_docs)] // documentation missing in model + pub config_version: ::std::option::Option<::std::string::String>, + /// Per-entity outcome counts for an import. + pub dimensions: crate::types::ImportEntityReport, + /// Per-entity outcome counts for an import. + pub default_configs: crate::types::ImportEntityReport, + /// Per-entity outcome counts for an import. + pub contexts: crate::types::ImportEntityReport, +} +impl ImportConfigTomlOutput { + #[allow(missing_docs)] // documentation missing in model + pub fn strategy(&self) -> &str { + use std::ops::Deref; self.strategy.deref() + } + #[allow(missing_docs)] // documentation missing in model + pub fn dry_run(&self) -> bool { + self.dry_run + } + #[allow(missing_docs)] // documentation missing in model + pub fn config_version(&self) -> ::std::option::Option<&str> { + self.config_version.as_deref() + } + /// Per-entity outcome counts for an import. + pub fn dimensions(&self) -> &crate::types::ImportEntityReport { + &self.dimensions + } + /// Per-entity outcome counts for an import. + pub fn default_configs(&self) -> &crate::types::ImportEntityReport { + &self.default_configs + } + /// Per-entity outcome counts for an import. + pub fn contexts(&self) -> &crate::types::ImportEntityReport { + &self.contexts + } +} +impl ImportConfigTomlOutput { + /// Creates a new builder-style object to manufacture [`ImportConfigTomlOutput`](crate::operation::import_config_toml::ImportConfigTomlOutput). + pub fn builder() -> crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder { + crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder::default() + } +} + +/// A builder for [`ImportConfigTomlOutput`](crate::operation::import_config_toml::ImportConfigTomlOutput). +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::default::Default, ::std::fmt::Debug)] +#[non_exhaustive] +pub struct ImportConfigTomlOutputBuilder { + pub(crate) strategy: ::std::option::Option<::std::string::String>, + pub(crate) dry_run: ::std::option::Option, + pub(crate) config_version: ::std::option::Option<::std::string::String>, + pub(crate) dimensions: ::std::option::Option, + pub(crate) default_configs: ::std::option::Option, + pub(crate) contexts: ::std::option::Option, +} +impl ImportConfigTomlOutputBuilder { + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn strategy(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.strategy = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_strategy(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.strategy = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_strategy(&self) -> &::std::option::Option<::std::string::String> { + &self.strategy + } + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn dry_run(mut self, input: bool) -> Self { + self.dry_run = ::std::option::Option::Some(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_dry_run(mut self, input: ::std::option::Option) -> Self { + self.dry_run = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_dry_run(&self) -> &::std::option::Option { + &self.dry_run + } + #[allow(missing_docs)] // documentation missing in model + pub fn config_version(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.config_version = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_config_version(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.config_version = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_config_version(&self) -> &::std::option::Option<::std::string::String> { + &self.config_version + } + /// Per-entity outcome counts for an import. + /// This field is required. + pub fn dimensions(mut self, input: crate::types::ImportEntityReport) -> Self { + self.dimensions = ::std::option::Option::Some(input); + self + } + /// Per-entity outcome counts for an import. + pub fn set_dimensions(mut self, input: ::std::option::Option) -> Self { + self.dimensions = input; self + } + /// Per-entity outcome counts for an import. + pub fn get_dimensions(&self) -> &::std::option::Option { + &self.dimensions + } + /// Per-entity outcome counts for an import. + /// This field is required. + pub fn default_configs(mut self, input: crate::types::ImportEntityReport) -> Self { + self.default_configs = ::std::option::Option::Some(input); + self + } + /// Per-entity outcome counts for an import. + pub fn set_default_configs(mut self, input: ::std::option::Option) -> Self { + self.default_configs = input; self + } + /// Per-entity outcome counts for an import. + pub fn get_default_configs(&self) -> &::std::option::Option { + &self.default_configs + } + /// Per-entity outcome counts for an import. + /// This field is required. + pub fn contexts(mut self, input: crate::types::ImportEntityReport) -> Self { + self.contexts = ::std::option::Option::Some(input); + self + } + /// Per-entity outcome counts for an import. + pub fn set_contexts(mut self, input: ::std::option::Option) -> Self { + self.contexts = input; self + } + /// Per-entity outcome counts for an import. + pub fn get_contexts(&self) -> &::std::option::Option { + &self.contexts + } + /// Consumes the builder and constructs a [`ImportConfigTomlOutput`](crate::operation::import_config_toml::ImportConfigTomlOutput). + /// This method will fail if any of the following fields are not set: + /// - [`strategy`](crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder::strategy) + /// - [`dry_run`](crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder::dry_run) + /// - [`dimensions`](crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder::dimensions) + /// - [`default_configs`](crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder::default_configs) + /// - [`contexts`](crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder::contexts) + pub fn build(self) -> ::std::result::Result { + ::std::result::Result::Ok( + crate::operation::import_config_toml::ImportConfigTomlOutput { + strategy: self.strategy + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("strategy", "strategy was not specified but it is required when building ImportConfigTomlOutput") + )? + , + dry_run: self.dry_run + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("dry_run", "dry_run was not specified but it is required when building ImportConfigTomlOutput") + )? + , + config_version: self.config_version + , + dimensions: self.dimensions + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("dimensions", "dimensions was not specified but it is required when building ImportConfigTomlOutput") + )? + , + default_configs: self.default_configs + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("default_configs", "default_configs was not specified but it is required when building ImportConfigTomlOutput") + )? + , + contexts: self.contexts + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("contexts", "contexts was not specified but it is required when building ImportConfigTomlOutput") + )? + , + } + ) + } +} + diff --git a/crates/superposition_sdk/src/operation/import_config_toml/builders.rs b/crates/superposition_sdk/src/operation/import_config_toml/builders.rs new file mode 100644 index 000000000..95936095b --- /dev/null +++ b/crates/superposition_sdk/src/operation/import_config_toml/builders.rs @@ -0,0 +1,198 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +pub use crate::operation::import_config_toml::_import_config_toml_output::ImportConfigTomlOutputBuilder; + +pub use crate::operation::import_config_toml::_import_config_toml_input::ImportConfigTomlInputBuilder; + +impl crate::operation::import_config_toml::builders::ImportConfigTomlInputBuilder { + /// Sends a request with this input using the given client. + pub async fn send_with(self, client: &crate::Client) -> ::std::result::Result< + crate::operation::import_config_toml::ImportConfigTomlOutput, + ::aws_smithy_runtime_api::client::result::SdkError< + crate::operation::import_config_toml::ImportConfigTomlError, + ::aws_smithy_runtime_api::client::orchestrator::HttpResponse + > + > { + let mut fluent_builder = client.import_config_toml(); + fluent_builder.inner = self; + fluent_builder.send().await + } + } +/// Fluent builder constructing a request to `ImportConfigToml`. +/// +/// Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a single transaction after validating the document. +#[derive(::std::clone::Clone, ::std::fmt::Debug)] +pub struct ImportConfigTomlFluentBuilder { + handle: ::std::sync::Arc, + inner: crate::operation::import_config_toml::builders::ImportConfigTomlInputBuilder, +config_override: ::std::option::Option, + } +impl + crate::client::customize::internal::CustomizableSend< + crate::operation::import_config_toml::ImportConfigTomlOutput, + crate::operation::import_config_toml::ImportConfigTomlError, + > for ImportConfigTomlFluentBuilder + { + fn send( + self, + config_override: crate::config::Builder, + ) -> crate::client::customize::internal::BoxFuture< + crate::client::customize::internal::SendResult< + crate::operation::import_config_toml::ImportConfigTomlOutput, + crate::operation::import_config_toml::ImportConfigTomlError, + >, + > { + ::std::boxed::Box::pin(async move { self.config_override(config_override).send().await }) + } + } +impl ImportConfigTomlFluentBuilder { + /// Creates a new `ImportConfigTomlFluentBuilder`. + pub(crate) fn new(handle: ::std::sync::Arc) -> Self { + Self { + handle, + inner: ::std::default::Default::default(), + config_override: ::std::option::Option::None, + } + } + /// Access the ImportConfigToml as a reference. + pub fn as_input(&self) -> &crate::operation::import_config_toml::builders::ImportConfigTomlInputBuilder { + &self.inner + } + /// Sends the request and returns the response. + /// + /// If an error occurs, an `SdkError` will be returned with additional details that + /// can be matched against. + /// + /// By default, any retryable failures will be retried twice. Retry behavior + /// is configurable with the [RetryConfig](aws_smithy_types::retry::RetryConfig), which can be + /// set when configuring the client. + pub async fn send(self) -> ::std::result::Result> { + let input = self.inner.build().map_err(::aws_smithy_runtime_api::client::result::SdkError::construction_failure)?; + let runtime_plugins = crate::operation::import_config_toml::ImportConfigToml::operation_runtime_plugins( + self.handle.runtime_plugins.clone(), + &self.handle.conf, + self.config_override, + ); + crate::operation::import_config_toml::ImportConfigToml::orchestrate(&runtime_plugins, input).await + } + + /// Consumes this builder, creating a customizable operation that can be modified before being sent. + pub fn customize( + self, + ) -> crate::client::customize::CustomizableOperation { + crate::client::customize::CustomizableOperation::new(self) + } + pub(crate) fn config_override( + mut self, + config_override: impl ::std::convert::Into, + ) -> Self { + self.set_config_override(::std::option::Option::Some(config_override.into())); + self + } + + pub(crate) fn set_config_override( + &mut self, + config_override: ::std::option::Option, + ) -> &mut Self { + self.config_override = config_override; + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn workspace_id(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.inner = self.inner.workspace_id(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_workspace_id(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.inner = self.inner.set_workspace_id(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_workspace_id(&self) -> &::std::option::Option<::std::string::String> { + self.inner.get_workspace_id() + } + #[allow(missing_docs)] // documentation missing in model + pub fn org_id(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.inner = self.inner.org_id(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_org_id(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.inner = self.inner.set_org_id(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_org_id(&self) -> &::std::option::Option<::std::string::String> { + self.inner.get_org_id() + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn strategy(mut self, input: crate::types::ImportStrategy) -> Self { + self.inner = self.inner.strategy(input); + self + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn set_strategy(mut self, input: ::std::option::Option) -> Self { + self.inner = self.inner.set_strategy(input); + self + } + /// How the import applies file entities to the workspace. Defaults to upsert. + pub fn get_strategy(&self) -> &::std::option::Option { + self.inner.get_strategy() + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn on_error(mut self, input: crate::types::ImportOnError) -> Self { + self.inner = self.inner.on_error(input); + self + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn set_on_error(mut self, input: ::std::option::Option) -> Self { + self.inner = self.inner.set_on_error(input); + self + } + /// Whether to abort (default) or continue on per-entity errors. + pub fn get_on_error(&self) -> &::std::option::Option { + self.inner.get_on_error() + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn dry_run(mut self, input: bool) -> Self { + self.inner = self.inner.dry_run(input); + self + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn set_dry_run(mut self, input: ::std::option::Option) -> Self { + self.inner = self.inner.set_dry_run(input); + self + } + /// When true, validates and summarises the import without persisting anything. Defaults to false. + pub fn get_dry_run(&self) -> &::std::option::Option { + self.inner.get_dry_run() + } + #[allow(missing_docs)] // documentation missing in model + pub fn config_tags(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.inner = self.inner.config_tags(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_config_tags(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.inner = self.inner.set_config_tags(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_config_tags(&self) -> &::std::option::Option<::std::string::String> { + self.inner.get_config_tags() + } + #[allow(missing_docs)] // documentation missing in model + pub fn toml_config(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.inner = self.inner.toml_config(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_toml_config(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.inner = self.inner.set_toml_config(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_toml_config(&self) -> &::std::option::Option<::std::string::String> { + self.inner.get_toml_config() + } +} + diff --git a/crates/superposition_sdk/src/protocol_serde.rs b/crates/superposition_sdk/src/protocol_serde.rs index a17bc9030..1c0f45da8 100644 --- a/crates/superposition_sdk/src/protocol_serde.rs +++ b/crates/superposition_sdk/src/protocol_serde.rs @@ -127,6 +127,14 @@ pub(crate) mod shape_get_webhook_by_event; pub(crate) mod shape_get_workspace; +pub(crate) mod shape_import_config_json; + +pub(crate) mod shape_import_config_json_input; + +pub(crate) mod shape_import_config_toml; + +pub(crate) mod shape_import_config_toml_input; + pub(crate) mod shape_list_audit_logs; pub(crate) mod shape_list_contexts; @@ -365,6 +373,8 @@ pub(crate) mod shape_function_execution_request; pub(crate) mod shape_function_list_response; +pub(crate) mod shape_import_entity_report; + pub(crate) mod shape_list_context_out; pub(crate) mod shape_list_default_config_out; @@ -439,6 +449,8 @@ pub(crate) mod shape_experiment_response; pub(crate) mod shape_function_response; +pub(crate) mod shape_import_error_list; + pub(crate) mod shape_list_versions_member; pub(crate) mod shape_organisation_response; @@ -459,6 +471,8 @@ pub(crate) mod shape_weight_recompute_response; pub(crate) mod shape_workspace_response; +pub(crate) mod shape_import_error_item; + pub(crate) mod shape_override_with_keys; pub(crate) mod shape_resolve_explanation_timeline; diff --git a/crates/superposition_sdk/src/protocol_serde/shape_import_config_json.rs b/crates/superposition_sdk/src/protocol_serde/shape_import_config_json.rs new file mode 100644 index 000000000..ae4e7807c --- /dev/null +++ b/crates/superposition_sdk/src/protocol_serde/shape_import_config_json.rs @@ -0,0 +1,182 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +#[allow(clippy::unnecessary_wraps)] +pub fn de_import_config_json_http_error(_response_status: u16, _response_headers: &::aws_smithy_runtime_api::http::Headers, _response_body: &[u8]) -> std::result::Result { + #[allow(unused_mut)] + let mut generic_builder = crate::protocol_serde::parse_http_error_metadata(_response_status, _response_headers, _response_body).map_err(crate::operation::import_config_json::ImportConfigJsonError::unhandled)?; + let generic = generic_builder.build(); + let error_code = match generic.code() { + Some(code) => code, + None => return Err(crate::operation::import_config_json::ImportConfigJsonError::unhandled(generic)) + }; + + let _error_message = generic.message().map(|msg|msg.to_owned()); + Err(match error_code { + "InternalServerError" => crate::operation::import_config_json::ImportConfigJsonError::InternalServerError({ + #[allow(unused_mut)] + let mut tmp = + { + #[allow(unused_mut)] + let mut output = crate::types::error::builders::InternalServerErrorBuilder::default(); + output = crate::protocol_serde::shape_internal_server_error::de_internal_server_error_json_err(_response_body, output).map_err(crate::operation::import_config_json::ImportConfigJsonError::unhandled)?; + let output = output.meta(generic); + output.build() + } + ; + if tmp.message.is_none() { + tmp.message = _error_message; + } + tmp + }), + _ => crate::operation::import_config_json::ImportConfigJsonError::generic(generic) + }) +} + +#[allow(clippy::unnecessary_wraps)] +pub fn de_import_config_json_http_response(_response_status: u16, _response_headers: &::aws_smithy_runtime_api::http::Headers, _response_body: &[u8]) -> std::result::Result { + Ok({ + #[allow(unused_mut)] + let mut output = crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder::default(); + output = crate::protocol_serde::shape_import_config_json::de_import_config_json(_response_body, output).map_err(crate::operation::import_config_json::ImportConfigJsonError::unhandled)?; + crate::serde_util::import_config_json_output_output_correct_errors(output).build().map_err(crate::operation::import_config_json::ImportConfigJsonError::unhandled)? + }) +} + +pub fn ser_import_config_json_headers( + input: &crate::operation::import_config_json::ImportConfigJsonInput, + mut builder: ::http::request::Builder + ) -> std::result::Result<::http::request::Builder, ::aws_smithy_types::error::operation::BuildError> { + if let ::std::option::Option::Some(inner_1) = &input.workspace_id { + let formatted_2 = inner_1.as_str(); + let header_value = formatted_2; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("workspace_id", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-workspace", header_value); + } + if let ::std::option::Option::Some(inner_3) = &input.org_id { + let formatted_4 = inner_3.as_str(); + let header_value = formatted_4; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("org_id", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-org-id", header_value); + } + if let ::std::option::Option::Some(inner_5) = &input.strategy { + let formatted_6 = inner_5.as_str(); + let header_value = formatted_6; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("strategy", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-import-strategy", header_value); + } + if let ::std::option::Option::Some(inner_7) = &input.on_error { + let formatted_8 = inner_7.as_str(); + let header_value = formatted_8; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("on_error", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-import-on-error", header_value); + } + if let ::std::option::Option::Some(inner_9) = &input.dry_run { + let mut encoder = ::aws_smithy_types::primitive::Encoder::from(*inner_9); + let formatted_10 = encoder.encode(); + let header_value = formatted_10; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("dry_run", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-import-dry-run", header_value); + } + if let ::std::option::Option::Some(inner_11) = &input.config_tags { + let formatted_12 = inner_11.as_str(); + let header_value = formatted_12; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("config_tags", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-config-tags", header_value); + } + Ok(builder) +} + +pub(crate) fn de_import_config_json(value: &[u8], mut builder: crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder) -> ::std::result::Result { + let mut tokens_owned = ::aws_smithy_json::deserialize::json_token_iter(crate::protocol_serde::or_empty_doc(value)).peekable(); + let tokens = &mut tokens_owned; + ::aws_smithy_json::deserialize::token::expect_start_object(tokens.next())?; + loop { + match tokens.next().transpose()? { + Some(::aws_smithy_json::deserialize::Token::EndObject { .. }) => break, + Some(::aws_smithy_json::deserialize::Token::ObjectKey { key, .. }) => { + match key.to_unescaped()?.as_ref() { + "config_version" => { + builder = builder.set_config_version( + ::aws_smithy_json::deserialize::token::expect_string_or_null(tokens.next())?.map(|s| + s.to_unescaped().map(|u| + u.into_owned() + ) + ).transpose()? + ); + } + "contexts" => { + builder = builder.set_contexts( + crate::protocol_serde::shape_import_entity_report::de_import_entity_report(tokens)? + ); + } + "default_configs" => { + builder = builder.set_default_configs( + crate::protocol_serde::shape_import_entity_report::de_import_entity_report(tokens)? + ); + } + "dimensions" => { + builder = builder.set_dimensions( + crate::protocol_serde::shape_import_entity_report::de_import_entity_report(tokens)? + ); + } + "dry_run" => { + builder = builder.set_dry_run( + ::aws_smithy_json::deserialize::token::expect_bool_or_null(tokens.next())? + ); + } + "strategy" => { + builder = builder.set_strategy( + ::aws_smithy_json::deserialize::token::expect_string_or_null(tokens.next())?.map(|s| + s.to_unescaped().map(|u| + u.into_owned() + ) + ).transpose()? + ); + } + _ => ::aws_smithy_json::deserialize::token::skip_value(tokens)? + } + } + other => return Err(::aws_smithy_json::deserialize::error::DeserializeError::custom(format!("expected object key or end object, found: {:?}", other))) + } + } + if tokens.next().is_some() { + return Err(::aws_smithy_json::deserialize::error::DeserializeError::custom("found more JSON tokens after completing parsing")); + } + Ok(builder) +} + diff --git a/crates/superposition_sdk/src/protocol_serde/shape_import_config_json_input.rs b/crates/superposition_sdk/src/protocol_serde/shape_import_config_json_input.rs new file mode 100644 index 000000000..08154789b --- /dev/null +++ b/crates/superposition_sdk/src/protocol_serde/shape_import_config_json_input.rs @@ -0,0 +1,12 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +pub fn ser_json_config_http_payload(payload: ::std::option::Option<::std::string::String>) -> ::std::result::Result<::std::vec::Vec, ::aws_smithy_types::error::operation::BuildError> { + let payload = match payload { + Some(t) => t, + None => return Ok( + Vec::new() + )}; + Ok( + payload.into_bytes() + ) +} + diff --git a/crates/superposition_sdk/src/protocol_serde/shape_import_config_toml.rs b/crates/superposition_sdk/src/protocol_serde/shape_import_config_toml.rs new file mode 100644 index 000000000..777a21cb1 --- /dev/null +++ b/crates/superposition_sdk/src/protocol_serde/shape_import_config_toml.rs @@ -0,0 +1,182 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +#[allow(clippy::unnecessary_wraps)] +pub fn de_import_config_toml_http_error(_response_status: u16, _response_headers: &::aws_smithy_runtime_api::http::Headers, _response_body: &[u8]) -> std::result::Result { + #[allow(unused_mut)] + let mut generic_builder = crate::protocol_serde::parse_http_error_metadata(_response_status, _response_headers, _response_body).map_err(crate::operation::import_config_toml::ImportConfigTomlError::unhandled)?; + let generic = generic_builder.build(); + let error_code = match generic.code() { + Some(code) => code, + None => return Err(crate::operation::import_config_toml::ImportConfigTomlError::unhandled(generic)) + }; + + let _error_message = generic.message().map(|msg|msg.to_owned()); + Err(match error_code { + "InternalServerError" => crate::operation::import_config_toml::ImportConfigTomlError::InternalServerError({ + #[allow(unused_mut)] + let mut tmp = + { + #[allow(unused_mut)] + let mut output = crate::types::error::builders::InternalServerErrorBuilder::default(); + output = crate::protocol_serde::shape_internal_server_error::de_internal_server_error_json_err(_response_body, output).map_err(crate::operation::import_config_toml::ImportConfigTomlError::unhandled)?; + let output = output.meta(generic); + output.build() + } + ; + if tmp.message.is_none() { + tmp.message = _error_message; + } + tmp + }), + _ => crate::operation::import_config_toml::ImportConfigTomlError::generic(generic) + }) +} + +#[allow(clippy::unnecessary_wraps)] +pub fn de_import_config_toml_http_response(_response_status: u16, _response_headers: &::aws_smithy_runtime_api::http::Headers, _response_body: &[u8]) -> std::result::Result { + Ok({ + #[allow(unused_mut)] + let mut output = crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder::default(); + output = crate::protocol_serde::shape_import_config_toml::de_import_config_toml(_response_body, output).map_err(crate::operation::import_config_toml::ImportConfigTomlError::unhandled)?; + crate::serde_util::import_config_toml_output_output_correct_errors(output).build().map_err(crate::operation::import_config_toml::ImportConfigTomlError::unhandled)? + }) +} + +pub fn ser_import_config_toml_headers( + input: &crate::operation::import_config_toml::ImportConfigTomlInput, + mut builder: ::http::request::Builder + ) -> std::result::Result<::http::request::Builder, ::aws_smithy_types::error::operation::BuildError> { + if let ::std::option::Option::Some(inner_1) = &input.workspace_id { + let formatted_2 = inner_1.as_str(); + let header_value = formatted_2; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("workspace_id", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-workspace", header_value); + } + if let ::std::option::Option::Some(inner_3) = &input.org_id { + let formatted_4 = inner_3.as_str(); + let header_value = formatted_4; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("org_id", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-org-id", header_value); + } + if let ::std::option::Option::Some(inner_5) = &input.strategy { + let formatted_6 = inner_5.as_str(); + let header_value = formatted_6; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("strategy", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-import-strategy", header_value); + } + if let ::std::option::Option::Some(inner_7) = &input.on_error { + let formatted_8 = inner_7.as_str(); + let header_value = formatted_8; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("on_error", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-import-on-error", header_value); + } + if let ::std::option::Option::Some(inner_9) = &input.dry_run { + let mut encoder = ::aws_smithy_types::primitive::Encoder::from(*inner_9); + let formatted_10 = encoder.encode(); + let header_value = formatted_10; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("dry_run", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-import-dry-run", header_value); + } + if let ::std::option::Option::Some(inner_11) = &input.config_tags { + let formatted_12 = inner_11.as_str(); + let header_value = formatted_12; + let header_value: ::http::HeaderValue = header_value.parse().map_err(|err| { + ::aws_smithy_types::error::operation::BuildError::invalid_field("config_tags", format!( + "`{}` cannot be used as a header value: {}", + &header_value, + err + )) + })?; + builder = builder.header("x-config-tags", header_value); + } + Ok(builder) +} + +pub(crate) fn de_import_config_toml(value: &[u8], mut builder: crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder) -> ::std::result::Result { + let mut tokens_owned = ::aws_smithy_json::deserialize::json_token_iter(crate::protocol_serde::or_empty_doc(value)).peekable(); + let tokens = &mut tokens_owned; + ::aws_smithy_json::deserialize::token::expect_start_object(tokens.next())?; + loop { + match tokens.next().transpose()? { + Some(::aws_smithy_json::deserialize::Token::EndObject { .. }) => break, + Some(::aws_smithy_json::deserialize::Token::ObjectKey { key, .. }) => { + match key.to_unescaped()?.as_ref() { + "config_version" => { + builder = builder.set_config_version( + ::aws_smithy_json::deserialize::token::expect_string_or_null(tokens.next())?.map(|s| + s.to_unescaped().map(|u| + u.into_owned() + ) + ).transpose()? + ); + } + "contexts" => { + builder = builder.set_contexts( + crate::protocol_serde::shape_import_entity_report::de_import_entity_report(tokens)? + ); + } + "default_configs" => { + builder = builder.set_default_configs( + crate::protocol_serde::shape_import_entity_report::de_import_entity_report(tokens)? + ); + } + "dimensions" => { + builder = builder.set_dimensions( + crate::protocol_serde::shape_import_entity_report::de_import_entity_report(tokens)? + ); + } + "dry_run" => { + builder = builder.set_dry_run( + ::aws_smithy_json::deserialize::token::expect_bool_or_null(tokens.next())? + ); + } + "strategy" => { + builder = builder.set_strategy( + ::aws_smithy_json::deserialize::token::expect_string_or_null(tokens.next())?.map(|s| + s.to_unescaped().map(|u| + u.into_owned() + ) + ).transpose()? + ); + } + _ => ::aws_smithy_json::deserialize::token::skip_value(tokens)? + } + } + other => return Err(::aws_smithy_json::deserialize::error::DeserializeError::custom(format!("expected object key or end object, found: {:?}", other))) + } + } + if tokens.next().is_some() { + return Err(::aws_smithy_json::deserialize::error::DeserializeError::custom("found more JSON tokens after completing parsing")); + } + Ok(builder) +} + diff --git a/crates/superposition_sdk/src/protocol_serde/shape_import_config_toml_input.rs b/crates/superposition_sdk/src/protocol_serde/shape_import_config_toml_input.rs new file mode 100644 index 000000000..918968b0e --- /dev/null +++ b/crates/superposition_sdk/src/protocol_serde/shape_import_config_toml_input.rs @@ -0,0 +1,12 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +pub fn ser_toml_config_http_payload(payload: ::std::option::Option<::std::string::String>) -> ::std::result::Result<::std::vec::Vec, ::aws_smithy_types::error::operation::BuildError> { + let payload = match payload { + Some(t) => t, + None => return Ok( + Vec::new() + )}; + Ok( + payload.into_bytes() + ) +} + diff --git a/crates/superposition_sdk/src/protocol_serde/shape_import_entity_report.rs b/crates/superposition_sdk/src/protocol_serde/shape_import_entity_report.rs new file mode 100644 index 000000000..b8a1be7cc --- /dev/null +++ b/crates/superposition_sdk/src/protocol_serde/shape_import_entity_report.rs @@ -0,0 +1,60 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +pub(crate) fn de_import_entity_report<'a, I>(tokens: &mut ::std::iter::Peekable) -> ::std::result::Result, ::aws_smithy_json::deserialize::error::DeserializeError> + where I: Iterator, ::aws_smithy_json::deserialize::error::DeserializeError>> { + match tokens.next().transpose()? { + Some(::aws_smithy_json::deserialize::Token::ValueNull { .. }) => Ok(None), + Some(::aws_smithy_json::deserialize::Token::StartObject { .. }) => { + #[allow(unused_mut)] + let mut builder = crate::types::builders::ImportEntityReportBuilder::default(); + loop { + match tokens.next().transpose()? { + Some(::aws_smithy_json::deserialize::Token::EndObject { .. }) => break, + Some(::aws_smithy_json::deserialize::Token::ObjectKey { key, .. }) => { + match key.to_unescaped()?.as_ref() { + "created" => { + builder = builder.set_created( + ::aws_smithy_json::deserialize::token::expect_number_or_null(tokens.next())? + .map(i32::try_from) + .transpose()? + ); + } + "updated" => { + builder = builder.set_updated( + ::aws_smithy_json::deserialize::token::expect_number_or_null(tokens.next())? + .map(i32::try_from) + .transpose()? + ); + } + "skipped" => { + builder = builder.set_skipped( + ::aws_smithy_json::deserialize::token::expect_number_or_null(tokens.next())? + .map(i32::try_from) + .transpose()? + ); + } + "deleted" => { + builder = builder.set_deleted( + ::aws_smithy_json::deserialize::token::expect_number_or_null(tokens.next())? + .map(i32::try_from) + .transpose()? + ); + } + "errors" => { + builder = builder.set_errors( + crate::protocol_serde::shape_import_error_list::de_import_error_list(tokens)? + ); + } + _ => ::aws_smithy_json::deserialize::token::skip_value(tokens)? + } + } + other => return Err(::aws_smithy_json::deserialize::error::DeserializeError::custom(format!("expected object key or end object, found: {:?}", other))) + } + } + Ok(Some(crate::serde_util::import_entity_report_correct_errors(builder).build().map_err(|err|::aws_smithy_json::deserialize::error::DeserializeError::custom_source("Response was invalid", err))?)) + } + _ => { + Err(::aws_smithy_json::deserialize::error::DeserializeError::custom("expected start object or null")) + } + } +} + diff --git a/crates/superposition_sdk/src/protocol_serde/shape_import_error_item.rs b/crates/superposition_sdk/src/protocol_serde/shape_import_error_item.rs new file mode 100644 index 000000000..de2555f15 --- /dev/null +++ b/crates/superposition_sdk/src/protocol_serde/shape_import_error_item.rs @@ -0,0 +1,45 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +pub(crate) fn de_import_error_item<'a, I>(tokens: &mut ::std::iter::Peekable) -> ::std::result::Result, ::aws_smithy_json::deserialize::error::DeserializeError> + where I: Iterator, ::aws_smithy_json::deserialize::error::DeserializeError>> { + match tokens.next().transpose()? { + Some(::aws_smithy_json::deserialize::Token::ValueNull { .. }) => Ok(None), + Some(::aws_smithy_json::deserialize::Token::StartObject { .. }) => { + #[allow(unused_mut)] + let mut builder = crate::types::builders::ImportErrorItemBuilder::default(); + loop { + match tokens.next().transpose()? { + Some(::aws_smithy_json::deserialize::Token::EndObject { .. }) => break, + Some(::aws_smithy_json::deserialize::Token::ObjectKey { key, .. }) => { + match key.to_unescaped()?.as_ref() { + "id" => { + builder = builder.set_id( + ::aws_smithy_json::deserialize::token::expect_string_or_null(tokens.next())?.map(|s| + s.to_unescaped().map(|u| + u.into_owned() + ) + ).transpose()? + ); + } + "message" => { + builder = builder.set_message( + ::aws_smithy_json::deserialize::token::expect_string_or_null(tokens.next())?.map(|s| + s.to_unescaped().map(|u| + u.into_owned() + ) + ).transpose()? + ); + } + _ => ::aws_smithy_json::deserialize::token::skip_value(tokens)? + } + } + other => return Err(::aws_smithy_json::deserialize::error::DeserializeError::custom(format!("expected object key or end object, found: {:?}", other))) + } + } + Ok(Some(crate::serde_util::import_error_item_correct_errors(builder).build().map_err(|err|::aws_smithy_json::deserialize::error::DeserializeError::custom_source("Response was invalid", err))?)) + } + _ => { + Err(::aws_smithy_json::deserialize::error::DeserializeError::custom("expected start object or null")) + } + } +} + diff --git a/crates/superposition_sdk/src/protocol_serde/shape_import_error_list.rs b/crates/superposition_sdk/src/protocol_serde/shape_import_error_list.rs new file mode 100644 index 000000000..29d185753 --- /dev/null +++ b/crates/superposition_sdk/src/protocol_serde/shape_import_error_list.rs @@ -0,0 +1,30 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +pub(crate) fn de_import_error_list<'a, I>(tokens: &mut ::std::iter::Peekable) -> ::std::result::Result>, ::aws_smithy_json::deserialize::error::DeserializeError> + where I: Iterator, ::aws_smithy_json::deserialize::error::DeserializeError>> { + match tokens.next().transpose()? { + Some(::aws_smithy_json::deserialize::Token::ValueNull { .. }) => Ok(None), + Some(::aws_smithy_json::deserialize::Token::StartArray { .. }) => { + let mut items = Vec::new(); + loop { + match tokens.peek() { + Some(Ok(::aws_smithy_json::deserialize::Token::EndArray { .. })) => { + tokens.next().transpose().unwrap(); break; + } + _ => { + let value = + crate::protocol_serde::shape_import_error_item::de_import_error_item(tokens)? + ; + if let Some(value) = value { + items.push(value); + } + } + } + } + Ok(Some(items)) + } + _ => { + Err(::aws_smithy_json::deserialize::error::DeserializeError::custom("expected start array or null")) + } + } +} + diff --git a/crates/superposition_sdk/src/serde_util.rs b/crates/superposition_sdk/src/serde_util.rs index 0344feacd..87da23131 100644 --- a/crates/superposition_sdk/src/serde_util.rs +++ b/crates/superposition_sdk/src/serde_util.rs @@ -584,6 +584,24 @@ if builder.enable_change_reason_validation.is_none() { builder.enable_change_rea builder } +pub(crate) fn import_config_json_output_output_correct_errors(mut builder: crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder) -> crate::operation::import_config_json::builders::ImportConfigJsonOutputBuilder { + if builder.strategy.is_none() { builder.strategy = Some(Default::default()) } +if builder.dry_run.is_none() { builder.dry_run = Some(Default::default()) } +if builder.dimensions.is_none() { builder.dimensions = { let builder = crate::types::builders::ImportEntityReportBuilder::default(); crate::serde_util::import_entity_report_correct_errors(builder).build().ok() } } +if builder.default_configs.is_none() { builder.default_configs = { let builder = crate::types::builders::ImportEntityReportBuilder::default(); crate::serde_util::import_entity_report_correct_errors(builder).build().ok() } } +if builder.contexts.is_none() { builder.contexts = { let builder = crate::types::builders::ImportEntityReportBuilder::default(); crate::serde_util::import_entity_report_correct_errors(builder).build().ok() } } + builder + } + +pub(crate) fn import_config_toml_output_output_correct_errors(mut builder: crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder) -> crate::operation::import_config_toml::builders::ImportConfigTomlOutputBuilder { + if builder.strategy.is_none() { builder.strategy = Some(Default::default()) } +if builder.dry_run.is_none() { builder.dry_run = Some(Default::default()) } +if builder.dimensions.is_none() { builder.dimensions = { let builder = crate::types::builders::ImportEntityReportBuilder::default(); crate::serde_util::import_entity_report_correct_errors(builder).build().ok() } } +if builder.default_configs.is_none() { builder.default_configs = { let builder = crate::types::builders::ImportEntityReportBuilder::default(); crate::serde_util::import_entity_report_correct_errors(builder).build().ok() } } +if builder.contexts.is_none() { builder.contexts = { let builder = crate::types::builders::ImportEntityReportBuilder::default(); crate::serde_util::import_entity_report_correct_errors(builder).build().ok() } } + builder + } + pub(crate) fn list_audit_logs_output_output_correct_errors(mut builder: crate::operation::list_audit_logs::builders::ListAuditLogsOutputBuilder) -> crate::operation::list_audit_logs::builders::ListAuditLogsOutputBuilder { if builder.total_pages.is_none() { builder.total_pages = Some(Default::default()) } if builder.total_items.is_none() { builder.total_items = Some(Default::default()) } @@ -1015,6 +1033,14 @@ if builder.dimensions.is_none() { builder.dimensions = Some(Default::default()) builder } +pub(crate) fn import_entity_report_correct_errors(mut builder: crate::types::builders::ImportEntityReportBuilder) -> crate::types::builders::ImportEntityReportBuilder { + if builder.created.is_none() { builder.created = Some(Default::default()) } +if builder.updated.is_none() { builder.updated = Some(Default::default()) } +if builder.skipped.is_none() { builder.skipped = Some(Default::default()) } +if builder.deleted.is_none() { builder.deleted = Some(Default::default()) } + builder + } + pub(crate) fn audit_log_full_correct_errors(mut builder: crate::types::builders::AuditLogFullBuilder) -> crate::types::builders::AuditLogFullBuilder { if builder.id.is_none() { builder.id = Some(Default::default()) } if builder.table_name.is_none() { builder.table_name = Some(Default::default()) } @@ -1248,6 +1274,12 @@ if builder.enable_change_reason_validation.is_none() { builder.enable_change_rea builder } +pub(crate) fn import_error_item_correct_errors(mut builder: crate::types::builders::ImportErrorItemBuilder) -> crate::types::builders::ImportErrorItemBuilder { + if builder.id.is_none() { builder.id = Some(Default::default()) } +if builder.message.is_none() { builder.message = Some(Default::default()) } + builder + } + pub(crate) fn resolve_explanation_timeline_item_correct_errors(mut builder: crate::types::builders::ResolveExplanationTimelineItemBuilder) -> crate::types::builders::ResolveExplanationTimelineItemBuilder { if builder.context_id.is_none() { builder.context_id = Some(Default::default()) } if builder.condition.is_none() { builder.condition = Some(Default::default()) } diff --git a/crates/superposition_sdk/src/types.rs b/crates/superposition_sdk/src/types.rs index b39d43f0e..e5c4b566a 100644 --- a/crates/superposition_sdk/src/types.rs +++ b/crates/superposition_sdk/src/types.rs @@ -115,6 +115,14 @@ pub use crate::types::_dimension_response::DimensionResponse; pub use crate::types::_default_config_response::DefaultConfigResponse; +pub use crate::types::_import_entity_report::ImportEntityReport; + +pub use crate::types::_import_error_item::ImportErrorItem; + +pub use crate::types::_import_on_error::ImportOnError; + +pub use crate::types::_import_strategy::ImportStrategy; + mod _audit_action; mod _audit_log_full; @@ -179,6 +187,14 @@ mod _group_type; mod _http_method; +mod _import_entity_report; + +mod _import_error_item; + +mod _import_on_error; + +mod _import_strategy; + mod _list_versions_member; mod _merge_strategy; diff --git a/crates/superposition_sdk/src/types/_import_entity_report.rs b/crates/superposition_sdk/src/types/_import_entity_report.rs new file mode 100644 index 000000000..c97557bda --- /dev/null +++ b/crates/superposition_sdk/src/types/_import_entity_report.rs @@ -0,0 +1,170 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. + +/// Per-entity outcome counts for an import. +#[non_exhaustive] +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::fmt::Debug)] +pub struct ImportEntityReport { + #[allow(missing_docs)] // documentation missing in model + pub created: i32, + #[allow(missing_docs)] // documentation missing in model + pub updated: i32, + #[allow(missing_docs)] // documentation missing in model + pub skipped: i32, + #[allow(missing_docs)] // documentation missing in model + pub deleted: i32, + #[allow(missing_docs)] // documentation missing in model + pub errors: ::std::option::Option<::std::vec::Vec::>, +} +impl ImportEntityReport { + #[allow(missing_docs)] // documentation missing in model + pub fn created(&self) -> i32 { + self.created + } + #[allow(missing_docs)] // documentation missing in model + pub fn updated(&self) -> i32 { + self.updated + } + #[allow(missing_docs)] // documentation missing in model + pub fn skipped(&self) -> i32 { + self.skipped + } + #[allow(missing_docs)] // documentation missing in model + pub fn deleted(&self) -> i32 { + self.deleted + } + #[allow(missing_docs)] // documentation missing in model + /// + /// If no value was sent for this field, a default will be set. If you want to determine if no value was sent, use `.errors.is_none()`. + pub fn errors(&self) -> &[crate::types::ImportErrorItem] { + self.errors.as_deref() + .unwrap_or_default() + } +} +impl ImportEntityReport { + /// Creates a new builder-style object to manufacture [`ImportEntityReport`](crate::types::ImportEntityReport). + pub fn builder() -> crate::types::builders::ImportEntityReportBuilder { + crate::types::builders::ImportEntityReportBuilder::default() + } +} + +/// A builder for [`ImportEntityReport`](crate::types::ImportEntityReport). +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::default::Default, ::std::fmt::Debug)] +#[non_exhaustive] +pub struct ImportEntityReportBuilder { + pub(crate) created: ::std::option::Option, + pub(crate) updated: ::std::option::Option, + pub(crate) skipped: ::std::option::Option, + pub(crate) deleted: ::std::option::Option, + pub(crate) errors: ::std::option::Option<::std::vec::Vec::>, +} +impl ImportEntityReportBuilder { + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn created(mut self, input: i32) -> Self { + self.created = ::std::option::Option::Some(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_created(mut self, input: ::std::option::Option) -> Self { + self.created = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_created(&self) -> &::std::option::Option { + &self.created + } + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn updated(mut self, input: i32) -> Self { + self.updated = ::std::option::Option::Some(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_updated(mut self, input: ::std::option::Option) -> Self { + self.updated = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_updated(&self) -> &::std::option::Option { + &self.updated + } + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn skipped(mut self, input: i32) -> Self { + self.skipped = ::std::option::Option::Some(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_skipped(mut self, input: ::std::option::Option) -> Self { + self.skipped = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_skipped(&self) -> &::std::option::Option { + &self.skipped + } + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn deleted(mut self, input: i32) -> Self { + self.deleted = ::std::option::Option::Some(input); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_deleted(mut self, input: ::std::option::Option) -> Self { + self.deleted = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_deleted(&self) -> &::std::option::Option { + &self.deleted + } + /// Appends an item to `errors`. + /// + /// To override the contents of this collection use [`set_errors`](Self::set_errors). + /// + pub fn errors(mut self, input: crate::types::ImportErrorItem) -> Self { + let mut v = self.errors.unwrap_or_default(); + v.push(input); + self.errors = ::std::option::Option::Some(v); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_errors(mut self, input: ::std::option::Option<::std::vec::Vec::>) -> Self { + self.errors = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_errors(&self) -> &::std::option::Option<::std::vec::Vec::> { + &self.errors + } + /// Consumes the builder and constructs a [`ImportEntityReport`](crate::types::ImportEntityReport). + /// This method will fail if any of the following fields are not set: + /// - [`created`](crate::types::builders::ImportEntityReportBuilder::created) + /// - [`updated`](crate::types::builders::ImportEntityReportBuilder::updated) + /// - [`skipped`](crate::types::builders::ImportEntityReportBuilder::skipped) + /// - [`deleted`](crate::types::builders::ImportEntityReportBuilder::deleted) + pub fn build(self) -> ::std::result::Result { + ::std::result::Result::Ok( + crate::types::ImportEntityReport { + created: self.created + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("created", "created was not specified but it is required when building ImportEntityReport") + )? + , + updated: self.updated + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("updated", "updated was not specified but it is required when building ImportEntityReport") + )? + , + skipped: self.skipped + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("skipped", "skipped was not specified but it is required when building ImportEntityReport") + )? + , + deleted: self.deleted + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("deleted", "deleted was not specified but it is required when building ImportEntityReport") + )? + , + errors: self.errors + , + } + ) + } +} + diff --git a/crates/superposition_sdk/src/types/_import_error_item.rs b/crates/superposition_sdk/src/types/_import_error_item.rs new file mode 100644 index 000000000..57b86c8c3 --- /dev/null +++ b/crates/superposition_sdk/src/types/_import_error_item.rs @@ -0,0 +1,85 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. +#[allow(missing_docs)] // documentation missing in model +#[non_exhaustive] +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::fmt::Debug)] +pub struct ImportErrorItem { + #[allow(missing_docs)] // documentation missing in model + pub id: ::std::string::String, + #[allow(missing_docs)] // documentation missing in model + pub message: ::std::string::String, +} +impl ImportErrorItem { + #[allow(missing_docs)] // documentation missing in model + pub fn id(&self) -> &str { + use std::ops::Deref; self.id.deref() + } + #[allow(missing_docs)] // documentation missing in model + pub fn message(&self) -> &str { + use std::ops::Deref; self.message.deref() + } +} +impl ImportErrorItem { + /// Creates a new builder-style object to manufacture [`ImportErrorItem`](crate::types::ImportErrorItem). + pub fn builder() -> crate::types::builders::ImportErrorItemBuilder { + crate::types::builders::ImportErrorItemBuilder::default() + } +} + +/// A builder for [`ImportErrorItem`](crate::types::ImportErrorItem). +#[derive(::std::clone::Clone, ::std::cmp::PartialEq, ::std::default::Default, ::std::fmt::Debug)] +#[non_exhaustive] +pub struct ImportErrorItemBuilder { + pub(crate) id: ::std::option::Option<::std::string::String>, + pub(crate) message: ::std::option::Option<::std::string::String>, +} +impl ImportErrorItemBuilder { + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn id(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.id = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_id(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.id = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_id(&self) -> &::std::option::Option<::std::string::String> { + &self.id + } + #[allow(missing_docs)] // documentation missing in model + /// This field is required. + pub fn message(mut self, input: impl ::std::convert::Into<::std::string::String>) -> Self { + self.message = ::std::option::Option::Some(input.into()); + self + } + #[allow(missing_docs)] // documentation missing in model + pub fn set_message(mut self, input: ::std::option::Option<::std::string::String>) -> Self { + self.message = input; self + } + #[allow(missing_docs)] // documentation missing in model + pub fn get_message(&self) -> &::std::option::Option<::std::string::String> { + &self.message + } + /// Consumes the builder and constructs a [`ImportErrorItem`](crate::types::ImportErrorItem). + /// This method will fail if any of the following fields are not set: + /// - [`id`](crate::types::builders::ImportErrorItemBuilder::id) + /// - [`message`](crate::types::builders::ImportErrorItemBuilder::message) + pub fn build(self) -> ::std::result::Result { + ::std::result::Result::Ok( + crate::types::ImportErrorItem { + id: self.id + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("id", "id was not specified but it is required when building ImportErrorItem") + )? + , + message: self.message + .ok_or_else(|| + ::aws_smithy_types::error::operation::BuildError::missing_field("message", "message was not specified but it is required when building ImportErrorItem") + )? + , + } + ) + } +} + diff --git a/crates/superposition_sdk/src/types/_import_on_error.rs b/crates/superposition_sdk/src/types/_import_on_error.rs new file mode 100644 index 000000000..781bceba8 --- /dev/null +++ b/crates/superposition_sdk/src/types/_import_on_error.rs @@ -0,0 +1,107 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. + +/// When writing a match expression against `ImportOnError`, it is important to ensure +/// your code is forward-compatible. That is, if a match arm handles a case for a +/// feature that is supported by the service but has not been represented as an enum +/// variant in a current version of SDK, your code should continue to work when you +/// upgrade SDK to a future version in which the enum does include a variant for that +/// feature. +/// +/// Here is an example of how you can make a match expression forward-compatible: +/// +/// ```text +/// # let importonerror = unimplemented!(); +/// match importonerror { +/// ImportOnError::Abort => { /* ... */ }, +/// ImportOnError::Continue => { /* ... */ }, +/// other @ _ if other.as_str() == "NewFeature" => { /* handles a case for `NewFeature` */ }, +/// _ => { /* ... */ }, +/// } +/// ``` +/// The above code demonstrates that when `importonerror` represents +/// `NewFeature`, the execution path will lead to the second last match arm, +/// even though the enum does not contain a variant `ImportOnError::NewFeature` +/// in the current version of SDK. The reason is that the variable `other`, +/// created by the `@` operator, is bound to +/// `ImportOnError::Unknown(UnknownVariantValue("NewFeature".to_owned()))` +/// and calling `as_str` on it yields `"NewFeature"`. +/// This match expression is forward-compatible when executed with a newer +/// version of SDK where the variant `ImportOnError::NewFeature` is defined. +/// Specifically, when `importonerror` represents `NewFeature`, +/// the execution path will hit the second last match arm as before by virtue of +/// calling `as_str` on `ImportOnError::NewFeature` also yielding `"NewFeature"`. +/// +/// Explicitly matching on the `Unknown` variant should +/// be avoided for two reasons: +/// - The inner data `UnknownVariantValue` is opaque, and no further information can be extracted. +/// - It might inadvertently shadow other intended match arms. +/// +/// How an import reacts when an individual entity fails to apply. +#[non_exhaustive] +#[derive(::std::clone::Clone, ::std::cmp::Eq, ::std::cmp::Ord, ::std::cmp::PartialEq, ::std::cmp::PartialOrd, ::std::fmt::Debug, ::std::hash::Hash)] +pub enum ImportOnError { + /// Roll the whole import back on the first error. + Abort, + /// Apply everything that is valid and report per-entity errors. + Continue, + /// `Unknown` contains new variants that have been added since this code was generated. + #[deprecated(note = "Don't directly match on `Unknown`. See the docs on this enum for the correct way to handle unknown variants.")] + Unknown(crate::primitives::sealed_enum_unknown::UnknownVariantValue) +} +impl ::std::convert::From<&str> for ImportOnError { + fn from(s: &str) -> Self { + match s { + "abort" => ImportOnError::Abort, +"continue" => ImportOnError::Continue, +other => ImportOnError::Unknown(crate::primitives::sealed_enum_unknown::UnknownVariantValue(other.to_owned())) + } + } + } +impl ::std::str::FromStr for ImportOnError { + type Err = ::std::convert::Infallible; + + fn from_str(s: &str) -> ::std::result::Result::Err> { + ::std::result::Result::Ok(ImportOnError::from(s)) + } + } +impl ImportOnError { + /// Returns the `&str` value of the enum member. + pub fn as_str(&self) -> &str { + match self { + ImportOnError::Abort => "abort", + ImportOnError::Continue => "continue", + ImportOnError::Unknown(value) => value.as_str() +} + } + /// Returns all the `&str` representations of the enum members. + pub const fn values() -> &'static [&'static str] { + &["abort", "continue"] + } + } +impl ::std::convert::AsRef for ImportOnError { + fn as_ref(&self) -> &str { + self.as_str() + } + } +impl ImportOnError { + /// Parses the enum value while disallowing unknown variants. + /// + /// Unknown variants will result in an error. + pub fn try_parse(value: &str) -> ::std::result::Result { + match Self::from(value) { + #[allow(deprecated)] + Self::Unknown(_) => ::std::result::Result::Err(crate::error::UnknownVariantError::new(value)), + known => Ok(known), + } + } + } +impl ::std::fmt::Display for ImportOnError { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + ImportOnError::Abort => write!(f, "abort"), +ImportOnError::Continue => write!(f, "continue"), +ImportOnError::Unknown(value) => write!(f, "{}", value) + } + } + } + diff --git a/crates/superposition_sdk/src/types/_import_strategy.rs b/crates/superposition_sdk/src/types/_import_strategy.rs new file mode 100644 index 000000000..e7d71b738 --- /dev/null +++ b/crates/superposition_sdk/src/types/_import_strategy.rs @@ -0,0 +1,113 @@ +// Code generated by software.amazon.smithy.rust.codegen.smithy-rs. DO NOT EDIT. + +/// When writing a match expression against `ImportStrategy`, it is important to ensure +/// your code is forward-compatible. That is, if a match arm handles a case for a +/// feature that is supported by the service but has not been represented as an enum +/// variant in a current version of SDK, your code should continue to work when you +/// upgrade SDK to a future version in which the enum does include a variant for that +/// feature. +/// +/// Here is an example of how you can make a match expression forward-compatible: +/// +/// ```text +/// # let importstrategy = unimplemented!(); +/// match importstrategy { +/// ImportStrategy::CreateOnly => { /* ... */ }, +/// ImportStrategy::Replace => { /* ... */ }, +/// ImportStrategy::Upsert => { /* ... */ }, +/// other @ _ if other.as_str() == "NewFeature" => { /* handles a case for `NewFeature` */ }, +/// _ => { /* ... */ }, +/// } +/// ``` +/// The above code demonstrates that when `importstrategy` represents +/// `NewFeature`, the execution path will lead to the second last match arm, +/// even though the enum does not contain a variant `ImportStrategy::NewFeature` +/// in the current version of SDK. The reason is that the variable `other`, +/// created by the `@` operator, is bound to +/// `ImportStrategy::Unknown(UnknownVariantValue("NewFeature".to_owned()))` +/// and calling `as_str` on it yields `"NewFeature"`. +/// This match expression is forward-compatible when executed with a newer +/// version of SDK where the variant `ImportStrategy::NewFeature` is defined. +/// Specifically, when `importstrategy` represents `NewFeature`, +/// the execution path will hit the second last match arm as before by virtue of +/// calling `as_str` on `ImportStrategy::NewFeature` also yielding `"NewFeature"`. +/// +/// Explicitly matching on the `Unknown` variant should +/// be avoided for two reasons: +/// - The inner data `UnknownVariantValue` is opaque, and no further information can be extracted. +/// - It might inadvertently shadow other intended match arms. +/// +/// How an import applies file entities to the workspace. +#[non_exhaustive] +#[derive(::std::clone::Clone, ::std::cmp::Eq, ::std::cmp::Ord, ::std::cmp::PartialEq, ::std::cmp::PartialOrd, ::std::fmt::Debug, ::std::hash::Hash)] +pub enum ImportStrategy { + /// Create entities that are present in the file but missing from the workspace. Existing entities are skipped. Nothing is deleted. + CreateOnly, + /// Mirror the file: create missing entities, update existing entities, and delete dimensions, default-configs and contexts that are absent from it. + Replace, + /// Create missing entities and update existing entities from the file. Entities absent from the file are left untouched. + Upsert, + /// `Unknown` contains new variants that have been added since this code was generated. + #[deprecated(note = "Don't directly match on `Unknown`. See the docs on this enum for the correct way to handle unknown variants.")] + Unknown(crate::primitives::sealed_enum_unknown::UnknownVariantValue) +} +impl ::std::convert::From<&str> for ImportStrategy { + fn from(s: &str) -> Self { + match s { + "create_only" => ImportStrategy::CreateOnly, +"replace" => ImportStrategy::Replace, +"upsert" => ImportStrategy::Upsert, +other => ImportStrategy::Unknown(crate::primitives::sealed_enum_unknown::UnknownVariantValue(other.to_owned())) + } + } + } +impl ::std::str::FromStr for ImportStrategy { + type Err = ::std::convert::Infallible; + + fn from_str(s: &str) -> ::std::result::Result::Err> { + ::std::result::Result::Ok(ImportStrategy::from(s)) + } + } +impl ImportStrategy { + /// Returns the `&str` value of the enum member. + pub fn as_str(&self) -> &str { + match self { + ImportStrategy::CreateOnly => "create_only", + ImportStrategy::Replace => "replace", + ImportStrategy::Upsert => "upsert", + ImportStrategy::Unknown(value) => value.as_str() +} + } + /// Returns all the `&str` representations of the enum members. + pub const fn values() -> &'static [&'static str] { + &["create_only", "replace", "upsert"] + } + } +impl ::std::convert::AsRef for ImportStrategy { + fn as_ref(&self) -> &str { + self.as_str() + } + } +impl ImportStrategy { + /// Parses the enum value while disallowing unknown variants. + /// + /// Unknown variants will result in an error. + pub fn try_parse(value: &str) -> ::std::result::Result { + match Self::from(value) { + #[allow(deprecated)] + Self::Unknown(_) => ::std::result::Result::Err(crate::error::UnknownVariantError::new(value)), + known => Ok(known), + } + } + } +impl ::std::fmt::Display for ImportStrategy { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match self { + ImportStrategy::CreateOnly => write!(f, "create_only"), +ImportStrategy::Replace => write!(f, "replace"), +ImportStrategy::Upsert => write!(f, "upsert"), +ImportStrategy::Unknown(value) => write!(f, "{}", value) + } + } + } + diff --git a/crates/superposition_sdk/src/types/builders.rs b/crates/superposition_sdk/src/types/builders.rs index b7fc3a81b..2b5d54b92 100644 --- a/crates/superposition_sdk/src/types/builders.rs +++ b/crates/superposition_sdk/src/types/builders.rs @@ -65,3 +65,7 @@ pub use crate::types::_dimension_response::DimensionResponseBuilder; pub use crate::types::_default_config_response::DefaultConfigResponseBuilder; +pub use crate::types::_import_entity_report::ImportEntityReportBuilder; + +pub use crate::types::_import_error_item::ImportErrorItemBuilder; + diff --git a/crates/superposition_types/src/api/config.rs b/crates/superposition_types/src/api/config.rs index 059e345d0..7e2e6b8b0 100644 --- a/crates/superposition_types/src/api/config.rs +++ b/crates/superposition_types/src/api/config.rs @@ -9,8 +9,8 @@ use serde_json::{Map, Value}; use superposition_derives::{IsEmpty, QueryParam}; use crate::{ - IsEmpty, custom_query::{CommaSeparatedStringQParams, QueryParam}, + IsEmpty, }; #[derive(Deserialize)] diff --git a/docs/docs/api/Superposition.openapi.json b/docs/docs/api/Superposition.openapi.json index 68095e309..5a8f2552f 100644 --- a/docs/docs/api/Superposition.openapi.json +++ b/docs/docs/api/Superposition.openapi.json @@ -310,6 +310,97 @@ ] } }, + "/config/json/import": { + "post": { + "description": "Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a single transaction after validating the document.", + "operationId": "ImportConfigJson", + "requestBody": { + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ImportConfigJsonInputPayload" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "x-config-tags", + "in": "header", + "schema": { + "type": "string" + } + }, + { + "name": "x-import-dry-run", + "in": "header", + "description": "When true, validates and summarises the import without persisting anything. Defaults to false.", + "schema": { + "type": "boolean", + "description": "When true, validates and summarises the import without persisting anything. Defaults to false." + } + }, + { + "name": "x-import-on-error", + "in": "header", + "description": "Whether to abort (default) or continue on per-entity errors.", + "schema": { + "$ref": "#/components/schemas/ImportOnError" + } + }, + { + "name": "x-import-strategy", + "in": "header", + "description": "How the import applies file entities to the workspace. Defaults to upsert.", + "schema": { + "$ref": "#/components/schemas/ImportStrategy" + } + }, + { + "name": "x-org-id", + "in": "header", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "x-workspace", + "in": "header", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ImportConfigJson 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImportConfigJsonResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Configuration Management" + ] + } + }, "/config/resolve": { "post": { "description": "Resolves and merges config values based on context conditions, applying overrides and merge strategies to produce the final configuration.", @@ -742,6 +833,97 @@ ] } }, + "/config/toml/import": { + "post": { + "description": "Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a single transaction after validating the document.", + "operationId": "ImportConfigToml", + "requestBody": { + "content": { + "text/plain": { + "schema": { + "$ref": "#/components/schemas/ImportConfigTomlInputPayload" + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "x-config-tags", + "in": "header", + "schema": { + "type": "string" + } + }, + { + "name": "x-import-dry-run", + "in": "header", + "description": "When true, validates and summarises the import without persisting anything. Defaults to false.", + "schema": { + "type": "boolean", + "description": "When true, validates and summarises the import without persisting anything. Defaults to false." + } + }, + { + "name": "x-import-on-error", + "in": "header", + "description": "Whether to abort (default) or continue on per-entity errors.", + "schema": { + "$ref": "#/components/schemas/ImportOnError" + } + }, + { + "name": "x-import-strategy", + "in": "header", + "description": "How the import applies file entities to the workspace. Defaults to upsert.", + "schema": { + "$ref": "#/components/schemas/ImportStrategy" + } + }, + { + "name": "x-org-id", + "in": "header", + "schema": { + "type": "string" + }, + "required": true + }, + { + "name": "x-workspace", + "in": "header", + "schema": { + "type": "string" + }, + "required": true + } + ], + "responses": { + "200": { + "description": "ImportConfigToml 200 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ImportConfigTomlResponseContent" + } + } + } + }, + "500": { + "description": "InternalServerError 500 response", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InternalServerErrorResponseContent" + } + } + } + } + }, + "tags": [ + "Configuration Management" + ] + } + }, "/config/versions": { "get": { "description": "Retrieves a paginated list of config versions with their metadata, hash values, and creation timestamps for audit and rollback purposes.", @@ -10116,6 +10298,136 @@ "HEAD" ] }, + "ImportConfigJsonInputPayload": { + "type": "string" + }, + "ImportConfigJsonResponseContent": { + "type": "object", + "description": "Summary of what an import created, updated, skipped or deleted.", + "properties": { + "strategy": { + "type": "string" + }, + "dry_run": { + "type": "boolean" + }, + "config_version": { + "type": "string" + }, + "dimensions": { + "$ref": "#/components/schemas/ImportEntityReport" + }, + "default_configs": { + "$ref": "#/components/schemas/ImportEntityReport" + }, + "contexts": { + "$ref": "#/components/schemas/ImportEntityReport" + } + }, + "required": [ + "contexts", + "default_configs", + "dimensions", + "dry_run", + "strategy" + ] + }, + "ImportConfigTomlInputPayload": { + "type": "string" + }, + "ImportConfigTomlResponseContent": { + "type": "object", + "description": "Summary of what an import created, updated, skipped or deleted.", + "properties": { + "strategy": { + "type": "string" + }, + "dry_run": { + "type": "boolean" + }, + "config_version": { + "type": "string" + }, + "dimensions": { + "$ref": "#/components/schemas/ImportEntityReport" + }, + "default_configs": { + "$ref": "#/components/schemas/ImportEntityReport" + }, + "contexts": { + "$ref": "#/components/schemas/ImportEntityReport" + } + }, + "required": [ + "contexts", + "default_configs", + "dimensions", + "dry_run", + "strategy" + ] + }, + "ImportEntityReport": { + "type": "object", + "description": "Per-entity outcome counts for an import.", + "properties": { + "created": { + "type": "number" + }, + "updated": { + "type": "number" + }, + "skipped": { + "type": "number" + }, + "deleted": { + "type": "number" + }, + "errors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImportErrorItem" + } + } + }, + "required": [ + "created", + "deleted", + "skipped", + "updated" + ] + }, + "ImportErrorItem": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "id", + "message" + ] + }, + "ImportOnError": { + "type": "string", + "description": "How an import reacts when an individual entity fails to apply.", + "enum": [ + "abort", + "continue" + ] + }, + "ImportStrategy": { + "type": "string", + "description": "How an import applies file entities to the workspace.", + "enum": [ + "create_only", + "upsert", + "replace" + ] + }, "InternalServerErrorResponseContent": { "type": "object", "properties": { diff --git a/docs/docs/api/import-config-json.ParamsDetails.json b/docs/docs/api/import-config-json.ParamsDetails.json new file mode 100644 index 000000000..85c1c6b92 --- /dev/null +++ b/docs/docs/api/import-config-json.ParamsDetails.json @@ -0,0 +1 @@ +{"parameters":[{"name":"x-config-tags","in":"header","schema":{"type":"string"}},{"name":"x-import-dry-run","in":"header","description":"When true, validates and summarises the import without persisting anything. Defaults to false.","schema":{"type":"boolean","description":"When true, validates and summarises the import without persisting anything. Defaults to false."}},{"name":"x-import-on-error","in":"header","description":"Whether to abort (default) or continue on per-entity errors.","schema":{"type":"string","description":"How an import reacts when an individual entity fails to apply.","enum":["abort","continue"],"title":"ImportOnError"}},{"name":"x-import-strategy","in":"header","description":"How the import applies file entities to the workspace. Defaults to upsert.","schema":{"type":"string","description":"How an import applies file entities to the workspace.","enum":["create_only","upsert","replace"],"title":"ImportStrategy"}},{"name":"x-org-id","in":"header","schema":{"type":"string"},"required":true},{"name":"x-workspace","in":"header","schema":{"type":"string"},"required":true}]} \ No newline at end of file diff --git a/docs/docs/api/import-config-json.RequestSchema.json b/docs/docs/api/import-config-json.RequestSchema.json new file mode 100644 index 000000000..d6ff4e1a3 --- /dev/null +++ b/docs/docs/api/import-config-json.RequestSchema.json @@ -0,0 +1 @@ +{"title":"Body","body":{"content":{"text/plain":{"schema":{"type":"string","title":"ImportConfigJsonInputPayload"}}},"required":true}} \ No newline at end of file diff --git a/docs/docs/api/import-config-json.StatusCodes.json b/docs/docs/api/import-config-json.StatusCodes.json new file mode 100644 index 000000000..9b151e3fb --- /dev/null +++ b/docs/docs/api/import-config-json.StatusCodes.json @@ -0,0 +1 @@ +{"responses":{"200":{"description":"ImportConfigJson 200 response","content":{"application/json":{"schema":{"type":"object","description":"Summary of what an import created, updated, skipped or deleted.","properties":{"strategy":{"type":"string"},"dry_run":{"type":"boolean"},"config_version":{"type":"string"},"dimensions":{"type":"object","description":"Per-entity outcome counts for an import.","properties":{"created":{"type":"number"},"updated":{"type":"number"},"skipped":{"type":"number"},"deleted":{"type":"number"},"errors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"}},"required":["id","message"],"title":"ImportErrorItem"}}},"required":["created","deleted","skipped","updated"],"title":"ImportEntityReport"},"default_configs":{"type":"object","description":"Per-entity outcome counts for an import.","properties":{"created":{"type":"number"},"updated":{"type":"number"},"skipped":{"type":"number"},"deleted":{"type":"number"},"errors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"}},"required":["id","message"],"title":"ImportErrorItem"}}},"required":["created","deleted","skipped","updated"],"title":"ImportEntityReport"},"contexts":{"type":"object","description":"Per-entity outcome counts for an import.","properties":{"created":{"type":"number"},"updated":{"type":"number"},"skipped":{"type":"number"},"deleted":{"type":"number"},"errors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"}},"required":["id","message"],"title":"ImportErrorItem"}}},"required":["created","deleted","skipped","updated"],"title":"ImportEntityReport"}},"required":["contexts","default_configs","dimensions","dry_run","strategy"],"title":"ImportConfigJsonResponseContent"}}}},"500":{"description":"InternalServerError 500 response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"title":"InternalServerErrorResponseContent"}}}}}} \ No newline at end of file diff --git a/docs/docs/api/import-config-json.api.mdx b/docs/docs/api/import-config-json.api.mdx new file mode 100644 index 000000000..61f75d969 --- /dev/null +++ b/docs/docs/api/import-config-json.api.mdx @@ -0,0 +1,69 @@ +--- +id: import-config-json +title: "ImportConfigJson" +description: "Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a single transaction after validating the document." +sidebar_label: "ImportConfigJson" +hide_title: true +hide_table_of_contents: true +api: eJztWFFv2zgM/iuCnzYgTosd+tK3bXfAdcCuRdPDPQRFoVh0otaWfJLczAj834+UZceunTbotsMw7KW1ZYr8+JEixewiATYxsnBSq+g8usgLbZxlnKVllrFEq1SuWWp0jkufFpd/MaGTMgflZqwAY6V1Uq2ZkLhkUYWdMQEpLzMXN3tRlRKkx8EX1CsV6rG4JQPmDFeWJ2SZ8dSBYY88k4J7jW4Dnal5NIs0WuMkeiE6mB+9hU8Wkc8iA/+WYN0HLarofBd5i8rRI1k+KTIuFb3ZZAM59+tVAajLOoMGUYOTLoMJ5ReqKN0VrzLNRVTXdWNLGkAkzpSACwU3PAd0wUbny12k8AUVfQkcxI6vLRogANEGuACDb4eAoIGeBunBxMJUsSnVSMkwev9sQDGCNGuphIZ/W+Y5N9LiKxHbKGVb6Ta6dP1AclW5DT7M2e9NGHGDZinPLMynMK+0zoCr/xvJJEdaxWCMNi+ThJYNaeMrMv4mZOxbpo3PVKlKYJiUiCbGHJKuYl6xnaSgy5+hmT/1Fn1oHTSAiW7ZlmihVSXkoxQlz1gwkHKZeQ95UWQVGQJV5phNkQeJ7y2y6PZpql6qP7zfk6wgPCR/Xb3ACsHtBYRQSIxRKvGgeoj0hvBIZqvNgy14AsPYlIUF417P0ZE2e9QkSKuDO60ycq+x70sBnvZkgqhFy8WQKW3WsRTHH9BxAegp64B+hb5bWrEFllOwJP/u9JT+TZXqfZliKMXabSFfQgX0zCa+fJ7cU7mcqoN6dQ+JG8Vo4Q9sxXSK2ctdL14N+2KGgRfNg32QRQGCzpGADOuhoGgVhoo3BdPbbWMwxQOWuTsqc+MCU3uH0NW7R6oRWk3v7/rQy45d7U83lp5E54CHv1SYyyni79wcexD87lnAdFxhiGvKQXHwW2Bn8luga/JbU3t6n7gx3J9nB/mko0O8UkxylYO1fA2TDaiXkMvIH41WenSmfOm5QCRPW+OyI2rv3p6EPVVjjT4o10DPDTe+xtyFC8WvyP40kW2vhb9C+pOE9KnONr7jQzyo1fvCP9v3h5Gpfau7Dl3uY2hxtffmbLJLooRRPFuAwcbh/Wdn36hRDiP3bIg6T8ZwppyhHTQzIIeN12Uz/LDPXKENGoqIHxw5NprmoUJbD4i7Db6dNCR7D06ag0HEQlIajJWfUGyON+5qzgs53zhXfOBWJu9L2r28pevM0+/ADZhO4HavbUHshM5+UGfHCK2Pb4E3N1fMSzOO4nTMmxC0dyZ/D6Dv/mAeRnaMGS/+nB0v4GMmVaq90hC7RYnRRqZl2NNdRPB+9u4sPv0tPj0jhBSMnPvcCbfCJn9ZE0oWBtYBut7A+uPO4IHd3jxNRdRkBL9JvWW4pKHwfeNlSD/MmA3lKErsdhhL+NtkdU3LOLUbykl8fMTBkK+Ia0rC9uJM6SqkpQ+Y6n4AfIa8QzAfoJqYyNHZkmQjyvnjjXz3OftZF0Y/Cbzei68ZhI/B2BvJXwnym86lx0DuzcuvhPzmOrTAt+wlm93k+f1N9efSgbVGIHSf+IZ07CUGulrZ90kChetJjdpm3W9OV5eLGxRehV/mci1oj+FbGtXxL+Go6/8AMlIv9A== +sidebar_class_name: "post api-method" +info_path: docs/api/superposition +custom_edit_url: null +--- + +import MethodEndpoint from "@theme/ApiExplorer/MethodEndpoint"; +import ParamsDetails from "@theme/ParamsDetails"; +import RequestSchema from "@theme/RequestSchema"; +import StatusCodes from "@theme/StatusCodes"; +import OperationTabs from "@theme/OperationTabs"; +import TabItem from "@theme/TabItem"; +import Heading from "@theme/Heading"; +import Translate from "@docusaurus/Translate"; + + + + + + + + + + +Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a single transaction after validating the document. + + + Request + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/docs/api/import-config-toml.ParamsDetails.json b/docs/docs/api/import-config-toml.ParamsDetails.json new file mode 100644 index 000000000..85c1c6b92 --- /dev/null +++ b/docs/docs/api/import-config-toml.ParamsDetails.json @@ -0,0 +1 @@ +{"parameters":[{"name":"x-config-tags","in":"header","schema":{"type":"string"}},{"name":"x-import-dry-run","in":"header","description":"When true, validates and summarises the import without persisting anything. Defaults to false.","schema":{"type":"boolean","description":"When true, validates and summarises the import without persisting anything. Defaults to false."}},{"name":"x-import-on-error","in":"header","description":"Whether to abort (default) or continue on per-entity errors.","schema":{"type":"string","description":"How an import reacts when an individual entity fails to apply.","enum":["abort","continue"],"title":"ImportOnError"}},{"name":"x-import-strategy","in":"header","description":"How the import applies file entities to the workspace. Defaults to upsert.","schema":{"type":"string","description":"How an import applies file entities to the workspace.","enum":["create_only","upsert","replace"],"title":"ImportStrategy"}},{"name":"x-org-id","in":"header","schema":{"type":"string"},"required":true},{"name":"x-workspace","in":"header","schema":{"type":"string"},"required":true}]} \ No newline at end of file diff --git a/docs/docs/api/import-config-toml.RequestSchema.json b/docs/docs/api/import-config-toml.RequestSchema.json new file mode 100644 index 000000000..48efbdc46 --- /dev/null +++ b/docs/docs/api/import-config-toml.RequestSchema.json @@ -0,0 +1 @@ +{"title":"Body","body":{"content":{"text/plain":{"schema":{"type":"string","title":"ImportConfigTomlInputPayload"}}},"required":true}} \ No newline at end of file diff --git a/docs/docs/api/import-config-toml.StatusCodes.json b/docs/docs/api/import-config-toml.StatusCodes.json new file mode 100644 index 000000000..b16aa8f06 --- /dev/null +++ b/docs/docs/api/import-config-toml.StatusCodes.json @@ -0,0 +1 @@ +{"responses":{"200":{"description":"ImportConfigToml 200 response","content":{"application/json":{"schema":{"type":"object","description":"Summary of what an import created, updated, skipped or deleted.","properties":{"strategy":{"type":"string"},"dry_run":{"type":"boolean"},"config_version":{"type":"string"},"dimensions":{"type":"object","description":"Per-entity outcome counts for an import.","properties":{"created":{"type":"number"},"updated":{"type":"number"},"skipped":{"type":"number"},"deleted":{"type":"number"},"errors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"}},"required":["id","message"],"title":"ImportErrorItem"}}},"required":["created","deleted","skipped","updated"],"title":"ImportEntityReport"},"default_configs":{"type":"object","description":"Per-entity outcome counts for an import.","properties":{"created":{"type":"number"},"updated":{"type":"number"},"skipped":{"type":"number"},"deleted":{"type":"number"},"errors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"}},"required":["id","message"],"title":"ImportErrorItem"}}},"required":["created","deleted","skipped","updated"],"title":"ImportEntityReport"},"contexts":{"type":"object","description":"Per-entity outcome counts for an import.","properties":{"created":{"type":"number"},"updated":{"type":"number"},"skipped":{"type":"number"},"deleted":{"type":"number"},"errors":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"message":{"type":"string"}},"required":["id","message"],"title":"ImportErrorItem"}}},"required":["created","deleted","skipped","updated"],"title":"ImportEntityReport"}},"required":["contexts","default_configs","dimensions","dry_run","strategy"],"title":"ImportConfigTomlResponseContent"}}}},"500":{"description":"InternalServerError 500 response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"title":"InternalServerErrorResponseContent"}}}}}} \ No newline at end of file diff --git a/docs/docs/api/import-config-toml.api.mdx b/docs/docs/api/import-config-toml.api.mdx new file mode 100644 index 000000000..97b99f324 --- /dev/null +++ b/docs/docs/api/import-config-toml.api.mdx @@ -0,0 +1,69 @@ +--- +id: import-config-toml +title: "ImportConfigToml" +description: "Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a single transaction after validating the document." +sidebar_label: "ImportConfigToml" +hide_title: true +hide_table_of_contents: true +api: eJztWN1v2zgM/1cEP21A3BQ79KVv+zjgCtzQosnhHoKiUCw60WpLPkluZgT+34+U5Y/UTht02+Ew7KW1ZYr88UeKFLOPBNjEyMJJraLL6CovtHGWcZaWWcYSrVK5YanROS4trz//yYROyhyUm7ECjJXWSbVhQuKSRRV2xgSkvMxc3OxFVUqQHgdfUa9UqMfilgyYM1xZnpBlxlMHhj3yTAruNbotdKbOolmk0Ron0SvRwfzoLSx1nqGAgX9KsO6DFlV0uY+8ReXokSzPi4xLRW822ULO/XpVAOqyzqBB1OCky2BC+ZUqSnfDq0xzEdV13diSBhCJMyXgQsENzwFdsNHlah8pfEFFXwMHseMbiwYIQLQFLsDg2zEgaGCgQXowsTBVbEo1UnIYvb+3oBhBmrVUQsO/LfOcG2nxlYhtlLKddFtdumEguarcFh/O2KcmjLhBs5RnFs6mMK+1zoCr/xrJJEdaxWCMNi+ThJYNaeNrMv4mZOxbpo3PVKlKYJiUiCbGHJKuYl6xnaSgy59DM3/oHfrQOmgAE92yHdFCq0rIRylKnrFgIOUy8x7yosgqMgSqzDGbIg8S31tk0d3TVL1Wv3u/J1lBeEj+pnqBFYI7CAihkBijVOJB9RDpDeGRzE6bB1vwBA5jUxYWjHs9RyfaHFCTIK0O7rXKyL3Gvi8FeNqTCaIWLReHTGmziaU4/YCOC8BAWQf0G/Td0YotsJyCJfl35+f0b6pU92WKoRRrt4V8CRXQM5v48jn/YvV0HdTrL5C4UYwW/sBWTKeYvdwN4tWwL2YYeNE82AdZFCDoHAnIsB4KilZhqHhTML3dNgZTPGCZu6cyNy4wtXcIXb1/pBqh1fT+rg+97NhNf7qx9CQ6Bzz8pcJcThF/5+bYg+D3wAKm4xpDXFMOiqPfAjuT3wJdk9+a2jP4xI3h/jw7yCcdPcQrxSRXOVjLNzDZgAYJuYr80WilR2fKl54rRPK0Na46onr3ehJ6qsYafVBugZ4bbnyNuQ8Xil+R/Wki214Lf4X0JwnpU51tfMeH+KBW94V/1veHkam+1d2GLvcxtLjae3Mx2SVRwiieLcBg4/D+s4vv1CgPI/dsiDpPxnCmnKEdNDMgh43XZTP8sM9coQ0aiogfHDm2muahQlsPiLstvs0bkucOqZo3B4OIhaQ0GCs/odgcb9zVGS/k2da54gO3Mnlf0u7VHV1nnn4HbsB0Ane9tgWxEzr7UZ0dI7Q+vgUulzfMSzOO4nTMmxC0dyZ/D6Dv/mAeR3aKGS/+nB0v4GMmVaq90hC7RYnRRqZl2NNdRPB+9u4iPv8tPr8ghBSMnPvcCbfCJn9ZE0oWBtYDdIOB9f87gwd2B/M0FVGTEfwm9VbhkkbCjZch/TBjtpSjKLHfYyzhL5PVNS3j1G4oJ/HxEQdDviauKQnbizOlq5CWPmCq+wHwGfKOwXyAamIiR2dLko0o50838sPn7GddGP0k8HovvmUQPgXjYCR/JcjvOpeeAnkwL78S8pvb0ALfspdsdpPnjzc1nEsPrDUCofvES9LRSxzoamXfJwkUbiA1apv1sDndXC+WKLwOv8zlWtAew3c0quNfwlHX/wIAyTAG +sidebar_class_name: "post api-method" +info_path: docs/api/superposition +custom_edit_url: null +--- + +import MethodEndpoint from "@theme/ApiExplorer/MethodEndpoint"; +import ParamsDetails from "@theme/ParamsDetails"; +import RequestSchema from "@theme/RequestSchema"; +import StatusCodes from "@theme/StatusCodes"; +import OperationTabs from "@theme/OperationTabs"; +import TabItem from "@theme/TabItem"; +import Heading from "@theme/Heading"; +import Translate from "@docusaurus/Translate"; + + + + + + + + + + +Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a single transaction after validating the document. + + + Request + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/docs/api/sidebar.ts b/docs/docs/api/sidebar.ts index dc47c3a4d..35daa795b 100644 --- a/docs/docs/api/sidebar.ts +++ b/docs/docs/api/sidebar.ts @@ -39,6 +39,13 @@ const sidebar: SidebarsConfig = { customProps: {}, className: "api-method post", }, + { + type: "doc", + id: "api/import-config-json", + label: "ImportConfigJson", + customProps: {}, + className: "api-method post", + }, { type: "doc", id: "api/get-resolved-config", @@ -67,6 +74,13 @@ const sidebar: SidebarsConfig = { customProps: {}, className: "api-method post", }, + { + type: "doc", + id: "api/import-config-toml", + label: "ImportConfigToml", + customProps: {}, + className: "api-method post", + }, { type: "doc", id: "api/list-versions", diff --git a/docs/docs/superposition-config-file/import-export.md b/docs/docs/superposition-config-file/import-export.md new file mode 100644 index 000000000..7dea9bfe7 --- /dev/null +++ b/docs/docs/superposition-config-file/import-export.md @@ -0,0 +1,113 @@ +--- +sidebar_position: 9 +title: Import & Export +description: Round-trip a whole workspace as a SuperTOML or JSON file +--- + +# Import & Export + +Superposition can serialize an entire workspace's configuration — +default-configs (with their schemas), dimensions and overrides — into a single +SuperTOML or JSON document, and read that same document back in. This makes it +easy to back up a workspace, copy config between workspaces, review changes as a +file diff, or manage configuration as code. + +Each format has an export endpoint and an import endpoint: + +| Format | Export | Import | +| ------ | ------------------- | -------------------------- | +| TOML | `GET /config/toml` | `POST /config/toml/import` | +| JSON | `GET /config/json` | `POST /config/json/import` | + +All requests carry the usual workspace headers, `x-workspace: ` and +`x-org-id: ` (plus `Authorization`). + +## Exporting + +```bash +# TOML +curl -X GET https:///config/toml \ + -H "x-workspace: " -H "x-org-id: " > config.toml + +# JSON +curl -X GET https:///config/json \ + -H "x-workspace: " -H "x-org-id: " > config.json +``` + +The response body is a complete SuperTOML / JSON document — see +[Format Specification](./format-specification) for its shape. + +## Importing + +POST the file to the matching `/import` endpoint. The body is parsed and **fully +validated** (schemas, dimension positions, cohort relationships, and every +context condition / override) before anything is written. The whole import runs +in a single database transaction. + +```bash +curl -X POST https:///config/toml/import \ + -H "x-workspace: " -H "x-org-id: " \ + -H "Content-Type: application/toml" \ + --data-binary @config.toml +``` + +On success you get a JSON summary of what changed: + +```json +{ + "strategy": "upsert", + "dry_run": false, + "config_version": "7231...", + "dimensions": { "created": 2, "updated": 1, "skipped": 0, "deleted": 0 }, + "default_configs": { "created": 5, "updated": 0, "skipped": 0, "deleted": 0 }, + "contexts": { "created": 3, "updated": 1, "skipped": 0, "deleted": 0 } +} +``` + +### Import options + +Behaviour is controlled with request headers: + +| Header | Values | Default | Effect | +| ------------------- | ----------------------------------- | -------- | -------------------------------------------------------------------------------------------------------- | +| `x-import-strategy` | `create_only` \| `upsert` \| `replace` | `upsert` | `create_only` creates missing entities and skips existing ones. `upsert` creates missing entities and updates existing ones. `replace` mirrors the file and **deletes** any dimension/default-config/context that is _not_ in the file. | +| `x-import-on-error` | `abort` \| `continue` | `abort` | `abort` rolls the whole import back on the first error. `continue` applies everything that's valid and returns the per-entity errors in the summary. | +| `x-import-dry-run` | `true` \| `false` | `false` | Parse, validate and compute the summary **without writing anything**. Great for previewing an import. | + +When `x-import-on-error: continue` is used, failed entities appear under an +`errors` array in the relevant section of the summary: + +```json +"default_configs": { + "created": 4, "updated": 0, "skipped": 0, "deleted": 0, + "errors": [{ "id": "per_km_rate", "error": "..." }] +} +``` + +### Tips + +- Use `x-import-dry-run: true` first to see exactly what an import would do. +- Use `x-import-strategy: replace` to make a workspace _exactly_ match a file + (e.g. restoring from a backup). Use the default `upsert` to layer a file on top + of existing config without removing anything. +- Use `x-import-strategy: create_only` to seed only the keys/dimensions/contexts + that don't exist yet, without touching anything already configured. +- `x-config-tags` is honoured and recorded against the config version the import + creates, just like other write endpoints. +- The endpoints are available in the generated SDKs as the `ImportConfigToml` + and `ImportConfigJson` operations. + +:::note +Value-validation and value-compute function bindings are not part of the +exported file, so they are not set by import. Bind functions to dimensions and +default-configs through their dedicated APIs after importing if needed. +::: + +:::caution +The imported file is validated as a self-contained config (every context only +references dimensions/keys defined in the same file), and dimension **positions** +are taken verbatim from the file. In `upsert` strategy this means the file's positions +should be consistent with the dimensions already in the workspace. To avoid +position clashes entirely, export the workspace, edit, and import back — or use +`replace` strategy, which makes the workspace match the file exactly. +::: diff --git a/docs/docs/superposition-config-file/intro.md b/docs/docs/superposition-config-file/intro.md index 7b40b5add..1b980949e 100644 --- a/docs/docs/superposition-config-file/intro.md +++ b/docs/docs/superposition-config-file/intro.md @@ -154,5 +154,6 @@ This makes editing SuperTOML files as comfortable as working with code in an IDE - [Type Safety](./type-safety) - Deep dive into JSON Schema validation - [Cascading Model](./cascading-model) - How overrides cascade - [Deterministic Resolution](./deterministic-resolution) - Priority and conflict resolution +- [Import & Export](./import-export) - Round-trip a whole workspace as a file - [Examples](./examples) - Complete working examples - [Config File Compatibility](./config-file-compatibility) - Common Linux/macOS configs as SuperTOML diff --git a/makefile b/makefile index e4385c7b4..9ade9f2e5 100644 --- a/makefile +++ b/makefile @@ -238,11 +238,22 @@ test: setup frontend superposition MASTER_ENCRYPTION_KEY=$${MASTER_ENCRYPTION_KEY:-"dGVzdC1tYXN0ZXIta2V5LTMyLWNoYXJhY3RlcnMtb2s="} \ $(MAKE) run & @echo "Awaiting superposition boot..." -## FIXME Curl doesn't retry. - @curl --silent --retry 10 \ - --connect-timeout 2 \ - --retry-all-errors \ - 'http://localhost:8080/health' 2>&1 > /dev/null +## `make run` (re)builds the frontend + server before the binary starts, so the +## health endpoint can take a while to come up. Poll it for up to 5 minutes +## instead of relying on curl's own retry handling, which gives up too early on +## connection-refused while the build is still running. + @for i in $$(seq 1 150); do \ + if curl --silent --fail --connect-timeout 2 \ + 'http://localhost:8080/health' > /dev/null 2>&1; then \ + echo "superposition is up"; \ + break; \ + fi; \ + if [ $$i -eq 150 ]; then \ + echo "superposition did not become healthy in time" >&2; \ + exit 1; \ + fi; \ + sleep 2; \ + done cd tests && bun test:clean $(MAKE) bindings-test $(MAKE) kill @@ -318,7 +329,7 @@ smithy-api-docs: smithy-build cp $(SMITHY_BUILD_SRC)/openapi/Superposition.openapi.json docs/docs/api/ cd docs && npm ci && npm run openapi-docs -smithy-updates: smithy-clients smithy-api-docs +smithy-updates: smithy-clean smithy-clients smithy-api-docs leptosfmt: leptosfmt $(LEPTOS_FMT_FLAGS) $(LEPTOS_PACKAGES) diff --git a/smithy/models/config.smithy b/smithy/models/config.smithy index 7280fd994..31a324ee4 100644 --- a/smithy/models/config.smithy +++ b/smithy/models/config.smithy @@ -183,6 +183,148 @@ operation GetConfigJson { } } +@documentation("How an import applies file entities to the workspace.") +enum ImportStrategy { + @documentation("Create entities that are present in the file but missing from the workspace. Existing entities are skipped. Nothing is deleted.") + CREATE_ONLY = "create_only" + + @documentation("Create missing entities and update existing entities from the file. Entities absent from the file are left untouched.") + UPSERT = "upsert" + + @documentation("Mirror the file: create missing entities, update existing entities, and delete dimensions, default-configs and contexts that are absent from it.") + REPLACE = "replace" +} + +@documentation("How an import reacts when an individual entity fails to apply.") +enum ImportOnError { + @documentation("Roll the whole import back on the first error.") + ABORT = "abort" + + @documentation("Apply everything that is valid and report per-entity errors.") + CONTINUE = "continue" +} + +@documentation("Per-entity outcome counts for an import.") +structure ImportEntityReport { + @required + created: Integer + + @required + updated: Integer + + @required + skipped: Integer + + @required + deleted: Integer + + errors: ImportErrorList +} + +list ImportErrorList { + member: ImportErrorItem +} + +structure ImportErrorItem { + @required + id: String + + @required + message: String +} + +@documentation("Summary of what an import created, updated, skipped or deleted.") +structure ImportConfigOutput { + @required + @notProperty + strategy: String + + @required + @notProperty + dry_run: Boolean + + @notProperty + config_version: String + + @required + @notProperty + dimensions: ImportEntityReport + + @required + @notProperty + default_configs: ImportEntityReport + + @required + @notProperty + contexts: ImportEntityReport +} + +@documentation("Imports a full config from a TOML document, persisting dimensions, default-configs and contexts in a single transaction after validating the document.") +@http(method: "POST", uri: "/config/toml/import") +@tags(["Configuration Management"]) +operation ImportConfigToml { + input := with [WorkspaceMixin] { + @documentation("How the import applies file entities to the workspace. Defaults to upsert.") + @httpHeader("x-import-strategy") + @notProperty + strategy: ImportStrategy + + @documentation("Whether to abort (default) or continue on per-entity errors.") + @httpHeader("x-import-on-error") + @notProperty + on_error: ImportOnError + + @documentation("When true, validates and summarises the import without persisting anything. Defaults to false.") + @httpHeader("x-import-dry-run") + @notProperty + dry_run: Boolean + + @httpHeader("x-config-tags") + @notProperty + config_tags: String + + @httpPayload + @required + @notProperty + toml_config: String + } + + output: ImportConfigOutput +} + +@documentation("Imports a full config from a JSON document, persisting dimensions, default-configs and contexts in a single transaction after validating the document.") +@http(method: "POST", uri: "/config/json/import") +@tags(["Configuration Management"]) +operation ImportConfigJson { + input := with [WorkspaceMixin] { + @documentation("How the import applies file entities to the workspace. Defaults to upsert.") + @httpHeader("x-import-strategy") + @notProperty + strategy: ImportStrategy + + @documentation("Whether to abort (default) or continue on per-entity errors.") + @httpHeader("x-import-on-error") + @notProperty + on_error: ImportOnError + + @documentation("When true, validates and summarises the import without persisting anything. Defaults to false.") + @httpHeader("x-import-dry-run") + @notProperty + dry_run: Boolean + + @httpHeader("x-config-tags") + @notProperty + config_tags: String + + @httpPayload + @required + @notProperty + json_config: String + } + + output: ImportConfigOutput +} + enum MergeStrategy { MERGE REPLACE diff --git a/smithy/models/main.smithy b/smithy/models/main.smithy index e67e344d5..b722bc3e6 100644 --- a/smithy/models/main.smithy +++ b/smithy/models/main.smithy @@ -30,6 +30,10 @@ service Superposition { Secret MasterKey ] + operations: [ + ImportConfigToml + ImportConfigJson + ] errors: [ InternalServerError ] diff --git a/smithy/patches/python.patch b/smithy/patches/python.patch index d8bb36cf1..8c5fc17dd 100644 --- a/smithy/patches/python.patch +++ b/smithy/patches/python.patch @@ -3,7 +3,7 @@ index 7c295b28..b7a7bd7e 100644 --- a/clients/python/sdk/pyproject.toml +++ b/clients/python/sdk/pyproject.toml @@ -3,7 +3,7 @@ - + [project] name = "superposition_sdk" -version = "0.0.1" @@ -14,7 +14,7 @@ index 7c295b28..b7a7bd7e 100644 @@ -48,6 +48,10 @@ exclude = [ "tests", ] - + +[tool.hatch.version] +source = "env" +variable = "VERSION" @@ -22,3 +22,790 @@ index 7c295b28..b7a7bd7e 100644 [tool.pyright] typeCheckingMode = "strict" reportPrivateUsage = false + +diff --git a/clients/python/sdk/superposition_sdk/models.py b/clients/python/sdk/superposition_sdk/models.py +--- a/clients/python/sdk/superposition_sdk/models.py ++++ b/clients/python/sdk/superposition_sdk/models.py +@@ -691,7 +691,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -894,7 +894,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -1195,7 +1195,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -2004,7 +2004,7 @@ + ShapeID("io.superposition#ResourceNotFound"): ResourceNotFound, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -2294,7 +2294,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -2811,7 +2811,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -2908,7 +2908,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -3005,7 +3005,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -3142,7 +3142,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -3279,7 +3279,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -3449,7 +3449,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -3629,7 +3629,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -3794,7 +3794,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -3879,7 +3879,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -4039,7 +4039,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -4199,7 +4199,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -4402,7 +4402,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -4567,7 +4567,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -4732,7 +4732,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -4819,7 +4819,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -4984,7 +4984,7 @@ + ShapeID("io.superposition#WebhookFailed"): WebhookFailed, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -5218,7 +5218,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -5447,7 +5447,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -5715,7 +5715,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -5934,7 +5934,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -6145,7 +6145,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -6332,7 +6332,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -6484,7 +6484,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -6645,7 +6645,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -6792,7 +6792,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -7062,7 +7062,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -7312,7 +7312,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -7393,7 +7393,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -7547,7 +7547,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -7784,7 +7784,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -7992,7 +7992,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -8073,7 +8073,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -8243,7 +8243,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -8323,7 +8323,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -8448,7 +8448,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -8581,7 +8581,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -8707,7 +8707,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -8787,7 +8787,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -8959,7 +8959,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -9210,7 +9210,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -9436,7 +9436,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -9653,7 +9653,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -10088,7 +10088,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -10258,7 +10258,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -10497,7 +10497,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -10689,7 +10689,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -10891,7 +10891,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -11102,7 +11102,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -11326,7 +11326,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -11543,7 +11543,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -11767,7 +11767,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -11984,7 +11984,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -12296,7 +12296,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -12463,7 +12463,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -12731,7 +12731,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -12903,7 +12903,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -13225,7 +13225,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -13418,7 +13418,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -13565,7 +13565,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -13690,7 +13690,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -13823,7 +13823,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -14035,7 +14035,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -14161,7 +14161,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -14339,7 +14339,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -14517,7 +14517,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -14696,7 +14696,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -15015,7 +15015,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -15188,7 +15188,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -15414,7 +15414,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -15658,7 +15658,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -15904,7 +15904,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -16161,7 +16161,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -16419,7 +16419,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -16496,7 +16496,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -16675,7 +16675,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -16862,7 +16862,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -16947,7 +16947,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -17098,7 +17098,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -17257,7 +17257,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -17402,7 +17402,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -17641,7 +17641,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) +@@ -17887,7 +17887,7 @@ + ShapeID("io.superposition#InternalServerError"): InternalServerError, + }), + effective_auth_schemes = [ +- ShapeID("smithy.api#httpBasicAuth") ++ ShapeID("smithy.api#httpBasicAuth"), + ShapeID("smithy.api#httpBearerAuth") + ] + ) diff --git a/tests/src/config_import.test.ts b/tests/src/config_import.test.ts new file mode 100644 index 000000000..e1661f5d2 --- /dev/null +++ b/tests/src/config_import.test.ts @@ -0,0 +1,352 @@ +import { + CreateWorkspaceCommand, + MigrateWorkspaceSchemaCommand, + WorkspaceStatus, + ListDimensionsCommand, + ListDefaultConfigsCommand, + ListContextsCommand, + GetConfigJsonCommand, + ImportConfigJsonCommand, + ImportConfigTomlCommand, + type ImportConfigOutput, + ImportStrategy, + type ImportOnError, +} from "@juspay/superposition-sdk"; +import { superpositionClient, ENV } from "../env.ts"; +import { describe, test, expect, beforeAll } from "bun:test"; + +// Import with the `replace` strategy mirrors the *entire* workspace, so these tests run in +// their own dedicated workspace to avoid clobbering data created by other suites. + +const IMPORT_WORKSPACE = "importtestws"; +const suffix = Math.random().toString(36).substring(7); + +const TIER = `imp_tier_${suffix}`; +const REGION = `imp_region_${suffix}`; +const RATE = `imp_rate_${suffix}`; +const FLAG = `imp_flag_${suffix}`; +const DRYRUN_KEY = `imp_dryrun_${suffix}`; +const TOML_KEY = `imp_toml_${suffix}`; + +type ImportOpts = { + strategy?: ImportStrategy; + on_error?: ImportOnError; + dry_run?: boolean; +}; + +async function importConfig( + format: "toml" | "json", + body: string, + opts: ImportOpts = {}, +): Promise<{ status: number; summary?: ImportConfigOutput; error?: unknown }> { + const base = { + workspace_id: IMPORT_WORKSPACE, + org_id: ENV.org_id, + ...opts, + }; + const cmd = + format === "json" + ? new ImportConfigJsonCommand({ ...base, json_config: body }) + : new ImportConfigTomlCommand({ ...base, toml_config: body }); + try { + const summary = await superpositionClient.send(cmd); + return { + status: summary.$metadata.httpStatusCode ?? 200, + summary, + }; + } catch (e: any) { + return { + status: + e?.$metadata?.httpStatusCode ?? + e?.$response?.statusCode ?? + 500, + error: e, + }; + } +} + +// A self-consistent JSON config: contexts only reference dimensions/keys defined +// in the same document. `opts.includeFlag` lets a test drop one default-config. +function buildJsonConfig(opts: { includeFlag: boolean }): string { + const defaultConfigs: Record = { + [RATE]: { value: 10, schema: { type: "number" } }, + }; + if (opts.includeFlag) { + defaultConfigs[FLAG] = { + value: { enabled: true, mode: "a", nested: { x: 1 } }, + schema: { type: "object" }, + }; + } + return JSON.stringify({ + "default-configs": defaultConfigs, + dimensions: { + [TIER]: { + position: 1, + schema: { type: "string", enum: ["gold", "silver"] }, + }, + [REGION]: { + position: 2, + schema: { type: "string", enum: ["us", "eu"] }, + }, + }, + overrides: [{ _context_: { [TIER]: "gold" }, [RATE]: 20 }], + }); +} + +async function listDefaultConfigKeys(): Promise { + const out = await superpositionClient.send( + new ListDefaultConfigsCommand({ + workspace_id: IMPORT_WORKSPACE, + org_id: ENV.org_id, + count: 100, + page: 1, + }), + ); + return (out.data ?? []).map((d) => d.key as string); +} + +async function getDefaultConfigValue(key: string): Promise { + const out = await superpositionClient.send( + new ListDefaultConfigsCommand({ + workspace_id: IMPORT_WORKSPACE, + org_id: ENV.org_id, + count: 100, + page: 1, + }), + ); + return (out.data ?? []).find((d) => d.key === key)?.value; +} + +async function listDimensionNames(): Promise { + const out = await superpositionClient.send( + new ListDimensionsCommand({ + workspace_id: IMPORT_WORKSPACE, + org_id: ENV.org_id, + count: 100, + page: 1, + }), + ); + return (out.data ?? []).map((d) => d.dimension as string); +} + +async function countContexts(): Promise { + const out = await superpositionClient.send( + new ListContextsCommand({ + workspace_id: IMPORT_WORKSPACE, + org_id: ENV.org_id, + count: 100, + page: 1, + }), + ); + return (out.data ?? []).length; +} + +beforeAll(async () => { + // Dedicated workspace so `replace` strategy imports can't affect other suites. + try { + await superpositionClient.send( + new CreateWorkspaceCommand({ + org_id: ENV.org_id, + workspace_admin_email: "admin@example.com", + workspace_name: IMPORT_WORKSPACE, + workspace_status: WorkspaceStatus.ENABLED, + allow_experiment_self_approval: true, + auto_populate_control: false, + enable_context_validation: true, + enable_change_reason_validation: false, + }), + ); + console.log(`Created import test workspace: ${IMPORT_WORKSPACE}`); + } catch (e: any) { + // Already exists from a previous run — fine, reuse it. + console.log(`Reusing import test workspace: ${e?.message ?? ""}`); + } + + await superpositionClient.send( + new MigrateWorkspaceSchemaCommand({ + org_id: ENV.org_id, + workspace_name: IMPORT_WORKSPACE, + }), + ); +}); + +describe("Config import - JSON", () => { + test("upsert import creates dimensions, default-configs and contexts", async () => { + const { status, summary } = await importConfig( + "json", + buildJsonConfig({ includeFlag: true }), + ); + + expect(status).toBe(200); + expect(summary).toBeDefined(); + expect(summary!.strategy).toBe("upsert"); + expect(summary!.dry_run).toBe(false); + expect(summary!.config_version).toBeDefined(); + expect(summary!.dimensions.created).toBeGreaterThanOrEqual(2); + expect(summary!.default_configs.created).toBeGreaterThanOrEqual(2); + expect(summary!.contexts.created).toBeGreaterThanOrEqual(1); + + const dims = await listDimensionNames(); + expect(dims).toContain(TIER); + expect(dims).toContain(REGION); + + const keys = await listDefaultConfigKeys(); + expect(keys).toContain(RATE); + expect(keys).toContain(FLAG); + + expect(await countContexts()).toBeGreaterThanOrEqual(1); + }); + + test("re-importing the same file updates instead of creating", async () => { + const { status, summary } = await importConfig( + "json", + buildJsonConfig({ includeFlag: true }), + ); + + expect(status).toBe(200); + expect(summary!.default_configs.created).toBe(0); + expect(summary!.default_configs.updated).toBeGreaterThanOrEqual(2); + expect(summary!.dimensions.updated).toBeGreaterThanOrEqual(2); + }); + + test("create_only skips entities that already exist", async () => { + const { status, summary } = await importConfig( + "json", + buildJsonConfig({ includeFlag: true }), + { strategy: ImportStrategy.CREATE_ONLY }, + ); + + expect(status).toBe(200); + expect(summary!.default_configs.created).toBe(0); + expect(summary!.default_configs.updated).toBe(0); + expect(summary!.default_configs.skipped).toBeGreaterThanOrEqual(2); + expect(summary!.dimensions.skipped).toBeGreaterThanOrEqual(2); + }); + + test("upsert replaces object default-config values wholesale", async () => { + const body = JSON.stringify({ + "default-configs": { + [FLAG]: { + value: { mode: "b", nested: { y: 2 } }, + schema: { type: "object" }, + }, + }, + dimensions: {}, + overrides: [], + }); + + const { status, summary } = await importConfig("json", body); + + expect(status).toBe(200); + expect(summary!.default_configs.updated).toBeGreaterThanOrEqual(1); + + const value = await getDefaultConfigValue(FLAG); + expect(value).toEqual({ + mode: "b", + nested: { y: 2 }, + }); + }); + + test("dry-run reports changes without persisting", async () => { + const body = JSON.stringify({ + "default-configs": { + [DRYRUN_KEY]: { value: 1, schema: { type: "number" } }, + }, + dimensions: {}, + overrides: [], + }); + + const { status, summary } = await importConfig("json", body, { + dry_run: true, + }); + + expect(status).toBe(200); + expect(summary!.dry_run).toBe(true); + expect(summary!.default_configs.created).toBeGreaterThanOrEqual(1); + // nothing committed, so no config version and the key must not exist + expect(summary!.config_version).toBeUndefined(); + + const keys = await listDefaultConfigKeys(); + expect(keys).not.toContain(DRYRUN_KEY); + }); + + test("replace strategy deletes entities absent from the file", async () => { + // Drop FLAG from the document; replace strategy should remove it. + const { status, summary } = await importConfig( + "json", + buildJsonConfig({ includeFlag: false }), + { strategy: ImportStrategy.REPLACE }, + ); + + expect(status).toBe(200); + expect(summary!.strategy).toBe("replace"); + expect(summary!.default_configs.deleted).toBeGreaterThanOrEqual(1); + + const keys = await listDefaultConfigKeys(); + expect(keys).toContain(RATE); + expect(keys).not.toContain(FLAG); + }); + + test("invalid body is rejected with a 4xx", async () => { + const { status } = await importConfig("json", "{ not valid json "); + expect(status).toBeGreaterThanOrEqual(400); + expect(status).toBeLessThan(500); + }); + + test("context referencing an undeclared dimension is rejected", async () => { + const body = JSON.stringify({ + "default-configs": { + [RATE]: { value: 10, schema: { type: "number" } }, + }, + dimensions: {}, + overrides: [{ _context_: { nonexistent_dim: "x" }, [RATE]: 20 }], + }); + const { status } = await importConfig("json", body); + expect(status).toBeGreaterThanOrEqual(400); + expect(status).toBeLessThan(500); + }); + + test("export via SDK can be re-imported (round-trip)", async () => { + const exported = await superpositionClient.send( + new GetConfigJsonCommand({ + workspace_id: IMPORT_WORKSPACE, + org_id: ENV.org_id, + }), + ); + expect(exported.json_config).toBeDefined(); + + const { status, summary } = await importConfig( + "json", + exported.json_config as string, + ); + expect(status).toBe(200); + // a faithful round-trip changes nothing new + expect(summary!.default_configs.created).toBe(0); + expect(summary!.dimensions.created).toBe(0); + }); +}); + +describe("Config import - TOML", () => { + test("upsert import via the TOML endpoint", async () => { + const toml = [ + "[default-configs]", + `${TOML_KEY} = { value = 5, schema = { type = "number" } }`, + "", + "[dimensions]", + `${TIER} = { position = 1, schema = { type = "string", enum = ["gold", "silver"] } }`, + "", + "[[overrides]]", + `_context_ = { ${TIER} = "silver" }`, + `${TOML_KEY} = 7`, + "", + ].join("\n"); + + const { status, summary } = await importConfig("toml", toml); + + expect(status).toBe(200); + expect(summary!.default_configs.created).toBeGreaterThanOrEqual(1); + + const keys = await listDefaultConfigKeys(); + expect(keys).toContain(TOML_KEY); + }); +});