diff --git a/.devcontainer/openapi.yaml b/.devcontainer/openapi.yaml new file mode 100644 index 0000000..0fce42f --- /dev/null +++ b/.devcontainer/openapi.yaml @@ -0,0 +1,521 @@ +openapi: 3.0.0 +info: + title: Import API + version: 2.1.0 + description: The Medallia Import API enables import of data to Medallia + Experience Cloud through a Data Import or Custom Import. + termsOfService: https://medallia.com/about/legal/terms/api +servers: + - url: __BackendUrl__ +paths: + /nps/inbound/v1/{importer-name}: + post: + summary: Import information to send NPS invitation via Medallia + tags: + - Import + description: Performs a data import + operationId: importDataV1 + parameters: + - $ref: "#/components/parameters/feed-name" + - $ref: "#/components/parameters/req-id" + - $ref: "#/components/parameters/accept-header" + - $ref: "#/components/parameters/content-type-header" + requestBody: + description: The data to be imported + required: true + content: + application/json: + schema: + oneOf: + - type: object + additionalProperties: + type: string + example: + transaction_date: 2021-03-23T20:15:37.000Z + email: mary@example.medallia.com + - type: array + items: + type: object + additionalProperties: + type: string + example: + transaction_date: 2021-03-23T20:15:37.000Z + email: mary@example.medallia.com + responses: + "200": + description: The response to a data import that is processed synchronously + headers: + X-RateLimit-Limit-day: + $ref: "#/components/headers/X-RateLimit-Limit-day" + X-RateLimit-Remaining-day: + $ref: "#/components/headers/X-RateLimit-Remaining-day" + X-RateLimit-Limit-second: + $ref: "#/components/headers/X-RateLimit-Limit-second" + X-RateLimit-Remaining-second: + $ref: "#/components/headers/X-RateLimit-Remaining-second" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Reset: + $ref: "#/components/headers/X-RateLimit-Reset" + X-Medallia-Rpc-Request-Id: + $ref: "#/components/headers/X-Medallia-Rpc-Request-Id" + content: + application/json: + schema: + type: object + required: + - records + - duplicates + - rejects + properties: + records: + type: integer + description: The number of records processed + duplicates: + type: integer + description: + The number of records found to match an existing record in the + system + rejects: + type: integer + description: The number of records that could not be processed for some reason + "202": + description: The response to a data import that is processed asynchronously + headers: + X-RateLimit-Limit-day: + $ref: "#/components/headers/X-RateLimit-Limit-day" + X-RateLimit-Remaining-day: + $ref: "#/components/headers/X-RateLimit-Remaining-day" + X-RateLimit-Limit-second: + $ref: "#/components/headers/X-RateLimit-Limit-second" + X-RateLimit-Remaining-second: + $ref: "#/components/headers/X-RateLimit-Remaining-second" + X-RateLimit-Limit: + $ref: "#/components/headers/X-RateLimit-Limit" + X-RateLimit-Remaining: + $ref: "#/components/headers/X-RateLimit-Remaining" + X-RateLimit-Reset: + $ref: "#/components/headers/X-RateLimit-Reset" + X-Medallia-Rpc-Request-Id: + $ref: "#/components/headers/X-Medallia-Rpc-Request-Id" + "401": + description: Unauthorized + "403": + description: Forbidden + "412": + description: Precondition failed + "500": + description: Internal server error + security: + - mecOauth: [] +components: + parameters: + req-id: + name: reqID + in: header + required: true + description: Request ID (GUID format) + schema: + type: string + format: uuid + example: 123e4567-e89b-12d3-a456-426614174000 + + accept-header: + name: Accept + in: header + required: false + schema: + type: string + example: application/json + + content-type-header: + name: Content-Type + in: header + required: true + schema: + type: string + example: application/json + + feed-name: + name: importer-name + in: path + description: The name of the importer + required: true + schema: + type: string + format: identifier + feed-file-id: + name: id + in: path + description: + The identifier which represents an asynchronous data import known + as a feed file + required: true + schema: + type: string + format: uuid + headers: + X-RateLimit-Limit-day: + description: The number of requests allowed in total in a 24-hour period. + Deprecated as of Sept. 2022 in favor of X-RateLimit-Limit. + schema: + type: integer + example: 10000 + deprecated: true + X-RateLimit-Remaining-day: + description: The number of requests available to make in a 24-hour period. + Deprecated as of Sept. 2022 in favor of X-RateLimit-Remaining. + schema: + type: integer + example: 10000 + deprecated: true + X-RateLimit-Limit-second: + description: The number of requests allowed in total in a 1-second period. + Deprecated as of Sept. 2022 in favor of X-RateLimit-Limit. + schema: + type: integer + example: 10 + deprecated: true + X-RateLimit-Remaining-second: + description: + The number of requests available to make in a 1-second period. + Deprecated as of Sept. 2022 in favor of X-RateLimit-Remaining. + schema: + type: integer + example: 10 + deprecated: true + X-RateLimit-Limit: + description: + Indicates the request quota closest to reaching its limit amongst + all available quotas. Follows the semantics of [IETF + draft-polli-ratelimit-headers](https://datatracker.ietf.org/doc/html/draft-polli-ratelimit-headers). + schema: + type: string + example: 10, 10;w=1, 100;w=86400 + X-RateLimit-Remaining: + description: + Indicates the number of requests remaining in the quota that is + closest to expiring (as indicated by `X-RateLimit-Limit`). Follows the + semantics of [IETF + draft-polli-ratelimit-headers](https://datatracker.ietf.org/doc/html/draft-polli-ratelimit-headers). + schema: + type: integer + example: 10 + X-RateLimit-Reset: + description: Indicates the number of seconds remaining until the quota + resets. Follows the semantics of [IETF + draft-polli-ratelimit-headers](https://datatracker.ietf.org/doc/html/draft-polli-ratelimit-headers). + schema: + type: integer + example: 10 + X-Medallia-Rpc-Request-Id: + description: The request ID, used by Medallia support for troubleshooting + schema: + type: string + format: identifier + example: 94615edd-d9d0-4921-8b3e-60615b3d3431 + schemas: + TruncatedOpenAPIObject: + type: object + description: + A truncated Open API object to list the basic information of all + available Import API endpoints + required: + - openapi + - info + - servers + - paths + properties: + openapi: + type: string + info: + type: object + properties: + title: + description: Hardcoded to Import API + type: string + version: + type: string + servers: + type: array + items: + type: object + properties: + url: + type: string + format: url + paths: + type: object + additionalProperties: + description: Describes an available Import API path to post data to + type: object + additionalProperties: + description: Displays the available methods to interact with that API path + type: object + properties: + summary: + type: string + description: + type: string + example: + openapi: 3.0.1 + info: + title: Import API + version: 1.0.0 + servers: + - url: https://example.apis.qa.den.medallia.com + paths: + /inbound/v2/async/active_b2b_onboarding_invitation: + post: + summary: active_b2b_onboarding_invitation + description: + This web feed is for the client to send Medallia invitation data + for the B2B Onboarding program ON ACTIVE. + /inbound/v2/async/active_b2b_org: + post: + summary: active_b2b_org + description: + This web feed is for the client to send Medallia org hierarchy data + for the B2B program WITH ACTIVE + AsyncIngestionResponse: + type: object + description: + Confirmation that the asynchronous file processing request has been + queued for processing. + required: + - feed_file_id + properties: + feed_file_id: + description: Feed file identifier. + type: string + format: uuid + FeedFileSummary: + description: Summary stats for a feed file + type: object + required: + - id + - status + properties: + id: + description: Feed file identifier. + type: string + format: uuid + status: + description: The current feed file status. + type: string + enum: + - QUEUED + - PROCESSING + - CANCELLED + - ERROR + - COMPLETED + - COMPLETED_PENDING_RESULTS + - COMPLETED_RESULTS_READY + - COMPLETED_RESULTS_UNAVAILABLE + stats: + description: Information about a processed feed file + type: object + required: + - creation_date + - processing_mode + properties: + records: + description: Aggregate record stats + type: object + required: + - input_count + properties: + input_count: + description: Total input record count + type: integer + success_count: + description: Success record count + type: integer + discarded_count: + description: Discarded record count + type: integer + duplicated_count: + description: Duplicate record count + type: integer + creation_date: + description: Job creation time (if available) + type: string + format: date-time + processing_start_date: + description: Processing start time (if available) + type: string + format: date-time + processing_end_date: + description: Processing end time (if available) + type: string + format: date-time + processing_mode: + description: The feed file processing mode used for the job + type: string + enum: + - PRETEND + - ACTIVE + processing_error_description: + description: Processing error description. If available + type: string + DetailedResults: + type: object + description: Detailed results of a processed FeedFile. + properties: + processing_results: + type: array + items: + $ref: "#/components/schemas/ProcessingResult" + input_to_field_mappings: + type: object + description: The fields that the input columns are being mapped to. + additionalProperties: + type: array + items: + type: string + processing_error_description: + description: Processing error description. If available + type: string + ProcessingResult: + type: object + description: Processing results of an entity type specified in the importer schema + required: + - entity_name + properties: + entity_name: + type: string + description: Type of entity imported such as record, unit, account, etc. + example: record + successful_results: + type: array + items: + $ref: "#/components/schemas/SuccessfulProcessedEntryResult" + discarded_results: + type: array + items: + $ref: "#/components/schemas/DiscardedResult" + SuccessfulProcessedEntryResult: + type: object + description: The result of a successfully processed entry. + required: + - input_position + - result + - has_duplicate_record_info + - field_values + properties: + input_position: + type: integer + description: The position of the record in the input array of the import request. + example: 1 + result: + type: string + description: The summary result. + example: Selected for sampling + has_duplicate_record_info: + type: boolean + description: + Denotes if an entity is updated and at least one field on the + entity contains duplicate information provided in the import request + field_values: + type: object + additionalProperties: + $ref: "#/components/schemas/FieldValue" + FieldValue: + type: object + description: The detailed changes for a field value set or updated from the input + required: + - current_content + - value_modified + - dup_checking + properties: + modified_content: + type: string + description: + When a field value is updated, this field contains the content that + was modified before the import processing + current_content: + type: string + description: The current value of the field after the import processing + value_modified: + type: boolean + description: True if the value was modified + dup_checking: + type: boolean + description: True if the field is being used as part of a unique index + DiscardedResult: + type: object + description: The result of a discarded entry + required: + - input_position + - input_values + - issues + properties: + input_position: + type: integer + description: The position of the record in the input array of the import request + example: 1 + input_values: + type: object + description: The raw input of the values that were provided in the import request + additionalProperties: + type: string + example: + transaction_date: 2021-03-23T20:15:37.000Z + email: mary@example.medallia.com + issues: + type: array + items: + $ref: "#/components/schemas/DiscardedResultIssue" + DiscardedResultIssue: + type: object + required: + - consequence + - explanation + - detail + - cause + properties: + consequence: + type: string + description: The consequence of the issue. + example: Record discarded + explanation: + type: string + description: The explanation of the issue. + example: missing required value + detail: + type: string + description: The details of the issue. + example: First name (e_firstname) + cause: + type: string + description: The description of the issue cause. + example: Issues associated with e_firstname ('First name') + ErrorMessage: + type: object + description: The representation of an error message. + required: + - error_type + - message + properties: + error_type: + type: string + description: The type of the error. + example: not_found + message: + type: string + description: The error message. + example: There is no resource named test_webfeed + securitySchemes: + mecOauth: + type: oauth2 + flows: + clientCredentials: + tokenUrl: https://{reporting-hostname}/oauth/{company}/token + scopes: {} +security: + - mecOauth: [] +x-readme: + explorer-enabled: true + proxy-enabled: true diff --git a/.devcontainer/test.json b/.devcontainer/test.json new file mode 100644 index 0000000..70bb866 --- /dev/null +++ b/.devcontainer/test.json @@ -0,0 +1,2021 @@ +{ + "doc": { + "metadata": { + "image": [], + "title": "", + "description": "", + "keywords": "", + "robots": "index" + }, + "mdx": { + "altBody": "", + "status": "rdmd" + }, + "api": { + "method": "get", + "url": "", + "auth": "required", + "params": [], + "results": { + "codes": [] + }, + "apiSetting": "606079f404910e006b487151" + }, + "swagger": { + "path": "/inbound/v2" + }, + "next": { + "description": "", + "pages": [] + }, + "algolia": { + "recordCount": 1, + "publishPending": false, + "updatedAt": "2023-08-30T23:27:01.982Z", + "translationFailure": false + }, + "_id": "64efd045b261ab002c4e19cb", + "title": "/inbound/v2", + "icon": "", + "updates": [], + "type": "endpoint", + "slug": "introspectimportapicollection", + "excerpt": "Discover all available endpoints to import data. This endpoint is available in Medallia Experience Cloud 2023 Fall Release as a generally available beta feature.", + "body": "", + "order": 1, + "isReference": true, + "deprecated": false, + "hidden": false, + "sync_unique": "introspectImportAPICollection", + "link_url": "", + "link_external": false, + "previousSlug": "", + "slugUpdatedAt": "2023-08-30T17:42:10.220Z", + "revision": 4, + "category": { + "_id": "606079f404910e006b487152", + "title": "Import API", + "slug": "import-api", + "order": 3, + "reference": true, + "isAPI": true, + "project": "605a62398adaf20036f463e2", + "version": "605a62398adaf20036f463e7", + "createdAt": "2021-03-28T12:43:32.798Z", + "__v": 0, + "type": "reference", + "id": "606079f404910e006b487152" + }, + "createdAt": "2023-08-30T23:27:01.595Z", + "updatedAt": "2023-09-07T18:46:56.279Z", + "parentDoc": "606079f504910e006b487153", + "version": { + "pdfStatus": "", + "source": "readme", + "_id": "605a62398adaf20036f463e7", + "version": "1.0", + "version_clean": "1.0.0", + "codename": "", + "is_stable": true, + "is_beta": false, + "is_hidden": false, + "is_deprecated": false, + "categories": [ + "605a62398adaf20036f463e9", + "605a62398adaf20036f463e9", + "605a62398adaf20036f463ef", + "605a66b397c5c9006ea78e92", + "605a68f3b644df004fef979a", + "605ba8d8b23e2a001eee304c", + "605cd3ad3c37a806fd858cc8", + "605cdcde70dbfb008072812d", + "605d00a433b2ea00584e0680", + "605d089d8d65c70052b738f6", + "605d08f38cc13000656ad9d5", + "605d09234ad5cb007209a2bb", + "605d11833c37a806fd862c2a", + "605d12d2fa689d00129bd3bc", + "605d1654c9d73300372af955", + "605d17ae97bea9000f9ee924", + "605d1a7293b77f061ac03e0f", + "605d1b24f71357082f97c002", + "605e00543da3fc011fb53eb0", + "605ecdeef30a6a0010911efc", + "605ece42860505002af4c21d", + "605ece63b7fa0d0016fc9da4", + "605ece70b5c297004b10f38f", + "605ece8589f06e007f923c11", + "605ece8f4572f20023eec7a4", + "605eced5b7fa0d0016fc9dca", + "605ecef97043150010429237", + "605ecf1389f06e007f923d0b", + "605ecf841b06fc0071ecc3fe", + "60605e01bb2c3700726af1df", + "60605e47d3c0a2007b1342d1", + "60605ea052b8c60022733120", + "60605fc68c49be006dcc1cb8", + "6060607fbcedfd005c6930d0", + "6060609cb1e5d9005065a952", + "606079f404910e006b487152", + "6060c63466ca38005b245158", + "60665e166c7cd6005afd3e9c", + "606e410ca6183400104a9334", + "606e41397960f200713177c9", + "606e416b276543004f31323b", + "6070a8250147d400165ed8b0", + "607686f4f10c4d005f1618d7", + "607a4cdbb74bf60045865a8d", + "60b91cb5249727005f6a28f4", + "61035d713f913a0017b10c35", + "6349830cf58963081e4e625e", + "63d9a0497f835300035ed44a" + ], + "project": "605a62398adaf20036f463e2", + "releaseDate": "2021-03-23T21:48:41.543Z", + "createdAt": "2021-03-23T21:48:41.543Z", + "__v": 1, + "apiRegistries": [] + }, + "project": "605a62398adaf20036f463e2", + "__v": 1, + "lastUpdatedHash": "b2ca9c9599bdbff0934a2c2a7b08a8958713c50a", + "reusableContent": [], + "isApi": true, + "tutorials": [], + "id": "64efd045b261ab002c4e19cb" + }, + "hideTOC": false, + "loginUrl": "https://dash.readme.com/auth/login/medallia?redirect=%2Fgo%2Fmedallia-enterprise-group%3Fredirect%3Dmedallia-apis", + "meta": { + "_id": "64efd045b261ab002c4e19cb", + "description": "Discover all available endpoints to import data. This endpoint is available in Medallia Experience Cloud 2023 Fall Release as a generally available beta feature.", + "hidden": false, + "image": [], + "keywords": "", + "metaTitle": "/inbound/v2", + "parent": "606079f504910e006b487153", + "robots": "index", + "slug": "introspectimportapicollection", + "title": "/inbound/v2", + "type": "reference" + }, + "oasDefinition": { + "openapi": "3.0.0", + "info": { + "title": "Import API", + "version": "2.1.0", + "description": "The Medallia Import API enables import of data to Medallia Experience Cloud through a Data Import or Custom Import.", + "termsOfService": "https://medallia.com/about/legal/terms/api" + }, + "servers": [ + { + "url": "https://{api-gateway-hostname}", + "variables": { + "api-gateway-hostname": { + "default": "instance-tenant.apis.medallia.com", + "description": "The Medallia Experience Cloud hostname used to access Medallia Reporting" + } + } + } + ], + "paths": { + "/inbound/v1/{importer-name}": { + "post": { + "tags": [ + "Import" + ], + "description": "Performs a data import", + "operationId": "importDataV1", + "parameters": [ + { + "$ref": "#/components/parameters/feed-name" + } + ], + "requestBody": { + "description": "The data to be imported", + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "transaction_date": "2021-03-23T20:15:37.000Z", + "email": "mary@example.medallia.com" + } + }, + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "transaction_date": "2021-03-23T20:15:37.000Z", + "email": "mary@example.medallia.com" + } + } + } + ] + } + }, + "text/csv": { + "schema": { + "type": "string" + } + }, + "text/xml": { + "schema": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "transaction_date": "2021-03-23T20:15:37.000Z", + "email": "mary@example.medallia.com" + } + }, + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "transaction_date": "2021-03-23T20:15:37.000Z", + "email": "mary@example.medallia.com" + } + } + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "The response to a data import that is processed synchronously", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + }, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "records", + "duplicates", + "rejects" + ], + "properties": { + "records": { + "type": "integer", + "description": "The number of records processed" + }, + "duplicates": { + "type": "integer", + "description": "The number of records found to match an existing record in the system" + }, + "rejects": { + "type": "integer", + "description": "The number of records that could not be processed for some reason" + } + } + } + } + } + }, + "202": { + "description": "The response to a data import that is processed asynchronously", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "412": { + "description": "Precondition failed" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "mecOauth": [] + } + ] + } + }, + "/inbound/v2": { + "get": { + "tags": [ + "Import" + ], + "description": "Discover all available endpoints to import data. This endpoint is available in Medallia Experience Cloud 2023 Fall Release as a generally available beta feature.", + "operationId": "introspectImportAPICollection", + "responses": { + "200": { + "description": "Lists all the available endpoints", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TruncatedOpenAPIObject" + } + }, + "application/yaml": { + "schema": { + "$ref": "#/components/schemas/TruncatedOpenAPIObject" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Object not found", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + } + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "mecOauth": [] + } + ] + } + }, + "/inbound/v2/{importer-name}": { + "get": { + "tags": [ + "Import" + ], + "description": "Inspect an importer to uncover input data requirements. This endpoint is available in Medallia Experience Cloud 2023 Fall Release as a generally available beta feature.", + "operationId": "introspectImportAPIEndpoint", + "parameters": [ + { + "$ref": "#/components/parameters/feed-name" + } + ], + "responses": { + "200": { + "description": "Retrieve the full Open API 3.0.1 spec of an endpoint", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + }, + "content": { + "application/json": { + "schema": { + "description": "Content matches OpenAPI Object as per https://swagger.io/specification/" + } + }, + "application/yaml": { + "schema": { + "description": "Content matches OpenAPI Object as per https://swagger.io/specification/" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Object not found", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + } + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "mecOauth": [] + } + ] + } + }, + "/inbound/v2/async/{importer-name}": { + "post": { + "tags": [ + "Import" + ], + "description": "Performs an asynchronous data import, returning the ID of the feed file created so processing status can be queried later. This endpoint is only available in Medallia Experience Cloud 2023 Summer Release as an opt-in feature. Reach out to your Medallia contact to enable this API feature as part of the Early Adopter Program.", + "operationId": "importDataV2", + "parameters": [ + { + "$ref": "#/components/parameters/feed-name" + } + ], + "requestBody": { + "description": "The data to be imported", + "required": true, + "content": { + "application/json": { + "schema": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "transaction_date": "2021-03-23T20:15:37.000Z", + "email": "mary@example.medallia.com" + } + }, + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "transaction_date": "2021-03-23T20:15:37.000Z", + "email": "mary@example.medallia.com" + } + } + } + ] + } + }, + "text/csv": { + "schema": { + "type": "string" + } + }, + "text/xml": { + "schema": { + "oneOf": [ + { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "transaction_date": "2021-03-23T20:15:37.000Z", + "email": "mary@example.medallia.com" + } + }, + { + "type": "array", + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "example": { + "transaction_date": "2021-03-23T20:15:37.000Z", + "email": "mary@example.medallia.com" + } + } + } + ] + } + } + } + }, + "responses": { + "202": { + "description": "The response to a data import that is processed asynchronously", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AsyncIngestionResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Object not found", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + }, + "405": { + "description": "The feed resource configuration is not compatible with this API resource. Please change the configuration of your feed resource.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + }, + "412": { + "description": "Precondition failed" + }, + "413": { + "description": "Payload too large" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "mecOauth": [] + } + ] + } + }, + "/inbound/v2/feedfiles/{id}": { + "parameters": [ + { + "$ref": "#/components/parameters/feed-file-id" + } + ], + "get": { + "tags": [ + "Feed File" + ], + "description": "Provides the summary statistics of a feed file import. This endpoint is only available in Medallia Experience Cloud 2023 Summer Release as an opt-in feature. Reach out to your Medallia contact to enable this API feature as part of the Early Adopter Program.", + "operationId": "getSummaryFeedFileResults", + "responses": { + "200": { + "description": "Successfully obtained the feed file statistics", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/FeedFileSummary" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Object not found.", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "mecOauth": [] + } + ] + } + }, + "/inbound/v2/feedfiles/{id}/detailed-results": { + "parameters": [ + { + "$ref": "#/components/parameters/feed-file-id" + } + ], + "get": { + "description": "Provides the detailed processing results of a Feed File. This endpoint is only available in Medallia Experience Cloud 2023 Summer Release as an opt-in feature. Reach out to your Medallia contact to enable this API feature as part of the Early Adopter Program.", + "tags": [ + "Feed File" + ], + "operationId": "getDetailedFeedFileResults", + "responses": { + "200": { + "description": "Successfully obtained the detailed results.", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DetailedResults" + } + } + } + }, + "400": { + "description": "Failed to obtain the detailed results.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Object not found.", + "headers": { + "X-RateLimit-Limit-day": { + "$ref": "#/components/headers/X-RateLimit-Limit-day" + }, + "X-RateLimit-Remaining-day": { + "$ref": "#/components/headers/X-RateLimit-Remaining-day" + }, + "X-RateLimit-Limit-second": { + "$ref": "#/components/headers/X-RateLimit-Limit-second" + }, + "X-RateLimit-Remaining-second": { + "$ref": "#/components/headers/X-RateLimit-Remaining-second" + }, + "X-RateLimit-Limit": { + "$ref": "#/components/headers/X-RateLimit-Limit" + }, + "X-RateLimit-Remaining": { + "$ref": "#/components/headers/X-RateLimit-Remaining" + }, + "X-RateLimit-Reset": { + "$ref": "#/components/headers/X-RateLimit-Reset" + }, + "X-Medallia-Rpc-Request-Id": { + "$ref": "#/components/headers/X-Medallia-Rpc-Request-Id" + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorMessage" + } + } + } + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "mecOauth": [] + } + ] + } + } + }, + "components": { + "parameters": { + "feed-name": { + "name": "importer-name", + "in": "path", + "description": "The name of the importer", + "required": true, + "schema": { + "type": "string", + "format": "identifier" + } + }, + "feed-file-id": { + "name": "id", + "in": "path", + "description": "The identifier which represents an asynchronous data import known as a feed file", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + }, + "headers": { + "X-RateLimit-Limit-day": { + "description": "The number of requests allowed in total in a 24-hour period. Deprecated as of Sept. 2022 in favor of X-RateLimit-Limit.", + "schema": { + "type": "integer" + }, + "example": 10000, + "deprecated": true + }, + "X-RateLimit-Remaining-day": { + "description": "The number of requests available to make in a 24-hour period. Deprecated as of Sept. 2022 in favor of X-RateLimit-Remaining.", + "schema": { + "type": "integer" + }, + "example": 10000, + "deprecated": true + }, + "X-RateLimit-Limit-second": { + "description": "The number of requests allowed in total in a 1-second period. Deprecated as of Sept. 2022 in favor of X-RateLimit-Limit.", + "schema": { + "type": "integer" + }, + "example": 10, + "deprecated": true + }, + "X-RateLimit-Remaining-second": { + "description": "The number of requests available to make in a 1-second period. Deprecated as of Sept. 2022 in favor of X-RateLimit-Remaining.", + "schema": { + "type": "integer" + }, + "example": 10, + "deprecated": true + }, + "X-RateLimit-Limit": { + "description": "Indicates the request quota closest to reaching its limit amongst all available quotas. Follows the semantics of [IETF draft-polli-ratelimit-headers](https://datatracker.ietf.org/doc/html/draft-polli-ratelimit-headers).", + "schema": { + "type": "string" + }, + "example": "10, 10;w=1, 100;w=86400" + }, + "X-RateLimit-Remaining": { + "description": "Indicates the number of requests remaining in the quota that is closest to expiring (as indicated by `X-RateLimit-Limit`). Follows the semantics of [IETF draft-polli-ratelimit-headers](https://datatracker.ietf.org/doc/html/draft-polli-ratelimit-headers).", + "schema": { + "type": "integer" + }, + "example": 10 + }, + "X-RateLimit-Reset": { + "description": "Indicates the number of seconds remaining until the quota resets. Follows the semantics of [IETF draft-polli-ratelimit-headers](https://datatracker.ietf.org/doc/html/draft-polli-ratelimit-headers).", + "schema": { + "type": "integer" + }, + "example": 10 + }, + "X-Medallia-Rpc-Request-Id": { + "description": "The request ID, used by Medallia support for troubleshooting", + "schema": { + "type": "string", + "format": "identifier" + }, + "example": "94615edd-d9d0-4921-8b3e-60615b3d3431" + } + }, + "schemas": { + "TruncatedOpenAPIObject": { + "type": "object", + "description": "A truncated Open API object to list the basic information of all available Import API endpoints", + "required": [ + "openapi", + "info", + "servers", + "paths" + ], + "properties": { + "openapi": { + "type": "string" + }, + "info": { + "type": "object", + "properties": { + "title": { + "description": "Hardcoded to Import API", + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "servers": { + "type": "array", + "items": { + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "url" + } + } + } + }, + "paths": { + "type": "object", + "additionalProperties": { + "description": "Describes an available Import API path to post data to", + "type": "object", + "additionalProperties": { + "description": "Displays the available methods to interact with that API path", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + } + } + }, + "example": { + "openapi": "3.0.1", + "info": { + "title": "Import API", + "version": "1.0.0" + }, + "servers": [ + { + "url": "https://example.apis.qa.den.medallia.com" + } + ], + "paths": { + "/inbound/v2/async/active_b2b_onboarding_invitation": { + "post": { + "summary": "active_b2b_onboarding_invitation", + "description": "This web feed is for the client to send Medallia invitation data for the B2B Onboarding program ON ACTIVE." + } + }, + "/inbound/v2/async/active_b2b_org": { + "post": { + "summary": "active_b2b_org", + "description": "This web feed is for the client to send Medallia org hierarchy data for the B2B program WITH ACTIVE" + } + } + } + } + }, + "AsyncIngestionResponse": { + "type": "object", + "description": "Confirmation that the asynchronous file processing request has been queued for processing.", + "required": [ + "feed_file_id" + ], + "properties": { + "feed_file_id": { + "description": "Feed file identifier.", + "type": "string", + "format": "uuid" + } + } + }, + "FeedFileSummary": { + "description": "Summary stats for a feed file", + "type": "object", + "required": [ + "id", + "status" + ], + "properties": { + "id": { + "description": "Feed file identifier.", + "type": "string", + "format": "uuid" + }, + "status": { + "description": "The current feed file status.", + "type": "string", + "enum": [ + "QUEUED", + "PROCESSING", + "CANCELLED", + "ERROR", + "COMPLETED", + "COMPLETED_PENDING_RESULTS", + "COMPLETED_RESULTS_READY", + "COMPLETED_RESULTS_UNAVAILABLE" + ] + }, + "stats": { + "description": "Information about a processed feed file", + "type": "object", + "required": [ + "creation_date", + "processing_mode" + ], + "properties": { + "records": { + "description": "Aggregate record stats", + "type": "object", + "required": [ + "input_count" + ], + "properties": { + "input_count": { + "description": "Total input record count", + "type": "integer" + }, + "success_count": { + "description": "Success record count", + "type": "integer" + }, + "discarded_count": { + "description": "Discarded record count", + "type": "integer" + }, + "duplicated_count": { + "description": "Duplicate record count", + "type": "integer" + } + } + }, + "creation_date": { + "description": "Job creation time (if available)", + "type": "string", + "format": "date-time" + }, + "processing_start_date": { + "description": "Processing start time (if available)", + "type": "string", + "format": "date-time" + }, + "processing_end_date": { + "description": "Processing end time (if available)", + "type": "string", + "format": "date-time" + }, + "processing_mode": { + "description": "The feed file processing mode used for the job", + "type": "string", + "enum": [ + "PRETEND", + "ACTIVE" + ] + }, + "processing_error_description": { + "description": "Processing error description. If available", + "type": "string" + } + } + } + } + }, + "DetailedResults": { + "type": "object", + "description": "Detailed results of a processed FeedFile.", + "properties": { + "processing_results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProcessingResult" + } + }, + "input_to_field_mappings": { + "type": "object", + "description": "The fields that the input columns are being mapped to.", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "processing_error_description": { + "description": "Processing error description. If available", + "type": "string" + } + } + }, + "ProcessingResult": { + "type": "object", + "description": "Processing results of an entity type specified in the importer schema", + "required": [ + "entity_name" + ], + "properties": { + "entity_name": { + "type": "string", + "description": "Type of entity imported such as record, unit, account, etc.", + "example": "record" + }, + "successful_results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SuccessfulProcessedEntryResult" + } + }, + "discarded_results": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DiscardedResult" + } + } + } + }, + "SuccessfulProcessedEntryResult": { + "type": "object", + "description": "The result of a successfully processed entry.", + "required": [ + "input_position", + "result", + "has_duplicate_record_info", + "field_values" + ], + "properties": { + "input_position": { + "type": "integer", + "description": "The position of the record in the input array of the import request.", + "example": 1 + }, + "result": { + "type": "string", + "description": "The summary result.", + "example": "Selected for sampling" + }, + "has_duplicate_record_info": { + "type": "boolean", + "description": "Denotes if an entity is updated and at least one field on the entity contains duplicate information provided in the import request" + }, + "field_values": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/FieldValue" + } + } + } + }, + "FieldValue": { + "type": "object", + "description": "The detailed changes for a field value set or updated from the input", + "required": [ + "current_content", + "value_modified", + "dup_checking" + ], + "properties": { + "modified_content": { + "type": "string", + "description": "When a field value is updated, this field contains the content that was modified before the import processing" + }, + "current_content": { + "type": "string", + "description": "The current value of the field after the import processing" + }, + "value_modified": { + "type": "boolean", + "description": "True if the value was modified" + }, + "dup_checking": { + "type": "boolean", + "description": "True if the field is being used as part of a unique index" + } + } + }, + "DiscardedResult": { + "type": "object", + "description": "The result of a discarded entry", + "required": [ + "input_position", + "input_values", + "issues" + ], + "properties": { + "input_position": { + "type": "integer", + "description": "The position of the record in the input array of the import request", + "example": 1 + }, + "input_values": { + "type": "object", + "description": "The raw input of the values that were provided in the import request", + "additionalProperties": { + "type": "string" + }, + "example": { + "transaction_date": "2021-03-23T20:15:37.000Z", + "email": "mary@example.medallia.com" + } + }, + "issues": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DiscardedResultIssue" + } + } + } + }, + "DiscardedResultIssue": { + "type": "object", + "required": [ + "consequence", + "explanation", + "detail", + "cause" + ], + "properties": { + "consequence": { + "type": "string", + "description": "The consequence of the issue.", + "example": "Record discarded" + }, + "explanation": { + "type": "string", + "description": "The explanation of the issue.", + "example": "missing required value" + }, + "detail": { + "type": "string", + "description": "The details of the issue.", + "example": "First name (e_firstname)" + }, + "cause": { + "type": "string", + "description": "The description of the issue cause.", + "example": "Issues associated with e_firstname ('First name')" + } + } + }, + "ErrorMessage": { + "type": "object", + "description": "The representation of an error message.", + "required": [ + "error_type", + "message" + ], + "properties": { + "error_type": { + "type": "string", + "description": "The type of the error.", + "example": "not_found" + }, + "message": { + "type": "string", + "description": "The error message.", + "example": "There is no resource named test_webfeed" + } + } + } + }, + "securitySchemes": { + "mecOauth": { + "type": "oauth2", + "flows": { + "clientCredentials": { + "tokenUrl": "https://{reporting-hostname}/oauth/{company}/token", + "scopes": {} + } + } + } + } + }, + "security": [ + { + "mecOauth": [] + } + ], + "x-readme": { + "explorer-enabled": true, + "proxy-enabled": true + }, + "_id": "606079f404910e006b487151" + }, + "oasPublicUrl": "@medallia-apis/v1.0#ks50we18llyd9wpj", + "oauth": false, + "rdmd": { + "baseUrl": "/medallia-apis", + "body": "", + "dehydrated": { + "body": "", + "toc": "" + }, + "mdx": false, + "opts": { + "alwaysThrow": false, + "compatibilityMode": false, + "copyButtons": true, + "correctnewlines": false, + "markdownOptions": { + "fences": true, + "commonmark": true, + "gfm": true, + "ruleSpaces": false, + "listItemIndent": "1", + "spacedTable": true, + "paddedTable": true + }, + "normalize": true, + "lazyImages": true, + "reusableContent": { + "tags": {} + }, + "safeMode": false, + "settings": { + "position": true + }, + "theme": "light", + "opts": { + "customBlocks": {} + }, + "baseUrl": "/medallia-apis", + "terms": [ + { + "_id": "605d1ba8a1f1650022a8959a", + "term": "Social media", + "definition": "solicits and manages feedback from social media sites." + }, + { + "_id": "605d1c0d4c96680031bc6119", + "term": "CX", + "definition": "Customer Experience" + }, + { + "_id": "605d1c1853bbff004e2f5bdc", + "term": "EX", + "definition": "Employee Experience" + }, + { + "_id": "605d1c1e8a6ded005d981bb8", + "term": "OCEM", + "definition": "Operational Customer Experience Management" + }, + { + "_id": "605d1c29a4bacc00408a20d6", + "term": "VoC", + "definition": "Voice of the Customer" + }, + { + "_id": "605d1c2f01db370016cdbf5a", + "term": "VoE", + "definition": "Voice of the Employee" + }, + { + "_id": "605d1c3501db370016cdbf76", + "term": "NPS", + "definition": "Net Promoter Score" + }, + { + "_id": "605d1c3cc4e7de0046d23f93", + "term": "ML", + "definition": "Machine Learning" + }, + { + "_id": "605d1c45a71c32004b0c6729", + "term": "NLP", + "definition": "Natural Language Processing" + }, + { + "_id": "605d1c4ca51ed10060c6da37", + "term": "OSAT", + "definition": "Overall Satisfaction" + }, + { + "_id": "605d1c57101c39000f61b984", + "term": "CSAT", + "definition": "Customer Satisfaction" + }, + { + "_id": "605d1c5f437273000fe1cb34", + "term": "BPP", + "definition": "Best Practice Packages: pre-configured MEC programs for specific verticals/lines of business" + }, + { + "_id": "605d1c67b0fb8f0079fc0b29", + "term": "QA", + "definition": "Quality Assurance: as it pertains to contact centers, monitoring customer interactions to identify issues, training opportunities, standardize coaching, improve CX" + }, + { + "_id": "605d1c6da71c32004b0c69e5", + "term": "TA", + "definition": "Text Analytics: Medallia's data analytics engine to uncover trends and sentiment from unstructured feedback" + }, + { + "_id": "605d1c735c3e8b0052702188", + "term": "MEC", + "definition": "Medallia Experience Cloud" + }, + { + "_id": "64147b431378390026dd9754", + "term": "Feedback fields", + "definition": "Also known as Q-fields. Represent information gathered from the survey taker, or information about individuals providing feedback. They can also be the information used to dynamically control a survey while the survey taker is taking the survey." + }, + { + "_id": "64147c6e2b7e5e0d45f7a276", + "term": "System fields", + "definition": "Also known as A-fields. Generated by Medallia Experience Cloud, they contain information about an event or the survey taker's relationship with the survey itself, such as the timestamp when respondents completed the survey or when they opened the invitation email. Some System fields also record and calculate specific, internal information from different Medallia products" + }, + { + "_id": "64147c88c988510052de9bb4", + "term": "Formula fields", + "definition": "Also known as K-fields. Build calculations within each feedback response. K-fields can operate on data stored in Q-fields, E-fields, A-fields, other K-fields, organizational hierarchies, and Lookup tables. For example, a company may have three different programs with different Likelihood to Recommend (LTR) questions for each program and wants to see these three questions rolled together into a single LTR metric in Reports and Exports. This can be accomplished by creating a K-field that returns the appropriate LTR Q-field for each survey program. This K-field can then be added to Reporting and Exports." + }, + { + "_id": "64147c92fd03e010c157dfb6", + "term": "Metadata fields", + "definition": "Also known as M-fields. Exist temporarily while a survey is being taken and are then discarded. They represent information (metadata) about the survey taker that is taking the survey, such as the kind of device they are using, and about the actions they have taken, such as the starting time and the number of pages submitted so far." + }, + { + "_id": "64147c9c3dc959005f30477f", + "term": "Event fields", + "definition": "Also known as E-fields. Describe either the actual contact episode or event or track various aspects of the feedback record as it moves through Experience Cloud." + }, + { + "_id": "64147ca5698db21254345ef7", + "term": "Importing", + "definition": "The process of collecting external data and bringing it into Medallia Experience Cloud." + }, + { + "_id": "64147cc5eae86c0073ba0553", + "term": "R-fields", + "definition": "Calculate aggregate scores across feedback records. R-fields look at all feedback records (unless otherwise indicated in the calculation) instead of looking at individual ones." + }, + { + "_id": "64147ce53d57940073403d52", + "term": "Exporting", + "definition": "The process of collecting Medallia Experience Cloud data and either sending it to external systems or making it available for download." + }, + { + "_id": "64147d00fd03e010c157e5a4", + "term": "Inviting", + "definition": "The process of creating an invitation for a respondent to take a survey. The process begins soon after Sampling identifies respondents to receive invitations." + }, + { + "_id": "64147f33d0f3f70c85b399a3", + "term": "Unit group", + "definition": "Classifies a unit's place in the company; examples include sales district, territory, line of business, geographical region, time zone, or managerial level. Users can filter and analyze feedback data along these organizational lines (for example, to find the month-over-month average scores for stores of a particular region)." + }, + { + "_id": "64147fb960e45f0c819f1347", + "term": "System report", + "definition": "Previously known as Standard report. These are minimally customizable reports and report modules intended to be used as-is." + }, + { + "_id": "64148049fca7ff0c34a22529", + "term": "User", + "definition": "Provides access to Medallia Experience Cloud, and controls what data users can see. Every person that logs in to Experience Cloud needs their own personal user account. Every user account belongs to one or more roles that define feature and administrative permissions for a set of users." + }, + { + "_id": "6414809889986e08fd28a9e9", + "term": "Respondent", + "definition": "Alternative term form of customer; use this when for situations where the person providing feedback is not necessarily a customer of the company" + }, + { + "_id": "641480d989986e08fd28aee8", + "term": "Package", + "definition": "Also known as App. A set of configuration entities that you can add to Medallia Experience Cloud, reducing the time required to launch a program or feature." + }, + { + "_id": "6414810d39a4380c21f095d7", + "term": "Alerts", + "definition": "Follow-up workflows associated with responses when the respondent's answers meet some condition, such as giving a low score or complaining about an over-due order. These actionable events start the alert workflow, which then proceeds through one or more states until the issue is resolved. Once the issue is closed, companies can use the Closed Loop Feedback system to capture additional information and improve the customer experience." + }, + { + "_id": "6414815c4c8006003421d66d", + "term": "Org sync", + "definition": "Maps a Medallia Experience Cloud program to a company organizational structure and hierarchies (updating with ongoing changes) to define permissions by role and individual." + }, + { + "_id": "64148177c8aca1005323f787", + "term": "Company", + "definition": "A Medallia customer who has users accessing Medallia Experience Cloud." + }, + { + "_id": "641481a7fb040c0dca0e7089", + "term": "Custom modules", + "definition": "Also known as Custom reports. They define charts or tables using AA2, an XML-based framework created by Medallia." + }, + { + "_id": "6414820e9976730d77b3c46b", + "term": "Roles", + "definition": "A set of permissions defining access to reports and administrative abilities in Medallia Experience Cloud. Roles do not determine access to Experience Cloud itself or to data, both of which are controlled by Users. \nAdministrative permissions control a role's administrative access to Experience Cloud. Reporting permissions control which reports a role can see in Medallia Web and Mobile reports and determine what users with that role can do in those reports." + }, + { + "_id": "6414824c6a665f0d1ee70d2f", + "term": "Permission", + "definition": "Also known as Capability. A privilege assigned to a role that allows users of that role to view or do a particular action." + }, + { + "_id": "641482e05ec60214e9c5bac9", + "term": "Sampling", + "definition": "The process of filtering raw invitations to identify respondents who will be sent invitations to personalized surveys. Sampling occurs after an importer collects the raw invitation data, and before a program causes the invitations to be sent." + }, + { + "_id": "641482ece5bcb80d0373da4d", + "term": "Social media", + "definition": "Solicits and manages feedback from social media sites." + }, + { + "_id": "6414830201f9110e35140fe6", + "term": "User accounts", + "definition": "See Users." + }, + { + "_id": "641483702bbcc2004a1d7bd7", + "term": "Administrator", + "definition": "A user with administrative permissions who can access Medallia Admin Suite or Setup." + }, + { + "_id": "6414839db3e46300663516eb", + "term": "Customer", + "definition": "A customer of the company; a person who fills out a survey and interacts with a Unit." + }, + { + "_id": "641483aada9d5c00739761c3", + "term": "Unit", + "definition": "The part of a company with which individuals interact; examples include front-line representatives, call center agents, employees, physical locations (stores, hotels), or websites. Units are the lowest common node in the organization." + }, + { + "_id": "641483f975db220c730a1550", + "term": "Instance", + "definition": "A specific (company) deployment. Users typically access a single instance, though a company may have multiple instances on Medallia Experience Cloud." + }, + { + "_id": "6414846164981c0b94e1b04d", + "term": "Text Analytics", + "definition": "The process of deriving meaningful insights from unstructured text feedback. This feedback is an essential resource for discovering and prioritizing potential improvements to the customer experience." + } + ], + "variables": { + "user": {}, + "defaults": [ + { + "source": "server", + "_id": "62e95b5748eed800139198ec", + "name": "conversations-api-gateway-hostname", + "default": "conversations.sc4.medallia.com", + "apiSetting": "61035cfaf45dafcfc5752d1a", + "type": "" + }, + { + "source": "server", + "_id": "62e95b5748eed800139198eb", + "name": "adapter-api-gateway-hostname", + "default": "adapter-gateway.example.com", + "apiSetting": "61035cfaf45dafcfc5752d1a", + "type": "" + }, + { + "source": "security", + "_id": "62e95b5748eed800139198ea", + "name": "conversationsOauth", + "type": "oauth2", + "apiSetting": "61035cfaf45dafcfc5752d1a" + }, + { + "source": "server", + "_id": "67069881074b3000388f9652", + "name": "api-gateway-hostname", + "default": "instance-tenant.apis.medallia.com", + "apiSetting": "605ecf841b06fc0071ecc3fd", + "type": "" + }, + { + "source": "security", + "_id": "67069881074b3000388f9651", + "name": "mecOauth", + "type": "oauth2", + "apiSetting": "605ecf841b06fc0071ecc3fd" + } + ] + } + }, + "terms": [ + { + "_id": "605d1ba8a1f1650022a8959a", + "term": "Social media", + "definition": "solicits and manages feedback from social media sites." + }, + { + "_id": "605d1c0d4c96680031bc6119", + "term": "CX", + "definition": "Customer Experience" + }, + { + "_id": "605d1c1853bbff004e2f5bdc", + "term": "EX", + "definition": "Employee Experience" + }, + { + "_id": "605d1c1e8a6ded005d981bb8", + "term": "OCEM", + "definition": "Operational Customer Experience Management" + }, + { + "_id": "605d1c29a4bacc00408a20d6", + "term": "VoC", + "definition": "Voice of the Customer" + }, + { + "_id": "605d1c2f01db370016cdbf5a", + "term": "VoE", + "definition": "Voice of the Employee" + }, + { + "_id": "605d1c3501db370016cdbf76", + "term": "NPS", + "definition": "Net Promoter Score" + }, + { + "_id": "605d1c3cc4e7de0046d23f93", + "term": "ML", + "definition": "Machine Learning" + }, + { + "_id": "605d1c45a71c32004b0c6729", + "term": "NLP", + "definition": "Natural Language Processing" + }, + { + "_id": "605d1c4ca51ed10060c6da37", + "term": "OSAT", + "definition": "Overall Satisfaction" + }, + { + "_id": "605d1c57101c39000f61b984", + "term": "CSAT", + "definition": "Customer Satisfaction" + }, + { + "_id": "605d1c5f437273000fe1cb34", + "term": "BPP", + "definition": "Best Practice Packages: pre-configured MEC programs for specific verticals/lines of business" + }, + { + "_id": "605d1c67b0fb8f0079fc0b29", + "term": "QA", + "definition": "Quality Assurance: as it pertains to contact centers, monitoring customer interactions to identify issues, training opportunities, standardize coaching, improve CX" + }, + { + "_id": "605d1c6da71c32004b0c69e5", + "term": "TA", + "definition": "Text Analytics: Medallia's data analytics engine to uncover trends and sentiment from unstructured feedback" + }, + { + "_id": "605d1c735c3e8b0052702188", + "term": "MEC", + "definition": "Medallia Experience Cloud" + }, + { + "_id": "64147b431378390026dd9754", + "term": "Feedback fields", + "definition": "Also known as Q-fields. Represent information gathered from the survey taker, or information about individuals providing feedback. They can also be the information used to dynamically control a survey while the survey taker is taking the survey." + }, + { + "_id": "64147c6e2b7e5e0d45f7a276", + "term": "System fields", + "definition": "Also known as A-fields. Generated by Medallia Experience Cloud, they contain information about an event or the survey taker's relationship with the survey itself, such as the timestamp when respondents completed the survey or when they opened the invitation email. Some System fields also record and calculate specific, internal information from different Medallia products" + }, + { + "_id": "64147c88c988510052de9bb4", + "term": "Formula fields", + "definition": "Also known as K-fields. Build calculations within each feedback response. K-fields can operate on data stored in Q-fields, E-fields, A-fields, other K-fields, organizational hierarchies, and Lookup tables. For example, a company may have three different programs with different Likelihood to Recommend (LTR) questions for each program and wants to see these three questions rolled together into a single LTR metric in Reports and Exports. This can be accomplished by creating a K-field that returns the appropriate LTR Q-field for each survey program. This K-field can then be added to Reporting and Exports." + }, + { + "_id": "64147c92fd03e010c157dfb6", + "term": "Metadata fields", + "definition": "Also known as M-fields. Exist temporarily while a survey is being taken and are then discarded. They represent information (metadata) about the survey taker that is taking the survey, such as the kind of device they are using, and about the actions they have taken, such as the starting time and the number of pages submitted so far." + }, + { + "_id": "64147c9c3dc959005f30477f", + "term": "Event fields", + "definition": "Also known as E-fields. Describe either the actual contact episode or event or track various aspects of the feedback record as it moves through Experience Cloud." + }, + { + "_id": "64147ca5698db21254345ef7", + "term": "Importing", + "definition": "The process of collecting external data and bringing it into Medallia Experience Cloud." + }, + { + "_id": "64147cc5eae86c0073ba0553", + "term": "R-fields", + "definition": "Calculate aggregate scores across feedback records. R-fields look at all feedback records (unless otherwise indicated in the calculation) instead of looking at individual ones." + }, + { + "_id": "64147ce53d57940073403d52", + "term": "Exporting", + "definition": "The process of collecting Medallia Experience Cloud data and either sending it to external systems or making it available for download." + }, + { + "_id": "64147d00fd03e010c157e5a4", + "term": "Inviting", + "definition": "The process of creating an invitation for a respondent to take a survey. The process begins soon after Sampling identifies respondents to receive invitations." + }, + { + "_id": "64147f33d0f3f70c85b399a3", + "term": "Unit group", + "definition": "Classifies a unit's place in the company; examples include sales district, territory, line of business, geographical region, time zone, or managerial level. Users can filter and analyze feedback data along these organizational lines (for example, to find the month-over-month average scores for stores of a particular region)." + }, + { + "_id": "64147fb960e45f0c819f1347", + "term": "System report", + "definition": "Previously known as Standard report. These are minimally customizable reports and report modules intended to be used as-is." + }, + { + "_id": "64148049fca7ff0c34a22529", + "term": "User", + "definition": "Provides access to Medallia Experience Cloud, and controls what data users can see. Every person that logs in to Experience Cloud needs their own personal user account. Every user account belongs to one or more roles that define feature and administrative permissions for a set of users." + }, + { + "_id": "6414809889986e08fd28a9e9", + "term": "Respondent", + "definition": "Alternative term form of customer; use this when for situations where the person providing feedback is not necessarily a customer of the company" + }, + { + "_id": "641480d989986e08fd28aee8", + "term": "Package", + "definition": "Also known as App. A set of configuration entities that you can add to Medallia Experience Cloud, reducing the time required to launch a program or feature." + }, + { + "_id": "6414810d39a4380c21f095d7", + "term": "Alerts", + "definition": "Follow-up workflows associated with responses when the respondent's answers meet some condition, such as giving a low score or complaining about an over-due order. These actionable events start the alert workflow, which then proceeds through one or more states until the issue is resolved. Once the issue is closed, companies can use the Closed Loop Feedback system to capture additional information and improve the customer experience." + }, + { + "_id": "6414815c4c8006003421d66d", + "term": "Org sync", + "definition": "Maps a Medallia Experience Cloud program to a company organizational structure and hierarchies (updating with ongoing changes) to define permissions by role and individual." + }, + { + "_id": "64148177c8aca1005323f787", + "term": "Company", + "definition": "A Medallia customer who has users accessing Medallia Experience Cloud." + }, + { + "_id": "641481a7fb040c0dca0e7089", + "term": "Custom modules", + "definition": "Also known as Custom reports. They define charts or tables using AA2, an XML-based framework created by Medallia." + }, + { + "_id": "6414820e9976730d77b3c46b", + "term": "Roles", + "definition": "A set of permissions defining access to reports and administrative abilities in Medallia Experience Cloud. Roles do not determine access to Experience Cloud itself or to data, both of which are controlled by Users. \nAdministrative permissions control a role's administrative access to Experience Cloud. Reporting permissions control which reports a role can see in Medallia Web and Mobile reports and determine what users with that role can do in those reports." + }, + { + "_id": "6414824c6a665f0d1ee70d2f", + "term": "Permission", + "definition": "Also known as Capability. A privilege assigned to a role that allows users of that role to view or do a particular action." + }, + { + "_id": "641482e05ec60214e9c5bac9", + "term": "Sampling", + "definition": "The process of filtering raw invitations to identify respondents who will be sent invitations to personalized surveys. Sampling occurs after an importer collects the raw invitation data, and before a program causes the invitations to be sent." + }, + { + "_id": "641482ece5bcb80d0373da4d", + "term": "Social media", + "definition": "Solicits and manages feedback from social media sites." + }, + { + "_id": "6414830201f9110e35140fe6", + "term": "User accounts", + "definition": "See Users." + }, + { + "_id": "641483702bbcc2004a1d7bd7", + "term": "Administrator", + "definition": "A user with administrative permissions who can access Medallia Admin Suite or Setup." + }, + { + "_id": "6414839db3e46300663516eb", + "term": "Customer", + "definition": "A customer of the company; a person who fills out a survey and interacts with a Unit." + }, + { + "_id": "641483aada9d5c00739761c3", + "term": "Unit", + "definition": "The part of a company with which individuals interact; examples include front-line representatives, call center agents, employees, physical locations (stores, hotels), or websites. Units are the lowest common node in the organization." + }, + { + "_id": "641483f975db220c730a1550", + "term": "Instance", + "definition": "A specific (company) deployment. Users typically access a single instance, though a company may have multiple instances on Medallia Experience Cloud." + }, + { + "_id": "6414846164981c0b94e1b04d", + "term": "Text Analytics", + "definition": "The process of deriving meaningful insights from unstructured text feedback. This feedback is an essential resource for discovering and prioritizing potential improvements to the customer experience." + } + ], + "variables": { + "user": {}, + "defaults": [ + { + "source": "server", + "_id": "62e95b5748eed800139198ec", + "name": "conversations-api-gateway-hostname", + "default": "conversations.sc4.medallia.com", + "apiSetting": "61035cfaf45dafcfc5752d1a", + "type": "" + }, + { + "source": "server", + "_id": "62e95b5748eed800139198eb", + "name": "adapter-api-gateway-hostname", + "default": "adapter-gateway.example.com", + "apiSetting": "61035cfaf45dafcfc5752d1a", + "type": "" + }, + { + "source": "security", + "_id": "62e95b5748eed800139198ea", + "name": "conversationsOauth", + "type": "oauth2", + "apiSetting": "61035cfaf45dafcfc5752d1a" + }, + { + "source": "server", + "_id": "67069881074b3000388f9652", + "name": "api-gateway-hostname", + "default": "instance-tenant.apis.medallia.com", + "apiSetting": "605ecf841b06fc0071ecc3fd", + "type": "" + }, + { + "source": "security", + "_id": "67069881074b3000388f9651", + "name": "mecOauth", + "type": "oauth2", + "apiSetting": "605ecf841b06fc0071ecc3fd" + } + ] + } + }, + "suggestedEdits": true +} \ No newline at end of file diff --git a/.devcontainer/test2.json b/.devcontainer/test2.json new file mode 100644 index 0000000..e69de29 diff --git a/.gitignore b/.gitignore index 875a118..8f9dc25 100644 --- a/.gitignore +++ b/.gitignore @@ -418,3 +418,5 @@ FodyWeavers.xsd *.msix *.msm *.msp + +.idea/ \ No newline at end of file diff --git a/DbApiBuilderEntityGenerator.sln b/DbApiBuilderEntityGenerator.sln index 35622e8..27b430c 100644 --- a/DbApiBuilderEntityGenerator.sln +++ b/DbApiBuilderEntityGenerator.sln @@ -9,6 +9,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbApiBuilderEntityGenerator EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbApiBuilderEntityGenerator.Core", "src\DbApiBuilderEntityGenerator.Core\DbApiBuilderEntityGenerator.Core.csproj", "{DAC9B44B-0E75-4AD5-80CB-4C1E2063142E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DbApiBuilderEntityGenerator.Core.Tests", "tests\DbApiBuilderEntityGenerator.Core.Tests\DbApiBuilderEntityGenerator.Core.Tests.csproj", "{1E6A2D39-C87E-457D-9733-C95A43256055}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,6 +47,18 @@ Global {DAC9B44B-0E75-4AD5-80CB-4C1E2063142E}.Release|x64.Build.0 = Release|Any CPU {DAC9B44B-0E75-4AD5-80CB-4C1E2063142E}.Release|x86.ActiveCfg = Release|Any CPU {DAC9B44B-0E75-4AD5-80CB-4C1E2063142E}.Release|x86.Build.0 = Release|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Debug|x64.ActiveCfg = Debug|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Debug|x64.Build.0 = Debug|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Debug|x86.ActiveCfg = Debug|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Debug|x86.Build.0 = Debug|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Release|Any CPU.Build.0 = Release|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Release|x64.ActiveCfg = Release|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Release|x64.Build.0 = Release|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Release|x86.ActiveCfg = Release|Any CPU + {1E6A2D39-C87E-457D-9733-C95A43256055}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -50,5 +66,6 @@ Global GlobalSection(NestedProjects) = preSolution {033FBC15-D1D8-4ACB-9D7D-4D0C7803345C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} {DAC9B44B-0E75-4AD5-80CB-4C1E2063142E} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {1E6A2D39-C87E-457D-9733-C95A43256055} = {0AB3BF05-4346-4AA6-1389-037BE0695223} EndGlobalSection EndGlobal diff --git a/src/DbApiBuilderEntityGenerator.Core/Class1.cs b/src/DbApiBuilderEntityGenerator.Core/Class1.cs deleted file mode 100644 index 2ea0c13..0000000 --- a/src/DbApiBuilderEntityGenerator.Core/Class1.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace DbApiBuilderEntityGenerator.Core; - -public class Class1 -{ - -} diff --git a/src/DbApiBuilderEntityGenerator.Core/ConfigurationSerializer.cs b/src/DbApiBuilderEntityGenerator.Core/ConfigurationSerializer.cs new file mode 100644 index 0000000..c27a73b --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/ConfigurationSerializer.cs @@ -0,0 +1,132 @@ +using System; +using DbApiBuilderEntityGenerator.Core.Serialization; +using Microsoft.Extensions.Logging; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace DbApiBuilderEntityGenerator.Core; + +/// +/// Serialization and Deserialization for the class +/// +public class ConfigurationSerializer : IConfigurationSerializer +{ + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + public ConfigurationSerializer(ILogger logger) + { + _logger = logger; + } + + /// + /// The options file name. Default 'generation.yml' + /// + public const string OptionsFileName = "generation.yml"; + + /// + /// Loads the options file using the specified and . + /// + /// The directory where the file is located. + /// The name of the options file. + /// An instance of if the file exists; otherwise null. + public GeneratorModel? Load(string? directory = null, string file = OptionsFileName) + { + var path = GetPath(directory, file); + if (!File.Exists(path)) + { + _logger.LogWarning("Option file not found: {file}", file); + return null; + } + + _logger.LogInformation("Loading options file: {file}", file); + using var reader = File.OpenText(path); + + return Load(reader); + } + + /// + /// Loads the options using the specified + /// + /// The reader. + /// + /// An instance of . + /// + public GeneratorModel? Load(TextReader reader) + { + if (reader == null) + return null; + + var deserializer = new DeserializerBuilder() + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + // use Serialization model for better yaml support + return deserializer.Deserialize(reader); + } + + /// + /// Saves the generator options to the specified and . + /// + /// The generator options to save. + /// The directory where the file is located. + /// The name of the options file. + /// The full path of the options file. + public string Save(GeneratorModel generatorOptions, string? directory = null, string file = OptionsFileName) + { + if (string.IsNullOrWhiteSpace(directory)) + directory = Environment.CurrentDirectory; + + if (string.IsNullOrWhiteSpace(file)) + file = OptionsFileName; + + if (!Directory.Exists(directory)) + { + _logger.LogTrace($"Creating Directory: {directory}"); + Directory.CreateDirectory(directory); + } + + _logger.LogInformation($"Saving options file: {file}"); + + var path = Path.Combine(directory, file); + + var serializer = new SerializerBuilder() + .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults) + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + using (var streamWriter = File.CreateText(path)) + serializer.Serialize(streamWriter, generatorOptions); + + return path; + } + + /// + /// Determines if the specified options file exists. + /// + /// The directory where the file is located. + /// The name of the options file. + /// true if options file exits; otherwise false. + public bool Exists(string? directory = null, string file = OptionsFileName) + { + var path = GetPath(directory, file); + return File.Exists(path); + } + + + private static string GetPath(string? directory, string? file) + { + if (string.IsNullOrWhiteSpace(directory)) + directory = Environment.CurrentDirectory; + + if (string.IsNullOrWhiteSpace(file)) + file = OptionsFileName; + + var path = Path.Combine(directory, file); + return path; + } + +} diff --git a/src/DbApiBuilderEntityGenerator.Core/DbApiBuilderEntityGenerator.Core.csproj b/src/DbApiBuilderEntityGenerator.Core/DbApiBuilderEntityGenerator.Core.csproj index 027ab9f..843930f 100644 --- a/src/DbApiBuilderEntityGenerator.Core/DbApiBuilderEntityGenerator.Core.csproj +++ b/src/DbApiBuilderEntityGenerator.Core/DbApiBuilderEntityGenerator.Core.csproj @@ -23,4 +23,7 @@ + + + diff --git a/src/DbApiBuilderEntityGenerator.Core/Extensions/StringExtensions.cs.cs b/src/DbApiBuilderEntityGenerator.Core/Extensions/StringExtensions.cs.cs new file mode 100644 index 0000000..4fa44a2 --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Extensions/StringExtensions.cs.cs @@ -0,0 +1,232 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.RegularExpressions; + +namespace DbApiBuilderEntityGenerator.Core.Extensions; + +public static partial class StringExtensions +{ + [GeneratedRegex("([A-Z][a-z]*)|([0-9]+)", RegexOptions.ExplicitCapture, matchTimeoutMilliseconds: 1000)] + private static partial Regex WordsExpression(); + + private static readonly Regex _splitNameRegex = new Regex(@"[\W_]+"); + + /// + /// Truncates the specified text. + /// + /// The text to truncate. + /// The number of characters to keep. + /// The ellipsis string to use when truncating. (Default ...) + /// + /// A truncate string. + /// + [return: NotNullIfNotNull(nameof(text))] + public static string? Truncate(this string? text, int keep, string ellipsis = "...") + { + if (string.IsNullOrEmpty(text)) + return text; + + if (text!.Length <= keep) + return text; + + ellipsis ??= string.Empty; + + if (text.Length <= keep + ellipsis.Length || keep < ellipsis.Length) + return text[..keep]; + + int prefix = keep - ellipsis.Length; + return string.Concat(text[..prefix], ellipsis); + } + + /// + /// Indicates whether the specified String object is null or an empty string + /// + /// A String reference + /// + /// if is null or empty; otherwise, . + /// + public static bool IsNullOrEmpty([NotNullWhen(false)] this string? item) + { + return string.IsNullOrEmpty(item); + } + + /// + /// Indicates whether a specified string is null, empty, or consists only of white-space characters + /// + /// A String reference + /// + /// if is null or empty; otherwise, . + /// + public static bool IsNullOrWhiteSpace([NotNullWhen(false)] this string? item) + { + if (item == null) + return true; + + for (int i = 0; i < item.Length; i++) + { + if (!char.IsWhiteSpace(item[i])) + return false; + } + + return true; + } + + /// + /// Determines whether the specified string is not . + /// + /// The value to check. + /// + /// if the specified is not ; otherwise, . + /// + public static bool HasValue([NotNullWhen(true)] this string? value) + { + return !string.IsNullOrEmpty(value); + } + + /// + /// Does string contain both uppercase and lowercase characters? + /// + /// The value. + /// True if contain mixed case. + public static bool IsMixedCase(this string s) + { + if (s.IsNullOrEmpty()) + return false; + + var containsUpper = s.Any(Char.IsUpper); + var containsLower = s.Any(Char.IsLower); + + return containsLower && containsUpper; + } + + /// + /// Converts a string to use camelCase. + /// + /// The value. + /// The to camel case. + public static string ToCamelCase(this string value) + { + if (string.IsNullOrEmpty(value)) + return value; + + string output = ToPascalCase(value); + if (output.Length > 2) + return char.ToLower(output[0]) + output[1..]; + + return output.ToLower(); + } + + /// + /// Converts a string to use PascalCase. + /// + /// Text to convert + /// The string + public static string ToPascalCase(this string value) + { + return value.ToPascalCase(_splitNameRegex); + } + + /// + /// Converts a string to use PascalCase. + /// + /// Text to convert + /// Regular Expression to split words on. + /// The string + public static string ToPascalCase(this string value, Regex splitRegex) + { + if (string.IsNullOrEmpty(value)) + return value; + + var mixedCase = value.IsMixedCase(); + var names = splitRegex.Split(value); + var output = new StringBuilder(); + + if (names.Length > 1) + { + foreach (string name in names) + { + if (name.Length > 1) + { + output.Append(char.ToUpper(name[0])); + output.Append(mixedCase ? name[1..] : name[1..].ToLower()); + } + else + { + output.Append(name.ToUpper()); + } + } + } + else if (value.Length > 1) + { + output.Append(char.ToUpper(value[0])); + output.Append(mixedCase ? value[1..] : value[1..].ToLower()); + } + else + { + output.Append(value.ToUpper()); + } + + return output.ToString(); + } + + /// + /// Combines two strings with the specified separator. + /// + /// The first string. + /// The second string. + /// The separator string. + /// A string combining the and parameters with the between them + [return: NotNullIfNotNull(nameof(first))] + [return: NotNullIfNotNull(nameof(second))] + public static string? Combine(this string? first, string? second, char separator = '/') + { + if (string.IsNullOrEmpty(first)) + return second; + + if (string.IsNullOrEmpty(second)) + return first; + + var firstEndsWith = first[^1] == separator; + var secondStartsWith = second[0] == separator; + + if (firstEndsWith && !secondStartsWith) + return string.Concat(first, second); + + if (!firstEndsWith && secondStartsWith) + return string.Concat(first, second); + + if (firstEndsWith && secondStartsWith) + return string.Concat(first, second[1..]); + + return $"{first}{separator}{second}"; + } + + /// + /// Converts a NameIdentifier and spaces it out into words "Name Identifier". + /// + /// The text value to convert. + /// The text converted + [return: NotNullIfNotNull(nameof(text))] + public static string? ToTitle(this string? text) + { + if (text.IsNullOrEmpty() || text.Length < 2) + return text; + + var words = WordsExpression().Matches(text); + + var wrote = false; + var builder = new DefaultInterpolatedStringHandler(literalLength: text.Length + 5, formattedCount: 1); + foreach (Match word in words) + { + if (wrote) + builder.AppendLiteral(" "); + + builder.AppendLiteral(word.Value); + wrote = true; + } + + return builder.ToStringAndClear(); + } +} diff --git a/src/DbApiBuilderEntityGenerator.Core/IConfigurationSerializer.cs b/src/DbApiBuilderEntityGenerator.Core/IConfigurationSerializer.cs new file mode 100644 index 0000000..3f01ffa --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/IConfigurationSerializer.cs @@ -0,0 +1,43 @@ +using System; + +namespace DbApiBuilderEntityGenerator.Core; + +/// +/// interface for serialization and deserialization of class +/// +public interface IConfigurationSerializer +{ + /// + /// Loads the options file using the specified and . + /// + /// The directory where the file is located. + /// The name of the options file. + /// An instance of if the file exists; otherwise null. + Serialization.GeneratorModel? Load(string? directory = null, string file = ConfigurationSerializer.OptionsFileName); + + /// + /// Loads the options using the specified + /// + /// The reader. + /// + /// An instance of . + /// + Serialization.GeneratorModel? Load(TextReader reader); + + /// + /// Saves the generator options to the specified and . + /// + /// The generator options to save. + /// The directory where the file is located. + /// The name of the options file. + /// The full path of the options file. + string Save(Serialization.GeneratorModel generatorOptions, string? directory = null, string file = ConfigurationSerializer.OptionsFileName); + + /// + /// Determines if the specified options file exists. + /// + /// The directory where the file is located. + /// The name of the options file. + /// true if options file exits; otherwise false. + bool Exists(string? directory = null, string file = ConfigurationSerializer.OptionsFileName); +} diff --git a/src/DbApiBuilderEntityGenerator.Core/IOptionVariable.cs b/src/DbApiBuilderEntityGenerator.Core/IOptionVariable.cs new file mode 100644 index 0000000..6acfa89 --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/IOptionVariable.cs @@ -0,0 +1,19 @@ +namespace DbApiBuilderEntityGenerator.Core; + +/// +/// Interface for option variables. +/// +public interface IOptionVariable +{ + /// + /// Sets variables on the specified VariableDictionary. + /// + /// The variable dictionary. + void Set(VariableDictionary variableDictionary); + + /// + /// Removes variables on the specified VariableDictionary. + /// + /// The variable dictionary. + void Remove(VariableDictionary variableDictionary); +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Options/DatabaseMatchOptions.cs b/src/DbApiBuilderEntityGenerator.Core/Options/DatabaseMatchOptions.cs new file mode 100644 index 0000000..cd47110 --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Options/DatabaseMatchOptions.cs @@ -0,0 +1,37 @@ +using System; + +namespace DbApiBuilderEntityGenerator.Core.Options; + +/// +/// Represents options for matching database tables and columns during a database operation. +/// +public class DatabaseMatchOptions : OptionsBase +{ + /// + /// Represents options for matching database tables and columns during a database operation. + /// + /// A dictionary of variables used to configure the matching options. Cannot be null. + /// An optional prefix used to filter or qualify the matching criteria. Can be null. + public DatabaseMatchOptions(VariableDictionary variables, string? prefix) + : base(variables, prefix) + { + Tables = []; + Columns = []; + } + + /// + /// Gets or sets a list of regular expression of tables to ignore. + /// + /// + /// The list of regular expression of tables to ignore. + /// + public List Tables { get; set; } + + /// + /// Gets or sets a list of regular expression of columns to ignore. + /// + /// + /// The list of regular expression of columns to ignore. + /// + public List Columns { get; set; } +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Options/DatabaseProviders.cs b/src/DbApiBuilderEntityGenerator.Core/Options/DatabaseProviders.cs new file mode 100644 index 0000000..cc68192 --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Options/DatabaseProviders.cs @@ -0,0 +1,33 @@ +namespace DbApiBuilderEntityGenerator.Core.Options; + +/// +/// The database to generate code for +/// +public enum DatabaseProviders +{ + /// + /// The SQL server provider + /// + SqlServer, + + /// + /// The PostgreSQL provider + /// + PostgreSQL, + + /// + /// The MySQL provider + /// + MySQL, + + /// + /// The sqlite provider + /// + Sqlite, + + /// + /// The Oracle provider + /// + Oracle + +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Options/EntityNaming.cs b/src/DbApiBuilderEntityGenerator.Core/Options/EntityNaming.cs new file mode 100644 index 0000000..cb47142 --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Options/EntityNaming.cs @@ -0,0 +1,25 @@ +using System; + +namespace DbApiBuilderEntityGenerator.Core.Options; + +/// +/// Entity class naming strategy +/// +public enum EntityNaming +{ + /// + /// Use table name as entity name + /// + Preserve = 0, + + /// + /// Use table name in plural form + /// + Plural = 1, + + /// + /// Use table name in singular form + /// + Singular = 2 + +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Options/GeneratorOptions.cs b/src/DbApiBuilderEntityGenerator.Core/Options/GeneratorOptions.cs new file mode 100644 index 0000000..92a3269 --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Options/GeneratorOptions.cs @@ -0,0 +1,126 @@ +using System.ComponentModel; +using YamlDotNet.Serialization; + +namespace DbApiBuilderEntityGenerator.Core.Options; + +/// +/// Top level generator configuration options +/// +public class GeneratorOptions : OptionsBase +{ + /// + /// Initializes a new instance of the class. + /// + public GeneratorOptions(VariableDictionary variables, + string? prefix) : base(variables, null) + { + Variables = variables; + Provider = DatabaseProviders.SqlServer; + Directory = @".\"; + Tables = []; + Schemas = []; + Exclude = new DatabaseMatchOptions(Variables, Prefix); + EntityNaming = EntityNaming.Singular; + RelationshipNaming = RelationshipNaming.Plural; + } + + [YamlIgnore] + public VariableDictionary Variables { get; } + + /// + /// Gets or sets the project directory. + /// + /// + /// The project directory. + /// + public string? Directory + { + get => GetProperty(); + set => SetProperty(value); + } + + /// + /// Gets or sets the database to generate code for. + /// + /// + /// The database to generate code for. + /// + [DefaultValue(DatabaseProviders.SqlServer)] + public DatabaseProviders Provider { get; set; } + + + /// + /// Gets or sets the connection string for reverse engineering the database + /// + /// + /// The connection string for reverse engineering the database + /// + public string? ConnectionString { get; set; } + + /// + /// Gets or sets the name of the connection in the user secret file. + /// + /// + /// The name of the connection. + /// + public string? ConnectionName { get; set; } + + /// + /// Gets or sets the user secrets identifier. A user secrets ID is unique value used to store and identify a collection of secret configuration values. + /// + /// + /// The user secrets identifier. + /// + public string? UserSecretsId { get; set; } + + /// + /// Gets or sets the tables to include in the model, or an empty enumerable to include all + /// + /// + /// The tables to include in the model, or an empty enumerable to include all + /// + public List Tables { get; } + + /// + /// Gets or sets the schema to include in the model, or an empty enumerable to include all. + /// + /// + /// The schema to include in the model, or an empty enumerable to include all. + /// + public List Schemas { get; } + + /// + /// Gets or sets the exclude table options. + /// + /// + /// The exclude table options. + /// + public DatabaseMatchOptions Exclude { get; } + + /// + /// Gets or sets the entity class naming strategy. + /// + /// + /// The entity class naming strategy. + /// + [DefaultValue(EntityNaming.Singular)] + public EntityNaming EntityNaming { get; set; } + + /// + /// Gets or sets the relationship property naming strategy. + /// + /// + /// The relationship property naming strategy. + /// + [DefaultValue(RelationshipNaming.Plural)] + public RelationshipNaming RelationshipNaming { get; set; } + + /// + /// Gets or sets the renaming expressions. + /// + /// + /// The renaming expressions. + /// + public SelectionOptions Renaming { get; } + +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Options/MatchOptions.cs b/src/DbApiBuilderEntityGenerator.Core/Options/MatchOptions.cs new file mode 100644 index 0000000..02dddf1 --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Options/MatchOptions.cs @@ -0,0 +1,66 @@ +using System; +using System.Text.RegularExpressions; +using DbApiBuilderEntityGenerator.Core.Extensions; +using YamlDotNet.Serialization; + +namespace DbApiBuilderEntityGenerator.Core.Options; + +/// +/// String match options +/// +public class MatchOptions : OptionsBase +{ + /// + /// Initializes a new instance of the class. + /// + /// The shared variables dictionary. + /// The variable key prefix. + public MatchOptions(VariableDictionary variables, string? prefix) + : base(variables, prefix) + { + } + + /// + /// Gets or sets the exact string match option. + /// + /// + /// The exact string match option. + /// + [YamlMember(Alias = "exact")] + public string? Exact + { + get => GetProperty(); + set => SetProperty(value); + } + + /// + /// Gets or sets the regular expression pattern match option. + /// + /// + /// The regular expression pattern match option. + /// + [YamlMember(Alias = "regex")] + public string? Expression + { + get => GetProperty(); + set => SetProperty(value); + } + + /// + /// Determines whether the specified value is a match. + /// + /// The value to match. + /// + /// true if the specified value is a match; otherwise, false. + /// + public bool IsMatch(string value) + { + if (Exact.HasValue()) + return string.Equals(value, Exact); + + if (Expression.HasValue()) + return Regex.IsMatch(value, Expression); + + return false; + } +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Options/OptionsBase.cs b/src/DbApiBuilderEntityGenerator.Core/Options/OptionsBase.cs new file mode 100644 index 0000000..07c9cad --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Options/OptionsBase.cs @@ -0,0 +1,50 @@ +using System; +using System.Runtime.CompilerServices; +using DbApiBuilderEntityGenerator.Core.Extensions; + +namespace DbApiBuilderEntityGenerator.Core.Options; + +public class OptionsBase +{ + + /// + /// Initializes a new instance of the class. + /// + /// The shared variables dictionary. + /// The variable key prefix. + public OptionsBase(VariableDictionary variables, string? prefix) + { + ArgumentNullException.ThrowIfNull(variables); + + } + public VariableDictionary Variables { get; } + + public string? Prefix { get; } + + protected string? GetProperty([CallerMemberName] string? propertyName = null) + { + var name = AppendPrefix(Prefix, propertyName); + if (name.IsNullOrWhiteSpace()) + return null; + + return Variables.Get(name); + } + + protected void SetProperty(string? value, [CallerMemberName] string? propertyName = null) + { + var name = AppendPrefix(Prefix, propertyName); + if (name.IsNullOrWhiteSpace()) + return; + + Variables.Set(name, value); + } + public static string? AppendPrefix(string? root, string? prefix) + { + if (prefix.IsNullOrWhiteSpace()) + return root; + + return root.HasValue() + ? $"{root}.{prefix}" + : prefix; + } +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Options/RelationshipNaming.cs b/src/DbApiBuilderEntityGenerator.Core/Options/RelationshipNaming.cs new file mode 100644 index 0000000..1b8fbdc --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Options/RelationshipNaming.cs @@ -0,0 +1,25 @@ +using System; + +namespace DbApiBuilderEntityGenerator.Core.Options; + +/// +/// Relationship property naming strategy +/// +public enum RelationshipNaming +{ + /// + /// Preserve property name as the entity name + /// + Preserve = 0, + + /// + /// Convert the property name to the entity plural name + /// + Plural = 1, + + /// + /// Add 'List' to the end of the entity name. + /// + Suffix = 2 + +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Options/SelectionOptions.cs b/src/DbApiBuilderEntityGenerator.Core/Options/SelectionOptions.cs new file mode 100644 index 0000000..eff102a --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Options/SelectionOptions.cs @@ -0,0 +1,36 @@ +using System; + +namespace DbApiBuilderEntityGenerator.Core.Options; + +/// +/// Selection options +/// +public class SelectionOptions : OptionsBase +{ + /// + /// Initializes a new instance of the class. + /// + public SelectionOptions(VariableDictionary variables, string? prefix) + : base(variables, prefix) + { + Entities = []; + Properties = []; + } + + /// + /// Gets or sets a list of regular expression of entities to select. + /// + /// + /// The list of regular expression of entities to select. + /// + public List Entities { get; } + + /// + /// Gets or sets a list of regular expression of properties to select. + /// + /// + /// The list of regular expression of properties to select. + /// + public List Properties { get; } + +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Serialization/DatabaseMatchModel.cs b/src/DbApiBuilderEntityGenerator.Core/Serialization/DatabaseMatchModel.cs new file mode 100644 index 0000000..66509c4 --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Serialization/DatabaseMatchModel.cs @@ -0,0 +1,26 @@ +using System; + +namespace DbApiBuilderEntityGenerator.Core.Serialization; + +/// +/// Represents a model that specifies patterns for tables and columns to be ignored during processing. +/// +public class DatabaseMatchModel +{ + /// + /// Gets or sets a list of regular expression of tables to ignore. + /// + /// + /// The list of regular expression of tables to ignore. + /// + public List? Tables { get; set; } + + /// + /// Gets or sets a list of regular expression of columns to ignore. + /// + /// + /// The list of regular expression of columns to ignore. + /// + public List? Columns { get; set; } + +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Serialization/GeneratorModel.cs b/src/DbApiBuilderEntityGenerator.Core/Serialization/GeneratorModel.cs new file mode 100644 index 0000000..7d110cf --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Serialization/GeneratorModel.cs @@ -0,0 +1,107 @@ +using System; +using System.ComponentModel; +using DbApiBuilderEntityGenerator.Core.Options; + +namespace DbApiBuilderEntityGenerator.Core.Serialization; + +/// +/// Generator Options +/// +public class GeneratorModel +{ + + public GeneratorModel() + { + RelationshipNaming = RelationshipNaming.Plural; + EntityNaming = EntityNaming.Singular; + } + + /// + /// Gets or sets the project directory. + /// + /// + /// The project directory. + /// + public string? OutputDirectory { get; set; } + + /// + /// Gets or sets the database to generate code for. + /// + /// + /// The database to generate code for. + /// + [DefaultValue(DatabaseProviders.SqlServer)] + public DatabaseProviders Provider { get; set; } = DatabaseProviders.SqlServer; + + /// + /// Gets or sets the connection string for reverse engineering the database + /// + /// + /// The connection string for reverse engineering the database + /// + public string? ConnectionString { get; set; } + + /// + /// Gets or sets the name of the connection in the user secret file. + /// + /// + /// The name of the connection. + /// + public string? ConnectionName { get; set; } + + /// + /// Gets or sets the user secrets identifier. A user secrets ID is unique value used to store and identify a collection of secret configuration values. + /// + /// + /// The user secrets identifier. + /// + public string? UserSecretsId { get; set; } + + /// + /// Gets or sets the tables to include in the model, or an empty enumerable to include all + /// + /// + /// The tables to include in the model, or an empty enumerable to include all + /// + public List? Tables { get; set; } + + /// + /// Gets or sets the schema to include in the model, or an empty enumerable to include all. + /// + /// + /// The schema to include in the model, or an empty enumerable to include all. + /// + public List? Schemas { get; set; } + + /// + /// Gets or sets the model that specifies which elements should be ignored during processing. + /// + public DatabaseMatchModel? Exclude { get; set; } + + /// + /// Gets or sets the entity class naming strategy. + /// + /// + /// The entity class naming strategy. + /// + [DefaultValue(EntityNaming.Singular)] + public EntityNaming EntityNaming { get; set; } + + /// + /// Gets or sets the relationship property naming strategy. + /// + /// + /// The relationship property naming strategy. + /// + [DefaultValue(RelationshipNaming.Plural)] + public RelationshipNaming RelationshipNaming { get; set; } + + /// + /// Gets or sets the renaming expressions. + /// + /// + /// The renaming expressions. + /// + public SelectionModel? Renaming { get; set; } +} + diff --git a/src/DbApiBuilderEntityGenerator.Core/Serialization/MatchModel.cs b/src/DbApiBuilderEntityGenerator.Core/Serialization/MatchModel.cs new file mode 100644 index 0000000..86c6746 --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Serialization/MatchModel.cs @@ -0,0 +1,44 @@ +using System; +using YamlDotNet.Serialization; + +namespace DbApiBuilderEntityGenerator.Core.Serialization; + +/// +/// Match options +/// +public class MatchModel +{ + /// + /// Gets or sets the exact string match option. + /// + /// + /// The exact string match option. + /// + [YamlMember(Alias = "exact")] + public string? Exact { get; set; } + + /// + /// Gets or sets the regular expression pattern match option. + /// + /// + /// The regular expression pattern match option. + /// + [YamlMember(Alias = "regex")] + public string? Expression { get; set; } + + /// + /// Performs an implicit conversion from to . + /// + /// The value to use for conversion. + /// + /// The result of the conversion. + /// + public static implicit operator MatchModel(string value) + { + return new MatchModel + { + Expression = value + }; + } + +} diff --git a/src/DbApiBuilderEntityGenerator.Core/Serialization/SelectionModel.cs b/src/DbApiBuilderEntityGenerator.Core/Serialization/SelectionModel.cs new file mode 100644 index 0000000..0565e61 --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/Serialization/SelectionModel.cs @@ -0,0 +1,27 @@ +using System; + +namespace DbApiBuilderEntityGenerator.Core.Serialization; + +/// +/// Selection options +/// +public class SelectionModel +{ + /// + /// Gets or sets a list of regular expression of entities to select. + /// + /// + /// The list of regular expression of entities to select. + /// + public List? Entities { get; set; } + + /// + /// Gets or sets a list of regular expression of properties to select. + /// + /// + /// The list of regular expression of properties to select. + /// + public List? Properties { get; set; } + + +} diff --git a/src/DbApiBuilderEntityGenerator.Core/VariableDictionary.cs b/src/DbApiBuilderEntityGenerator.Core/VariableDictionary.cs new file mode 100644 index 0000000..ff2505b --- /dev/null +++ b/src/DbApiBuilderEntityGenerator.Core/VariableDictionary.cs @@ -0,0 +1,213 @@ +using System; +using System.Text; + +namespace DbApiBuilderEntityGenerator.Core; + +/// +/// Variable substitution dictionary +/// +public class VariableDictionary +{ + private readonly Dictionary _variables = new(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets or sets a value indicating whether should evaluate variable expressions. + /// + /// + /// true if should evaluate; otherwise, false. + /// + public bool ShouldEvaluate { get; set; } = true; + + /// + /// Gets or sets a variable by name. + /// + /// The name of the variable to set. + /// The current (evaluated) value of the variable. + public string? this[string name] + { + get => Get(name); + set => Set(name, value); + } + + /// + /// Sets a variable value. + /// + /// The name of the variable. + /// The value of the variable. + public void Set(string name, string? value) + { + if (name == null) + return; + + _variables[name] = value; + } + + /// + /// Sets the specified option variable. + /// + /// The option variable. + public void Set(IOptionVariable optionVariable) + { + optionVariable.Set(this); + } + + /// + /// Gets the value of a variable, or returns null if the variable is not defined. If the variable contains an expression, it will be evaluated first. + /// + /// The name of the variable. + /// + /// The value of the variable, or null if the variable is not defined. + /// + public string? Get(string name) + { + if (!_variables.TryGetValue(name, out var variable)) + return null; + + + return ShouldEvaluate ? Evaluate(variable) : variable; + } + + /// + /// Removes the variable with specified name. + /// + /// The variable name. + public void Remove(string name) + { + if (name == null) + return; + + _variables.Remove(name); + } + + /// + /// Removes the specified option variable. + /// + /// The option variable. + public void Remove(IOptionVariable optionVariable) + { + optionVariable.Remove(this); + } + + /// + /// Evaluates the specified variable or text. + /// + /// The variable or text. + /// The result of the variable. + /// Invalid variable format + public string? Evaluate(string? variableOrText) + { + if (variableOrText == null) + return null; + + var loop = new HashSet(StringComparer.OrdinalIgnoreCase); + return Eval(variableOrText, loop); + } + + + private string? Eval(string? variableOrText, ISet loop) + { + if (variableOrText == null) + return null; + + var result = new StringBuilder(variableOrText.Length * 2); + var variable = new StringBuilder(); + var state = State.OutsideExpression; + + using (var reader = new StringReader(variableOrText)) + { + do + { + int c = -1; + switch (state) + { + case State.OutsideExpression: + c = reader.Read(); + switch (c) + { + case -1: + state = State.End; + break; + case '{': + state = State.OnOpenBracket; + break; + case '}': + state = State.OnCloseBracket; + break; + default: + result.Append((char)c); + break; + } + + break; + case State.OnOpenBracket: + c = reader.Read(); + switch (c) + { + case -1: + throw new FormatException(); + case '{': + result.Append('{'); + state = State.OutsideExpression; + break; + default: + variable.Append((char)c); + state = State.InsideExpression; + break; + } + + break; + case State.InsideExpression: + c = reader.Read(); + switch (c) + { + case -1: + throw new FormatException(); + case '}': + + var v = variable.ToString(); + if (loop.Add(v) && _variables.TryGetValue(v, out string? value)) + { + value = Eval(value, loop); + result.Append(value); + } + + variable.Length = 0; + state = State.OutsideExpression; + break; + default: + variable.Append((char)c); + break; + } + + break; + case State.OnCloseBracket: + c = reader.Read(); + switch (c) + { + case '}': + result.Append('}'); + state = State.OutsideExpression; + break; + default: + throw new FormatException(); + } + + break; + default: + throw new FormatException("Invalid parse state."); + } + } while (state != State.End); + } + + return result.ToString(); + } + private enum State + { + OutsideExpression, + OnOpenBracket, + InsideExpression, + OnCloseBracket, + End + } + +} diff --git a/src/DbApiBuilderEntityGenerator/sample.yaml b/src/DbApiBuilderEntityGenerator/sample.yaml index 65f6735..73181e6 100644 --- a/src/DbApiBuilderEntityGenerator/sample.yaml +++ b/src/DbApiBuilderEntityGenerator/sample.yaml @@ -8,10 +8,12 @@ provider: SqlServer connectionName: "ConnectionStrings:Generator" # the user secret identifier, can be shared with .net core project -userSecretsId: - "984ef0cf-2b22-4fd1-876d-e01499da4c1f" +userSecretsId: "984ef0cf-2b22-4fd1-876d-e01499da4c1f" - # tables to include or empty to include all +# the directory to output the configuration. Default is current. +outputDirectory: .\ + +# tables to include or empty to include all tables: - Priority - Status diff --git a/tests/DbApiBuilderEntityGenerator.Core.Tests/DbApiBuilderEntityGenerator.Core.Tests.csproj b/tests/DbApiBuilderEntityGenerator.Core.Tests/DbApiBuilderEntityGenerator.Core.Tests.csproj new file mode 100644 index 0000000..0359f1e --- /dev/null +++ b/tests/DbApiBuilderEntityGenerator.Core.Tests/DbApiBuilderEntityGenerator.Core.Tests.csproj @@ -0,0 +1,29 @@ + + + + net10.0 + enable + enable + false + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/DbApiBuilderEntityGenerator.Core.Tests/Options/sample.yaml b/tests/DbApiBuilderEntityGenerator.Core.Tests/Options/sample.yaml new file mode 100644 index 0000000..73181e6 --- /dev/null +++ b/tests/DbApiBuilderEntityGenerator.Core.Tests/Options/sample.yaml @@ -0,0 +1,49 @@ +# the connection string to the database +connectionString: "Data Source=(local);Initial Catalog=Tracker;Integrated Security=True" + +# the database provider name. Default:SqlServer +provider: SqlServer + +# config name to read the connection string from the user secrets file +connectionName: "ConnectionStrings:Generator" + +# the user secret identifier, can be shared with .net core project +userSecretsId: "984ef0cf-2b22-4fd1-876d-e01499da4c1f" + +# the directory to output the configuration. Default is current. +outputDirectory: .\ + +# tables to include or empty to include all +tables: + - Priority + - Status + - Task + - User + +# schemas to include or empty to include all +schemas: + - dbo + +# exclude tables or columns +exclude: + # list of expressions for tables to exclude, source is Schema.TableName + tables: + - exact: dbo.SchemaVersions + - regex: dbo\.SchemaVersions$ + # list of expressions for columns to exclude, source is Schema.TableName.ColumnName + columns: + - exact: dbo.SchemaVersions\.Version + - regex: dbo\.SchemaVersions\.Version$ + +# how to generate entity class names from the table name. Preserve|Plural|Singular. Default: Singular +entityNaming: Singular + +# how to generate relationship collections names for the entity. Default: Plural +relationshipNaming: Plural + +# Rename entities and properties with regular expressions. Matched expressions will be removed. +renaming: + entities: + - ^(sp|tbl|udf|vw)_ + properties: + - ^{Table.Name}(?=Id|Name) diff --git a/tests/DbApiBuilderEntityGenerator.Core.Tests/OptionsTests.cs b/tests/DbApiBuilderEntityGenerator.Core.Tests/OptionsTests.cs new file mode 100644 index 0000000..64547e6 --- /dev/null +++ b/tests/DbApiBuilderEntityGenerator.Core.Tests/OptionsTests.cs @@ -0,0 +1,51 @@ +using System.Reflection; +using DbApiBuilderEntityGenerator.Core.Serialization; +using Microsoft.Extensions.Logging.Abstractions; +using Xunit.Abstractions; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace DbApiBuilderEntityGenerator.Core.Tests; + +public class OptionsTests +{ + private readonly ITestOutputHelper _output; + + public OptionsTests(ITestOutputHelper output) + { + _output = output; + } + + [Fact] + public void SaveDefault() + { + var generatorOptions = new GeneratorModel(); + // set user secret values + generatorOptions.UserSecretsId = Guid.NewGuid().ToString(); + generatorOptions.ConnectionName = "ConnectionStrings:Generator"; + + var serializer = new SerializerBuilder() + .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitDefaults) + .WithNamingConvention(CamelCaseNamingConvention.Instance) + .Build(); + + var yaml = serializer.Serialize(generatorOptions); + + _output.WriteLine(yaml); + } + + [Fact] + public void Load() + { + var serializer = new ConfigurationSerializer(NullLogger.Instance); + + var resourcePath = "DbApiBuilderEntityGenerator.Core.Tests.Options.sample.yaml"; + var assembly = Assembly.GetExecutingAssembly(); + + using var stream = assembly.GetManifestResourceStream(resourcePath); + using var reader = new StreamReader(stream); + + var options = serializer.Load(reader); + Assert.NotNull(options); + } +}